Se você simplesmente deseja saber se os conjuntos são iguais, o equals
método on AbstractSet
é implementado aproximadamente como abaixo:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection c = (Collection) o;
if (c.size() != size())
return false;
return containsAll(c);
}
Observe como ele otimiza os casos comuns em que:
- os dois objetos são iguais
- o outro objeto não é um conjunto, e
- os tamanhos dos dois conjuntos são diferentes.
Depois disso, containsAll(...)
retornará false
assim que encontrar um elemento no outro conjunto que também não esteja neste conjunto. Mas se todos os elementos estiverem presentes em ambos os conjuntos, será necessário testar todos eles.
O pior caso de desempenho, portanto, ocorre quando os dois conjuntos são iguais, mas não os mesmos objetos. Esse custo é normalmente O(N)
ou O(NlogN)
dependendo da implementação de this.containsAll(c)
.
E você obtém desempenho próximo do pior caso se os conjuntos forem grandes e diferirem apenas em uma pequena porcentagem dos elementos.
ATUALIZAR
Se você deseja investir tempo em uma implementação de conjunto customizado, há uma abordagem que pode melhorar o caso "quase o mesmo".
A ideia é que você precisa pré-calcular e armazenar em cache um hash para todo o conjunto, de modo que possa obter o valor do hashcode atual do conjunto O(1)
. Em seguida, você pode comparar o código hash para os dois conjuntos como uma aceleração.
Como você poderia implementar um hashcode assim? Bem, se o hashcode definido foi:
- zero para um conjunto vazio, e
- o XOR de todos os códigos hash do elemento para um conjunto não vazio,
então você poderia atualizar de forma barata o hashcode em cache do conjunto cada vez que você adicionasse ou removesse um elemento. Em ambos os casos, você simplesmente XOR o hashcode do elemento com o conjunto atual de hashcode.
Obviamente, isso pressupõe que os hashcodes do elemento são estáveis, enquanto os elementos são membros de conjuntos. Ele também assume que a função hashcode das classes de elemento oferece uma boa distribuição. Isso ocorre porque, quando os dois conjuntos de códigos de hash são iguais, você ainda precisa recorrer à O(N)
comparação de todos os elementos.
Você poderia levar essa ideia um pouco mais longe ... pelo menos em teoria.
AVISO - Isso é altamente especulativo. Um "experimento mental", se quiser.
Suponha que sua classe de elemento definido tenha um método para retornar somas de verificação de criptografia para o elemento. Agora implemente as somas de verificação do conjunto aplicando um XOR nas somas de verificação retornadas para os elementos.
O que isso nos compra?
Bem, se assumirmos que nada secreto está acontecendo, a probabilidade de que quaisquer dois elementos de conjunto desiguais tenham as mesmas somas de verificação de N bits é 2 -N . E a probabilidade de 2 conjuntos desiguais terem as mesmas somas de verificação de N bits também é 2 -N . Então, minha ideia é que você pode implementar equals
como:
public boolean equals(Object o) {
if (o == this)
return true;
if (!(o instanceof Set))
return false;
Collection c = (Collection) o;
if (c.size() != size())
return false;
return checksums.equals(c.checksums);
}
De acordo com as premissas acima, isso só dará a resposta errada uma vez no tempo 2- N . Se você tornar N grande o suficiente (por exemplo, 512 bits), a probabilidade de uma resposta errada torna-se insignificante (por exemplo, aproximadamente 10 -150 ).
A desvantagem é que calcular as somas de verificação de criptografia para os elementos é muito caro, especialmente à medida que o número de bits aumenta. Portanto, você realmente precisa de um mecanismo eficaz para memorizar as somas de verificação. E isso pode ser problemático.
E a outra desvantagem é que uma probabilidade diferente de zero de erro pode ser inaceitável, não importa quão pequena seja a probabilidade. (Mas se for esse o caso ... como você lida com o caso em que um raio cósmico vira um bit crítico? Ou se ele simultaneamente vira o mesmo bit em duas instâncias de um sistema redundante?)