Najłatwiejszy sposób porównywania tablic w C #


180

W Javie Arrays.equals()pozwala na łatwe porównanie zawartości dwóch podstawowych tablic (przeciążenia są dostępne dla wszystkich podstawowych typów).

Czy jest coś takiego w C #? Czy istnieje „magiczny” sposób porównywania zawartości dwóch tablic w języku C #?


1
Dodano „.net” do tagów, ponieważ ta technika może być używana w innych podobnych językach opartych na .net.
Evan Plaice

3
Wszyscy, którzy to czytają, powinni pamiętać, że akceptowaną odpowiedzią jest użycie SequenceEqual. SequenceEqual nie tylko sprawdza, czy zawierają te same dane, ale także czy zawierają te same dane w tej samej kolejności
John Demetriou

Odpowiedzi:


262

Możesz użyć Enumerable.SequenceEqual. Działa to dla wszystkich IEnumerable<T>, nie tylko tablic.


Działa to tylko wtedy, gdy są w tej samej kolejności
John Demetriou,

1
SequenceEqualmoże nie być dobrym wyborem pod względem wydajności, ponieważ jego obecna implementacja może w pełni wyliczyć jedno z jego źródeł, jeśli różnią się one tylko długością. W przypadku tablic moglibyśmy Lengthnajpierw sprawdzić równość, aby uniknąć wyliczania tablic o różnych długościach tylko po to, aby uzyskać wynik false.
Frédéric

72

Użyj Enumerable.SequenceEqualw LINQ .

int[] arr1 = new int[] { 1,2,3};
int[] arr2 = new int[] { 3,2,1 };

Console.WriteLine(arr1.SequenceEqual(arr2)); // false
Console.WriteLine(arr1.Reverse().SequenceEqual(arr2)); // true

1
Pamiętaj, że to rzuca dla zerowych argumentów, więc nie zakładaj, żenew int[] {1}.SequenceEquals(null) == false
sara

30

Również w przypadku tablic (i krotek) można użyć nowych interfejsów z .NET 4.0: IStructuralComparable i IStructuralEquatable . Korzystając z nich możesz nie tylko sprawdzić równość tablic, ale także je porównać.

static class StructuralExtensions
{
    public static bool StructuralEquals<T>(this T a, T b)
        where T : IStructuralEquatable
    {
        return a.Equals(b, StructuralComparisons.StructuralEqualityComparer);
    }

    public static int StructuralCompare<T>(this T a, T b)
        where T : IStructuralComparable
    {
        return a.CompareTo(b, StructuralComparisons.StructuralComparer);
    }
}

{
    var a = new[] { 1, 2, 3 };
    var b = new[] { 1, 2, 3 };
    Console.WriteLine(a.Equals(b)); // False
    Console.WriteLine(a.StructuralEquals(b)); // True
}
{
    var a = new[] { 1, 3, 3 };
    var b = new[] { 1, 2, 3 };
    Console.WriteLine(a.StructuralCompare(b)); // 1
}

Przepraszam, czy powinno to być 1 czy 0 cali a.StructuralCompare(b)?
mafu

W przypadku tablic o dużym typie wartości ich użycie powoduje spadek wydajności, ponieważ ich obecna implementacja będzie oznaczać każdą wartość do porównania.
Frédéric

18

W przypadku .NET 4.0 i nowszych można porównać elementy w tablicy lub krotki za pomocą typu StructuralComparisons :

object[] a1 = { "string", 123, true };
object[] a2 = { "string", 123, true };

Console.WriteLine (a1 == a2);        // False (because arrays is reference types)
Console.WriteLine (a1.Equals (a2));  // False (because arrays is reference types)

IStructuralEquatable se1 = a1;
//Next returns True
Console.WriteLine (se1.Equals (a2, StructuralComparisons.StructuralEqualityComparer)); 

Edycja: mówił zbyt wcześnie. Czy mogę porównać StructualEqualityCompare z IStructuralComparable? Chcę wywołać CompareTo z dwiema tablicami obiektów, aby dowiedzieć się, która z nich jest „pierwsza”. Próbowałem IStructuralComparable se1 = a1; Console.WriteLine (se1.CompareTo (a2, StructuralComparisons.StructuralEqualityComparer)); Pobieranie: nie można konwertować z „System.Collections.IEqualityComparer” na „System.Collections.IComparer”
shindigo

1
OK - prawidłowe wywołanie to: IStructuralComparable se1 = a1; Console.WriteLine (se1.CompareTo (a2, StructuralComparisons.StructuralComparer));
shindigo

15

SequenceEqual zwróci prawdę tylko wtedy, gdy spełnione są dwa warunki.

  1. Zawierają te same elementy.
  2. Elementy są w tej samej kolejności.

Jeśli chcesz tylko sprawdzić, czy zawierają te same elementy niezależnie od ich kolejności, a Twój problem jest tego typu

Czy wartości2 zawiera wszystkie wartości zawarte w argumentach wartości1?

można użyć metody rozszerzenia LINQ, Enumerable.Excepta następnie sprawdzić, czy wynik ma jakąkolwiek wartość. Oto przykład

int[] values1 = { 1, 2, 3, 4 };
int[] values2 = { 1, 2, 5 };
var result = values1.Except(values2);
if(result.Count()==0)
{
   //They are the same
}
else
{
    //They are different
}

A także dzięki temu automatycznie otrzymujesz różne przedmioty. Dwie pieczenie na jednym ogniu.

Pamiętaj, że jeśli wykonujesz swój kod w ten sposób

var result = values2.Except(values1);

otrzymasz różne wyniki.

W moim przypadku mam lokalną kopię tablicy i chcę sprawdzić, czy coś zostało usunięte z oryginalnej tablicy, więc używam tej metody.


2
Tablice zawierające te same wartości w różnej kolejności po prostu NIE SĄ RÓWNE. Czy myślisz o „Demetriou” == „uoirtemeD”?
edc65

To zależy. Jeśli używasz tablic jako nieuporządkowanych kolekcji i chcesz tylko sprawdzić, czy zawierają te same elementy (np. Wartości z bazy danych na liście konfiguracji), to jest to najłatwiejszy sposób, jaki znalazłem. Jeśli kolejność ma znaczenie (np. Ciąg znaków), użyjesz SequenceEqual.
Armando

11

W przypadku testów jednostkowych można użyć CollectionAssert.AreEqualzamiast Assert.AreEqual.

To chyba najłatwiejszy sposób.


11

Jeśli chcesz z nullwdziękiem obsługiwać dane wejściowe i ignorować kolejność elementów, wypróbuj następujące rozwiązanie:

static class Extensions
{
    public static bool ItemsEqual<TSource>(this TSource[] array1, TSource[] array2)
    {
        if (array1 == null && array2 == null)
            return true;
        if (array1 == null || array2 == null)
            return false;
        return array1.Count() == array2.Count() && !array1.Except(array2).Any();
    }
}

Kod testu wygląda następująco:

class Program
{
    static void Main(string[] args)
    {
        int[] a1 = new int[] { 1, 2, 3 };
        int[] a2 = new int[] { 3, 2, 1 };
        int[] a3 = new int[] { 1, 3 };
        int[] a4 = null;
        int[] a5 = null;
        int[] a6 = new int[0];

        Console.WriteLine(a1.ItemsEqual(a2)); // Output: True.
        Console.WriteLine(a2.ItemsEqual(a3)); // Output: False.
        Console.WriteLine(a4.ItemsEqual(a5)); // Output: True. No Exception.
        Console.WriteLine(a4.ItemsEqual(a3)); // Output: False. No Exception.
        Console.WriteLine(a5.ItemsEqual(a6)); // Output: False. No Exception.
    }
}

Było to dla mnie pomocne, ale jeśli a1 = { 1, 1 }i a2 = { 1, 2 }, to pierwszy test zwróci zły wynik. Deklaracja zwrotu powinna byćreturn array1.Count() == array2.Count() && !array1.Except(array2).Any() && !array2.Except(array1).Any();
Polshgiant

2

W przypadku niektórych aplikacji może być lepsze:

string.Join(",", arr1) == string.Join(",", arr2)

2

To rozwiązanie LINQ działa, nie wiem, jak wypada w porównaniu z wydajnością do SequenceEquals. Ale obsługuje różne długości tablic, a .All zakończy działanie na pierwszym elemencie, który nie jest równy, bez iteracji po całej tablicy.

private static bool arraysEqual<T>(IList<T> arr1, IList<T> arr2)
        =>
            ReferenceEquals(arr1, arr2) || (
                arr1 != null && arr2 != null &&
                arr1.Count == arr2.Count &&
                arr1.Select((a, i) => arr2[i].Equals(a)).All(i => i)
            );

1

Porównanie elementarne? co powiesz na

public void Linq78a()
{
 int[] numbers1 = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
 int[] numbers = { 5, 4, 1, 3, 9, 8, 6, 7, 2, 0 };
 bool bb = numbers.Zip(numbers1, (a, b) => (a == b)).Any(p => !p);
 if (!bb) Console.WriteLine("Lists are equal (bb)");
   else Console.WriteLine("Lists are not equal (bb)");
}

Zastąp warunek (a == b) czymkolwiek, co chciałbyś porównać w aib.

(to łączy dwa przykłady z próbek Linq dewelopera MSDN )


1
Nie obsługuje tablic o różnych długościach (może nieprawidłowo dać true) i nulltablic (ulegnie awarii).
Frédéric

1

Zrobiłem to w studiach wizualnych i zadziałało doskonale; porównanie indeksów tablic po indeksie z krótkim tym kodem.

private void compareButton_Click(object sender, EventArgs e)
        {
            int[] answer = { 1, 3, 4, 6, 8, 9, 5, 4, 0, 6 };
            int[] exam = { 1, 2, 3, 6, 8, 9, 5, 4, 0, 7 };

            int correctAnswers = 0;
            int wrongAnswers = 0;

            for (int index = 0; index < answer.Length; index++)
            {
                if (answer[index] == exam[index])
                {
                    correctAnswers += 1;
                }
                else
                {
                    wrongAnswers += 1;
                }
            }

            outputLabel.Text = ("The matching numbers are " + correctAnswers +
                "\n" + "The non matching numbers are " + wrongAnswers);
        }

wyjście będzie; Pasujące liczby to 7 Niepasujące liczby to 3


2
Nie obsługuje tablic o różnych długościach (ulegnie awarii), nulltablic (również ulegnie awarii) i robi coś innego niż to, o co prosił OP. Poprosił tylko o znajomość równości, nie licząc, ile przedmiotów różni się lub pasuje.
Frédéric

0

Zakładając, że równość tablic oznacza, że ​​obie tablice mają równe elementy przy równych indeksach, istnieje SequenceEqualodpowiedź i IStructuralEquatableodpowiedź .

Ale oba mają wady, jeśli chodzi o wydajność.

SequenceEqual Obecna implementacja nie skraca się, gdy tablice mają różne długości, więc może wyliczyć jedną z nich całkowicie, porównując każdy z jej elementów.

IStructuralEquatablenie jest ogólna i może powodować boksowanie każdej porównywanej wartości. Co więcej, nie jest to bardzo proste w użyciu i już wymaga zakodowania pewnych metod pomocniczych, które go ukrywają.

Z punktu widzenia wydajności może być lepiej użyć czegoś takiego:

bool ArrayEquals<T>(T[] first, T[] second)
{
    if (first == second)
        return true;
    if (first == null || second == null)
        return false;
    if (first.Length != second.Length)
        return false;
    for (var i = 0; i < first.Length; i++)
    {
        if (first[i] != second[i])
            return false;
    }
    return true;
}

Ale oczywiście nie jest to też jakiś „magiczny sposób” sprawdzania równości tablic.

Tak więc obecnie nie, tak naprawdę nie ma odpowiednika Java Arrays.equals()w .Net.


Czy wartość pierwsza i druga nie byłaby prawdą? null == null, prawda?
Jesse Williams

1
Pierwszy test zwróci prawdę, jeśli oba są null. O co ci chodzi?
Frédéric

1
if (first [i]! = second [i]) nie będzie działać z rodzajami. Musisz użyć if (! First [i] .Equals (second [i])).
Jack Griffin
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.