차이점에 대해 두 개의 일반 목록을 비교하는 가장 빠른 방법


214

두 개의 대규모 (> 50.000 개 항목)를 비교하는 가장 빠르며 (최소한 자원 집약적) 결과적으로 아래 목록과 같은 두 개의 목록이 있습니다.

  1. 첫 번째 목록에는 나타나지만 두 번째 목록에는 나타나지 않는 항목
  2. 두 번째 목록에는 나타나지만 첫 번째 목록에는 나타나지 않는 항목

현재 List 또는 IReadOnlyCollection으로 작업하고 있으며 linq 쿼리 에서이 문제를 해결합니다.

var list1 = list.Where(i => !list2.Contains(i)).ToList();
var list2 = list2.Where(i => !list.Contains(i)).ToList();

그러나 이것은 내가 원하는만큼 성능이 좋지 않습니다. 많은 목록을 처리해야 할 때이 작업을 더 빠르고 덜 집중적으로 수행 할 생각이 있습니까?

답변:


454

사용 Except:

var firstNotSecond = list1.Except(list2).ToList();
var secondNotFirst = list2.Except(list1).ToList();

나는 이것보다 실제로 조금 더 빠른 접근법이 있다고 생각하지만, 이것조차도 O (N * M) 접근법보다 훨씬 빠를 것입니다.

이들을 결합하려면 위와 함께 return 문을 사용하여 메서드를 만들 수 있습니다.

return !firstNotSecond.Any() && !secondNotFirst.Any();

주목해야 할 점은 문제의 원래 코드와 여기의 솔루션 사이에 결과에 차이 가 있다는 것 입니다 . 한 목록에있는 중복 요소는 내 코드로 한 번만보고되지만 많은 수로보고됩니다 원래 코드에서 발생하는 시간.

예를 들어 [1, 2, 2, 2, 3]및의 목록을 사용 [1]하면 "list1의 요소이지만 list2가 아닌 요소"는 원래 코드의 결과가 [2, 2, 2, 3]됩니다. 내 코드로는 그냥 있습니다 [2, 3]. 많은 경우에 문제가되지는 않지만 알아 둘 가치가 있습니다.


8
이것은 정말 큰 성능 향상입니다! 이 답변에 감사드립니다.
Frank

2
두 개의 거대한 목록이 궁금합니다. 비교하기 전에 정렬하는 것이 유용합니까? 또는 확장 방법을 제외하고 전달 된 목록이 이미 정렬되어 있습니다.
Larry

9
@Larry : 정렬되지 않았습니다. 해시 세트를 빌드합니다.
Jon Skeet

2
@PranavSingh : 적절한 평등을 가진 모든 것에 작동합니다. 따라서 사용자 정의 유형이 재정의 Equals(object)및 / 또는 구현 IEquatable<T>하는 경우 괜찮습니다.
Jon Skeet

2
@ k2ibegin : IEquatable<T>구현 또는 object.Equals(object)메소드를 사용하는 기본 동등 비교기를 사용합니다 . 최소한의 재현 가능한 예를 사용하여 새로운 질문을 작성해야하는 것처럼 들립니다 . 실제로 의견을 진단 할 수는 없습니다.
Jon Skeet

40

더 효율적인 방법은 다음과 Enumerable.Except같습니다.

var inListButNotInList2 = list.Except(list2);
var inList2ButNotInList = list2.Except(list);

이 방법은 지연된 실행을 사용하여 구현됩니다. 예를 들어 다음과 같이 쓸 수 있습니다.

var first10 = inListButNotInList2.Take(10);

내부적으로 Set<T>객체를 비교하기 위해 a 를 사용하기 때문에 효율적 입니다. 먼저 두 번째 시퀀스에서 고유 한 모든 값을 수집 한 다음 첫 번째 결과를 스트리밍하여 이전에 보지 않았는지 확인합니다.


1
흠. 상당히 연기되지 않았습니다. 부분적으로 연기되었다고 말하고 싶습니다. 완료 Set<T>는 두 번째 시퀀스에서 작성되고 (즉, 완전히 반복되고 저장 됨) 첫 번째 시퀀스에서 추가 할 수있는 항목이 생성됩니다.
쓰셨

2
@ spender, 그것은 수의 값이 게으르게 실행되는 것이 아니라 시작시에 저장 Where되기 때문에 실행 이 부분적으로 지연 된다고 말하는 것과 같습니다 . list.Where(x => x.Id == 5)5
jwg

27

Enumerable.SequenceEqual 메서드

등식 비교기에 따라 두 시퀀스가 ​​같은지 여부를 결정합니다. MS.Docs

Enumerable.SequenceEqual(list1, list2);

이것은 모든 기본 데이터 유형에 적용됩니다. 사용자 정의 객체에서 사용해야하는 경우 구현해야합니다.IEqualityComparer

동등성에 대한 객체 비교를 지원하는 방법을 정의합니다.

IEqualityComparer 인터페이스

동등성에 대한 객체 비교를 지원하는 방법을 정의합니다. IEqualityComparer를위한 MS.Docs


이것이 정답이어야합니다. 문제는 SETS가 아니라 요소의 중복을 포함 할 수있는 LISTS에 관한 것입니다.
Adrian Nasui

3
결과 SequenceEqual가 단순 하다는 것을 감안할 때 이것이 어떻게 대답 할 수 있는지 보지 못합니다 bool. OP는 두 가지 결과 목록을 원하고 세트 작업 측면에서 원하는 것을 설명합니다. "첫 번째 목록에는 표시되지만 두 번째 목록에는 표시되지 않는 항목" 순서가 적절하다는 표시는 없지만 SequenceEqual 순서가 적절 하다고 간주합니다. 이것은 완전히 다른 질문에 대답하는 것으로 보입니다.
Jon Skeet

그렇습니다. 맞습니다.
제가이

9

결과를 대소 문자를 구분하지 않으려면 다음이 작동합니다.

List<string> list1 = new List<string> { "a.dll", "b1.dll" };
List<string> list2 = new List<string> { "A.dll", "b2.dll" };

var firstNotSecond = list1.Except(list2, StringComparer.OrdinalIgnoreCase).ToList();
var secondNotFirst = list2.Except(list1, StringComparer.OrdinalIgnoreCase).ToList();

firstNotSecondb1.dll 이 포함 됩니다

secondNotFirstb2.dll 이 포함 됩니다


5

이 문제는 아니지만 목록을 동등하고 그렇지 않은 비교하는 코드가 있습니다! 동일한 객체 :

public class EquatableList<T> : List<T>, IEquatable<EquatableList<T>> where    T : IEquatable<T>

/// <summary>
/// True, if this contains element with equal property-values
/// </summary>
/// <param name="element">element of Type T</param>
/// <returns>True, if this contains element</returns>
public new Boolean Contains(T element)
{
    return this.Any(t => t.Equals(element));
}

/// <summary>
/// True, if list is equal to this
/// </summary>
/// <param name="list">list</param>
/// <returns>True, if instance equals list</returns>
public Boolean Equals(EquatableList<T> list)
{
    if (list == null) return false;
    return this.All(list.Contains) && list.All(this.Contains);
}

1
이것이 사용자 정의 데이터 유형을 비교할 수 있어야하는 것입니다. 그런 다음Except
Pranav Singh

정렬 가능한 유형으로 더 잘 할 수 있습니다. 이것은 O (n ^ 2)에서 실행되는 반면 O (nlogn)은 수행 할 수 있습니다.
yuvalm2

3

이 방법으로 시도하십시오 :

var difList = list1.Where(a => !list2.Any(a1 => a1.id == a.id))
            .Union(list2.Where(a => !list1.Any(a1 => a1.id == a.id)));

13
이것은 끔찍한 성능으로 인해 첫 번째 항목의 모든 항목에 대한 두 번째 목록을 스캔해야합니다. 작동하지 않기 때문에 downvoting하지는 않지만 원래 코드만큼 나쁩니다.
쓰셨

3
using System.Collections.Generic;
using System.Linq;

namespace YourProject.Extensions
{
    public static class ListExtensions
    {
        public static bool SetwiseEquivalentTo<T>(this List<T> list, List<T> other)
            where T: IEquatable<T>
        {
            if (list.Except(other).Any())
                return false;
            if (other.Except(list).Any())
                return false;
            return true;
        }
    }
}

때로는 두 목록이 다른지 여부 만 알고 그 차이점이 무엇인지 알아야 합니다 . 이 경우이 확장 방법을 프로젝트에 추가하십시오. 나열된 객체는 IEquatable을 구현해야합니다!

용법:

public sealed class Car : IEquatable<Car>
{
    public Price Price { get; }
    public List<Component> Components { get; }

    ...
    public override bool Equals(object obj)
        => obj is Car other && Equals(other);

    public bool Equals(Car other)
        => Price == other.Price
            && Components.SetwiseEquivalentTo(other.Components);

    public override int GetHashCode()
        => Components.Aggregate(
            Price.GetHashCode(),
            (code, next) => code ^ next.GetHashCode()); // Bitwise XOR
}

Component클래스가 무엇이든 여기에 표시된 메소드 Car는 거의 동일하게 구현되어야합니다.

GetHashCode를 어떻게 작성했는지 주목하는 것이 매우 중요합니다. 순서가 제대로 구현하기에 IEquatable, Equals그리고 GetHashCode 해야한다 논리적으로 호환 방식으로 인스턴스의 속성에서 작동합니다.

내용이 같은 두 목록은 여전히 ​​다른 객체이며 다른 해시 코드를 생성합니다. 이 두 목록이 동일하게 취급되기를 원하므로 각 목록에 GetHashCode대해 동일한 값을 생성 해야 합니다. 해시 코드를 목록의 모든 요소에 위임하고 표준 비트 XOR을 사용하여 해시 코드를 모두 결합하여이를 수행 할 수 있습니다. XOR은 순서에 구애받지 않으므로 목록이 다르게 정렬되는지는 중요하지 않습니다. 그것들은 동등한 멤버만을 포함하고 있다는 것만 중요합니다.

참고 : 이상한 이름은 메서드가 목록의 요소 순서를 고려하지 않는다는 것을 의미합니다. 목록의 요소 순서를 신경 쓰면이 방법이 적합하지 않습니다!


1

이 코드를 사용하여 수백만 개의 레코드가있는 두 목록을 비교했습니다.

이 방법은 시간이 많이 걸리지 않습니다

    //Method to compare two list of string
    private List<string> Contains(List<string> list1, List<string> list2)
    {
        List<string> result = new List<string>();

        result.AddRange(list1.Except(list2, StringComparer.OrdinalIgnoreCase));
        result.AddRange(list2.Except(list1, StringComparer.OrdinalIgnoreCase));

        return result;
    }

0

결합 된 결과 만 필요한 경우에도 작동합니다.

var set1 = new HashSet<T>(list1);
var set2 = new HashSet<T>(list2);
var areEqual = set1.SetEquals(set2);

여기서 T는 목록 요소 유형입니다.


-1

재미 있을지도 모르지만 나를 위해 일하십시오.

string.Join ( "", List1)! = 문자열 .Join ( "", List2)


여기에 기록 된 것처럼 List <string> 또는 List <int>에서는 작동하지 않습니다. 예를 들어 두 목록 11; 2; 3 및 1; 12; 3은 문자열을 일부와 결합하지 않기 때문에 동일합니다. 목록에서 사용할 수없는 고유 한 구분 기호입니다. 그 외에도 많은 항목이있는 목록의 문자열을 연결하면 성능이 저하 될 수 있습니다.
SwissCoder

@SwissCoder : 당신이 틀 렸습니다. 이것은 문자열에 대한 공연 학살자가 아닙니다. 50.000 문자열 (각 길이 3)의 두 목록이있는 경우이 알고리즘은 내 컴퓨터에서 3ms가 필요합니다. 대답은 7이 필요합니다. 트릭은 Jibz가 하나의 문자열 비교 만 필요하다고 생각합니다. 물론 그는 고유 한 구분 기호를 추가해야합니다.
user1027167

@ user1027167 : 문자열을 직접 비교하는 것에 대해 이야기하지 않습니다 (이것도 질문이 아닙니다). 50.000 개의 객체로 List에있는 모든 객체의 .ToString () 메소드를 호출하면 구현 방식에 따라 큰 문자열을 만들 수 있습니다. 나는 그것이 갈 길이라고 생각하지 않습니다. 그런 다음 "고유 한"문자 나 문자열을 사용하는 것이 위험하므로 코드를 실제로 재사용 할 수는 없습니다.
SwissCoder

그래 사실이야 질문자는 목록의 데이터 유형을 제공하지 않고 가장 빠른 방법을 요청했습니다. 아마도이 답변은 질문자의 유스 케이스에 가장 빠른 방법 일 것입니다.
user1027167

-3

이것이 요소별로 두 목록을 비교하는 간단하고 쉬운 방법이라고 생각합니다.

x=[1,2,3,5,4,8,7,11,12,45,96,25]
y=[2,4,5,6,8,7,88,9,6,55,44,23]

tmp = []


for i in range(len(x)) and range(len(y)):
    if x[i]>y[i]:
        tmp.append(1)
    else:
        tmp.append(0)
print(tmp)

3
이것은 C # 질문이며 C # 코드를 제공하지 않았습니다.
Wai Ha Lee

1
아마도이 답변을 삭제하고 예를 들어 옮길 수 있습니다 (예 : 파이썬에서 두 목록을 비교하고 일치 항목을 반환하는 방법은 무엇입니까?
Wai Ha Lee

-4

이것이 가장 좋은 해결책입니다

var list3 = list1.Where(l => list2.ToList().Contains(l));

1
이것은의 List<T>각 요소마다 새로운 요소를 생성하기 때문에 실제로 매우 나쁩니다 list1. 또한 결과가 list3아닌 경우 호출 됩니다 List<T>.
Wai Ha Lee
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.