RXJS - merge two stream results into one sorted array - javascript

I have two streams. Let's say:
const firstStream = Rx.of([
{
first: 'first',
}, {
third: 'third',
}
]);
const secondStream = Rx.of([
{
second: 'second'
}, {
fourth: 'fourth'
}
]);
Now I want a stream that combines the result of these two streams and maps the result array sorted as follows:
const resultArr = [
{
first: 'first',
},
{
second: 'second'
},
{
third: 'third',
},
{
fourth: 'fourth'
}
];
I tried to use combineLatest with RxJS flatmap operator, but that did not work out. I provided a stackblitz playground to test around: StackBlitz
I'm sure there are plenty of ways to do this. Maybe someone can help me out :)

const { from, merge } = rxjs;
const { reduce, map, mergeMap } = rxjs.operators
const a = from(['first', 'third']);
const b = from(['second', 'fourth']);
const sortMap = {
first: 0,
second: 1,
third: 2,
fourth: 4,
}
merge(a, b).pipe(
// wait until every observable has completed,
// zip all the values into an array
reduce((res, item) => res.concat(item), []),
// sort the array accordingly to your needs
map(list => list.sort((a, b) => sortMap[a] - sortMap[b])),
// flatten the array into a sequence
mergeMap(list => list),
).subscribe(console.log);
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.js" integrity="sha256-mNXCdYv896VtdKYTBWgurbyH+p9uDUgWE4sYjRnB5dM=" crossorigin="anonymous"></script>

enter code hereAs you said your streams first complete and after that, you need the sorted value as single output of the stream, so I would recommend the forkJoin operator, which operator will Wait for Observables to complete and then combine last values they emitted.
const { of, forkJoin } = rxjs;
const { map } = rxjs.operators;
let a$ = of([1, 8, 10, 4]);
let b$ = of([3, 5, 43, 0]);
forkJoin(a$, b$)
.pipe(
map(([a, b]) => [...a, ...b]),
map(x => x.sort((a, b) => a - b))
)
.subscribe(x => {
console.log('Sorted =>', x);
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/6.5.2/rxjs.umd.js" integrity="sha256-mNXCdYv896VtdKYTBWgurbyH+p9uDUgWE4sYjRnB5dM=" crossorigin="anonymous"></script>

Related

Passing an array to callback

how would you solve this: Update the transform function such that it works with n number elements and also same function works for string element ?
const input = [
[2,3,5],
[2,4],
[7,8,9]
];
/*Edit only the transform */
const transform = (input, callback) => {
return callback([input[0],input[1]]);
}
/*Edit code only above */
const output = transform(input, (elm1, elm2) => {
return elm1.concat(elm2);// should return [7,8,9,2,4,2,3,5]
});
const input2 = ["hello", "welcome", !];
const output2 = transform(input2, (elm) => {
return elm.toUppercase(); // should return HELLO WELCOME !
});
Thank you all
In my opinion you're not writing clear JavaScript code. Functional paradigms are your friend but units of work that aren't meaningful will only work against you. What benefit does your transform function provide, why is it better than calling the cb method directly on your data? Consider looking into es6 array functions like flatMap and reduce.
const input = [
[2,3,5],
[2,4],
[7,8,9]
]
console.log(input.reduceRight((acc,cur) => acc.concat(cur), []))
// [7, 8, 9, 2, 4, 2, 3, 5]
console.log(["hello", "world"].map(str => str.toUpperCase()).join(" "))
//"HELLO WORLD"
You would need to pass the whole array to your transform function.
For the first output => flat the arrays using flat(Infinity)
For the second output => 'merge' all array value using join and then apply toUpperCase to the whole string:
const input = [
[2,3,5],
[2,4],
[7,8,9]
];
const transform = (input, callback) => {
return callback(input);
}
const output = transform(input.reverse(), (elm1, elm2) => {
return elm1.concat(elm2); // should return [7,8,9,2,4,2,3,5]
});
console.log(output.flat(Infinity).filter(Boolean));
const input2 = ["hello", " welcome", " !"];
const output2 = transform(input2, (elm) => {
return elm.join('').toUpperCase(); // should return HELLO WELCOME !
});
console.log(output2);

lodash generate deep object with collection data

Hi suppose i have access to those data ref
const GlobalDataA = { uid0: {}, uid1: {}, uid2: {} };
const GlobalDataB = { uid3: {}, uid4: {}, uid5: {} };
const mapperA = ['uid0', 'uid1'];
const mapperB = ['uid3', 'uid4'];
const range = 4;
how i can easily with Lodash generate a new collections object with keys uid like this
{
uid0: { uid3: [0, 1, 2, 3], uid4: [0, 1, 2, 3] },
uid1: { uid3: [0, 1, 2, 3], uid4: [0, 1, 2, 3] },
}
i know how proceed to generate a collections array
const test = mapperA.map((A) => mapperB.map((B) => _.range(range).map((value, i) => i)));
it will give me this
but my target is to generate a new object with uid key like this
ideally a 1 or 2 line formula to stay clean will be great !
I try pick and zipObject from loadash without success !
thanks
Return pairs of [key, value] from the _.map() calls, and convert to objects using _.fromPairs():
const mapperA = ['uid0', 'uid1'];
const mapperB = ['uid3', 'uid4'];
const range = 4;
const result = _.fromPairs(mapperA.map((A) =>
[
A,
_.fromPairs(mapperB.map((B) => [
B,
_.range(range)
]))
])
);
console.log(result)
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.20/lodash.min.js" integrity="sha512-90vH1Z83AJY9DmlWa8WkjkV79yfS2n2Oxhsi2dZbIv0nC4E6m5AbH8Nh156kkM7JePmqD6tcZsfad1ueoaovww==" crossorigin="anonymous"></script>
With vanilla JS, you can replace _.fromPairs() with Object.fromEntries(), and generate the range with Array.from():
const mapperA = ['uid0', 'uid1'];
const mapperB = ['uid3', 'uid4'];
const range = 4;
const result = Object.fromEntries(mapperA.map((A) =>
[
A,
Object.fromEntries(mapperB.map((B) => [
B,
Array.from({ length: range }, (_, i) => i)
]))
])
);
console.log(result)

Convert array of observables of observables to array of observables rxjs

To simplify the problem i have used numbers and strings here. The code:
const numbers$:Observable<number[]> = of([1,2,3]);
const strings: string[] = ["a","b"];
function getStrings(): Observable<string>[]{
return numbers$.pipe(
map((numbers: number[]) => {
const strings$: Observable<string>[] = strings.map(s => of(s));
return strings$;
}),
)
}
getStrings().subscribe(x => console.log(x))
The error i am getting is:
Type 'Observable<Observable<string>[]>' is missing the following properties from type 'Observable<string>[]
How can i get Observable<string>[] from getStrings function? I have tried to use flatMap, switchMap but unable to get the perfect combination.
Stackblitz
You'll need to use mergeMap() and forkJoin():
function getStrings(): Observable<string[]>{
return numbers$.pipe(
mergeMap((numbers: number[]) => {
const strings$: Observable<string>[] = strings.map(s => of(s));
return forkJoin(strings$);
}),
)
}
getStrings().subscribe(x => console.log(x))
https://stackblitz.com/edit/rxjs-dzvsbh?file=index.ts
So as I understand you want to "zip" two lists together, via observable ?
I can offer you this one
const numbers$: Observable<number[]> = of([1, 2, 3]);
const strings$: Observable<string[]> = of(['a', 'b']);
const combined$: Observable<any> = zip(numbers$, strings$)
.pipe(
map(([numbers, strings]) =>
numbers.length > strings.length ?
numbers.map((value, index) => [value, strings[index]]) :
strings.map((value, index) => [numbers[index], value]))
);
combined$.subscribe(value => console.log(value));
This will log:
[
[
1,
"a"
],
[
2,
"b"
],
[
3,
null
]
]

mapping one object to another using some functional programming

I would like to map one array of object into another in a more functional style, I am using typescript.
Basically I am using delete to remove a property on a object, I would like to know if there is a better way to write it.
const data = props.data.map(d => ({
order: d.position,
logs: d.batches.map(b => {
let log= {
amount: b.scrap,
batchNumber: '', // NO GOOD
}
if (!b.batch || b.batch.length === 0) {
delete log.batchNumber // NO GOOD
}
return log
}),
}))
example input data:
const data = [
position: 1,
batches: [
{batchNumber: '', ammount: 3}
]
]
result:
const data = [{
order: 1,
logs:[ {ammount:3}]
}
]
You can do another map on the batches to return a new array of objects, and attach that to your returned object instead:
const out = data.map(({ position: order, batches }) => {
const logs = batches.map(({ batchNumber, ammount }) => {
if (batchNumber) return { batchNumber, ammount };
return { ammount };
});
return { order, logs }
});
DEMO
One approach would be to make a shallow copy of the target omitting keys you want to delete, for example:
let drop = key => obj => Object.keys(obj).reduce((r, k) =>
k === key ? r : {...r, [k]: obj[k]}, {});
let test = [
{foo:11, bar:2, baz: 3},
{foo:22, bar:2, baz: 3},
{foo:33, bar:2, baz: 3},
];
console.log(test.map(drop('bar')));
To add another option to the mix: it is possible to use Object.assign to optionally assign the property:
const data = [{
position: 1,
batches: [{batchNumber: '',ammount: 3}, {batchNumber: 'withNr',ammount: 4}]
}];
const res = data.map(d =>
({
order: d.position,
logs : d.batches.map(({ammount, batchNumber}) => Object.assign({ammount}, batchNumber ? {batchNumber} : null ))
})
);
console.log(res);

Loop over object es6

I have an object which looks like this:
const object = {
head: 1,
eyes: 2,
arms: 2,
legs: 3
}
I want to loop over this object and this and log out each key name e.g. eyes for the amount of the value.
this would result in:
head
eyes
eyes
arms
arms
legs
legs
legs
Currently I have this solution but it feels like it could be done more neatly and readible.
Object.keys(object)
.map(key => {
return [...Array(object[key])].map( (_, i) => {
return console.log(key)
})
Any suggestions?
You could use Object.entries() and map() method and return new array.
const object = {head: 1,eyes: 2,arms: 2,legs: 3}
const res = [].concat(...Object.entries(object).map(([k, v]) => Array(v).fill(k)))
console.log(res)
Or you could use reduce() with spread syntax in array.
const object = {head: 1,eyes: 2,arms: 2,legs: 3}
const res = Object
.entries(object)
.reduce((r, [k, v]) => [...r, ...Array(v).fill(k)], [])
// Or you can use push instead
// .reduce((r, [k, v]) => (r.push(...Array(v).fill(k)), r), [])
console.log(res)
Object.entries(object)
.forEach(([key, times]) => console.log((key + "\n").repeat(times)));
One may use String.prototype.repeat...
"...it feels like it could be done more neatly and readible."
Recursion makes it pretty clean and understandable.
const object = {
head: 1,
eyes: 2,
arms: 2,
legs: 3
};
Object.entries(object).forEach(function f([k,v]) {
if (v) {
console.log(k);
f([k, --v]);
}
})
You can rearrange things a bit if you know the value will always be greater than 0.
const object = {
head: 1,
eyes: 2,
arms: 2,
legs: 3
};
Object.entries(object).forEach(function f([k,v]) {
console.log(k);
if (--v) f([k, v]);
})

Categories

Resources