Esiste un metodo integrato per confrontare le raccolte?


178

Vorrei confrontare i contenuti di un paio di raccolte nel mio metodo Equals. Ho un dizionario e un IList. Esiste un metodo integrato per farlo?

Modificato: voglio confrontare due dizionari e due IList, quindi penso che il significato dell'uguaglianza sia chiaro: se i due dizionari contengono le stesse chiavi mappate sugli stessi valori, allora sono uguali.


Indipendentemente dall'ordine o non per IList? La domanda è ambigua.
nawfal,

Enumerable.SequenceEquale ISet.SetEqualsfornire versioni di questa funzionalità. Se vuoi essere indipendente dall'ordine e lavorare con raccolte che hanno duplicati, dovrai crearne uno tuo.
Dai

Risposte:


185

Enumerable.SequenceEqual

Determina se due sequenze sono uguali confrontando i loro elementi utilizzando un IEqualityComparer (T) specificato.

Non è possibile confrontare direttamente l'elenco e il dizionario, ma è possibile confrontare l'elenco di valori dal Dizionario con l'elenco


52
Il problema è che SequenceEqual si aspetta che gli elementi siano nello stesso ordine. La classe Dictionary non garantisce l'ordine delle chiavi o dei valori durante l'enumerazione, quindi se hai intenzione di usare SequenceEqual, devi prima ordinare i .Keys e .Values!
Orion Edwards,

3
@Orion: ... a meno che tu non voglia rilevare le differenze di ordine, ovviamente :-)
schoetbi,

30
@schoetbi: Perché dovresti rilevare le differenze di ordine in un contenitore che non garantisce l'ordine ?
Matti Virkkunen,

4
@schoetbi: serve per estrarre un determinato elemento da un IEnumerable. Un dizionario Tuttavia, non ordina di garanzia, in modo .Keyse .Valuespuò restituire le chiavi ei valori in qualsiasi ordine si sentono come, e che l'ordine è destinata a cambiare il dizionario viene modificato così. Ti suggerisco di leggere cos'è un dizionario e cosa non lo è.
Matti Virkkunen,

5
TestTools di MS e NUnit forniscono CollectionAssert.AreEquivalent
tymtam

44

Come altri hanno suggerito e notato, SequenceEqualè sensibile all'ordine. Per risolverlo, è possibile ordinare il dizionario in base alla chiave (che è univoco, quindi l'ordinamento è sempre stabile) e quindi utilizzare SequenceEqual. L'espressione seguente controlla se due dizionari sono uguali indipendentemente dal loro ordine interno:

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

EDIT: Come sottolineato da Jeppe Stig Nielsen, alcuni oggetti hanno un IComparer<T>incompatibile con il loro IEqualityComparer<T>, dando risultati errati. Quando si usano le chiavi con un tale oggetto, è necessario specificare una corretta IComparer<T>per quelle chiavi. Ad esempio, con le chiavi di stringa (che presentano questo problema), è necessario effettuare le seguenti operazioni per ottenere risultati corretti:

dictionary1.OrderBy(kvp => kvp.Key, StringComparer.Ordinal).SequenceEqual(dictionary2.OrderBy(kvp => kvp.Key, StringComparer.Ordinal))

Cosa succede se il tipo di chiave non lo farà CompareTo? La tua soluzione esploderà allora. Cosa succede se il tipo di chiave ha un comparatore predefinito che è incompatibile con il comparatore di uguaglianza predefinito? Questo è il caso di string, lo sai. Ad esempio, questi dizionari (con comparatori impliciti di uguaglianza di default impliciti) falliranno il tuo test (sotto tutte le informazioni di cultura di cui sono a conoscenza):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: Per quanto riguarda l'incompatibilità tra IComparere IEqualityComparer- non ero a conoscenza di questo problema, molto interessante! Ho aggiornato la risposta con una possibile soluzione. A proposito della mancanza di CompareTo, penso che lo sviluppatore dovrebbe assicurarsi che il delegato fornito al OrderBy()metodo restituisca qualcosa di paragonabile. Penso che questo sia vero per qualsiasi uso o OrderBy(), anche al di fuori dei confronti tra dizionari.
Allon Guralnek,

15

Oltre al citato SequenceEqual , che

è vero se due liste hanno la stessa lunghezza e i loro elementi corrispondenti si equivalgono in base a un comparatore

(che può essere il comparatore predefinito, ovvero un overriden Equals() )

vale la pena ricordare che in .Net4 ci sono SetEquals su ISetoggetti, che

ignora l'ordine degli elementi e tutti gli elementi duplicati.

Quindi, se vuoi avere un elenco di oggetti, ma non è necessario che siano in un ordine specifico, considera che un ISet(come un HashSet) potrebbe essere la scelta giusta.


7

Dai un'occhiata al metodo 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);

13
Questo non è affidabile perché SequenceEqual si aspetta che i valori escano dal dizionario in un ordine affidabile - Il dizionario non fornisce tali garanzie sull'ordine e il dizionario.Keys potrebbe uscire come [2, 1] anziché [1, 2] e il tuo test fallirebbe
Orion Edwards,

5

.NET manca di potenti strumenti per confrontare le raccolte. Ho sviluppato una soluzione semplice che puoi trovare al link seguente:

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

Ciò eseguirà un confronto di uguaglianza indipendentemente dall'ordine:

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

Questo verificherà se gli articoli sono stati aggiunti / rimossi:

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

Questo vedrà quali elementi nel dizionario sono cambiati:

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

10
Naturalmente .NET ha potenti strumenti per confrontare le raccolte (sono operazioni basate su set). .Removedè lo stesso di list1.Except(list2), .Addedè list2.Except(list1), .Equalè list1.Intersect(list2)ed .Differentè original.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value). Puoi fare quasi ogni confronto con LINQ.
Allon Guralnek,

3
Correzione: .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 puoi anche aggiungere OldValue = left.Valuese hai bisogno anche del vecchio valore.
Allon Guralnek,

3
@AllonGuralnek i tuoi suggerimenti sono validi, ma non gestiscono il caso in cui l'Elenco non è un vero insieme - in cui l'elenco contiene lo stesso oggetto più volte. Il confronto tra {1, 2} e {1, 2, 2} non restituirebbe nulla di aggiunto / rimosso.
Niall Connaughton,


4

Non sapevo del metodo Enumerable.SequenceEqual (impari qualcosa ogni giorno ....), ma stavo per suggerire di usare un metodo di estensione; qualcosa come questo:

    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;

    }

È interessante notare che, dopo aver impiegato 2 secondi per leggere SequenceEqual, sembra che Microsoft abbia creato la funzione che ho descritto per te.


4

Questo non risponde direttamente alle tue domande, ma forniscono sia TestTools di MS che NUnit

 CollectionAssert.AreEquivalent

che fa praticamente quello che vuoi.


Stavo cercando questo per il mio test NUnit
Blem,

1

Per confrontare le raccolte puoi anche usare LINQ. Enumerable.Intersectrestituisce tutte le coppie uguali. Puoi comporre due dizionari in questo modo:

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

Il primo confronto è necessario perché dict2può contenere tutte le chiavi didict1 e altro.

Puoi anche usare pensare a variazioni usando Enumerable.Excepte Enumerable.Unionche portano a risultati simili. Ma può essere usato per determinare le differenze esatte tra i set.


1

Che ne dici di questo esempio:

 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);
}

Per gentile concessione: https://www.dotnetperls.com/dictionary-equals


value! = pair.Value sta facendo un confronto di riferimento, usa invece Equals
kofifus

1

Per le raccolte ordinate (elenco, matrice) utilizzare SequenceEqual

per l'uso di HashSet SetEquals

per Dizionario puoi fare:

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;
    }
  }
}

(Una soluzione più ottimizzata utilizzerà l'ordinamento ma ciò richiederà IComparable<TValue>)


0

No. Il framework di raccolta non ha alcun concetto di uguaglianza. Se ci pensate, non c'è modo di confrontare le raccolte che non è soggettivo. Ad esempio, confrontando il tuo IList con il tuo dizionario, sarebbero uguali se tutte le chiavi fossero nell'IList, tutti i valori fossero nell'IList o se entrambi fossero nell'IList? Non esiste un modo ovvio di confrontare queste due raccolte senza sapere a cosa servono per cui un metodo uguale a uno scopo generale non ha senso.



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;
}

0

Non c'era, non è e potrebbe non esserlo, almeno ci crederei. Il motivo dietro l'uguaglianza di raccolta è probabilmente un comportamento definito dall'utente.

Gli elementi nelle raccolte non dovrebbero essere in un ordine particolare sebbene abbiano un ordinamento naturale, non è ciò su cui gli algoritmi di confronto dovrebbero fare affidamento. Supponi di avere due raccolte di:

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

Sono uguali o no? Devi sapere ma non so quale sia il tuo punto di vista.

Le raccolte sono concettualmente non ordinate per impostazione predefinita, fino a quando gli algoritmi non forniscono le regole di ordinamento. La stessa cosa che SQL Server porterà alla tua attenzione è quando provi a fare l'impaginazione, ti richiede di fornire regole di ordinamento:

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

Ancora altre due collezioni:

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

Ancora una volta, sono uguali o no? Dimmelo tu ..

La ripetibilità degli elementi di una collezione svolge il suo ruolo in diversi scenari e in alcune raccolte simili Dictionary<TKey, TValue> non consentono nemmeno elementi ripetuti.

Credo che questi tipi di uguaglianza siano definiti dall'applicazione e il quadro pertanto non ha fornito tutte le possibili implementazioni.

Bene, in generale Enumerable.SequenceEqualè abbastanza buono ma restituisce false nel caso seguente:

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

Ho letto alcune risposte a domande come questa (potresti google per loro) e cosa userei, in generale:

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);
    }
}

Ciò significa che una raccolta rappresenta l'altra nei suoi elementi, compresi i tempi ripetuti senza tenere conto dell'ordinamento originale. Alcune note di attuazione:

  • GetHashCode()è solo per l'ordinamento, non per l'uguaglianza; Penso che sia abbastanza in questo caso

  • Count() non enumera realmente la raccolta e rientra direttamente nell'implementazione della proprietà di ICollection<T>.Count

  • Se i riferimenti sono uguali, è solo Boris

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.