c# - 如何使用 IEnumerable.GroupBy 比较元素之间的多个属性?
问题描述
如何对“相邻”站点进行分组:
给定数据:
List<Site> sites = new List<Site> {
new Site { RouteId="A", StartMilepost=0.00m, EndMilepost=1.00m },
new Site { RouteId="A", StartMilepost=1.00m, EndMilepost=2.00m },
new Site { RouteId="A", StartMilepost=5.00m, EndMilepost=7.00m },
new Site { RouteId="B", StartMilepost=3.00m, EndMilepost=5.00m },
new Site { RouteId="B", StartMilepost=11.00m, EndMilepost=13.00m },
new Site { RouteId="B", StartMilepost=13.00m, EndMilepost=14.00m },
};
我想要结果:
[
[
Site { RouteId="A", StartMilepost=0.00m, EndMilepost=1.00m },
Site { RouteId="A", StartMilepost=1.00m, EndMilepost=2.00m }
],
[
Site { RouteId="A", StartMilepost=5.00m, EndMilepost=7.00m }
],
[
Site { RouteId="B", StartMilepost=3.00m, EndMilepost=5.00m }
],
[
Site { RouteId="B", StartMilepost=11.00m, EndMilepost=13.00m },
Site { RouteId="B", StartMilepost=13.00m, EndMilepost=14.00m }
]
]
我尝试使用 GroupBy 和自定义比较器函数检查 routeIds 匹配,并且第一个站点的结束里程碑等于下一个站点开始里程碑。我的 HashKey 函数只是检查 routeId,因此路由中的所有站点都会被合并在一起,但我认为比较器会假设 A = B,B = C,然后 A = C,所以 C 不会与 A 分组,B,C 因为在我的邻接情况下,A 不等于 C。
解决方案
首先,设Site
类为(用于调试/演示)
public class Site {
public Site() { }
public string RouteId;
public Decimal StartMilepost;
public Decimal EndMilepost;
public override string ToString() => $"{RouteId} {StartMilepost}..{EndMilepost}";
}
好吧,正如你所看到的,我们必须打破规则:相等必须是传递的,即无论何时
A equals B
B equals C
然后
A equals C
在您的示例中并非如此。但是,如果我们按照我们对站点进行排序StartMilepost
,从技术上讲,可以IEqualityComparer<Site>
这样实现:
public class MySiteEqualityComparer : IEqualityComparer<Site> {
public bool Equals(Site x, Site y) {
if (ReferenceEquals(x, y))
return true;
else if (null == x || null == y)
return false;
else if (x.RouteId != y.RouteId)
return false;
else if (x.StartMilepost <= y.StartMilepost && x.EndMilepost >= y.StartMilepost)
return true;
else if (y.StartMilepost <= x.StartMilepost && y.EndMilepost >= x.StartMilepost)
return true;
return false;
}
public int GetHashCode(Site obj) {
return obj == null
? 0
: obj.RouteId == null
? 0
: obj.RouteId.GetHashCode();
}
}
然后GroupBy
像往常一样;请注意这OrderBy
是必需的,因为这里的比较顺序很重要。假设我们有
A = {RouteId="X", StartMilepost=0.00m, EndMilepost=1.00m}
B = {RouteId="X", StartMilepost=1.00m, EndMilepost=2.00m}
C = {RouteId="X", StartMilepost=2.00m, EndMilepost=3.00m}
在这里A == B
,B == C
(所以如果A, B, C
所有项目都在同一个组中)但是A != C
(因此A, C, B
最终会出现在3
组中)
代码:
List<Site> sites = new List<Site> {
new Site { RouteId="A", StartMilepost=0.00m, EndMilepost=1.00m },
new Site { RouteId="A", StartMilepost=1.00m, EndMilepost=2.00m },
new Site { RouteId="A", StartMilepost=5.00m, EndMilepost=7.00m },
new Site { RouteId="B", StartMilepost=3.00m, EndMilepost=5.00m },
new Site { RouteId="B", StartMilepost=11.00m, EndMilepost=13.00m },
new Site { RouteId="B", StartMilepost=13.00m, EndMilepost=14.00m },
};
var result = sites
.GroupBy(item => item.RouteId)
.Select(group => group
// Required Here, since MySiteEqualityComparer breaks the rules
.OrderBy(item => item.StartMilepost)
.GroupBy(item => item, new MySiteEqualityComparer())
.ToArray())
.ToArray();
// Let's have a look
var report = string.Join(Environment.NewLine, result
.Select(group => string.Join(Environment.NewLine,
group.Select(g => string.Join("; ", g)))));
Console.Write(report);
结果:
A 0.00..1.00; A 1.00..2.00
A 5.00..7.00
B 3.00..5.00
B 11.00..13.00; B 13.00..14.00
推荐阅读
- sql - SQL SERVER 查询输出上出现一些奇怪的迹象
- java - 如何恢复 Play 商店中已有的 jks 文件的密码?
- jquery - $.extend 不是 React SSR 的函数
- seo - 查看详细说明后如何更改 H1
- java - @Tag 中的 spingfox-swagger2 描述不被尊重
- python - 为什么 Tensorflow tf.FIFOQueue 在下面的代码中会提前关闭?
- python - 从美丽的汤提取物中删除标签
- python - 尝试在 ColdFusion 站点上集成 Python 版本的 Vidyo
- ruby-on-rails - 简单形式 - 未定义的局部变量或方法“对象”
- python - 如何以编程方式更新选定的源索引?