Here is a polished version of Shlomo's idea, of maintaining the latest values per key using the Scan
operator and an ImmutableDictionary
as state. The custom operator below (SampleByKey
) samples a sequence of key-bearing elements at a specific interval. Upon each sampling tick an IDictionary<TKey, TSource>
is emitted, containing the latest values that have been emitted so far by each key.
public static IObservable<IDictionary<TKey, TSource>> SampleByKey<TSource, TKey>(
this IObservable<TSource> source,
Func<TSource, TKey> keySelector,
TimeSpan interval,
IEqualityComparer<TKey> keyComparer = default)
{
return source
.Scan(ImmutableDictionary.Create<TKey, TSource>(keyComparer),
(dict, x) => dict.SetItem(keySelector(x), x))
.Publish(published => Observable
.Interval(interval)
.WithLatestFrom(published, (_, dict) => dict)
.TakeUntil(published.LastOrDefaultAsync()));
}
Usage example:
IObservable<IDictionary<string, Price>> sampled = priceFeed
.SampleByKey(p => p.Key, TimeSpan.FromSeconds(1.0));
In case that zero elements have been emitted by the source
during two samplings, the same dictionary will be emitted consecutively.
This implementation is very similar with one I've posted previously in a question, about how to sample a sequence at a dynamically changeable time interval.
Note: I scraped my previous implementation (Revision 1) as too complex and potentially leaky. Shlomo's approach is easier to understand and modify as needed.