【问题标题】:C# more efficient way of comparing two collectionsC#比较两个集合的更有效方法
【发布时间】:2019-03-02 05:08:27
【问题描述】:

我有两个收藏

List<Car> currentCars = GetCurrentCars();
List<Car> newCars = GetNewCars();

我不想使用 foreach 循环或其他东西,因为我认为应该有更好的方法来做到这一点。

我正在寻找更有效的方法来比较这些集合并获得结果:

  1. newCars 中而不是 currentCars 中的汽车列表
  2. 不在 newCars 和 currentCars 中的汽车列表

类型 Car 具有 int 属性 Id。

有一个答案,已被删除说 我所说的高效是什么意思:更少的代码、更少的机制和更易读的案例

那么这样想我有什么案例?

什么是更少的代码、更少的机制和更易读的案例?

【问题讨论】:

  • 您描述的操作是“设置差异”。如果您需要大量计算集合差异,或者在大型集合上,那么您首先不应该使用 List。您应该使用 HashSet,这是一种专门为计算此类差异而优化的数据结构。
  • @Eric Lippert 在我的情况下它是非常小的集合(1-3 项),所以我认为 HashSet 中没有必要。

标签: c# .net c#-4.0


【解决方案1】:

你可以这样做:

// 1) List of cars in newCars and not in currentCars
var newButNotCurrentCars = newCars.Except(currentCars);

// 2) List of cars in currentCars and not in newCars
var currentButNotNewCars = currentCars.Except(newCars);

代码使用Enumerable.Except 扩展方法(在.Net 3.5 及更高版本中可用)。

我相信这符合您“更少代码、更少机制和更易读”的标准。

【讨论】:

  • +1,LINQ 可能是一种简单的方法,尤其是Except()
  • +1 但不能保证 except 会执行 a for each 因为它很可能会以任何方式使用 Ienumerable 和 foreach。
  • 他说他不想要foreach循环,这可能是因为可读性,他没有具体说明。我将绕开棘手的性能问题,提供最易读和可维护的代码。
【解决方案2】:

你可以使用Except:

var currentCarsNotInNewCars = currentCars.Except(newCars);
var newCarsNotInCurrentCars = newCars.Except(currentCars);

但这与foreach 解决方案相比没有性能优势。它看起来更干净。
另外,请注意,您需要为您的 Car 类实现 IEquatable&lt;T&gt;,因此比较是在 ID 而不是参考上进行的。

就性能而言,更好的方法是不使用List&lt;T&gt;,而是使用Dictionary&lt;TKey, TValue&gt;,并将ID作为键:

var currentCarsDictionary = currentCars.ToDictionary(x => x.ID);
var newCarsDictionary = newCars.ToDictionary(x => x.ID);

var currentCarsNotInNewCars = 
    currentCarsDictionary.Where(x => !newCarsDictionary.ContainsKey(x.Key))
                         .Select(x => x.Value);

var newCarsNotInCurrentCars = 
    newCarsDictionary.Where(x => !currentCarsDictionary.ContainsKey(x.Key))
                     .Select(x => x.Value);

【讨论】:

  • 但这本质上是一个循环
  • @landoncz:我知道。我认为不使用循环是不可能的。
  • 我同意,从技术上讲,您必须使用循环,除非您要进行一些疯狂的递归(然后您将有一些无法维护的东西,并且还有可能导致堆栈崩溃)。
  • 是的,我也打算这么说,但为了简洁起见,我决定不这样做。
  • @Daniel Hilgarth 我尝试了 .Except() 方法的第一件事,但我意识到我需要按照你所说的实现 IEquatable,因为我只需要通过 Id 进行比较。字典方法对我来说效果很好,所以我不需要实现任何接口,我会这样做,但我使用的是自动生成的类(POCO,所以 Car 它是自动生成的 POCO 类)所以我不想修改 t4 模板或创建部分类以实现 IEquatable.
【解决方案3】:

如果你从HashSets 开始,你可以使用Except 方法。

HashSet<Car> currentCars = GetCurrentCars();
HashSet<Car> newCars = GetNewCars();

currentCars.Except(newCars);
newCars.Except(currentCars);

使用集合比使用列表要快得多。 (在后台,列表只是做一个 foreach,集合可以优化)。

【讨论】:

  • +1 用于 HashSet。虽然它会执行得更快,但它会消耗更多的内存并且填充 HashSet 的初始成本会更高。如果您计划多次使用同一个集合,则应该只使用 HashSet。
  • 非常正确。不过,权衡几乎总是大小与速度。
  • HashSet 是否有一个特殊的除了不是 LINQ 扩展方法?我认为您的意思是 exceptWith,它会就地变异。
【解决方案4】:

您可以使用 LINQ...

        List<Car> currentCars = new List<Car>();
        List<Car> newCars = new List<Car>();

        List<Car> currentButNotNew = currentCars.Where(c => !newCars.Contains(c)).ToList();
        List<Car> newButNotCurrent = newCars.Where(c => !currentCars.Contains(c)).ToList();

...但不要上当。对你来说可能代码更少,但肯定会有一些 for 循环在某个地方

编辑:没有意识到有一个例外方法:(

【讨论】:

    【解决方案5】:

    我会覆盖 CarEquals 以按 id 进行比较,然后您可以使用 IEnumerable.Except 扩展方法。如果您无法覆盖 Equals,您可以创建自己的 IEqualityComparer&lt;Car&gt;,它通过 id 比较两辆汽车。

    class CarComparer : IEqualityComparer<Car>
    {
        public bool Equals(Car x, Car y)
        {
            return x != null && y != null && x.Id == y.Id;
        }
    
        public int GetHashCode(Car obj)
        {
            return obj == null ? 0 : obj.Id;
        }
    }
    

    【讨论】:

    • 我认为这是不以某种方式循环遍历每个项目的唯一方法。
    • @landoncz:我看不出这是如何避免循环的。他还建议使用Except 扩展方法,尽管他拼错了。
    • 抱歉,我以为他会像这样覆盖整个列表,D'oh,猜你不能在回复中添加代码......
    【解决方案6】:

    如果您正在寻找效率,请在 Cars 上实现 IComparable(按您的唯一 ID 排序)并使用 SortedList。然后,您可以一起浏览您的集合并在 O(n) 中评估您的检查。这当然会增加 List 插入的成本以保持排序的性质。

    【讨论】:

      【解决方案7】:

      您可以将较小的列表复制到基于哈希表的集合(如 HashSet 或 Dictionary)中,然后遍历第二个列表并检查该项目是否存在于哈希表中。

      这将把时间从 foreach 案例中的幼稚 foreach 中的 O(N^2) 减少到 O(N)。

      这是您在不了解列表的情况下可以做的最好的事情(例如,如果列表经过排序,您可能会做得更好,但是,因为您必须“触摸”每辆车至少检查一次,以检查它是否在新车清单上,你永远不会比 O(N)) 做得更好

      【讨论】:

        【解决方案8】:

        如果 Id 属性的比较足以让您判断 Car 是否与另一个 Car 相等,为了避免某种循环,您可以使用自己的类覆盖 List 来跟踪项目并使用IEqualityComparer 在整个集合上,像这样:

        class CarComparer : IList<Car>, IEquatable<CarComparer>
        {
            public bool Equals(CarComparer other)
            {
                return object.Equals(GetHashCode(),other.GetHashCode());
            }
        
            public override int GetHashCode()
            {
                return _runningHash;
            }
        
            public void Insert(int index, Car item)
            {
                // Update _runningHash here
                throw new NotImplementedException();
            }
        
            public void RemoveAt(int index)
            {
                // Update _runningHash here
                throw new NotImplementedException();
            }
        
            // More IList<Car> Overrides ....
        }
        

        然后,您只需覆盖AddRemove 等以及任何其他可能影响列表中项目的方法。然后,您可以保留一个私有变量,它是列表中项目的某种 Id 的哈希值。当覆盖你的 Equals 方法时,你可以只比较这个私有变量。到目前为止,这不是最干净的方法(因为您必须跟上您的哈希变量),但它会导致您不必循环进行比较。如果是我,我会像这里提到的那样使用 Linq...

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-11-04
          • 2014-05-15
          • 2013-11-19
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多