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
- Anonymous
August 16, 2009
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 …
- 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);
- 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();
Anonymous
September 16, 2009
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) );Anonymous
September 20, 2009
My questions about Reactive Framework function LetRec
- question: why rec1 never completed? var rec1 = Observable.LetRec<int>(r => Observable.Cons(42, r).Skip(1));
- 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) ) ) );