0

This is how my array looks like: and my array is not type of string, it's type of it own object called MyObject (just like setter and getter objects in java)

["Car","model","year","color","price"]
["Table","model","year","color","price"]
["Car","model","year","color","price"]
["Car","model","year","color","price"]
["Laptop","model","year","color","price"]
["Laptop","model","year","color","price"]

now I want to group by this in typescript and count how many of that item exist in array (also like in sql)

name  |count
 Car  | 3
Laptop| 2
Table | 1

and in my typescript file I have this

private groupByObjects() {
  //this read all data in array allData[] from service
  this.dataService.retrieveData().subscribe(allData => {

});

}

Could anyone please help me to write this in typescript?

5 Answers5

1

I tried a lot and can write the answer which is works for me perfectly. So I post it now and hope it helps anyone who has the same problem as mine.

private groupByElement(receivedData: ReceivedData, elements: Array<GroupByElement>) {
    let groupElement: GroupByElement = new GroupByElement;

    if (!elements.find(x => x.element== receivedData.element)) {
      groupElement.element= receivedData.element;
      groupElement.count = 1;
      elements.push(groupElement);
    } else {
      this.updateElementCounter = elements.find(x => x.element== receivedData.element)?.count;
      this.updateElementCounter! += 1;
      this.indexElementCount = elements.findIndex(x => x.element== receivedData.element);

      elements[this.indexElementCount].count = this.updateElementCounter!;
    }
  }
ouflak
  • 2,458
  • 10
  • 44
  • 49
0

You can do this in O(n) through a simple reduction

In your case,

type ApplyFunction<X, T, R> = (all: X, current: T) => R
type GetFunction<T,R> = (t: T) => R

const groupAndApplyByIndex = <T, V extends string | number, R>(data: Array<T>, get: GetFunction<T,V>, apply: ApplyFunction<Record<V, R>, V, R>) => {
  return data.reduce((all, element) => {
    return {
      ...all,
      [get(element)]: apply(all, get(element))
    }
  }, {} as Record<V, R>)
}

And just use the above function in your code by

private groupByObjects() {
  //this read all data in array allData[] from service
  this.dataService.retrieveData().subscribe(allData => {

    const getX: GetFunction<string[], string> = (t) => t[0]
    const counts = groupAndApplyByIndex(allData, getX, (all, element) => ((all[element] as number || 0) + 1))

    // Rest of your logic...
  });

By using the above method, you have a function which can count or do other transformations that you define through apply

TypeScript Playground

Urmzd
  • 682
  • 4
  • 17
  • I need typescript not javascript – SpacemanSps Dec 01 '21 at 11:13
  • you are correct but there is one problem, my array is not type of `string` it's` myObject` but when I change the code it gives me error that it can't be anything except `string` or `number` – SpacemanSps Dec 01 '21 at 13:27
  • This information is not specified in the question. What is the type of `myObject` @SpacemanSps – Urmzd Dec 01 '21 at 13:48
  • it's a class that contain data like in java set and get class, so all data that read will be assign to this class because the input data I get is Json and that works pretty good except here – SpacemanSps Dec 01 '21 at 13:53
  • Can you show the class responsible? Are set and get always present? I need a [MRE](https://stackoverflow.com/help/minimal-reproducible-example) to help :) – Urmzd Dec 01 '21 at 14:37
  • @SpacemanSps Look at the provided answer. It has been updated to reflect the usage of any object. – Urmzd Dec 01 '21 at 22:46
0

I like the approach of a list reducer using a function that return another function. That way you can always specify which key to use, in your case this key is 0.

interface PropObject {
  [index: string]: number;
}

const groupByCounter = (key : number) => (result : PropObject ,current : string []) => {

  result[current[key]] = result[current[key]] ? result[current[key]] + 1 : 1;
  return result;
};

const data : string [][] = [["Car","model","year","color","price"],
["Table","model","year","color","price"],
["Car","model","year","color","price"],
["Car","model","year","color","price"],
["Laptop","model","year","color","price"],
["Laptop","model","year","color","price"]];

const group = data.reduce(groupByCounter(0),{});
console.log(group);

Link to typescript playground.

Check out the javascript version

David Lemon
  • 1,560
  • 10
  • 21
  • Some of this code is redundant (checking if the type is "undefined"). Undefined values are falsy by nature, so `result[key]` suffices. Also the `typeof` operator is redundant. If you want to explicitly checks if undefined, `result[key] == undefined` suffices. – Urmzd Nov 30 '21 at 15:34
  • @Urmzd I have updated the code – David Lemon Nov 30 '21 at 15:42
  • @DavidLemon could you please help me to fix this problem? this is the error I get `No overload matches this call. Argument of type '(result: PropObject, current: string[]) => PropObject' is not assignable to parameter of type '(previousValue: allData, currentValue: allData, currentIndex: number, array: allData[]) => allData'. Types of parameters 'result' and 'previousValue' are incompatible. Type 'allData' is not assignable to type 'PropObject'. Index signature for type 'string' is missing in type 'allData'.` my array is my object type `allData: myObject[][]` – SpacemanSps Dec 01 '21 at 11:18
  • This code is also very dangerous. Mutating objects directly is never recommended.. – Urmzd Dec 01 '21 at 14:39
0

Why not use lodash ?

u can get this with only 2 lines :

private groupByObjects() {
  //this read all data in array allData[] from service
  this.dataService.retrieveData().subscribe(allData => {
  const counts = _.countBy(allData, '[0]');
  const data = _.unionBy(allData, '[0]');
  console.log(counts, data);
});

do not forget to install and import lodash :

npm install -s lodash

import * as _ from 'lodash';
Piva Gregory
  • 442
  • 2
  • 13
0

Given that you are already using rxjs, I would stay using that. I don't know if you'll think it's the best answer, and I didn't test it so you might have to debug, but I find this solution fun.

type Item = [string, string, string, string, string]; // you should make this tighter
type ItemCount = [string, number];

const source$: Observable<Item[]> = of([
  ["Car","model","year","color","price"],
  ["Table","model","year","color","price"],
  ["Car","model","year","color","price"],
  ["Car","model","year","color","price"],
  ["Laptop","model","year","color","price"],
  ["Laptop","model","year","color","price"]
]); // mock of this.dataService.retrieveData()

source$.pipe(
  mergeMap(res => from(res)), // emit each item one at a time
  groupBy((item: Item) => item[0]),
  mergeMap((item$: GroupedObservable<Item>) => item$.pipe( // item$ is an observable of items were each item's first element equals item$.key
    count(), // wait until complete, then emit count
    map<number, ItemCount>(c => [item$.key, c]) // turn count into tuple of [key, count]
  )),
  toArray(), // Wait until each item$ has been converted, then emit the final ItemCount[]
  map((itemCounts: ItemCount[]) => new Map<string, number>(itemCounts)) // you could do Object.fromEntries(itemCounts) instead. I like map objects
).subscribe(mapOfCountsPerKey => {
    // whatever you want to do
});
JSmart523
  • 2,069
  • 1
  • 7
  • 17