"Here you have", someone says and you are given this input stream of values that you somewhat want to do distinctUntilChanged() upon...
Input: '1-1----11---2--1122----1---2---2-2-1-2---|'
Output: '1-----------2--1-2-----1---2-------1-2---|'
Nothing weird so far,
But now someone says "it's okey" if the same value comes again, "but only if it's not to soon!". I want at least '----' ticks between the same value. "Okey" you say and you add a throttle
const source = new Subject<number>();
// mysterious cave troll is randomly source.next(oneOrTwo)
const example = source.pipe(throttle(val => interval(4000)));
Input: '1-1----11---2--1122----1---2---2-2-1-2-----|'
Output: '1------1----2----2-----1-------2-----2-----|'
"That's not what I want! Look at all the value you missed", referring to that you throttle in regards to all values being streamed.
Input: '1-1----11---2--1122----1---2---2-2-1-2-----|'
Output: '1------1----2----2-----1-------2-----2-----|'
'-------------->1<--------->2<----->1<------|' <-- Missed values
"Here, let me show show you" the mysterious man says and gives you this
Wanted output
Input: '1-1----11---2--1112----1---2---2-2-1-2-----|'
Output: '1------1----2--1--2----1---2-----2-1-------|'
My answer to this is that it feels like a combined window wouldn't do.
From someone more experienced,
is this a hard problem to solve? (or have I missed an obvious solution)
First I came up with idea to somehow combine distinctUntilChanged() and throttleTimte(), however it was not possible for me to come up with solution and then I tried something else.
The operator I came up with is throttleDistinct() that works as you would like to: StackBlit Editor Link
It has 2 parameters which are:
duration: number which is in milliseconds and is similar to
duration in throttleTime(duration: number)
equals: (a: T, b: T) => boolean which is function to compare if previous item is equal to next item, which has default
implementation of (a, b) => a === b
import { of, fromEvent, interval, Observable } from 'rxjs';
import { map, scan, filter, } from 'rxjs/operators';
const source = fromEvent(document, 'keypress')
.pipe(map((x: any) => x.keyCode as number))
source
.pipe(
throttleDistinct(1000),
)
.subscribe((x) => console.log('__subscribe__', x));
export function throttleDistinct<T>(
duration: number,
equals: (a: T, b: T) => boolean = (a, b) => a === b
) {
return (source: Observable<T>) => {
return source
.pipe(
map((x) => {
const obj = { val: x, time: Date.now(), keep: true };
return obj;
}),
scan((acc, cur) => {
const diff = cur.time - acc.time;
const isSame = equals(acc.val, cur.val)
return diff > duration || (diff < duration && !isSame)
? { ...cur, keep: true }
: { ...acc, keep: false };
}),
filter((x) => x.keep),
map((x) => x.val),
)
}
}
Off the top of my head, you want to buffer by the time interval, then distinct within each buffer.
Effectively you want to restart / reboot the distinct run every n milliseconds.
source.pipe(
bufferTime(ms),
mergeMap(bufferArray => from(bufferArray).pipe(distinctUntilChanged()) )
)
This is my second attempt, it filters the stream by output (rather than taking distinctUntil) then throttles and merges the two streams.
Of course, we may not have a known set of values (1,2,...n).
If I can figure out that wrinkle, will add a further example.
const output = merge(
source.pipe( filter(x => x === 1), throttle(val => interval(ms))),
source.pipe( filter(x => x === 2), throttle(val => interval(ms)))
)
Here is my check (ms = 4000)
input 1-1----11---2--1112----1---2---2-2-1-2-----
expected 1------1----2--1--2----1---2-----2-1-------
filter(1) 1-1----11------111-----1-----------1-------
throttle(1) 1------1-------1-------1-----------1-------
filter(2) ------------2-----2--------2---2-2---2-----
throttle(2) ------------2-----2--------2-----2---------
merged 1------1----2--1--2----1---2-----2-1-------
expected 1------1----2--1--2----1---2-----2-1-------
Extending to n values
I think this will work where the set of values in the stream is not known in advance (or has a large range so extending the previous answer is impractical).
It should work as long as the source completes.
merge(
source.pipe(
distinct().pipe(
mapTo(distinctVal => source.pipe(
filter(val = val === distinctVal),
throttle(val => interval(ms))
)
)
)
)
I don't have a proof yet, will post that next.
Here is a tricky solution base on theory of operators, but I can't sure it really works, because I will need to mock a source emission first.
So throttle and distinct stream always have the latest value cached, zip make sure they always got emitted in pair, zip will always emit when any of the stream emit because it's shareReplay(1).
We always take the value emit from distinctStream, even when zip stream is trigger by throttle, because distinctStream always have the last cached value.
const throttleStream= source.pipe(throttle(val => interval(4000)),shareReplay(1))
const distinctStream= source.pipe(distinctUntilChanged(),shareReplay(1))
zip(throttleStream,distinctStream).pipe(
map((t,d)=>d)
)
I found a solution that works, does someone have any take on this?
source.pipe(
windowTime(4000),
concatMap(obs => obs.pipe(distinct()))
);
Examples from before, in a StackBlitz example
UPDATE: this does not actually work 100%. It only take the current window into consideration. So you can for example have
`[1-12][2---]` which would give `1--22---|`
where [----] would represent the time window. In other words, if a value is first emitted last in one window and emitted first in the next window, the same value will pass through right after each other.
Thanks #eric99 for making me realize this.
Related
In the following example, for whatever reason, initial value is ignored.
const frameRateSubject: BehaviorSubject<number> = new BehaviorSubject(24);
const loadedMetadata$: Observable<Event> = fromEvent(this.videoElement, 'loadedmetadata');
frameRateSubject.asObservable()
.pipe(
withLatestFrom(loadedMetadata$), // by commenting out this line, both 24 and 20 values are received
tap(([frameRate]: [number, Event]) => {
// initial value of 24 is never received, why is it?
console.log('frameRateSubject', frameRate)
})
)
.subscribe();
setTimeout(() => {
frameRateSubject.next(20)
}, 10000)
Any ideas why?
withLatestFrom combines the source observable (here frameRateSubject$) with other streams (loadedMetadata$) and emits values calculated from the latest values of each, only when the source emits.
But in your case loadedMetadata$ hasn't emitted when frameRateSubject$ emits 24. So the value is skipped.
CombineLatest is most likely the operator you are looking for here.
To make my specific condition as easy as possible I have the following observables.
const a = of(true);
const b = of(true);
I am looking to determine that if either or both of these are true then return an observable of true but if both are false, return an observable of false.
if (a || b) ? true : false;
I seem to be tripping over how to combine them correctly, I was using combineLatest to get an array.
combineLatest([
a,
b,
])
// [true, true]
I think that https://xgrommx.github.io/rx-book/content/observable/observable_instance_methods/some.html is what I need but that seems long gone and searching for "some" isn't amazing.
combineLatest([
a,
b,
]).pipe(
some(x => x === true)
)
You can use combineLatest to produce single observable based on multiple source observables:
const myBool$ = combineLatest([a$, b$]).pipe(
map(([a, b]) => a || b)
);
Your combined observable will emit an array of values whenever any of the sources emit (note it won't emit for the first time until each source emits at least once). You can then use the map operator to transform the emitted array into your single boolean.
The code above will emit the boolean value whenever any source emits. However, it may not be desirable to receive emissions if the resultant value hasn't changes (ex: if a and b are both true, then b changes to false, the result is still true).
We can prevent emitting the same result by using distinctUntilChanged:
const myBool$ = combineLatest([a$, b$]).pipe(
map(([a, b]) => a || b),
distinctUntilChanged()
);
I am trying to achieve something like below
from([1,2,3,4]).pipe(
Filter(v=> v % 2 === 0),
Tap( () => call server for even number only),
Swallow it, don't emit
).subscribe(()=>{log my odd numbers})
I know I can use filter( () => false) but was wondering if there is a better solution to it? Thanks
What do you mean? Swallow what? Based on what? Filter accepts/rejects values based on a predicate, if you want a better solution, what criteria would you prefer?
If you want to swallow everything, that's not functionally much different from terminating your Observable:
of([1,2,3,4]).pipe(
filter(v=> v % 2 === 0)
).subscribe(() => call server here);
Though I would expect an error as you can't use % on arrays. Though this should work:
from([1,2,3,4]).pipe(
filter(v=> v % 2 === 0)
).subscribe(() => call server here);
or
of([1,2,3,4]).pipe(
filter(v=> v.length % 2 === 0)
).subscribe(() => call server here);
Higher-order operators
If your server call is an observable-like object (promise, array, observable, ect), then you can use the higher-order operators like mergeMap, switchMap, concatMap, ect. In this case, you're not 'swallowing' a value so much as transforming (mapping) it into your call.
This also means whatever value your call server observable emits still gets passed forward. This really is a stream transformation rather than filtering/swallowing values.
from([1,2,3,4]).pipe(
filter(v=> v % 2 === 0),
concatMap(v => call server observable)
);
Update
Helper Function
/****
* Pipeable Operator:
* Takes arrays emitted by the source and spaces out their
* values by the given interval time in milliseconds
****/
function intervalArray<T>(intervalTime = 1000): OperatorFunction<T[], T> {
return pipe(
concatMap((v: T[]) =>
concat(
...v.map((value: T) =>
EMPTY.pipe(
delay(intervalTime),
startWith(value)
)
)
)
)
);
}
Mocking a hot observerable emitting values overtime:
const source = of([1,2,3,4,5]).pipe(
intervalArray(250),
shareReplay(1)
);
Solution
Here is how I might implement your question for such an observable. The simplest thing to do is to just subscribe to your observable twice:
source.pipe(
filter(v => v % 2 === 0),
concatMap(evenNumber => this.serviceCall(evenNumber))
).subscribe(/* serviceCallResult => Do Nothing */);
source.pipe(
filter(v => v % 2 !== 0)
).subscribe(oddNumber => console.log(
"I got an odd Number: ",
oddNumber
));
As the title suggests, I want to delay each value in an iterable by some amount of time while keeping the iterable lazily evaluated. Here's the closest I've got so far which works fine for finite iterables or those which don't throw an error
function* iter () {
let i = 0
while (true) yield i++
}
rxjs.zip(
rxjs.from(iter()),
rxjs.timer(500, 500),
x => x
).subscribe(console.log)
Another way might be:
const source$ = interval(0);
source$.pipe(
concatMap(x =>
of(x).pipe(delay(500))
)
).subscribe(console.log);
What is the best way to model a poller with a timeout, where a certain condition causes an early-exit as 'reactive streams'?
e.g.
If I had an observable which produced a decreasing sequence of positive integers every second
9,8,7,6,5,4,3,2,1,0
What is the best way to write a consumer which takes the latest single event after 5 seconds OR the '0' event if it produced earlier than the timeout.
This is my code as it stands at the moment: (Example in Java)
int intialValue = 10;
AtomicInteger counter = new AtomicInteger(intialValue);
Integer val = Observable.interval(1, TimeUnit.SECONDS)
.map(tick -> counter.decrementAndGet())
.takeUntil(it -> it == 0)
.takeUntil(Observable.timer(5, TimeUnit.SECONDS))
.lastElement()
.blockingGet();
System.out.println(val);
if initialValue = 10, I expect 6 to print. if initialValue = 2, i expect 0 to print before the 5 second timeout expires.
I'm interested if there is a better way to do this.
I don't think there is really a much better way than what you did. You have to have the following:
An interval to emit on (interval)
An aggregator to decrement and store the last value (scan)
A termination condition on the value (takeWhile)
A termination condition on time (takeUntil(timer(...)))
Get the last value on completion (last)
Each one is represented by an operator. You can't do much to get around that. I used a few different operators (scan for aggregation and takeWhile for termination on value) but it is the same number of operators.
const { interval, timer } = rxjs;
const { scan, takeWhile, takeUntil, last, tap } = rxjs.operators;
function poll(start) {
console.log('start', start);
interval(1000).pipe(
scan((x) => x - 1, start),
takeWhile((x) => x >= 0),
takeUntil(timer(5000)),
tap((x) => { console.log('tap', x); }),
last()
).subscribe(
(x) => { console.log('next', x); },
(e) => { console.log('error', e); },
() => { console.log('complete'); }
);
}
poll(10);
setTimeout(() => { poll(2); }, 6000);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.1.0/rxjs.umd.min.js"></script>
I'm not clear on how you expect it to function on the boundaries. In your example you always decrement before emiting so if your initial value is 10 then you emit 9, 8, 7, 6 (4 values). If you wanted to start with 10 then . you could do scan(..., start + 1) but that would end you at 7 because the timer in the takeUntil(...) aligns with the source interval so that 6 would be excluded. If you want to emit 5 values then you could do takeUntil(timer(5001)). Also, if you don't want to wait a second to emit the first value then you could put startWith(start) right after the scan(...). Or you could do timer(0, 1000) with scan(..., start + 1) instead of the source interval.
Also note that the termination on value (takeWhile) will not terminate till the invalid value is produced (-1). So it will continue for a second after receiving the termination value (0). It seems that most of the termination operators work that way where if they terminate on some value then they wont let the others through.
You could do a take(5) instead of takeUntil(timer(5000)) because you know it fires on a matching interval if that works for your scenario. That would also get around the issue of excluding the last value because of the timers lining up.