Using Reactive to sum the certain sections of Observable - reactive-programming

I am trying to use reactive operators to find individual sum of the values emitted by the observable. The end goal is to emit individual sums. The sequence looks something like this. The ones I want to add up are occuring as continuous groups (of varying length) with varying frequency in between the values I want to discard. The ones I want to add have a field which is of type bool and has value true.
-(F,2)-(T,4)-(T,2)-(T,7)-(F,8)-(F,9)-(F,1)-(T,2)-(T,1)-(F,1)-
What have I tried so far:
myObservable.
.Where(x => x.IsItUseful == true)
.Aggregate(0.0, (sum,currentItem) => sum + currentItem.Value)
.Subscribe("NotYet")
This one give back the sum of ALL elements which have been marked as true.
myObservable
.SkipWhile(x => x.IsItUseful == false)
.TakeWhile(x => x.IsItUseful == true)
.Aggregate(0.0, (sum, currentItem) => sum + currentItem.Item3)
.Subscribe("NotYetAgain");
This one gives the sum of the first group only.
Right now I am trying along these lines.
myObservable
.Buffer(myObservable.DistinctUntilChanged(x => x.IsItUseful => true)
.Subscribe("NotSure")
I am still hazy on on BufferBoundary and BufferClosingSelector. I think a new buffer will open once I process a group of valid values. And this new buffer will have values from that point on wards till the end of another valid group. This means that I will pick up some not valid values too before the second group. I haven't been able to find some examples on Buffer with both open and close options getting used. Not sure if this is right approach too.
The final option is that I write an extension method on Buffer and put my custom logic there. But if there is an out of box solution I will prefer that.

There's two primary approaches I would recommend here. One uses Scan, the other uses Buffer/Window. Both of them have edge case problems that are solvable, but need clarity on the problem side.
Here's the Scan solution:
var result = source
.Scan((0, true), (state, value) => (value.IsItUseful ? state.Item1 + value.Value : 0, value.IsItUseful))
.Publish(_tuples =>
_tuples.Zip(_tuples.Skip(1), (oldTuple, newTuple) => (oldTuple, newTuple))
)
.Where(t => t.oldTuple.Item2 == true && t.newTuple.Item2 == false)
.Select(t => t.oldTuple.Item1);
Scan is similar to Aggregate, just more useful: Aggregate will only dump out one value at the end; whereas Scan emits intermediate values. So we track the running sum in there, resetting to 0 when we see a false. The next step (Zip) combines the latest message with its predecessor, so we can figure out whether or not we have to emit: We want to emit if the new flag value is false, but the old flag value is true. We then emit the old sum.
There's an edge case problem here if the last flag value is true: I'm assuming you want to emit on the OnCompleted, but that won't currently happen. Please clarify if that's needed.
Here's the Window solution:
var result2 = source
.Publish(_values => _values
.Window(_values.Select(v => v.IsItUseful).DistinctUntilChanged().Where(b => b == false))
)
.SelectMany(o => o.Where(a => a.IsItUseful).Sum(a => a.Value));
Window by the distinctly new falses, then sum them, similar to what you proposed.
The edge case problem here is that you end up with a leading and tailing 0 if you begin/end with falses (as your sample set does). Removing those would require some clean up as well.
FYI: Window and Buffer are practically the same: They have the same overloads and each group values into "windows". Window returns them as an observable stream, and Buffer holds them into a list which returns when the window closes. For more look here.
Here's runner code if anybody else wants to test this:
public class Message
{
public Message(bool b, int v)
{
IsItUseful = b;
Value = v;
}
public bool IsItUseful { get; set; }
public int Value { get; set; }
}
var values = new List<Message>
{
new Message(false, 2),
new Message(true, 4),
new Message(true, 2),
new Message(true, 7),
new Message(false, 8),
new Message(false, 9),
new Message(false, 1),
new Message(true, 2),
new Message(true, 1),
new Message(false, 1),
};
var source = values.ToObservable();
var result = source
.Scan((0, true), (state, value) => (value.IsItUseful ? state.Item1 + value.Value : 0, value.IsItUseful))
.Publish(_tuples =>
_tuples.Zip(_tuples.Skip(1), (oldTuple, newTuple) => (oldTuple, newTuple))
)
.Where(t => t.oldTuple.Item2 == true && t.newTuple.Item2 == false)
.Select(t => t.oldTuple.Item1);
var result2 = source
.Publish(_values => _values
.Buffer(_values.Select(v => v.IsItUseful).DistinctUntilChanged().Where(b => b == false))
)
.Select(o => o.Where(a => a.IsItUseful).Sum(a => a.Value));
result.Dump(); //Linqpad
result2.Dump(); //Linqpad

Related

RxJs: request list from server, consume values, re-request when we're almost out of values

I'm fetching a list of items from a REST api. The user interacts with each one via a click, and when there are only, say, a couple left unused, I'd like to repeat the request to get more items. I'm trying to do this using a proper RxJs (5) stream-oriented approach.
So, something like:
var userClick$ = Observable.fromEvent(button.nativeElement, 'click');
var needToExtend$ = new BehaviorSubject(1);
var list$ = needToExtend$
.flatMap( () => this.http.get("http://myserver/get-list") )
.flatMap( x => x['list'] );
var itemsUsed$ = userClick$.zip(list$, (click, item) => item);
itemsUsed$.subscribe( item => use(item) );
and then, to trigger a re-load when necessary:
list$.subscribe(
if (list$.isEmpty()) {
needToExtend$.next(1);
}
)
This last bit is wrong, and manually re-triggering doesn't seem very "stream-oriented" even if it did work as intended. Any ideas?
This is similar to Rxjs - Consume API output and re-query when cache is empty but I can't make assumptions about the length of the list returned by the API, and I'd like to re-request before the list is completely consumed. And the solution there feels a bit too clever. There must be a more readable way, right?
How about something like this:
const LIST_LIMIT = 3;
userClick$ = Observable.fromEvent(button.nativeElement, 'click');
list$ = this.http.get("http://myserver/get-list").map(r => r.list);
clickCounter$ = this.userClick$.scan((acc: number, val) => acc + 1, 0);
getList$ = new BehaviorSubject([]);
this.getList$
.switchMap(previousList => this.list$)
.switchMap(list => this.clickCounter$, (list, clickCount) => { return {list, clickCount}; })
.filter(({list, clickCount}) => clickCount >= list.length - LIST_LIMIT)
.map(({list, clickCount}) => list)
.subscribe(this.getList$);
The logic here if you define a list getter stream, and a signal to trigger it.
First, the signal causes switchMap to fetch a new list, which is then fed into another switchmap that resubscribes to a click counter. You combine the result of both streams and feed that to filter, which only emits when the click count is greater than or equal to the list length minus 3 (or whatever you want). Then the signal is subscribed to this whole stream so that it retriggers itself.
Edit: the biggest weakness of this is that you need to set the list value (for display) in a side effect rather than in subscription or with the async pipe. You can rearrange it and multicast though:
const LIST_LIMIT = 3;
userClick$ = Observable.fromEvent(button.nativeElement, 'click');
list$ = this.http.get("http://myserver/get-list").map(r => r.list);
clickCounter$: Observable<number> = this.userClick$.scan((acc: number, val) => acc + 1, 0).startWith(0);
getList$ = new BehaviorSubject([]);
refresh$ = this.getList$
.switchMap(list => this.clickCounter$
.filter(clickCount => list.length <= clickCount + LIST_LIMIT)
.first(),
(list, clickCount) => list)
.switchMap(previousList => this.list$)
.multicast(() => this.getList$);
this.refresh$.connect();
this.refresh$.subscribe(e => console.log(e));
This way has a few advantages, but may be a little less "readable". The pieces are mostly the same, but instead you go to the counter first and let that lead into the switch to the list fetch. and you multicast it to restart the counter.
I'm not clear on how you are tracking getting the next set of items so I will assume it is some form of paging for my answer. I also assume that you don't know the total number of items.
console.clear();
const pageSize = 5;
const pageBuffer = 2;
const data = [...Array(17).keys()]
function getData(page) {
const begin = pageSize * page
const end = begin + pageSize;
return Rx.Observable.of(data.slice(begin, end));
}
const clicks = Rx.Observable.interval(400);
clicks
.scan(count => ++count, 0)
.do(() => console.log('click'))
.map(count => {
const page = Math.floor(count / pageSize) + 1;
const total = page * pageSize;
return { total, page, count }
})
.filter(x => x.total - pageBuffer === x.count)
.startWith({ page: 0 })
.switchMap(x => getData(x.page))
.takeWhile(x => x.length > 0)
.subscribe(
x => { console.log('next: ', x); },
x => { console.log('error: ', x); },
() => { console.log('completed'); }
);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/5.5.3/Rx.min.js"></script>
Here is an explaination:
Rx.Observable.interval(#): simulates the client click events
.scan(...): accumulates the click events
.map(...): calculates the page index and potential total item count (actual count could be less but it doesn't matter for our purposes
.filter(...): only allow to pass through to get a new page of data if it has just hit the page buffer.
.startWith(...): get the first page without waiting for clicks. The +1 on the page calculation in the .scan accounts for this.
.switchMap(...): get the next page of data.
.takeWhile(...): keep the stream open till we get an empty list.
So it will get an initial page and then go get a new page whenever the number of clicks comes within the designated buffer. Once all items have been retrieved (known by empty list) it will complete.
One thing I didn't figure out how to do is to complete the list when the page length is less than the page size. Not sure if it matters to you.

Efficiently combine many IObservable<bool> streams with boolean operators

I'm looking to combine many IObservable<bool> streams such that when the latest value for all of them is true, a true is emitted, and otherwise a false is emitted.
CombinedLast would allow me to build something like this for two streams easily, but a) I'm not sure the API easily allows thousands of streams to be combined and b) I'm not sure how efficient it would be even if it could.
All is kinda similar to what I want except I'm assuming that works over a single sequence and once false cannot dynamically changes back to true.
Also I need the values to be "distinct until changed", although the DistintUntilChanged operator may not be efficient for this?
I'm hoping for an O(1) algorithm.
A good approach for combining the latest is to start with a IObservable<IObservable<T>> and turn it in to a IObservable<T[]>. This becomes a very dynamic way to combine as many values you need.
Here's an extension method to do this:
public static IObservable<T[]> CombineLatest<T>(this IObservable<IObservable<T>> sources)
{
return
sources.Publish(ss =>
Observable.Create<T[]>(o =>
{
var composite = new CompositeDisposable();
var list = new List<T>();
composite.Add(
ss.Subscribe(source =>
{
var index = list.Count;
list.Add(default(T));
composite.Add(source.Subscribe(x => list[index] = x));
}));
composite.Add(ss.Merge().Select(x => list.ToArray()).Subscribe(o));
return composite;
}));
}
This nicely creates and tracks all subscriptions and uses a closure to define the index that each subscription needs to use to update its value in the list that is used for output.
If you use it like this:
var sources = new Subject<IObservable<bool>>();
var output = sources.CombineLatest();
output.Subscribe(x => Console.WriteLine(x));
var s1 = new Subject<bool>();
sources.OnNext(s1);
s1.OnNext(true);
var s2 = new Subject<bool>();
sources.OnNext(s2);
s2.OnNext(false);
var s3 = new Subject<bool>();
sources.OnNext(s3);
s3.OnNext(true);
s2.OnNext(true);
s1.OnNext(false);
Then you get this output:
If you change the definition of output to var output = sources.CombineLatest().Select(xs => xs.Aggregate((x, y) => x & y)); then you get the output that I think you're after:
True
False
False
True
False
I don't know how to do this in a classically functional way and still achieve O(1). This used mutable state, and is O(1) for observing each message, but O(n) for memory:
public IObservable<bool> CombineBooleans(this IObservable<bool>[] source)
{
return source.Select((o, i) => o.Select(b => (value: b, index: i)))
.Merge()
.Scan((array: new bool[source.Length], countFalse: source.Length), (state, item) =>
{
var countFalse = state.countFalse;
if (state.array[item.index] == item.value)
return (state.array, countFalse); //nothing to change, emit same state
else if (state.array[item.index]) //previous/current state is true, becoming false
{
countFalse++;
state.array[item.index] = false;
}
else //previous/current state is false, becoming true
{
countFalse--;
state.array[item.index] = true;
}
return (state.array, countFalse);
})
.Scan((countFalse: source.Length, oldCountFalse: source.Length), (state, item) => (countFalse: item.countFalse, oldCountFalse: state.countFalse))
.SelectMany(state =>
state.countFalse == 1 && state.oldCountFalse == 0
? Observable.Return(false)
: state.countFalse == 0 && state.oldCountFalse == 1
? Observable.Return(true)
: Observable.Empty<bool>()
)
.Publish()
.RefCount();
}
EDIT: Added .Publish().Refcount() to eliminate multiple-subscriber bugs.

Confusion over behavior of Publish().Refcount()

I've got a simple program here that displays the number of letters in various words. It works as expected.
static void Main(string[] args) {
var word = new Subject<string>();
var wordPub = word.Publish().RefCount();
var length = word.Select(i => i.Length);
var report =
wordPub
.GroupJoin(length,
s => wordPub,
s => Observable.Empty<int>(),
(w, a) => new { Word = w, Lengths = a })
.SelectMany(i => i.Lengths.Select(j => new { Word = i.Word, Length = j }));
report.Subscribe(i => Console.WriteLine($"{i.Word} {i.Length}"));
word.OnNext("Apple");
word.OnNext("Banana");
word.OnNext("Cat");
word.OnNext("Donkey");
word.OnNext("Elephant");
word.OnNext("Zebra");
Console.ReadLine();
}
And the output is:
Apple 5
Banana 6
Cat 3
Donkey 6
Elephant 8
Zebra 5
I used the Publish().RefCount() because "wordpub" is included in "report" twice. Without it, when a word is emitted first one part of the report would get notified by a callback, and then the other part of report would be notified, double the notifications. That is kindof what happens; the output ends up having 11 items rather than 6. At least that is what I think is going on. I think of using Publish().RefCount() in this situation as simultaneously updating both parts of the report.
However if I change the length function to ALSO use the published source like this:
var length = wordPub.Select(i => i.Length);
Then the output is this:
Apple 5
Apple 6
Banana 6
Cat 3
Banana 3
Cat 6
Donkey 6
Elephant 8
Donkey 8
Elephant 5
Zebra 5
Why can't the length function also use the same published source?
This was a great challenge to solve!
So subtle the conditions that this happens.
Apologies in advance for the long explanation, but bear with me!
TL;DR
Subscriptions to the published source are processed in order, but before any other subscription directly to the unpublished source. i.e. you can jump the queue!
With GroupJoin subscription order is important to determine when windows open and close.
My first concern would be that you are publish refcounting a subject.
This should be a no-op.
Subject<T> has no subscription cost.
So when you remove the Publish().RefCount() :
var word = new Subject<string>();
var wordPub = word;//.Publish().RefCount();
var length = word.Select(i => i.Length);
then you get the same issue.
So then I look to the GroupJoin (because my intuition suggests that Publish().Refcount() is a red herring).
For me, eyeballing this alone was too hard to rationalise, so I lean on a simple debugging too I have used dozens of times of the years - a Trace or Log extension method.
public interface ILogger
{
void Log(string input);
}
public class DumpLogger : ILogger
{
public void Log(string input)
{
//LinqPad `Dump()` extension method.
// Could use Console.Write instead.
input.Dump();
}
}
public static class ObservableLoggingExtensions
{
private static int _index = 0;
public static IObservable<T> Log<T>(this IObservable<T> source, ILogger logger, string name)
{
return Observable.Create<T>(o =>
{
var index = Interlocked.Increment(ref _index);
var label = $"{index:0000}{name}";
logger.Log($"{label}.Subscribe()");
var disposed = Disposable.Create(() => logger.Log($"{label}.Dispose()"));
var subscription = source
.Do(
x => logger.Log($"{label}.OnNext({x.ToString()})"),
ex => logger.Log($"{label}.OnError({ex})"),
() => logger.Log($"{label}.OnCompleted()")
)
.Subscribe(o);
return new CompositeDisposable(subscription, disposed);
});
}
}
When I add the logging to your provided code it looks like this:
var logger = new DumpLogger();
var word = new Subject<string>();
var wordPub = word.Publish().RefCount();
var length = word.Select(i => i.Length);
var report =
wordPub.Log(logger, "lhs")
.GroupJoin(word.Select(i => i.Length).Log(logger, "rhs"),
s => wordPub.Log(logger, "lhsDuration"),
s => Observable.Empty<int>().Log(logger, "rhsDuration"),
(w, a) => new { Word = w, Lengths = a })
.SelectMany(i => i.Lengths.Select(j => new { Word = i.Word, Length = j }));
report.Subscribe(i => ($"{i.Word} {i.Length}").Dump("OnNext"));
word.OnNext("Apple");
word.OnNext("Banana");
word.OnNext("Cat");
word.OnNext("Donkey");
word.OnNext("Elephant");
word.OnNext("Zebra");
This will then output in my log something like the following
Log with Publish().RefCount() used
0001lhs.Subscribe()
0002rhs.Subscribe()
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe()
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe()
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose()
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Banana 6
...
However when I remove the usage Publish().RefCount() the new log output is as follows:
Log without only Subject
0001lhs.Subscribe()
0002rhs.Subscribe()
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe()
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe()
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Apple 6
OnNext
Banana 6
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose()
...
This gives us some insight, however when the issue really becomes clear is when we start annotating our logs with a logical list of subscriptions.
In the original (working) code with the RefCount our annotations might look like this
//word.Subsribers.Add(wordPub)
0001lhs.Subscribe() //wordPub.Subsribers.Add(0001lhs)
0002rhs.Subscribe() //word.Subsribers.Add(0002rhs)
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe() //wordPub.Subsribers.Add(0003lhsDuration)
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe() //wordPub.Subsribers.Add(0005lhsDuration)
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose() //wordPub.Subsribers.Remove(0003lhsDuration)
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Banana 6
So in this example, when word.OnNext("Banana"); is executed the chain of observers is linked in this order
wordPub
0002rhs
However, wordPub has child subscriptions!
So the real subscription list looks like
wordPub
0001lhs
0003lhsDuration
0005lhsDuration
0002rhs
If we annotate the Subject only log we see where the subtlety lies
0001lhs.Subscribe() //word.Subsribers.Add(0001lhs)
0002rhs.Subscribe() //word.Subsribers.Add(0002rhs)
0001lhs.OnNext(Apple)
0003lhsDuration.Subscribe() //word.Subsribers.Add(0003lhsDuration)
0002rhs.OnNext(5)
0004rhsDuration.Subscribe()
0004rhsDuration.OnCompleted()
0004rhsDuration.Dispose()
OnNext
Apple 5
0001lhs.OnNext(Banana)
0005lhsDuration.Subscribe() //word.Subsribers.Add(0005lhsDuration)
0002rhs.OnNext(6)
0006rhsDuration.Subscribe()
0006rhsDuration.OnCompleted()
0006rhsDuration.Dispose()
OnNext
Apple 6
OnNext
Banana 6
0003lhsDuration.OnNext(Banana)
0003lhsDuration.Dispose()
So in this example, when word.OnNext("Banana"); is executed the chain of observers is linked in this order
1. 0001lhs
2. 0002rhs
3. 0003lhsDuration
4. 0005lhsDuration
As the 0003lhsDuration subscription is activated after the 0002rhs, it wont see the "Banana" value to terminate the window, until after the rhs has been sent the value, thus yielding it in the still open window.
Whew
As #francezu13k50 points out the obvious and simple solution to your problem is to just use word.Select(x => new { Word = x, Length = x.Length });, but as I think you have given us a simplified version of your real problem (appreciated) I understand why this isn't suitable.
However, as I dont know what your real problem space is I am not sure what to suggest to you to provide a solution, except that you have one with your current code, and now you should know why it works the way it does.
RefCount returns an Observable that stays connected to the source as long as there is at least one subscription to the returned Observable. When the last subscription is disposed, RefCount disposes it's connection to the source, and reconnects when a new subscription is being made. It might be the case with your report query that all subscriptions to the 'wordPub' are disposed before the query is fulfilled.
Instead of the complicated GroupJoin query you could simply do :
var report = word.Select(x => new { Word = x, Length = x.Length });
Edit:
Change your report query to this if you want to use the GroupJoin operator :
var report =
wordPub
.GroupJoin(length,
s => wordPub,
s => Observable.Empty<int>(),
(w, a) => new { Word = w, Lengths = a })
.SelectMany(i => i.Lengths.FirstAsync().Select(j => new { Word = i.Word, Length = j }));
Because GroupJoin seems to be very tricky to work with, here is another approach for correlating the inputs and outputs of functions.
static void Main(string[] args) {
var word = new Subject<string>();
var length = new Subject<int>();
var report =
word
.CombineLatest(length, (w, l) => new { Word = w, Length = l })
.Scan((a, b) => new { Word = b.Word, Length = a.Word == b.Word ? b.Length : -1 })
.Where(i => i.Length != -1);
report.Subscribe(i => Console.WriteLine($"{i.Word} {i.Length}"));
word.OnNext("Apple"); length.OnNext(5);
word.OnNext("Banana");
word.OnNext("Cat"); length.OnNext(3);
word.OnNext("Donkey");
word.OnNext("Elephant"); length.OnNext(8);
word.OnNext("Zebra"); length.OnNext(5);
Console.ReadLine();
}
This approach works if every input has 0 or more outputs subject to the constraints that (1) outputs only arrive in the same order as the inputs AND (2) each output corresponds to its most recent input. This is like a LeftJoin - each item in the first list (word) is paired with items in the right list (length) that subsequently arrive, up until another item in the first list is emitted.
Trying to use regular Join instead of GroupJoin. I thought the problem was that when a new word was created there was a race condition inside Join between creating a new window and ending the current one. So here I tried to elimate that by pairing every word with a null signifying the end of the window. Doesn't work, just like the first version did not. How is it possible that a new window is created for each word without the previous one being closed first? Completely confused.
static void Main(string[] args) {
var lgr = new DelegateLogger(Console.WriteLine);
var word = new Subject<string>();
var wordDelimited =
word
.Select(i => Observable.Return<string>(null).StartWith(i))
.SelectMany(i => i);
var wordStart = wordDelimited.Where(i => i != null);
var wordEnd = wordDelimited.Where(i => i == null);
var report = Observable
.Join(
wordStart.Log(lgr, "word"), // starts window
wordStart.Select(i => i.Length),
s => wordEnd.Log(lgr, "expireWord"), // ends current window
s => Observable.Empty<int>(),
(l, r) => new { Word = l, Length = r });
report.Subscribe(i => Console.WriteLine($"{i.Word} {i.Length}"));
word.OnNext("Apple");
word.OnNext("Banana");
word.OnNext("Cat");
word.OnNext("Zebra");
word.OnNext("Elephant");
word.OnNext("Bear");
Console.ReadLine();
}

How to only emit consistent calculations?

I'm using reactive programming to do a bunch of calculations. Here is a simple example that tracks two numbers and their sum:
static void Main(string[] args) {
BehaviorSubject<int> x = new BehaviorSubject<int>(1);
BehaviorSubject<int> y = new BehaviorSubject<int>(2);
var sum = Observable.CombineLatest(x, y, (num1, num2) => num1 + num2);
Observable
.CombineLatest(x, y, sum, (xx, yy, sumsum) => new { X = xx, Y = yy, Sum = sumsum })
.Subscribe(i => Console.WriteLine($"X:{i.X} Y:{i.Y} Sum:{i.Sum}"));
x.OnNext(3);
Console.ReadLine();
}
This generates the following output:
X:1 Y:2 Sum:3
X:3 Y:2 Sum:3
X:3 Y:2 Sum:5
Notice how second output result is "incorrect" because it is showing that 3+2=3. I understand why this is happening (x is updated before the sum is updated) but I want my output calculations to be atomic/consistent - no value should be emitted until all dependent calculations are complete. My first approach was this...
Observable.When(sum.And(Observable.CombineLatest(x, y)).Then((s, xy) => new { Sum = s, X = xy[0], Y = xy[1] } ));
This seems to work for my simple example. But my actual code has LOTS of calculated values and I couldn't figure out how to scale it. For example, if there was a sum and squaredSum, I don't know how to wait for each of these to emit something before taking action.
One method that should work (in-theory) is to timestamp all the values I care about, as shown below.
Observable
.CombineLatest(x.Timestamp(), y.Timestamp(), sum.Timestamp(), (xx, yy, sumsum) => new { X = xx, Y = yy, Sum = sumsum })
.Where(i=>i.Sum.Timestamp>i.X.Timestamp && i.Sum.Timestamp>i.Y.Timestamp)
// do the calculation and subscribe
This method could work for very complicated models. All I have to do is ensure that no calculated value is emitted that is older than any core data value. I find this to be a bit of a kludge. It didn't actually work in my console app. When I replaced Timestamp with a custom extension that assigned a sequential int64 it did work.
What is a simple, clean way to handle this kind of thing in general?
=======
I'm making some progress here. This waits for a sum and sumSquared to emit a value before grabbing the data values that triggered the calculation.
var all = Observable.When(sum.And(sumSquared).And(Observable.CombineLatest(x, y)).Then((s, q, data)
=> new { Sum = s, SumSquared = q, X = data[0], Y = data[1] }));
This should do what you want:
Observable.CombineLatest(x, y, sum)
.DistinctUntilChanged(list => list[2])
.Subscribe(list => Console.WriteLine("{0}+{1}={2}", list[0], list[1], list[2]));
It waits until the sum has been updated, which means that all its sources must have been updated too.
You problem isn't because x is updated before the sum is updated per se. It's really about the way that you've constructed your query.
You've effectively created two queries: Observable.CombineLatest(x, y, (num1, num2) => num1 + num2) & Observable.CombineLatest(x, y, sum, (xx, yy, sumsum) => new { X = xx, Y = yy, Sum = sumsum }). Since in each you're subscribing to x then you've create two subscriptions. Meaning that when x updates then two lots of updates occur.
You need to avoid creating two subscriptions.
If you write your code like this:
BehaviorSubject<int> x = new BehaviorSubject<int>(1);
BehaviorSubject<int> y = new BehaviorSubject<int>(2);
Observable
.CombineLatest(x, y, (num1, num2) => new
{
X = num1,
Y = num2,
Sum = num1 + num2
})
.Subscribe(i => Console.WriteLine($"X:{i.X} Y:{i.Y} Sum:{i.Sum}"));
x.OnNext(3);
...then you correctly get this output:
X:1 Y:2 Sum:3
X:3 Y:2 Sum:5
I've started to get my head around this some more. Here is a more detailed example of what I'm trying to accomplish. This is some code that validates a first and last name, and should only generate a whole name when both parts are valid. As you can see I'm trying to use a bunch of small independently defined functions, like "firstIsValid", and then compose them together to calculate something more complex.
It seems like the challenge I'm facing here is trying to correlate inputs and outputs in my functions. For example, "firstIsValid" generates an output that says some first name was valid, but doesn't tell you which one. In option 2 below, I'm able to correlate them using Zip.
This strategy won't work if a validation function does not generate one output for each input. For example, if the user is typing web addresses and we're trying to validate them on the web, maybe we'd do a Throttle and/or Switch. There might be 10 web addresses for a single "webAddressIsValid". In that situation, I think I have to include the output with the input. Maybe have an IObservable> where the string is the web address and the bool is whether it is valid or not.
static void Main(string[] args) {
var first = new BehaviorSubject<string>(null);
var last = new BehaviorSubject<string>(null);
var firstIsValid = first.Select(i => string.IsNullOrEmpty(i) || i.Length < 3 ? false : true);
var lastIsValid = last.Select(i => string.IsNullOrEmpty(i) || i.Length < 3 ? false : true);
// OPTION 1 : Does not work
// Output: bob smith, bob, bob roberts, roberts
// firstIsValid and lastIsValid are not in sync with first and last
//var whole = Observable
// .CombineLatest(first, firstIsValid, last, lastIsValid, (f, fv, l, lv) => new {
// First = f,
// Last = l,
// FirstIsValid = fv,
// LastIsValid = lv
// })
// .Where(i => i.FirstIsValid && i.LastIsValid)
// .Select(i => $"{i.First} {i.Last}");
// OPTION 2 : Works as long as every change in a core data value generates one calculated value
// Output: bob smith, bob robert
var firstValidity = Observable.Zip(first, firstIsValid, (f, fv) => new { Name = f, IsValid = fv });
var lastValidity = Observable.Zip(last, lastIsValid, (l, lv) => new { Name = l, IsValid = lv });
var whole =
Observable.CombineLatest(firstValidity, lastValidity, (f, l) => new { First = f, Last = l })
.Where(i => i.First.IsValid && i.Last.IsValid)
.Select(i => $"{i.First.Name} {i.Last.Name}");
whole.Subscribe(i => Console.WriteLine(i));
first.OnNext("bob");
last.OnNext("smith");
last.OnNext(null);
last.OnNext("roberts");
first.OnNext(null);
Console.ReadLine();
}
Another approach here. Each value gets a version number (like a timestamp). Any time a calculated value is older than the data (or other calculated values it relies upon) we can ignore it.
public class VersionedValue {
static long _version;
public VersionedValue() { Version = Interlocked.Increment(ref _version); }
public long Version { get; }
}
public class VersionedValue<T> : VersionedValue {
public VersionedValue(T value) { Value = value; }
public T Value { get; }
public override string ToString() => $"{Value} {Version}";
}
public static class ExtensionMethods {
public static IObservable<VersionedValue<T>> Versioned<T>(this IObservable<T> values) => values.Select(i => new VersionedValue<T>(i));
public static VersionedValue<T> AsVersionedValue<T>(this T obj) => new VersionedValue<T>(obj);
}
static void Main(string[] args) {
// same as before
//
var whole = Observable
.CombineLatest(first.Versioned(), firstIsValid.Versioned(), last.Versioned(), lastIsValid.Versioned(), (f, fv, l, lv) => new {
First = f,
Last = l,
FirstIsValid = fv,
LastIsValid = lv
})
.Where(i => i.FirstIsValid.Version > i.First.Version && i.LastIsValid.Version > i.Last.Version)
.Where(i => i.FirstIsValid.Value && i.LastIsValid.Value)
.Select(i => $"{i.First.Value} {i.Last.Value}");

Reactive Extension key press media controls

I have a media application that allows the user to Play, Pause, step frame by frame, FastForward, etc. I am attempting to use Rx to get the following behavior for stepping and FastForward.
If the user clicks the right arrow less than 2 times/300ms I want to frame step.
If the user holds down the right arrow I want to fast forward until the right arrow button is released.
I think I have the fast forward part correct, but am not sure how to craft this to get the step functionality. I am also open to "better" ways to do the fast forward.
//start FF when we get 2 key presses within the threshold time
Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Where(k => k.EventArgs.Key == Key.Right)
.Timestamp()
.Buffer(2)
.Where(x => (x[1].Timestamp - x[0].Timestamp).Milliseconds < 300)
.Subscribe(x =>
{
Console.WriteLine("FastForward GO");
_viewModel.FastForward();
});
//stop ff on the key up
Observable.FromEventPattern<KeyEventArgs>(this, "KeyUp")
.Where(k => k.EventArgs.Key == Key.Right)
.Subscribe(x => {
Console.WriteLine("FastForward STOP");
_viewModel.StopFastForward();
});
Solution
var up = Observable.FromEventPattern<KeyEventArgs>(this, "KeyUp")
.Where(x => x.EventArgs.KeyCode == Keys.Right);
// Take, Concat, and Repeat work together to prevent repeated KeyDown events.
var down = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Where(x => x.EventArgs.KeyCode == Keys.Right)
.Take(1)
.Concat(up.Take(1).IgnoreElements())
.Repeat();
var t = TimeSpan.FromMilliseconds(300);
var tap = down.SelectMany(x =>
Observable.Amb(
Observable.Empty<EventPattern<KeyEventArgs>>().Delay(t),
up.Take(1)
))
.Publish()
.RefCount();
var longPress = down.SelectMany(x =>
Observable.Return(x).Delay(t).TakeUntil(tap)
);
There's multiple ways to do this, but this works at getting the "longPress" you need, as well as the "tap". You can use longPress to start fast-fowarding, up to stop fast-forwarding, and tap for frame-stepping.
tap yields when a key has been pressed and released within a timespan of t.
longPress yields when the key has been held down for longer than t.
up yields when the key has been released.
Explaination
A problem exists because the KeyDown event is repeated multiple times for each physical press of a key.
var down = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown");
In this case, we need a way to filter out the repeated KeyDown events. We can do that by using a combination of operators. First, we'll use Take(1). This will yield the first event and ignore the rest.
var first = down.Take(1);
If we only needed to get a single actual key press, this would be great. But, alas, we need to get all of the actual key presses. We need to wait for the KeyUp event to occur and start the whole thing over. To do this, we can use a combination of Concat and Repeat. For the concat observable, we need to make sure we're only taking 1 up event, and that we're ignore the elements of the up observable, otherwise we end up feeding all of the up events into our new observable.
var down = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Take(1)
.Contact(up.Take(1).IgnoreElements())
.Repeat();
This gives us the actual down events, without the in-between repeated events.
Now that we've cleaned up our source observables, we can start composing them in useful ways. What we're looking for is a "tap" event and a "long press" event. To get the tap event, we need to take a single actual down event, and make sure that it isn't held down too long... One way to do this is using the Amb operator.
var tap = down.SelectMany(x =>
Observable.Amb(
Observable.Empty<EventPattern<KeyEventArgs>>().Delay(t),
up.Take(1)
))
The Amb operator stands for "ambiguous". It takes a number of Observables, listens to each one, and waits for them to yield something. Once one of them yields an event, the Amb operator ignores (disposes the subscriptions of) the other observables.
In our case, for each down event that occurs, we use the SelectMany and Amb operator to check to see which yields or completes first... a single up event, or an empty observable that completes after a timespan of t. If the up event occurs before the the empty observable completes, its a tap. Otherwise, we ignore it.
Now we can do a similar thing for "long press", except this time we want to delay the KeyDown event until we know that it's not a tap. We can use a combination of the Delay and TakeUntil operators to do this. Delay makes sure the long press doesn't occur before a tap can be registered, and TakeUntil makes sure we ignore the KeyPress if it turned out to be a tap after all.
var longPress = down.SelectMany(x =>
Observable.Return(x).Delay(t).TakeUntil(tap)
);
Generalized Solution
This version works for any key.
var up = Observable.FromEventPattern<KeyEventArgs>(this, "KeyUp");
var downWithRepeats = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown");
var down =
Observable.Merge(
up.Select(x => new { e = x, type = "KeyUp" }),
downWithRepeats.Select(x => new { e = x, type = "KeyDown" })
)
.GroupByUntil(
x => x.e.EventArgs.KeyCode,
g => g.Where(y => y.type == "KeyUp")
)
.SelectMany(x => x.FirstAsync())
.Select(x => x.e);
var t = TimeSpan.FromMilliseconds(300);
var tap = down.SelectMany(x =>
Observable.Amb(
Observable.Empty<EventPattern<KeyEventArgs>>().Delay(t),
up.Where(y => y.EventArgs.KeyCode == x.EventArgs.KeyCode).Take(1)
))
.Publish()
.RefCount();
var longPress = down.SelectMany(x =>
Observable.Return(x).Delay(t).TakeUntil(
tap.Where(y => y.EventArgs.KeyCode == x.EventArgs.KeyCode)
)
);
Usage
Observable.Merge(
down .Select(x => string.Format("{0} - press", x.EventArgs.KeyCode)),
tap .Select(x => string.Format("{0} - tap", x.EventArgs.KeyCode)),
longPress.Select(x => string.Format("{0} - longPress", x.EventArgs.KeyCode)),
up .Select(x => string.Format("{0} - up", x.EventArgs.KeyCode))
)
.ObserveOn(SynchronizationContext.Current)
.Select(x => string.Format("{0} - {1}", x, DateTime.Now.ToLongTimeString()))
.Subscribe(text => this.myTextBox.Text = text);
Here's an alternative to Chris's that gives three streams, one for clicks, one for begin holds and one for end holds. Makes use of TimeInterval for recording duration between events.
WinForms Version
We can capture KeyDown eliminating repeats by using GroupByUntil to group KeyDown until a KeyUp occurs:
TimeSpan limit = TimeSpan.FromMilliseconds(300);
var key = Keys.Right;
var keyUp = Observable.FromEventPattern<KeyEventArgs>(this, "KeyUp")
.Where(i => i.EventArgs.KeyCode == key)
.Select(_ => true);
var keyDown = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Where(i => i.EventArgs.KeyCode == key)
.GroupByUntil(k => 0, _ => keyUp)
.SelectMany(x => x.FirstAsync());
var keyDownDuration = keyDown.Select(k => keyUp.TimeInterval()).Switch();
var clicks = keyDownDuration.Where(i => i.Interval < limit);
var beginHold = keyDown.Select(k => Observable.Timer(limit).TakeUntil(keyUp))
.Switch();
var endHold = keyDownDuration.Where(i => i.Interval > limit);
/* usage */
clicks.Subscribe(_ => Console.WriteLine("Click"));
beginHold.Subscribe(_ => Console.WriteLine("Hold Begin"));
endHold.Subscribe(_ => Console.WriteLine("Hold End"));
WPF Version
Originally, I had mistakenly assumed the WPF flavour of KevEventArgs as IsRepeat is not available in the WinForms version - which means this won't work for OP, but I'll leave it in as it may be of use for others.
TimeSpan limit = TimeSpan.FromMilliseconds(300);
var key = Key.Right;
var keyUp = Observable.FromEventPattern<KeyEventArgs>(this, "KeyUp")
.Where(i => i.EventArgs.Key == key);
var keyDown = Observable.FromEventPattern<KeyEventArgs>(this, "KeyDown")
.Where(i => i.EventArgs.IsRepeat == false
&& i.EventArgs.Key == key);
var keyDownDuration = keyDown.Select(k => keyUp.TimeInterval()).Switch();
var clicks = keyDownDuration.Where(i => i.Interval < limit);
var beginHold = keyDown.Select(k => Observable.Timer(limit).TakeUntil(keyUp))
.Switch();
var endHold = keyDownDuration.Where(i => i.Interval > limit);
/* usage */
clicks.Subscribe(_ => Console.WriteLine("Click"));
beginHold.Subscribe(_ => Console.WriteLine("Hold Begin"));
endHold.Subscribe(_ => Console.WriteLine("Hold End"));
To Test The Code
Include nuget package rx-main and paste the WinForms/WPF or code snippets as appropriate to the end of the Form contructor. Then run the code and press the right arrow key whilst observing the VS Output window to see the result.