實現一個基于相等性比較的 GroupBy

實現一個基于相等性比較的 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<IGrouping<TKey, T>>,默認的 GroupingAdd 方法是 internal

我們先自定義一個簡單 IGrouping,實現代碼如下:

private?sealed?class?Grouping<TKey,?T>?:?IGrouping<TKey,?T>
{private?readonly?List<T>?_items?=?new();public?Grouping(TKey?key)?=>?Key?=?key????throw?new?ArgumentNullException(nameof(key));public?TKey?Key?{?get;?}public?void?Add(T?t)?=>?_items.Add(t);public?int?Count?=>?_items.Count;public?IEnumerator<T>?GetEnumerator(){return?_items.GetEnumerator();}IEnumerator?IEnumerable.GetEnumerator(){return?GetEnumerator();}
}

接著來實現我們的按相等性比較的 GroupBy,實現如下:

public?static?IEnumerable<IGrouping<TKey,?T>>?GroupByEquality<T,?TKey>(this?IEnumerable<T>?source,Func<T,?TKey>?keySelector,Func<TKey,?TKey,?bool>?comparer)
{var?groups?=?new?List<Grouping<TKey,?T>>();foreach?(var?item?in?source){var?key?=?keySelector(item);var?group?=?groups.FirstOrDefault(x?=>?comparer(x.Key,?key));if?(group?is?null){group?=?new?Grouping<TKey,?T>(key);group.List.Add(item);groups.Add(group);}else{keyAction?.Invoke(group.Key,?item);group.List.Add(item);}}return?groups;
}

我們來測試一下我們的 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}");}
}

輸出結果如下:

c4c511a3677a01c9d9c1b88a9733dbec.png

可以看到前面的數據分成了兩組,但是可以看到的數據里仍然是信息不全的,我們可以稍微改進一下上面的方法,修改后如下:

public?static?IEnumerable<IGrouping<TKey,?T>>?GroupByEquality<T,?TKey>(this?IEnumerable<T>?source,Func<T,?TKey>?keySelector,Func<TKey,?TKey,?bool>?comparer,Action<TKey,?T>??keyAction?=?null,?Action<T,?TKey>??itemAction?=?null)
{var?groups?=?new?List<Grouping<TKey,?T>>();foreach?(var?item?in?source){var?key?=?keySelector(item);var?group?=?groups.FirstOrDefault(x?=>?comparer(x.Key,?key));if?(group?is?null){group?=?new?Grouping<TKey,?T>(key){item};groups.Add(group);}else{keyAction?.Invoke(group.Key,?item);group.Add(item);}}if?(itemAction?!=?null){foreach?(var?group?in?groups.Where(g?=>?g.Count?>?1)){foreach?(var?item?in?group)itemAction.Invoke(item,?group.Key);}}return?groups;
}

增加了一個 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 內的各個數據,此時我們再來運行一下我們的示例,結果如下:

a2a8a1176178053c5d9c9c7e36e38299.png

可以看到現在我們的數據就都有 Id 和 Name 了~~

More

我們也可以增加一個 IEqualityComparer 的重載來支持自定義的 comparer

public?static?IEnumerable<IGrouping<TKey,?T>>?GroupByEquality<T,?TKey>(this?IEnumerable<T>?source,Func<T,?TKey>?keySelector,IEqualityComparer<TKey>?keyComparer,Action<TKey,?T>??keyAction?=?null,?Action<T,?TKey>??itemAction?=?null)?where?TKey?:?notnull
{return?GroupByEquality(source,?keySelector,?keyComparer.Equals,?keyAction,?itemAction);
}

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

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/282487.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/282487.shtml
英文地址,請注明出處:http://en.pswp.cn/news/282487.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

win7系統下載 ghost win7 Sp1 64位純凈3月版

win7系統下載 ghost win7 Sp1 64位純凈3月版 軟件名稱: Ghost Win7 Sp1 64位純凈3月版軟件語言: 簡體中文軟件大小: 5.25大小: GB發布日期: 2017-03-21文件名稱: ZJY_Ghost_win 7_X64_CJ201703.GHOM D 5: EB16DCD608A5CCFE34B58…

CrossPHP框架的常用操作

1. 在視圖控制器中使用$this->res()方法來生成資源文件的絕對路徑$this->res(css/style.css);生成的連接為http://youdomain.com/static/css/style.css2. 生成指定app名稱的連接$this->appUrl()第一個參數為基礎url, 第二個參數為app名稱, 第三個參數為 控制器:方法 第…

WPF-07 Style之觸發器

觸發器能夠在改變屬性值的時候&#xff0c;根據值變化執行操作&#xff0c;在不需要創建一個新的控件的情況下&#xff0c;可以動態的改變控件的外觀&#xff0c;當條件滿足時&#xff0c;觸發器可以改變任何屬性的值&#xff0c;觸發器通常定義在Style中&#xff0c;在窗體的根…

jdk自帶常用命令行工具使用

轉自&#xff1a;http://blog.csdn.net/winwill2012/article/details/46364923jps命令使用jps命令類似于Linux下的ps命令&#xff0c;用于列出當前正在運行的所有Java進程。基本用法直接運行不加任何參數就能列出所有java進程的pid和類的短名稱。例如&#xff1a;常用參數-q參數…

crossphp框架中,在模板中加載其他模板

這里說我自己做的項目的應用場景 要求是用layui框架的layer組件,實現彈出層效果,用原聲PHP無疑很容易做到,但是如果應用到crossphp框架流程就會非常麻煩 這里簡單講一下大致的步驟: 1. 在一個模板文件中應用layui的layer組件實現彈出框 index.tpl.php2. 從我們自己定義的路徑上…

for(auto c:s)與for(auto c:s)

在c11標準下可以執行的特殊格式的for循環語句&#xff0c;區別在于引用類型可以改變原來的值 #include<iostream> using namespace std; int main() {string s("hello world");for(auto c:s)ct;cout<<s<<endl;//結果為hello worldfor(auto &c:…

MASA Framework的MinimalAPIs應用

在以前的MVC引用程序中&#xff0c;控制器是一個功能齊全的框架&#xff0c;但它偏重&#xff0c;因此在.Net6.0官方引入了MinimalAPIs&#xff0c;即最小API&#xff0c;與MVC相比&#xff0c;它足夠的簡潔&#xff0c;適合小型服務來使用&#xff0c;下面就讓我們看看如何使用…

【轉】Java開發必須要知道的知識體系

Java Java是一門超高人氣編程語言&#xff0c;擁有跨平臺、面向對象、泛型編程等特性。在TIOBE編程語言排行榜中&#xff0c;連續奪得第一寶座&#xff0c;而且國內各大知名互聯網公司&#xff0c;后端開發首選語言&#xff1a;非Java莫屬。今天只是梳理下Java知識體系&#xf…

CrossPHP--在我們用ajax,js取不到指定數據時,我們可以換一種方式

項目中遇到的問題: 需求: 用的是layui的laypage組件,進行分頁操作,熟悉layui的朋友都知道,laypage需要從服務端給其一個總條數, 但是在進行ajax請求時出了問題, 我是這樣定義的但是調用的時候卻無法將數值直接返回回去,所以這里只能更換一種思路 在控制器中進行數據的查詢,然后…

VS 代碼行數統計

按CTRLSHIFTF (Find in files)&#xff0c;勾上支持正則表達式&#xff0c;然后輸入搜索內容&#xff1a; ^:b*[^:b#/].*$#開頭和/開頭或者空行都不計入代碼量。如果需要只統計代碼文件的代轉載于:https://www.cnblogs.com/sunlyk/p/7484728.html

MySQL設置從庫只讀模式

常見現象 運維工作中會經常維護MySQL主從服務器&#xff0c;當然Slave我們只是用于讀操作。 一般權限開通也只授權只讀賬號&#xff0c;但是有時候維護工作可能不是一個人在做&#xff0c;你不能保證其他同事都按照這個標準操作。 有同事可能會授權Slave庫MySQL賬號為all或者se…

尋找kernel32.dll的地址

為了尋找kernel32.dll的地址&#xff0c;可以直接輸出&#xff0c;也可以通過TEB,PEB等查找。 尋找TEB: dt _TEB nt!_TEB 0x000 NtTib : _NT_TIB 0x01c EnvironmentPointer : Ptr32 Void 0x020 ClientId : _CLIENT_ID 0x028 ActiveRpcHandle : Ptr32 Void 0x02c ThreadLocalSto…

layui彈出層使用(layer.alert / layer.open / layer.prompt )

一 layer.alert 效果圖: 代碼: //取消提現 function back(id) {layer.alert(真的要取消嗎, {skin: layui-layer-molv //樣式類名 自定義樣式,closeBtn: 1 // 是否顯示關閉按鈕,anim: 1 //動畫類型,btn: [確定,取消] //按鈕,icon: 6 // icon,yes:function(){return $.aj…

SkiaSharp 自繪彈幕效果

SkiaSharp 自繪彈幕效果控件名&#xff1a;SkiaSharpBarrage作者&#xff1a; 驚鏵原文鏈接&#xff1a; https://github.com/yanjinhuagood/SkiaSharpBarrage框架使用.NET60&#xff1b;Visual Studio 2022;項目使用 MIT 開源許可協議&#xff1b;接著上一篇 WPF 彈幕上期有…

JavaScript中this指向

一.重點來了&#xff0c;this指向問題&#xff1a;1.this指向之普通函數。 2.this指向之對象 3.this指向之構造函數 4.this指向之&#xff08;call,apply&#xff09;動態更改this指向。 二.具體分析如下 1.普通函數 // 第23行的調用者為null,this指向也為null,// 所以這時js把…

【python】python中的定義類屬性和對像屬性

python中變量是沒有類型的可以綁定任意類型&#xff0c;但是在語法上不能聲明變量。 那我們怎麼來聲名一個變量呢&#xff1f; fNone 這樣我們給著個變量綁定了以各None類型&#xff0c;我們隨時可用重新綁定其它類型。這樣我們起到了預先聲名變量的效果。 類中如何去定義類的…

提交Form表單,submit之前做js判斷處理

效果:在點擊提交按鈕時,首先進行js判斷, 如果不符合條件,則alert出提示信息,并return false. 主要點就在于給form表單添加一個onsubmit事件. 在onsubmit事件中定義的函數里進行js驗證處理.代碼 : <!DOCTYPE html> <html lang"en"> <head><meta …

如何在Windows上一鍵部署PaddleOCR的WebAPI服務

PaddleOCR旨在打造一套豐富、領先、且實用的OCR工具庫&#xff0c;助力開發者訓練出更好的模型&#xff0c;并應用落地。官方開源項目地址&#xff1a;https://github.com/PaddlePaddle/PaddleOCR一定會有小伙伴們看完不知道如何部署與應用&#xff0c;怎么才能融入到自己的產品…

微軟為 Visual Studio 擴展添加對 Arm64 的支持

微軟在 6 月份推出了支持 Arm64 架構的 Visual Studio&#xff0c;這是第一個原生支持在基于 Arm 的處理器上構建和調試 Arm64 應用程序的 Visual Studio 版本。近日&#xff0c;他們宣布為 Visual Studio 擴展也添加了對 Arm64 的支持&#xff0c;因此開發者可在 Arm64 Visual…

WIN10 查看已經連接的wifi的密碼

命令行: 1. 顯示以前連接過的wifi2. 將wifi配置存入文件中3. 查看剛剛保存的wifi配置的文件這樣,我們就可以看到連接的wifi名稱和wifi密碼了.