使用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()做到这一点?

编辑:我应该对此更加清楚:我确实有一个列表列表,我不知道会有多少列表,上面的三个列表只是一个例子,我实际上是 IEnumerable<IEnumerable<SomeClass>>

感谢您提供的所有出色答案。事实证明,有四个选项可以解决此问题:List + aggregate(@Marcel Gosselin),List + foreach(@ JaredPar,@ Gabe Moothart),HashSet + aggregate(@jesperll)和HashSet + foreach(@Tony the Pony)。我做了这些解决方案的一些性能测试(不同列表的数量元素的数量在每个列表和随机数最大尺寸。

事实证明,在大多数情况下,HashSet的性能都优于List(由于我猜HashSet的性质,大列表和较小的随机数大小除外)。我找不到foreach方法和聚合之间的任何实际区别。方法(foreach方法的性能稍好一些。)

对我来说,聚合方法确实很吸引人(我将其作为公认的答案),但我不会说这是最易读的解决方案。再次感谢大家!

Answers:


72

怎么样:

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

这样,就可以通过始终使用同一HashSet并在单个语句中对其进行优化。只要确保listOfLists始终包含至少一个列表即可。


1
哇,我绝对无法想到这个解决方案。找到解决方案后,这似乎很明显.....嗯,不,我要发表评论只是为了确保我的同事们不会以为我除草了很多:)
Samuel

功能范例胜出)
anatol

为什么需要跳过?问,因为我不知道
Issa Fram,

跳过是因为第一个元素用于哈希集的初始填充。您必须这样做,因为否则它是一堆带有空集的交叉点。
SirPentor '18年

我了解解决方案。我想e代表枚举器?我还能问问h代表什么吗?我猜h代表HashSet吗?
Quan

62

您确实可以使用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();
}

1
@Skeet“小马托尼”?
·穆阿特

是的,foreach很有道理。与Marcel答案中的Aggregate方法相比,此性能有何不同?
奥斯卡

@Oskar:是的,我的答案使用单个哈希集,而不是每次都创建一个新的哈希集。但是,您仍然可以将Aggregate与set ...一起使用。
乔恩·斯基特

ck ...只是试图制定一个汇总解决方案,而且它很棘手,因为HashSet.IntersectWith返回null :(
Jon Skeet

1
你好 关于您的IntersectAll()方法的一个问题(少数):是否有一种简单的方法来添加选择器作为参数,比较值(例如:)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调用,将其移到外部仅执行一次。我没有测试性能是否先前的代码是否比新代码更快。所需的更改是Aggregate在最后一行上指定方法的泛型类型参数,如下所示:

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

谢谢,我只是尝试了一下,它有效!以前没有使用过Aggregate(),但我想那是我想要的东西。
奥斯卡,

正如我对Tony的回答所指出的那样,我相信他的解决方案会更好。
Marcel Gosselin

3
如果您使用Aggregate <IEnumerable <int >>
pomber,则

@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
谢谢,但是我确实有一个列表列表,而不是三个单独的列表..我需要一些独立于listOfLists中的列表而工作的东西。
奥斯卡,

4
@Oskar你可以很容易地在一个循环中运行
加布Moothart

2

这是我的不带交集功能的列表列表(ListOfLists)的单行解决方案:

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

这应该适用于.net 4(或更高版本)


0

在搜索了'net之后,并没有真正提出我喜欢的(或起作用的)东西,我就睡在上面,并提出了这个建议。我使用的类(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();
}

这是一个仅使用整数列表而不是类的示例(这是我最初的实现)。

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);
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.