[c#] IEnumerable.Intersect ()로 여러 목록의 교차

다음과 같이 교차점을 찾고 싶은 목록이 있습니다.

var list1 = new List<int>() { 1, 2, 3 };
var list2 = new List<int>() { 2, 3, 4 };
var list3 = new List<int>() { 3, 4, 5 };
var listOfLists = new List<List<int>>() { list1, list2, list3 };

// expected intersection is List<int>() { 3 };

IEnumerable.Intersect ()로 이것을 수행하는 방법이 있습니까?

편집 : 나는 이것에 대해 더 명확해야했습니다. 정말 목록이 있습니다. 얼마나 많을 지 모르겠습니다. 위의 세 목록은 단지 예였습니다. IEnumerable<IEnumerable<SomeClass>>

해결책

모든 훌륭한 답변에 감사드립니다. 이 문제를 해결하기위한 네 가지 옵션이 있습니다 : List + aggregate (@Marcel Gosselin), List + foreach (@JaredPar, @Gabe Moothart), HashSet + aggregate (@jesperll) 및 HashSet + foreach (@Tony the Pony). 이 솔루션에 대한 성능 테스트를 수행했습니다 ( 목록 수 , 각 목록 의 요소 임의의 수 최대 크기 변경).

대부분의 상황에서 HashSet은 List보다 성능이 더 좋습니다 (내가 추측하는 HashSet의 특성 때문에 큰 목록과 작은 난수 크기는 제외). 메서드 (foreach 메서드는 약간 더 잘 됩니다.)

나에게 집계 방법은 정말 매력적이지만 (그리고 나는 그것을 받아 들인 대답으로 갈 것입니다) 가장 읽기 쉬운 솔루션이라고 말하지 않을 것입니다 .. 다시 한번 감사드립니다!



답변

어때 :

var intersection = listOfLists
    .Skip(1)
    .Aggregate(
        new HashSet<T>(listOfLists.First()),
        (h, e) => { h.IntersectWith(e); return h; }
    );

그렇게하면 전체적으로 동일한 HashSet을 사용하여 단일 문에서 최적화됩니다. listOfLists에 항상 하나 이상의 목록이 포함되어 있는지 확인하십시오.


답변

실제로 Intersect두 번 사용할 수 있습니다 . 그러나 이것이 더 효율적이라고 생각합니다.

HashSet<int> hashSet = new HashSet<int>(list1);
hashSet.IntersectWith(list2);
hashSet.IntersectWith(list3);
List<int> intersection = hashSet.ToList();

물론 작은 세트의 문제는 아니지만 큰 세트가 많은 경우 중요 할 수 있습니다.

기본적으로 Enumerable.Intersect각 호출마다 세트를 생성해야합니다. 세트 작업을 더 많이 수행 할 것이라는 것을 알고 있다면 해당 세트를 유지하는 것이 좋습니다.

그 어느 때보 다 성능과 가독성을 면밀히 주시하십시오 Intersect. 두 번 호출하는 메서드 체인 은 매우 매력적입니다.

편집 : 업데이트 된 질문 :

public List<T> IntersectAll<T>(IEnumerable<IEnumerable<T>> lists)
{
    HashSet<T> hashSet = null;
    foreach (var list in lists)
    {
        if (hashSet == null)
        {
            hashSet = new HashSet<T>(list);
        }
        else
        {
            hashSet.IntersectWith(list);
        }
    }
    return hashSet == null ? new List<T>() : hashSet.ToList();
}

또는 비어 있지 않고 Skip이 상대적으로 저렴하다는 것을 알고 있다면 :

public List<T> IntersectAll<T>(IEnumerable<IEnumerable<T>> lists)
{
    HashSet<T> hashSet = new HashSet<T>(lists.First());
    foreach (var list in lists.Skip(1))
    {
        hashSet.IntersectWith(list);
    }
    return hashSet.ToList();
}


답변

이것을 시도해보십시오, 작동하지만 집계에서 .ToList ()를 제거하고 싶습니다.

var list1 = new List<int>() { 1, 2, 3 };
var list2 = new List<int>() { 2, 3, 4 };
var list3 = new List<int>() { 3, 4, 5 };
var listOfLists = new List<List<int>>() { list1, list2, list3 };
var intersection = listOfLists.Aggregate((previousList, nextList) => previousList.Intersect(nextList).ToList());

최신 정보:

@pomber의 설명에 ToList()따라 Aggregate호출 내부를 제거 하고 외부로 이동하여 한 번만 실행할 수 있습니다. 이전 코드가 새 코드보다 빠른지 여부를 테스트하지 않았습니다. 필요한 변경 사항은 Aggregate아래와 같이 마지막 줄에 메서드 의 제네릭 유형 매개 변수를 지정하는 것입니다.

var intersection = listOfLists.Aggregate<IEnumerable<int>>(
   (previousList, nextList) => previousList.Intersect(nextList)
   ).ToList();


답변

다음을 수행 할 수 있습니다.

var result = list1.Intersect(list2).Intersect(list3).ToList();


답변

이것은 IntersectMany라고 부르는 확장 메서드가있는 솔루션의 내 버전입니다.

public static IEnumerable<TResult> IntersectMany<TSource, TResult>(this IEnumerable<TSource> source, Func<TSource, IEnumerable<TResult>> selector)
{
    using (var enumerator = source.GetEnumerator())
    {
        if(!enumerator.MoveNext())
            return new TResult[0];

        var ret = selector(enumerator.Current);

        while (enumerator.MoveNext())
        {
            ret = ret.Intersect(selector(enumerator.Current));
        }

        return ret;
    }
}

따라서 사용법은 다음과 같습니다.

var intersection = (new[] { list1, list2, list3 }).IntersectMany(l => l).ToList();


답변

이것은 교차 기능이없는 List of List (ListOfLists)에 대한 한 행 솔루션입니다.

var intersect = ListOfLists.SelectMany(x=>x).Distinct().Where(w=> ListOfLists.TrueForAll(t=>t.Contains(w))).ToList()

.net 4 이상에서 작동합니다.


답변

‘넷’을 검색했지만 내가 좋아하는 (또는 효과가있는) 무언가를 찾지 못해 잠을 잤다. 광산은 클래스 ( SearchResult)를 사용하는데 EmployeeId, 이것이 제가 목록에서 공통적으로 사용해야하는 것입니다. EmployeeId모든 목록에 있는 모든 레코드를 반환 합니다. 화려하지는 않지만 간단하고 이해하기 쉽습니다. 제가 좋아하는 것입니다. 작은 목록 (제 경우)의 경우 제대로 작동해야하며 누구나 이해할 수 있습니다!

private List<SearchResult> GetFinalSearchResults(IEnumerable<IEnumerable<SearchResult>> lists)
{
    Dictionary<int, SearchResult> oldList = new Dictionary<int, SearchResult>();
    Dictionary<int, SearchResult> newList = new Dictionary<int, SearchResult>();

    oldList = lists.First().ToDictionary(x => x.EmployeeId, x => x);

    foreach (List<SearchResult> list in lists.Skip(1))
    {
        foreach (SearchResult emp in list)
        {
            if (oldList.Keys.Contains(emp.EmployeeId))
            {
                newList.Add(emp.EmployeeId, emp);
            }
        }

        oldList = new Dictionary<int, SearchResult>(newList);
        newList.Clear();
    }

    return oldList.Values.ToList();
}

다음은 클래스가 아닌 int 목록을 사용하는 예제입니다 (이는 원래 구현이었습니다).

static List<int> FindCommon(List<List<int>> items)
{
    Dictionary<int, int> oldList = new Dictionary<int, int>();
    Dictionary<int, int> newList = new Dictionary<int, int>();

    oldList = items[0].ToDictionary(x => x, x => x);

    foreach (List<int> list in items.Skip(1))
    {
        foreach (int i in list)
        {
            if (oldList.Keys.Contains(i))
            {
                newList.Add(i, i);
            }
        }

        oldList = new Dictionary<int, int>(newList);
        newList.Clear();
    }

    return oldList.Values.ToList();
}