将List <int>拆分为连续数字组

PM *_*tra 3 c# linq

我有一个排序,List<int>好像{ 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)
Run Code Online (Sandbox Code Playgroud)

Bra*_*ner 8

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

public static IEnumerable<IEnumerable<int>> GroupConsecutive(this IEnumerable<int> list) {
    var group = new List<int>();
    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<int> {i};
        }
    }
    yield return group;
}
Run Code Online (Sandbox Code Playgroud)

你可以像这样使用它:

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

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


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

public static class EnumerableExtensions
{
    public static IEnumerable<IEnumerable<int>> GroupConsecutive(this IEnumerable<int> 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);
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

  • 是不是更容易计算并返回`Enumerable.Range`而不是列表? (3认同)
  • @BradleyUffner:我实际上并没有,*运行*代码,但现在看起来非常好. (2认同)

Net*_*age 6

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

public static IEnumerable<IEnumerable<int>> GroupConsecutive(this IEnumerable<int> src) {
    var more = false; // compiler can't figure out more is assigned before use
    IEnumerable<int> ConsecutiveSequence(IEnumerator<int> 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
            // have to process to compute outside level  
            yield return ConsecutiveSequence(si).ToList();
        while (more);
    }
}
Run Code Online (Sandbox Code Playgroud)

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

public static IEnumerable<IEnumerable<int>> GroupConsecutive(this IEnumerable<int> iterable, Func<int,int> ordering = null) {
    ordering = ordering ?? (n => n);
    foreach (var tg in iterable
                         .Select((e, i) => (e, i))
                         .GroupBy(t => t.i - ordering(t.e)))
        yield return tg.Select(t => t.e);
}
Run Code Online (Sandbox Code Playgroud)

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

public static IEnumerable<IEnumerable<int>> GroupConsecutive(this IEnumerable<int> iterable, Func<int,int> ordering = null) => 
    iterable
      .Select((e, i) => (e, i))
      .GroupBy(
        t => t.i - (ordering ?? (n => n))(t.e),
        (k,tg) => tg.Select(t => t.e));
Run Code Online (Sandbox Code Playgroud)

注意:启用了可为空注释上下文的 C# 8 应该Func<int,int>?在这两种 Python 方法中使用。您也可以使用??=分配ordering.

  • C# 迭代器方法陷入延迟执行陷阱。试试 `var result = input.GroupConsecutive().ToList()` 你就会看到。虽然我相信使用 `GetEnumerator` 的实现是正确的方法,但当前的实现是错误的。过于注重简洁而不是功能。毕竟,拥有自定义扩展方法很重要,以允许简洁的使用,而不是简洁的实现。 (2认同)