Existe um método interno para comparar coleções?

178

Gostaria de comparar o conteúdo de algumas coleções no meu método Equals. Eu tenho um dicionário e um IList. Existe um método interno para fazer isso?

Editado: quero comparar dois dicionários e duas ILists, então acho que o que significa igualdade é claro - se os dois dicionários contêm as mesmas chaves mapeadas para os mesmos valores, então são iguais.

TimK
fonte
Independentemente da ordem ou não IList? A pergunta é ambígua.
Nawfal
Enumerable.SequenceEquale ISet.SetEqualsforneça versões dessa funcionalidade. Se você quer ser independente de pedidos e trabalhar com coleções com duplicatas, precisará fazer o seu próprio. Confira a implementação sugerida neste post
ChaseMedallion 3/16

Respostas:

185

Enumerable.SequenceEqual

Determina se duas seqüências são iguais comparando seus elementos usando um IEqualityComparer (T) especificado.

Você não pode comparar diretamente a lista e o dicionário, mas pode comparar a lista de valores do Dicionário com a lista

Glenn Slaven
fonte
52
O problema é que SequenceEqual espera que os elementos estejam na mesma ordem. A classe Dictionary não garante a ordem das chaves ou dos valores ao enumerar; portanto, se você usar o SequenceEqual, precisará classificar os .Keys e .Values ​​primeiro!
Orion Edwards
3
@Orion: ... a menos que você deseja detectar diferenças de ordenação, é claro :-)
schoetbi
30
@schoetbi: Por que você deseja detectar diferenças de pedidos em um contêiner que não garante o pedido ?
Matti Virkkunen
4
@schoetbi: É para tirar um certo elemento de um IEnumerable. No entanto, um dicionário não fim garantia, então .Keyse .Valuespode retornar as chaves e valores em qualquer ordem eles se sentem como, e que a ordem é provável que a mudança como o dicionário é modificado também. Eu sugiro que você leia o que é um dicionário e o que não é.
Matti Virkkunen
5
O TestTools e o NUnit da MS fornecem CollectionAssert.AreEquivalent
tymtam
44

Como outros sugeriram e observaram, SequenceEqualé sensível à ordem. Para resolver isso, você pode classificar o dicionário por chave (que é única e, portanto, a classificação é sempre estável) e usar SequenceEqual. A expressão a seguir verifica se dois dicionários são iguais, independentemente de sua ordem interna:

dictionary1.OrderBy(kvp => kvp.Key).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key))

Edição: Como apontado por Jeppe Stig Nielsen, alguns objetos têm um IComparer<T>que é incompatível com seus IEqualityComparer<T>, produzindo resultados incorretos. Ao usar chaves com esse objeto, você deve especificar um correto IComparer<T>para essas chaves. Por exemplo, com chaves de seqüência de caracteres (que apresentam esse problema), você deve fazer o seguinte para obter resultados corretos:

dictionary1.OrderBy(kvp => kvp.Key, StringComparer.Ordinal).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))
Allon Guralnek
fonte
E se o tipo de chave não for CompareTo? Sua solução vai explodir então. E se o tipo de chave tiver um comparador padrão incompatível com seu comparador de igualdade padrão? Este é o caso string, você sabe. Como exemplo destes dicionários (com comparadores de igualdade padrão implícitos) irá falhar seu teste (sob todas as informações sobre cultura que conheço):var dictionary1 = new Dictionary<string, int> { { "Strasse", 10 }, { "Straße", 20 }, }; var dictionary2 = new Dictionary<string, int> { { "Straße", 20 }, { "Strasse", 10 }, };
Jeppe Stig Nielsen
@JeppeStigNielsen: Quanto à incompatibilidade entre IComparere IEqualityComparer- eu não estava ciente desse problema, muito interessante! Atualizei a resposta com uma possível solução. Sobre a falta de CompareTo, acho que o desenvolvedor deve garantir que o delegado fornecido ao OrderBy()método retorne algo comparável. Eu acho que isso é verdade para qualquer uso ou OrderBy()mesmo fora das comparações de dicionário.
Allon Guralnek
15

Além do SequenceEqual mencionado , que

é verdadeiro se duas listas tiverem o mesmo comprimento e seus elementos correspondentes forem comparados de acordo com um comparador

(que pode ser o comparador padrão, ou seja, um substituto Equals())

vale ressaltar que em .Net4 há SetEquals em ISetobjetos, o que

ignora a ordem dos elementos e quaisquer elementos duplicados.

Portanto, se você deseja ter uma lista de objetos, mas eles não precisam estar em uma ordem específica, considere que um ISet(como a HashSet) pode ser a escolha certa.

Destino
fonte
7

Dê uma olhada no método Enumerable.SequenceEqual

var dictionary = new Dictionary<int, string>() {{1, "a"}, {2, "b"}};
var intList = new List<int> {1, 2};
var stringList = new List<string> {"a", "b"};
var test1 = dictionary.Keys.SequenceEqual(intList);
var test2 = dictionary.Values.SequenceEqual(stringList);
aku
fonte
13
Isso não é confiável porque o SequenceEqual espera que os valores saiam do dicionário em uma ordem confiável - o dicionário não oferece tais garantias sobre ordem e dicionário. As chaves podem sair como [2, 1] em vez de [1, 2] e seu teste seria um fracasso
Orion Edwards
5

.NET Não possui nenhuma ferramenta poderosa para comparar coleções. Eu desenvolvi uma solução simples que você pode encontrar no link abaixo:

http://robertbouillon.com/2010/04/29/comparing-collections-in-net/

Isso executará uma comparação de igualdade, independentemente da ordem:

var list1 = new[] { "Bill", "Bob", "Sally" };
var list2 = new[] { "Bob", "Bill", "Sally" };
bool isequal = list1.Compare(list2).IsSame;

Isso verificará se os itens foram adicionados / removidos:

var list1 = new[] { "Billy", "Bob" };
var list2 = new[] { "Bob", "Sally" };
var diff = list1.Compare(list2);
var onlyinlist1 = diff.Removed; //Billy
var onlyinlist2 = diff.Added;   //Sally
var inbothlists = diff.Equal;   //Bob

Você verá quais itens do dicionário foram alterados:

var original = new Dictionary<int, string>() { { 1, "a" }, { 2, "b" } };
var changed = new Dictionary<int, string>() { { 1, "aaa" }, { 2, "b" } };
var diff = original.Compare(changed, (x, y) => x.Value == y.Value, (x, y) => x.Value == y.Value);
foreach (var item in diff.Different)
  Console.Write("{0} changed to {1}", item.Key.Value, item.Value.Value);
//Will output: a changed to aaa
user329244
fonte
10
É claro que o .NET possui ferramentas poderosas para comparar coleções (são operações baseadas em conjuntos). .Removedé o mesmo que list1.Except(list2), .Addedé list2.Except(list1), .Equalé list1.Intersect(list2)e .Differenté original.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value). Você pode fazer quase qualquer comparação com o LINQ.
Allon Guralnek
3
Correção: .Differenté original.Join(changed, left => left.Key, right => right.Key, (left, right) => new { Key = left.Key, NewValue = right.Value, Different = left.Value == right.Value).Where(d => d.Different). E você pode até adicionar OldValue = left.Valuese precisar do valor antigo também.
Allon Guralnek
3
@AllonGuralnek, suas sugestões são boas, mas elas não tratam o caso em que a Lista não é um conjunto verdadeiro - em que a lista contém o mesmo objeto várias vezes. Comparar {1, 2} e {1, 2, 2} não retornaria nada adicionado / removido.
Niall Connaughton
4

Eu não sabia sobre o método Enumerable.SequenceEqual (você aprende algo todos os dias ...), mas sugeria usar um método de extensão; algo assim:

    public static bool IsEqual(this List<int> InternalList, List<int> ExternalList)
    {
        if (InternalList.Count != ExternalList.Count)
        {
            return false;
        }
        else
        {
            for (int i = 0; i < InternalList.Count; i++)
            {
                if (InternalList[i] != ExternalList[i])
                    return false;
            }
        }

        return true;

    }

Curiosamente, depois de levar 2 segundos para ler sobre o SequenceEqual, parece que a Microsoft criou a função que descrevi para você.

Giovanni Galbo
fonte
4

Isso não está respondendo diretamente às suas perguntas, mas o TestTools e o NUnit da MS fornecem

 CollectionAssert.AreEquivalent

o que faz praticamente o que você deseja.

timtim
fonte
Estava procurando por esta para o meu teste NUnit
Blem
1

Para comparar coleções, você também pode usar o LINQ. Enumerable.Intersectretorna todos os pares que são iguais. Você pode comparar dois dicionários como este:

(dict1.Count == dict2.Count) && dict1.Intersect(dict2).Count() == dict1.Count

A primeira comparação é necessária porque dict2pode conter todas as chaves de dict1e mais.

Você também pode usar pensar em variações usando Enumerable.Excepte Enumerable.Unionque levam a resultados semelhantes. Mas pode ser usado para determinar as diferenças exatas entre os conjuntos.

Chrono
fonte
1

Que tal este exemplo:

 static void Main()
{
    // Create a dictionary and add several elements to it.
    var dict = new Dictionary<string, int>();
    dict.Add("cat", 2);
    dict.Add("dog", 3);
    dict.Add("x", 4);

    // Create another dictionary.
    var dict2 = new Dictionary<string, int>();
    dict2.Add("cat", 2);
    dict2.Add("dog", 3);
    dict2.Add("x", 4);

    // Test for equality.
    bool equal = false;
    if (dict.Count == dict2.Count) // Require equal count.
    {
        equal = true;
        foreach (var pair in dict)
        {
            int value;
            if (dict2.TryGetValue(pair.Key, out value))
            {
                // Require value be equal.
                if (value != pair.Value)
                {
                    equal = false;
                    break;
                }
            }
            else
            {
                // Require key be present.
                equal = false;
                break;
            }
        }
    }
    Console.WriteLine(equal);
}

Cortesia: https://www.dotnetperls.com/dictionary-equals

ispostback
fonte
valor! = pair.Value está fazendo comparação de referência, use iguais em vez disso
kofifus
1

Para coleções ordenadas (lista, matriz), use SequenceEqual

para uso com HashSet SetEquals

para o Dictionary, você pode fazer:

namespace System.Collections.Generic {
  public static class ExtensionMethods {
    public static bool DictionaryEquals<TKey, TValue>(this IReadOnlyDictionary<TKey, TValue> d1, IReadOnlyDictionary<TKey, TValue> d2) {
      if (object.ReferenceEquals(d1, d2)) return true; 
      if (d2 is null || d1.Count != d2.Count) return false;
      foreach (var (d1key, d1value) in d1) {
        if (!d2.TryGetValue(d1key, out TValue d2value)) return false;
        if (!d1value.Equals(d2value)) return false;
      }
      return true;
    }
  }
}

(Uma solução mais otimizada usará a classificação, mas isso exigirá IComparable<TValue>)

kofifus
fonte
0

Não. A estrutura de coleta não tem nenhum conceito de igualdade. Se você pensar bem, não há como comparar coleções que não são subjetivas. Por exemplo, comparando seu IList ao seu Dicionário, eles seriam iguais se todas as chaves estivessem no IList, todos os valores estivessem no IList ou se ambos estivessem no IList? Não há uma maneira óbvia de comparar essas duas coleções sem o conhecimento do que elas devem ser usadas, de modo que um método igual para uso geral não faz sentido.

Evil Andy
fonte
0

Não, porque a estrutura não sabe como comparar o conteúdo de suas listas.

Veja isso:

http://blogs.msdn.com/abhinaba/archive/2005/10/11/479537.aspx

Mark Ingram
fonte
3
Já não existem várias opções para informar à estrutura como comparar os elementos? IComparer<T>, Substituindo object.Equals, IEquatable<T>, IComparable<T>...
Stefan Steinegger
0
public bool CompareStringLists(List<string> list1, List<string> list2)
{
    if (list1.Count != list2.Count) return false;

    foreach(string item in list1)
    {
        if (!list2.Contains(item)) return false;
    }

    return true;
}
mbadeveloper
fonte
0

Não havia, não existe e pode não haver, pelo menos eu acredito que sim. A razão por trás da igualdade de coleção é provavelmente um comportamento definido pelo usuário.

Os elementos das coleções não devem estar em uma ordem específica, embora tenham uma ordem natural, não é nisso que os algoritmos de comparação devem confiar. Digamos que você tenha duas coleções de:

{1, 2, 3, 4}
{4, 3, 2, 1}

Eles são iguais ou não? Você deve saber, mas eu não sei qual é o seu ponto de vista.

Coleções são conceitualmente desordenadas por padrão, até que os algoritmos forneçam as regras de classificação. A mesma coisa que o SQL Server chamará a atenção é quando você tenta fazer paginação, requer que você forneça regras de classificação:

https://docs.microsoft.com/pt-BR/sql/t-sql/queries/select-order-by-clause-transact-sql?view=sql-server-2017

Mais duas coleções:

{1, 2, 3, 4}
{1, 1, 1, 2, 2, 3, 4}

Novamente, eles são iguais ou não? Diz-me tu ..

A repetibilidade de elementos de uma coleção desempenha seu papel em diferentes cenários e algumas coleções, como Dictionary<TKey, TValue>nem mesmo permitem elementos repetidos.

Acredito que esses tipos de igualdade são definidos por aplicativos e, portanto, a estrutura não forneceu todas as implementações possíveis.

Bem, em casos gerais, Enumerable.SequenceEqualé bom o suficiente, mas retorna falso no seguinte caso:

var a = new Dictionary<String, int> { { "2", 2 }, { "1", 1 }, };
var b = new Dictionary<String, int> { { "1", 1 }, { "2", 2 }, };
Debug.Print("{0}", a.SequenceEqual(b)); // false

Eu li algumas respostas para perguntas como essa (você pode pesquisar no Google por elas) e o que eu usaria, em geral:

public static class CollectionExtensions {
    public static bool Represents<T>(this IEnumerable<T> first, IEnumerable<T> second) {
        if(object.ReferenceEquals(first, second)) {
            return true;
        }

        if(first is IOrderedEnumerable<T> && second is IOrderedEnumerable<T>) {
            return Enumerable.SequenceEqual(first, second);
        }

        if(first is ICollection<T> && second is ICollection<T>) {
            if(first.Count()!=second.Count()) {
                return false;
            }
        }

        first=first.OrderBy(x => x.GetHashCode());
        second=second.OrderBy(x => x.GetHashCode());
        return CollectionExtensions.Represents(first, second);
    }
}

Isso significa que uma coleção representa a outra em seus elementos, incluindo tempos repetidos sem levar em consideração o pedido original. Algumas notas da implementação:

  • GetHashCode()é apenas para ordenar, não para igualdade; Eu acho que é suficiente neste caso

  • Count() realmente não enumera a coleção e se enquadra diretamente na implementação da propriedade ICollection<T>.Count

  • Se as referências são iguais, é apenas Boris

Ken Kin
fonte