2

I'm an RX newbie trying to construct something that seems complex to me.

Here's the problem: I have a hot observable that is producing key-value pairs, let's say <int, foo>. They're coming in no particular order. The output should be an observable for each distinct key in the source observable. So, for each value in the source, the result should be: a new observable if the key has not been seen yet, with the new observable immediately producing the corresponding value. If the key has been seen before, the corresponding value should be produced by the corresponding observable that already exists. So:

<1, foo>-<2, foo>-<2, foo>-<1, foo>-<3, foo>-<4, foo>-<1, foo>-<3, foo>x

Output:
<1, foo>-------------------<1, foo>-------------------<1, foo>---------x
---------<2, foo>-<2,foo>----------------------------------------------x
------------------------------------<3, foo>-------------------<3, foo>x
---------------------------------------------<4, foo>------------------x

I'm attempting this with window, but am stuck on how to "detect" that a key has already been seen and then "route" the value to the existing observable.

Thanks!

tempy
  • 1,567
  • 2
  • 18
  • 28

1 Answers1

4

This is exactly what GroupBy does. e.g.:

var subject = new Subject<KeyValuePair<int, string>>();

var groups = subject.GroupBy(x => x.Key);

GroupBy takes a keySelector function that selects the key from an element. The output is a stream of streams, where each stream emitted has type IGroupedObservable<int, KeyValuePair<int, string>>. This is a subclass of IObservable and gives you a Key property so you can also identify the selected key.

So if the following code is added to the above example, where we just print out the key of each new stream:

groups.Subscribe(x => Console.WriteLine(x.Key));

subject.OnNext(new KeyValuePair<int, string>(1, "a"));
subject.OnNext(new KeyValuePair<int, string>(2, "b"));
subject.OnNext(new KeyValuePair<int, string>(1, "c"));

We get:

1
2

Which are the keys of the two streams produced. Two prove we have all the values, if instead we use SelectMany to flatten the result of the GroupBy, and pluck out just the values:

groups.SelectMany(x => x).Subscribe(y => Console.WriteLine(y.Value));

subject.OnNext(new KeyValuePair<int, string>(1, "a"));
subject.OnNext(new KeyValuePair<int, string>(2, "b"));
subject.OnNext(new KeyValuePair<int, string>(1, "c"));

We get the output:

a
b
c

as expected.

James World
  • 29,019
  • 9
  • 86
  • 120
  • 2
    Note that `GroupBy` groups last until the source stream terminates - so if you have very long running streams you may have need of `GroupByUntil` which can limit the lifetime of each group based on a duration selector. If group has been terminated by the duration selector, a new value bearing that key will cause a new group to form. – James World Nov 08 '14 at 22:27
  • Yup this is exactly what I need. I hope I haven't badly and slowly reimplemented too many other existing operators =/ Thanks for the understandable explanation! – tempy Nov 09 '14 at 10:48