Lei de Demeter

Um método f de uma classe C  só deve chamar métodos de:

  • C
  • Um objecto criado por f
  • Um objecto passado como argumento para f
  • Um objecto guardado numa variável de instância de C

Kerberos MIT

Kerberos

Como funciona:

O Kerberos foi desenvolvido a pensar em autenticação e não em autorização.  Podemos comparar o Kerberos a uma espécie de serviço supremo que nos diz: “sim, podes confiar em mim e esta pessoa é quem ela diz que é”.

Kerberos só trabalha a parte da autenticação. Não faz autorização (conceder ou negar acesso a serviços baseados no desejo de o utilizador usá-los) e também não trata a parte de gestão de contas, ou seja, não guarda nenhuma informação relacionada com UIDs, GIDs, home paths.

Sendo Kerberos um protocolo, têm várias implementações, desenvolvidas para vários propósitos:

  • Kerberos MIT: O original. Devido a restrições de exportação da tecnologia de encriptação, foi desenvolvido na Europa o Kerberos Heimdal
  • Kerberos Heimdal:  A versão Suiça do Kerberos. É compatível com o Kerberos MIT. As restrições do Kerberos MIT foram levantadas em 2000 de forma que ambas as implementações coexistem em larga escala.
  • Active Directory:  Não é propriamente uma implementação do Kerberos. É um diretório Microsoft com uma implementação Kerberos e mais alguns serviços associados (LDAP). Não é directamente compatível com o Kerberos MIT ou Heimdal.
  • TrustBroker: Uma implementação comercial do protocolo Kerberos, desenvolvida pela CyberSafe. Suporta uma série de sistemas operativos (Windows, Linux, Unix,…) e oferece interoperabilidade com outras implementações.
  • Shishi: Uma implementação do Kerberos 5 para GNU.
Feita a introdução, vamos avançar para os detalhes de funcionamento. Vamos só falar em Kerberos V5.
Ticket Exchange Service:

A comunicação no Kerberos é construída à volta do protocolo Needham-Shroeder  (NS protocol), desenhado de forma a providenciar um serviço de autenticação seguro e distribuído, através da criptografia de uma chave secreta.
Todas as chaves são secretas, partilhadas pelas extremidades de uma ligação Kerberos. Difere dos sistemas assimétricos onde existe uma chave pública que toda a gente sabe e uma chave privada que permanece num servidor.
Para um utilizador, a chave secreta é a sua password em hash(a password é convertida numa string utilizando uma função de um caminho hash [one way hash function], string essa que passa a ser utilizada como chave), normalmente guardada no Key Distribution Center (KDC).
Para um serviço, a chave é uma sequência gerada aleatoriamente que também é guardada no KDC e num ficheiro chamado keytab na máquina onde se encontra o serviço.
De forma a que este sistema funcione, os clientes e os serviços têm ambos de confiar num terceiro serviço externo, o servidor Kerberos, que seja capaz de providenciar as chaves a pedido.
A comunicação Kerberos é construída à volta de tickets . Os tickets são uma esquema de dados encriptados transmitidos pela rede e guardados no lado do cliente (a forma como são guardados depende do sistema operativo do cliente e de outras configurações). Tradicionalmente, são guardados num ficheiro em /tmp.
A parte central de uma rede baseada no protocolo Kerberos é o Key Distribution Center (KDC). Consiste em três partes:
  • Um Authentication Server que responde a pedidos de autenticação por parte dos clientes. Aqui estamos na fase de AS_REQUEST e AS_REPLY, onde o cliente recebe um Ticket Granting Ticket (TGT)
  • Um Ticket Granting Server, que emite Ticket Granting Service (TGS) para um cliente. Esta é a fase TGS_REQUEST e TGS_REPLY onde o cliente recebe um TGS que vai permiti-lo autenticar-se junto de um serviço acessível na rede.
  • Uma base de dados que guarda todas as chaves secretas (dos clientes e dos serviços) e mais alguma informação relacionada com as contas Kerberos (datas de criação, políticas…)
Normalmente o KDC é uma máquina dedicada apenas a servir serviços Kerberos (por questões de segurança).
As contas Kerberos chamam-se principals (é o equivalente a um username nas contas Unix).
O sistema de criptografia Kerberos é DES e suas variantes como o 3DES
Mecanismo de autenticação – Ticket Granting Tickets :

O mecanismo de autenticação é o primeiro passo a ser executado num ambiente Kerberos. Ele providencia o utilizador com um Ticket Granting Ticket (TGT) que serve de pós- autenticação  para mais tarde aceder a serviços específicos, Single Sign On.
-> Pré-Autenticação
Este método veio colmatar uma falha de segurança no Kerberos 4 e no fundo, este passo implica que o cliente primeiro que tudo tem de se identificar junto do KDC antes de obter um TGT.
-> 1º passo: Authentication Service Request – AS_REQUEST
Esta é a primeira mensagem enviada ao KDC. Contém:
  • O principal nome do cliente
  • O principal Ticket Granting Server (chamado “krbtgt principal”) que vai ser necessário mais tarde para obter o TGS.
  • O timestamp do cliente
  • O tempo de vida do ticket pedido (normalmente entre 8 a 10 horas)
O KDC recebe esta mensagem, verifica se o principal do cliente existe na sua base de dados e, se o timestamp entre a máquina do cliente e do KDC estiver próximo (tipicamente entre 3 a 5 minutos). Esta verificação do timestamp é somente para avisar o cliente, caso haja alguma de-sincronização temporal, antes de avançarem mais no processo de autenticação.
Se a pré-autenticação for obrigatória, o KDC não retorna um TGT. Em vez disso ele envia uma mensagem NEEDED_PREAUTH e pede ao cliente para enviar alguns dados pré-autenticação antes de entregar o TGT. Tradicionalmente o método usado é o PA-ENC-TIMESTAMP, onde o timestamp corrente é encriptado usando a chave do utilizador. conhecida no lado do cliente através da password.
Neste caso, o cliente reenvia uma mensagem AS_REQUEST mas desta vez incluindo o timestamp encriptado. Se a pré-autenticação for bem sucedida, o KDC retorna um TGT. Este é o passo AS_REPLY.
-> 2º passo: Authentication Service Reply – AS_REPLY
Após verificação, o Authentication Server gera uma chave de sessão aleatória (“short term” key). O KDC faz duas cópias: uma é para o cliente e é adicionada à mensagem AS_REPLY; a segunda cópia fica disponível no Ticket Granting Server. Esta última chave é para ser usada em negociações posteriores para outros tickets relacionados com serviços kerberizados.
Então, partindo do princípio que o cliente é autenticado com sucesso, o KDC envia uma mensagem AS_REPLY contendo o Ticket Granting Ticket. Será guardado numa espécie de cache de credenciais para ser usado mais tarde. A mensagem é encriptada com a chave do utilizador.
A mensagem AS_REPLY é formada por duas camadas: a primeira é encriptada com a chave do cliente enquanto que a segunda camada é o próprio TGT, primeiro encriptado com a chave do Ticket Granting Server  e depois reencriptado com a chave do utilizador. Desta forma só o utilizador autenticado é que vai conseguir desencriptar a mensagem para obter o TGT.
O conteúdo da mensagem AS_REPLY é o seguinte:
  • encriptado com a chave do utilizador
    • uma cópia da chave da sessão para o utilizador
    • o tempo de duração do ticket
    • o nome principal do krbtgt
  • primeiro encriptado com a chave do Ticket Granting Server e depois com a chave do utilizador.Este é o TGT
    • uma cópia da chave de sessão
    • o tempo de vida do ticket
    • o timestamp do KDC
    • o principal do cliente
    • o endereço IP do cliente
NOTA: apesar do TGT ser desencriptado e colocado na cache no lado do cliente, o seu conteúdo não pode ser lido no lado do cliente porque o mesmo está encriptado com a chave do Ticket Granting Server que só é conhecida pelo Ticket Granting Server.
Resumindo, podemos representar o mecanismo de autenticação desta forma:
Kerberos V5 Authentication Service - TGT delivery
Mecanismo do Serviço – Ticket Granting Service

Partindo do princípio que o cliente já passou pelo mecanismo de autenticação e já tem o Ticket Granting Ticket (TGT), o que ele quer agora é pedir acesso a um determinado serviço que está kerberizado na rede (pode ser um web server, um file sharing…). Para alcançar este objetivo, o cliente vai pedir um Ticket Granting Service (TGS).
Mais uma vez este pedido é dividido em dois passos: o TGS_REQUEST e o TGS_REPLY. Ambas as mensagens estão encriptadas por motivos de segurança.
-> 1º passo: Ticket Granting Service Request – TGS_REQUEST
Quando um utilizador quer aceder a um serviço kerberizado, primeiro tem de se identificar junto dele. Este pré requisito necessita de uma ligação separada ao Ticket Granting Server: o TGS_REQUEST.
A mensagem enviada pelo cliente é composta por vários elementos:
  • o próprio pedido do TGS, contendo o serviço principal e o tempo de vida pedido.
  • o TGT adquirido anteriormente (após uma autenticação bem sucedida)
  • um autenticador
O autenticador é a mensagem ecnriptada com a chave de sessão adquirida durante o processo AS e contém o principal do utilizador e um timestamp. Assim, o KDC assegura que esta mensagem única vem da pessoa certa: primeiro verificando a chave temporária de sessão negociada anteriormente e segundo através do timestamp que detecta respostas fraudulentas. O objectivo do autenticador é frustar respostas.
Após um pedido válido (um TGT válido e um autenticador correcto), o Ticket Granting Server vai retornar o TGS.
-> 2º passo: Ticket Granting Service Reply – TGS_REPLY
Neste passo, o servidor vai gerar um novo conjunto de chaves de sessão.
A mensagem de resposta do servidor está encriptada com a chave de sessão adquirida através do processo AS, de forma que só o cliente que efetivamente se tinha autenticado anteriormente no KDC é que vai conseguir ler o seu conteúdo e extrair o TGS guardado.
A mensagem forma o TGS_REPLY e contém
  • encriptada com a chave de sessão adquirida durante o processo AS:
    • cópia de uma nova chave de sessão para o utilizador
    • tempo de vida efetivo do ticket
    • nome do serviço principal
  • primeiro encriptado com a chave do serviço de longo termo, depois com a chave de sessão actual. Isto é o TGS:
    • cópia de uma nova chave de sessão, para o serviço
    • tempo de vida efetivo do bilhete
    • KDC timestamp
    • principal do cliente
    • endereço IP do cliente
-> 3º passo: Contactar o serviço
Assim que o cliente tiver obtido o seu TGS, irá usá-lo para se autenticar diretamente junto do serviço requisitado. Este passo depende muito do tipo de serviço que pediu ajuda ao Kerberos de forma que não dá para detalhar muito.
O serviço tem acesso ao seu keytab, um ficheiro que guarda a sua chave de longo termo. Esta chave é que vai permitir ao serviço desencriptar o TGS enviado pelo cliente e obter todas as informações necessárias para criar um contexto de segurança
Esquematicamente,
Kerberos V5 Ticket Granting Service - TGS delivery
Conclusão:
Podemos dividir o protocolo Kerberos em três passos fundamentais:
  1. Processo de autenticação onde o utilizador (e anfitrião) obtém um Ticket Granting Ticket (TGT) como um token de autenticação.
  2. Processo de requisito de um serviço, onde o utilizador obtém um Ticket Granting Service (TGS) para aceder ao serviço.
  3. Acesso ao serviço, onde o utilizador (e anfitrião) utilizam o TGS para se autenticarem e acederem a determinado serviço.
Kerberos V5 Tickets Negociation Mechanism

Problema da Distância Documental

Motivação: Dados dois documentos, quão semelhantes eles são?

  • Idênticos?
  • Modificados ou relacionados (plágio, DNA…)
Como exemplo prático vamos brincar com o velho e o novo Testamento mas antes há-que introduzir algumas definições:
    1. Palavra: sequência de caracteres alfanuméricos (por exemplo, a frase “666.6 é quase o número da besta” tem 8 palavras).
    2. Frequência de palavras: Definimos a frequência de uma palavra , D(w) como o número de vezes que ela ocorre num documento D.
    3. Distância Métrica: A distância métrica entre documentos é definida como o produto interno entre os vectores D1 e D2 que contêm as frequências de todas as palavras nos dois documentos. Matematicamente,
      D1 . D2 = ∑w[D1(w).D2(w)]          (1)
    4. Ângulo Métrico: o ângulo entre os vectores D1 e D2 dá-nos uma indicação da sobreposição dos dois documentos. Matematicamente,
      θ(D1.D2) = arccos(D1.D2 / ||D1|| * ||D2||)          com   0 ≤ θ ≤ π/2. Um ângulo métrico de 0 significa que os dois documentos são idênticos enquanto que um ângulo métrico de π/2 indica-nos que não há palavras em comum.
    5. Número de palavras em um Documento: a magnitude do vector D que contém frequências de palavras de todas as palavras no documento.. Matematicamente, N(D) = ||D|| = √(D.D)
No fundo, o que estamos a fazer aqui é calcular o produto interno entre dois vectores. Para uma melhor compreensão, ver Dot Product
Uma boa fonte para livros em formato txt é Gutenberg
Resumindo, para calcular a distância entre dois documentos,
     Para cada um dos dois ficheiros:
          - Ler o ficheiro
          - Fazer uma lista de palavras
          - Contar as frequências
          - Ordenar
     Uma vez obtidos os vectores D1 e D2:
          Calcular o ângulo θ
Vamos ver o código:
/**
 * @author Manuel Leiria
 *
 */
public class DocumentMatcher {
	private Map<String, Integer> mapWordFrequencyDoc1 = 
             new TreeMap<String, Integer>();
	private Map<String, Integer> mapWordFrequencyDoc2 = 
             new TreeMap<String, Integer>();

	/**
	 * @return the mapWordFrequencyDoc1
	 */
	public Map<String, Integer> getMapWordFrequencyDoc1() {
		return Collections.unmodifiableMap(mapWordFrequencyDoc1);
	}
	/**
	 * @return the mapWordFrequencyDoc2
	 */
	public Map<String, Integer> getMapWordFrequencyDoc2() {
		return Collections.unmodifiableMap(mapWordFrequencyDoc2);
	}
	/**
	 *
	 * @param file1
	 * @param file2
	 */
	public DocumentMatcher(final String file1, final String file2) {
		buildFrequencyMap(file1, mapWordFrequencyDoc1);
		buildFrequencyMap(file2, mapWordFrequencyDoc2);
		synchronizeWordMaps();
	}
	/**
	 * Constroi um Mapa na forma [palavra, num. de vezes que
         *  ela ocorre no documento]
	 * @param file
	 * @param mapWordFrequency
	 */
	private void buildFrequencyMap
             (final String file, Map<String, Integer> mapWordFrequency){
		try{
			Scanner input = new Scanner(new File(file));
			while(input.hasNextLine()){
			    final String line = input.nextLine();
			        StringTokenizer parser = 
                                 new StringTokenizer(line, ",.;:()-!?' ");
			    while(parser.hasMoreTokens()){
			        final String word = 
                                    parser.nextToken().toUpperCase();

				Integer listing = mapWordFrequency.get(word);
				if(listing == null){
				    listing = 1;
				}else{
				    listing += 1;
				}
				mapWordFrequency.put(word, listing);
			    }
			}
		input.close();
		}catch(IOException e){
			System.out.println(e);
		}
	}
	/**
	 * Adiciona as palavras que nao existem nos respectivos mapas
	 */
	private void synchronizeWordMaps(){
		for(final Map.Entry<String, Integer> entry : 
                  mapWordFrequencyDoc1.entrySet()){
		    if(!mapWordFrequencyDoc2.containsKey(entry.getKey())){
		      mapWordFrequencyDoc2.put(entry.getKey(), 0);
		    }
		}
		for(final Map.Entry<String, Integer> entry : 
                  mapWordFrequencyDoc2.entrySet()){
		    if(!mapWordFrequencyDoc1.containsKey(entry.getKey())){
		      mapWordFrequencyDoc1.put(entry.getKey(), 0);
		    }
		}
	}
	/**
	 *
	 * @return o angulo theta entre os dois documentos
	 * Um theta = 0 => documetos identicos
	 * Um theta = pi/2 => Nao ha palavras em comum
	 */
	public double getTheta(){
		double [] frequency1 = 
                  new double[mapWordFrequencyDoc1.size()];
		double [] frequency2 = 
                  new double[mapWordFrequencyDoc2.size()];
		int cnt = 0;
		for(final Map.Entry<String, Integer> entry : 
                  mapWordFrequencyDoc1.entrySet()){
			frequency1[cnt] = entry.getValue();
			cnt++;
		}
		cnt = 0;
		for(final Map.Entry<String, Integer> entry : 
                  mapWordFrequencyDoc2.entrySet()){
			frequency2[cnt] = entry.getValue();
			cnt++;
		}
		double theta = 0;
		for(int i=0,n=frequency1.length;i<n;i++){
			theta += frequency1[i]*frequency2[i];
		}
		return Math.acos(theta / 
                (getNorm(mapWordFrequencyDoc1) * 
                  getNorm(mapWordFrequencyDoc2)));
	}
	/**
	 * Calcula a norma de D(w)
	 * @param vector
	 * @return ||D(w)||
	 */
	private double getNorm(Map<String, Integer> vector ){
		double norm = 0.0;
		for(final Map.Entry<String, Integer> entry : 
                  vector.entrySet()){
			norm += Math.pow(entry.getValue(), 2);
		}
		return Math.sqrt(norm);
	}
	/**
	 * Escreve o resultado para um ficheiro
	 * @param file
	 * @param mapWordFrequency
	 */
	public void writeString(final String file, 
          final Map<String, Integer> mapWordFrequency){
		try {
			final PrintWriter output = new PrintWriter(file);
			for(final Map.Entry<String, Integer> entry : 
                          mapWordFrequency.entrySet()){
				output.println(entry);
			}
			output.close();
		} catch (FileNotFoundException e) {
			System.out.println(e);
		}
	}
}
public class TestDocumentMatcher {

	public static final String PATH =
		"/home/manuel/Documentos/Books/";
	public static final String IN_FILE_1 =
		"New_Testament.txt";
	public static final String IN_FILE_2 =
		"Old_Testament.txt";

	public static final String OUT_FILE_1 =
		"New_Testament.out";
	public static final String OUT_FILE_2 =
		"Old_Testament.out";
	/**
	 * @param args
	 */
	public static void main(String[] args) {
		long initTime = Calendar.getInstance().getTimeInMillis();
		DocumentMatcher dc =
			new DocumentMatcher(PATH + IN_FILE_1, 
                          PATH + IN_FILE_2);

		dc.writeString(PATH + OUT_FILE_1, 
                  dc.getMapWordFrequencyDoc1());
		dc.writeString(PATH + OUT_FILE_2, 
                  dc.getMapWordFrequencyDoc2());

		System.out.println("THETA-->" + dc.getTheta());
		System.out.println("Pi/2-->" + Math.PI/2);
		long elapsedTime =
			Calendar.getInstance().
                          getTimeInMillis() - initTime;
		System.out.println("TIME ELAPSED-->" + 
                  elapsedTime/1000 + " seconds");
	}
}
THETA-->0.2850240436473282
Pi/2-->1.5707963267948966
TIME ELAPSED-->1 seconds

1 – Probabilidade e Valor Expectável no Poker Holdem

Vamos começar pelos conceitos mais básicos e a partir daí construir as fundações da teoria matemática para (tentar) analisar um jogo de poker na variante Holdem. Vão ser muitos posts :-)

A ideia vai ser apresentar a  teoria e de seguida concretizar com um exemplo de uma situação real num jogo de poker.

Probabilidade

Se n tentativas de um experimento (tal como dar uma mão de holdem) produzir n0 ocorrências de  um evento x, definimos a probabilidade p de x ocorrer p(x) como

p(x) = lim(n→∞) n0/n

Comecemos com uma pergunta muito simples: Qual é a probabilidade de sair um ás?

Temos 4 ases num baralho de 52 cartas, ou seja, 4 chances em 52: p(A)=4/52=1/13. Note-se que estamos a somar as probabilidades individuais (a probabilidade de sair um ás mais a probabilidade de sair outro ás…) porque são mutuamente exclusivas, i.e., uma carta não pode ser ao mesmo tempo um ás de copas e um ás de espadas.

Eventos Independentes

Consideremos agora os seguintes eventos:

  • A carta é uma copa : p(H) = 13/52=1/4 (há 13 copas possíveis num baralho de 52 cartas)
  • A carta é um ás: p(A) = 4/52=1/13 (há 4 ases possíveis num baralho de 52 cartas)

mas neste caso, se quisermos calcular a probabilidade de sair um ás ou uma copa, não podemos adicionar como anteriormente porque pode sair uma carta que seja um ás e uma copa (um ás de copas).

Podemos então lançar a ideia de eventos independentes: se a probabilidade de dois eventos ocorrerem for igual ao produto das probabilidades individuais => os eventos são independentes. Neste caso, a probabilidade de A e B ocorrerem chama-se probabilidade conjunta.

A probabilidade conjunta de uma carta ser uma copa e um ás é 1/4 * 1/13

Eventos Dependentes

Para introduzir este conceito, consideremos os seguintes eventos:

  • B: a primeira carta a sair é um ás
  • A: a segunda carta a sair é um ás

e façamos a seguinte pergunta: qual a probabilidade de A acontecer sabendo que B aconteceu (ou seja, qual é a probabilidade de sair um ás na segunda carta sabendo que já saiu um ás na primeira carta)?. A isto chama-se probabilidade condicional. É fácil ver que os eventos são independentes se a probabilidade condicional de A dado B for igual à probabilidade de A (o fato de B ter acontecido não influencia em nada o A).

Vamos utilizar a seguinte notação:

  • p(A U B) = Probabilidade de A ou B ocorrerem
  • p(A ∩ B) = Probabilidade de A e B ocorrerem
  • p(A|B) = Probabilidade de A ocorrer dado que B já ocorreu

Eventos mutuamente exclusivos:

  • p(A B) = p(A) + p(B)          (1.1)

Eventos independentes

  • p(A ∩ B) = p(A)p(B)          (1.2)

Para todos os eventos

  • p(A B) = p(A) + p(B) - p(A ∩ B)          (1.3)

Para eventos dependentes

  • p(A ∩ B) = p(A)p(B|A)          (1.4)

Podemos ver que a eq.(1.1) é um caso especial da eq.(1.3).

Podemos ver que a eq.(1.2) é um caso especial da eq(1.4). Facilmente concluímos que, para eventos independentes, p(B|A) = p(B).

Com os dois eventos definidos anteriormente, façamos a seguinte pergunta: com que frequência uma mão de holdem contém dois ases?

p(A) = 1/13

p(B) = 1/13

no entanto os eventos são dependentes! Se

p(A ∩ B) = p(A)p(B|A) = 1/13 * 1/17 = 1/221

Antes de avançarmos com uma pergunta bem mais interessante, vamos expor algumas propriedades das probabilidades (para ver algo mais formal sobre este assunto, consultar por exemplo Probability). Definimos C como o evento certo e I o evento impossível. Então,

  • 0 p(A) ≤ 1 qualquer que seja o evento A
  • p(C) = 1
  • p(I) = 0
  • p(A) + p(‾A) = 1

Já temos material suficiente para conseguirmos responder à seguinte questão: Tendo uma mão do mesmo naipe, qual a probabilidade de sair um flush no flop?

Com duas cartas no mesmo naipe na mão, sobram 11 cartas do mesmo naipe no baralho. Consideremos os 3 eventos:

  • A: a primeira carta do flop ser uma carta de flush
  • B: a segunda carta do flop ser uma carta de flush, sabendo que a primeira carta é de flush
  • C: a terceira carta do flop ser uma carta de flush sabendo que as duas anteriores são cartas de flush

Na notação definida anteriormente, o que queremos calcular é a p(A ∩ B ∩ C). Temos os seguintes dados:

  • p(A) = 11/50 (há 11 cartas possíveis do mesmo naipe que as que temos na mão em 50 cartas)
  • p(B\A) = 10/49 (como já sairam 3 cartas do mesmo naipe, sobram 10 cartas em 49 possíveis)
  • p(C|(A ∩ B) = 9/48 (há 9 cartas possíveis em 48)

Da eq.(1.4), p(A ∩ B) = p(A)p(B|A) = 11/50 x 10/49 = 11/245

Definimos D = A ∩ B. Então, p(D ∩ C) = p(D)p(C\D)

Reunindo  tudo, p(A ∩ B ∩ C) = p(C|(A ∩ B) p(∩ B) = 9/48 * 11/245 = 33/3920 (um pouco menos de 1%)

Distribuições de Probabilidades

Quando uma distribuição de probabilidades tem valores numéricos associados às possíveis saídas, podemos encontrar o valor expectável (EV) dessa distribuição, que é o valor de cada saída multiplicada pela sua probabilidade, tudo somado no fim. De outra forma, podemos dizer que para uma distribuição de probabilidades P onde cada uma das n saídas tem um valor xi e uma probabilidade pi, então o valor expectável de P é

<P> = ∑ pi * xi

e esta é uma ideia central do poker: para ganhar é necessário estar constantemente a maximizar o valor expectável.

Há uma propriedade importante do valor expectável: O EV é aditivo, i.e., de n apostas diferentes em linha, o EV é igual à soma dos EVs individuais para cada aposta.


Quatro gatinhos

Sr. Félix: Querida, quantos gatinhos tivemos na última ninhada?
Sra. Félix: Quatro.

Sr. Félix:e quantos machos.
Sra. Félix: ainda não sei.

Sr. Félix: é pouco provável que sejam todos machos.
Sra. Félix: tão-pouco provável que sejam todos fêmeas

Sr. Félix: Não é assim tão difícil de calcular. Há 50% de probabilidades de cada gatinho ser macho ou fêmea. Por isso, o resultado mais provável são dois machos e duas fêmeas.

Será que o pensamento do Sr. Félix está correcto??
Analisemos as combinações possíveis:

M M M M
M M M F
M M F M
M M F F
M F M M
M F M F
M F F M
M F F F
F M M M
F M M F
F M F M
F M F F
F F M M
F F M F
F F F M
F F F F

Só dois em dezasseis casos é que são todos do mesmo sexo. Então, a probabilidade disso acontecer será 2/16 = 1/8. É muito pouco provável que sejam todos do mesmo sexo.

Analisemos a hipótese de dois machos e duas fêmeas. Ocorre 6 vezes, ou seja a probabilidade é de 6/16 = 3/8. É, sem dúvida superior a 1/8.

Então, até agora, podemos dizer que é mais provável o casal ter dois machos e duas fêmeas do que quatro machos ou quatro fêmeas.
Mas a parte mais interessante é esta: consideremos 3 de um sexo e um de outro. Esta possibilidade ocorre em oito casos e a respectiva probabilidade é 8/16=1/2 o que é claramente superior à do caso 2-2

Quase toda a gente se surpreende com o facto de em famílias com quatro filhos ser mais provável haver três do mesmo sexo e um do outro do que dois de cada sexo.


Calculadora RPN

Uma expressão aritmética está em notação postfix (também chamada reverse polish notation ou RPN)se cada operador for colocado depois dos seus operandos. Por exemplo 3*(4+5) fica 3 4 5 + *. A notação 3*(4+5) chama-se infix. As expressões quando estão em notação postfix, são mais fáceis de serem processadas pelos computadores do que as expressões infix.
A seguinte classe processa expressões rpn e efectua as respectivas operações aritméticas. O centro nevrálgico do programa é o interface Deque e a classe ArrayDeque. Um deque é uma queue com duas saídas, ou seja, é uma estrutura linear que permite inserções e remoções só nos dois extremos (Deque).
Então, temos:

/**
 * Esta classe faz o parsing de expressões em postfix
 * (reverse polish notation)
 * e efectua as respectivas operações aritméticas
 *
 * @author Manuel
 *
 */
public class RPNCalculator {

	private static Deque stack =
		new ArrayDeque();
	/**
	 *
	 * @param input
	 */
	public static void calculate(String input) {
		char ch = input.charAt(0);
		if (ch == '+' || ch == '-' || ch == '*'
			|| ch == '/') {
			double y =
			  Double.parseDouble(
                                          stack.pop());
			double x =
			  Double.parseDouble(
			    stack.pop());
			double z = 0.0;
			switch (ch) {
			case '+':
				z = x + y;
				break;
			case '-':
				z = x - y;
				break;
			case '*':
				z = x * y;
				break;
			case '/':
				z = x / y;
				break;
			}
			System.out.printf
			  ("\t%.2f %c %.2f = %.2f%n",
			    x, ch, y, z);
			stack.push(new Double(z).toString());
		} else {
			stack.push(input);
		}
	}

	public static void main(String[] args) {
		Scanner in = new Scanner(System.in);
		while (true) {
			String input = in.nextLine();
			calculate(input);
			if(input.equalsIgnoreCase("q"))
				break;
		}
	}
}

A parte chata é que para fazer uma qualquer operação, temo primeiro que converter a expresão para a sua correspondente versão RPN. Segue um programa que faz isso mesmo:

public class ConvertInfixToPostfix {

	public static void main(String[] args){

		Deque stack =
			new ArrayDeque();
		String line =
			new Scanner(System.in).nextLine();
		System.out.println(line);
		Scanner scanner = new Scanner(line);
		while(scanner.hasNext()){
			if(scanner.hasNextInt()){
				System.out.print(
				  scanner.nextInt() + " ");
			}else{
				String str = scanner.next();
				if("+-*/".indexOf(str) >= 0){
					stack.push(str);
				}else if(str.equals(")")){
				  System.out.print(
				    stack.pop() + " ");
				}
			}
		}
		while(!stack.isEmpty()){
			System.out.print(stack.pop() + " ");
		}
		System.out.println();
	}
}

Dicas: pesquisa binária e pesquisa sequêncial

/**
 * Pesquisa um numero x num array ordenado.É
 * muito mais eficiente do que a pesquisa sequencial
 * mas requer que os elementos do array estejam ordenados.
 * Ele repetidamente divide a sequência em dois e em cada
 * vez restringe a pesquisa à metade que contém o elemento.
 *
 * Este algoritmo corre no tempo O(log(n)) o que quer dizer que
 * em média, o tempo de execução é proporcional ao logaritmo
 * do número de elementos no array.
 *
 *
 * @return o indice do numero x ou -1 se não encontrou
 */
public int binarySearch(int[] a, int x){

	int low = 0;
	int high = a.length;

	while(low < high){
		int i = (low + high) / 2;
		if(a[i] == x){
			return i;
		}else if(a[i] < x){
			low = i + 1;
		}else{
			high = i;
		}
	}
	return -1;
}
/**
 * Pesquisa sequencial (ou pesquisa linear) é o algoritmo
 * de pesquisa mais simples mas também é o menos eficiente.
 * Ele examina cada elemento sequencialmente começando
 * pelo primeiro elemento até chegar ao fim do array(se
 * eu estiver à procura de alguém num combóio, utilizo a
 * pesquisa sequencial)
 *
 * Este algoritmo corre no tempo O(n) o que quer dizer que
 * em média, o tempo de execução é proporcional ao número
 * de elementos no array.
 *
 * @return o indice do elemento a pesquisar ou 0 se não
 * o encontrar
 */
public int sequentialSearch(int[] a, int x){
	for(int i = 0, n = a.length; i < n; i++){
		if(a[i] == x){
			return i;
		}
	}
	return -1;
}
/**
 * O algoritmo de binarysearch pode encontrar elementos rapidamente
 * num array que já está ordenado o que sugere que devamos ter os
 * elementos ordenados no array.No entanto, inserir novos elementos
 * num array ordenado é difícil pois temos de transladar os elementos
 * maiores para arranjar espaço para o novo elemento.Podemos faze-lo
 * assim:
 * @param a o array
 * @param n o numero de elementos que já estão ordenados no array
 * @param x o elemento a ser inserido no meio dos elementos
 */
void insert(int[] a, int n, int x){
  /**PreCondicao: a[0] <= ... <= a[n-1], e n < a.length */
  /**PosCondicao: a[0] <= ... <= a[n], e x está entre eles */
  int i = 0;
  while(i < n && a[i] <= x){
  	i++;
  }
  System.arraycopy(a, i, a, i+1, n-i);
  a[i] = x;
}

Brincadeiras matemáticas 2

Este também é interessante:

Provar que qualquer número a é igual a um número menor b:
Sejam a, b, c quaisquer números tais que

a = b + c

então, multiplicando os dois lados por a – b, obtemos

a (a – b) = (b + c)(a – b)
a^2 -ab = ba – b^2 + ca – cb

passemos ac para o lado esquerdo,

a^2 – ab -ac = ab – b^2 – bc

factorizar,

a(a – b – c) = b(a – b – c)

dividindo ambos os lados por a – b – c obtemos

a = b

e esta?


Brincadeiras matemáticas

Eu tenho dois filhos e pelo menos um deles é rapaz.Qual é a probabilidade do outro ser rapaz?


Singleton: duas abordagens

Um Singleton é uma classe que só pode ser instanciada exactamente uma vez.
Temos duas abordagens possíveis. Em ambas mantemos o construtor privado e providenciamos membros to tipo public static de forma a que os clientes
possam aceder a uma e uma só instância da classe.

  1. O membro public static é uma variável do tipo final:

    //Singleton com uma variável final
    public class A {
    public static final A INSTANCE = new A();
    private A() {}
    }

    O construtor privado é chamado uma única vez para inicializar a variável A.INSTANCE que é do tipo public static final.
  2. Providenciamos um método do tipo public static em vez da variável.

    // Singleton with static factory
    public class A {
    private static final A INSTANCE = new A();
    private A() {}
    public static A getInstance() {
    return INSTANCE;
    }
    }

    Todas as chamadas ao método estático a.getInstance()
    retornam uma referência ao mesmo objecto e não é possível criar mais nenhuma
    instância de A

A primeira abordagem deverá ser usada se tivermos a certeza absoluta que a classe
A será um Singleton para toda a vida enquanto que
a segunda abordagem dá-nos alguma flexibilidade no código, no sentido em que
agora a classe é do tipo Singleton mas mais tarde poderemos alterar esta
propriedade sem termos que alterar a API.

Uma nota sobre a serialização de Singletons:para tornarmos uma classe
singleton serializable, não é condição suficiente implementar o
interface Serializable á declaração da classe, i.e.,
public class A implements Serializable, temos de
implementar o método readResolve() caso contrário,
cada deserialização da instância irá resultar na criação de uma nova instância.
Para prevenir isto, deveremos adicionar o seguinte método à classe
A:

// metodo readResolve para preservar a propriedade singleton
private Object readResolve() throws ObjectStreamException {
/*
* Retorna a unica classe A e deixa o garbage colector
* tratar do resto
*/
return INSTANCE;
}


Seguir

Get every new post delivered to your Inbox.