пaean
пaean

Reputation: 71

RxJS: using groupBy and creating key/value pairs from values

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

Answers (2)

Barremian
Barremian

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

StPaulis
StPaulis

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

Related Questions