C# 如何使用Linq计算列表中的值范围?

C# 如何使用Linq计算列表中的值范围?,c#,linq,C#,Linq,我有一个C中的有序数字列表,我想根据它们的secuencial值,用LINQ计算最小值和最大值 列表总是按顺序排列的,从不为空 例如: 我的列表对象: 1060 1061 .... 1089 1090 6368 6369 .... 6383 6384 30165 30166 .... 30214 30215 我的预期结果: 1060-1090 6368-6384 30165-30215 谢谢。对于此类问题,该方法非常方便。这就是它所做的: 将指定的函数应用于两个序列的对应元素,生成结果序列

我有一个C中的有序数字列表,我想根据它们的secuencial值,用LINQ计算最小值和最大值

列表总是按顺序排列的,从不为空

例如:

我的列表对象:

1060
1061
....
1089
1090
6368
6369
....
6383
6384
30165
30166
....
30214
30215
我的预期结果:

1060-1090
6368-6384
30165-30215

谢谢。

对于此类问题,该方法非常方便。这就是它所做的:

将指定的函数应用于两个序列的对应元素,生成结果序列

通过将序列与自身进行ziping,它可用于将序列的连续元素配对

var source = new List<int> { 1, 2, 3, 4, 5, 11, 12, 13, 21, 22 };
var gaps = source
    .Zip(source.Skip(1), (n1, n2) => (n1, n2, gap: n2 - n1)) // Calculate the gaps
    .Where(e => e.gap != 1) // Select non sequential pairs
    .ToArray();
var gapsEx = gaps
    .Prepend((n1: 0, n2: source.First(), gap: 0)) // Add the first element
    .Append((n1: source.Last(), n2: 0, gap: 0)) // Add the last element
    .ToArray();
var results = gapsEx
    .Zip(gapsEx.Skip(1), (e1, e2) => (from: e1.n2, to: e2.n1)); // Pairwise gaps

Console.WriteLine($"Results: {String.Join(", ", results.Select(r => r.from + "-" + r.to))}");
输出:

结果:1-5、11-13、21-22


考虑为IEnumerable创建一个扩展方法,这样就可以像使用LINQ函数一样使用它。看

您的示例没有处理几个问题:

如果您的输入序列为空怎么办? 如果输入没有排序怎么办? 如果你有几次相同的值:12335? 如果子序列只有一个连续的数字:1 2 7 18 19会怎么样? 因此,让我们给出一个适当的要求:

给定整数的输入序列,创建整数对的输出序列,其中值是输入序列中连续数字序列的第一个和最后一个数字

示例:

1060 1061 ... 1089 1090 6368 6369 ... 6384 30165 ... => [1060, 1090] [6369, 6384] [30165 2 3 4 5 17 18 19 4 5 6 7 1 2 3 4 5 => [2, 5] [17, 19] [4, 7] [1 5] 2 3 4 5 6 8 9 => [2, 5] [6, 6] [8, 9] 我将以元组序列的形式返回成对序列。如果需要,您可以为此创建一个专用类

static IEnumerable<Tuple<int, int>> ToMinMaxTuples(this IEnumerable<int> source)
{
    // TODO: source == null
    var enumerator = source.GetEnumerator();
    if (enumerator.MoveNext())
    {
        // there is at least one item in source
        int min = enumerator.Current;
        int max = min;
        while (enumerator.MoveNext())
        {
            // there is another item in the sequence
            if (enumerator.Current == max + 1)
            {
                // current is part of the current sequence, continue with next number
                max = enumerator.Current;
            }
            else
            {
                // current is not part of the current sequence,
                // it is the start of the next one
                // yield return [min, max] as a Tuple:
                yield return new Tuple<int, int>(min, max);

                // start the next sequence:
                min = enumerator.Current;
                max = min;
            }
        }
    }
}

如果实现一个简单的pair类,那么可以使用.Aggregate LINQ方法。 由于元组是不可变的,所以pair类是必需的,但它可以像这样轻松地构造

public class MinMaxPair<T>
{
    public MinMaxPair(T min, T max)
    {
        Min = min;
        Max = max;
    }

    public T Min;
    public T Max;
}

使用我的扫描扩展方法的成对增强版本(基于APL扫描运算符,类似于聚合,但返回中间结果),我创建了变量广义分组方法。使用GroupByPairs,而我以前为此类问题创建了GroupBySequential方法

public static class IEnumerableExt {
    // TKey combineFn((TKey Key, T Value) PrevKeyItem, T curItem):
    // PrevKeyItem.Key = Previous Key
    // PrevKeyItem.Value = Previous Item
    // curItem = Current Item
    // returns new Key
    public static IEnumerable<(TKey Key, T Value)> ScanToPairs<T, TKey>(this IEnumerable<T> src, TKey seedKey, Func<(TKey Key, T Value), T, TKey> combineFn) {
        using (var srce = src.GetEnumerator())
            if (srce.MoveNext()) {
                var prevkv = (seedKey, srce.Current);

                while (srce.MoveNext()) {
                    yield return prevkv;
                    prevkv = (combineFn(prevkv, srce.Current), srce.Current);
                }
                yield return prevkv;
            }
    }

    // bool testFn(T prevItem, T curItem)
    // returns groups by runs of matching bool
    public static IEnumerable<IGrouping<int, T>> GroupByPairsWhile<T>(this IEnumerable<T> src, Func<T, T, bool> testFn) =>
        src.ScanToPairs(1, (kvp, cur) => testFn(kvp.Value, cur) ? kvp.Key : kvp.Key + 1)
           .GroupBy(kvp => kvp.Key, kvp => kvp.Value);

    public static IEnumerable<IGrouping<int, int>> GroupBySequential(this IEnumerable<int> src) => src.GroupByPairsWhile((prev, cur) => prev + 1 == cur);

}
这假设列表未排序。如果已知列表已排序,则可以使用First和Last,而不是Min和Max


注意:扩展方法可能看起来很复杂,但它们为多种不同类型的分组提供了基础,包括按相等项的运行分组、按通用测试函数分组,以及在成对工作时处理第一个和最后一个元素的各种种子和结束策略。

为什么选择LINQ?您将如何选择ndle像5、7、9这样的序列?列表总是有序的吗?是的,我编辑了我的帖子。我认为你的执行有一个错误。最后一个范围没有屈服。顺便说一句,不要忘记!你是对的:最后一个范围没有屈服。如果我有时间,如果我记得这样做,我会纠正它。也许其他人愿意添加最后一对的屈服回报。当然,我们应该把GetEnumerator放在using语句中。谢谢Harald。我会试试你的解决方案。谢谢。这是最简单的解决方案。
public class MinMaxPair<T>
{
    public MinMaxPair(T min, T max)
    {
        Min = min;
        Max = max;
    }

    public T Min;
    public T Max;
}
nums.Aggregate(
    new List<MinMaxPair<int>>(),
    (sets, next) =>
    {
        if (!sets.Any() || next - sets.Last().Max > 1)
        {
            sets.Add(new MinMaxPair<int>(next, next));
        }
        else
        {
            var minMax = sets.Last();
            if (next < minMax.Min)
                minMax.Min = next;
            else
                minMax.Max = next;
        }
        return sets;
    });
//Sample list of ordered integers
List<int> lst = new List<int>{101,102,103,104,106,107,108,111,112,114,120,121};

// find minimum element of each sub-sequence within the above list
var minBoundaries = lst.Where(i => !lst.Contains(i-1)).ToList();

// find maximum element of each sub-sequence within the above list
var maxBoundaries = lst.Where(i => !lst.Contains(i+1)).ToList();

//format minimum and maximum elements of each sub-sequence as per the sample output in the question
var result = new List<string>();
for(int i = 0; i < maxBoundaries.Count; i++) 
    result.Add(minBoundaries[i]+"-"+maxBoundaries[i]);
public static class IEnumerableExt {
    // TKey combineFn((TKey Key, T Value) PrevKeyItem, T curItem):
    // PrevKeyItem.Key = Previous Key
    // PrevKeyItem.Value = Previous Item
    // curItem = Current Item
    // returns new Key
    public static IEnumerable<(TKey Key, T Value)> ScanToPairs<T, TKey>(this IEnumerable<T> src, TKey seedKey, Func<(TKey Key, T Value), T, TKey> combineFn) {
        using (var srce = src.GetEnumerator())
            if (srce.MoveNext()) {
                var prevkv = (seedKey, srce.Current);

                while (srce.MoveNext()) {
                    yield return prevkv;
                    prevkv = (combineFn(prevkv, srce.Current), srce.Current);
                }
                yield return prevkv;
            }
    }

    // bool testFn(T prevItem, T curItem)
    // returns groups by runs of matching bool
    public static IEnumerable<IGrouping<int, T>> GroupByPairsWhile<T>(this IEnumerable<T> src, Func<T, T, bool> testFn) =>
        src.ScanToPairs(1, (kvp, cur) => testFn(kvp.Value, cur) ? kvp.Key : kvp.Key + 1)
           .GroupBy(kvp => kvp.Key, kvp => kvp.Value);

    public static IEnumerable<IGrouping<int, int>> GroupBySequential(this IEnumerable<int> src) => src.GroupByPairsWhile((prev, cur) => prev + 1 == cur);

}
var ans = src.GroupBySequential().Select(g => new { Min = g.Min(), Max = g.Max() });