System.Reactive Puzzle followup


Steffen has already posted two beautiful solutions. As often happens with specs, I was not detailed enough about what I want the behavior of the signature I provided to be:


I meant multiClickSpeedInMilliSeconds to be the total time between the first and the Nth click. Steffen’s implementation assumes it is the time between each two clicks. With that assumption his solutions work great!

Steffen, one small gotcha to watch out for with the Zip solution: as you’re using the click event twice in that solution:


return click.Skip(count)
             .Zip(click.Select(_ =>
DateTime.Now), (e, t) => new {e, t = DateTime.Now – t})
             .Where(e => e.t.TotalMilliseconds < multiClickSpeedInMilliSeconds)
             .Select(e => e.e);


you’ll subscribe to click twice for each subscriber to your observable. Now in this case that is not really a problem, but if click was an observable that has side effects, you would see the side effect happen twice. Try using the Let operator for this scenario..


 I will post some of our team’s solutions to this problem tomorrow…


Comments (3)

  1. Steffen Zeidler says:

    Nice, solving the puzzle I learn more and more about Rx.

    The 2. simulation of clicks with Observable.Generate starts twice without Let, but now I know …

    2. solution with Zip and Let version 2

    return click

     //time stamp

     .Select(e => new { e, t = DateTime.Now })

     //time span between [n+count-1] and [n]

     .Let(o => o

       .Skip(count – 1)

       .Zip(o, (e1, e2) =>

         new { e1.e, dt = e1.t – e2.t }))

     //hit counter

     .Scan(new { e = new RoutedEventArgs(), c = 0 }, (a, e) =>

       new { e.e, c = (e.dt.TotalMilliseconds < multiClickSpeedInMilliSeconds) ? (a.c + 1) : 0 })

     .Where(a => a.c % count == 1)

     .Select(e => e.e);

    3. solution with ToEnumerable and back ToObservable

    IEnumerable<TSource> GetMultiClickEnum<TSource>(IObservable<TSource> o, int count, int multiClickSpeedInMilliSeconds)

    {  var timeStamp = new DateTime[count-1];

     int current = 0;

     int skip = count – 1;

     foreach (var item in o.ToEnumerable())

     {

       DateTime tOld = timeStamp[current];

       timeStamp[current] = DateTime.Now;

       current = (current + 1) % timeStamp.Length;

       if (skip > 0)

       {

         skip–;

       }

       else if ((DateTime.Now – tOld).TotalMilliseconds < multiClickSpeedInMilliSeconds)

       {

         skip = count – 1;

         yield return item;

       }

     }

    }

    return GetMultiClickEnum(click, count, multiClickSpeedInMilliSeconds).ToObservable();

  2. Steffen Zeidler says:

    My 4. solution with Observable.Create

    var timeStamp = new DateTime[count – 1];

    int current = 0;

    int skip = count – 1;

    return Observable.Create<int>(o =>

       click.Subscribe(e =>

       {

           DateTime tOld = timeStamp[current];

           timeStamp[current] = DateTime.Now;

           current = (current + 1) % timeStamp.Length;

           if (skip > 0)

           {

               skip–;

           }

           else if ((DateTime.Now – tOld).TotalMilliseconds < multiClickSpeedInMilliSeconds)

           {

               skip = count – 1;

               o.OnNext(e);

           }

       },

       o.OnError,

       o.OnCompleted)

    );

  3. Steffen Zeidler says:

    My questions about Reactive Framework function LetRec

    1. question: why rec1 never completed?

    var rec1 = Observable.LetRec<int>(r => Observable.Cons(42, r).Skip(1));

    2. question: why in rec2 function Do() runs twice?

    var rec2 = Observable.LetRec<int>(r => Observable.Return(42).Do(i => Debug.WriteLine(i)));

    My 5. solution with SelectMany and LetRec

    return click

       //in the case of time span it is better to use UtcNow instead of Now

       .Select(e => new { e, t = DateTime.UtcNow })

       .Let(click1 =>

       Observable.LetRec<TSource>(r =>

           Observable.Cons(default(TSource), r)

           .SelectMany(

               click1

               .Skip(count – 1)

               .Zip(click1, (e1, e2) => new { e1.e, dt = e1.t – e2.t })

               .Where(e => e.dt.TotalMilliseconds < multiClickSpeedInMilliSeconds)

               .Select(e => e.e)

               .Take(1)

           )

       )

    );

Skip to main content