0

I am new with linq, I need to split an IEnumerable of a type Couple(string text, bool indicator) to multiple IEnumerables based on the indicator, I tried with skipWhile and TakeWhile but I didn't find a solution, the input is as follows:

Couple("a",false)
Couple("b",false)
Couple("c",true),
Couple("d",false)
Couple("e",false)
Couple("f",true),
Couple("g",true),
Couple("h",true),
Couple("i",false)
Couple("j",true),
Couple("k",true),
Couple("l",false)
Couple("m",false)

The result should be 7 IEnumerables

list1: Couple("a",false)
       Couple("b",false)
list2: Couple("c",true)
list3: Couple("d",false)
       Couple("e",false)
list4: Couple("f",true)
       Couple("g",true)
       Couple("h",true)
list5: Couple("i",false)
list6: Couple("j",true)
       Couple("k",true)
list7: Couple("l",false) 
       Couple("m",false)

Any help please?

Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
SAliaMunch
  • 67
  • 9

3 Answers3

2

Try this:

public static IEnumerable<IList<Couple>> Split(IEnumerable<Couple> couples)
{
    using (var enumerator = couples.GetEnumerator())
    {
        if (!enumerator.MoveNext())
        {
            yield break;
        }
        var current = enumerator.Current;
        var group = new List<Couple> { current };
        while (enumerator.MoveNext())
        {
            var next = enumerator.Current;
            if (current.Indicator.Equals(next.Indicator))
            {
                group.Add(next);
            }
            else
            {
                yield return group;
                group = new List<Couple> { next };
            }
            current = next;
        }
        yield return group;
    }
}

Example:

var couples = new List<Couple> 
{
    new Couple("a",false),
    new Couple("b",false),
    new Couple("c",true),
    new Couple("d",false),
    new Couple("e",false),
    new Couple("f",true),
    new Couple("g",true),
    new Couple("h",true),
    new Couple("i",false),
    new Couple("j",true),
    new Couple("k",true),
    new Couple("l",false),
    new Couple("m",false),
};

var groupNr = 1;
foreach (var couplesGroup in Split(couples))
{
    Console.WriteLine($"List {groupNr++}: ");
    foreach (var couple in couplesGroup)
    {
        Console.WriteLine($"{couple.Text, 10}, {couple.Indicator}");
    }
    Console.WriteLine();
}
Vitali
  • 645
  • 9
  • 14
2

Here is a generic extension method for splitting sequences. It requires a function that examines two consecutive elements, and determines if these elements should be split or not. A result of true means split the elements. A result of false means don't split the elements, and keep them together in the same sub-sequence.

public static class EnumerableExtensions
{
    /// <summary>Splits a sequence to subsequences according to a specified
    /// predicate.</summary>
    /// <param name="splitPredicate">A function to determine if two consecutive
    /// elements should be split.</param>
    public static IEnumerable<TSource[]> SplitByPredicate<TSource>(
        this IEnumerable<TSource> source,
        Func<TSource, TSource, bool> splitPredicate)
    {
        var enumerator = source.GetEnumerator();
        bool finished = false;
        TSource previous = default;
        using (enumerator)
        {
            if (!enumerator.MoveNext()) yield break;
            while (!finished)
            {
                yield return GetSubsequence().ToArray();
            }
        }

        IEnumerable<TSource> GetSubsequence()
        {
            while (true)
            {
                yield return enumerator.Current;
                previous = enumerator.Current;
                if (!enumerator.MoveNext()) { finished = true; break; }
                if (splitPredicate(previous, enumerator.Current)) break;
            }
        }
    }
}

Usage example:

var subsequences = couples.SplitByPredicate(
    (x, y) => x.Indicator != y.Indicator);
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Updated with a more concise implementation. – Theodor Zoulias Sep 29 '19 at 09:12
  • Note that `ToList` is generally preferable to `ToArary`. – NetMage Sep 17 '21 at 19:15
  • @NetMage yeah, I've read [this thread](https://stackoverflow.com/questions/1105990/is-it-better-to-call-tolist-or-toarray-in-linq-queries) a long time ago, with the accepted answer favoring `ToList`. TBH I am not convinced. The `ToArray` allocates more memory for its creation, but its memory footprint is smaller than a `List`. And it is also faster to enumerate. For a generic method like the `SplitByPredicate`, where God knows how it will be used, choosing between the two options is a tough call. I am going with the `ToArray`, mostly because it looks cleaner to my eyes. – Theodor Zoulias Sep 17 '21 at 19:53
  • I would say the memory footprint is larger (at least until GC) since `ToArray` must allocate everything `ToList` does and then one more. Also, to me `ToList` is cleaner since `List` is much more flexible and arrays should only be used when explicitly needed (e.g. only for APIs requiring an array). Six of one... – NetMage Sep 21 '21 at 19:28
  • @NetMage by memory footprint I mean the memory that is allocated during the object's lifetime. Usually the objects returned by LINQ queries are thrown away after they are read once, but there is nothing preventing a programmer from caching them in a static field during the start up, and using them until the application exits. In that case returning `List`s instead of arrays will increase the memory footprint of the cache by 50% on average. Of course in this case you can say that the developer can project the lists to arrays before caching them, which is a valid argument. – Theodor Zoulias Sep 21 '21 at 19:43
0

Here is a solution without Linq:

class Program
{

  public class Couple
  {
    public string Text;
    public bool Indicator;
    public Couple(string text, bool indicator)
    {
      Text = text;
      Indicator = indicator;
    }
  }

  static void Main(string[] args)
  {
    var list = new List<Couple>();

    list.Add(new Couple("a", false));
    list.Add(new Couple("b", false));
    list.Add(new Couple("c", true));
    list.Add(new Couple("d", false));
    list.Add(new Couple("e", false));
    list.Add(new Couple("f", true));
    list.Add(new Couple("g", true));
    list.Add(new Couple("h", true));
    list.Add(new Couple("i", false));
    list.Add(new Couple("j", true));
    list.Add(new Couple("k", true));
    list.Add(new Couple("l", false));
    list.Add(new Couple("m", false));

    var result = new List<List<Couple>>();

    int index = 0;
    bool last = list[0].Indicator;
    result.Add(new List<Couple>());

    foreach ( var item in list )
    {
      if ( item.Indicator != last )
      {
        index++;
        result.Add(new List<Couple>());
      }
      last = item.Indicator;
      result[index].Add(item);
    }

    for ( index = 0; index < result.Count; index++ )
    {
      Console.WriteLine($"List n°{index}");
      foreach ( var item in result[index] )
        Console.WriteLine($"  text: {item.Text}");
    }

    Console.WriteLine("");
    Console.ReadKey();
  }