컬렉션을 비교하는 기본 제공 방법이 있습니까?


178

내 Equals 방법에서 두 컬렉션의 내용을 비교하고 싶습니다. 사전과 IList가 있습니다. 이 작업을 수행하는 기본 제공 방법이 있습니까?

편집 : 두 개의 사전과 두 개의 IList를 비교하고 싶기 때문에 평등의 의미가 분명하다고 생각합니다. 두 사전에 동일한 값에 매핑 된 동일한 키가 포함되어 있으면 동일합니다.


순서에 관계없이 IList? 질문이 모호합니다.
nawfal

Enumerable.SequenceEqual그리고 ISet.SetEquals이 기능의 버전을 제공합니다. 순서에 구애받지 않고 복제본이있는 컬렉션을 사용하려면 직접 롤백해야합니다. 이 게시물
ChaseMedallion

답변:


185

Enumerable.SequenceEqual

지정된 IEqualityComparer (T)를 사용하여 요소를 비교하여 두 시퀀스가 ​​같은지 확인합니다.

목록과 사전을 직접 비교할 수는 없지만 사전의 값 목록을 목록과 비교할 수는 있습니다.


52
문제는 SequenceEqual에서 요소의 순서가 동일해야한다는 것입니다. Dictionary 클래스는 열거시 키 또는 값의 순서를 보장하지 않으므로 SequenceEqual을 사용하려면 .Keys 및 .Values를 먼저 정렬해야합니다!
Orion Edwards

3
@Orion : ... 주문 차이를 감지하지 않으려면 물론 :-)
schoetbi

30
@ schoetbi : 왜 주문을 보장하지 않는 컨테이너의 주문 차이를 감지하고 싶 습니까?
Matti Virkkunen

4
@ schoetbi : IEnumerable에서 특정 요소를 가져 오기위한 것입니다. 그러나, 사전 그렇게하지 보장 순서를 수행 .Keys하고 .Values그들이 같은 느낌 임의의 순서로 키와 값을 반환 할 수 있으며, 사전이 아니라 수정으로 순서가 변경 될 수 있습니다. 사전이 무엇인지 아닌지 읽어보십시오.
Matti Virkkunen

5
MS의 TestTools와 NUnit은 CollectionAssert.AreEquivalent를 제공합니다
tymtam

44

다른 사람들이 제안하고 언급했듯이 SequenceEqual순서에 민감합니다. 이를 해결하기 위해 키를 기준으로 사전을 정렬하고 (고유하므로 정렬은 항상 안정적 임)을 사용할 수 있습니다 SequenceEqual. 다음 표현식은 내부 순서와 상관없이 두 사전이 동일한 지 확인합니다.

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

편집 : Jeppe Stig Nielsen이 지적한 것처럼 일부 객체에는와 IComparer<T>호환되지 않는 객체가있어 IEqualityComparer<T>잘못된 결과를 생성합니다. 이러한 객체와 함께 키를 사용하는 경우 IComparer<T>해당 키에 올바른 키를 지정해야 합니다. 예를 들어,이 문제를 나타내는 문자열 키를 사용하면 올바른 결과를 얻으려면 다음을 수행해야합니다.

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

키 유형이 없으면 CompareTo어떻게됩니까? 그러면 솔루션이 폭발합니다. 키 유형에 기본 동등 비교기와 호환되지 않는 기본 비교기가있는 경우 어떻게해야합니까? 이 경우에 해당 string합니다. 예를 들어, 이러한 사전 (암시 적 기본 평등 비교를 사용하는)은 테스트에 실패합니다 (내가 알고있는 모든 문화 정보에서) :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 : 사이의 호환성에 관해서는 IComparerIEqualityComparer-이 문제를 인식하지 않았다, 매우 흥미로운! 가능한 해결책으로 답변을 업데이트했습니다. 의 부족에 CompareTo대해 개발자는 OrderBy()메소드에 제공된 대리자가 비슷한 것을 반환 해야한다고 생각합니다 . 나는 이것이 OrderBy()심지어 사전 비교 밖에서 도 사용된다고 생각합니다 .
Allon Guralnek

15

언급 한 외에 SequenceEqual 어느

두 목록의 길이가 같고 해당 요소가 비교 자에 따라 동일하게 비교되는 경우 true

(기본 비교기, 즉 overriden 일 수 있음 Equals())

.Net4 에는 ISet객체에 SetEquals 가 있음을 언급 할 가치가 있습니다.

요소의 순서와 중복 요소를 무시합니다.

따라서 객체 목록을 원하지만 특정 순서로 정렬 할 필요는없는 경우 ISet(와 같은 HashSet)이 올바른 선택 일 수 있습니다.


7

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
SequenceEqual은 값이 사전에서 신뢰할 수있는 순서로 나오기를 기대하기 때문에 신뢰할 수 없습니다. 사전은 순서와 사전에 대해 그런 보증을하지 않습니다. 키는 [1, 2] 대신 [2, 1]로 나올 수 있습니다. 및 테스트가 실패
오리온 에드워즈

5

.NET 모음을 비교할 수있는 강력한 도구가 없습니다. 아래 링크에서 찾을 수있는 간단한 솔루션을 개발했습니다.

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

순서에 관계없이 평등 비교를 수행합니다.

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

항목이 추가 / 제거되었는지 확인합니다.

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

사전의 어떤 항목이 변경되었는지 확인할 수 있습니다.

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
물론 .NET에는 컬렉션을 비교할 수있는 강력한 도구가 있습니다 (세트 기반 작업 임). , is , is 및 is 와 .Removed동일 합니다. LINQ와 거의 모든 비교를 수행 할 수 있습니다. list1.Except(list2).Addedlist2.Except(list1).Equallist1.Intersect(list2).Differentoriginal.Join(changed, left => left.Key, right => right.Key, (left, right) => left.Value == right.Value)
Allon Guralnek

3
수정 : .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). OldValue = left.Value이전 값도 필요한 경우 추가 할 수도 있습니다 .
Allon Guralnek

3
@AllonGuralnek 귀하의 제안은 좋지만 목록이 실제 세트가 아닌 경우-목록에 동일한 객체가 여러 번 포함되는 경우는 처리하지 않습니다. {1, 2}와 {1, 2, 2}를 비교하면 추가 / 제거 된 내용이 반환되지 않습니다.
Niall Connaughton

1
@CrispinH Archive.org는 Robert Bouillon의 친구 입니다. ; ^)
ruffin

4

Enumerable.SequenceEqual 메소드 (매일 무언가를 배우고 있습니다 ...)에 대해 몰랐지만 확장 메소드를 사용하는 것이 좋습니다. 이 같은:

    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;

    }

흥미롭게도 SequenceEqual에 대해 2 초가 걸린 후에 Microsoft가 내가 설명한 기능을 구축 한 것 같습니다.


4

이것은 귀하의 질문에 직접 대답하지는 않지만 MS의 TestTools와 NUnit은

 CollectionAssert.AreEquivalent

그것은 당신이 원하는 것을 거의 수행합니다.


내 NUnit 테스트를 위해 이것을 찾고 있었다
Blem

1

컬렉션을 비교하기 위해 LINQ를 사용할 수도 있습니다. Enumerable.Intersect동일한 모든 쌍을 반환합니다. 다음과 같이 두 사전을 비교할 수 있습니다.

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

dict2모든 키를 포함 할 수 있으므로 첫 번째 비교가 필요 합니다 dict1.

또한 사용의 변화를 생각 사용할 수 있습니다 Enumerable.ExceptEnumerable.Union유사한 결과가 리드. 그러나 세트 간의 정확한 차이를 결정하는 데 사용할 수 있습니다.


1

이 예제는 어떻습니까 :

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

예의 : https://www.dotnetperls.com/dictionary-equals


value! = pair.Value는 참조 비교를하고 있습니다. 대신 Equals를 사용하십시오
kofifus

1

정렬 된 컬렉션 (List, Array)의 경우 SequenceEqual

해시 셋용 SetEquals

사전의 경우 다음을 수행 할 수 있습니다.

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

(보다 최적화 된 솔루션은 정렬을 사용하지만을 필요로합니다 IComparable<TValue>)


0

컬렉션 프레임 워크에는 평등이라는 개념이 없습니다. 당신이 그것에 대해 생각한다면 주관적이지 않은 컬렉션을 비교할 방법이 없습니다. 예를 들어 IList를 Dictionary와 비교할 때 모든 키가 IList에 있거나 모든 값이 IList에 있거나 둘 다 IList에 있으면 모두 동일합니까? 사용 목적에 대한 지식없이이 두 컬렉션을 비교할 수있는 확실한 방법은 없으므로 범용 equals 방법은 의미가 없습니다.



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

적어도 나는 그렇게 믿지 않았을 것입니다. 컬렉션 평등은 아마도 사용자 정의 동작 때문일 수 있습니다.

컬렉션의 요소는 순서가 자연 스럽지만 특정 순서가 아니어야합니다. 비교 알고리즘이 의존하는 것은 아닙니다. 두 가지 컬렉션이 있다고 가정 해보십시오.

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

그들은 평등합니까? 당신은 알아야하지만 당신의 관점이 무엇인지 모르겠습니다.

알고리즘이 정렬 규칙을 제공 할 때까지 콜렉션은 기본적으로 개념적으로 정렬되지 않습니다. 페이지 매김을 시도 할 때 SQL Server가주의를 기울이는 것과 마찬가지로 정렬 규칙을 제공해야합니다.

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

또 다른 두 컬렉션 :

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

다시, 그들은 평등합니까? 당신은 저에게 말해 ..

컬렉션의 요소 반복성은 다른 시나리오에서 그 역할을하며 일부 컬렉션 Dictionary<TKey, TValue>은 반복되는 요소를 허용하지 않습니다.

나는 이러한 종류의 평등이 응용 프로그램 정의이며 따라서 프레임 워크가 가능한 모든 구현을 제공하지는 않았다고 생각합니다.

일반적으로 Enumerable.SequenceEqual충분하지만 다음과 같은 경우에는 false를 반환합니다.

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

나는 이것과 같은 질문에 대한 답변 (당신은 그들을 위해 구글 일 수도 있음)과 내가 일반적으로 사용할 것을 읽었다 :

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

즉, 하나의 컬렉션은 원래 순서를 고려하지 않고 반복되는 시간을 포함하여 요소에서 다른 컬렉션을 나타냅니다. 구현에 대한 몇 가지 참고 사항 :

  • GetHashCode()평등이 아닌 질서를위한 것입니다. 이 경우에는 충분하다고 생각합니다

  • Count() 실제로 컬렉션을 열거하지 않고 직접 속성 구현에 속합니다. ICollection<T>.Count

  • 참고 문헌이 동일하면 Boris입니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.