3

I'm looking for an observable selector with a signature akin to this:

static IObservable<T> TakeLatest(this IObservable<T> input, TimeSpan interval)

Which should:

  1. Emit the first item as soon as input emits its first item
  2. From then on, in fixed time intervals afterwards, emit the most recent item produced by input
  3. Complete (or fail) whenever input completes (or fails)

In terms of marbles, something like the following - assuming interval = 2 time units:

Time 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Input A B C D E F (complete)
Output A B D D E E complete (F not emitted anymore)

Is there any out-of-the-box way of doing so, or a reasonably easy selector to produce these results?

Bogey
  • 4,926
  • 4
  • 32
  • 57
  • I think you want [debounce](http://reactivex.io/documentation/operators/debounce.html) – Liam Mar 19 '21 at 15:18
  • @Liam I don't think that works/is enough (correct me if wrong). E.g. if source produces a value, I think debounce would emit this item at most once; in my scenario, it may be needed to emit one and the same item multiple times (if no other more recent items are available) – Bogey Mar 19 '21 at 15:22
  • I think you'd want to combine it with [repeat](http://reactivex.io/documentation/operators/repeat.html) then. Problem is I only know rxjs and not rx.Net so I'm not 100% sure how the operators map. Hence only a comment – Liam Mar 19 '21 at 15:35
  • You are probably searching for the [`Sample`](http://introtorx.com/Content/v1.0.10621.0/13_TimeShiftedSequences.html#Sample) operator. In case you also want to be able to change dynamically the sampling interval, take a look at [this](https://stackoverflow.com/questions/48648135/generate-events-with-dynamically-changeable-time-interval "Generate events with dynamically changeable time interval") question. – Theodor Zoulias Mar 19 '21 at 17:42

2 Answers2

3

This should probably do exactly what you want. I haven't tested it though.

/// <summary>Samples the source observable sequence at each interval,
/// allowing repeated emissions of the same element.</summary>
public static IObservable<T> SampleWithDuplicates<T>(this IObservable<T> source,
    TimeSpan interval, IScheduler scheduler = null)
{
    scheduler ??= DefaultScheduler.Instance;
    return source.Publish(published => Observable
        .Interval(interval, scheduler)
        .WithLatestFrom(published, (_, x) => x)
        .Merge(published.FirstAsync())
        .TakeUntil(published.LastOrDefaultAsync()));
}
Theodor Zoulias
  • 34,835
  • 7
  • 69
  • 104
  • 1
    Works very well indeed! (It wouldn't emit the very first item, but that's easy to add of course). Thank you – Bogey Mar 19 '21 at 19:23
  • Could you post right answer as none of suggested solutions doesn't work as expected by OP? – Dmitry Stepanov Mar 22 '21 at 16:44
  • @DmitryStepanov OK, done. It's still untested though. – Theodor Zoulias Mar 22 '21 at 16:55
  • @TheodorZoulias, I tested it and it outputs the following: A A C D E E – Dmitry Stepanov Mar 23 '21 at 07:17
  • 1
    @DmitryStepanov apparently the OP's marble diagram has race conditions, since the interval ticks at about the same time that an item is emitted by the source sequence. So it's not guaranteed that the output will always be A B D D E E. – Theodor Zoulias Mar 23 '21 at 07:26
  • 1
    @TheodorZoulias - A very nice query! – Enigmativity Mar 28 '21 at 04:44
  • @Enigmativity there is not much creativity or originality in this query. It's just a simplified version of a `SampleByKey` operator I've posted in [this](https://stackoverflow.com/questions/45823090/is-it-possible-to-sample-the-latest-item-in-a-hot-observable-grouped-by-key/65402258#65402258 "Is it possible to sample the latest item in a hot observable grouped by key?") question. – Theodor Zoulias Mar 29 '21 at 02:11
0

I've done the following now - I think it works, but I'll heave this open in case anyone can think of a more elegant way (or can think of an issue with my current implementation)

static IObservable<T> TakeLatest(this IObservable<T> input, TimeSpan interval, IScheduler scheduler) => input
  .FirstAsync()
  .Select(_ => Observable.Interval(interval, scheduler).StartWith(0))
  .Switch()
  .CombineLatest(input, (a,b) => (a,b))
  .DistinctUntilChanged(x => x.a)
  .Select(x => x.b)
  .TakeUntil(input.LastAsync());
Bogey
  • 4,926
  • 4
  • 32
  • 57
  • 2
    One issue is that the `input` sequence is subscribed thrice, so it's not gonna work correctly with cold sequences. You probably need to `Publish` the `input` sequence. – Theodor Zoulias Mar 19 '21 at 17:49