Reputation: 71
Let's say I have this array of observables:
animals$ = from([
{ animal: 'rat', color: 'grey', speed: 2 },
{ animal: 'rat', color: 'black', speed: 3 },
{ animal: 'dog', color: 'grey', speed: 2 },
{ animal: 'dog', color: 'black', speed: 1 },
{ animal: 'cat', color: 'grey', speed: 5 },
{ animal: 'cat', color: 'black', speed: 1 },
]);
I want to get an observable array of the following format, where the results have been grouped by animal type, sorted alphabetically by animal, and the color values are transformed to keys for speed values:
[
{ animal: 'cat', grey: 5, black: 1 },
{ animal: 'dog', grey: 2, black: 1 },
{ animal: 'rat', grey: 1, black: 3 },
]
Is it possible by using groupBy? Most examples I have found so far are quite different, where in the results are in an array rather than combined to make key/value pairs.
Upvotes: 0
Views: 636
Reputation: 31105
IMO better fit for this scenario would be the RxJS reduce
operator + Array#findIndex
function to group the objects and RxJS map
operator + Array#sort
function to sort the elements of the array.
Try the following
const { from } = rxjs;
const { map, reduce } = rxjs.operators;
const animals$ = from([ { animal: 'rat', color: 'grey', speed: 2 }, { animal: 'rat', color: 'black', speed: 3 }, { animal: 'dog', color: 'grey', speed: 2 }, { animal: 'dog', color: 'black', speed: 1 }, { animal: 'cat', color: 'grey', speed: 5 }, { animal: 'cat', color: 'black', speed: 1 }, ]);
animals$.pipe(
reduce((acc, curr) => {
const idx = acc.findIndex((item) => item.animal === curr.animal);
if (idx !== -1) {
acc[idx] = {
...acc[idx],
[curr.color]: curr.speed,
};
} else {
acc = [
...acc,
{
animal: curr.animal,
[curr.color]: curr.speed,
},
];
}
return acc;
}, []),
map((animals) =>
animals.sort((a1, a2) =>
a1.animal > a2.animal ? 1 : a1.animal < a2.animal ? -1 : 0
)
)
).subscribe(console.log);
.as-console-wrapper { max-height: 100% !important; top: 0px }
<script src="https://cdnjs.cloudflare.com/ajax/libs/rxjs/7.4.0/rxjs.umd.min.js"></script>
Working example: Stackblitz
Upvotes: 3
Reputation: 2916
Another solution would be the following:
of([
{ animal: 'rat', color: 'grey', speed: 2 },
{ animal: 'rat', color: 'black', speed: 3 },
{ animal: 'dog', color: 'grey', speed: 2 },
{ animal: 'dog', color: 'black', speed: 1 },
{ animal: 'cat', color: 'grey', speed: 5 },
{ animal: 'cat', color: 'black', speed: 1 },
])
.pipe(
map((result) => {
let res: { animal: string; grey: number; black: number }[] = [];
const animals = new Set(result.map((x) => x.animal));
animals.forEach((animal) => {
const grey: number = result
.filter((x) => x.animal === animal && x.color === 'grey')
.map((x) => x.speed)
.reduce((x, y) => x + y);
const black = result
.filter((x) => x.animal === animal && x.color === 'black')
.map((x) => x.speed)
.reduce((x, y) => x + y);
res.push({
animal,
grey,
black,
});
});
return res;
})
)
Working example: Stackblitz
Upvotes: -1