如何使用Linq从List <>计算值的范围?

时间:2019-04-28 18:47:34

标签: c# linq

我有一个C#中的有序数字列表,我想用LINQ计算可以根据其安全值获取的最小和最大值

列表始终是有序的,永远不会为空。

例如:

我的列表对象:

1060
1061
....
1089
1090
6368
6369
....
6383
6384
30165
30166
....
30214
30215

我的预期结果:

1060-1090
6368-6384
30165-30215

谢谢。

5 个答案:

答案 0 :(得分:1)

对于此类问题,Zip方法很方便。这就是它的作用:

将指定的函数应用于两个序列的相应元素,产生结果序列。

通过将序列与自身压缩在一起,可用于将序列的连续元素配对。

fn counting_function() -> u32 {
    static mut counter: u32 = 0;
    let retval = unsafe { counter };
    unsafe {
        counter += 1;
    }
    retval
}

输出:

  

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

答案 1 :(得分:1)

请考虑为IEnumerable<TSource>创建扩展方法,因此可以像使用LINQ函数一样使用它。参见Extension Methods Demystified

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

  • 如果您的输入序列为空怎么办?
  • 如果未订购输入怎么办?
  • 如果您多次获得相同的值怎么办:1 2 3 3 3 3 4 5?
  • 如果您的子序列只有一个连续数字: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]

我将成对的序列作为Tuple<int, int>的序列返回。如果需要,您可以为此创建一个专用的类。

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;
            }
        }
    }
}

用法:

IEnumerable<Tuple<int, int>> result = myInputList.ToMinMaxTuples();

或者在一些大型LINQ语句中间:

var result = Students
    .Where(student => student.Country == "Republique Française")
    .Select(student => student.Grade)
    .ToMinMaxTuples()
    .OrderBy(tuple => tuple.Item1)
    .ThenBy(tuple => tuple.Item2);

答案 2 :(得分:1)

//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]);

答案 3 :(得分:0)

如果实现简单的配对类,则可以使用.Aggregate() LINQ方法。 由于Tuple是不可变的,因此有必要使用对类,但是可以很容易地构造成这样。

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

    public T Min;
    public T Max;
}

然后在适当的位置进行.Aggregate()调用

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;
    });

答案 4 :(得分:0)

使用Scan扩展方法的成对增强版本,该方法基于类似于APL扫描运算符的聚合方法,但返回中间结果,因此创建了变量广义分组方法。我使用GroupByPairsWhile(先前已)为此类问题创建了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);

}

使用扩展方法,您的问题很简单:

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

这假定列表未排序。如果已知列表是有序的,则可以使用First()Last()代替Min()Max()

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