항목의 순서에 관계없이 동일성을 위해 두 컬렉션을 비교


162

C #에서 두 컬렉션을 비교하고 싶지만 이것을 효율적으로 구현하는 가장 좋은 방법은 확실하지 않습니다.

Enumerable.SequenceEqual 에 대한 다른 스레드를 읽었 지만 정확히 내가 원하는 것은 아닙니다.

필자의 경우 두 컬렉션 모두 (주문에 관계없이) 동일한 항목을 포함하면 동일합니다.

예:

collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};

collection1 == collection2; // true

내가 일반적으로하는 일은 한 컬렉션의 각 항목을 반복하여 다른 컬렉션에 있는지 확인한 다음 다른 컬렉션의 각 항목을 반복하여 첫 번째 컬렉션에 있는지 확인하는 것입니다. (길이를 비교하여 시작합니다).

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

그러나 이것은 완전히 올바른 것은 아니며 두 컬렉션을 동등하게 비교하는 가장 효율적인 방법은 아닐 것입니다.

내가 잘못 생각할 수있는 예는 다음과 같습니다.

collection1 = {1, 2, 3, 3, 4}
collection2 = {1, 2, 2, 3, 4}

내 구현과 동일합니다. 각 항목을 찾은 횟수를 세고 두 컬렉션의 개수가 같은지 확인해야합니까?


예제는 일종의 C # (의사 C #이라고 함)에 있지만 원하는 언어로 대답하면 중요하지 않습니다.

참고 : 예제에서 정수를 단순화하기 위해 사용했지만 참조 유형 객체도 사용할 수 있기를 원합니다 (내용이 아닌 객체의 참조 만 비교되기 때문에 키로 올바르게 작동하지 않습니다).


1
알고리즘은 어떻습니까? 모든 것들은 무언가를 비교하고, 일반적인 목록은 linq를 비교합니다. 실제로 우리는 알고리즘을 구식 프로그래머로 사용하지 않을 것이라고 약속 했습니까?
누리 YILMAZ

평등을 확인하지 않고 동등성을 확인하고 있습니다. 이질적이지만 중요한 차이점입니다. 그리고 오래 전에 이것은 좋은 Q + A입니다.
CAD bloke

아래에 설명 된 사전 기반 방법의 조정 버전에 대해 설명하는 이 게시물에 관심 있을 수 있습니다 . 가장 간단한 사전 접근 방식의 한 가지 문제는 .NET의 Dictionary 클래스가 null 키를 허용하지 않기 때문에 null을 올바르게 처리하지 못한다는 것입니다.
ChaseMedallion

답변:


112

Microsoft는 이미 테스트 프레임 워크에서 CollectionAssert를 다룬 것으로 나타났습니다.

비고

두 컬렉션은 같은 수량에 동일한 순서로 요소가 있으면 동일합니다. 동일한 객체를 참조하는 경우가 아니라 값이 동일한 경우 요소가 동일합니다.

리플렉터를 사용하여 AreEquivalent () 뒤의 코드를 수정하여 해당하는 동등 비교기를 만듭니다. null을 고려하고 IEqualityComparer를 구현하며 일부 효율성과 엣지 사례 검사가 있기 때문에 기존 답변보다 더 완벽합니다. 게다가, 그것은 Microsoft입니다 :)

public class MultiSetComparer<T> : IEqualityComparer<IEnumerable<T>>
{
    private readonly IEqualityComparer<T> m_comparer;
    public MultiSetComparer(IEqualityComparer<T> comparer = null)
    {
        m_comparer = comparer ?? EqualityComparer<T>.Default;
    }

    public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == null)
            return second == null;

        if (second == null)
            return false;

        if (ReferenceEquals(first, second))
            return true;

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

            if (firstCollection.Count == 0)
                return true;
        }

        return !HaveMismatchedElement(first, second);
    }

    private bool HaveMismatchedElement(IEnumerable<T> first, IEnumerable<T> second)
    {
        int firstNullCount;
        int secondNullCount;

        var firstElementCounts = GetElementCounts(first, out firstNullCount);
        var secondElementCounts = GetElementCounts(second, out secondNullCount);

        if (firstNullCount != secondNullCount || firstElementCounts.Count != secondElementCounts.Count)
            return true;

        foreach (var kvp in firstElementCounts)
        {
            var firstElementCount = kvp.Value;
            int secondElementCount;
            secondElementCounts.TryGetValue(kvp.Key, out secondElementCount);

            if (firstElementCount != secondElementCount)
                return true;
        }

        return false;
    }

    private Dictionary<T, int> GetElementCounts(IEnumerable<T> enumerable, out int nullCount)
    {
        var dictionary = new Dictionary<T, int>(m_comparer);
        nullCount = 0;

        foreach (T element in enumerable)
        {
            if (element == null)
            {
                nullCount++;
            }
            else
            {
                int num;
                dictionary.TryGetValue(element, out num);
                num++;
                dictionary[element] = num;
            }
        }

        return dictionary;
    }

    public int GetHashCode(IEnumerable<T> enumerable)
    {
        if (enumerable == null) throw new ArgumentNullException(nameof(enumerable));

        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + (val?.GetHashCode() ?? 42);

        return hash;
    }
}

샘플 사용법 :

var set = new HashSet<IEnumerable<int>>(new[] {new[]{1,2,3}}, new MultiSetComparer<int>());
Console.WriteLine(set.Contains(new [] {3,2,1})); //true
Console.WriteLine(set.Contains(new [] {1, 2, 3, 3})); //false

또는 두 컬렉션을 직접 비교하려는 경우 :

var comp = new MultiSetComparer<string>();
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","c","b"})); //true
Console.WriteLine(comp.Equals(new[] {"a","b","c"}, new[] {"a","b"})); //false

마지막으로 선택한 평등 비교기를 사용할 수 있습니다.

var strcomp = new MultiSetComparer<string>(StringComparer.OrdinalIgnoreCase);
Console.WriteLine(strcomp.Equals(new[] {"a", "b"}, new []{"B", "A"})); //true

7
100 % 확실하지는 않지만 귀하의 답변이 리버스 엔지니어링에 대한 Microsoft의 이용 약관을 위반하는 것 같습니다.
Ian Dallas

1
안녕하세요 Ohad, stackoverflow.com/questions/371328/ 주제에서 다음 긴 토론을 읽으십시오. 해시 세트에서 오브젝트 해시 코드를 변경하면 해시 세트에서 해당 조치가 중단되고 예외가 발생할 수 있습니다. 규칙은 다음과 같습니다. 두 객체가 같으면 해시 코드가 동일해야합니다. 두 객체가 동일한 해시 코드를 가지고 있다면 반드시 같을 필요는 없습니다. 해시 코드는 전체 객체의 수명 동안 동일하게 유지되어야합니다! 그렇기 때문에 ICompareable 및 IEqualrity를 ​​강화해야합니다.
James Roeiter

2
@JamesRoeiter 아마도 내 의견이 잘못되었습니다. 사전에 이미 포함 된 해시 코드가있는 경우 사전과의 실제 동등성 을 확인합니다 EqualityComparer(제공 한 또는 EqualityComparer.Default, Reflector 또는 참조 소스를 확인하여이를 확인할 수 있음). 사실,이 메소드가 실행되는 동안 객체가 변경되고 (특히 해시 코드가 변경되면) 결과가 예기치 않게 발생하지만이 컨텍스트 에서이 메소드가 스레드로부터 안전하지 않다는 것을 의미합니다.
Ohad Schneider

1
@JamesRoeiter x와 y가 비교하려는 두 객체라고 가정합니다. 그것들이 다른 해시 코드를 가지고 있다면, 우리는 그것들이 다르다는 것을 알고 있습니다 (동일한 아이템은 동일한 해시 코드를 가지고 있기 때문에) 위의 구현은 정확합니다. 그것들이 동일한 해시 코드를 가지고 있다면, 사전 구현은 지정된 (또는 지정되지 않은 경우)을 사용하여 실제 동등성 을 확인 하고 다시 구현이 올바른지 확인합니다. EqualityComparerEqualityComparer.Default
하드 슈나이더

1
@CADbloke 인터페이스 Equals때문에 메소드 이름을 지정해야합니다 IEqualityComparer<T>. 당신이보아야 할 것은 비교기 자체 의 이름입니다 . 이 경우에는 MultiSetComparer의미가 있습니다.
Ohad Schneider

98

간단하고 매우 효율적인 솔루션은 두 컬렉션을 정렬 한 다음 동일한 지 비교하는 것입니다.

bool equal = collection1.OrderBy(i => i).SequenceEqual(
                 collection2.OrderBy(i => i));

이 알고리즘은 O (N * logN)이며 위의 솔루션은 O (N ^ 2)입니다.

컬렉션에 특정 속성이있는 경우 더 빠른 솔루션을 구현할 수 있습니다. 예를 들어 두 컬렉션이 모두 해시 세트 인 경우 복제본을 포함 할 수 없습니다. 또한 해시 세트에 일부 요소가 포함되어 있는지 확인하는 것이 매우 빠릅니다. 이 경우 귀하와 유사한 알고리즘이 가장 빠를 것입니다.


1
사용하는 System.Linq를 추가하기 만하면됩니다. 처음으로 작동하게 함
Junior Mayhé

이 코드가 루프 내에 있고 collection1이 업데이트되고 collection2가 그대로 유지되면 두 컬렉션에 동일한 객체가 있어도 디버거는이 "같음"변수에 대해 false를 표시합니다.
Junior Mayhé

5
@ Chaulky-OrderBy가 필요하다고 생각합니다. 참조 : dotnetfiddle.net/jA8iwE
Brett

다른 답변은 "위의"로 언급 된 것은 무엇입니까? 아마도 stackoverflow.com/a/50465/3195477 ?
UuDdLrLrSs 19.16.

32

사전 "dict"을 만든 다음 첫 번째 컬렉션의 각 멤버에 대해 dict [member] ++;

그런 다음 동일한 방식으로 두 번째 컬렉션을 반복하지만 각 멤버에 대해 dict [member]-를 수행하십시오.

마지막으로 사전의 모든 멤버를 반복하십시오.

    private bool SetEqual (List<int> left, List<int> right) {

        if (left.Count != right.Count)
            return false;

        Dictionary<int, int> dict = new Dictionary<int, int>();

        foreach (int member in left) {
            if (dict.ContainsKey(member) == false)
                dict[member] = 1;
            else
                dict[member]++;
        }

        foreach (int member in right) {
            if (dict.ContainsKey(member) == false)
                return false;
            else
                dict[member]--;
        }

        foreach (KeyValuePair<int, int> kvp in dict) {
            if (kvp.Value != 0)
                return false;
        }

        return true;

    }

편집 : 내가 알 수있는 한 가장 효율적인 알고리즘과 동일한 순서입니다. 이 알고리즘은 사전이 O (1) 조회를 사용한다고 가정 할 때 O (N)입니다.


이것은 거의 내가 원하는 것입니다. 그러나 정수를 사용하지 않더라도이 작업을 수행하고 싶습니다. 참조 객체를 사용하고 싶지만 사전의 키로 올바르게 작동하지 않습니다.
mbillard

모노, 귀하의 항목이 비교할 수없는 경우 귀하의 질문은 바보입니다. 키를 사전에서 키로 사용할 수 없으면 사용할 수있는 솔루션이 없습니다.
skolima

1
모노는 키를 정렬 할 수 없다는 의미였습니다. 그러나 Daniel의 솔루션은 분명히 나무가 아닌 해시 테이블로 구현되도록 고안되었으며 동등성 테스트와 해시 함수가있는 한 작동합니다.
erickson

물론 도움을 요청했지만 중요한 점이 없어서 받아 들일 수 없습니다 (내 답변에서 다루고 있습니다).
mbillard

1
: FWIW, 당신이 당신의 마지막 foreach 루프와 return 문을 단순화 할 수 있습니다return dict.All(kvp => kvp.Value == 0);
타이슨 윌리엄스

18

이것은 비교 방법 (C #에서)의 일반적인 구현입니다 (D.Jennings의 영향을 크게 받음).

/// <summary>
/// Represents a service used to compare two collections for equality.
/// </summary>
/// <typeparam name="T">The type of the items in the collections.</typeparam>
public class CollectionComparer<T>
{
    /// <summary>
    /// Compares the content of two collections for equality.
    /// </summary>
    /// <param name="foo">The first collection.</param>
    /// <param name="bar">The second collection.</param>
    /// <returns>True if both collections have the same content, false otherwise.</returns>
    public bool Execute(ICollection<T> foo, ICollection<T> bar)
    {
        // Declare a dictionary to count the occurence of the items in the collection
        Dictionary<T, int> itemCounts = new Dictionary<T,int>();

        // Increase the count for each occurence of the item in the first collection
        foreach (T item in foo)
        {
            if (itemCounts.ContainsKey(item))
            {
                itemCounts[item]++;
            }
            else
            {
                itemCounts[item] = 1;
            }
        }

        // Wrap the keys in a searchable list
        List<T> keys = new List<T>(itemCounts.Keys);

        // Decrease the count for each occurence of the item in the second collection
        foreach (T item in bar)
        {
            // Try to find a key for the item
            // The keys of a dictionary are compared by reference, so we have to
            // find the original key that is equivalent to the "item"
            // You may want to override ".Equals" to define what it means for
            // two "T" objects to be equal
            T key = keys.Find(
                delegate(T listKey)
                {
                    return listKey.Equals(item);
                });

            // Check if a key was found
            if(key != null)
            {
                itemCounts[key]--;
            }
            else
            {
                // There was no occurence of this item in the first collection, thus the collections are not equal
                return false;
            }
        }

        // The count of each item should be 0 if the contents of the collections are equal
        foreach (int value in itemCounts.Values)
        {
            if (value != 0)
            {
                return false;
            }
        }

        // The collections are equal
        return true;
    }
}

12
훌륭한 작업이지만 참고 : 1. Daniel Jennings 솔루션과 달리, 이것은 모음 모음의 foreach 루프 내에서 find 함수로 인해 O (N)가 아니라 O (N ^ 2)입니다. 2. 코드를 더 이상 수정하지 않고 ICollection <T> 대신 IEnumerable <T>을 허용하도록 메서드를 일반화 할 수 있습니다.
Ohad Schneider

The keys of a dictionary are compared by reference, so we have to find the original key that is equivalent to the "item"-사실이 아닙니다. 이 알고리즘은 잘못된 가정을 기반으로하며 작동하는 동안 매우 비효율적입니다.
Antonín Lejsek


7

shouldly 를 사용하는 경우 ContainsAll과 함께 ShouldAllBe를 사용할 수 있습니다.

collection1 = {1, 2, 3, 4};
collection2 = {2, 4, 1, 3};

collection1.ShouldAllBe(item=>collection2.Contains(item)); // true

마지막으로 확장을 작성할 수 있습니다.

public static class ShouldlyIEnumerableExtensions
{
    public static void ShouldEquivalentTo<T>(this IEnumerable<T> list, IEnumerable<T> equivalent)
    {
        list.ShouldAllBe(l => equivalent.Contains(l));
    }
}

최신 정보

ShouldBe 메서드 에는 선택적 매개 변수가 있습니다 .

collection1.ShouldBe(collection2, ignoreOrder: true); // true

1
방금 최신 버전bool ignoreOrder 에서 shouldBe 메소드에 매개 변수가 있음을 발견했습니다 .
Pier-Lionel Sgard

5

편집 : 나는 이것이 실제로 세트에서만 작동한다는 것을 알 자마자 깨달았습니다. 중복 항목이있는 컬렉션을 올바르게 처리하지 못할 것입니다. 예를 들어 {1, 1, 2} 및 {2, 2, 1}은이 알고리즘의 관점에서 동일한 것으로 간주됩니다. 그러나 컬렉션이 세트 (또는 그 평등을 그렇게 측정 할 수있는 경우) 인 경우 아래 내용이 유용하기를 바랍니다.

내가 사용하는 솔루션은 다음과 같습니다.

return c1.Count == c2.Count && c1.Intersect(c2).Count() == c1.Count;

Linq는 표지 아래 사전을 수행하므로 O (N)이기도합니다. (컬렉션이 같은 크기가 아닌 경우 O (1)입니다).

Daniel이 제안한 "SetEqual"메소드, Igor가 제안한 OrderBy / SequenceEquals 메소드 및 제안을 사용하여 상태 점검을 수행했습니다. 결과는 다음과 같습니다. Igor의 경우 O (N * LogN), 광산 및 Daniel의 경우 O (N)입니다.

Linq 교차 코드의 단순성이 선호되는 솔루션이라고 생각합니다.

__Test Latency(ms)__
N, SetEquals, OrderBy, Intersect    
1024, 0, 0, 0    
2048, 0, 0, 0    
4096, 31.2468, 0, 0    
8192, 62.4936, 0, 0    
16384, 156.234, 15.6234, 0    
32768, 312.468, 15.6234, 46.8702    
65536, 640.5594, 46.8702, 31.2468    
131072, 1312.3656, 93.7404, 203.1042    
262144, 3765.2394, 187.4808, 187.4808    
524288, 5718.1644, 374.9616, 406.2084    
1048576, 11420.7054, 734.2998, 718.6764    
2097152, 35090.1564, 1515.4698, 1484.223

이 코드의 유일한 문제는 값 형식을 비교하거나 포인터를 참조 형식과 비교할 때만 작동한다는 것입니다. 컬렉션에 동일한 객체의 서로 다른 두 인스턴스가있을 수 있으므로 각각을 비교하는 방법을 지정할 수 있어야합니다. 교차 메소드에 비교 대리자를 전달할 수 있습니까?
mbillard

물론, 당신은 비교 대의원을 전달할 수 있습니다. 그러나 내가 추가 한 세트에 대한 위의 제한 사항에 유의하십시오. 이로 인해 적용 가능성이 크게 제한됩니다.

Intersect 메서드는 고유 한 컬렉션을 반환합니다. a = {1,1,2} 및 b = {2,2,1}이 주어지면 a.Intersect (b) .Count ()! = a.Count이므로식이 올바르게 false를 반환합니다. {1,2} .Count! = {1,1,2} .Count 링크 참조 [/ link] (두 측면은 비교 전에 구별된다는 점에 유의하십시오.)
Griffin

5

반복이없고 순서가없는 경우 다음 EqualityComparer를 사용하여 컬렉션을 사전 키로 사용할 수 있습니다.

public class SetComparer<T> : IEqualityComparer<IEnumerable<T>> 
where T:IComparable<T>
{
    public bool Equals(IEnumerable<T> first, IEnumerable<T> second)
    {
        if (first == second)
            return true;
        if ((first == null) || (second == null))
            return false;
        return first.ToHashSet().SetEquals(second);
    }

    public int GetHashCode(IEnumerable<T> enumerable)
    {
        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + val.GetHashCode();

        return hash;
    }
}

다음 은 내가 사용한 ToHashSet () 구현입니다. 해시 코드 알고리즘 (존 소총의 방법으로) 유효 자바에서 비롯됩니다.


비교기 클래스의 직렬화 가능 지점은 무엇입니까? : o 또한 ISet<T>세트를 의미 하도록 입력을 변경할 수 있습니다 (예 : 중복 없음).
nawfal

나는 그것을 직렬화 ...에 관해서를 표시 할 때 @nawfal 덕분에, 내가 무슨 생각 모른다 ISet, 여기에 아이디어는 치료를하는 것이 었습니다 IEnumerable(당신이 가지고 있기 때문에 세트로 IEnumerable이상에서 0 upvotes을 고려하지만, 우선를) 날카로운 생각하지 않았을 수 있습니다 오년 : P
오핫 슈나이더

4
static bool SetsContainSameElements<T>(IEnumerable<T> set1, IEnumerable<T> set2) {
    var setXOR = new HashSet<T>(set1);
    setXOR.SymmetricExceptWith(set2);
    return (setXOR.Count == 0);
}

솔루션에는 .NET 3.5와 System.Collections.Generic네임 스페이스 가 필요 합니다. MS에 따르면 , SymmetricExceptWithO (N + m) 의 조작, N 첫번째 세트의 요소의 개수를 나타내는 m 번째의 요소의 수를 표현한다. 필요한 경우 언제든지이 함수에 동등 비교자를 추가 할 수 있습니다.


3

.Except ()를 사용하지 않는 이유

// Create the IEnumerable data sources.
string[] names1 = System.IO.File.ReadAllLines(@"../../../names1.txt");
string[] names2 = System.IO.File.ReadAllLines(@"../../../names2.txt");
// Create the query. Note that method syntax must be used here.
IEnumerable<string> differenceQuery =   names1.Except(names2);
// Execute the query.
Console.WriteLine("The following lines are in names1.txt but not names2.txt");
foreach (string s in differenceQuery)
     Console.WriteLine(s);

http://msdn.microsoft.com/en-us/library/bb397894.aspx


2
Except중복 항목을 계산하는 데는 작동하지 않습니다. 세트 {1,2,2} 및 {1,1,2}에 대해서는 true를 리턴합니다.
Cristian Diaconescu

@CristiDiaconescu ".Distinct ()"를 수행하여 중복을 제거 할 수 있습니다
Korayem

OP가 요청하고 [1, 1, 2] != [1, 2, 2]있습니다. 사용 Distinct하면 그것들이 똑같아 보일 것입니다.
Cristian Diaconescu

2

중복 된 게시물이지만 컬렉션 비교를위한 솔루션을 확인하십시오 . 꽤 간단합니다.

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

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

원본 게시물은 여기에 있습니다 .


1

erickson 은 거의 옳습니다. 복제본 수를 맞추기 위해 Bag 을 원합니다 . Java에서는 다음과 같습니다.

(new HashBag(collection1)).equals(new HashBag(collection2))

C #에 내장 Set 구현이 있다고 확신합니다. 나는 그것을 먼저 사용할 것입니다. 성능에 문제가있는 경우 항상 다른 Set 구현을 사용할 수 있지만 동일한 Set 인터페이스를 사용할 수 있습니다.


1

다음은 누군가에게 유용 할 경우를 대비하여 ohadsc의 답변에 대한 확장 방법 변형입니다.

static public class EnumerableExtensions 
{
    static public bool IsEquivalentTo<T>(this IEnumerable<T> first, IEnumerable<T> second)
    {
        if ((first == null) != (second == null))
            return false;

        if (!object.ReferenceEquals(first, second) && (first != null))
        {
            if (first.Count() != second.Count())
                return false;

            if ((first.Count() != 0) && HaveMismatchedElement<T>(first, second))
                return false;
        }

        return true;
    }

    private static bool HaveMismatchedElement<T>(IEnumerable<T> first, IEnumerable<T> second)
    {
        int firstCount;
        int secondCount;

        var firstElementCounts = GetElementCounts<T>(first, out firstCount);
        var secondElementCounts = GetElementCounts<T>(second, out secondCount);

        if (firstCount != secondCount)
            return true;

        foreach (var kvp in firstElementCounts)
        {
            firstCount = kvp.Value;
            secondElementCounts.TryGetValue(kvp.Key, out secondCount);

            if (firstCount != secondCount)
                return true;
        }

        return false;
    }

    private static Dictionary<T, int> GetElementCounts<T>(IEnumerable<T> enumerable, out int nullCount)
    {
        var dictionary = new Dictionary<T, int>();
        nullCount = 0;

        foreach (T element in enumerable)
        {
            if (element == null)
            {
                nullCount++;
            }
            else
            {
                int num;
                dictionary.TryGetValue(element, out num);
                num++;
                dictionary[element] = num;
            }
        }

        return dictionary;
    }

    static private int GetHashCode<T>(IEnumerable<T> enumerable)
    {
        int hash = 17;

        foreach (T val in enumerable.OrderBy(x => x))
            hash = hash * 23 + val.GetHashCode();

        return hash;
    }
}

아이디어가 얼마나 잘 수행됩니까?
nawfal

나는 이것을 소규모 컬렉션에만 사용하므로 Big-O 복잡성이나 벤치마킹에 대해 생각하지 않았습니다. HaveMismatchedElements만으로는 O (M * N)이므로 큰 컬렉션에서는 제대로 수행되지 않을 수 있습니다.
Eric J.

경우 IEnumerable<T>의 쿼리는 다음 호출은 Count()좋은 생각이 아니다. Ohad의 원래 답변의 접근 방식이 ICollection<T>더 좋은지 확인 하는 것이 좋습니다.
nawfal

1

이것 보다 개선 된 솔루션 있습니다.

public static bool HasSameElementsAs<T>(
        this IEnumerable<T> first, 
        IEnumerable<T> second, 
        IEqualityComparer<T> comparer = null)
    {
        var firstMap = first
            .GroupBy(x => x, comparer)
            .ToDictionary(x => x.Key, x => x.Count(), comparer);

        var secondMap = second
            .GroupBy(x => x, comparer)
            .ToDictionary(x => x.Key, x => x.Count(), comparer);

        if (firstMap.Keys.Count != secondMap.Keys.Count)
            return false;

        if (firstMap.Keys.Any(k1 => !secondMap.ContainsKey(k1)))
            return false;

        return firstMap.Keys.All(x => firstMap[x] == secondMap[x]);
    }

0

이 문제에 대한 많은 해결책이 있습니다. 중복에 신경 쓰지 않으면 둘 다 정렬하지 않아도됩니다. 먼저 항목 수가 같은지 확인하십시오. 그런 다음 컬렉션 중 하나를 정렬하십시오. 그런 다음 정렬 된 컬렉션의 두 번째 컬렉션에서 각 항목을 검색합니다. 주어진 항목을 찾지 못하면 중지하고 false를 반환하십시오. 이것의 복잡성 :-첫 번째 컬렉션 정렬 : N Log (N)-두 번째에서 첫 번째로 각 항목 검색 : NLOG (N)이므로 2 * N * LOG (N)로 일치하고 모든 것을 찾는다고 가정합니다. 이것은 두 가지 정렬의 복잡성과 유사합니다. 또한 차이가있는 경우 더 일찍 중지 할 수있는 이점이 있습니다. 그러나이 비교를 시작하기 전에 둘 다 정렬되고 qsort와 같은 것을 사용하여 정렬을 시도하면 정렬 비용이 더 많이 듭니다. 이에 대한 최적화가 있습니다. 요소의 범위를 알고있는 소규모 컬렉션에 유용한 또 다른 대안은 비트 마스크 인덱스를 사용하는 것입니다. 이렇게하면 O (n) 성능이 제공됩니다. 또 다른 대안은 해시를 사용하여 찾아 보는 것입니다. 소규모 컬렉션의 경우 일반적으로 정렬 또는 비트 마스크 인덱스를 수행하는 것이 훨씬 좋습니다. 해시 테이블은 더 나쁜 지역성이라는 단점을 가지고 있으므로 명심하십시오. 다시, 그것은 당신이 경우에만 중복에 신경 쓰지 마십시오. 중복을 설명하려면 둘 다 정렬하십시오.


0

대부분의 경우 적합한 답변은 Igor Ostrovsky 중 하나이며 다른 답변은 객체 해시 코드를 기반으로합니다. 그러나 객체에 대한 해시 코드를 생성 할 때 객체 Id 필드 (데이터베이스 엔터티의 경우)와 같은 IMMUTABLE 필드 만 기반으로 수행합니다. Equals 메서드를 재정의 할 때 GetHashCode를 재정의해야하는 이유는 무엇입니까?

즉, 두 컬렉션을 비교할 경우 다른 항목의 필드가 같지 않더라도 비교 방법에서 결과가 사실 일 수 있습니다. 컬렉션을 심도있게 비교하려면 Igor의 방법을 사용하고 IEqualirity를 ​​구현해야합니다.

저와 Mr.Schnider의 의견을 가장 많이 투표 한 게시물에서 읽으십시오.

제임스


0

IEnumerable<T>(세트가 바람직하지 않은 경우) 및 "순서 무시" 에서 중복을 허용하면 을 사용할 수 있습니다 .GroupBy().

나는 복잡성 측정에 대한 전문가는 아니지만, 초보적인 이해는 이것이 O (n)이어야한다는 것입니다. 나는 O (n ^ 2)와 같은 다른 O (n) 연산 내에서 O (n) 연산을 수행하여 오는 것으로 이해 ListA.Where(a => ListB.Contains(a)).ToList()합니다. ListB의 모든 항목은 ListA의 각 항목과 동일한 지 평가됩니다.

내가 말했듯이, 복잡성에 대한 나의 이해는 제한적이므로 내가 틀렸다면 이것을 수정하십시오.

public static bool IsSameAs<T, TKey>(this IEnumerable<T> source, IEnumerable<T> target, Expression<Func<T, TKey>> keySelectorExpression)
    {
        // check the object
        if (source == null && target == null) return true;
        if (source == null || target == null) return false;

        var sourceList = source.ToList();
        var targetList = target.ToList();

        // check the list count :: { 1,1,1 } != { 1,1,1,1 }
        if (sourceList.Count != targetList.Count) return false;

        var keySelector = keySelectorExpression.Compile();
        var groupedSourceList = sourceList.GroupBy(keySelector).ToList();
        var groupedTargetList = targetList.GroupBy(keySelector).ToList();

        // check that the number of grouptings match :: { 1,1,2,3,4 } != { 1,1,2,3,4,5 }
        var groupCountIsSame = groupedSourceList.Count == groupedTargetList.Count;
        if (!groupCountIsSame) return false;

        // check that the count of each group in source has the same count in target :: for values { 1,1,2,3,4 } & { 1,1,1,2,3,4 }
        // key:count
        // { 1:2, 2:1, 3:1, 4:1 } != { 1:3, 2:1, 3:1, 4:1 }
        var countsMissmatch = groupedSourceList.Any(sourceGroup =>
                                                        {
                                                            var targetGroup = groupedTargetList.Single(y => y.Key.Equals(sourceGroup.Key));
                                                            return sourceGroup.Count() != targetGroup.Count();
                                                        });
        return !countsMissmatch;
    }

0

이 간단한 솔루션IEnumerable제네릭 형식을 강제 로 구현 IComparable합니다. 의 때문에 OrderBy'의 정의.

그러한 가정을하고 싶지 않지만이 솔루션을 계속 사용하려면 다음 코드를 사용할 수 있습니다.

bool equal = collection1.OrderBy(i => i?.GetHashCode())
   .SequenceEqual(collection2.OrderBy(i => i?.GetHashCode()));

0

Unit Testing Assertions의 목적을 위해 비교한다면, 비교를하기 전에 창 밖으로 약간의 효율성을 내고 각 목록을 문자열 표현 (csv)으로 변환하는 것이 합리적 일 수 있습니다. 이렇게하면 기본 테스트 어설 션 메시지에 오류 메시지 내의 차이점이 표시됩니다.

용법:

using Microsoft.VisualStudio.TestTools.UnitTesting;

// define collection1, collection2, ...

Assert.Equal(collection1.OrderBy(c=>c).ToCsv(), collection2.OrderBy(c=>c).ToCsv());

도우미 확장 방법 :

public static string ToCsv<T>(
    this IEnumerable<T> values,
    Func<T, string> selector,
    string joinSeparator = ",")
{
    if (selector == null)
    {
        if (typeof(T) == typeof(Int16) ||
            typeof(T) == typeof(Int32) ||
            typeof(T) == typeof(Int64))
        {
            selector = (v) => Convert.ToInt64(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(decimal))
        {
            selector = (v) => Convert.ToDecimal(v).ToStringInvariant();
        }
        else if (typeof(T) == typeof(float) ||
                typeof(T) == typeof(double))
        {
            selector = (v) => Convert.ToDouble(v).ToString(CultureInfo.InvariantCulture);
        }
        else
        {
            selector = (v) => v.ToString();
        }
    }

    return String.Join(joinSeparator, values.Select(v => selector(v)));
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.