Каков наиболее эффективный способ группировки объектов в массиве?
Например, учитывая этот массив объектов:
[
{ 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" }
]
Я отображаю эту информацию в таблице. Я хотел бы сгруппировать разные методы, но я хочу суммировать значения.
Я использую Underscore.js для его функции groupby, которая полезна, но не выполняет весь трюк, потому что я не хочу, чтобы они «разделялись», а «объединялись», больше как group by
метод SQL .
То, что я ищу, сможет суммировать конкретные значения (если требуется).
Так что, если бы я занимался групповым спортом Phase
, я бы хотел получить:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
И если бы я сделал поклонник 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 }
]
Есть ли полезный сценарий для этого, или я должен придерживаться Underscore.js, а затем циклически проходить по полученному объекту, чтобы сделать итоги самостоятельно?
var groupBy = function<TItem>(xs: TItem[], key: string) : {[key: string]: TItem[]} { ...
Использование объекта ES6 Map:
О карте: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
источник
get()
метода? что я хочу, чтобы вывод отображался без передачи ключаconsole.log(grouped.entries());
в примере jsfiddle, он возвращает итерацию, которая ведет себя как массив ключей + значений. Можете ли вы попробовать это и посмотреть, поможет ли это?console.log(Array.from(grouped));
Array.from(groupBy(jsonObj, item => i.type)).map(i => ( {[i[0]]: i[1].length} ))
с ES6:
источник
...result
. Теперь я не могу спать из-за этого.Хотя ответ linq интересен, он также довольно тяжелый. Мой подход несколько иной:
Вы можете увидеть это в действии на JSBin .
Я не видел в Underscore ничего
has
такого, что делает то , что делает, хотя, возможно, я упускаю это. Это почти так же, как_.contains
, но использует,_.isEqual
а не===
для сравнения. Кроме этого, остальная часть этого является специфической для проблемы, хотя с попыткой быть универсальной.Теперь
DataGrouper.sum(data, ["Phase"])
возвращаетсяИ
DataGrouper.sum(data, ["Phase", "Step"])
возвращаетНо здесь
sum
есть только одна потенциальная функция. Вы можете зарегистрировать других, как вам нравится:а теперь
DataGrouper.max(data, ["Phase", "Step"])
вернусьили если вы зарегистрировали это:
тогда звонок
DataGrouper.tasks(data, ["Phase", "Step"])
тебеDataGrouper
сама функция Вы можете позвонить с вашими данными и списком свойств, по которым вы хотите сгруппировать. Он возвращает массив, элементы которого являются объектами с двумя свойствами:key
это набор сгруппированных свойств,vals
это массив объектов, содержащий оставшиеся свойства, отсутствующие в ключе. Например,DataGrouper(data, ["Phase", "Step"])
даст:DataGrouper.register
принимает функцию и создает новую функцию, которая принимает исходные данные и свойства для группировки. Эта новая функция затем принимает выходной формат, как указано выше, и запускает вашу функцию по очереди для каждого из них, возвращая новый массив. Сгенерированная функция сохраняется как свойство вDataGrouper
соответствии с указанным вами именем, а также возвращается, если вам нужна локальная ссылка.Ну, это много объяснений. Надеюсь, код достаточно прост!
источник
Я бы проверил lodash groupBy Похоже, она делает именно то, что вы ищете. Это также довольно легкий и действительно простой.
Пример скрипки: https://jsfiddle.net/r7szvt5k/
При условии, что ваше имя массива является
arr
groupBy с lodash просто:источник
Вероятно, это проще сделать с
linq.js
помощью истинной реализации LINQ в JavaScript ( DEMO ):результат:
Или, проще, используя строковые селекторы ( DEMO ):
источник
GroupBy(function(x){ return x.Phase; })
Вы можете построить ES6
Map
изarray.reduce()
.Это имеет несколько преимуществ перед другими решениями:
_.groupBy()
)Map
а не объект (например, как возвращено_.groupBy()
). Это имеет много преимуществ , в том числе:Map
является более полезным результатом, чем массив массивов. Но если вам нужен массив массивов, вы можете вызватьArray.from(groupedMap.entries())
(для массива[key, group array]
пар) илиArray.from(groupedMap.values())
(для простого массива массивов).В качестве примера последнего пункта представьте, что у меня есть массив объектов, с которыми я хочу выполнить (поверхностное) слияние по id, например:
Чтобы сделать это, я обычно начинаю с группировки по id, а затем объединяю каждый из полученных массивов. Вместо этого вы можете выполнить слияние непосредственно в
reduce()
:источник
a.reduce(function(em, e){em.set(e.id, (em.get(e.id)||[]).concat([e]));return em;}, new Map())
примерно)От: http://underscorejs.org/#groupBy
источник
Вы можете сделать это с помощью библиотеки Alasql JavaScript:
Попробуйте этот пример на jsFiddle .
Кстати: на больших массивах (100000 записей и более) Alasql быстрее, чем Linq. Смотрите тест на jsPref .
Комментарии:
источник
источник
MDN имеет этот пример в своей
Array.reduce()
документации.источник
Хотя на этот вопрос есть несколько ответов, и ответы выглядят немного сложнее, я предлагаю использовать ванильный Javascript для группировки с вложенным (при необходимости)
Map
.источник
Без мутаций:
источник
Это решение принимает любую произвольную функцию (не ключ), поэтому оно более гибкое, чем решения, описанные выше, и позволяет использовать функции стрелок , которые похожи на лямбда-выражения, используемые в LINQ :
ПРИМЕЧАНИЕ: хотите ли вы расширить
Array
прототип, зависит от вас.Пример поддерживается в большинстве браузеров:
Пример использования функций стрелок (ES6):
Оба примера выше возвращают:
источник
let key = 'myKey'; let newGroupedArray = myArrayOfObjects.reduce(function (acc, val) { (acc[val[key]] = acc[val[key]] || []).push(val); return acc;});
Я хотел бы предложить свой подход. Во-первых, отдельная группировка и агрегирование. Давайте объявим прототип «group by». Требуется другая функция, чтобы создать строку «хэш» для каждого элемента массива, по которой нужно сгруппировать.
когда группировка завершена, вы можете агрегировать данные, как вам нужно, в вашем случае
в общем это работает. Я проверил этот код в консоли Chrome. и не стесняйтесь улучшать и находить ошибки;)
источник
map[_hash(key)].key = key;
tomap[_hash(key)].key = _hash(key);
.Представьте, что у вас есть что-то вроде этого:
[{id:1, cat:'sedan'},{id:2, cat:'sport'},{id:3, cat:'sport'},{id:4, cat:'sedan'}]
Делая это:
const categories = [...new Set(cars.map((car) => car.cat))]
Вы получите это:
['sedan','sport']
Объяснение: 1. Сначала мы создаем новый Set, передавая массив. Поскольку Set допускает только уникальные значения, все дубликаты будут удалены.
Установить документ: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set Spread OperatorDoc: https://developer.mozilla.org/en-US/docs/Web/JavaScript / Справочник / Операторы / Spread_syntax
источник
Проверенный ответ - просто мелкая группировка. Это довольно приятно понимать понижение. Вопрос также задает проблема дополнительных совокупных расчетов.
Вот REAL GROUP BY для массива объектов по некоторым полям с 1) вычисляемым именем ключа и 2) полным решением для каскадирования группировки, предоставляя список требуемых ключей и преобразовывая его уникальные значения в корневые ключи, такие как SQL GROUP BY делает.
Поиграйте
estKey
- вы можете группировать по более чем одному полю, добавлять дополнительные агрегации, вычисления или другую обработку.Также вы можете рекурсивно группировать данные. Например, сначала сгруппировать
Phase
, затем поStep
полю и так далее. Дополнительно сдуйте данные об остатках жира.источник
Этот выводит массив.
источник
На основании предыдущих ответов
и это немного лучше смотреть с синтаксисом распространения объекта, если ваша среда поддерживает.
Здесь наш редуктор принимает частично сформированное возвращаемое значение (начиная с пустого объекта) и возвращает объект, состоящий из разложенных членов предыдущего возвращаемого значения, вместе с новым членом, ключ которого вычисляется из значения текущего итери в
prop
и значение которого является списком всех значений для этого реквизита вместе с текущим значением.источник
источник
Вот неприятное, трудно читаемое решение с использованием ES6:
Для тех, кто спрашивает, как это вообще работает, вот объяснение:
В обоих у
=>
вас есть бесплатныйreturn
Array.prototype.reduce
Функция принимает до 4 -х параметров. Вот почему добавляется пятый параметр, поэтому мы можем получить дешевое объявление переменной для группы (k) на уровне объявления параметра, используя значение по умолчанию. (да, это волшебство)Если наша текущая группа не существует на предыдущей итерации, мы создаем новый пустой массив.
((r[k] || (r[k] = []))
Это будет вычислять до крайнего левого выражения, другими словами, существующий массив или пустой массив , поэтому сразуpush
после этого выражения есть, потому что в любом случае вы получите массив.Когда есть
return
,,
оператор запятой отбрасывает крайнее левое значение, возвращая подстроенную предыдущую группу для этого сценария.Более простая версия, которая делает то же самое:
источник
Давайте создадим универсальный
Array.prototype.groupBy()
инструмент. Просто для разнообразия давайте воспользуемся ES6-фантазией, оператором спреда для некоторого совпадения паттернов по Хаскеллесу на рекурсивном подходе. Также давайте сделаем так, чтобы мыArray.prototype.groupBy()
принимали обратный вызов, который принимает item (e
) index (i
) и примененный array (a
) в качестве аргументов.источник
Ответ Ceasar хорош, но работает только для внутренних свойств элементов внутри массива (длина в случае строки).
эта реализация работает больше как: эта ссылка
надеюсь это поможет...
источник
От @mortb, @jmarceli ответ и из этого поста ,
Я пользуюсь тем,
JSON.stringify()
чтобы быть личностью для ПРИМИТИВНОЙ ЦЕННОСТИ нескольких столбцах группы.Без стороннего
С Лодаш сторонним
источник
Версия на
reduce
базе ES6 с поддержкой функцииiteratee
.Работает так же, как и ожидалось, если
iteratee
функция не предусмотрена:В контексте вопроса ОП:
Другая версия ES6, которая меняет группировку и использует
values
askeys
и thekeys
asgrouped values
:Примечание: функции размещены в их краткой форме (одна строка) для краткости и связаны только с идеей. Вы можете расширить их и добавить дополнительную проверку ошибок и т. Д.
источник
Я бы проверил декларативный JS,
groupBy
кажется, он делает именно то, что вы ищете. Это также:источник
источник
Вот версия ES6, которая не будет работать на нулевых участниках
источник
Просто , чтобы добавить к Скотт Sauyet в ответ , некоторые люди спрашивали в комментариях , как использовать свою функцию GroupBy значению1, значение2 и т.д., вместо того , чтобы группировать только одно значение.
Все, что нужно, это отредактировать его функцию суммы:
оставляя основной (DataGrouper) без изменений:
источник
С функцией сортировки
Образец:
источник