【问题标题】:Fastest way to find common items across multiple lists in C#在 C# 中跨多个列表查找公共项目的最快方法
【发布时间】:2008-09-03 04:13:46
【问题描述】:

鉴于以下情况:

List<List<Option>> optionLists;

确定出现在所有 N 个列表中的 Option 对象子集的快速方法是什么?相等性是通过一些字符串属性来确定的,例如 option1.Value == option2.Value。

所以我们应该以List&lt;Option&gt; 结尾,其中每个项目只出现一次。

【问题讨论】:

    标签: c#


    【解决方案1】:

    好的,这将找到具有出现在每个列表中的值的选项对象列表。

    var x = from list in optionLists
            from option in list
            where optionLists.All(l => l.Any(o => o.Value == option.Value))
            orderby option.Value
            select option;
    

    它不执行“不同”选择,因此它会返回多个 Option 对象,其中一些具有相同的值。

    【讨论】:

      【解决方案2】:

      Matt's answer 的基础上,由于我们只对所有列表共有的选项感兴趣,我们可以简单地检查第一个列表中其他人共享的选项:

      var sharedOptions =
          from option in optionLists.First( ).Distinct( )
          where optionLists.Skip( 1 ).All( l => l.Contains( option ) )
          select option;
      

      如果一个选项列表不能包含重复的整体,Distinct 调用是不必要的。如果列表的大小差异很大,最好迭代最短列表中的选项,而不是任何列表恰好是First。排序或散列集合可用于缩短 Contains 调用的查找时间,但对于中等数量的项目应该没有太大区别。

      【讨论】:

        【解决方案3】:

        这是一个更有效的实现:

        static SortedDictionary<T,bool>.KeyCollection FindCommon<T> (List<List<T>> items)
        {
          SortedDictionary<T, bool>
            current_common = new SortedDictionary<T, bool> (),
            common = new SortedDictionary<T, bool> ();
        
          foreach (List<T> list in items)
          {
            if (current_common.Count == 0)
            {
              foreach (T item in list)
              {
                common [item] = true;
              }
            }
            else
            {
              foreach (T item in list)
              {
                if (current_common.ContainsKey(item))
                  common[item] = true;
                else
                  common[item] = false;
              }
            }
        
            if (common.Count == 0)
            {
              current_common.Clear ();
              break;
            }
        
            SortedDictionary<T, bool>
              swap = current_common;
        
            current_common = common;
            common = swap;
            common.Clear ();
          }
        
          return current_common.Keys;
        }    
        

        它的工作原理是创建一组迄今为止处理的所有列表共有的所有项目,并将每个列表与该集合进行比较,创建当前列表和迄今为止的公共项目列表共有的临时项目集。实际上是 O(n.m),其中 n 是列表的数量,m 是列表中的项目数。

        使用示例:

        static void Main (string [] args)
        {
          Random
            random = new Random();
        
          List<List<int>>
            items = new List<List<int>>();
        
          for (int i = 0 ; i < 10 ; ++i)
          {
            List<int>
              list = new List<int> ();
        
            items.Add (list);
        
            for (int j = 0 ; j < 100 ; ++j)
            {
              list.Add (random.Next (70));
            }
          }
        
          SortedDictionary<int, bool>.KeyCollection
            common = FindCommon (items);
        
          foreach (List<int> list in items)
          {
            list.Sort ();
          }
        
          for (int i = 0 ; i < 100 ; ++i)
          {
            for (int j = 0 ; j < 10 ; ++j)
            {
              System.Diagnostics.Trace.Write (String.Format ("{0,-4:D} ", items [j] [i]));
            }
        
            System.Diagnostics.Trace.WriteLine ("");
          }
        
          foreach (int item in common)
          {
            System.Diagnostics.Trace.WriteLine (String.Format ("{0,-4:D} ", item));
          }
        }
        

        【讨论】:

        • 这很好,我会将return current_common.Keys; 替换为return new List&lt;T&gt;(current_common.Keys);,它非常适合我的需求,例如public static List&lt;T&gt; FindCommon&lt;T&gt;(params List&lt;T&gt;[] lists)
        • 没关系,事实证明这总是只返回最后一个列表,如果没有SortedDictionary,我无法弄清楚如何使用它,只是普通的Dictionary,因为我不需要这些值本身更改顺序。
        • @SSpoke 你是对的。它只返回最后一个列表,而不是所有列表的交集。
        【解决方案4】:

        写得最快:)

        var subset = optionLists.Aggregate((x, y) => x.Intersect(y))
        

        【讨论】:

          【解决方案5】:

          使用 hashSet 怎么样?这样你就可以在 O(n) 中做你想做的事,其中 n 是所有列表中组合的项目数,我认为这是最快的方法。

          您只需遍历每个列表并将找到的值插入哈希集中 当您插入一个已经存在的键时,您将收到 false 作为 .add method, 的返回值,否则返回 true

          【讨论】:

            【解决方案6】:

            排序,然后做一些类似于合并排序的事情。

            基本上你会这样做:

            1. 从每个列表中检索第一项
            2. 比较项目,如果相等,输出
            3. 如果任何项目在其他项目之前,按排序方式,从相应列表中检索新项目以替换它,否则,从所有列表中检索新项目以全部替换它们
            4. 只要您还有物品,请返回 2。

            【讨论】:

              【解决方案7】:

              我没有性能统计数据,但是如果您不想滚动自己的方法,各种集合库都有一个“Set”或“Set(T)”对象,它们提供了通常的集合过程。 (按我使用它们的顺序列出)。

              1. IESI Collections(字面意思就是设置类)
              2. PowerCollections(暂时不更新)
              3. C5(从未个人使用过)

              【讨论】:

                【解决方案8】:

                您可以通过计算所有列表中所有项目的出现次数来做到这一点 - 出现次数等于列表数量的项目对所有列表都是通用的:

                    static List<T> FindCommon<T>(IEnumerable<List<T>> lists)
                    {
                        Dictionary<T, int> map = new Dictionary<T, int>();
                        int listCount = 0; // number of lists
                
                        foreach (IEnumerable<T> list in lists)
                        {
                            listCount++;
                            foreach (T item in list)
                            {
                                // Item encountered, increment count
                                int currCount;
                                if (!map.TryGetValue(item, out currCount))
                                    currCount = 0;
                
                                currCount++;
                                map[item] = currCount;
                            }
                        }
                
                        List<T> result= new List<T>();
                        foreach (KeyValuePair<T,int> kvp in map)
                        {
                            // Items whose occurrence count is equal to the number of lists are common to all the lists
                            if (kvp.Value == listCount)
                                result.Add(kvp.Key);
                        }
                
                        return result;
                    }
                

                【讨论】:

                • 这个失败..我有 3 个不同的列表,只有 1 个共同值,它说我有 3.. 共同的值,因为 2 个列表共享 2 个共同的值,但 1 个列表仅共享1 个共同值,您不能只计算所有值然后检查有多少列表不起作用.. 但我确实喜欢代码
                • @logicnp 我刚刚被定向到这篇文章,我检查了方法。它不能正常工作(它返回的项目不属于列表中包含的所有 IEnumerable-s 的一部分。我冒昧地对其进行了更正,并将其发布在这里:
                【解决方案9】:
                /// <summary>
                    /// The method FindCommonItems, returns a list of all the COMMON ITEMS in the lists contained in the listOfLists.
                    /// The method expects lists containing NO DUPLICATE ITEMS.
                    /// </summary>
                    /// <typeparam name="T"></typeparam>
                    /// <param name="allSets"></param>
                    /// <returns></returns>
                    public static List<T> FindCommonItems<T>(IEnumerable<List<T>> allSets)
                    {
                        Dictionary<T, int> map = new Dictionary<T, int>();
                        int listCount = 0; // Number of lists.
                        foreach (IEnumerable<T> currentSet in allSets)
                        {
                            int itemsCount = currentSet.ToList().Count;
                            HashSet<T> uniqueItems = new HashSet<T>();
                            bool duplicateItemEncountered = false;
                            listCount++;
                            foreach (T item in currentSet)
                            {
                                if (!uniqueItems.Add(item))
                                {
                                    duplicateItemEncountered = true;
                                }                        
                                if (map.ContainsKey(item))
                                {
                                    map[item]++;
                                } 
                                else
                                {
                                    map.Add(item, 1);
                                }
                            }
                            if (duplicateItemEncountered)
                            {
                                uniqueItems.Clear();
                                List<T> duplicateItems = new List<T>();
                                StringBuilder currentSetItems = new StringBuilder();
                                List<T> currentSetAsList = new List<T>(currentSet);
                                for (int i = 0; i < itemsCount; i++)
                                {
                                    T currentItem = currentSetAsList[i];
                                    if (!uniqueItems.Add(currentItem))
                                    {
                                        duplicateItems.Add(currentItem);
                                    }
                                    currentSetItems.Append(currentItem);
                                    if (i < itemsCount - 1)
                                    {
                                        currentSetItems.Append(", ");
                                    }
                                }
                                StringBuilder duplicateItemsNamesEnumeration = new StringBuilder();
                                int j = 0;
                                foreach (T item in duplicateItems)
                                {
                                    duplicateItemsNamesEnumeration.Append(item.ToString());
                                    if (j < uniqueItems.Count - 1)
                                    {
                                        duplicateItemsNamesEnumeration.Append(", ");
                                    }
                                }
                                throw new Exception("The list " + currentSetItems.ToString() + " contains the following duplicate items: " + duplicateItemsNamesEnumeration.ToString());
                            }
                        }
                        List<T> result= new List<T>();
                        foreach (KeyValuePair<T, int> itemAndItsCount in map)
                        {
                            if (itemAndItsCount.Value == listCount) // Items whose occurrence count is equal to the number of lists are common to all the lists.
                            {
                                result.Add(itemAndItsCount.Key);
                            }
                        }
                
                        return result;
                    }
                

                【讨论】:

                  【解决方案10】:

                  @Skizz 方法不正确。它还返回项目中所有列表不共有的项目。 这是更正的方法:

                  /// <summary>.
                      /// The method FindAllCommonItemsInAllTheLists, returns a HashSet that contains all the common items in the lists contained in the listOfLists,
                      /// regardless of the order of the items in the various lists.
                      /// </summary>
                      /// <typeparam name="T"></typeparam>
                      /// <param name="listOfLists"></param>
                      /// <returns></returns>
                      public static HashSet<T> FindAllCommonItemsInAllTheLists<T>(List<List<T>> listOfLists)
                      {
                          if (listOfLists == null || listOfLists.Count == 0)
                          {
                              return null;
                          }
                          HashSet<T> currentCommon = new HashSet<T>();
                          HashSet<T> common = new HashSet<T>();
                  
                          foreach (List<T> currentList in listOfLists)
                          {
                              if (currentCommon.Count == 0)
                              {
                                  foreach (T item in currentList)
                                  {
                                      common.Add(item);
                                  }
                              }
                              else
                              {
                                  foreach (T item in currentList)
                                  {
                                      if (currentCommon.Contains(item))
                                      {
                                          common.Add(item);
                                      }
                                  }
                              }
                              if (common.Count == 0)
                              {
                                  currentCommon.Clear();
                                  break;
                              }
                              currentCommon.Clear(); // Empty currentCommon for a new iteration.
                              foreach (T item in common) /* Copy all the items contained in common to currentCommon. 
                                                          *            currentCommon = common; 
                                                          * does not work because thus currentCommon and common would point at the same object and 
                                                          * the next statement: 
                                                          *            common.Clear();
                                                          * will also clear currentCommon.
                                                          */
                              {
                                  if (!currentCommon.Contains(item))
                                  {
                                      currentCommon.Add(item);
                                  }
                              }
                              common.Clear();
                          }
                  
                          return currentCommon;
                      }
                  

                  【讨论】:

                    【解决方案11】:

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

                    【讨论】:

                      猜你喜欢
                      • 1970-01-01
                      • 2020-10-24
                      • 2011-01-05
                      • 1970-01-01
                      • 2016-06-09
                      • 2017-12-18
                      • 2021-07-17
                      • 1970-01-01
                      • 1970-01-01
                      相关资源
                      最近更新 更多