如何使用Linq对列表中连续相同值的项进行分组
用LINQ实现连续相同Name项的分组筛选
先看你给出的原始数据:
var schedules = new List<Item>{ new Item { Id=1, Name = "S" }, new Item { Id=2, Name = "P" }, new Item { Id=3, Name = "X" }, new Item { Id=4, Name = "X" }, new Item { Id=5, Name = "P" }, new Item { Id=6, Name = "P" }, new Item { Id=7, Name = "P" }, new Item { Id=8, Name = "S" } };
你的需求很明确:要把连续相同Name的项归为子列表,而且只保留元素数量≥2的组,最终结果应该是这样:
var groupedAndSelectedList = new List<List<Item>>{ new List<Item> { new Item { Id=3, Name = "X" }, new Item { Id=4, Name = "X" } }, new List<Item> { new Item { Id=5, Name = "P" }, new Item { Id=6, Name = "P" }, new Item { Id=7, Name = "P" } } };
确实,普通的GroupBy会把所有同名的项都塞到一组里,不管是不是连续的,所以得换个思路用LINQ实现。这里给你两种可行的方案:
方案一:用Aggregate跟踪分组状态
Aggregate方法可以帮我们在遍历过程中跟踪当前的分组状态,非常适合这种连续分组的场景:
var result = schedules.Aggregate(new List<List<Item>>(), (groups, currentItem) => { // 检查有没有分组,或者最后一组的Name和当前元素不一致 if (!groups.Any() || groups.Last().First().Name != currentItem.Name) { // 新建一个分组,把当前元素加进去 groups.Add(new List<Item> { currentItem }); } else { // 把当前元素加入最后一个同名单分组 groups.Last().Add(currentItem); } return groups; }) // 过滤掉只有单个元素的分组 .Where(group => group.Count >= 2) .ToList();
逻辑说明:
- 初始化一个空的分组列表作为累积器;
- 逐个遍历原始列表的元素:
- 如果还没有分组,或者最后一个分组的Name和当前元素不同,就新建一个分组;
- 如果Name相同,就把当前元素追加到最后一个分组里;
- 最后过滤掉元素数小于2的分组,得到符合要求的结果。
方案二:给连续组分配唯一ID再分组
这种方式更直观,先给每个连续的同名序列分配一个递增的GroupId,再按ID分组:
int currentGroupId = 0; string previousName = null; var result = schedules .Select(item => { // 如果当前元素Name和上一个不同,就更新分组ID if (item.Name != previousName) { currentGroupId++; previousName = item.Name; } return new { Item = item, GroupId = currentGroupId }; }) .GroupBy(groupedItem => groupedItem.GroupId) .Select(group => group.Select(g => g.Item).ToList()) .Where(group => group.Count >= 2) .ToList();
逻辑说明:
- 用两个变量跟踪当前分组ID和上一个元素的Name;
- 遍历每个元素时,判断Name是否变化,变化就递增分组ID;
- 按分组ID进行GroupBy,再把每个组转成Item列表;
- 最后过滤掉小分组即可。
两种方案都能完美实现你的需求,选哪种看你个人习惯~
内容的提问来源于stack exchange,提问作者barteloma
相关产品推荐
相关产品推荐

