【问题标题】:Finding Consecutive Items in List using Linq使用 Linq 在列表中查找连续的项目
【发布时间】:2017-06-18 01:06:54
【问题描述】:

假设我有以下整数数组:

int[] numbers = { 1, 6, 4, 10, 9, 12, 15, 17, 8, 3, 20, 21, 2, 23, 25, 27, 5, 67,33, 13, 8, 12, 41, 5 };

我如何编写一个 Linq 查询来找到 3 个连续元素,例如,大于 10?另外,如果我能指定我想说的第一、第二、第三组这样的元素,那就太好了。

例如,Linq 查询应该能够识别: 12,15,17作为第一组连续元素 23、25、27为第二组 67,33,13为第三组

如果我指定我想要第二组 3 个连续元素,查询应该返回给我第二组。

谢谢。

【问题讨论】:

    标签: linq


    【解决方案1】:

    更新:虽然技术上不是 Patrick 在 cmets 中指出的“linq 查询”,但该解决方案是可重用、灵活且通用的。

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace ConsoleApplication32
    {
        class Program
        {
            static void Main(string[] args)
            {
                int[] numbers = { 1, 6, 4, 10, 9, 12, 15, 17, 8, 3, 20, 21, 2, 23, 25, 27, 5, 67,33, 13, 8, 12, 41, 5 };
    
                var consecutiveGroups = numbers.FindConsecutiveGroups((x) => x > 10, 3);
    
                foreach (var group in consecutiveGroups)
                {
                    Console.WriteLine(String.Join(",", group));
                }
            }        
        }
    
        public static class Extensions
        {
            public static IEnumerable<IEnumerable<T>> FindConsecutiveGroups<T>(this IEnumerable<T> sequence, Predicate<T> predicate, int count)
            {
                IEnumerable<T> current = sequence;
    
                while (current.Count() > count)
                {
                    IEnumerable<T> window = current.Take(count);
    
                    if (window.Where(x => predicate(x)).Count() >= count)
                        yield return window;
    
                    current = current.Skip(1);
                }
            }
        }
    }
    

    输出:

    12,15,17
    23,25,27
    67,33,13 
    

    要获得第二组,请更改:

    var consecutiveGroups = numbers.FindConsecutiveGroups((x) => x > 10, 3);
    

    收件人:

    var consecutiveGroups = numbers.FindConsecutiveGroups((x) => x > 10, 3).Skip(1).Take(1);
    

    更新 2 在我们的生产使用中对此进行了调整后,随着 numbers 数组中的项目数越来越大,以下实现要快得多。

    public static IEnumerable<IEnumerable<T>> FindConsecutiveGroups<T>(this IEnumerable<T> sequence, Predicate<T> predicate, int sequenceSize)
    {
        IEnumerable<T> window = Enumerable.Empty<T>();
    
        int count = 0;
    
        foreach (var item in sequence)
        {
            if (predicate(item))
            {
                window = window.Concat(Enumerable.Repeat(item, 1));
                count++;
    
                if (count == sequenceSize)
                {
                    yield return window;
                    window = window.Skip(1);
                    count--;
                }
            }
            else
            {
                count = 0;
                window = Enumerable.Empty<T>();
            }
        }
    }
    

    【讨论】:

    • 太棒了! - 非常感谢。
    • @Patrick,从技术上讲,它不是“linq 查询”,但如果您删除 using System.Linq;行,这是行不通的,因此它是 Linq,只是不是以查询的形式。而且这种方式更加灵活。以可重复使用的方式解决问题。
    • @Patrick - 没错,这不是 Linq 查询,但它确实允许我执行 Linq 查询来完成我需要做的事情。
    【解决方案2】:
    int[] numbers = { 1, 6, 4, 10, 9, 12, 15, 17, 8, 3, 20, 21, 2, 23, 25, 27, 5, 67, 33, 13, 8, 12, 41, 5 };
    
    var numbersQuery = numbers.Select((x, index) => new { Index = index, Value = x});
    
    var query = from n in numbersQuery
                from n2 in numbersQuery.Where(x => n.Index == x.Index - 1).DefaultIfEmpty()
                from n3 in numbersQuery.Where(x => n.Index == x.Index - 2).DefaultIfEmpty()
                where n.Value > 10
                where n2 != null && n2.Value > 10
                where n3 != null && n3.Value > 10
                select new
                {
                  Value1 = n.Value,
                  Value2 = n2.Value,
                  Value3 = n3.Value
                };
    

    为了指定哪个组,可以调用Skip方法

    query.Skip(1)
    

    【讨论】:

    • 非常好!一个大的 LINQ 查询。
    【解决方案3】:

    你为什么不试试这个扩展方法?

    public static IEnumerable<IEnumerable<T>> Consecutives<T>(this IEnumerable<T> numbers, int ranges, Func<T, bool> predicate)
    {
        IEnumerable<T> ordered = numbers.OrderBy(a => a).Where(predicate);
        decimal n = Decimal.Divide(ordered.Count(), ranges);
        decimal max = Math.Ceiling(n); // or Math.Floor(n) if you want
        return from i in Enumerable.Range(0, (int)max)
               select ordered.Skip(i * ranges).Take(ranges);
    }
    

    唯一需要改进的可能是调用Count 方法,因为会导致numbers 的枚举(因此查询失去了惰性)。

    无论如何,我确信这符合您的linqness 要求。

    编辑:或者这是 less words 版本(它不使用 Count 方法):

    public static IEnumerable<IEnumerable<T>> Consecutives<T>(this IEnumerable<T> numbers, int ranges, Func<T, bool> predicate)
    {
        var ordered = numbers.OrderBy(a => a);
        return ordered.Where(predicate)
                      .Select((element, i) => ordered.Skip(i * ranges).Take(ranges))
                      .TakeWhile(Enumerable.Any);
    }
    

    【讨论】:

      【解决方案4】:

      我必须为双打列表执行此操作。有上限也有下限。这也不是真正的 Linq 解决方案,它只是我用脚本语言编写的一种实用方法,它只实现了 C# 的一个子集。

      var sequence =
       [0.25,0.5,0.5,0.5,0.7,0.8,0.7,0.9,0.5,0.5,0.8,0.8,0.5,0.5,0.65,0.65,0.65,0.65,0.65,0.65,0.65];
      double lowerLimit = 0.1;
      double upperLimit = 0.6;
      int minWindowLength = 3;
      
      // return type is a list of lists
      var windows = [[0.0]];
      windows.Clear();
      
      int consec = 0;
      int index = 0;
      
      while (index < sequence.Count){
      
              // store segments here
              var window = new System.Collections.Generic.List<double>();
      
              while ((index < sequence.Count) && (sequence[index] > upperLimit || sequence[index] < lowerLimit)) {        
                  window.Add(sequence[index]);
                  consec = consec + 1;
                  index = index +1;
              }
      
              if (consec > minWindowLength) {
                  windows.Add(window);
              }
      
              window = new System.Collections.Generic.List<double>();
              consec = 0;
      
              index = index+1;
      }
      
      return windows;
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2020-07-20
        • 2010-11-13
        • 2016-08-24
        • 1970-01-01
        • 2012-07-06
        • 1970-01-01
        • 2013-02-23
        • 1970-01-01
        相关资源
        最近更新 更多