实现一个基于相等性比较的 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.mzph.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密码了.