LINQ lambda用于计数序列数

时间:2019-01-18 04:53:00

标签: c#

我有如下示例学生数据:

ExamDate           Test           Result
01/21/2017         Math           Pass 
06/02/2017         Science        Pass
05/31/2018         Math           Fail
06/28/2018         Science        Pass 
07/03/2018         Math           Pass 
07/19/2018         Science        Fail *
08/01/2018         Math           Fail 
09/13/2018         Science        Fail *
09/15/2018         Math           Fail 
10/01/2018         Science        Fail *
12/15/2019         Math           Pass 
10/11/2019         Science        Fail *
...

在以上排序的ExamDate中,有4个连续的科学失败测试,​​以*或(4-1)= 3连续出现科学连续失败。同样,有2个连续的数学失败测试或1个数学连续失败。

How can I group above data using LINQ lambda into a format like below:
    Science: 4 consecutive fail tests or (4-1) = 3 sequential fails
    Math: 2 consecutive fail tests or (2-1) = 1 sequential fails

是否需要帮助LINQ语法根据考试日期对每个测试(数学,科学)的连续连续失败次数进行计数?

2 个答案:

答案 0 :(得分:0)

我的10美分:

假设这是您的课程:

        public class Student
        {
            public DateTime ExamDate { get; set; }
            public string Test { get; set; }

            public bool Result { get; set; }

            public bool IsStarred { get; set; }
        }

这是您的数据:

        List<Student> students = new List<Student>
        {
            new Student { ExamDate = new DateTime(2017, 01, 21), Test = "Math", Result = true, IsStarred = false },
            new Student { ExamDate = new DateTime(2017, 06, 02), Test = "Science", Result = true, IsStarred = false },
            new Student { ExamDate = new DateTime(2018, 05, 31), Test = "Math", Result = false, IsStarred = false },
            new Student { ExamDate = new DateTime(2018, 06, 28), Test = "Science", Result = true, IsStarred = false },
            new Student { ExamDate = new DateTime(2018, 07, 03), Test = "Math", Result = true, IsStarred = false },
            new Student { ExamDate = new DateTime(2018, 07, 19), Test = "Science", Result = false, IsStarred = true },
            new Student { ExamDate = new DateTime(2018, 08, 01), Test = "Math", Result = true, IsStarred = false },
            new Student { ExamDate = new DateTime(2018, 09, 13), Test = "Science", Result = false, IsStarred = true },
            new Student { ExamDate = new DateTime(2018, 09, 15), Test = "Math", Result = false, IsStarred = false },
            new Student { ExamDate = new DateTime(2018, 10, 01), Test = "Science", Result = false, IsStarred = true },
            new Student { ExamDate = new DateTime(2019, 12, 15), Test = "Math", Result = true, IsStarred = false },
            new Student { ExamDate = new DateTime(2019, 11, 10), Test = "Science", Result = false, IsStarred = true }
        };

然后您可以执行以下操作:

  var query = from student in students
            orderby student.ExamDate
            group student.Result by student.Test into g
            select new
            {
                Group = g.Key,
                Elements = g.OrderByDescending(p2 => p2)
            };

答案 1 :(得分:0)

首先(我不想在这些问题上作详细介绍),请确保在问一个问题时至少要放置结构和测试数据,以便我们可以轻松地运行和测试解决方案。花了五分钟才开始玩,甚至没有编写代码。

第二,这个问题有一个小时了,这里有几个人想提供帮助,但是您没有澄清问题中需要的简单内容。

所有这些。

这也许是您想要的,假设它是连续的组

var query = list.GroupBy(x => x.Test)
                .Select(x => new
                    {
                       x.Key,
                       Results = x.ChunkBy(p => p.Result)
                                  .Select(y => new { y.Key, Count = y.Count() })
                                  .Where(z => z.Count > 1)
                    });
foreach (var item in query)
{
   Console.WriteLine($"Group key = {item.Key}");
   foreach (var inner in item.Results.Where(x => x.Key =="Fail"))
   {
      Console.WriteLine($" - {inner.Count} consecutive fail tests or ({inner.Count}-1) = {inner.Count-1} sequential fails ");
   }
}

Full Demo Here

示例输出

注意:这是一个更复杂的数据集

Group key = Math
 - 2 consecutive fail tests or (2-1) = 1  sequential fails 
 - 2 consecutive fail tests or (2-1) = 1  sequential fails 
 - 2 consecutive fail tests or (2-1) = 1  sequential fails 
Group key = Science
 - 4 consecutive fail tests or (4-1) = 3  sequential fails 
 - 8 consecutive fail tests or (8-1) = 7  sequential fails 
 - 2 consecutive fail tests or (2-1) = 1  sequential fails

要使其正常工作(它需要在内存中运行),我在这里Group results by contiguous keys借用了Microsoft的线程安全方法。这有点矫over过正,并且有更简单的方法可以实现这一点,但这是相当标志性的一段代码

扩展名

public static class MyExtensions
{
   public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector)
      => source.ChunkBy(keySelector, EqualityComparer<TKey>.Default);


   public static IEnumerable<IGrouping<TKey, TSource>> ChunkBy<TSource, TKey>(this IEnumerable<TSource> source, Func<TSource, TKey> keySelector, IEqualityComparer<TKey> comparer)
   {
      const bool noMoreSourceElements = true;
      var enumerator = source.GetEnumerator();

      if (!enumerator.MoveNext())
         yield break;

      while (true)
      {
         var key = keySelector(enumerator.Current);    
         var current = new Chunk<TKey, TSource>(key, enumerator, value => comparer.Equals(key, keySelector(value)));

         yield return current;

         if (current.CopyAllChunkElements() == noMoreSourceElements)
            yield break;
      }
   }
}

帮助程序类

public class Chunk<TKey, TSource> : IGrouping<TKey, TSource>
{

   private readonly ChunkItem _head;    
   private readonly object _mLock = new object(); 
   private IEnumerator<TSource> _enumerator;    
   private bool _isLastSourceElement;    
   private Func<TSource, bool> _predicate;    
   private ChunkItem _tail;

   public Chunk(TKey key, IEnumerator<TSource> enumerator, Func<TSource, bool> predicate)
   {
      Key = key;
      _enumerator = enumerator;
      _predicate = predicate;
      _head = new ChunkItem(enumerator.Current);
      _tail = _head;
   }

   private bool DoneCopyingChunk => _tail == null;

   public TKey Key { get; }

   public IEnumerator<TSource> GetEnumerator()
   {  
      var current = _head;

      while (current != null)
      {
         yield return current.Value;

         lock (_mLock)
            if (current == _tail)
               CopyNextChunkElement();

         current = current.Next;
      }
   }

   IEnumerator IEnumerable.GetEnumerator()
      => GetEnumerator();


   private void CopyNextChunkElement()
   {

      _isLastSourceElement = !_enumerator.MoveNext();

      if (_isLastSourceElement || !_predicate(_enumerator.Current))
      {
         _enumerator = null;
         _predicate = null;
      }
      else
         _tail.Next = new ChunkItem(_enumerator.Current);

      _tail = _tail.Next;
   }

   internal bool CopyAllChunkElements()
   {
      while (true)
         lock (_mLock)
         {
            if (DoneCopyingChunk)
               return _isLastSourceElement;

            CopyNextChunkElement();
         }
   }
   private class ChunkItem
   {
      public readonly TSource Value;
      public ChunkItem Next;

      public ChunkItem(TSource value)
      {
         Value = value;
      }
   }
}