I have 2 streams of events:
1. Stream of mouse drag and drop events (drag start ... drag end ... drag start ... drag end)
2. Stream of key press events ('a' ... 'b' .... 'c' .... 'd')
I need to combine into a stream that only contains events from the second streams (so only key presses) but it needs to filter out all key presses that occur between a drag start and drag end, except for the last one.
So if the sources are like this:
... Start ............... End .............. Start .............. End
and
...........'a'...'b'...'c'.......'d'...'e'..........'f'....'g'.......
The result should be like this:
...........................'c'...'d'...'e'..........................'g'
Is something like this possible using Rx.net in C#?
The answer is yes. Answer first, then explanation:
public static class X
{
public static IObservable<T> GatedDebounce<T>(this IObservable<T> source, IObservable<bool> gating)
{
var finalStream = gating
.StartWith(false)
.DistinctUntilChanged()
.Publish(_gating => source.Publish(_source => Observable.Merge(
_source
.Window(_gating.Where(b => b), _ => _gating.Where(b => !b))
.SelectMany(o => o.LastAsync()),
_source
.Window(_gating.Where(b => !b), _ => _gating.Where(b => b))
.Merge()
)));
return finalStream;
}
}
Then, given an IObservable<T> representing your values, and an IObservable<bool> representing where drags start and stop (true meaning drag-start, and false meaning drag-end), you would call it like this:
var throttledStream= valueStream.GatedDebounce(gateStream);
Explanation:
To understand it better, let's throw out the Publish calls, and break it up into pieces:
Piece 1,
source
.Window(gating.Where(b => b), _ => gating.Where(b => !b))
.SelectMany(o => o.LastAsync())
This Window function means call means we start a sub-set observable (or window) whenever gating emits true, and end that window whenever gating emits false. From that window, we select the last item, if it exists. This will only be emitted when the window closes.
Piece 2,
source
.Window(gating.Where(b => !b), _ => gating.Where(b => b))
.Merge() //Equivalent to .SelectMany(o => o) if you prefer
This Window function does the opposite: Start a window whenever gating emits false, and end it whenever gating emits true. From that window we emit everything when it arrives.
Put these two together with Merge and you get 90% of the way to your solution. The rest:
The .StartWith(false) is to make sure we open a window when you initially start the observable, otherwise values that happen before the first gating item are lost.
The DistintUntilChanged() is a cheap way to make sure our gates are t, f, t, f and never two of the same value in a row, which would cause two simultaneous windows to open.
The Publish calls are good practice to prevent multiple subscriptions. You can find better explanations for that in some other Q&A's on here.
Related
I am trying to learn reactive programming, so forgive me if I ask a silly question. I'm also open to advice on changing my design.
I am working in scala-swing to display the results of a simulator. With one setting, a chart is displayed as a histogram; with the other setting the chart is displayed as the cumulative sum. (I'm probably using the wrong word; in the first setting you might have bin1=2, bin2=5, bin3=3; in the second setting the first height is 2, the second is 2 + 5, the third is 2 + 5 + 3, etc.). The simulator can be slow, so I originally used a Future to compute it, and the set the data into the chart. I decided to try a reactive approach, so my requirements are: 1. I don't want to recreate the data when I change the display mode, and 2. I want to set the Observable once for the chart and have the chart listen to the same Observable permanently.
I got this to work when I started the chain with a PublishSubject and the Future set the data into the start of the chain. When the display mode changed, I created a new PublishSubject().map(newRenderingLogic).subscribe(theChartsObservable). I am now trying to do what looks like the "right way," but it's not working correctly. I've tried to simplify what I have done:
val textObservable: Subject[String] = PublishSubject()
textObservable.subscribe(text => {
println(s"Text: ${text}")
})
var textSubscription: Option[Subscription] = None
val start = Observable.from(Future {
"Base text"
}).cache
var i = 0
val button = new Button() {
text = "Click"
reactions += {
case event => {
i += 1
if (textSubscription.isDefined) {
textSubscription.get.unsubscribe()
}
textSubscription = Some(start.map(((j: Int) => { (base: String) => s"${base} ${j}" })(i)).subscribe(textObservable))
}
}
}
On start, an Observable is created and logic to print some text is added to it. Then, an Observable with the generated data is created and a cache is added so that the result is replayed if the next subscription comes in after its results are generated. Then, a button is created. Then on button clicks a middle observable is chained with unique logic (it's a function that creates a function to append the value of i into the string, run with the current value of i; I tried to make something that couldn't just be reused) that is supposed to change with each click. Then the first Observable is subscribed to it so that the results of the whole chain end up being printed.
In theory, the cache operation takes care of not regenerating the data, and this works once, but onComplete is called on textObservable and then it can't be used again. It works if I subscribe it like this:
textSubscription = Some(start.map(((j: Int) => { (base: String) => s"${base} ${j}" })(i)).subscribe(text => textObservable.onNext(text)))
because the call to onComplete is intercepted, but this looks wrong and I wanted to know if there was a more typical way to do this, or architect it. It makes me think that I don't understand how this is supposed to be done if there isn't an out-of-the-box operation to do this.
Thank you.
I'm not 100% sure if I got the essence of your question right, but: if you have an Observable that may complete and you want to turn it into an Observable that never completes, you can just concatenate it with Observable.never.
For example:
// will complete after emitting those three elements:
val completes = Observable.from(List(1, 2, 3))
// will emit those three elements, but will never complete:
val wontComplete = completes ++ Observable.never
UPDATE
I think I've figured out the solution. I explain it in this video. Basically, use timeoutWith, and some tricks with zip (within zip).
https://youtu.be/0A7C1oJSJDk
If I have a single observable like this:
A-1-2--B-3-4-5-C--D--6-7-E
I want to put the "numbers" as lower priority; it should wait until the "letters" is filled up (a group of 2 for example) OR a timeout is reached, and then it can emit. Maybe the following illustration (of the desired result) can help:
A------B-1-----C--D-2----E-3-4-5-6-7
I've been experimenting with some ideas... one of them: first step is to split that stream (groupBy), one containing letters, and the other containing numbers..., then "something in the middle" happen..., and finally those two (sub)streams get merged.
It's that "something in the middle" what I'm trying to figure out.
How to achieve it? Is that even possible with RxJS (ver 5.5.6)? If not, what's the closest one? I mean, what I want to avoid is having the "numbers" flooding the stream, and not giving enough chance for the "letters" to be processed in timely manner.
Probably this video I made of my efforts so far can clarify as well:
Original problem statement: https://www.youtube.com/watch?v=mEmU4JK5Tic
So far: https://www.youtube.com/watch?v=HWDI9wpVxJk&feature=youtu.be
The problem with my solution so far (delaying each emission in "numbers" substream using .delay) is suboptimal, because it keeps clocking at slow pace (10 seconds) even after the "characters" (sub)stream has ended (not completed -- no clear boundary here -- just not getting more value for indeterminate amount of time). What I really need is, to have the "numbers" substream raise its pace (to 2 seconds) once that happen.
Unfortunately I don't know RxJs5 that much and use xstream myself (authored by one of the contributor to RxJS5) which is a little bit simpler in terms of the number of operators.
With this I crafted the following example:
(Note: the operators are pretty much the same as in Rx5, the main difference is with flatten wich is more or less like switch but seems to handle synchronous streams differently).
const xs = require("xstream").default;
const input$ = xs.of("A",1,2,"B",3,4,5,"C","D",6,7,"E");
const initialState = { $: xs.never(), count: 0, buffer: [] };
const state$ = input$
.fold((state, value) => {
const t = typeof value;
if (t === "string") {
return {
...state,
$: xs.of(value),
count: state.count + 1
};
}
if (state.count >= 2) {
const l = state.buffer.length;
return {
...state,
$: l > 0 ? xs.of(state.buffer[0]) : xs.of(value) ,
count: 0,
buffer: state.buffer.slice(1).concat(value)
};
}
return {
...state,
$: xs.never(),
buffer: state.buffer.concat(value),
};
}, initialState);
xs
.merge(
state$
.map(s => s.$),
state$
.last()
.map(s => xs.of.apply(xs, s.buffer))
)
.flatten()
.subscribe({
next: console.log
});
Which gives me the result you are looking for.
It works by folding the stream on itself, looking at the type of values and emitting a new stream depending on it. When you need to wait because not enough letters were dispatched I emit an emptystream (emits no value, no errors, no complete) as a "placeholder".
You could instead of emitting this empty stream emit something like
xs.empty().endsWith(xs.periodic(timeout)).last().mapTo(value):
// stream that will emit a value only after a specified timeout.
// Because the streams are **not** flattened concurrently you can
// use this as a "pending" stream that may or may not be eventually
// consumed
where value is the last received number in order to implement timeout related conditions however you would then need to introduce some kind of reflexivity with either a Subject in Rx or xs.imitate with xstream because you would need to notify your state that your "pending" stream has been consumed wich makes the communication bi-directionnal whereas streams / observables are unidirectionnal.
The key here the use of timeoutWith, to switch to the more aggresive "pacer", when the "events" kicks in. In this case the "event" is "idle detected in the higher-priority stream".
The video: https://youtu.be/0A7C1oJSJDk
I have a ReactiveList with keywords. The user can add or remove keyword from that list. The app needs to verify if the user typed one of the keywords.
There was already a similar post but it doesn't take in account a flexible list:
Using Reactive Extension for certain KeyPress sequences?
var keyElements = new ReactiveList<KeyElement>();
IObservable<IObservable<int>> rangeToMax = Observable.Merge(keyElements.ItemsAdded, keyElements.ItemsRemoved).Select(obs => Observable.Range(2, keyElements.Select(ke => ke.KeyTrigger.Length).Max()));
IObservable<IObservable<string>> detectedKeyTrigger = rangeToMax
.Select(n => _keyPressed.Buffer(n, 1))
.Merge().Where(m => keyElements.Where(ke => ke.KeyTrigger == m).Any());
//Here I want to end up with IObservable<string> instead of IObservable<IObservable<string>>
I can get rid of the outer IObservable by reassigning the detectedKeyTrigger each time an element in the reactive list changes, but then I lose all my subscriptions.
So, how can I end up with just an Observable of strings?
First off, both Max and Any have overloads which takes a selector and a predicate respectively. This negates the need of the Select.
Next, I changed the Observable.Merge to use the Changed property of ReactiveList which is the Rx version of INotifyCollectionChanged. I also changed the Select to produce an IEnumerable of ints instead; it just felt more Rightâ„¢.
var keyElements = new ReactiveList<KeyElement>();
IObservable<IEnumerable<int>> rangeToMax = keyElements.Changed
.Select(_ => Enumerable.Range(2, keyElements.Max(keyElement => keyElement.KeyTrigger.Length));
IObservable<IObservable<string>> detectedKeyTrigger = rangeToMax.
.Select(range => range
.Select(length => _keyPressed.Buffer(length, 1).Select(chars => new string(chars.ToArray()))) // 1
.Merge() // 2
.Where(m => keyElements.Any(ke => ke.KeyTrigger == m)) // 3
.Switch(); // 4
Create an IObservable<string> which emits the last n characters typed by the user. Create such an observable for each of the possible lengths of an combo
Merge the observables in the IEnumerable<IObservable<string>> into one Observable<string>
Only let strings which mach one of the KeyTriggers through
As rangeToMax.Select produces an IObservable<IObservable<string>> we use Switch to only subscribe to the most recent IObservable<string> the IObservable<IObservable<string>> produces.
I want to separate an observable sequence (IObservable) into several observable sequences (the criteria is complicated, but for the sake of demonstration we can simply use count). This, quite obviously, calls for Window.
private static IObservable<int> GenerateSequence()
{
return Observable.Range(1, 5);
}
await GenerateSequence()
.Window(2)
.Select((w, i) => new {i, w})
.Do(w => w.w.Dump($"Window {w.i}"));
Output is as expected:
Window 0-->1
Window 0-->2
Window 0-->X
Window 1-->3
Window 1-->4
Window 1-->X
Window 2-->5
Window 2-->X
(X's mark the OnCompleted)
Now, for some reason I want only first of those sequences. Hence, FirstAsync:
(await GenerateSequence()
.Window(2)
.FirstAsync())
.Dump("Window");
But the strange thing is, I get no output at all, as if the sequence I get from FirstAsync was completely dead.
I'm kinda new to Rx, so I'm totally lost as to what and why exactly happens here.
EDIT:
Your answer works. You may want to look at replacing Window with Buffer. They're virtually identical, except Buffer was meant for similar scenarios. It returns a list though instead of array:
var t = GenerateSequence()
.Buffer(2)
.FirstAsync()
//.Select(list => list.ToArray()) //If you're particular about Task<int[]> over Task<IList<int>>
.ToTask();
As another aside, .SelectMany(i => i) can be replaced with .Merge().
Old answer:
I generally don't mix Rx with await. It's apparently supported, but seems... unintuitive. This code dumps the output of the first window:
GenerateSequence()
.Window(2)
.FirstAsync()
.Subscribe(i => i.Dump("Window"));
This dumps the first item from each window:
GenerateSequence()
.Window(2)
.Select(o => o.FirstAsync())
.Subscribe(i => i.Dump("Window"));
It wasn't clear from your question which one you wanted.
Actually, the solution was quite simple (although I still don't get what happens in my original code) - use SelectMany to flatten the sequence:
GenerateSequence()
.Window(2)
.FirstAsync()
.Concat()
.Dump("SO");
Produces, as expected:
SO-->1
SO-->2
SO-->X
And then, to achieve my original goal - i.e., to change the monad and return the result as Task<int[]>:
GenerateSequence()
.Window(2)
.FirstAsync()
.Concat()
.ToArray()
.ToTask();
I have a cold observable with static number of items, I needed some time delay between each item, I have combined it with another IObservable I got through Observable.Timer. I am using Zip .
var ob1 = Observable.Range(1, 100);
var ob2 = Observable.Timer(TimeSpan.FromSeconds(1.0));
var myObservable = Observable.Zip(ob1, ob2, (a, b) => b);
myObservable.Subscribe(a => Console.WriteLine("Item encountered"));
///Allow enough time for Timer observable to give back multiple ticks
Thread.Sleep(3000);
But output only prints "Item encountered" once. What am I missing ?
To confirm the commentary, Observable.Interval is the way to go for just a single argument - and thus it has always been!
I found the solution. Observable.Timer takes two arguments for my scenario, first one is due time for first item and second due time is for all subsequent items. And if only one TimeSpan argument is supplied, it would yield only one item.
Observable.Timer(TimeSpan.FromSeconds(1.0), TimeSpan.FromSeconds(1.0));