RXJS concatMap should also lazy pull? - rxjs

If i use a range and limit the output with takeWhile:
Rx.Observable
.range( 1, 10 )
.do( idx => console.log('range: ', idx ))
.takeWhile( idx => idx <= 5 )
.subscribe( idx => console.log( 'res : ', idx ))
;
the output is:
range: 1
res : 1
range: 2
res : 2
range: 3
res : 3
range: 4
res : 4
range: 5
res : 5
range: 6
the produces values by range are not all consumed. 6 is pulled, does not pass the takeWhile, no more values are taken.
Now if i have a concatMap in between:
Rx.Observable
.range( 1, 10 )
.do( idx => console.log('range: ', idx ))
.concatMap( idx => {
var res = new Rx.Subject<number>();
setTimeout( () => {
res.next( idx );
res.complete();
}, 10 );
return res;
})
.takeWhile( idx => idx <= 5 )
.subscribe( idx => console.log( 'res: ', idx ))
;
The output is this:
range: 1
range: 2
range: 3
range: 4
range: 5
range: 6
range: 7
range: 8
range: 9
range: 10
res: 1
res: 2
res: 3
res: 4
res: 5
I would expect, that the values from range production would be limited here as well. concatMap preserves the order, so it makes only sense to pull the next value, when the previous observable is completed. But all range errors are pulled.
Is this a bug? Or what is the real behavior. Can you please help to understand.

Values produced by range() are buffered inside concatMap() operator and then pulled one by one. Then you're using setTimeout() to asynchronously emit values. Operators in general don't try to utilize any backpressure so all items from the source are emitted when they're ready.
Note that you could achieve the same even when using Observable.of() and the asynchronous scheduler Scheduler.async. This makes the emission from Observable.of to happen in a new event which makes it asynchronous.
const Observable = Rx.Observable;
const Scheduler = Rx.Scheduler;
Rx.Observable
.range( 1, 10 )
.do( idx => console.log('range: ', idx ))
.concatMap( idx => Observable.of(idx, Scheduler.async))
.takeWhile( idx => idx <= 5 )
.subscribe( idx => console.log( 'res: ', idx ));
See live demo: https://jsbin.com/xukolo/3/edit?js,console

Related

RxJs combineLatest subscribe never fires

Script
import { forkJoin, zip, combineLatest, Subject, ReplaySubject } from 'rxjs';
import { withLatestFrom, take, first } from 'rxjs/operators';
const letters: string = 'abcdefghijklmnopqrstuvwxyz';
function getLetter(i: number) : string {
const l: string = letters[i].toString();
console.log(` letter ${l}`);
return letters[i].toString();
}
function getNumber(i: number) : string {
const l: string = letters[i].toString();
console.log(` number ${i}`);
return i.toString();
}
const sa = new ReplaySubject<string>(1);
const sz = new ReplaySubject<string>(1);
combineLatest(sa, sz)
.subscribe(([a, z]) => {console.log(`s: ${a}-${z}`);});
var i: number = 0;
var b: boolean = true;
while(i < 10) {
console.log(`i: ${i}`);
sa.next(getNumber(i));
if(b) {
sa.next(getLetter(i));
}
i++;
b = !b;
}
Output
C:\Work\ts-experiments>tsc
C:\Work\ts-experiments>node dist\index.js
i: 0
number 0
letter a
i: 1
number 1
i: 2
number 2
letter c
i: 3
number 3
i: 4
number 4
letter e
i: 5
number 5
i: 6
number 6
letter g
i: 7
number 7
i: 8
number 8
letter i
i: 9
number 9
C:\Work\ts-experiments>
Why nothing from the .subscribe?
(Unfortunately this doesn't work in JSFiddle.)
Because sz never fires. For combineLatest to fire, all of the observables should emit at least once.

How can I rewrite this countdown timer using RxPY?

Here's RxJS code I'm trying to reproduce with RxPY.
const counter$ = interval(1000);
counter$
.pipe(
mapTo(-1),
scan((accumulator, current) => {
return accumulator + current;
}, 10),
takeWhile(value => value >= 0)
)
.subscribe(console.log);
9
8
7
6
5
4
3
2
1
0
-1
And here's what I through was equivalent but is not
counter = rx.interval(1)
composed = counter.pipe(
ops.map(lambda value: value - 1),
ops.scan(lambda acc, curr: acc + curr, 10),
ops.take_while(lambda value: value >= 0),
)
composed.subscribe(lambda value: print(value))
9
9
10
12
15
19
Could someone help me to understand what I'm missing here?
I don't know python at all, but I do notice one difference in your map between your js and python:
mapTo(-1) // always emits -1
-- vs --
ops.map(lambda value: value - 1) # emits interval index - 1
I think the solution is simple, just remove the "value":
ops.map(lambda value: -1)
However, if your "current value" is always -1 you can simplify by not using map at all and put -1 in your scan() function. Here's what it looks like in rxjs:
const countDown$ = interval(1000).pipe(
scan(accumulator => accumulator - 1, 10)
takeWhile(value => value >= 0)
);

How to copy list of values from one array to another array on some interval using Rxjs?

Using Rxjs, I need to copy 10 next values evey 200ms from arr1 to arr2. I come up with below code, but there should be some Rxjs way to do.
originalArr= [];
newArr = [];
Rx.Observable.interval(200)
.scan((acc, val) => acc + 10)
.takeWhile(v => v < this.value.length)
.subscribe(val => {
const arr = this.value.slice(val, val+10);
arr.forEach(v => this.newVal.push(v));
});

Queue operator for RxJS

Is there an operator in RxJS that would allow me to buffer items and let them out one by one whenever a signal observable fires? Sort of like bufferWhen, but instead of dumping the whole buffer on each signal it would dump a certain number per signal. It could even dump the number that gets emitted by the signal observable.
Input observable: >--a--b--c--d--|
Signal observable: >------1---1-1-|
Count in buffer: !--1--21-2-121-|
Output observable: >------a---b-c-|
Yes, you can use zip to do what you want:
const input = Rx.Observable.from(["a", "b", "c", "d", "e"]);
const signal = new Rx.Subject();
const output = Rx.Observable.zip(input, signal, (i, s) => i);
output.subscribe(value => console.log(value));
signal.next(1);
signal.next(1);
signal.next(1);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs#5/bundles/Rx.min.js"></script>
In fact, zip is used as an example in this GitHub issue that pertains to buffering.
If you want to use the signal's emitted value to determine how many buffered values are to be released, you could do something like this:
const input = Rx.Observable.from(["a", "b", "c", "d", "e"]);
const signal = new Rx.Subject();
const output = Rx.Observable.zip(
input,
signal.concatMap(count => Rx.Observable.range(0, count)),
(i, s) => i
);
output.subscribe(value => console.log(value));
signal.next(1);
signal.next(2);
signal.next(1);
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs#5/bundles/Rx.min.js"></script>
window can be used to separate the timeline. And takeLast is used to hold the output.
let signal = Rx.Observable.interval(1000).take(4);
let input = Rx.Observable.interval(300).take(10).share();
let output = input
.do(value => console.log(`input = ${value}`))
.window(signal)
.do(() => console.log(`*** signal : end OLD and start NEW subObservable`))
.mergeMap(subObservable => {
return subObservable.takeLast(100);
})
.share()
output.subscribe(value => console.log(` output = ${value}`));
Rx.Observable.merge(input.mapTo(1), output.mapTo(-1))
.scan((count, diff) => {
return count + diff;
}, 0)
.subscribe(count => console.log(` count = ${count}`));
.as-console-wrapper { max-height: 100% !important; top: 0; }
<script src="https://unpkg.com/rxjs/bundles/Rx.min.js"></script>
Result:
22:28:37.971 *** signal : end OLD and start NEW subObservable
22:28:38.289 input = 0
22:28:38.292 count = 1
22:28:38.575 input = 1
22:28:38.576 count = 2
22:28:38.914 input = 2
22:28:38.915 count = 3
<signal received>
22:28:38.977 output = 0
22:28:38.979 count = 2
22:28:38.980 output = 1
22:28:38.982 count = 1
22:28:38.984 output = 2
22:28:38.986 count = 0
22:28:38.988 *** signal : end OLD and start NEW subObservable
22:28:39.175 input = 3
22:28:39.176 count = 1
22:28:39.475 input = 4
22:28:39.478 count = 2
22:28:39.779 input = 5
22:28:39.780 count = 3
<signal received>
22:28:39.984 output = 3
22:28:39.985 count = 2
22:28:39.986 output = 4
22:28:39.988 count = 1
22:28:39.989 output = 5
22:28:39.990 count = 0
22:28:39.992 *** signal : end OLD and start NEW subObservable
22:28:40.075 input = 6
22:28:40.077 count = 1
22:28:40.377 input = 7
22:28:40.378 count = 2
22:28:40.678 input = 8
22:28:40.680 count = 3
22:28:40.987 input = 9
22:28:40.990 count = 4
<input completed>
22:28:40.992 output = 6
22:28:40.993 count = 3
22:28:40.995 output = 7
22:28:40.996 count = 2
22:28:40.998 output = 8
22:28:40.999 count = 1
22:28:41.006 output = 9
22:28:41.007 count = 0

Tournament bracket placement algorithm

Given a list of opponent seeds (for example seeds 1 to 16), I'm trying to write an algorithm that will result in the top seed playing the lowest seed in that round, the 2nd seed playing the 2nd-lowest seed, etc.
Grouping 1 and 16, 2 and 15, etc. into "matches" is fairly easy, but I also need to make sure that the higher seed will play the lower seed in subsequent rounds.
An example bracket with the correct placement:
1 vs 16
1 vs 8
8 vs 9
1 vs 4
4 vs 13
4 vs 5
5 vs 12
1 vs 2
2 vs 15
2 vs 7
7 vs 10
2 vs 3
3 vs 14
3 vs 6
6 vs 11
As you can see, seed 1 and 2 only meet up in the final.
This JavaScript returns an array where each even index plays the next odd index
function seeding(numPlayers){
var rounds = Math.log(numPlayers)/Math.log(2)-1;
var pls = [1,2];
for(var i=0;i<rounds;i++){
pls = nextLayer(pls);
}
return pls;
function nextLayer(pls){
var out=[];
var length = pls.length*2+1;
pls.forEach(function(d){
out.push(d);
out.push(length-d);
});
return out;
}
}
> seeding(2)
[1, 2]
> seeding(4)
[1, 4, 2, 3]
> seeding(8)
[1, 8, 4, 5, 2, 7, 3, 6]
> seeding(16)
[1, 16, 8, 9, 4, 13, 5, 12, 2, 15, 7, 10, 3, 14, 6, 11]
With your assumptions, players 1 and 2 will play in the final, players 1-4 in the semifinals, players 1-8 in the quarterfinals and so on, so you can build the tournament recursively backwards from the final as AakashM proposed. Think of the tournament as a tree whose root is the final.
In the root node, your players are {1, 2}.
To expand the tree recursively to the next level, take all the nodes on the bottom layer in the tree, one by one, and create two children for them each, and place one of the players of the original node to each one of the child nodes created. Then add the next layer of players and map them to the game so that the worst newly added player plays against the best pre-existing player and so on.
Here first rounds of the algorithm:
{1,2} --- create next layer
{1, _}
/ --- now fill the empty slots
{1,2}
\{2, _}
{1, 4} --- the slots filled in reverse order
/
{1,2}
\{2, 3} --- create next layer again
/{1, _}
{1, 4}
/ \{4, _}
{1,2} --- again fill
\ /{2, _}
{2, 3}
\{3, _}
/{1, 8}
{1, 4}
/ \{4, 5} --- ... and so on
{1,2}
\ /{2, 7}
{2, 3}
\{3, 6}
As you can see, it produces the same tree you posted.
I've come up with the following algorithm. It may not be super-efficient, but I don't think that it really needs to be. It's written in PHP.
<?php
$players = range(1, 32);
$count = count($players);
$numberOfRounds = log($count / 2, 2);
// Order players.
for ($i = 0; $i < $numberOfRounds; $i++) {
$out = array();
$splice = pow(2, $i);
while (count($players) > 0) {
$out = array_merge($out, array_splice($players, 0, $splice));
$out = array_merge($out, array_splice($players, -$splice));
}
$players = $out;
}
// Print match list.
for ($i = 0; $i < $count; $i++) {
printf('%s vs %s<br />%s', $players[$i], $players[++$i], PHP_EOL);
}
?>
I also wrote a solution written in PHP. I saw Patrik Bodin's answer, but thought there must be an easier way.
It does what darkangel asked for: It returns all seeds in the correct positions. The matches are the same as in his example, but in a prettier order, seed 1 and seed number 16 are on the outside of the schema (as you see in tennis tournaments).
If there are no upsets (meaning a higher seeded player always wins from a lower seeded player), you will end up with seed 1 vs seed 2 in the final.
It actually does two things more:
It shows the correct order (which is a requirement for putting byes in the correct positions)
It fills in byes in the correct positions (if required)
A perfect explanation about what a single elimination bracket should look like: http://blog.playdriven.com/2011/articles/the-not-so-simple-single-elimination-advantage-seeding/
Code example for 16 participants:
<?php
define('NUMBER_OF_PARTICIPANTS', 16);
$participants = range(1,NUMBER_OF_PARTICIPANTS);
$bracket = getBracket($participants);
var_dump($bracket);
function getBracket($participants)
{
$participantsCount = count($participants);
$rounds = ceil(log($participantsCount)/log(2));
$bracketSize = pow(2, $rounds);
$requiredByes = $bracketSize - $participantsCount;
echo sprintf('Number of participants: %d<br/>%s', $participantsCount, PHP_EOL);
echo sprintf('Number of rounds: %d<br/>%s', $rounds, PHP_EOL);
echo sprintf('Bracket size: %d<br/>%s', $bracketSize, PHP_EOL);
echo sprintf('Required number of byes: %d<br/>%s', $requiredByes, PHP_EOL);
if($participantsCount < 2)
{
return array();
}
$matches = array(array(1,2));
for($round=1; $round < $rounds; $round++)
{
$roundMatches = array();
$sum = pow(2, $round + 1) + 1;
foreach($matches as $match)
{
$home = changeIntoBye($match[0], $participantsCount);
$away = changeIntoBye($sum - $match[0], $participantsCount);
$roundMatches[] = array($home, $away);
$home = changeIntoBye($sum - $match[1], $participantsCount);
$away = changeIntoBye($match[1], $participantsCount);
$roundMatches[] = array($home, $away);
}
$matches = $roundMatches;
}
return $matches;
}
function changeIntoBye($seed, $participantsCount)
{
//return $seed <= $participantsCount ? $seed : sprintf('%d (= bye)', $seed);
return $seed <= $participantsCount ? $seed : null;
}
?>
The output:
Number of participants: 16
Number of rounds: 4
Bracket size: 16
Required number of byes: 0
C:\projects\draw\draw.php:7:
array (size=8)
0 =>
array (size=2)
0 => int 1
1 => int 16
1 =>
array (size=2)
0 => int 9
1 => int 8
2 =>
array (size=2)
0 => int 5
1 => int 12
3 =>
array (size=2)
0 => int 13
1 => int 4
4 =>
array (size=2)
0 => int 3
1 => int 14
5 =>
array (size=2)
0 => int 11
1 => int 6
6 =>
array (size=2)
0 => int 7
1 => int 10
7 =>
array (size=2)
0 => int 15
1 => int 2
If you change 16 into 6 you get:
Number of participants: 6
Number of rounds: 3
Bracket size: 8
Required number of byes: 2
C:\projects\draw\draw.php:7:
array (size=4)
0 =>
array (size=2)
0 => int 1
1 => null
1 =>
array (size=2)
0 => int 5
1 => int 4
2 =>
array (size=2)
0 => int 3
1 => int 6
3 =>
array (size=2)
0 => null
1 => int 2
# Here's one in python - it uses nested list comprehension to be succinct:
from math import log, ceil
def seed( n ):
""" returns list of n in standard tournament seed order
Note that n need not be a power of 2 - 'byes' are returned as zero
"""
ol = [1]
for i in range( ceil( log(n) / log(2) ) ):
l = 2*len(ol) + 1
ol = [e if e <= n else 0 for s in [[el, l-el] for el in ol] for e in s]
return ol
For JavaScript code, use one of the two functions below. The former embodies imperative style & is much faster. The latter is recursive & neater, but only applicable to relatively small number of teams (<16384).
// imperative style
function foo(n) {
const arr = new Array(n)
arr[0] = 0
for (let i = n >> 1, m = 1; i >= 1; i >>= 1, m = (m << 1) + 1) {
for (let j = n - i; j > 0; j -= i) {
arr[j] = m - arr[j -= i]
}
}
return arr
}
Here you fill in the spots one by one by mirroring already occupied ones. For example, the first-seeded team (that is number 0) goes to the topmost spot. The second one (1) occupies the opposite spot in the other half of the bracket. The third team (2) mirrors 1 in their half of the bracket & so on. Despite the nested loops, the algorithm has a linear time complexity depending on the number of teams.
Here is the recursive method:
// functional style
const foo = n =>
n === 1 ? [0] : foo(n >> 1).reduce((p, c) => [...p, c, n - c - 1], [])
Basically, you do the same mirroring as in the previous function, but recursively:
For n = 1 team, it's just [0].
For n = 2 teams, you apply this function to the argument n-1 (that is,
1) & get [0]. Then you double the array by inserting mirrored
elements between them at even positions. Thus, [0] becomes [0, 1].
For n = 4 teams, you do the same operation, so [0, 1] becomes [0, 3,
1, 2].
If you want to get human-readable output, increase each element of the resulting array by one:
const readableArr = arr.map(i => i + 1)
At each round sort teams by seeding criteria
(If there are n teams in a round)team at ith position plays with team n-i+1
Since this comes up when searching on the subject, and it's hopeless to find another answer that solves the problem AND puts the seeds in a "prettier" order, I will add my version of the PHP code from darkangel. I also added the possibility to give byes to the higher seed players.
This was coded in an OO environment, so the number of participants are in $this->finalists and the number of byes are in $this->byes. I have only tested the code without byes and with two byes.
public function getBracket() {
$players = range(1, $this->finalists);
for ($i = 0; $i < log($this->finalists / 2, 2); $i++) {
$out = array();
$reverse = false;
foreach ($players as $player) {
$splice = pow(2, $i);
if ($reverse) {
$out = array_merge($out, array_splice($players, -$splice));
$out = array_merge($out, array_splice($players, 0, $splice));
$reverse = false;
} else {
$out = array_merge($out, array_splice($players, 0, $splice));
$out = array_merge($out, array_splice($players, -$splice));
$reverse = true;
}
}
$players = $out;
}
if ($this->byes) {
for ($i = 0; $i < $this->byes; $i++ ) {
for ($j = (($this->finalists / pow(2, $i)) - 1); $j > 0; $j--) {
$newPlace = ($this->finalists / pow(2, $i)) - 1;
if ($players[$j] > ($this->finalists / (pow(2 ,($i + 1))))) {
$player = $players[$j];
unset($players[$j]);
array_splice($players, $newPlace, 0, $player);
}
}
}
for ($i = 0; $i < $this->finalists / (pow(2, $this->byes)); $i++ ) {
$swap[] = $players[$i];
}
for ($i = 0; $i < $this->finalists /(pow(2, $this->byes)); $i++ ) {
$players[$i] = $swap[count($swap) - 1 - $i];
}
return array_reverse($players);
}
return $players;
}
I worked on a PHP / Laravel plugin that generates brackets with / without preliminary round robin. Maybe it can be useful to you, I don't know what tech you are using. Here is the github.
https://github.com/xoco70/kendo-tournaments
Hope it helps!
A C version.
int * pctournamentSeedArray(int PlayerCnt)
{
int * Array;
int * PrevArray;
int i;
Array = meAlloc(sizeof(int) * PlayerCnt);
if (PlayerCnt == 2)
{
Array[0] = 0;
Array[1] = 1;
return Array;
}
PrevArray = pctournamentSeedArray(PlayerCnt / 2);
for (i = 0; i < PlayerCnt;i += 2)
{
Array[i] = PrevArray[i / 2];
Array[i + 1] = (PlayerCnt - 1) - Array[i] ;
}
meFree(PrevArray);
return Array;
}

Resources