Самый эффективный способ группировать по массиву объектов
Каков наиболее эффективный способ группировки объектов в массиве?
Например, учитывая этот массив объектов:
[
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
]
Im, отображающий эту информацию в таблице. Id нравится группировать разные методы, но я хочу суммировать значения.
Я использую Underscore.js для своей функции groupby, что полезно, но не делает весь трюк, потому что я не хочу, чтобы они "расщеплялись", но "сливались", больше похожи на group by
SQL group by
методу.
То, что я искал, сможет суммировать определенные значения (если требуется).
Поэтому, если я сделал groupby Phase
, Id хочу получить:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
И если я сделал groupy Phase
/Step
, Id получает:
[
{ Phase: "Phase 1", Step: "Step 1", Value: 15 },
{ Phase: "Phase 1", Step: "Step 2", Value: 35 },
{ Phase: "Phase 2", Step: "Step 1", Value: 55 },
{ Phase: "Phase 2", Step: "Step 2", Value: 75 }
]
Есть ли полезный скрипт для этого, или я должен придерживаться использования Underscore.js, а затем прокручивать результирующий объект, чтобы сами делать итоги?
Ответы
Ответ 1
Если вы хотите избежать внешних библиотек, вы можете кратко реализовать groupBy()
версию groupBy()
следующим образом:
var groupBy = function(xs, key) {
return xs.reduce(function(rv, x) {
(rv[x[key]] = rv[x[key]] || []).push(x);
return rv;
}, {});
};
console.log(groupBy(['one', 'two', 'three'], 'length'));
// => {3: ["one", "two"], 5: ["three"]}
Ответ 2
Использование объекта ES6 Map:
function groupBy(list, keyGetter) {
const map = new Map();
list.forEach((item) => {
const key = keyGetter(item);
const collection = map.get(key);
if (!collection) {
map.set(key, [item]);
} else {
collection.push(item);
}
});
return map;
}
// example usage
const pets = [
{type:"Dog", name:"Spot"},
{type:"Cat", name:"Tiger"},
{type:"Dog", name:"Rover"},
{type:"Cat", name:"Leo"}
];
const grouped = groupBy(pets, pet => pet.type);
console.log(grouped.get("Dog")); // -> [{type:"Dog", name:"Spot"}, {type:"Dog", name:"Rover"}]
console.log(grouped.get("Cat")); // -> [{type:"Cat", name:"Tiger"}, {type:"Cat", name:"Leo"}]
Ответ 3
с ES6:
const groupBy = (items, key) => items.reduce(
(result, item) => ({
...result,
[item[key]]: [
...(result[item[key]] || []),
item,
],
}),
{},
);
Ответ 4
Хотя ответ linq интересен, он также довольно тяжелый. Мой подход несколько иной:
var DataGrouper = (function() {
var has = function(obj, target) {
return _.any(obj, function(value) {
return _.isEqual(value, target);
});
};
var keys = function(data, names) {
return _.reduce(data, function(memo, item) {
var key = _.pick(item, names);
if (!has(memo, key)) {
memo.push(key);
}
return memo;
}, []);
};
var group = function(data, names) {
var stems = keys(data, names);
return _.map(stems, function(stem) {
return {
key: stem,
vals:_.map(_.where(data, stem), function(item) {
return _.omit(item, names);
})
};
});
};
group.register = function(name, converter) {
return group[name] = function(data, names) {
return _.map(group(data, names), converter);
};
};
return group;
}());
DataGrouper.register("sum", function(item) {
return _.extend({}, item.key, {Value: _.reduce(item.vals, function(memo, node) {
return memo + Number(node.Value);
}, 0)});
});
Вы можете увидеть это в действии на JSBin.
Я не видел ничего, что делает Underscore то, что has
делает, хотя я, возможно, отсутствует его. Он почти такой же, как _.contains
, но использует _.isEqual
а не ===
для сравнений. Помимо этого, остальная часть этого вопроса является специфичной для конкретной задачи, хотя и с попыткой получить общий характер.
Теперь DataGrouper.sum(data, ["Phase"])
возвращает
[
{Phase: "Phase 1", Value: 50},
{Phase: "Phase 2", Value: 130}
]
И DataGrouper.sum(data, ["Phase", "Step"])
[
{Phase: "Phase 1", Step: "Step 1", Value: 15},
{Phase: "Phase 1", Step: "Step 2", Value: 35},
{Phase: "Phase 2", Step: "Step 1", Value: 55},
{Phase: "Phase 2", Step: "Step 2", Value: 75}
]
Но sum
является только одной потенциальной функцией. Вы можете регистрировать других, как вам нравится:
DataGrouper.register("max", function(item) {
return _.extend({}, item.key, {Max: _.reduce(item.vals, function(memo, node) {
return Math.max(memo, Number(node.Value));
}, Number.NEGATIVE_INFINITY)});
});
и теперь DataGrouper.max(data, ["Phase", "Step"])
вернутся
[
{Phase: "Phase 1", Step: "Step 1", Max: 10},
{Phase: "Phase 1", Step: "Step 2", Max: 20},
{Phase: "Phase 2", Step: "Step 1", Max: 30},
{Phase: "Phase 2", Step: "Step 2", Max: 40}
]
или если вы зарегистрировали это:
DataGrouper.register("tasks", function(item) {
return _.extend({}, item.key, {Tasks: _.map(item.vals, function(item) {
return item.Task + " (" + item.Value + ")";
}).join(", ")});
});
затем вызов DataGrouper.tasks(data, ["Phase", "Step"])
доставит вам
[
{Phase: "Phase 1", Step: "Step 1", Tasks: "Task 1 (5), Task 2 (10)"},
{Phase: "Phase 1", Step: "Step 2", Tasks: "Task 1 (15), Task 2 (20)"},
{Phase: "Phase 2", Step: "Step 1", Tasks: "Task 1 (25), Task 2 (30)"},
{Phase: "Phase 2", Step: "Step 2", Tasks: "Task 1 (35), Task 2 (40)"}
]
Сам DataGrouper
является функцией. Вы можете называть его своими данными и списком свойств, которые вы хотите группировать. Он возвращает массив, чьи элементы являются объектами с двумя свойствами: key
- это совокупность сгруппированных свойств, vals
- массив объектов, содержащих оставшиеся свойства не в ключе. Например, DataGrouper(data, ["Phase", "Step"])
даст:
[
{
"key": {Phase: "Phase 1", Step: "Step 1"},
"vals": [
{Task: "Task 1", Value: "5"},
{Task: "Task 2", Value: "10"}
]
},
{
"key": {Phase: "Phase 1", Step: "Step 2"},
"vals": [
{Task: "Task 1", Value: "15"},
{Task: "Task 2", Value: "20"}
]
},
{
"key": {Phase: "Phase 2", Step: "Step 1"},
"vals": [
{Task: "Task 1", Value: "25"},
{Task: "Task 2", Value: "30"}
]
},
{
"key": {Phase: "Phase 2", Step: "Step 2"},
"vals": [
{Task: "Task 1", Value: "35"},
{Task: "Task 2", Value: "40"}
]
}
]
DataGrouper.register
принимает функцию и создает новую функцию, которая принимает исходные данные и свойства для группировки. Эта новая функция затем принимает выходной формат, как указано выше, и запускает вашу функцию против каждого из них по очереди, возвращая новый массив. Сгенерированная функция сохраняется как свойство DataGrouper
соответствии с именем, которое вы поставляете, а также возвращается, если вы просто хотите локальную ссылку.
Хорошо, что много объяснений. Я надеюсь, что код достаточно прост.
Ответ 5
Скорее всего, это легче сделать с linq.js
, который предназначен для реализации LINQ в JavaScript ( DEMO):
var linq = Enumerable.From(data);
var result =
linq.GroupBy(function(x){ return x.Phase; })
.Select(function(x){
return {
Phase: x.Key(),
Value: x.Sum(function(y){ return y.Value|0; })
};
}).ToArray();
результат:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
Или, проще говоря, с помощью строковых селекторов ( DEMO):
linq.GroupBy("$.Phase", "",
"k,e => { Phase:k, Value:e.Sum('$.Value|0') }").ToArray();
Ответ 6
Я бы проверил группу lodash. Похоже, она делает именно то, что вы ищете. Это также довольно легкий и действительно простой.
Пример скрипки: https://jsfiddle.net/r7szvt5k/
При условии, что имя вашего массива arr
groupBy с lodash просто:
import groupBy from 'lodash/groupBy';
// if you still use require:
// const groupBy = require('lodash/groupBy');
const a = groupBy(arr, function(n) {
return n.Phase;
});
// a is your array grouped by Phase attribute
Ответ 7
Вы можете создать Map
array.reduce()
из array.reduce()
.
const groupedMap = initialArray.reduce(
(entryMap, e) => entryMap.set(e.id, [...entryMap.get(e.id)||[], e]),
new Map()
);
Это имеет несколько преимуществ по сравнению с другими решениями:
- Он не требует каких-либо библиотек (в отличие от
_.groupBy()
) - Вы получаете
Map
JavaScript, а не объект (например, возвращаемый _.groupBy()
). Это имеет множество преимуществ, в том числе: - он запоминает порядок, в котором элементы были сначала добавлены,
- ключи могут быть любого типа, а не просто строк.
-
Map
является более полезным результатом для массива массивов. Но если вам нужен массив массивов, вы можете вызвать Array.from(groupedMap.entries())
(для [key, group array]
пар [key, group array]
) или Array.from(groupedMap.values())
(для простой массив массивов). - Это довольно гибко; часто, независимо от того, что вы планируете делать дальше с этой картой, можно сделать непосредственно в рамках сокращения.
В качестве примера последней точки, представьте, что у меня есть массив объектов, которые я хочу сделать (неглубоко) слиянием по id, например:
const objsToMerge = [{id: 1, name: "Steve"}, {id: 2, name: "Alice"}, {id: 1, age: 20}];
// The following variable should be created automatically
const mergedArray = [{id: 1, name: "Steve", age: 20}, {id: 2, name: "Alice"}]
Для этого я обычно начинал с группировки по id, а затем слияния каждого из результирующих массивов. Вместо этого вы можете выполнить слияние непосредственно в reduce()
:
const mergedArray = Array.from(
objsToMerge.reduce(
(entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}),
new Map()
).values()
);
Ответ 8
_.groupBy([{tipo: 'A' },{tipo: 'A'}, {tipo: 'B'}], 'tipo');
>> Object {A: Array[2], B: Array[1]}
От: http://underscorejs.org/#groupBy
Ответ 9
Array.prototype.groupBy = function(keyFunction) {
var groups = {};
this.forEach(function(el) {
var key = keyFunction(el);
if (key in groups == false) {
groups[key] = [];
}
groups[key].push(el);
});
return Object.keys(groups).map(function(key) {
return {
key: key,
values: groups[key]
};
});
};
Ответ 10
Вы можете сделать это с помощью Alasql библиотеки JavaScript:
var data = [ { Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }];
var res = alasql('SELECT Phase, Step, SUM(CAST([Value] AS INT)) AS [Value] \
FROM ? GROUP BY Phase, Step',[data]);
Попробуйте этот пример в jsFiddle.
BTW: На больших массивах (100000 записей и более) Alasql быстрее tham Linq. См. Тест в jsPref.
Комментарии:
- Здесь я помещаю значение в квадратные скобки, потому что VALUE является ключевым словом в SQL
- Мне нужно использовать функцию CAST() для преобразования строковых значений в тип номера.
Ответ 11
Хотя на этот вопрос есть несколько ответов, и ответы выглядят немного сложнее, я предлагаю использовать ванильный Javascript для группировки с вложенным (при необходимости) Map
.
function groupBy(array, groups, valueKey) {
var map = new Map;
groups = [].concat(groups);
return array.reduce((r, o) => {
groups.reduce((m, k, i, { length }) => {
var child;
if (m.has(o[k])) return m.get(o[k]);
if (i + 1 === length) {
child = Object
.assign(...groups.map(k => ({ [k]: o[k] })), { [valueKey]: 0 });
r.push(child);
} else {
child = new Map;
}
m.set(o[k], child);
return child;
}, map)[valueKey] += +o[valueKey];
return r;
}, [])
};
var data = [{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" }, { Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }, { Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" }, { Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" }, { Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" }, { Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" }, { Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" }, { Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }];
console.log(groupBy(data, 'Phase', 'Value'));
console.log(groupBy(data, ['Phase', 'Step'], 'Value'));
.as-console-wrapper { max-height: 100% !important; top: 0; }
Ответ 12
MDN имеет этот пример в своей документации Array.reduce()
.
// Grouping objects by a property
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce#Grouping_objects_by_a_property#Grouping_objects_by_a_property
var people = [
{ name: 'Alice', age: 21 },
{ name: 'Max', age: 20 },
{ name: 'Jane', age: 20 }
];
function groupBy(objectArray, property) {
return objectArray.reduce(function (acc, obj) {
var key = obj[property];
if (!acc[key]) {
acc[key] = [];
}
acc[key].push(obj);
return acc;
}, {});
}
var groupedPeople = groupBy(people, 'age');
// groupedPeople is:
// {
// 20: [
// { name: 'Max', age: 20 },
// { name: 'Jane', age: 20 }
// ],
// 21: [{ name: 'Alice', age: 21 }]
// }
Ответ 13
Я хотел бы предложить мой подход. Во-первых, раздельная группировка и агрегация. Давайте объявим прототипную функцию "group by". Требуется другая функция для создания строки "хэш" для каждого элемента массива, который будет группироваться.
Array.prototype.groupBy = function(hash){
var _hash = hash ? hash : function(o){return o;};
var _map = {};
var put = function(map, key, value){
if (!map[_hash(key)]) {
map[_hash(key)] = {};
map[_hash(key)].group = [];
map[_hash(key)].key = key;
}
map[_hash(key)].group.push(value);
}
this.map(function(obj){
put(_map, obj, obj);
});
return Object.keys(_map).map(function(key){
return {key: _map[key].key, group: _map[key].group};
});
}
Когда группировка завершена, вы можете агрегировать данные, как вам нужно, в вашем случае
data.groupBy(function(o){return JSON.stringify({a: o.Phase, b: o.Step});})
/* aggreagating */
.map(function(el){
var sum = el.group.reduce(
function(l,c){
return l + parseInt(c.Value);
},
0
);
el.key.Value = sum;
return el.key;
});
в общем, он работает. Я протестировал этот код в хром-консоли. и не стесняйтесь улучшать и находить ошибки;)
Ответ 14
Без мутаций:
const groupBy = (xs, key) => xs.reduce((acc, x) => Object.assign({}, acc, {
[x[key]]: (acc[x[key]] || []).concat(x)
}), {})
console.log(groupBy(['one', 'two', 'three'], 'length'));
// => {3: ["one", "two"], 5: ["three"]}
Ответ 15
Это решение принимает любую произвольную функцию (не ключ), поэтому оно более гибкое, чем решения, описанные выше, и позволяет использовать функции стрелок, которые похожи на лямбда-выражения, используемые в LINQ:
Array.prototype.groupBy = function (funcProp) {
return this.reduce(function (acc, val) {
(acc[funcProp(val)] = acc[funcProp(val)] || []).push(val);
return acc;
}, {});
};
ПРИМЕЧАНИЕ: хотите ли вы расширить прототип Array
решать только вам.
Пример поддерживается в большинстве браузеров:
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(function(c){return c.a;})
Пример использования функций стрелок (ES6):
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(c=>c.a)
Оба примера выше возвращают:
{
"1": [{"a": 1, "b": "b"}, {"a": 1, "c": "c"}],
"2": [{"a": 2, "d": "d"}]
}
Ответ 16
groupByArray(xs, key) {
return xs.reduce(function (rv, x) {
let v = key instanceof Function ? key(x) : x[key];
let el = rv.find((r) => r && r.key === v);
if (el) {
el.values.push(x);
}
else {
rv.push({
key: v,
values: [x]
});
}
return rv;
}, []);
}
Этот вывод выводит массив.
Ответ 17
Основываясь на предыдущих ответах
const groupBy = (prop) => (xs) =>
xs.reduce((rv, x) =>
Object.assign(rv, {[x[prop]]: [...(rv[x[prop]] || []), x]}), {});
и немного лучше посмотреть с синтаксисом распространения объектов, если ваша среда поддерживает.
const groupBy = (prop) => (xs) =>
xs.reduce((acc, x) => ({
...acc,
[ x[ prop ] ]: [...( acc[ x[ prop ] ] || []), x],
}), {});
Здесь наш редуктор принимает частично сформированное возвращаемое значение (начиная с пустого объекта) и возвращает объект, состоящий из членов расширенного значения предыдущего возвращаемого значения, вместе с новым членом, ключ которого вычисляется из текущего значения iteree в prop
и значение которого представляет собой список всех значений для этой опоры вместе с текущим значением.
Ответ 18
Позволяет сгенерировать общий инструмент Array.prototype.groupBy()
. Просто для разнообразия можно использовать ES6 fanciness для оператора спредов для некоторого соответствия шаблону Haskellesque на рекурсивном подходе. Также позвольте нашему Array.prototype.groupBy()
принять обратный вызов, который принимает элемент (e
) индекс (i
) и применяемый массив (a
) в качестве аргументов.
Array.prototype.groupBy = function(cb){
return function iterate([x,...xs], i = 0, r = [[],[]]){
cb(x,i,[x,...xs]) ? (r[0].push(x), r)
: (r[1].push(x), r);
return xs.length ? iterate(xs, ++i, r) : r;
}(this);
};
var arr = [0,1,2,3,4,5,6,7,8,9],
res = arr.groupBy(e => e < 5);
console.log(res);
Ответ 19
Ответ на Ceasar хорош, но работает только для внутренних свойств элементов внутри массива (длина в случае строки).
эта реализация больше похожа: эта ссылка
const groupBy = function (arr, f) {
return arr.reduce((out, val) => {
let by = typeof f === 'function' ? '' + f(val) : val[f];
(out[by] = out[by] || []).push(val);
return out;
}, {});
};
надеюсь это поможет...
Ответ 20
Array.prototype.groupBy = function (groupingKeyFn) {
if (typeof groupingKeyFn !== 'function') {
throw new Error("groupBy take a function as only parameter");
}
return this.reduce((result, item) => {
let key = groupingKeyFn(item);
if (!result[key])
result[key] = [];
result[key].push(item);
return result;
}, {});
}
var a = [
{type: "video", name: "a"},
{type: "image", name: "b"},
{type: "video", name: "c"},
{type: "blog", name: "d"},
{type: "video", name: "e"},
]
console.log(a.groupBy((item) => item.type));
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
Ответ 21
Проверенный ответ - это не группировка, а прямой ответ.
REAL GROUP BY для массива объектов по некоторому полю с вычисленным именем ключа.
const inputArray = [
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
];
var outObject = inputArray.reduce(function(a, e) {
// GROUP BY estimated key (estKey), well, may be a just plain key
// a -- Accumulator result object
// e -- sequentally checked Element, the Element that is tested just at this itaration
// new grouping name may be calculated, but must be based on real value of real field
let estKey = (e['Phase']);
(a[estKey] ? a[estKey] : (a[estKey] = null || [])).push(e);
return a;
}, {});
console.log(outObject);
Ответ 22
ES6 reduce
версия версии с поддержкой функции iteratee
.
Работает так же, как и ожидалось, если функция iteratee
не предусмотрена:
const data = [{id: 1, score: 2},{id: 1, score: 3},{id: 2, score: 2},{id: 2, score: 4}]
const group = (arr, k) => arr.reduce((r, c) => (r[c[k]] = [...r[c[k]] || [], c], r), {});
const groupBy = (arr, k, fn = () => true) =>
arr.reduce((r, c) => (fn(c[k]) ? r[c[k]] = [...r[c[k]] || [], c] : null, r), {});
console.log(group(data, 'id')) // grouping via 'reduce'
console.log(groupBy(data, 'id')) // same result if 'fn' is omitted
console.log(groupBy(data, 'score', x => x > 2 )) // group with the iteratee
Ответ 23
let groupbyKeys = function(arr, ...keys) {
let keysFieldName = keys.join();
return arr.map(ele => {
let keysField = {};
keysField[keysFieldName] = keys.reduce((keyValue, key) => {
return keyValue + ele[key]
}, "");
return Object.assign({}, ele, keysField);
}).reduce((groups, ele) => {
(groups[ele[keysFieldName]] = groups[ele[keysFieldName]] || [])
.push([ele].map(e => {
if (keys.length > 1) {
delete e[keysFieldName];
}
return e;
})[0]);
return groups;
}, {});
};
console.log(groupbyKeys(array, 'Phase'));
console.log(groupbyKeys(array, 'Phase', 'Step'));
console.log(groupbyKeys(array, 'Phase', 'Step', 'Task'));
Ответ 24
Вот версия ES6, которая не будет разбиваться на нулевые члены
function groupBy (arr, key) {
return (arr || []).reduce((acc, x = {}) => ({
...acc,
[x[key]]: [...acc[x[key]] || [], x]
}), {})
}
Ответ 25
Просто, чтобы добавить к Скотт Sauyet ответа, некоторые люди спрашивали в комментариях, как использовать свою функцию GroupBy значение1, значение2 и т.д., вместо того, чтобы группировать только одно значение.
Все, что требуется, - отредактировать свою функцию суммы:
DataGrouper.register("sum", function(item) {
return _.extend({}, item.key,
{VALUE1: _.reduce(item.vals, function(memo, node) {
return memo + Number(node.VALUE1);}, 0)},
{VALUE2: _.reduce(item.vals, function(memo, node) {
return memo + Number(node.VALUE2);}, 0)}
);
});
оставив основной (DataGrouper) неизменным:
var DataGrouper = (function() {
var has = function(obj, target) {
return _.any(obj, function(value) {
return _.isEqual(value, target);
});
};
var keys = function(data, names) {
return _.reduce(data, function(memo, item) {
var key = _.pick(item, names);
if (!has(memo, key)) {
memo.push(key);
}
return memo;
}, []);
};
var group = function(data, names) {
var stems = keys(data, names);
return _.map(stems, function(stem) {
return {
key: stem,
vals:_.map(_.where(data, stem), function(item) {
return _.omit(item, names);
})
};
});
};
group.register = function(name, converter) {
return group[name] = function(data, names) {
return _.map(group(data, names), converter);
};
};
return group;
}());
Ответ 26
С функцией сортировки
export const groupBy = function groupByArray(xs, key, sortKey) {
return xs.reduce(function(rv, x) {
let v = key instanceof Function ? key(x) : x[key];
let el = rv.find(r => r && r.key === v);
if (el) {
el.values.push(x);
el.values.sort(function(a, b) {
return a[sortKey].toLowerCase().localeCompare(b[sortKey].toLowerCase());
});
} else {
rv.push({ key: v, values: [x] });
}
return rv;
}, []);
};
Образец:
var state = [
{
name: "Arkansas",
population: "2.978M",
flag:
"https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
category: "city"
},{
name: "Crkansas",
population: "2.978M",
flag:
"https://upload.wikimedia.org/wikipedia/commons/9/9d/Flag_of_Arkansas.svg",
category: "city"
},
{
name: "Balifornia",
population: "39.14M",
flag:
"https://upload.wikimedia.org/wikipedia/commons/0/01/Flag_of_California.svg",
category: "city"
},
{
name: "Florida",
population: "20.27M",
flag:
"https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Florida.svg",
category: "airport"
},
{
name: "Texas",
population: "27.47M",
flag:
"https://upload.wikimedia.org/wikipedia/commons/f/f7/Flag_of_Texas.svg",
category: "landmark"
}
];
console.log(JSON.stringify(groupBy(state,'category','name')));
Ответ 27
Из @mortb, @jmarceli ответ и из этого сообщения,
Я использую JSON.stringify()
как идентификатор для нескольких столбцов группы PRIMITIVE VALUE.
Без сторонних
function groupBy(list, keyGetter) {
const map = new Map();
list.forEach((item) => {
const key = keyGetter(item);
if (!map.has(key)) {
map.set(key, [item]);
} else {
map.get(key).push(item);
}
});
return map;
}
const pets = [
{type:"Dog", age: 3, name:"Spot"},
{type:"Cat", age: 3, name:"Tiger"},
{type:"Dog", age: 4, name:"Rover"},
{type:"Cat", age: 3, name:"Leo"}
];
const grouped = groupBy(pets,
pet => JSON.stringify({ type: pet.type, age: pet.age }));
console.log(grouped);
С Lodash сторонними
const pets = [
{type:"Dog", age: 3, name:"Spot"},
{type:"Cat", age: 3, name:"Tiger"},
{type:"Dog", age: 4, name:"Rover"},
{type:"Cat", age: 3, name:"Leo"}
];
let rslt = _.groupBy(pets, pet => JSON.stringify(
{ type: pet.type, age: pet.age }));
console.log(rslt);
Ответ 28
Я взял этот метод из underscore.js fiddler
window.helpers=(function (){
var lookupIterator = function(value) {
if (value == null){
return function(value) {
return value;
};
}
if (typeof value === 'function'){
return value;
}
return function(obj) {
return obj[value];
};
},
each = function(obj, iterator, context) {
var breaker = {};
if (obj == null) return obj;
if (Array.prototype.forEach && obj.forEach === Array.prototype.forEach) {
obj.forEach(iterator, context);
} else if (obj.length === +obj.length) {
for (var i = 0, length = obj.length; i < length; i++) {
if (iterator.call(context, obj[i], i, obj) === breaker) return;
}
} else {
var keys = []
for (var key in obj) if (Object.prototype.hasOwnProperty.call(obj, key)) keys.push(key)
for (var i = 0, length = keys.length; i < length; i++) {
if (iterator.call(context, obj[keys[i]], keys[i], obj) === breaker) return;
}
}
return obj;
},
// An internal function used for aggregate "group by" operations.
group = function(behavior) {
return function(obj, iterator, context) {
var result = {};
iterator = lookupIterator(iterator);
each(obj, function(value, index) {
var key = iterator.call(context, value, index, obj);
behavior(result, key, value);
});
return result;
};
};
return {
groupBy : group(function(result, key, value) {
Object.prototype.hasOwnProperty.call(result, key) ? result[key].push(value) : result[key] = [value];
})
};
})();
var arr=[{a:1,b:2},{a:1,b:3},{a:1,b:1},{a:1,b:2},{a:1,b:3}];
console.dir(helpers.groupBy(arr,"b"));
console.dir(helpers.groupBy(arr,function (el){
return el.b>2;
}));
Ответ 29
Вы могли бы сделать это следующим образом. Я только что сформировал новый массив и вернул его из функции groupBy. Рассчитанный подсчет от простого цикла с помощью функции .map
var arr = [
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
];
var groupBy = (arr, pahse, step='') => {
var pahseArr = [];
var resultArr = [];
arr.map((item)=>{
var pushed = false;
pahseArr.map((ele)=>{
if(ele===item.Phase){
pushed = true;
}
})
if(!pushed){
pahseArr.push(item.Phase);
}
})
pahseArr.map((item)=>{
var sum = 0;
arr.map((ele)=>{
if(ele.Phase===item){
sum += parseFloat(ele.Value)
}
})
resultArr.push({
Phase: item,
Value: sum
})
})
if(step!=''){
var resultArr = [];
pahseArr.map((item)=>{
var stepArr = [];
arr.map((item2)=>{
var pushed = false;
stepArr.map((ele)=>{
if(ele===item2.Step){
pushed = true;
}
})
if(!pushed){
stepArr.push(item2.Step);
}
})
stepArr.map((item1)=>{
var sum = 0;
arr.map((ele)=>{
if(ele.Step===item1 && ele.Phase===item){
sum += parseFloat(ele.Value)
}
})
resultArr.push({
Phase: item,
Step: item1,
Value: sum
})
})
})
return resultArr;
}
return resultArr;
}
console.log(groupBy(arr, 'Phase'));
console.log(groupBy(arr, 'Phase', 'Step'));
Ответ 30
Я включил принятый ответ, чтобы включить группировку по нескольким свойствам, добавить затем и сделать его чисто функциональным без мутации. См. Демонстрацию по адресу https://stackblitz.com/edit/typescript-ezydzv
export interface Group {
key: any;
items: any[];
}
export interface GroupBy {
keys: string[];
thenby?: GroupBy;
}
export const groupBy = (array: any[], grouping: GroupBy): Group[] => {
const keys = grouping.keys;
const groups = array.reduce((groups, item) => {
const group = groups.find(g => keys.every(key => item[key] === g.key[key]));
const data = Object.getOwnPropertyNames(item)
.filter(prop => !keys.find(key => key === prop))
.reduce((o, key) => ({ ...o, [key]: item[key] }), {});
return group
? groups.map(g => (g === group ? { ...g, items: [...g.items, data] } : g))
: [
...groups,
{
key: keys.reduce((o, key) => ({ ...o, [key]: item[key] }), {}),
items: [data]
}
];
}, []);
return grouping.thenby ? groups.map(g => ({ ...g, items: groupBy(g.items, grouping.thenby) })) : groups;
};