IEnumerable.Intersect()を使用した複数のリストの共通部分


83

このように交差点を見つけたいリストのリストがあります:

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()を使用してこれを行う方法はありますか?

編集:私はこれについてもっと明確にすべきでした:私は本当にリストのリストを持っています、私はいくつになるかわかりません、上記の3つのリストは単なる例でした、私が持っているのは実際には IEnumerable<IEnumerable<SomeClass>>

解決

すべての素晴らしい答えをありがとう。これを解決するには、List + Aggregate(@Marcel Gosselin)、List + foreach(@ JaredPar、@ Gabe Moothart)、HashSet + Aggregate(@jesperll)、HashSet + foreach(@Tony the Pony)の4つのオプションがあることがわかりました。私は様々なこれらのソリューションにいくつかのパフォーマンステスト(やったリストの数を要素数各リストとにおける乱数の最大サイズ。

ほとんどの状況で、HashSetはリストよりもパフォーマンスが優れていることがわかります(HashSetの性質上、大きなリストと小さな乱数サイズを除く)。foreachメソッドと集計の間に実際の違いは見つかりませんでした。メソッド(foreachメソッドのパフォーマンスはわずかに向上します。)

私にとって、集計方法は本当に魅力的です(そして、私はそれを受け入れられた答えとして使用します)が、それが最も読みやすい解決策であるとは言えません。

回答:


72

どうですか:

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

そうすれば、同じHashSetを全体を通して使用し、単一のステートメントで使用することで最適化されます。listOfListsに常に少なくとも1つのリストが含まれていることを確認してください。


1
うわー、私がこの解決策について自分自身を考えることができた方法はありません。解決策が決まったら、それは明らかなようです.....うーん、いいえ、コメントを残します。同僚が私が雑草を取りすぎていると思わないようにするためです:)
Samuel

関数型パラダイムが勝つ)
anatol 2018年

なぜスキップが必要なのですか?わからないので尋ねる
Issa Fram 2018

最初の要素がハッシュセットの初期入力に使用されるため、スキップがあります。これを行う必要があります。そうしないと、空のセットとの交差点の集まりになるためです。
SirPentor 2018年

私は解決策を理解しています。eは列挙子の略だと思いますか?hが何を表すのか尋ねることもできますか?hはHashSetの略だと思いますか?
クアン

62

あなたは確かにIntersect2回使用することができます。ただし、これはより効率的だと思います。

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

もちろん、小さなセットでは問題ありませんが、大きなセットがたくさんある場合は、それが重要になる可能性があります。

基本的Enumerable.Intersectに、呼び出しごとにセットを作成する必要があります。より多くのセット操作を実行することがわかっている場合は、そのセットを保持することをお勧めします。

いつものように、パフォーマンスと読みやすさを注意深く監視してください。2Intersect回呼び出すメソッドチェーンは非常に魅力的です。

編集:更新された質問の場合:

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

または、それが空ではなく、そのスキップが比較的安価であることがわかっている場合:

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

1
@スキート「ポニーのトニー」?
Gabe Moothart 2009年

ええ、foreachは理にかなっています。Marcelの回答のAggregateメソッドと比較した場合のパフォーマンスの違いはありますか?
オスカー

@Oskar:はい、私の答えは、毎回新しいハッシュセットを作成する代わりに、単一のハッシュセットを使用しています。ただし、セットでAggregateを使用することはできます...編集します。
Jon Skeet

Ick ... Aggregateソリューションを作成しようとしましたが、HashSet.IntersectWithがnullを返すため、厄介です:(
Jon Skeet

1
こんにちは。あなたのIntersectAll()方法に関する1つの質問(これは一握りです):パラメーターとしてセレクターを追加し、値を比較し(例:) Func<TResult, TKey> selector、それでも使用する簡単な方法はありますInsertectWith()か?
tigrou 2014

28

これを試してみてください、それは動作しますが、私は本当に集合体の.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呼び出しの内部を取り除き、外部に移動して1回だけ実行することができます。以前のコードが新しいコードよりも速いかどうかについては、パフォーマンスをテストしませんでした。必要な変更は、Aggregate以下のように最後の行でメソッドのジェネリック型パラメーターを指定することです。

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

おかげで、私はそれを試してみました、そしてそれはうまくいきます!以前はAggregate()を使用していませんでしたが、私が探していたのはこのようなものだったと思います。
オスカー

Tonyの回答に対するコメントとして指定したように、彼のソリューションの方がパフォーマンスが向上すると思います。
Marcel Gosselin

3
Aggregate <IEnumerable <int >>
pomber 2016

@pomber、あなたのコメントが賛成票なしで3年経ったなんて信じられません。さて、今日は私の友達のあなたの日です。
ショーン

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

4

あなたは次のことができます

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

1
おかげで、私は本当にリストのリストを持っています。3つの別々のリストではありません。listOfListsにあるリストの数に関係なく機能するものが必要です。
オスカー

4
@Oskar簡単にループで実行できます
Gabe Moothart 2009年

2

これは、交差関数のないリストのリスト(ListOfLists)の1行のソリューションです。

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

これは.net4(またはそれ以降)で機能するはずです


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.