【问题标题】:Intersection of multiple lists with IEnumerable.Intersect()多个列表与 IEnumerable.Intersect() 的交集
【发布时间】:2009-11-04 15:56:16
【问题描述】:

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

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&lt;IEnumerable&lt;SomeClass&gt;&gt;

解决方案

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

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

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

【问题讨论】:

    标签: c# .net linq


    【解决方案1】:

    怎么样:

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

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

    【讨论】:

    • 哇,我不可能想到这个解决方案。一旦你有了解决方案,这似乎很明显......嗯,不,我会发表评论,以确保我的同事不会认为我吃太多杂草:)
    • 功能范式获胜)
    • 为什么需要 Skip?因为不知道所以问
    • 跳过是因为第一个元素用于哈希集的初始填充。你必须这样做,否则它就是一堆空集的交集。
    • 我了解解决方案。我猜 e 代表枚举数?我也可以问一下h代表什么吗?我猜 h 代表 HashSet?
    【解决方案2】:

    您确实可以使用两次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:是的,我的答案使用单个哈希集,而不是每次都创建一个新哈希集。但是,您仍然可以将 Aggregate 与集合一起使用...将进行编辑。
    • Ick... 只是想制定一个聚合解决方案,这很恶心,因为 HashSet.IntersectWith 返回 null :(
    • 嗨。关于您的IntersectAll() 方法的一个问题(这是少数):是否有一种简单的方法可以添加选择器作为参数,比较值(例如:Func&lt;TResult, TKey&gt; selector)并仍然使用InsertectWith()
    • @tigrou:不是很容易——因为您仍然希望返回List&lt;T&gt; 而不是List&lt;TKey&gt;,对吧?最好的方法可能是创建一个EqualityComparer&lt;T&gt;,通过投影到TKey 来实现。
    【解决方案3】:

    试试这个,它可以工作,但我真的很想在聚合中摆脱 .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 的评论,可以去掉Aggregate 调用中的ToList() 并将其移到外面只执行一次。我没有测试以前的代码是否比新代码更快的性能。所需的更改是在最后一行指定Aggregate 方法的泛型类型参数,如下所示:

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

    【讨论】:

    • 谢谢,我刚刚试了一下,效果很好!以前没有使用过 Aggregate(),但我想这就是我正在寻找的东西。
    • 正如我对托尼的回答所指定的评论,我相信他的解决方案会表现得更好。
    • 如果你使用 Aggregate>,你可以去掉聚合中的 .ToList()
    • @pomber,我不敢相信你的评论已经 3 年没有人投票了。那么今天是你的一天,我的朋友。
    【解决方案4】:

    您可以执行以下操作

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

    【讨论】:

    • 谢谢,但我确实有一个列表列表,而不是三个单独的列表。我需要一些独立于 listOfLists 中有多少列表的东西。
    • @Oskar 您可以轻松地循环运行它
    【解决方案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();
    

    【讨论】:

    • IntersectMany 好主意!我喜欢它。
    【解决方案6】:

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

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

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

    【讨论】:

      【解决方案7】:

      在网上搜索并没有真正想出我喜欢(或有效)的东西后,我睡在上面并想出了这个。我的使用一个类 (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();
      }
      

      【讨论】:

        【解决方案8】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2011-04-20
          • 1970-01-01
          • 2022-06-11
          • 2020-07-08
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多