groupByMulti
Groups items into multiple buckets based on multiple keys per item. Each item can appear in multiple groups if the key extractor returns an array.
1/**
2 * Groups items into multiple buckets based on multiple keys per item.
3 * Each item can appear in multiple groups if the key extractor returns an array.
4 *
5 * @param arr - The array to group.
6 * @param fn - A function that returns one or more keys for each item.
7 * @returns An object where each key maps to an array of grouped items.
8 */
9export function groupByMulti<T, K extends string | number>(
10 arr: T[],
11 fn: (item: T) => K | K[]
12): Record<K, T[]> {
13 return arr.reduce((acc, item) => {
14 const keys = fn(item);
15 const keyList = Array.isArray(keys) ? keys : [keys];
16 for (const key of keyList) {
17 if (!acc[key]) acc[key] = [];
18 acc[key].push(item);
19 }
20 return acc;
21 }, {} as Record<K, T[]>);
22}
Multi-key grouping support
Unlike
groupBy
, this utility allows each item to be placed into multiple groups by returning an array of keys. This is ideal when items logically belong to several categories.Efficient and concise
Uses a single
reduce
loop and minimal branching, ensuring performance remains linear (O(n * m)
, wherem
is the average number of keys per item).Flexible key extraction
Accepts either a single key or multiple keys per item via the extractor function, offering greater versatility in grouping logic.
Non-mutating and type-safe
Produces a new
Record<K, T[]>
while preserving original data, with full TypeScript type support.
Tests | Examples
1test('groupByMulti - group by multiple tags', () => {
2 const posts = [
3 { title: 'Post 1', tags: ['react', 'javascript'] },
4 { title: 'Post 2', tags: ['css', 'design'] },
5 { title: 'Post 3', tags: ['javascript'] },
6 ];
7
8 expect(groupByMulti(posts, post => post.tags)).toEqual({
9 react: [{ title: 'Post 1', tags: ['react', 'javascript'] }],
10 javascript: [
11 { title: 'Post 1', tags: ['react', 'javascript'] },
12 { title: 'Post 3', tags: ['javascript'] },
13 ],
14 css: [{ title: 'Post 2', tags: ['css', 'design'] }],
15 design: [{ title: 'Post 2', tags: ['css', 'design'] }],
16 });
17});
18
19test('groupByMulti - single key fallback', () => {
20 const data = ['apple', 'banana', 'cherry'];
21 expect(groupByMulti(data, word => word[0])).toEqual({
22 a: ['apple'],
23 b: ['banana'],
24 c: ['cherry'],
25 });
26});
Common Use Cases
Tag-based categorization
Group blog posts, files, or products under multiple tags or labels.
Access control / role mapping
Group users or permissions by multiple roles or responsibilities.
Search indexing
Pre-process data for fast lookup across multiple indexed fields or terms.
Reverse-mapping datasets
For example, mapping songs to genres when a song belongs to multiple genres.