Gostaria de comparar duas coleções (em C #), mas não tenho certeza da melhor maneira de implementar isso com eficiência.
Eu li o outro tópico sobre Enumerable.SequenceEqual , mas não é exatamente o que estou procurando.
No meu caso, duas coleções seriam iguais se ambas contivessem os mesmos itens (não importa a ordem).
Exemplo:
collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};
collection1 == collection2; // true
O que eu costumo fazer é percorrer cada item de uma coleção e ver se existe na outra coleção, depois percorrer cada item da outra coleção e ver se existe na primeira coleção. (Começo comparando os comprimentos).
if (collection1.Count != collection2.Count)
return false; // the collections are not equal
foreach (Item item in collection1)
{
if (!collection2.Contains(item))
return false; // the collections are not equal
}
foreach (Item item in collection2)
{
if (!collection1.Contains(item))
return false; // the collections are not equal
}
return true; // the collections are equal
No entanto, isso não está totalmente correto e provavelmente não é a maneira mais eficiente de comparar duas coleções de igualdade.
Um exemplo em que posso pensar que seria errado é:
collection1 = {1, 2, 3, 3, 4}
collection2 = {1, 2, 2, 3, 4}
O que seria igual à minha implementação. Devo apenas contar o número de vezes que cada item é encontrado e garantir que as contagens sejam iguais nas duas coleções?
Os exemplos estão em algum tipo de C # (vamos chamá-lo de pseudo-C #), mas dê sua resposta no idioma que você desejar, não importa.
Nota: Eu usei números inteiros nos exemplos por simplicidade, mas quero poder usar objetos do tipo referência também (eles não se comportam corretamente como chaves porque apenas a referência do objeto é comparada, não o conteúdo).
fonte
Respostas:
Acontece que a Microsoft já abordou isso em sua estrutura de teste: CollectionAssert.AreEquivalent
Usando o refletor, modifiquei o código por trás de AreEquivalent () para criar um comparador de igualdade correspondente. É mais completo que as respostas existentes, uma vez que leva em consideração os nulos, implementa o IEqualityComparer e possui algumas verificações de eficiência e de casos extremos. além disso, é a Microsoft :)
Uso da amostra:
Ou se você quiser comparar duas coleções diretamente:
Por fim, você pode usar seu comparador de igualdade de sua escolha:
fonte
EqualityComparer
(o que você forneceu ouEqualityComparer.Default
você pode verificar o Reflector ou a fonte de referência para verificar isso). É verdade que, se os objetos mudarem (e especificamente o seu código de hash) mudar enquanto esse método estiver em execução, os resultados serão inesperados, mas isso significa que esse método não é seguro para threads nesse contexto.EqualityComparer
(ouEqualityComparer.Default
se nenhum foi especificado) e, novamente, a implementação está correta.Equals
por causa daIEqualityComparer<T>
interface. O que você deve observar é o nome do próprio comparador . Nesse caso, é oMultiSetComparer
que faz sentido.Uma solução simples e bastante eficiente é classificar as duas coleções e compará-las para igualdade:
Esse algoritmo é O (N * logN), enquanto sua solução acima é O (N ^ 2).
Se as coleções tiverem certas propriedades, você poderá implementar uma solução mais rápida. Por exemplo, se as duas coleções forem conjuntos de hash, elas não poderão conter duplicatas. Além disso, verificar se um conjunto de hash contém algum elemento é muito rápido. Nesse caso, um algoritmo semelhante ao seu provavelmente seria o mais rápido.
fonte
Crie um dicionário "dict" e, em seguida, para cada membro da primeira coleção, faça dict [member] ++;
Em seguida, faça um loop sobre a segunda coleção da mesma maneira, mas para cada membro dite [member] -.
No final, faça um loop sobre todos os membros do dicionário:
Edit: Tanto quanto eu posso dizer isso está na mesma ordem que o algoritmo mais eficiente. Esse algoritmo é O (N), assumindo que o Dicionário use pesquisas O (1).
fonte
return dict.All(kvp => kvp.Value == 0);
Esta é minha implementação genérica (fortemente influenciada por D.Jennings) do método de comparação (em C #):
fonte
The keys of a dictionary are compared by reference, so we have to find the original key that is equivalent to the "item"
- isso não é verdade. O algoritmo é baseado em suposições erradas e, enquanto funciona, é terrivelmente ineficiente.Você poderia usar um Hashset . Veja o método SetEquals .
fonte
Se você usar o Shouldly , poderá usar o ShouldAllBe with Contains.
E, finalmente, você pode escrever uma extensão.
ATUALIZAR
Existe um parâmetro opcional no método ShouldBe .
fonte
bool ignoreOrder
no método ShouldBe .Edição: Percebi, logo que afirmei, que isso realmente funciona apenas para conjuntos - ele não lidará adequadamente com coleções com itens duplicados. Por exemplo, {1, 1, 2} e {2, 2, 1} serão considerados iguais da perspectiva desse algoritmo. Se suas coleções são conjuntos (ou sua igualdade pode ser medida dessa maneira), espero que você ache o que é útil abaixo.
A solução que eu uso é:
O Linq faz o dicionário sob as cobertas, então isso também é O (N). (Observe que é O (1) se as coleções não tiverem o mesmo tamanho).
Fiz uma verificação de integridade usando o método "SetEqual" sugerido por Daniel, o método OrderBy / SequenceEquals sugerido por Igor e minha sugestão. Os resultados estão abaixo, mostrando O (N * LogN) para Igor e O (N) para o meu e o de Daniel.
Eu acho que a simplicidade do código de interseção do Linq o torna a solução preferível.
fonte
No caso de sem repetições e sem ordem, o seguinte EqualityComparer pode ser usado para permitir coleções como chaves de dicionário:
Aqui está a implementação ToHashSet () que eu usei. O algoritmo de código hash vem do Java efetivo (por meio de Jon Skeet).
fonte
ISet<T>
expressá-la para conjuntos (ou seja, sem duplicatas).ISet
, a idéia aqui era tratar oIEnumerable
conjunto (porque você tem umIEnumerable
para começar), apesar de considerar os 0 votos positivos em mais de 5 anos que podem não ter sido a ideia mais nítida: PA solução requer o .NET 3.5 e o
System.Collections.Generic
espaço para nome. Segundo a Microsoft ,SymmetricExceptWith
é uma operação O (n + m) , com n representando o número de elementos no primeiro conjunto e m representando o número de elementos no segundo. Você sempre pode adicionar um comparador de igualdade a essa função, se necessário.fonte
Por que não usar .Except ()
http://msdn.microsoft.com/en-us/library/bb397894.aspx
fonte
Except
não funcionará para contar itens duplicados. Retornará true para os conjuntos {1,2,2} e {1,1,2}.[1, 1, 2] != [1, 2, 2]
. Usar osDistinct
faria parecer iguais.Uma publicação duplicada, mas confira minha solução para comparar coleções . É bem simples:
Isso executará uma comparação de igualdade, independentemente da ordem:
Isso verificará se os itens foram adicionados / removidos:
Isso verá quais itens do dicionário foram alterados:
Post original aqui .
fonte
erickson está quase certo: como você deseja corresponder à contagem de duplicatas, você quer uma bolsa . Em Java, isso se parece com:
Tenho certeza de que o C # possui uma implementação interna do conjunto. Eu usaria isso primeiro; se o desempenho for um problema, você sempre poderá usar uma implementação diferente do Set, mas usar a mesma interface do Set.
fonte
Aqui está minha variante do método de extensão da resposta do ohadsc, caso seja útil para alguém
fonte
IEnumerable<T>
s são consultas, chamarCount()
não é uma boa ideia. A abordagem da resposta original de Ohad para verificar se estãoICollection<T>
é a melhor idéia.Aqui está uma solução que é uma melhoria em relação a esta .
fonte
Existem muitas soluções para esse problema. Se você não se importa com duplicatas, não precisa classificar as duas. Primeiro, verifique se eles têm o mesmo número de itens. Depois disso, classifique uma das coleções. Em seguida, pesquise cada item da segunda coleção na coleção classificada. Se você não encontrar um determinado item, pare e retorne false. A complexidade disso: - classificando a primeira coleção: N Log (N) - pesquisando cada item do segundo ao primeiro: NLOG (N) para que você termine com 2 * N * LOG (N) assumindo que eles coincidem e você procure tudo. Isso é semelhante à complexidade da classificação de ambos. Além disso, você tem o benefício de parar mais cedo, se houver alguma diferença. No entanto, lembre-se de que, se os dois forem classificados antes de você entrar nessa comparação e tentar classificar usando algo como um qsort, a classificação será mais cara. Existem otimizações para isso. Outra alternativa, que é ótima para pequenas coleções em que você conhece o intervalo dos elementos, é usar um índice de máscara de bit. Isso lhe dará um desempenho O (n). Outra alternativa é usar um hash e procurá-lo. Para coleções pequenas, geralmente é muito melhor fazer a classificação ou o índice de máscara de bit. Hashtable tem a desvantagem de pior localidade, portanto, tenha isso em mente. Novamente, isso é apenas se você não não me importo com duplicatas. Se você deseja contabilizar duplicatas, escolha a classificação de ambas.
fonte
Em muitos casos, a única resposta adequada é a de Igor Ostrovsky, outras respostas são baseadas no código de hash dos objetos. Mas quando você gera um código de hash para um objeto, você o faz apenas com base nos campos IMMUTABLE - como o campo Id do objeto (no caso de uma entidade do banco de dados) - Por que é importante substituir GetHashCode quando o método Equals é substituído?
Isso significa que, se você comparar duas coleções, o resultado poderá ser verdadeiro no método de comparação, mesmo que os campos dos diferentes itens sejam diferentes. Para comparar profundamente as coleções, você precisa usar o método Igor e implementar o IEqualirity.
Por favor, leia os comentários meus e do Sr. Schnider em seu post mais votado.
James
fonte
Permitindo duplicatas na
IEnumerable<T>
(se os conjuntos não forem desejáveis \ possíveis) e na "ordem de ignorância", você poderá usar a.GroupBy()
.Não sou especialista em medidas de complexidade, mas meu entendimento rudimentar é que isso deve ser O (n). Entendo O (n ^ 2) como decorrente da execução de uma operação O (n) dentro de outra operação O (n) como
ListA.Where(a => ListB.Contains(a)).ToList()
. Cada item na Lista B é avaliado quanto à igualdade em relação a cada item na Lista A.Como eu disse, meu entendimento sobre complexidade é limitado, então me corrija se estiver errado.
fonte
Esta solução simples força o
IEnumerable
tipo genérico a ser implementadoIComparable
. Por causa daOrderBy
definição de.Se você não deseja fazer essa suposição, mas ainda deseja usar esta solução, pode usar o seguinte trecho de código:
fonte
Ao comparar com o objetivo de Unit Testing Assertions, pode fazer sentido lançar alguma eficiência pela janela e simplesmente converter cada lista em uma representação de string (csv) antes de fazer a comparação. Dessa forma, a mensagem de Asserção de teste padrão exibirá as diferenças na mensagem de erro.
Uso:
Método de extensão auxiliar:
fonte