1

I would like to buffer items in a sequence according to a condition. The problem is that this condition depends on the items that are processed.

Let me put an example:

Given this:

new[] { 1, 3, 5, 7, 2, 4, 6, 8, 1 };
  • If n is odd, stop buffering
  • If n is even, start buffering

This way, the result sequence should be:

{ 1 }
{ 3 }
{ 5 }
{ 7 }
{ 2, 4, 6, 8 }
{ 1 }

I've tried variations of this without success:

var boundaries = origin.Select(x => x % 2 != 0).DistinctUntilChanged();
var result = origin.Buffer(boundaries);
SuperJMN
  • 13,110
  • 16
  • 86
  • 185

1 Answers1

1

This might be close to what you want. Instead of the Buffer operator it uses the GroupByUntil operator, which I consider to be more reliable.

/// <summary>
/// Splits the elements of a sequence into chunks that are starting with
/// elements that satisfy the predicate.
/// </summary>
public static IObservable<IList<TSource>> BufferByPredicate<TSource>(
    this IObservable<TSource> source,
    Predicate<TSource> startNewBufferPredicate)
{
    ArgumentNullException.ThrowIfNull(source);
    ArgumentNullException.ThrowIfNull(startNewBufferPredicate);
    return source
        .SelectMany(x =>
        {
            var subSequence = Observable.Return((Value: x, HasValue: true));
            if (startNewBufferPredicate(x))
                // Add a fake "boundary" element before the real element.
                subSequence = subSequence.Prepend((default, false));
            return subSequence;
        })
        .GroupByUntil(_ => 0, g => g.SkipWhile(e => e.HasValue))
        .SelectMany(g => g.Where(e => e.HasValue).Select(e => e.Value).ToArray())
        .Where(w => w.Length > 0);
}

Usage example:

 IObservable<IList<int>> result = origin.BufferByPredicate(x => x % 2 != 0);
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • Unfortunately, the results I'm getting with this method aren't correct. Using the data from the OP, I'm getting these groups. `{ 1 }; { 3 }, { 4 }; { 5 }, { 2 }, { 4 }, { 6 }, { 8 }; { 1 }` Ouch. It seems the comments cannot be formatted using multiple lines. I'd had to use semicolons as "newline". – SuperJMN Nov 15 '22 at 10:44
  • @SuperJMN the `BufferByPredicate` is supposed to produce a group [7, 2, 4, 6, 8]. Unfortunately I can't confirm it right now because my PC has a problem. – Theodor Zoulias Nov 15 '22 at 11:45
  • That's strange. I hope you can try the code soon. Tested with `{ 1, 3, 5, 7, 2, 4, 6, 8, 1 }` and got the results above. – SuperJMN Nov 15 '22 at 23:54
  • @SuperJMN [here](https://dotnetfiddle.net/tNAkJc) is an online demo. It seems that it works according to my expectations. – Theodor Zoulias Nov 20 '22 at 08:32
  • OK, I've tested and it still produces the wrong results. The expected outcome is: { 1 } { 3 }; { 5 }; { 7 }; { 2, 4, 6, 8 }; { 1 }; – SuperJMN Nov 22 '22 at 22:51
  • 1
    @SuperJMN yea, when I posted this answer it was not clear in which group the value 7 belongs. Feel free to modify this answer so that it fits your needs! – Theodor Zoulias Nov 22 '22 at 23:10