IEnumerable.Intersect ()로 여러 목록의 교차


85

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

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 메서드는 약간 더 잘 됩니다.)

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

답변:


74

어때 :

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

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


1
와,이 솔루션에 대해 스스로 생각할 수 없었습니다. 해결책이 있으면 분명해 보입니다 ..... 흠, 아니, 동료들이 내가 너무 많은 잡초를 취한다고 생각하지 않도록 코멘트를 남길 것입니다 :)
Samuel

기능적인 패러다임의 승리)
아나 톨

Skip이 필요한 이유는 무엇입니까? 나도 몰라 물어 때문에
이싸 프램

첫 번째 요소가 해시 세트의 초기 채우기에 사용되기 때문에 건너 뛰기가 있습니다. 그렇지 않으면 빈 세트가있는 교차점이기 때문에 이것을해야합니다.
SirPentor

나는 해결책을 이해합니다. e가 열거자를 의미한다고 생각합니까? h가 무엇을 의미하는지 물어볼 수 있습니까? h가 HashSet을 의미한다고 생각합니까?
Quan

63

실제로 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();
}

예, foreach는 말이됩니다. Marcel의 답변에서 Aggregate 방법과 비교하여 성능 차이가 있습니까?
Oskar

@Oskar : 예, 내 대답은 매번 새 해시 세트를 만드는 대신 단일 해시 세트를 사용합니다. 그러나 집합과 함께 집계를 계속 사용할 수 있습니다.
Jon Skeet

Ick ... Aggregate 솔루션을 해결하려고 시도했지만 HashSet.IntersectWith가 null을 반환하기 때문에 icky입니다. :(
Jon Skeet

1
안녕. 당신의 IntersectAll()방법 에 관한 한 가지 질문 (소수) : 선택자를 매개 변수로 추가하고 값을 비교하고 (예 :) Func<TResult, TKey> selector여전히 사용 하는 간단한 방법이 InsertectWith()있습니까?
tigrou

@tigrou : 끔찍하게 쉽지는 않습니다. 왜냐하면 여전히 a List<T>대신 a를 반환하고 싶기 때문입니다 List<TKey>. 가장 좋은 방법은 아마 생성하는 것 EqualityComparer<T>에 투영하여 구현 한을 TKey.
Jon Skeet

29

이것을 시도해보십시오, 작동하지만 집계에서 .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();

고마워요, 방금 시도했는데 작동합니다! Havn은 이전에 Aggregate ()를 사용하지 않았지만 내가 찾던 것과 같은 것 같습니다.
Oskar

Tony의 답변에 대한 의견으로 지정했듯이 그의 솔루션이 더 잘 수행 될 것이라고 믿습니다.
Marcel Gosselin

3
Aggregate <IEnumerable <int >>을 사용하면 집계에서 .ToList ()를 제거 할 수 있습니다.
pomber

@pomber, 나는 당신의 코멘트가 찬성없이 3 년 동안 갔다는 것을 믿을 수 없습니다. 오늘은 당신의 날입니다.
Sean은

5

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

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

1
고마워요.하지만 세 개의 개별 목록이 아닌 목록 목록이 있습니다. listOfLists에 얼마나 많은 목록이 있는지 독립적으로 작동하는 것이 필요합니다.
Oskar

4
@Oskar 당신은 쉽게 루프에서 실행할 수 있습니다
Gabe Moothart

5

이것은 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();

2

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

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

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


0

'넷'을 검색했지만 내가 좋아하는 (또는 효과가있는) 무언가를 찾지 못해 잠을 잤다. 광산은 클래스 ( 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();
}

-1

목록이 모두 작은 경우 간단한 해결책입니다. 목록이 더 큰 경우 해시 세트만큼 수행되지 않습니다.

public static IEnumerable<T> IntersectMany<T>(this IEnumerable<IEnumerable<T>> input)
{
    if (!input.Any())
        return new List<T>();

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