2

I am trying to implement an efficient group by alogrithm from Most efficient method to groupby on an array of objects and trying to put the types. But I get the error:

T[keyof T] cannot be used to index {}

Here is my attempt

 static groupBy<T>(xs:T[], key: keyof T) {
        return xs.reduce((rv, x)=> {
            (rv[x[key]] = rv[x[key]] || []).push(x);
            return rv;
        }, {});
    };
TSR
  • 17,242
  • 27
  • 93
  • 197

3 Answers3

0

rv: any rv will be any.

class A {
  static groupBy<T>(xs: T[], key: keyof T) {
    return xs.reduce((rv: any, x) => {
      (rv[x[key]] = rv[x[key]] || []).push(x);
      return rv;
    }, {});
  }
}

How to use it:

interface Person {
  name: string;
  age: number;
  salary: number;
}
const data: Person[] = [
  {name:"deepak", age: 30, salary: 2000},
  {name:"deepak1", age: 32, salary: 1000},
  {name:"deepak", age: 29, salary: 3000}
]
class A {
  static groupBy<T>(xs: T[], key: keyof T) {
    return xs.reduce((rv: any, x) => {
      (rv[x[key]] = rv[x[key]] || []).push(x);
      return rv;
    }, {});
  }
}
console.log(A.groupBy(data, "name"))

Defination from Lodash:

groupBy(
            predicate?: Lodash.ListIterator<T, boolean> | Lodash.DictionaryIterator<T, boolean> | string,
            thisArg?: any,
        ): Lodash.Dictionary<T[]>;
        groupBy<R extends {}>(predicate?: R): Lodash.Dictionary<T[]>;

Sync group return an object and object cant have any other element as key other than string|number. Else you can follow more genric solution.

interface Any<T> {
  [key: string]: T[];
}
interface SMap<T> {
  [key: string]: T;
}
class A {
  static groupBy<T extends SMap<string>>(xs: T[], key: keyof T) {
    return xs.reduce((rv: Any<T>, x) => {
      if (!rv[x[key]]) {
        rv[x[key]] = [];
      }
      rv[x[key]].push(x);
      return rv;
    }, {});
  }
}
xdeepakv
  • 7,835
  • 2
  • 22
  • 32
  • it doesn't define type correctly `const result2 = A.groupBy([{ test: 'key' }], 'test');` `result2.key2.length; // possible, but should be an error.` – satanTime May 12 '20 at 16:11
  • "_object cant have any other element as key other than string|number_" Objects can have [Symbol](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Symbol) as key. – Zuckerberg May 12 '20 at 16:12
  • i did some search, i think first sample is the correct way to do it.Even lodash is using `any` . please check.. updated. – xdeepakv May 12 '20 at 16:40
0

if you don't want to use any, you need properly define types to tell that reducer can combine data.

function groupBy<T extends {[LK in K]: keyof any}, K extends keyof T>(xs:T[], key: K) {
  return xs.reduce<{[LK2 in T[K]]?: Array<T>}>((rv, x) => {
            (rv[x[key]] = rv[x[key]] || [])?.push(x);
            return rv;
        }, {});
};

const result = groupBy([{ test: 'key' }], 'test');

result.key?.length; // 1

T is an object where type of passed key is something that can be used as a key (for rv).

for reducer - it starts with an empty object - we need to says that result will be an object where value of the key is an array of entities from the xs {[LK2 in T[K]]?: Array<T>}

satanTime
  • 12,631
  • 1
  • 25
  • 73
0

Not as short as the original. However, it does not use "any" or "as" casting. It also supports any data type for the group key, hence, unknown.

export function groupBy<T>(xs: T[], key: keyof T): Map<unknown, T[]> {
  return xs.reduce((rv: Map<unknown, T[]>, entity: T) => {
    const value = entity[key];
    if (rv.has(value)) {
      rv.get(value)?.push(entity)
    } else {
      rv.set(value, [entity]);
    }
    return rv;
  }, new Map());
};

example of usage:

const badgesByTypes = groupBy(currentState.badges, 'type');
for (const [key, values] of badgesByTypes.entries()) {
}
Leblanc Meneses
  • 3,001
  • 1
  • 23
  • 26