将List 拆分为连续数组

我有一个排序的List{ 1, 2, 3, 4, 6, 7, 9 }
我想把它分成几个组 – 每个组都有这样的连续数字: { {1, 2, 3, 4}, {6, 7}, {9} }

我知道我可以使用for循环来遍历列表,并在当前值和先前值之间进行比较,然后决定是追加到最后一个组还是创建一个新组。 但我想找到一种“漂亮”的方式来做到这一点。 也许使用LINQ?

编辑:

我从项目more-itertools中找到了一个python代码:

 def consecutive_groups(iterable, ordering=lambda x: x): for k, g in groupby( enumerate(iterable), key=lambda x: x[0] - ordering(x[1]) ): yield map(itemgetter(1), g) 

这是我对使用迭代器的扩展方法的建议:

 public static IEnumerable> GroupConsecutive(this IEnumerable src) { bool more = false; // compiler can't figure out more is assigned before use IEnumerable ConsecutiveSequence(IEnumerator csi) { int prevCurrent; do yield return (prevCurrent = csi.Current); while ((more = csi.MoveNext()) && csi.Current-prevCurrent == 1); } var si = src.GetEnumerator(); if (si.MoveNext()) { do yield return ConsecutiveSequence(si).ToList(); // have to process to compute outside level :( while (more); } } 

我必须说Python算法非常令人印象深刻,这里是它的C#实现:

 public static IEnumerable> GroupConsecutive(this IEnumerable iterable, Func ordering = null) { ordering = ordering ?? (n => n); foreach (var tg in iterable.Select((e, i) => (e, i)).GroupBy(t => ti - ordering(te))) yield return tg.Select(t => te); } 

这是Python算法的C#单行实现:

 public static IEnumerable> GroupConsecutive(this IEnumerable iterable, Func ordering = null) => iterable.Select((e, i) => (e, i)).GroupBy(t => ti - (ordering ?? (n => n))(te), (k,tg) => tg.Select(t => te)); 

这是一个扩展方法,取自http://bugsquash.blogspot.com/2010/01/grouping-consecutive-integers-in-c.html

 public static IEnumerable> GroupConsecutive(this IEnumerable list) { var group = new List(); foreach (var i in list) { if (group.Count == 0 || i - group[group.Count - 1] <= 1) group.Add(i); else { yield return group; group = new List {i}; } } yield return group; } 

你可以像这样使用它:

 var numbers = new[] { 1, 2, 3, 4, 6, 7, 9 }; var groups = numbers.GroupConsecutive(); 

一旦C#7发布,使用Span可以提高效率,从而避免创建新列表。


此更新版本无需分配任何列表即可完成。

 public static class EnumerableExtensions { public static IEnumerable> GroupConsecutive(this IEnumerable list) { if (list.Any()) { var count = 1; var startNumber = list.First(); int last = startNumber; foreach (var i in list.Skip(1)) { if (i < last) { throw new ArgumentException($"List is not sorted.", nameof(list)); } if (i - last == 1) count += 1; else { yield return Enumerable.Range(startNumber, count); startNumber = i; count = 1; } last = i; } yield return Enumerable.Range(startNumber, count); } } } 

@Bradley Uffner和@NetMage非分配迭代器方法的正确实现是这样的:

 public static IEnumerable> GroupConsecutive(this IEnumerable source) { using (var e = source.GetEnumerator()) { for (bool more = e.MoveNext(); more; ) { int first = e.Current, last = first, next; while ((more = e.MoveNext()) && (next = e.Current) > last && next - last == 1) last = next; yield return Enumerable.Range(first, last - first + 1); } } } 

即使对于无序输入,它也能正常工作,只对源序列进行一次迭代,并正确处理所有极点情况和整数上/下溢。 它失败的唯一情况是连续范围计数大于int.MaxValue

但是看看你的后续问题 ,下面的实现可能更适合你的需求:

 public static IEnumerable<(int First, int Last)> ConsecutiveRanges(this IEnumerable source) { using (var e = source.GetEnumerator()) { for (bool more = e.MoveNext(); more;) { int first = e.Current, last = first, next; while ((more = e.MoveNext()) && (next = e.Current) > last && next - last == 1) last = next; yield return (first, last); } } } 

请尝试以下代码;

 public static IEnumerable> GroupConsecutive(this IEnumerable source) { if (!source.Any()) { yield break;} var prev = source.First(); var grouped = new List(){ prev }; source = source.Skip(1); while (source.Any()) { var current = source.First(); if (current - prev != 1) { yield return grouped; grouped = new List(); } grouped.Add(current); source = source.Skip(1); prev = current; } yield return grouped; } var numbers = new[] { 1, 2, 3, 4, 6, 7, 9 }; var result = numbers.GroupConsecutive(); Output 1,2,3,4 6,7 9