實現一個基於相等性比較的 GroupBy
Intro
在我們的系統裡有些資料可能會有問題,資料來源頭不在我們這裡,資料不好修復,在做
GroupBy
的時候就會很痛苦,預設的 group by 會依賴於
HashCode
,而某些場景下
HashCode
可能並不太大做統一,所以擴充套件了一個不依賴
HashCode
,只需要考慮相等性比較的一個
GroupBy
Sample
我們有下面這樣的一些資料
var students = new StudentResult[]{ new() { StudentName = “Ming”, CourseName = “Chinese”, Score = 80, }, new() { StudentId = 1, StudentName = “Ming”, CourseName = “English”, Score = 60, }, new() { StudentId = 2, StudentName = “Mike”, CourseName = “English”, Score = 70, }, new() { StudentId = 1, CourseName = “Math”, Score = 100, }, new() { StudentName = “Mike”, CourseName = “Chinese”, Score = 60, },};
這些資料是一些學生成績,但是學生的資訊不全,學生資訊可能有 Id,可能有 Name,假設每個學生的 Id 和 Name 都是唯一的,不會重複,將上面的資訊按學生分組並獲取每個學生的總分數,你會怎麼實現呢?
Implement
預設的實現依賴於
HashCode
,實現原始碼可以參考文末連結,而多個欄位的
HashCode
比較難以統一,所以就想著自己擴充套件
GroupBy
,實現程式碼如下:
GroupBy
的返回值是
IEnumerable
,預設的
Grouping
的
Add
方法是
internal
的
我們先自定義一個簡單
IGrouping
,實現程式碼如下:
private sealed class Grouping
接著來實現我們的按相等性比較的
GroupBy
,實現如下:
public static IEnumerable
我們來測試一下我們的
GroupBy
,測試程式碼:
var groups = students。GroupByEquality(x => new Student() { Id = x。StudentId, Name = x。StudentName }, (s1, s2) => s1。Id == s2。Id || s1。Name == s2。Name, (k, x) => { if (k。Id <= 0 && x。StudentId > 0) { k。Id = x。StudentId; } if (k。Name。IsNullOrEmpty() && x。StudentName。IsNotNullOrEmpty()) { k。Name = x。StudentName; } });foreach (var group in groups){ Console。WriteLine(“——————————————————-”); Console。WriteLine($“{group。Key。Id} {group。Key。Name}, Total score: {group。Sum(x => x。Score)}”); foreach (var result in group) { Console。WriteLine($“{result。StudentId} {result。StudentName}\n{result。CourseName} {result。Score}”); }}
輸出結果如下:
可以看到前面的資料分成了兩組,但是可以看到的資料裡仍然是資訊不全的,我們可以稍微改進一下上面的方法,修改後如下:
public static IEnumerable
增加了一個
itemAction
,這裡加了一個 group count 大於 1 的條件,因為只有一個元素的時候,key 一定是來自這個元素不需要更新,所以加了一個條件,再來修改一下我們呼叫的示例:
var groups = students。GroupByEquality(x => new Student() { Id = x。StudentId, Name = x。StudentName }, (s1, s2) => s1。Id == s2。Id || s1。Name == s2。Name, (k, x) => { if (k。Id <= 0 && x。StudentId > 0) { k。Id = x。StudentId; } if (k。Name。IsNullOrEmpty() && x。StudentName。IsNotNullOrEmpty()) { k。Name = x。StudentName; } }, (x, k) => { if (k。Id > 0 && x。StudentId <= 0) { x。StudentId = k。Id; } if (k。Name。IsNotNullOrEmpty() && x。StudentName。IsNullOrEmpty()) { x。StudentName = k。Name; } });foreach (var group in groups){ Console。WriteLine(“——————————————————-”); Console。WriteLine($“{group。Key。Id} {group。Key。Name}, Total score: {group。Sum(x => x。Score)}”); foreach (var result in group) { Console。WriteLine($“{result。StudentId} {result。StudentName}\n{result。CourseName} {result。Score}”); }}
增加了
itemAction
,在最後將 key 的資訊再同步回 group 內的各個資料,此時我們再來執行一下我們的示例,結果如下:
可以看到現在我們的資料就都有 Id 和 Name 了~~
More
我們也可以增加一個
IEqualityComparer
的過載來支援自定義的 comparer
public static IEnumerable
References
https://github。com/dotnet/runtime/blob/main/src/libraries/System。Linq/src/System/Linq/Grouping。cs
https://github。com/dotnet/runtime/blob/main/src/libraries/System。Linq/src/System/Linq/Lookup。cs
https://github。com/WeihanLi/WeihanLi。Common/blob/05ba92b5439bfa8623ae9b3133bf78daf4a8f6b4/src/WeihanLi。Common/Extensions/EnumerableExtension。cs#L275
https://github。com/WeihanLi/WeihanLi。Common/blob/dev/samples/DotNetCoreSample/GroupByEqualitySample。cs#L10
文章來源於amazingdotnet ,作者WeihanLi