배열에서 객체를 그룹화하는 가장 효율적인 방법은 무엇입니까?
예를 들어,이 객체 배열이 주어진 경우 :
[
{ 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 함수에 사용하고 있습니다. 이는 도움이되지만 SQL group by
메서드 와 같이 “분할”하지만 “병합”되기를 원하지 않기 때문에 모든 트릭을 수행하지는 않습니다 .
내가 찾고있는 것은 특정 값을 합계 할 수있을 것입니다 (요청한 경우).
따라서 groupby를 수행 Phase
하면 다음을 수신하고 싶습니다.
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
그리고 groupy 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를 사용하고 결과 객체를 반복하여 총계를 직접 수행해야합니까?
답변
외부 라이브러리를 피하려면 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"]}
답변
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"}]
지도 정보 :
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
답변
ES6 사용시 :
const groupBy = (items, key) => items.reduce(
(result, item) => ({
...result,
[item[key]]: [
...(result[item[key]] || []),
item,
],
}),
{},
);
답변
있지만 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
제공 한 이름 에 따라 속성으로 저장되며 로컬 참조를 원할 경우 반환됩니다.
글쎄요, 그것은 많은 설명입니다. 코드는 매우 간단합니다.
답변
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
답변
이것은 아마도 더 쉽게 이루어집니다 linq.js
자바 스크립트에서 LINQ의 진정한 구현 (의도 된, 데모 ) :
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();
답변
에서 ES6 Map
을 빌드 할 수 있습니다 array.reduce()
.
const groupedMap = initialArray.reduce(
(entryMap, e) => entryMap.set(e.id, [...entryMap.get(e.id)||[], e]),
new Map()
);
이것은 다른 솔루션보다 몇 가지 장점이 있습니다.
- 라이브러리가 필요하지 않습니다 (예 :
_.groupBy()
) Map
객체가 아닌 자바 스크립트를받습니다 (예 :에서 반환_.groupBy()
). 이것은 많은 이점이 있습니다 .- 항목이 처음 추가 된 순서를 기억합니다.
- 키는 문자열이 아닌 모든 유형이 될 수 있습니다.
- A
Map
는 배열 배열보다 유용한 결과입니다. 그러나 배열 배열을 원하면Array.from(groupedMap.entries())
([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()
);