UNPKG

remeda

Version:

A utility library for JavaScript and Typescript.

1 lines 6.79 kB
{"version":3,"file":"groupBy.cjs","names":["purry","output: BoundedPartial<Record<Key, NonEmptyArray<T>>>"],"sources":["../src/groupBy.ts"],"sourcesContent":["import type { BoundedPartial } from \"./internal/types/BoundedPartial\";\nimport type { NonEmptyArray } from \"./internal/types/NonEmptyArray\";\nimport { purry } from \"./purry\";\n\n/**\n * Groups the elements of a given iterable according to the string values\n * returned by a provided callback function. The returned object has separate\n * properties for each group, containing arrays with the elements in the group.\n * Unlike the built in `Object.groupBy` this function also allows the callback to\n * return `undefined` in order to exclude the item from being added to any\n * group.\n *\n * If you are grouping objects by a property of theirs (e.g.\n * `groupBy(data, ({ myProp }) => myProp)` or `groupBy(data, prop('myProp'))`)\n * consider using `groupByProp` (e.g. `groupByProp(data, 'myProp')`) instead,\n * as it would provide better typing.\n *\n * @param data - The items to group.\n * @param callbackfn - A function to execute for each element in the iterable.\n * It should return a value indicating the group of the current element, or\n * `undefined` when the item should be excluded from any group.\n * @returns An object with properties for all groups, each assigned to an array\n * containing the elements of the associated group.\n * @signature\n * R.groupBy(data, callbackfn)\n * @example\n * R.groupBy([{a: 'cat'}, {a: 'dog'}] as const, R.prop('a')) // => {cat: [{a: 'cat'}], dog: [{a: 'dog'}]}\n * R.groupBy([0, 1], x => x % 2 === 0 ? 'even' : undefined) // => {even: [0]}\n * @dataFirst\n * @category Array\n */\nexport function groupBy<T, Key extends PropertyKey = PropertyKey>(\n data: ReadonlyArray<T>,\n callbackfn: (\n value: T,\n index: number,\n data: ReadonlyArray<T>,\n ) => Key | undefined,\n): BoundedPartial<Record<Key, NonEmptyArray<T>>>;\n\n/**\n * Groups the elements of a given iterable according to the string values\n * returned by a provided callback function. The returned object has separate\n * properties for each group, containing arrays with the elements in the group.\n * Unlike the built in `Object.groupBy` this function also allows the callback to\n * return `undefined` in order to exclude the item from being added to any\n * group.\n *\n * If you are grouping objects by a property of theirs (e.g.\n * `groupBy(data, ({ myProp }) => myProp)` or `groupBy(data, prop('myProp'))`)\n * consider using `groupByProp` (e.g. `groupByProp(data, 'myProp')`) instead,\n * as it would provide better typing.\n *\n * @param callbackfn - A function to execute for each element in the iterable.\n * It should return a value indicating the group of the current element, or\n * `undefined` when the item should be excluded from any group.\n * @returns An object with properties for all groups, each assigned to an array\n * containing the elements of the associated group.\n * @signature\n * R.groupBy(callbackfn)(data);\n * @example\n * R.pipe(\n * [{a: 'cat'}, {a: 'dog'}] as const,\n * R.groupBy(R.prop('a')),\n * ); // => {cat: [{a: 'cat'}], dog: [{a: 'dog'}]}\n * R.pipe(\n * [0, 1],\n * R.groupBy(x => x % 2 === 0 ? 'even' : undefined),\n * ); // => {even: [0]}\n * @dataLast\n * @category Array\n */\nexport function groupBy<T, Key extends PropertyKey = PropertyKey>(\n callbackfn: (\n value: T,\n index: number,\n data: ReadonlyArray<T>,\n ) => Key | undefined,\n): (items: ReadonlyArray<T>) => BoundedPartial<Record<Key, NonEmptyArray<T>>>;\n\nexport function groupBy(...args: ReadonlyArray<unknown>): unknown {\n return purry(groupByImplementation, args);\n}\n\nconst groupByImplementation = <T, Key extends PropertyKey = PropertyKey>(\n data: ReadonlyArray<T>,\n callbackfn: (\n value: T,\n index: number,\n data: ReadonlyArray<T>,\n ) => Key | undefined,\n): BoundedPartial<Record<Key, NonEmptyArray<T>>> => {\n // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- Using Object.create(null) allows us to remove everything from the prototype chain, leaving it as a pure object that only has the keys *we* add to it. This prevents issues like the one raised in #1046\n const output: BoundedPartial<Record<Key, NonEmptyArray<T>>> =\n Object.create(null);\n\n for (let index = 0; index < data.length; index++) {\n // Accessing the object directly instead of via an iterator on the `entries` showed significant performance benefits while benchmarking.\n const item = data[index];\n\n // @ts-expect-error [ts2345] -- TypeScript is not able to infer that the index wouldn't overflow the array and that it shouldn't add `undefined` to the type. We don't want to use the `!` operator here because it's semantics are different because it changes the type of `item` to `NonNullable<T>` which is inaccurate because T itself could have `undefined` as a valid value.\n const key = callbackfn(item, index, data);\n if (key !== undefined) {\n // Once the prototype chain is fixed, it is safe to access the prop directly without needing to check existence or types.\n const items = output[key];\n\n if (items === undefined) {\n // It is more performant to create a 1-element array over creating an empty array and falling through to a unified the push. It is also more performant to mutate the existing object over using spread to continually create new objects on every unique key.\n // @ts-expect-error [ts2322] -- In addition to the typing issue we have for `item`, this line also creates a typing issue for the whole object, as TypeScript is having a hard time inferring what values could be adding to the object.\n output[key] = [item];\n } else {\n // It is more performant to add the items to an existing array over continually creating a new array every time we add an item to it.\n // @ts-expect-error [ts2345] -- See comment above about the effective typing for `item` here.\n items.push(item);\n }\n }\n }\n\n // Set the prototype as if we initialized our object as a normal object (e.g. `{}`). Without this none of the built-in object methods like `toString` would work on this object and it would act differently than expected.\n Object.setPrototypeOf(output, Object.prototype);\n\n return output;\n};\n"],"mappings":"wCAgFA,SAAgB,EAAQ,GAAG,EAAuC,CAChE,OAAOA,EAAAA,EAAM,EAAuB,EAAK,CAG3C,MAAM,GACJ,EACA,IAKkD,CAElD,IAAMC,EACJ,OAAO,OAAO,KAAK,CAErB,IAAK,IAAI,EAAQ,EAAG,EAAQ,EAAK,OAAQ,IAAS,CAEhD,IAAM,EAAO,EAAK,GAGZ,EAAM,EAAW,EAAM,EAAO,EAAK,CACzC,GAAI,IAAQ,IAAA,GAAW,CAErB,IAAM,EAAQ,EAAO,GAEjB,IAAU,IAAA,GAGZ,EAAO,GAAO,CAAC,EAAK,CAIpB,EAAM,KAAK,EAAK,EAQtB,OAFA,OAAO,eAAe,EAAQ,OAAO,UAAU,CAExC"}