5

The follow code will break the list into sub-lists which begins with "[" and end with "]". How to convert it to use yield return so it can handle very huge stream input lazily? --Or how to implement it in F# with lazily enumerating?-- (never mind, I think f#implementation should be trivial)

var list = new List<string> { "[", "1", "2", "3", "]", "[", "2", "2", "]", "[", "3", "]" };
IEnumerable<IEnumerable<string>> result = Split(list);

static IEnumerable<IEnumerable<string>> Split(List<string> list)
{
    return list.Aggregate(new List<List<string>>(), // yield return?
    (sum, current) =>
    {
        if (current == "[")
            sum.Add(new List<string>());
        else if (current == "]")
            return sum; // Convert to yield return?
        else
            sum.Last().Add(current);
        return sum; // Convert to yield return?
    });
}
Guy Coder
  • 24,501
  • 8
  • 71
  • 136
ca9163d9
  • 27,283
  • 64
  • 210
  • 413
  • Feels like aggregate is not quite the right thing to use here. Write your own version of it, then superimpose the logic on top. – leppie Feb 18 '16 at 05:45
  • 1
    @leppie The problem with the `Aggregate` is that he's not actually aggregating anything. The lambda is only ever used for its side effects, so it's noting more than a much harder to read `foreach` loop. – Servy Feb 18 '16 at 05:59

3 Answers3

12

C# doesn't support anonymous iterator blocks, so you'll simply need to use a named method instead of an anonymous method.

public static IEnumerable<IEnumerable<string>> Split(IEnumerable<string> tokens)
{
    using(var iterator = tokens.GetEnumerator())
        while(iterator.MoveNext())
            if(iterator.Current == "[")
                yield return SplitGroup(iterator);
}

public static IEnumerable<string> SplitGroup(
    IEnumerator<string> iterator)
{
    while(iterator.MoveNext() && iterator.Current != "]")
        yield return iterator.Current;
}
Servy
  • 202,030
  • 26
  • 332
  • 449
1

While C# not allow yield in lambda I think we could workaround like this

static IEnumerable<IEnumerable<string>> Split(List<string> list)
{
    var tmp = list as IEnumerable<string>;
    while(tmp.FirstOrDefault() == "[")
    {
        yield return tmp.TakeWhile((current) => current != "]");
        tmp = tmp.SkipWhile((current) => current != "]");
    }
}
Thaina Yu
  • 1,372
  • 2
  • 16
  • 27
-3

I found another way of doing this, using Linq. Not sure on efficience, but it works.

int index=0;
list.Select(item=> new { item=item, index= item=="]"? ++index : index })
                  .Where(c => !(c.item == "[" || c.item =="]"))
                  .GroupBy(g=>g.index)
                  .Select(e=> e.Select(c=>c.item));

Working Demo

Hari Prasad
  • 16,716
  • 4
  • 21
  • 35
  • That's not lazily generating the sequence. `Groupby` is going to need to process the *entire* sequence before it can yield any results, which the OP has said they don't want to do. – Servy Feb 18 '16 at 14:40