LINQ筆記:利用Except()進行快速資料差集比對

在LINQ的世界裡,若在一般的情況下要比對兩個資料的差集,通常我們會直觀用.Any搭配.Contains直接做掉(詳見文末相關參考),但其實這樣的方式只適用於數百筆資料量少的情況之下,若是資料上看數萬、數十萬甚至更多筆資料,那麼我就完全不建議採用這種方式,因為這種方式產生的運算量將形成O(n x m)的笛卡爾乘積運算(Cartesian product),若採用Except()方法可以將運算量降低至O(n + m),相較之下速度自然是快到爆炸了。

這篇文章將會針對直觀的寫法,以及Except()的兩種寫法進行羅列比較,大家可以自行看看喜歡哪一種寫法,我個人的建議如果是真的遇到要使用Except()的情境了,那應該採用實作IEqualityComparer比較器介面來完成,來達成最佳的效能(但是程式碼會比較囉嗦)。

LINQ資料差集的做法比較

首先先定義ORM資料類別,並創造出兩個準備進行差集的資料集合。母體讓他直接攻上十萬筆且故意採用數值與文字混合,來進行效能的比較。

List<Student> AllStudents = new List<Student>();
for (int i = 0; i < 100000; i++)
{ AllStudents.Add(new Student() { ID = i, Name = $"ABC{i}" }); }

List<Student> BadStudents = new List<Student>();
for (int i = 75000; i < 90000; i++)
{ BadStudents.Add(new Student() { ID = i, Name = $"ABC{i}" }); }

public class Student
{
  public int ID { get; set; }
  public string Name { get; set; }
}

第一種:LINQ差集直觀寫法(效能最差)

var MS = AllStudents
  .Where(x => !BadStudents.Any(y => y.ID == x.ID && y.Name == x.Name))
  .ToList();

第二種:LINQ差集Except()偷懶寫法(效能次之)

var MS = AllStudents
  .Select(x => new {x.ID, x.Name })
  .Except(BadStudents.Select(x => new { x.ID, x.Name }))
  .ToList();

第三種:LINQ差集Except()完整寫法(效能最佳)

var MS = AllStudents
  .Except(BadStudents, new StudentComparer())
  .ToList();

//實作比較器介面
public class StudentComparer : IEqualityComparer<Student>
{
  public bool Equals(Student x, Student y)
  {
    if (object.ReferenceEquals(x, y))
    { return true; }
    if (object.ReferenceEquals(x, null) || object.ReferenceEquals(y, null))
    { return false; }
    return x.ID == y.ID && x.Name == y.Name;
  }
  public int GetHashCode(Student obj)
  {
    if (obj == null)
    { return 0; }
    int IDHashCode = obj.ID.GetHashCode();
    int NameHashCode = obj.Name == null ? 0 : obj.Name.GetHashCode();
    return IDHashCode ^ NameHashCode;
  }
}

當然啦,還有很多種其他五花八門的Except()寫法,但我不想去擴大額外討論。

實驗比較結果:

因為偷懶,在此僅記錄我實驗完成的數據,若不信的人就...自己動手做樂趣多吧。

LINQ差集方法 執行時間 耗用記憶體
直觀寫法 21736ms 20.0MB
Except()偷懶寫法 51ms 22.9MB
Except()完整寫法 37ms 22.6MB

建議效能要看他增進的百分率,因為如果把比對的資料差距擴大到百萬筆,那執行時間與耗用記憶體的差距會變得非常可觀。

相關參考

LINQ SQL LeftOuterJoin Except 資料 集合 比對 相差 差集