In Rx instead of only getting the last debounced object, can I get the complete sequence? - rxjs

I want to know if one of the debounced objects was a green ball. Filtering for only green balls before or after the debounce leads to incorrect behavior.

You can use the buffer operator together with the debounce operator. Here a very basic example:
// This is our event stream. In this example we only track mouseup events on the document
const move$ = Observable.fromEvent(document, 'mouseup');
// We want to create a debounced version of the initial stream
const debounce$ = move$.debounceTime(1000);
// Now create the buffered stream from the initial move$ stream.
// The debounce$ stream can be used to emit the values that are in the buffer
const buffered$ = move$.buffer(debounce$);
// Subscribe to your buffered stream
buffered$.subscribe(res => console.log('Buffered Result: ', res));

If I understand correctly what you want to achieve, you probably need to build an Observable which emits some sort of object which contains both the source value (i.e. blue, red, green in your case) as well as a flag that indicates whether or not there was a green in the debounced values.
If this is true, you can try to code along these lines
const s = new Subject<string>();
setTimeout(() => s.next('B'), 100);
setTimeout(() => s.next('G'), 1100);
setTimeout(() => s.next('B'), 1200);
setTimeout(() => s.next('G'), 1300);
setTimeout(() => s.next('R'), 1400);
setTimeout(() => s.next('B'), 2400);
let hasGreen = false;
s
.do(data => hasGreen = hasGreen || data === 'G')
.debounceTime(500)
.map(data => ({data, hasGreen})) // this map has to come before the following do
.do(() => hasGreen = false)
.subscribe(data => console.log(data))
Be careful about the sequence. In particular you have to put the map operator which creates the object you want to emit before the do that resets your variable.

This could be done with a non-trivial set of operators and side-effecting a flow by introducing extra channels:
import java.util.Queue;
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicLong;
import org.junit.Test;
import io.reactivex.*;
import io.reactivex.functions.Consumer;
import io.reactivex.schedulers.*;
import io.reactivex.subjects.PublishSubject;
public class DebounceTimeDrop {
#Test
public void test() {
PublishSubject<Integer> source = PublishSubject.create();
TestScheduler scheduler = new TestScheduler();
source.compose(debounceTime(10, TimeUnit.MILLISECONDS, scheduler, v -> {
System.out.println(
"Dropped: " + v + " # T=" + scheduler.now(TimeUnit.MILLISECONDS));
}))
.subscribe(v -> System.out.println(
"Passed: " + v + " # T=" + scheduler.now(TimeUnit.MILLISECONDS)),
Throwable::printStackTrace,
() -> System.out.println(
"Done " + " # T=" + scheduler.now(TimeUnit.MILLISECONDS)));
source.onNext(1);
scheduler.advanceTimeBy(10, TimeUnit.MILLISECONDS);
scheduler.advanceTimeBy(20, TimeUnit.MILLISECONDS);
source.onNext(2);
scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS);
source.onNext(3);
scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS);
source.onNext(4);
scheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS);
source.onNext(5);
scheduler.advanceTimeBy(10, TimeUnit.MILLISECONDS);
scheduler.advanceTimeBy(20, TimeUnit.MILLISECONDS);
source.onNext(6);
scheduler.advanceTimeBy(10, TimeUnit.MILLISECONDS);
scheduler.advanceTimeBy(20, TimeUnit.MILLISECONDS);
source.onComplete();
}
public static <T> ObservableTransformer<T, T> debounceTime(
long time, TimeUnit unit, Scheduler scheduler,
Consumer<? super T> dropped) {
return o -> Observable.<T>defer(() -> {
AtomicLong index = new AtomicLong();
Queue<Timed<T>> queue = new ConcurrentLinkedQueue<>();
return o.map(v -> {
Timed<T> t = new Timed<>(v,
index.getAndIncrement(), TimeUnit.NANOSECONDS);
queue.offer(t);
return t;
})
.debounce(time, unit, scheduler)
.map(v -> {
while (!queue.isEmpty()) {
Timed<T> t = queue.peek();
if (t.time() < v.time()) {
queue.poll();
dropped.accept(t.value());
} else
if (t == v) {
queue.poll();
break;
}
}
return v.value();
})
.doOnComplete(() -> {
while (!queue.isEmpty()) {
dropped.accept(queue.poll().value());
}
});
});
}
}
prints
Passed: 1 # T=10
Dropped: 2 # T=43
Dropped: 3 # T=43
Dropped: 4 # T=43
Passed: 5 # T=43
Passed: 6 # T=73
Done # T=93

Related

RxJS: How to create an event manager with a buffer that flush based on multiple conditions

I have a requirement to create an event manager with a buffer that flushes if one of 3 criteria is met:
2 seconds pass
50 events received
Flush on demand if requested by user
All criteria will reset when buffer flushes (reset the 2 second timer, reset the 50 events count...etc)
This is what I've implemeted so far and it seems to be working but I'm wondering if there's a better way to achieve this requirement.
import { interval, merge, Subject, Subscription } from "rxjs";
import { bufferWhen, filter, tap } from "rxjs/operators";
class Foo {
private eventListener: Subject < string > = new Subject();
private eventForceFlushListener: Subject < void > = new Subject();
private eventBufferSizeListener: Subject < void > = new Subject();
private maxBufferSize = 50;
private currentBufferSize = 0;
/**
*
* Buffer that will flush if one of the 3 criteria is met:
* - 50 texts are received
* - 2 seconds pass
* - Force flush by user
*/
private eventBufferOperator = () => merge(interval(2 * 1000), this.eventForceFlushListener, this.eventBufferSizeListener);
/**
* Flush buffer if requested by user. (for example flush buffer before app close so we dont lose buffered texts)
*/
public forceFlush() {
this.eventForceFlushListener.next();
}
/**
* Method used by users to emit texts to the listener
*/
public emitText(text: string) {
this.eventListener.next(text);
this.currentBufferSize = this.currentBufferSize + 1;
if (this.currentBufferSize == this.maxBufferSize) {
// flush all evenst when maxBufferSize is reached
this.eventBufferSizeListener.next();
// buffer size is reset below in the function that's inside "subscribe"
}
}
public subscribeToEventListerenr() {
const eventListenerSubscription = this.eventListener
.pipe(
tap((text) => text.trim()),
filter((text) => true),
bufferWhen(this.eventBufferOperator),
filter((events) => !!events.length)
)
.subscribe((x) => {
console.log(x);
this.maxBufferSize = 0; // reset size buffer
});
return eventListenerSubscription;
}
}
Users then can use this event manager as follows:
const eventManager = new Foo();
eventManager.subscribeToEventListerenr();
eventManager.emitText('message1');
eventManager.emitText('message2');
5 seconds pass
5 events received
Flush on demand if requested by user
const { race, Subject, take, share, buffer, tap, bufferCount, bufferTime, startWith, exhaustMap, map } = rxjs;
const observer = (str) => ({
subscribe: () => console.log(`${str} -> subscribe`),
next: () => console.log(`${str} -> next`),
unsubscribe: () => console.log(`${str} -> unsubscribe`),
});
const event$ = new Subject()
const share$ = event$.pipe(map((_, i) => i + 1), share());
const flush$ = new Subject();
const trigger$ = flush$.pipe(tap(observer('flush$')));
const bufferSize$ = share$.pipe(startWith(null), bufferCount(5), tap(observer('BufferSize 5')));
const bufferTime$ = share$.pipe(bufferTime(5000), tap(observer('5 Sec')));
const race$ = race(bufferTime$, trigger$, bufferSize$).pipe(take(1));
const buffer$ = share$.pipe(exhaustMap(() => race$));
share$.pipe(buffer(buffer$)).subscribe((x) => console.log(x));
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.5.6/rxjs.umd.min.js"></script>
<button onclick="event$.next()">event</button>
<button onclick="flush$.next()">flush</button>
https://stackblitz.com/edit/rxjs-5qgaeq
My final answer
window
bufferTime
const bufferBy$ = new Subject<void>();
const maxBufferSize = 3;
const bufferTimeSpan = 5000;
source$.pipe(
window(bufferBy$),
mergeMap(bufferTime(bufferTimeSpan, null, maxBufferSize)),
).subscribe(...);
https://stackblitz.com/edit/rxjs-hoafkr

Does nearBindgen not support child classes?

When I try to store an instance of a class that is the child of a different class in a PersistentVector or PersistentSet, when I read the same data, the instance fields have their default values.
The same doesn't happen when storing a regular class.
Is this a known issue?
Minimal reproduceable code:
// index.ts
import { PersistentVector } from "near-sdk-as";
#nearBindgen
class Base {
constructor(
public value1: u64
) { }
}
#nearBindgen
class Child extends Base {
constructor(
value1: u64,
public value2: u64
) {
super(value1);
}
}
#nearBindgen
export class Contract {
private pers: PersistentVector<Base> = new PersistentVector<Base>("vec");
add(value1: u64, value2: u64): Child {
const c = new Child(value1, value2);
this.pers.pushBack(c);
return c;
}
get(): Array<Base> {
const res: Array<Base> = [];
for (let i = 0; i < this.pers.length; i++) {
res.push(this.pers[i]);
}
return res;
}
}
// index.unit.spec.ts
import { Contract } from "../assembly/index";
let cont: Contract
beforeEach(() => {
cont = new Contract();
})
describe("Contract", () => {
it("add returns correct value", () => {
const result = cont.add(7, 3);
expect(result.value1).toBe(7);
expect(result.value2).toBe(3);
});
it("get after add returns 1 correct value", () => {
cont.add(7, 3);
const results = cont.get();
expect(results).toHaveLength(1);
const result = results[0];
expect(result.value1).toBe(7);
});
})
It produces the following test output:
[Describe]: Contract
[Success]: ✔ add returns correct value
[Fail]: ✖ get after add returns 1 correct value
[Actual]: 0 as u64
[Expected]: 7 as u64
[Stack]: RuntimeError: unreachable
at node_modules/#as-pect/assembly/assembly/internal/assert/assert (wasm://wasm/00023e6a:wasm-function[52]:0xde9)
at node_modules/#as-pect/assembly/assembly/internal/Expectation/Expectation<u64>#toBe (wasm://wasm/00023e6a:wasm-function[138]:0x2d9c)
at start:src/minimal/__tests__/index.unit.spec~anonymous|1~anonymous|1 (wasm://wasm/00023e6a:wasm-function[176]:0x466a)
at node_modules/#as-pect/assembly/assembly/internal/call/__call (wasm://wasm/00023e6a:wasm-function[179]:0x4690)
[File]: src/minimal/__tests__/index.unit.spec.ts
[Groups]: 2 pass, 2 total
[Result]: ✖ FAIL
[Snapshot]: 0 total, 0 added, 0 removed, 0 different
[Summary]: 1 pass, 1 fail, 2 total
[Time]: 2.333ms

How to seek to a position in a song Discord.js?

I am facing some difficulty with seeking to a specified timestamp in the current song. I have separate files for all my commands. I want to create a seek.js file which takes input a specified time and then passes it to the play.js file(it plays the current song in the queue) but the problem is I cant seem to find a way to how do this.
This is my play command.
const { Collector } = require("discord.js");
const ytdlDiscord = require("ytdl-core-discord");
//const play = require("../commands/play");
module.exports = {
async play(song, message){
const queue = message.client.queue.get(message.guild.id);
if(!song){
setTimeout(function(){
if(!queue.connection.dispatcher && message.guild.me.voice.channel){
queue.channel.leave();
queue.textChannel.send(`**Cadenza** left successfully`).catch(console.error);
}
else return;
},120000);
message.client.queue.delete(message.guild.id);
return queue.textChannel.send(`**Music Queue Ended**`);
}
let stream = await ytdlDiscord(song.url,{filter: 'audioonly', quality: 'highestaudio', highWaterMark: 1<<25});
let streamType = song.url.includes("youtube.com") ? "opus" : "ogg/opus";
queue.connection.on("disconnect", () => message.client.queue.delete(message.guild.id));
const dispatcher = queue.connection
.play(stream, {type: streamType, highWaterMark: 1})
.on("finish", () => {
if(queue.loop){
let last = queue.songs.shift();
queue.songs.push(last);
module.exports.play(queue.songs[0], message);
}else{
queue.songs.shift();
module.exports.play(queue.songs[0], message);
}
})
.on("error", (err) => {
console.error(err);
queue.songs.shift();
module.exports.play(queue.songs[0], message);
});
dispatcher.setVolumeLogarithmic(queue.volume / 100);
queue.textChannel.send(`Started Playing **${song.title}**`);
}
};
seek command
const { play } = require("../include/play");
function timeConvert(str){
const t = str.split(':');
let s = 0, m = 1;
while(t.length > 0){
s = +m * parseInt(t.pop(),10);
m = m * 60;
}
return s;
}
module.exports = {
name: 'seek',
description: 'Seeks to a certain point in the current track.',
execute(message,args){
const queue = message.client.queue.get(message.guild.id);
if(!queue) return message.channel.send("There is no song playing.").catch(console.error);
queue.playing = true;
let time = timeConvert(args[0]);
if( time > queue.songs[0].duration)
return message.channel.send(`**Input a valid time**`);
else{
let time = timeConvert(args[0]) * 1000;
#main code here
}
}
}
How can I pass the time variable to play() so that the current song seeks to that amount?

Functional/Stream programming for the graph problem "Reconstruct Itinerary"

I am trying to solve the reconstruct itinerary problem (https://leetcode.com/problems/reconstruct-itinerary/) in Scala using functional approach. Java solution works but Scala doesn't. One reason I found out was the hashmap is being updated and every iteration has the latest hashmap (even when popping from recursion) which is weird.
Here is the solution in Java:
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.PriorityQueue;
public class Solution1 {
private void dfg(Map<String, PriorityQueue<String>> adj, LinkedList<String> result, String vertex){
PriorityQueue<String> pq = adj.get(vertex);
while (pq!=null && !pq.isEmpty()){
System.out.println("Before :"+adj.get(vertex));
String v = pq.poll();
System.out.println("After :"+ adj.get(vertex));
dfg(adj,result,v);
}
result.addFirst(vertex);
}
public List<String> findItinerary(List<List<String>> tickets){
Map<String,PriorityQueue<String>> adj = new HashMap<>();
for(List<String> ticket: tickets){
adj.putIfAbsent(ticket.get(0),new PriorityQueue<>());
adj.get(ticket.get(0)).add(ticket.get(1));
}
LinkedList<String> result = new LinkedList<>();
dfg(adj,result,"JFK");
//not reverse yet
return result;
}
public static void main(String[] args){
List<List<String>> tickets = new ArrayList<>();
List t1= new ArrayList();
t1.add("JFK");
t1.add("SFO");
tickets.add(t1);
List t2= new ArrayList();
t2.add("JFK");
t2.add("ATL");
tickets.add(t2);
List t3= new ArrayList();
t3.add("SFO");
t3.add("ATL");
tickets.add(t3);
List t4= new ArrayList();
t4.add("ATL");
t4.add("JFK");
tickets.add(t4);
List t5= new ArrayList();
t5.add("ATL");
t5.add("SFO");
tickets.add(t5);
System.out.println();
Solution1 s1 = new Solution1();
List<String> finalRes = s1.findItinerary(tickets);
for(String model : finalRes) {
System.out.print(model + " ");
}
}
}
Here is my solution in Scala which is not working:
package graph
class Itinerary {
}
case class Step(g: Map[String,List[String]],sort: List[String]=List())
object Solution {
def main(arr: Array[String]) = {
val tickets = List(List("JFK","SFO"),List("JFK","ATL"),List("SFO","ATL"),List("ATL","JFK"),List("ATL","SFO"))
println(findItinerary(tickets))
}
def findItinerary(tickets: List[List[String]]): List[String] = {
val g = tickets.foldLeft(Map[String,List[String]]())((m,t)=>{
val key=t(0)
val value= t(1)
m + (key->(m.getOrElse(key,Nil) :+ value).sorted)
})
println(g)
// g.keys.foldLeft(Step())((s,n)=> dfs(n,g,s)).sort.toList
dfs("JFK",Step(g)).sort.toList
}
def dfs(vertex: String,step: Step): Step = {
println("Input vertex " + vertex)
println("Input map "+ step.g)
val updatedStep= step.g.getOrElse(vertex,Nil).foldLeft(step) ((s,n)=>{
//println("Processing "+n+" of vertex "+vertex)
//delete link
val newG = step.g + (vertex->step.g.getOrElse(vertex,Nil).filter(v=>v!=n))
// println(newG)
dfs(n,step.copy(g=newG))
})
println("adding vertex to result "+vertex)
updatedStep.copy(sort = updatedStep.sort:+vertex)
}
}
Scala is sometimes approached as a "better" Java, but that's really very limiting. If you can get into the FP mindset, and study the Standard Library, you'll find that it's a whole new world.
def findItinerary(tickets: List[List[String]]): List[String] = {
def loop(from : String
,jump : Map[String,List[String]]
,acc : List[String]) : List[String] = jump.get(from) match {
case None => if (jump.isEmpty) from::acc else Nil
case Some(next::Nil) => loop(next, jump - from, from::acc)
case Some(nLst) =>
nLst.view.map{ next =>
loop(next, jump+(from->(nLst diff next::Nil)), from::acc)
}.find(_.lengthIs > 0).getOrElse(Nil)
}
loop("JFK"
,tickets.groupMap(_(0))(_(1)).map(kv => kv._1 -> kv._2.sorted)
,Nil).reverse
}
I am going to be honest that I didn't look through your code to see where the problem was. But, I got caught by the problem and decided to give it a go; here is the code:
(hope my code helps you)
type Airport = String // Refined 3 upper case letters.
final case class AirlineTiket(from: Airport, to: Airport)
object ReconstructItinerary {
// I am using cats NonEmptyList to improve type safety, but you can easily remove it from the code.
private final case class State(
currentAirport: Airport,
availableDestinations: Map[Airport, NonEmptyList[Airport]],
solution: List[Airport]
)
def apply(tickets: List[AirlineTiket])(start: Airport): Option[List[Airport]] = {
#annotation.tailrec
def loop(currentState: State, checkpoints: List[State]): Option[List[Airport]] = {
if (currentState.availableDestinations.isEmpty) {
// We used all the tickets, so we can return this solution.
Some((currentState.currentAirport :: currentState.solution).reverse)
} else {
val State(currentAirport, availableDestinations, solution) = currentState
availableDestinations.get(currentAirport) match {
case None =>
// We got into nowhere, lets see if we can return to a previous state...
checkpoints match {
case checkpoint :: remaining =>
// If we can return from there
loop(currentState = checkpoint, checkpoints = remaining)
case Nil =>
// If we can't, then we can say that there is no solution.
None
}
case Some(NonEmptyList(destination, Nil)) =>
// If from the current airport we can only travel to one destination, we will just follow that.
loop(
currentState = State(
currentAirport = destination,
availableDestinations - currentAirport,
currentAirport :: solution
),
checkpoints
)
case Some(NonEmptyList(destination, destinations # head :: tail)) =>
// If we can travel to more than one destination, we are going to try all in order.
val newCheckpoints = destinations.map { altDestination =>
val newDestinations = NonEmptyList(head = destination, tail = destinations.filterNot(_ == altDestination))
State(
currentAirport = altDestination,
availableDestinations.updated(key = currentAirport, value = newDestinations),
currentAirport :: solution
)
}
loop(
currentState = State(
currentAirport = destination,
availableDestinations.updated(key = currentAirport, value = NonEmptyList(head, tail)),
currentAirport :: solution
),
newCheckpoints ::: checkpoints
)
}
}
}
val availableDestinations = tickets.groupByNel(_.from).view.mapValues(_.map(_.to).sorted).toMap
loop(
currentState = State(
currentAirport = start,
availableDestinations,
solution = List.empty
),
checkpoints = List.empty
)
}
}
You can see the code running here.

Calling n times the same observable

I have a http get webservice which I need to call n times, adding the return of my last call each time (first time there is a default value) how can I do it ?
You can use 'expand' operator from rxjs. It will loop until it's supplied with empty() observable. Here is example:
import { empty } from 'rxjs';
private service; <--- service that gives us the observable by some value
private initialValue: number = 5;
private counter: number = 0;
private source$: Observable<number> = this.service.getSourceWithValue(initialValue);
source$.pipe(
expand(value => isCounterExceeded()
? incrementCounterAndGetNextSourceObservableWithValue(value);
: empty()
);
// if counter is not exceeded we will increment the counter and create another
// observable based on current value. If it is exceeded, we are stopping the loop by
// returning the empty() observable
private incrementCounterAndGetNextSourceObservableWithValue(value: number): Observable<number> {
this.counter++;
return this.service.getSourceWithValue(value);
}
private isCounterExceeded() {
return this.counter >= 4;
}
This sounds like you could use expand:
const N = 4;
const source = of(1).pipe(
expand((previous, index) => index === 4 ? EMPTY : of(previous * 2))
);
source.subscribe(console.log);
Live demo: https://stackblitz.com/edit/rxjs-fcpin2

Resources