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

时间:2018-01-22 17:55:53

标签: c# linq

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

我知道我可以使用for循环来遍历列表,并在当前值和先前值之间进行比较,然后决定是追加到最后一个组还是创建一个新组。但我想找到一个漂亮的&#34;这样做的方式。也许使用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)

4 个答案:

答案 0 :(得分: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;
}

你可以像这样使用它:

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

一旦发布了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);
        }
    }
}

答案 1 :(得分:3)

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

public static IEnumerable<IEnumerable<int>> GroupConsecutive(this IEnumerable<int> src) {
    bool 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
            yield return ConsecutiveSequence(si).ToList(); // have to process to compute outside level :(
        while (more);
    }
}

我必须说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);
}

这是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));

答案 2 :(得分:2)

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

public static IEnumerable<IEnumerable<int>> GroupConsecutive(this IEnumerable<int> 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

但是看看你的follow up question,下面的实现可能更适合你的需求:

public static IEnumerable<(int First, int Last)> ConsecutiveRanges(this IEnumerable<int> 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);
        }
    }
}

答案 3 :(得分:0)

尝试以下代码;

public static IEnumerable<IEnumerable<int>> GroupConsecutive(this IEnumerable<int> source)
{
    if (!source.Any()) { yield break;}
    var prev = source.First();
    var grouped = new List<int>(){ prev };
    source = source.Skip(1);
    while (source.Any())
    {
        var current = source.First();
        if (current - prev != 1)
        {
            yield return grouped;
            grouped = new List<int>();
        }
        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