如何使用LINQ查询满足特定事件序列顺序条件的Thing对象?
作为非英语母语者,我很难搜索到相关内容——因为我想到的搜索关键词,搜出来的结果大多都是关于OrderBy(Descending)的,完全不是我要找的东西。
我现在有个场景:我有Thing对象,每个Thing包含一组ThingEvent对象的集合。我想查询那些事件满足特定发生顺序的Thing对象。
我先写个伪代码来表达我的需求:
var result = things.Where(t => t.ThingEvents .Has( te => te.EventType == 1 ) .FollowedBy( te => te.EventType == 2 ) ) .ToList();
这只是用来示意的伪代码,Thing.ThingEvents是IEnumerable<ThingEvent>类型的集合,所以上面的Has()和FollowedBy()是我脑补的两个LINQ方法,它们要实现的功能是:
- 第一,验证给定的谓词是否成立——这一点和LINQ的
Where()类似; - 第二,对于
FollowedBy()来说,要确保当前匹配的事件出现在前一个“匹配项”之后。
第二点就是我卡壳的地方:这意味着ThingEvents集合必须是按顺序排列的(在执行这个查询前,集合已经被正确排序了)。
举几个匹配/不匹配的例子:
- 事件类型序列
[1,2]会匹配; [1,1,1,1,2,2,2,2]也会匹配;[1,2,1,2,3,1,2,1,2]同样匹配;- 但
[2,1,1,1,1,1]不会匹配——因为这里从来没有出现“事件2在事件1之后发生”的情况。
现在的问题是:用.NET内置的LINQ方法,能不能实现这样的查询?
如果不行的话,通过扩展LINQ来实现这样的功能可行吗?我理解不了的地方是:像Where()这样的方法只返回一个布尔值,表示被查询项是否满足谓词,但我脑补的Has()和FollowedBy()不仅要返回布尔值,还要把“在序列中的位置上下文”传递给后续方法。当然类似的方法还可以有其他的,比如PrecededBy()之类的。
我是不是在做LINQ设计之外的事情?或者我的思路完全错了?又或者我漏掉了什么基础的知识点?
感谢任何相关的见解!
编辑:我已经尽量描述准确了,但还是有细节没说清楚,造成了混淆😅
有人指出,我例子里的FollowedBy到底是指“紧随其后”(也就是下一个元素就是目标),还是“之后任意位置存在”。我原本的意思是后者,但大家的问题让我意识到,这两种场景其实都有需求。
再补充点上下文,方便大家理解这个需求的实用性:
- 系统中有很多
Thing对象,常规查询可能会返回几百个,聚合数据的话可能有几千个; - 每个
Thing的ThingEvents数量在0到20之间; Thing这些类都在类库代码里,上面的查询需求是为了让用户能从Thing对象的数据库中读取、生成统计信息——比如用户会问:“有没有Thing出现过事件1发生后,又发生了事件2的情况?”这就是我上面伪代码想表达的需求。
LINQ能轻松实现大部分查询,但我找不到简单的方法来查询“集合中元素的相对位置关系”。
也有人提到要避免多次枚举集合的问题。那IEnumerable是不是不合适的抽象?我是不是需要一个能感知“上一个/当前元素位置”的类似接口?我喜欢LINQ的灵活性,但我不想为了实现这种灵活性而重造轮子——或者说,我真的需要这么做吗?
第二次编辑:这算个题外话,今早洗澡时突然想到,这个问题用正则表达式可能很容易解决!(虽然这就放弃了优雅的LINQ方案😂)
假设我们只有10种事件类型,给它们分配0-9的字符,每个Thing可以把自己的事件序列转换成对应的字符串。然后我们就能用正则表达式来匹配复杂的序列了:
- 用
1.*2来实现“事件1之后任意位置有事件2”的FollowedBy(); - 用
12来实现“事件1紧随其后是事件2”的场景。
我可能真的会试试这个方案,虽然实际实现会比这个示例复杂一点,但能获得我想要的灵活性。如果这个方案能证明正则表达式有时候确实能让问题变简单,说不定我还能拿个诺贝尔奖(开玩笑的),到时候我请大家喝啤酒!
备注:内容来源于stack exchange,提问作者Rune Jacobsen

