使用IEnumerable.Intersect()交叉多个列表

时间:2009-11-04 15:56:17

标签: c# .net linq

我有一个列表列表,我想找到这样的交集:

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>>

感谢所有好的答案。事实证明有四个选项可以解决这个问题:列表+聚合(@Marcel Gosselin), List + foreach (@JaredPar,@ Gabe Moothart), HashSet +聚合(@ jesperll)和 HashSet + foreach (@Tony the Pony)。我对这些解决方案进行了一些性能测试(每个列表中列表数元素数随机数最大大小。

事实证明,在大多数情况下,HashSet的性能优于List(除了大型列表和小的随机数大小,因为我猜HashSet的性质。) 我找不到foreach方法和聚合方法之间的任何真正区别(foreach方法执行稍微更好。)

对我来说,聚合方法真的很吸引人(我将其视为已接受的答案),但我不会说这是最可读的解决方案..再次感谢所有人!

8 个答案:

答案 0 :(得分:59)

怎么样:

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

通过使用相同的HashSet并仍然在单个语句中进行优化。只需确保listOfLists始终包含至少一个列表。

答案 1 :(得分:55)

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

答案 2 :(得分:24)

试试这个,它有效,但我真的想摆脱聚合中的.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();

答案 3 :(得分:4)

您可以执行以下操作

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

答案 4 :(得分:4)

这是我的解决方案版本,其中包含一个名为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();

答案 5 :(得分:1)

这是我没有交叉函数的列表列表(ListOfLists)的单行解决方案:

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

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

答案 6 :(得分: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();
}

答案 7 :(得分:-1)

如果您的列表都很小,这是一个简单的解决方案。如果您有更大的列表,那么它的表现就不如哈希集:

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

    return input.Aggregate(Enumerable.Intersect);
}