Home > Software design >  How to do projection in TypeScript?
How to do projection in TypeScript?

Time:11-07

I want to populate orders which is an array of type Order. The expected result is orders=[{id:1,qt:4},{id:2, qt:2},{id:3,qt:2}]. How to do so in TypeScript? I am new to it.

export class Product {
  constructor(public id: number, public name: string, public price: number) {}
}
export interface Order {
  id: number;
  qt: number;
}

export const products: Product[] = [
  new Product(1, 'Apple', 2.1),
  new Product(2, 'Banana', 2.2),
  new Product(3, 'Chocolate', 2.3),
  new Product(4, 'Dessert', 2.4),
];

export const cart: Product[] = [
  products[0],
  products[0],
  products[2],
  products[1],
  products[2],
  products[0],
  products[1],
  products[0],
];

export const orders: Order[] = [];

Edit

For those who want to know how orders=[{id:1,qt:4},{id:2, qt:2},{id:3,qt:2}] is obtained.

In the cart:

  • the quantity of apples (id:1) is qt:4
  • the quantity of bananas (id:2) is qt:2
  • the quantity of chocolates (id:3) is qt:2

So by using cart, I have to obtain orders=[{id:1,qt:4},{id:2, qt:2},{id:3,qt:2}]. It should be clear.

CodePudding user response:

Since you're looking for a "LINQ-like" solution, you probably want to use the higher order functions like map/filter/reduce.

Strictly, your problem cannot be solved purely with LINQ projections. Those merely represent map (Select), concatMap/flatMap (SelectMany), and zip (Zip). Your problem involves counting the occurences of each id throughout the entire array.

Pretty much every data manipulation problem can be solved with higher order folds, i.e reduce in javascript land, Aggregate in C# land. This one is no exception. The first thing to do, is to count the occurrences of each id, and build a counter object.

cart.reduce((acc, { id }) => {
    acc[id] = (acc[id] ?? 0)   1;
    return acc;
}, {} as Record<number, number>);

Essentially, you start the fold operation with an empty object, then add each id and its occurrence count. Every time an id is encountered in the cart array, you increment its count in the object. If the id doesn't already exist in the accumulating object, nullish coalescing (acc[id] ?? 0) uses 0 and increments that instead.

This will give you-

{ '1': 4, '2': 2, '3': 2 }

Now, you need to turn this into-

[ { id: 1, qt: 4 }, { id: 2, qt: 2 }, { id: 3, qt: 2 } ]

For that, use Object.entries on the fold result to get-

> Object.entries({ '1': 4, '2': 2, '3': 2 })
[ [ '1', 4 ], [ '2', 2 ], [ '3', 2 ] ]

Finally, a simple map is all you need-

Object.entries(...).map(([id, qt]) => ({ id: Number(id), qt }))

Combining all that, you have-

export const orders: Order[] = Object.entries(
    cart.reduce((acc, { id }) => {
        acc[id] = (acc[id] ?? 0)   1;
        return acc;
    }, {} as Record<number, number>)
).map(([id, qt]) => ({ id: Number(id), qt }));

One thing to note here is that Object.entries is pretty inefficient since it builds up an array instead of an iterator. If you're into efficiency, roll an iterator version of Object.entries and use that instead, using generator functions-

function* objEntries<T>(x: Record<string, T>): IterableIterator<[string, T]> {
  for (const k in x) {
    yield [k, x[k]];
  }
}
  • Related