【问题标题】:Fastest way to check if two List<T> are equal检查两个 List<T> 是否相等的最快方法
【发布时间】:2013-01-09 13:48:48
【问题描述】:

我有两个列表

ListA&lt;Emp&gt;ListB&lt;Emp&gt; 两者都有 1000 条记录。

Emp 是 Employee 类的对象。下面是我的Employee

public class Employee
{
    int ID = 0;
    string Name = String.Empty;
    string Dept = String.Empty;
    string Address = String.Empty;
    int Age = 0;
    string Email = String.Empty;
}

我想验证两个列表是否相等。 Emp 对象可以按不同的顺序放置。此外,可能有几个 Emp 对象在两个列表中具有完全相同的信息。我也必须验证这些。

我尝试对列表进行排序并使用SequenceEqual进行比较

Enumerable.SequenceEqual(ListA.OrderBy(s => s), ListB.OrderBy(s => s)

我遇到了错误

At least one object must implement IComparable.
Exception Stack trace is as below 

   at System.Collections.Comparer.Compare(Object a, Object b)
   at System.Collections.Generic.ObjectComparer`1.Compare(T x, T y)
   at System.Linq.EnumerableSorter`2.CompareKeys(Int32 index1, Int32 index2)
   at System.Linq.EnumerableSorter`1.QuickSort(Int32[] map, Int32 left, Int32 right)
   at System.Linq.EnumerableSorter`1.Sort(TElement[] elements, Int32 count)
   at System.Linq.OrderedEnumerable`1.<GetEnumerator>d__0.MoveNext()
   at System.Linq.Enumerable.SequenceEqual[TSource](IEnumerable`1 first, IEnumerable`1 second, IEqualityComparer`1 comparer)
   at System.Linq.Enumerable.SequenceEqual[TSource](IEnumerable`1 first, IEnumerable`1 second)

我该如何实现呢?另外,如果你们能提供给我最快的方法会更好,因为 List 中的对象数量可能会增长到 1000 万。 感谢您的帮助!

编辑:每个员工都必须在两个列表中,顺序无关紧要。但是,如果 ListA 包含 5 次相同的员工对象(这意味着一些重复的条目),而 ListB 包含 4 次员工对象,则 ListA 和 ListB 不相等。

【问题讨论】:

  • 等于意味着每个员工都在两个列表中,或者顺序也必须相同(SequenceEqual 建议什么)?
  • 您是否首先实施了IEquatable&lt;Employee&gt;
  • 您想按什么排序列表?对 Employee 对象进行排序本身没有意义。
  • 每个员工都会在这两个列表中,顺序无关紧要。但是,如果 ListA 包含 5 次相同的员工对象(这意味着一些重复的条目),而 ListB 包含 4 次员工对象,则 ListA 和 ListB 不相等。
  • 如果您需要重复的实例数来匹配,那么我认为您需要先排序。看我的回答。

标签: c# .net


【解决方案1】:

您可以将SequenceEqual 与自定义IEqualityComparer&lt;Employee&gt; 一起使用:

class EmployeeComparer : IEqualityComparer<Employee>
{
    public bool Equals(Employee x, Employee y)
    {
        if (x == null || y == null) return false;

        bool equals = x.ID==y.ID && x.Name == y.Name && x.Dept == y.Dept 
            && x.Address == y.Address && x.Age == y.Age && x.Email == y.Email;
        return equals;
    }

    public int GetHashCode(Employee obj)
    {
        if (obj == null) return int.MinValue;

        int hash = 19;
        hash = hash + obj.ID.GetHashCode();
        hash = hash + obj.Name.GetHashCode();
        hash = hash + obj.Dept.GetHashCode();
        hash = hash + obj.Address.GetHashCode();
        hash = hash + obj.Age.GetHashCode();
        hash = hash + obj.Email.GetHashCode();
        return hash;
    }
}

现在很简单:

listA.SequenceEqual(ListB, new EmployeeComparer());

如果订单不重要并且您只想知道是否所有员工都在两个列表中,您可以使用HashSet&lt;Employee&gt;.SetEquals 来确定两个列表是否包含相同的人:

var empComparer =  new EmployeeComparer();
bool bothEqual = new HashSet<Employee>(ListA, empComparer)
      .SetEquals(new HashSet<Employee>(ListB, empComparer));

【讨论】:

  • 有什么理由不直接在Employee 类上实现IComparable&lt;&gt;(可能还有IEquatable&lt;&gt;)?
  • @siride:所有 Linq 方法,如 DistinctExceptIntersect 等 pp. 都期待 IEqualityComparer&lt;T&gt;
  • 顺便说一句,来自MSDN,如果使用相同的比较器,这是一个 O(n) 操作。
  • @TimSchmelter,这很好用,除了重复匹配的东西。抱歉,这个重复的匹配内容不在原始帖子中(而是在评论中)。现在我已经编辑了。
  • @TimSchmelter:如果你不提供相等比较器,它会使用默认值,它会在被比较的类型上检查IEquatablemsdn.microsoft.com/en-us/library/ms224763.aspx
【解决方案2】:

最佳复杂度是 O(N) 下面用HashSet实现:

实现 GetHashCode 和 Equals 的类:

public class Employee
{
    public int ID = 0;
    public string Name = String.Empty;
    public string Dept = String.Empty;
    public string Address = String.Empty;
    public int Age = 0;
    public string Email = String.Empty;

    public override int GetHashCode()
    {
        return
            ID.GetHashCode() ^
            (Name ?? String.Empty).GetHashCode() ^
            (Dept ?? String.Empty).GetHashCode() ^
            (Address ?? String.Empty).GetHashCode() ^
            Age.GetHashCode() ^
            (Email ?? String.Empty).GetHashCode()
            ;
    }
    public override bool Equals(object obj)
    {
        Employee other = obj as Employee;
        if (obj == null)
            return false;

        return ID == other.ID &&
                Name == other.Name &&
                Dept == other.Dept &&
                Address == other.Address &&
                Age == other.Age &&
                Email == other.Email;
    }
}

比较列表的功能:

public static bool CompareLists(List<Employee> list1, List<Employee> list2)
{
    if (list1 == null || list2 == null)
        return list1 == list2;

    if (list1.Count != list2.Count)
        return false;
    Dictionary<Employee, int> hash = new Dictionary<Employee, int>();
    foreach (Employee employee in list1)
    {
        if (hash.ContainsKey(employee))
        {
            hash[employee]++;
        }
        else
        {
            hash.Add(employee, 1);
        }
    }

    foreach (Employee employee in list2)
    {
        if (!hash.ContainsKey(employee) || hash[employee] == 0)
        {
            return false;
        }
        hash[employee]--;
    }

    return true;
}

【讨论】:

  • 需要处理多个员工,但我喜欢不是排序而是散列。
  • HashSet 没有给出精确的比较。如果 ListA 有 EmpA 3 次,EmpB 4 次,ListB 有 EmpA 4 次,EmpB 3 次,则此解决方案将确定等于。但也许这就是 OP 想要的。
【解决方案3】:

如果列表中的数字将变得巨大(10M),您可能必须考虑并行化查找以获得可接受的查询时间。

考虑使用PLINQ

更清楚你所说的“平等”是什么意思会很好。等效性检查有多复杂?您是在检查 objects 是否相同或对象 values 是否相同?

另一个考虑因素是这个;如果元素的数量会变得很大,您是否可以考虑将此检查从 .NET 移到您的数据库中 - 也许作为存储过程?您可能会发现它在那里执行效率更高。

【讨论】:

  • 对象值也将相同
  • @Bunyip。如果你想在没有并行的情况下签入代码,上面的 Tims 答案是可行的方法。但是,如果数字变大,它会变慢。您要么必须在 .NET 中并行化,要么将其移至 sproc。我会选择后者。从语法上讲,SequenceEqual 很优雅,但考虑一下它实际上要执行的内容 - 可能是嵌套的 for 循环。
  • @siride,Bunyip 询问最快的方法是比较两个列表。恕我直言,这是一个性能问题。但是,也许我误解了。 SequenceEqual 写入很快,但执行可能会变得很慢。
  • @siride 没有问题。我们目前正在我们的应用程序中进行性能优化,所以这个问题与我非常相关。我们发现,当元素数量较少时,留在 .NET 中会更快(linq 到对象,避免 DB 往返),但是当它们变大时,我们会更快地进入数据库(单程,优化查询)。我们还必须小心在 .NET 中使用并行机制,因为我们会很快耗尽我们的线程池。确实是逐案。
【解决方案4】:

将列表简化为标量类型:int, string, ....

L1.Select(x => x.K).ToArray()

使用异常方法

L1.Select(x => x.K).ToArray().Except(L1.Select(x => x.K).ToArray())

如果结果集的计数为 0,则列表等于

L1.Select(x => x.K).ToArray().Except(L1.Select(x => x.K).ToArray()).Count()

大家一起

public class Program {
    public static void Main(String[] args) {
        List<O> L1 = new List<O>{
            new O {K = 1, V = "abcd"},
            new O {K = 2, V = "efgh"}
        };
        List<O> L2 = new List<O>{
            new O {K = 1, V = "abcd"}
        };
        List<O> L3 = new List<O>{
            new O {K = 1, V = "abcd"},
            new O {K = 3, V = "ijkl"}
        };
        List<O> L4 = new List<O>{
            new O {K = 2, V = "efgh"},
            new O {K = 1, V = "abcd"}

        };

        Console.WriteLine(L1.Select(x => x.K).ToArray().Except(L1.Select(x => x.K).ToArray()).Count());
        Console.WriteLine(L1.Select(x => x.K).ToArray().Except(L2.Select(x => x.K).ToArray()).Count());
        Console.WriteLine(L1.Select(x => x.K).ToArray().Except(L3.Select(x => x.K).ToArray()).Count());
        Console.WriteLine(L1.Select(x => x.K).ToArray().Except(L4.Select(x => x.K).ToArray()).Count());

    }
} 

public class O {
    public int K { get; set; }
    public String V { get; set; }
}

【讨论】:

    【解决方案5】:

    正是它所说的。
    在 Employee 类上实现 IComparable
    还需要覆盖 Equals
    由于可能大量调用 GetHashCode 将其保存并仅计算更改。
    测试过

    IComparable Interface

    public MainWindow()
    {
        InitializeComponent();
        List<Person> PLa = new List<Person>();
        List<Person> PLb = new List<Person>();
    
        PLa.Add(new Person { Age = 3, Name = "Jim"});
        PLa.Add(new Person { Age = 2, Name = "Jimmmy" });
        PLa.Add(new Person { Age = 1, Name = "Jim" });
    
        PLb.Add(new Person { Age = 1, Name = "Jim" });
        PLb.Add(new Person { Age = 3, Name = "Jim" });
        PLb.Add(new Person { Age = 2, Name = "Jimmmy" });
    
        System.Diagnostics.Debug.WriteLine(ListSameIgnoreOrder(PLa, PLb));
    
    }
    
    public bool ListSameIgnoreOrder(List<Person> PLa, List<Person> PLb)
    {
        if (PLa.Count != PLb.Count) return false;
        //PLa.Sort();
        //PLb.Sort();
        return Enumerable.SequenceEqual(PLa.OrderBy(s => s), PLb.OrderBy(s => s));
        //for (int i = 0; i < PLa.Count; i++)
        //{
        //    System.Diagnostics.Debug.WriteLine(
        //        PLa[i].Age.ToString() + " " + PLb[i].Age.ToString() + " " +
        //        PLa[i].Name + " " + PLb[i].Name);
        //    if (!PLa[i].Equals(PLb[i])) return false;
        //}
        //return true;
    }
    
    public class Person : object, IComparable
    {
        private int age = 0;
        private string name = string.Empty;
        private int hash;
    
        public int Age
        {
            get { return age; }
            set 
            {
                if (age == value) return;
                age = value;
                CalcHash();
            }
        }
        public string Name
        {
            get { return name; }
            set 
            { 
                if (name == value) return;
                name = value;
                CalcHash();
            }
        }
    
        public override bool Equals(Object obj)
        {
            //Check for null and compare run-time types.
            if (obj == null || !(obj is Person)) return false;
            Person f = (Person)obj;
            if (f.Age != this.Age) return false;
            return (string.Compare(f.name, this.name) == 0);
        }
    
        private void CalcHash()
        {
            hash = Age.GetHashCode() ^
                (Name ?? String.Empty).GetHashCode();
        }
    
        public override int GetHashCode()
        {
            return hash;
            //return age ^ name.GetHashCode();
        }
    
        public int CompareTo(object obj)
        {
            if (obj == null) return 1;
    
            Person otherPerson = obj as Person;
            if (otherPerson != null)
            {
                if (otherPerson.Age > this.Age) return -1;
                if (otherPerson.Age < this.Age) return 1;
                // compare all properties like above
                return string.Compare(otherPerson.name, this.name);
            }
            else
                throw new ArgumentException("Object is not a Person");
        }
        public Person() { CalcHash(); }
    }
    

    【讨论】:

      【解决方案6】:

      这行得通。

      public bool EqualList(Dictionary<int, string> a, Dictionary<int, string> b)
      {
          if (a.Count == b.Count)
          {
              bool rs = false;
              foreach (var i in a)
              {
                  if (b.ContainsKey(i.Key))
                  {
                      rs = true;
                  }
                  else
                  {
                      rs = false;
                      break;
                  }
              }
              return rs;
          }
          else
          {
              return false;
          }
      

      用法:

      if(EqualList(List<A>.ToDictionary(k => k.Key, k => k.Value), List<B>.ToDictionary(k => k.Key, k => k.Value)){
      
      }else{
      
      }
      
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2010-11-24
      • 1970-01-01
      • 1970-01-01
      • 2014-06-05
      • 2010-12-18
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多