Como você substitui corretamente isEqual:
no Objective-C? A "captura" parece ser que, se dois objetos forem iguais (conforme determinado pelo isEqual:
método), eles deverão ter o mesmo valor de hash.
A seção Introspecção do Guia de fundamentos do cacau tem um exemplo de como substituir isEqual:
, copiado da seguinte forma, para uma classe chamada MyWidget
:
- (BOOL)isEqual:(id)other {
if (other == self)
return YES;
if (!other || ![other isKindOfClass:[self class]])
return NO;
return [self isEqualToWidget:other];
}
- (BOOL)isEqualToWidget:(MyWidget *)aWidget {
if (self == aWidget)
return YES;
if (![(id)[self name] isEqual:[aWidget name]])
return NO;
if (![[self data] isEqualToData:[aWidget data]])
return NO;
return YES;
}
Ele verifica a igualdade do ponteiro, depois a igualdade de classe e, finalmente, compara os objetos usando isEqualToWidget:
, que apenas verifica as propriedades name
e data
. O que o exemplo não mostra é como substituir hash
.
Vamos supor que existem outras propriedades que não afetam a igualdade, digamos age
. O hash
método não deve ser substituído de maneira que apenas name
e data
afete o hash? E se sim, como você faria isso? Basta adicionar os hashes de name
e data
? Por exemplo:
- (NSUInteger)hash {
NSUInteger hash = 0;
hash += [[self name] hash];
hash += [[self data] hash];
return hash;
}
Isso é suficiente? Existe uma técnica melhor? E se você tiver primitivos, como int
? Converta-os para NSNumber
obter seu hash? Ou estruturas como NSRect
?
( Peido do cérebro : originalmente escrevia "OR bit a bit" junto com |=
.
fonte
if (![other isKindOfClass:[self class]])
- Tecnicamente, isso significa que a igualdade não será comutativa. Ou seja, A = B não significa B = A (por exemplo, se uma é uma subclasse da outra)Respostas:
Começar com
Então, para cada primitivo que você fizer
Para objetos, você usa 0 para zero e, caso contrário, seu código de hash.
Para booleanos, você usa dois valores diferentes
Explicação e atribuição
Este não é o trabalho de tcurdt, e os comentários estavam pedindo mais explicações, por isso acredito que uma edição para atribuição é justa.
Esse algoritmo foi popularizado no livro "Effective Java", e o capítulo relevante pode ser encontrado online aqui . Esse livro popularizou o algoritmo, que agora é o padrão em vários aplicativos Java (incluindo o Eclipse). Derivou, no entanto, de uma implementação ainda mais antiga, atribuída de várias formas a Dan Bernstein ou Chris Torek. Esse algoritmo antigo flutuava originalmente na Usenet, e certas atribuições são difíceis. Por exemplo, há alguns comentários interessantes neste código do Apache (procure por seus nomes) que referenciam a fonte original.
Resumindo, este é um algoritmo de hash muito antigo e simples. Não é o mais eficiente e nem mesmo é comprovado matematicamente como um algoritmo "bom". Mas é simples e muitas pessoas o usam há muito tempo com bons resultados, por isso tem muito apoio histórico.
fonte
Estou pegando o Objective-C, então não posso falar especificamente para esse idioma, mas nos outros idiomas que uso se duas instâncias são "Iguais", elas devem retornar o mesmo hash - caso contrário, você terá todos tipos de problemas ao tentar usá-los como chaves em uma hashtable (ou qualquer coleção do tipo dicionário).
Por outro lado, se 2 instâncias não forem iguais, elas podem ou não ter o mesmo hash - é melhor se não tiverem. Essa é a diferença entre uma pesquisa O (1) em uma tabela de hash e uma pesquisa O (N) - se todos os seus hashes colidirem, você poderá achar que pesquisar sua tabela não é melhor do que pesquisar uma lista.
Em termos de práticas recomendadas, seu hash deve retornar uma distribuição aleatória de valores para sua entrada. Isso significa que, por exemplo, se você tem um dobro, mas a maioria dos seus valores tende a se agrupar entre 0 e 100, é necessário garantir que os hashes retornados por esses valores sejam distribuídos uniformemente por todo o intervalo possível de valores possíveis de hash . Isso melhorará significativamente seu desempenho.
Existem vários algoritmos de hash por aí, incluindo vários listados aqui. Eu tento evitar a criação de novos algoritmos de hash, pois podem ter grandes implicações no desempenho. Portanto, usar os métodos de hash existentes e fazer uma combinação bit a bit de algum tipo, como no exemplo, é uma boa maneira de evitá-lo.
fonte
Por exemplo:
Solução encontrada em http://nshipster.com/equality/ por Mattt Thompson (que também fez referência a essa pergunta em seu post!)
fonte
Achei este tópico extremamente útil, fornecendo tudo o que eu precisava para obter meus métodos
isEqual:
ehash
implementados com uma captura. Ao testar variáveis de instância de objeto noisEqual:
código de exemplo, use:Isso repetidamente falhou ( ou seja , retornou NÃO ) sem erro, quando eu sabia que os objetos eram idênticos no meu teste de unidade. O motivo foi que uma das
NSString
variáveis de instância era nula; portanto, a instrução acima foi:e como nada responderá a qualquer método, isso é perfeitamente legal, mas
retorna nulo , que é NÃO , portanto, quando o objeto e o objeto em teste tivessem um objeto nulo, eles seriam considerados diferentes ( ou seja ,
isEqual:
retornariam NÃO ).Essa correção simples foi alterar a instrução if para:
Dessa forma, se os endereços forem os mesmos, a chamada do método será ignorada, independentemente de serem nulas ou ambas apontando para o mesmo objeto, mas se elas não forem nulas ou se apontarem para objetos diferentes, o comparador será chamado apropriadamente.
Espero que isso poupe a alguém alguns minutos de coçar a cabeça.
fonte
A função hash deve criar um valor semi-exclusivo que provavelmente não colidirá ou corresponderá ao valor de hash de outro objeto.
Aqui está a função hash completa, que pode ser adaptada às variáveis de instância de suas classes. Ele usa o NSUInteger em vez de int para compatibilidade em aplicativos de 64/32 bits.
Se o resultado se tornar 0 para objetos diferentes, você corre o risco de colidir hashes. A colisão de hashes pode resultar em um comportamento inesperado do programa ao trabalhar com algumas das classes de coleção que dependem da função de hash. Certifique-se de testar sua função de hash antes de usar.
fonte
result = prime * result + [self isSelected] ? yesPrime : noPrime;
. Descobri que isso estava sendo definidoresult
como (por exemplo)1231
, presumo que o?
operador tenha precedência. Corrigi o problema adicionando colchetes:result = prime * result + ([self isSelected] ? yesPrime : noPrime);
A maneira mais fácil, porém ineficiente, é retornar o mesmo
-hash
valor para cada instância. Caso contrário, sim, você deve implementar o hash baseado apenas em objetos que afetam a igualdade. Isso é complicado se você usar comparações relaxadas em-isEqual:
(por exemplo, comparações de strings que não diferenciam maiúsculas de minúsculas). Para ints, geralmente você pode usar o próprio int, a menos que esteja comparando com os NSNumbers.Não use | =, porém, ele irá saturar. Use ^ = em vez disso.
Curiosidade aleatória:,
[[NSNumber numberWithInt:0] isEqual:[NSNumber numberWithBool:NO]]
mas[[NSNumber numberWithInt:0] hash] != [[NSNumber numberWithBool:NO] hash]
. (rdar: // 4538282, aberto desde 05 de maio de 2006)fonte
Lembre-se de que você só precisa fornecer um hash igual quando
isEqual
verdadeiro. QuandoisEqual
é falso, o hash não precisa ser desigual, embora presumivelmente seja. Conseqüentemente:Mantenha o hash simples. Escolha uma variável de membro (ou poucos membros) que seja a mais distinta.
Por exemplo, para CLPlacemark, apenas o nome é suficiente. Sim, existem 2 ou 3 distintos CLPlacemark com exatamente o mesmo nome, mas esses são raros. Use esse hash.
...
Observe que não me preocupo em especificar a cidade, o país etc. O nome é suficiente. Talvez o nome e CLLocation.
O hash deve ser distribuído uniformemente. Então você pode combinar a variável de vários membros usando o sinal de intercalação ^ (sinal xor)
Então é algo como
Dessa forma, o hash será distribuído igualmente.
Então, o que fazer na matriz?
Mais uma vez, simples. Você não precisa fazer o hash de todos os membros da matriz. O suficiente para fazer o hash do primeiro elemento, último elemento, a contagem, talvez alguns elementos do meio, e é isso.
fonte
Espere, certamente uma maneira muito mais fácil de fazer isso é substituir primeiro
- (NSString )description
e fornecer uma representação em cadeia do estado do seu objeto (você deve representar todo o estado do seu objeto nessa cadeia).Em seguida, forneça a seguinte implementação de
hash
:Isso se baseia no princípio de que "se dois objetos de seqüência de caracteres forem iguais (conforme determinado pelo método isEqualToString:), eles deverão ter o mesmo valor de hash".
Fonte: Referência da Classe NSString
fonte
description
, não vejo por que isso é inferior a qualquer uma das soluções mais votadas. Pode não ser a solução mais matematicamente elegante, mas deve fazer o truque. Como afirma Brian B. (resposta mais votada neste momento): "Tento evitar a criação de novos algoritmos de hash" - concordou! - Eu apenashash
oNSString
!description
inclui o endereço do ponteiro. Portanto, isso cria duas instâncias diferentes da mesma classe iguais a hash diferente, que violam a suposição básica de que dois objetos iguais têm o mesmo hash!Os contratos iguais e hash são bem especificados e pesquisados exaustivamente no mundo Java (consulte a resposta de @ mipardi), mas todas as mesmas considerações devem se aplicar ao Objective-C.
O Eclipse faz um trabalho confiável para gerar esses métodos em Java, então aqui está um exemplo do Eclipse portado manualmente para o Objective-C:
E para uma subclasse
YourWidget
que adiciona uma propriedadeserialNo
:Esta implementação evita algumas armadilhas de subclassificação no exemplo
isEqual:
da Apple:other isKindOfClass:[self class]
é assimétrico para duas subclasses diferentes deMyWidget
. A igualdade precisa ser simétrica: a = b se e somente se b = a. Isso poderia ser facilmente corrigido alterando o teste paraother isKindOfClass:[MyWidget class]
, então todas asMyWidget
subclasses seriam mutuamente comparáveis.isKindOfClass:
teste de subclasse impede que as subclasses sejam substituídasisEqual:
por um teste de igualdade refinado. Isso ocorre porque a igualdade precisa ser transitiva: se a = be a = c, então b = c. Se umaMyWidget
instância for comparada a duasYourWidget
instâncias, essasYourWidget
instâncias deverão ser comparadas entre si, mesmo que sejamserialNo
diferentes.A segunda questão pode ser resolvida considerando-se que os objetos são iguais apenas se pertencerem exatamente à mesma classe, daí o
[self class] != [object class]
teste aqui. Para classes de aplicativos típicas , essa parece ser a melhor abordagem.No entanto, certamente há casos em que o
isKindOfClass:
teste é preferível. Isso é mais típico das classes de estrutura do que as classes de aplicativos. Por exemplo, qualquer umNSString
deve comparar igual a qualquer outroNSString
com a mesma sequência de caracteres subjacente, independentemente da distinçãoNSString
/NSMutableString
, e também independentemente de quais classes privadas noNSString
cluster de classes estão envolvidas.Nesses casos,
isEqual:
deve ter um comportamento bem definido e bem documentado, e deve ficar claro que as subclasses não podem substituir isso. Em Java, a restrição 'no override' pode ser imposta sinalizando os métodos equals e hashcode comofinal
, mas o Objective-C não tem equivalente.fonte
MyWidget
se entende por não ser um cluster de classes.Isso não responde diretamente à sua pergunta (de forma alguma), mas eu usei o MurmurHash antes para gerar hashes: murmurhash
Acho que devo explicar o porquê: murmurhash é muito rápido ...
fonte
Eu achei esta página um guia útil para substituir métodos de tipo igual e hash. Ele inclui um algoritmo decente para o cálculo de códigos de hash. A página é voltada para Java, mas é muito fácil adaptá-la ao Objective-C / Cocoa.
fonte
Também sou novato no Objetivo C, mas encontrei um excelente artigo sobre identidade versus igualdade no Objetivo C aqui . Pela minha leitura, parece que você pode manter a função hash padrão (que deve fornecer uma identidade única) e implementar o método isEqual para comparar valores de dados.
fonte
Equality vs Identity
de Karl Kraft é realmente bom.isEqual:
, também deverá substituirhash
.Quinn está errado ao dizer que a referência ao hash do sopro é inútil aqui. Quinn está certo de que deseja entender a teoria por trás do hash. O murmúrio destila grande parte dessa teoria em uma implementação. Vale a pena descobrir como aplicar essa implementação a esse aplicativo específico.
Alguns dos principais pontos aqui:
A função de exemplo de tcurdt sugere que '31' é um bom multiplicador porque é primo. É preciso mostrar que ser primo é uma condição necessária e suficiente. De fato, 31 (e 7) provavelmente não são primos particularmente bons porque 31 == -1% 32. Um multiplicador ímpar com cerca de metade dos bits definidos e metade dos bits limpos provavelmente será melhor. (A constante de multiplicação de hash de sopro possui essa propriedade.)
Esse tipo de função hash provavelmente seria mais forte se, após a multiplicação, o valor do resultado fosse ajustado por meio de um deslocamento e xor. A multiplicação tende a produzir os resultados de muitas interações de bits na extremidade superior do registro e resultados de baixa interação na extremidade inferior do registro. O deslocamento e xor aumentam as interações na extremidade inferior do registro.
Definir o resultado inicial como um valor em que cerca de metade dos bits são zero e cerca de metade dos bits são um também tenderia a ser útil.
Pode ser útil ter cuidado com a ordem em que os elementos são combinados. Provavelmente, deve-se primeiro processar booleanos e outros elementos em que os valores não sejam fortemente distribuídos.
Pode ser útil adicionar alguns estágios de embaralhamento de bits extras no final do cálculo.
Se o hash do sopro é realmente rápido ou não para este aplicativo é uma questão em aberto. O hash do sopro pré-mistura os bits de cada palavra de entrada. Várias palavras de entrada podem ser processadas em paralelo, o que ajuda os cpus em pipeline com vários problemas.
fonte
Combinando a resposta do @ tcurdt com a resposta do @ oscar-gomez para obter nomes de propriedades , podemos criar uma solução fácil para o isEqual e o hash:
Agora, em sua classe personalizada, você pode implementar facilmente
isEqual:
ehash
:fonte
Observe que, se você estiver criando um objeto que pode ser alterado após a criação, o valor do hash não será alterado se o objeto for inserido em uma coleção. Na prática, isso significa que o valor do hash deve ser corrigido a partir do ponto de criação do objeto inicial. Consulte a documentação da Apple no método -hash do protocolo NSObject para obter mais informações:
Isso me parece uma completa loucura, pois potencialmente torna as pesquisas de hash muito menos eficientes, mas acho que é melhor errar por precaução e seguir o que a documentação diz.
fonte
Desculpe se eu arriscar soar um caixão completo aqui, mas ... ... ninguém se incomodou em mencionar que, para seguir as "melhores práticas", você definitivamente não deve especificar um método igual que NÃO levaria em consideração todos os dados pertencentes ao seu objeto de destino, por exemplo os dados são agregados ao seu objeto, em comparação com um associado, devem ser levados em consideração ao implementar iguais. Se você não quiser levar em consideração uma comparação, diga 'idade' em uma comparação, escreva um comparador e use-o para realizar suas comparações em vez de isEqual :.
Se você definir um método isEqual: que executa arbitrariamente a comparação de igualdade, você corre o risco de que esse método seja usado incorretamente por outro desenvolvedor, ou até por você mesmo, depois de esquecer o 'toque' na sua interpretação igual.
Portanto, embora este seja um ótimo questionário sobre hash, normalmente você não precisa redefinir o método de hash, provavelmente deve definir um comparador ad-hoc.
fonte