@chaisser/array-group-by
v1.0.1
Published
Group array elements by a key or function
Maintainers
Readme
@chaisser/array-group-by
Advanced array grouping utilities for JavaScript and TypeScript
A comprehensive toolkit for grouping arrays by various criteria. Group by property, function, range, date, type, or build complex hierarchies.
Features
- Property-based, function-based, and multi-criteria grouping
- Specialized grouping: by range, date, type, truthiness, predicate, hash
- Group analysis: statistics, sizes, percentages, distribution
- Group transformation: map, filter, reduce, sort, flatten, merge
- Item queries: find common, duplicated, or unique items across groups
- Hierarchical grouping with tree creation and traversal
- Full TypeScript support with generics
Installation
npm install @chaisser/array-group-by
# or
yarn add @chaisser/array-group-by
# or
pnpm add @chaisser/array-group-byQuick Start
import {
groupBy,
groupByProp,
groupByMultiple,
groupByCustom,
getGroupKeys,
getGroupSizes,
flattenGroups,
mapGroups,
filterGroups
} from '@chaisser/array-group-by';
const users = [
{ name: 'Alice', role: 'admin', department: 'IT' },
{ name: 'Bob', role: 'user', department: 'IT' },
{ name: 'Charlie', role: 'admin', department: 'HR' },
{ name: 'David', role: 'user', department: 'IT' }
];
// Group by property
const byDept = groupByProp(users, 'department');
// { IT: [Alice, Bob, David], HR: [Charlie] }
// Group by function
const byRole = groupBy(users, user => user.role);
// { admin: [Alice, Charlie], user: [Bob, David] }
// Group by multiple functions
const byBoth = groupByMultiple(users, [
user => user.role,
user => user.department
]);
// { 'admin|IT': [Alice], 'user|IT': [Bob, David], 'admin|HR': [Charlie] }
// Group with value transformation
const namesByDept = groupByCustom(
users,
user => user.department,
user => user.name
);
// { IT: ['Alice', 'Bob', 'David'], HR: ['Charlie'] }API Reference
Basic Grouping
| Function | Signature | Description |
|----------|-----------|-------------|
| groupBy | (array, keyFn) => Record<string, T[]> | Group by key function |
| groupByProp | (array, prop) => Record<string, T[]> | Group by object property |
| groupByMultiple | (array, keyFns) => Record<string, T[]> | Group by multiple key functions (keys joined with \|) |
| groupByCustom | (array, keyFn, valueFn?) => Record<string, T[]> | Group with optional value transformation |
| groupByCustomTransform | (array, options) => Record<string, R[]> | Group with key, value, and transform functions |
Specialized Grouping
| Function | Signature | Description |
|----------|-----------|-------------|
| groupByUnique | (array, keyFn) => Record<string, T[]> | Include only first item per key |
| groupByIndex | (array, index) => Record<number, T[]> | Group by array index into buckets |
| groupByRange | (array, options) => Record<string, T[]> | Group by numeric ranges ({ keyFn, ranges: [{ min, max, label? }] }) |
| groupByComputed | (array, keyFn) => Record<string, T[]> | Key function receives (item, index) |
| groupByPredicate | (array, predicate) => [T[], T[]] | Split into passed/failed arrays |
| partition | (array, predicate) => [T[], T[]] | Alias for groupByPredicate |
| groupIntoChunks | (array, chunkSize) => T[][] | Split into fixed-size chunks |
| groupByObjectKey | (array, key) => Record<string, T[]> | Group by dynamic object key |
| groupByBoolean | (array, predicate) => Record<string, T[]> | Groups: 'true' / 'false' |
| groupByResult | (array, fn) => Record<string, T[]> | Key returns 'success' \| 'failure' \| 'pending' |
| groupByHash | (array, hashFn) => Record<string, T[]> | Group by hash function |
| groupByValue | (array) => Record<string, T[]> | Group by String(item) equality |
| groupByCustomHash | (array, options) => Record<string, T[]> | Group by custom/transform hash |
| groupByDateRange | (array, options) => Record<string, T[]> | Group by date range with granularity |
| groupBySize | (array, options) => Record<string, T[]> | Group arrays by their length (small/medium/large/xlarge) |
| groupByType | (array) => Record<string, T[]> | Group by typeof |
| groupByExistence | (array, prop) => Record<string, T[]> | Groups: 'has' / 'missing' |
| groupByTruthy | (array, prop) => Record<string, T[]> | Groups: 'truthy' / 'falsy' |
| groupByNullish | (array, prop) => Record<string, T[]> | Groups: 'nullish' / 'present' |
Group Analysis
| Function | Returns | Description |
|----------|---------|-------------|
| getGroupKeys(obj) | string[] | All group keys |
| getGroupSizes(obj) | Record<string, number> | Size of each group |
| getGroupsArray(obj) | T[][] | Groups as nested array |
| getLargestGroup(obj) | { key, group } | Group with most items |
| getSmallestGroup(obj) | { key, group } | Group with fewest items |
| getMaxGroup(obj) | { key, group } | Alias for getLargestGroup |
| getMinGroup(obj) | { key, group } | Alias for getSmallestGroup |
| getGroupCount(obj) | number | Number of groups |
| getTotalItemCount(obj) | number | Total items across all groups |
| getGroupPercentages(obj) | Record<string, number> | Percentage of each group |
| getGroupsSortedBySize(obj) | { key, size, group }[] | Groups sorted by size descending |
| getAverageGroupSize(obj) | number | Average group size |
| getMedianGroupSize(obj) | number | Median group size |
| getGroupSizeRange(obj) | { min, max, average, median } | Size statistics |
| getGroupStatistics(obj) | { totalGroups, totalItems, averageSize, medianSize, minSize, maxSize } | Comprehensive stats |
| getGroupsBySizeCategory(obj) | { small, medium, large, xlarge } | Categorize groups by size thresholds |
Group Filtering
| Function | Description |
|----------|-------------|
| filterGroups(obj, predicate) | Filter groups by predicate on group array |
| filterGroupsBySize(obj, predicate) | Filter groups by size predicate |
| getEmptyGroups(obj) | Groups with 0 items |
| getNonEmptyGroups(obj) | Groups with 1+ items |
| getGroupsWithinSize(obj, min, max) | Groups within size range |
| getGroupsAboveSize(obj, minSize) | Groups larger than threshold |
| getGroupsBelowSize(obj, maxSize) | Groups smaller than threshold |
| findGroupsBySize(obj, targetSize) | Groups matching exact size |
| findGroupsBySizeRange(obj, min, max) | Groups within size range |
| findGroupBy(obj, key) | Get group by key (or undefined) |
| findGroupContaining(obj, item) | Find group containing an item |
| findGroupsContaining(obj, items) | Find groups containing any of the items |
Group Transformation
| Function | Returns | Description |
|----------|---------|-------------|
| flattenGroups(obj) | T[] | All items from all groups into one array |
| mapGroups(obj, fn) | Record<string, R> | Map a function over each group |
| reduceGroups(obj, fn, initialValue) | R | Reduce across all groups |
| sortGroups(obj, fn) | Record<string, T[]> | Sort group keys by comparator |
| sortGroupsAlphabetically(obj) | Record<string, T[]> | Sort group keys A-Z |
| sortGroupsBySize(obj) | Record<string, T[]> | Sort group keys by size ascending |
| reverseGroupOrder(obj) | Record<string, T[]> | Reverse items within each group |
| mergeGroups(...objs) | Record<string, T[]> | Merge multiple grouped objects |
| countGroups(obj) | Record<string, number> | Count items per group |
| countTotalItems(obj) | number | Total items count |
| groupsToArray(obj) | { key, value }[] | Convert to array of entries |
| groupsToMap(obj) | Map<string, T[]> | Convert to Map |
| groupsToTree(obj) | { root: { key, items, children } } | Convert to tree structure |
Item Queries
| Function | Description |
|----------|-------------|
| getGroupBy(obj, key) | Get items for a group (returns [] if missing) |
| getGroupBySafe(obj, key) | Get items for a group (returns [] if nullish) |
| getItemDistribution(obj) | Map<T, number> of item frequency across groups |
| getDuplicatedItems(obj) | Items appearing in multiple groups |
| getCommonItems(obj) | Items appearing in all groups |
| getCommonItemsInGroups(obj, groupKeys) | Items common to specified groups |
| getItemsInAtLeastNGroups(obj, n) | Items in at least N groups |
| getItemsUniqueToGroup(obj, targetGroup) | Items only in one specific group |
| getItemsUniqueAcrossAll(obj) | All unique items |
| getGroupsContainingItem(obj, item) | Keys of groups containing an item |
| getGroupsContainingAny(obj, items) | Groups containing any of the items |
Validation
| Function | Returns | Description |
|----------|---------|-------------|
| isGrouped(array) | boolean | Check if value is a non-empty array |
| isValid(array) | boolean | Check if value is an array |
| validateGroups(obj) | { valid, errors } | Validate all groups are non-empty arrays |
| areAllGroupsEmpty(obj) | boolean | All groups have 0 items |
| isAnyGroupEmpty(obj) | boolean | Any group has 0 items |
| areGroupsEqualSize(obj) | boolean | All groups have same size |
| areGroupsAboveThreshold(obj, threshold) | boolean | All groups above size |
| areGroupsBelowThreshold(obj, threshold) | boolean | All groups below size |
Hierarchy
| Function | Description |
|----------|-------------|
| createGroupHierarchy(obj, keySeparator?) | Build hierarchy from composite keys (default separator '>') |
| flattenGroupHierarchy(hierarchy) | Flatten hierarchy back to flat groups |
| searchGroupHierarchy(hierarchy, searchKey) | Search nodes by key |
| getGroupFromHierarchy(hierarchy, path) | Get group by dot-separated path |
| getGroupAncestors(hierarchy, targetKey) | Get ancestor nodes of a group |
| getGroupDescendants(hierarchy, targetKey) | Get descendant items of a group |
| getLeafGroups(hierarchy) | Keys of groups without children |
| getInternalGroups(hierarchy) | Keys of groups with children |
| getGroupLevel(hierarchy, targetKey) | Depth level of a group (0-indexed) |
| getGroupPath(hierarchy, targetKey) | Full path of keys to a group |
Utility
| Function | Description |
|----------|-------------|
| createGroupKey(...values) | Join values into composite key with \| |
| parseGroupKey(key) | Split composite key by \| |
Examples
Grouping by Range
import { groupByRange } from '@chaisser/array-group-by';
const scores = [
{ name: 'Alice', score: 85 },
{ name: 'Bob', score: 42 },
{ name: 'Charlie', score: 91 },
{ name: 'David', score: 67 }
];
const byGrade = groupByRange(scores, {
keyFn: item => item.score,
ranges: [
{ min: 90, max: 100, label: 'A' },
{ min: 80, max: 89, label: 'B' },
{ min: 60, max: 79, label: 'C' },
{ min: 0, max: 59, label: 'F' }
]
});
// { A: [Charlie], B: [Alice], C: [David], F: [Bob] }Partition and Chunks
import { partition, groupIntoChunks } from '@chaisser/array-group-by';
const [evens, odds] = partition([1, 2, 3, 4, 5, 6], n => n % 2 === 0);
// evens: [2, 4, 6], odds: [1, 3, 5]
const chunks = groupIntoChunks([1, 2, 3, 4, 5], 2);
// [[1, 2], [3, 4], [5]]Group Statistics
import {
groupByProp,
getGroupStatistics,
getGroupPercentages,
getGroupsSortedBySize
} from '@chaisser/array-group-by';
const employees = [
{ name: 'Alice', dept: 'IT' },
{ name: 'Bob', dept: 'IT' },
{ name: 'Charlie', dept: 'HR' },
{ name: 'David', dept: 'IT' },
{ name: 'Eve', dept: 'Finance' }
];
const byDept = groupByProp(employees, 'dept');
const stats = getGroupStatistics(byDept);
// { totalGroups: 3, totalItems: 5, averageSize: 1.67, medianSize: 1, minSize: 1, maxSize: 3 }
const percentages = getGroupPercentages(byDept);
// { IT: 60, HR: 20, Finance: 20 }
const sorted = getGroupsSortedBySize(byDept);
// [{ key: 'IT', size: 3, group: [...] }, { key: 'HR', size: 1, ... }, ...]Finding Common and Unique Items
import { getCommonItems, getDuplicatedItems, getItemsUniqueToGroup } from '@chaisser/array-group-by';
const grouped = {
teamA: ['Alice', 'Bob', 'Charlie'],
teamB: ['Bob', 'Charlie', 'David'],
teamC: ['Charlie', 'David', 'Eve']
};
getCommonItems(grouped);
// ['Charlie'] — appears in all groups
getDuplicatedItems(grouped);
// ['Bob', 'Charlie', 'David'] — appears in multiple groups
getItemsUniqueToGroup(grouped, 'teamA');
// ['Alice'] — only in teamAGroup by Date Range
import { groupByDateRange } from '@chaisser/array-group-by';
const events = [
{ name: 'Event 1', date: new Date('2024-01-03') },
{ name: 'Event 2', date: new Date('2024-01-10') },
{ name: 'Event 3', date: new Date('2024-01-17') },
{ name: 'Event 4', date: new Date('2024-01-25') }
];
const byWeek = groupByDateRange(events, {
dateFn: e => e.date,
startDate: new Date('2024-01-01'),
endDate: new Date('2024-01-31'),
granularity: 'week'
});
// { week_0: [...], week_1: [...], week_2: [...], week_3: [...] }Merging and Sorting Groups
import {
groupByProp,
mergeGroups,
sortGroupsAlphabetically,
sortGroupsBySize
} from '@chaisser/array-group-by';
const q1 = groupByProp(salesQ1, 'region');
const q2 = groupByProp(salesQ2, 'region');
const merged = mergeGroups(q1, q2);
// Combines all items per region from both quarters
const alphabetical = sortGroupsAlphabetically(merged);
const bySize = sortGroupsBySize(merged);Hierarchical Grouping
import {
createGroupHierarchy,
getLeafGroups,
getGroupPath
} from '@chaisser/array-group-by';
const flat = {
'Engineering>Frontend>Senior': [developer1],
'Engineering>Frontend>Junior': [developer2],
'Engineering>Backend>Senior': [developer3],
'Sales>Enterprise': [salesperson1]
};
const hierarchy = createGroupHierarchy(flat, '>');
// Nested tree structure
const leaves = getLeafGroups(hierarchy);
// Deepest group keys
const path = getGroupPath(hierarchy, 'Senior');
// ['Engineering', 'Frontend', 'Senior']Performance
- O(n) for single-pass grouping
- Single-pass multi-criteria grouping via
groupByMultiple - No unnecessary intermediate object creation
- All operations use standard JavaScript constructs
License
MIT
Repository
- GitHub: @chaisser
- NPM: @chaisser/array-group-by
