C#快速提取List连续值:鼠标轨迹数据分组优化需求
优雅处理鼠标轨迹数据分组的LINQ方案
嘿,这个处理鼠标轨迹分组的需求我太熟悉了!之前做类似的交互分析工具时也踩过遍历写法的坑,代码冗余还容易写错边界条件。用LINQ结合相邻分组的技巧,能让代码优雅很多,而且可读性和维护性都能上来。
首先,先明确我们要解决的核心问题:把连续相同ButtonsDownFlag的鼠标事件分成组——原生LINQ的GroupBy是按值全局分组,没法区分连续的序列,所以我们先写一个通用的相邻分组扩展方法,这个方法以后处理其他连续序列场景也能复用:
public static IEnumerable<IEnumerable<T>> GroupAdjacent<T>(this IEnumerable<T> source, Func<T, T, bool> predicate) { using var enumerator = source.GetEnumerator(); if (!enumerator.MoveNext()) yield break; var currentGroup = new List<T> { enumerator.Current }; while (enumerator.MoveNext()) { if (predicate(currentGroup.Last(), enumerator.Current)) { currentGroup.Add(enumerator.Current); } else { yield return currentGroup; currentGroup = new List<T> { enumerator.Current }; } } yield return currentGroup; }
接下来,我们基于这个扩展方法,结合你的需求拆分出两个目标列表:
1. 提取拖拽操作列表(连续≥2个ButtonsDownFlag=1的组)
我们筛选出所有连续Flag=1且长度≥2的组,然后把组内每个元素的TimeStamp替换为组首的时间戳:
var dragOperations = mouseInfos .GroupAdjacent((prev, curr) => prev.ButtonsDownFlag == curr.ButtonsDownFlag) .Where(group => group.First().ButtonsDownFlag == 1 && group.Count() >= 2) .SelectMany(group => group.Select(item => new { TimeStamp = group.First().TimeStamp, item.PosX, item.PosY }) ) .ToList();
2. 提取点击操作列表(单独非0Flag项,含连续右键)
这里需要区分两种情况:
- 对于
Flag=1的组,只有当组长度为1时才视为点击; - 对于其他非0Flag(比如右键2、双按键3),不管是否连续,每个元素都视为单独点击:
var clickOperations = mouseInfos .GroupAdjacent((prev, curr) => prev.ButtonsDownFlag == curr.ButtonsDownFlag) .Where(group => group.First().ButtonsDownFlag != 0) .SelectMany(group => group.First().ButtonsDownFlag == 1 ? group.Where(_ => group.Count() == 1).Select(item => new { item.PosX, item.PosY, item.ButtonsDownFlag }) : group.Select(item => new { item.PosX, item.PosY, item.ButtonsDownFlag }) ) .ToList();
测试示例数据
用你给出的示例数据验证:
- 拖拽列表会得到:
(t2,x2,y2), (t2,x3,y3), (t7,x7,y7), (t7,x8,y8), (t7,x9,y9),完全符合预期; - 点击列表会得到:
(x5,y5,1), (xb,yb,2), (xd,yd,2), (xe,ye,2),也和需求一致。
一些注意事项
- 因为你的
MouseInfo类中TimeStamp是readonly的,所以我们用匿名类型来存储修改后的数据,如果需要返回MouseInfo实例,可以添加一个带参数的构造函数来创建新对象; - 这个方案的时间复杂度是O(n),和遍历写法效率一致,但代码更简洁,逻辑更清晰;
- 扩展方法
GroupAdjacent是通用的,可以放在你的工具类中,后续处理任何连续序列分组都能直接用。
内容的提问来源于stack exchange,提问作者Tide Gu
相关产品推荐
相关产品推荐

