@tidyjs/tidy
Version:
Tidy up your data with JavaScript, inspired by dplyr and the tidyverse
220 lines (217 loc) • 7.71 kB
JavaScript
import { group } from 'd3-array';
import { assignGroupKeys } from './helpers/assignGroupKeys.js';
import { groupMap } from './helpers/groupMap.js';
import { groupTraversal } from './helpers/groupTraversal.js';
import { identity } from './helpers/identity.js';
import { isObject } from './helpers/isObject.js';
import { singleOrArray } from './helpers/singleOrArray.js';
function groupBy(groupKeys, fns, options) {
if (typeof fns === "function") {
fns = [fns];
} else if (arguments.length === 2 && fns != null && !Array.isArray(fns)) {
options = fns;
}
const _groupBy = (items) => {
const grouped = makeGrouped(items, groupKeys);
const results = runFlow(grouped, fns, options == null ? void 0 : options.addGroupKeys);
if (options == null ? void 0 : options.export) {
switch (options.export) {
case "grouped":
return results;
case "levels":
return exportLevels(results, options);
case "entries-obj":
case "entriesObject":
return exportLevels(results, {
...options,
export: "levels",
levels: ["entries-object"]
});
default:
return exportLevels(results, {
...options,
export: "levels",
levels: [options.export]
});
}
}
const ungrouped = ungroup(results, options == null ? void 0 : options.addGroupKeys);
return ungrouped;
};
return _groupBy;
}
groupBy.grouped = (options) => ({...options, export: "grouped"});
groupBy.entries = (options) => ({...options, export: "entries"});
groupBy.entriesObject = (options) => ({...options, export: "entries-object"});
groupBy.object = (options) => ({...options, export: "object"});
groupBy.map = (options) => ({...options, export: "map"});
groupBy.keys = (options) => ({...options, export: "keys"});
groupBy.values = (options) => ({...options, export: "values"});
groupBy.levels = (options) => ({...options, export: "levels"});
function runFlow(items, fns, addGroupKeys) {
let result = items;
if (!(fns == null ? void 0 : fns.length))
return result;
for (const fn of fns) {
if (!fn)
continue;
result = groupMap(result, (items2, keys) => {
const context = {groupKeys: keys};
let leafItemsMapped = fn(items2, context);
if (addGroupKeys !== false) {
leafItemsMapped = leafItemsMapped.map((item) => assignGroupKeys(item, keys));
}
return leafItemsMapped;
});
}
return result;
}
function makeGrouped(items, groupKeys) {
const groupKeyFns = singleOrArray(groupKeys).map((key, i) => {
const keyFn = typeof key === "function" ? key : (d) => d[key];
const keyCache = new Map();
return (d) => {
const keyValue = keyFn(d);
const keyValueOf = isObject(keyValue) ? keyValue.valueOf() : keyValue;
if (keyCache.has(keyValueOf)) {
return keyCache.get(keyValueOf);
}
const keyWithName = [key, keyValue];
keyCache.set(keyValueOf, keyWithName);
return keyWithName;
};
});
const grouped = group(items, ...groupKeyFns);
return grouped;
}
function ungroup(grouped, addGroupKeys) {
const items = [];
groupTraversal(grouped, items, [], identity, (root, keys, values) => {
let valuesToAdd = values;
if (addGroupKeys !== false) {
valuesToAdd = values.map((d) => assignGroupKeys(d, keys));
}
root.push(...valuesToAdd);
});
return items;
}
const defaultCompositeKey = (keys) => keys.join("/");
function processFromGroupsOptions(options) {
var _a;
const {
flat,
single,
mapLeaf = identity,
mapLeaves = identity,
addGroupKeys
} = options;
let compositeKey;
if (options.flat) {
compositeKey = (_a = options.compositeKey) != null ? _a : defaultCompositeKey;
}
const groupFn = (values, keys) => {
return single ? mapLeaf(addGroupKeys === false ? values[0] : assignGroupKeys(values[0], keys)) : mapLeaves(values.map((d) => mapLeaf(addGroupKeys === false ? d : assignGroupKeys(d, keys))));
};
const keyFn = flat ? (keys) => compositeKey(keys.map((d) => d[1])) : (keys) => keys[keys.length - 1][1];
return {groupFn, keyFn};
}
function exportLevels(grouped, options) {
const {groupFn, keyFn} = processFromGroupsOptions(options);
let {mapEntry = identity} = options;
const {levels = ["entries"]} = options;
const levelSpecs = [];
for (const levelOption of levels) {
switch (levelOption) {
case "entries":
case "entries-object":
case "entries-obj":
case "entriesObject": {
const levelMapEntry = (levelOption === "entries-object" || levelOption === "entries-obj" || levelOption === "entriesObject") && options.mapEntry == null ? ([key, values]) => ({key, values}) : mapEntry;
levelSpecs.push({
id: "entries",
createEmptySubgroup: () => [],
addSubgroup: (parentGrouped, newSubgroup, key, level) => {
parentGrouped.push(levelMapEntry([key, newSubgroup], level));
},
addLeaf: (parentGrouped, key, values, level) => {
parentGrouped.push(levelMapEntry([key, values], level));
}
});
break;
}
case "map":
levelSpecs.push({
id: "map",
createEmptySubgroup: () => new Map(),
addSubgroup: (parentGrouped, newSubgroup, key) => {
parentGrouped.set(key, newSubgroup);
},
addLeaf: (parentGrouped, key, values) => {
parentGrouped.set(key, values);
}
});
break;
case "object":
levelSpecs.push({
id: "object",
createEmptySubgroup: () => ({}),
addSubgroup: (parentGrouped, newSubgroup, key) => {
parentGrouped[key] = newSubgroup;
},
addLeaf: (parentGrouped, key, values) => {
parentGrouped[key] = values;
}
});
break;
case "keys":
levelSpecs.push({
id: "keys",
createEmptySubgroup: () => [],
addSubgroup: (parentGrouped, newSubgroup, key) => {
parentGrouped.push([key, newSubgroup]);
},
addLeaf: (parentGrouped, key) => {
parentGrouped.push(key);
}
});
break;
case "values":
levelSpecs.push({
id: "values",
createEmptySubgroup: () => [],
addSubgroup: (parentGrouped, newSubgroup) => {
parentGrouped.push(newSubgroup);
},
addLeaf: (parentGrouped, key, values) => {
parentGrouped.push(values);
}
});
break;
default: {
if (typeof levelOption === "object") {
levelSpecs.push(levelOption);
}
}
}
}
const addSubgroup = (parentGrouped, keys, level) => {
var _a, _b;
if (options.flat) {
return parentGrouped;
}
const levelSpec = (_a = levelSpecs[level]) != null ? _a : levelSpecs[levelSpecs.length - 1];
const nextLevelSpec = (_b = levelSpecs[level + 1]) != null ? _b : levelSpec;
const newSubgroup = nextLevelSpec.createEmptySubgroup();
levelSpec.addSubgroup(parentGrouped, newSubgroup, keyFn(keys), level);
return newSubgroup;
};
const addLeaf = (parentGrouped, keys, values, level) => {
var _a;
const levelSpec = (_a = levelSpecs[level]) != null ? _a : levelSpecs[levelSpecs.length - 1];
levelSpec.addLeaf(parentGrouped, keyFn(keys), groupFn(values, keys), level);
};
const initialOutputObject = levelSpecs[0].createEmptySubgroup();
return groupTraversal(grouped, initialOutputObject, [], addSubgroup, addLeaf);
}
export { groupBy };
//# sourceMappingURL=groupBy.js.map