简体   繁体   中英

LINQ lambda for counting number of sequences

I have the sample student data as follows:

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 *
...

In the above sorting ExamDate, there is 4 consecutive Science fail tests mark by * or (4-1) = 3 Science sequential fails in a row. Similarly, there is 2 consecutive Math fail tests or 1 Math sequential fail.

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

Need help LINQ syntax to count how many sequential consecutive fails each test (Math, Science) based on sorting exam date?

My 10 cents:

Suppose this is your class:

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

            public bool Result { get; set; }

            public bool IsStarred { get; set; }
        }

and this is your data:

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

Then you can do this:

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

Firstly (and i don't want to dwell on these points), please make sure when you ask a question that you at least put the structures and the test data so we can easily run and test a solution. it took 5 minutes just playing around with it before i could even write code.

Secondly, This question is an hour old, there are several people here who wanted to help however you gave no clarification about simple things that were needed in the question..

All that aside.

This maybe what you are looking for, the assumption is it counts consecutive groups

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

Example Output

Note : this is with a more complicated data set

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

For this to work (and it needs to run in memory), i have borrowed a thread safe method from Microsoft here Group results by contiguous keys . Its a little overkill and there are easier ways to achieve this, however this is a fairly iconic piece of code

Extensions

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

Helper Class

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM