[IMP] web: utils: add groupBy function

This commit introduces a new utility function: 'groupBy'. It is
similar in API and result as the underscore method of the same
name.

X-original-commit: 834a1967161dc537cb2d5543314a062dbac08f9c
This commit is contained in:
Julien Mougenot
2020-06-22 13:04:00 +00:00
committed by Aaron Bohy
parent 669bba4694
commit a6cd6d5a89
2 changed files with 104 additions and 13 deletions
+51 -13
View File
@@ -205,6 +205,28 @@ var diacriticsMap = {
const patchMap = new WeakMap();
/**
* Helper function returning an extraction handler to use on array elements to
* return a certain attribute or mutated form of the element.
*
* @private
* @param {string | function} criterion
* @returns {(element: any) => any}
*/
function _getExtractorFrom(criterion) {
if (criterion) {
switch (typeof criterion) {
case 'string': return element => element[criterion];
case 'function': return criterion;
default: throw new Error(
`Expected criterion of type 'string' or 'function' and got '${typeof criterion}'`
);
}
} else {
return element => element;
}
}
var utils = {
/**
@@ -303,6 +325,31 @@ var utils = {
reader.readAsDataURL(file);
});
},
/**
* Returns an object holding different groups defined by a given criterion
* or a default one. Each group is a subset of the original given list.
* The given criterion can either be:
* - a string: a property name on the list elements which value will be the
* group name,
* - a function: a handler that will return the group name from a given
* element.
*
* @param {any[]} list
* @param {string | function} [criterion]
* @returns {Object}
*/
groupBy: function (list, criterion) {
const extract = _getExtractorFrom(criterion);
const groups = {};
for (const element of list) {
const group = String(extract(element));
if (!(group in groups)) {
groups[group] = [];
}
groups[group].push(element);
}
return groups;
},
/**
* Returns a human readable number (e.g. 34000 -> 34k).
*
@@ -656,22 +703,13 @@ var utils = {
* - a function: a handler that will return the sortable primitive from a given element.
*
* @param {any[]} array
* @param {(string|function)} [criterion]
* @param {string | function} [criterion]
*/
sortBy: function (array, criterion) {
let getter;
if (criterion) {
switch (typeof criterion) {
case 'string': getter = element => element[criterion]; break;
case 'function': getter = criterion; break;
default: throw new Error(`Expected criterion of type 'string' or 'function' and got '${typeof criterion}'`);
}
} else {
getter = element => element;
}
const extract = _getExtractorFrom(criterion);
return array.slice().sort((elA, elB) => {
const a = getter(elA);
const b = getter(elB);
const a = extract(elA);
const b = extract(elB);
if (isNaN(a) && isNaN(b)) {
return a > b ? 1 : a < b ? -1 : 0;
} else {
@@ -7,6 +7,59 @@ QUnit.module('core', {}, function () {
QUnit.module('utils');
QUnit.test('groupBy', function (assert) {
assert.expect(7);
const { groupBy } = utils;
// Invalid
assert.throws(
() => groupBy({}),
new TypeError(`list is not iterable`)
);
assert.throws(
() => groupBy([], true),
new Error(`Expected criterion of type 'string' or 'function' and got 'boolean'`)
);
assert.throws(
() => groupBy([], 3),
new Error(`Expected criterion of type 'string' or 'function' and got 'number'`)
);
assert.throws(
() => groupBy([], {}),
new Error(`Expected criterion of type 'string' or 'function' and got 'object'`)
);
// criterion = default
assert.deepEqual(
groupBy(["a", "b", 1, true]),
{
1: [1],
a: ["a"],
b: ["b"],
true: [true],
}
);
// criterion = string
assert.deepEqual(
groupBy([{ x: "a" }, { x: "a" }, { x: "b" }], "x"),
{
a: [{ x: "a" }, { x: "a" }],
b: [{ x: "b" }],
}
);
// criterion = function
assert.deepEqual(
groupBy(["a", "b", 1, true], (x) => `el${x}`),
{
ela: ["a"],
elb: ["b"],
el1: [1],
eltrue: [true],
}
);
});
QUnit.test('intersperse', function (assert) {
assert.expect(27);