Javascript 按对象数组分组的最有效方法
在数组中按对象分组最有效的方法是什么 例如,给定此对象数组:Javascript 按对象数组分组的最有效方法,javascript,arrays,object,group-by,underscore.js,Javascript,Arrays,Object,Group By,Underscore.js,在数组中按对象分组最有效的方法是什么 例如,给定此对象数组: [ { 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 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" }
]
我正在表格中显示此信息。我想按不同的方法分组,但我想求和这些值
我正在使用underline.js作为它的groupby函数,这很有帮助,但并不是全部,因为我不希望它们“分开”而是“合并”,更像SQLgroupby
方法
我要寻找的是能够计算特定值的总和(如果需要的话)
因此,如果我在groupby阶段
,我希望收到:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
如果我做了groupy阶段
/步骤
,我会收到:
[
{ 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 }
]
是否有一个对此有用的脚本,或者我应该坚持使用underline.js,然后通过结果对象循环自己进行合计?这可能更容易完成,它旨在成为JavaScript()中LINQ的真正实现: 结果:
[
{ Phase: "Phase 1", Value: 50 },
{ Phase: "Phase 2", Value: 130 }
]
或者,更简单地使用基于字符串的选择器():
虽然答案很有趣,但也很重要。我的方法有些不同:
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)});
});
你可以看到
我在下划线中没有看到任何与所具有的功能相同的内容,尽管我可能没有看到它。它与.contains
非常相似,但是使用.isEqual
而不是=
进行比较。除此之外,本文的其余部分是针对具体问题的,尽管试图泛化
现在DataGrouper.sum(数据,[“阶段])
返回
[
{Phase: "Phase 1", Value: 50},
{Phase: "Phase 2", Value: 130}
]
[
{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}
]
和DataGrouper.sum(数据,[“阶段”,“步骤])
返回
[
{Phase: "Phase 1", Value: 50},
{Phase: "Phase 2", Value: 130}
]
[
{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(数据,[“阶段”,“步骤])
将返回
[
{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(数据,[“阶段”,“步骤])
将获得
[
{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(数据,[“阶段”,“步骤])
将产生:
[
{
"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
的属性,如果您只需要本地引用,还将返回该函数
好吧,这是很多解释。我希望代码相当简单 我想提出我的方法。首先,将分组和聚合分开。让我们声明原型“groupby”函数。需要另一个函数为每个要分组的数组元素生成“哈希”字符串
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;
});
它的共同点是有效的。我已经在chrome控制台中测试了这段代码。并随时改进和发现错误;)
From:您可以使用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]);
试试这个例子
顺便说一句:在大型阵列(100000条或更多记录)上使用更快的tham Linq。见测试
评论:
- 这里我把值放在方括号中,因为值是SQL中的关键字
- 我必须使用CAST()函数将字符串值转换为数字类型
我会检查它是否完全符合您的要求。它也很轻,非常简单
小提琴示例:
如果您的数组名为arr
,则使用lodash的groupBy仅为:
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
如果希望避免使用外部库,可以简洁地实现groupBy()
的普通版本,如下所示:
var groupBy=function(xs,key){
返回x.reduce(功能(rv,x){
(rv[x[键]]=rv[x[键]| |[])。按(x);
返回rv;
}, {});
};
log(groupBy(['one','two','three'],'length');
//=>{3:[“一”、“两”]、5:[“三”]}
虽然这个问题有一些答案,而且答案看起来有点过于复杂,但我建议使用普通Javascript作为带有嵌套的分组依据(如果必要)
函数groupBy(数组、组、valueKey){
var-map=新映射;
组=[].concat(组);
返回数组.reduce((r,o)=>{
reduce((m,k,i,{length})=>{
var-child;
如果(m.has(o[k])返回m.get(o[k]);
如果(i+1==长度){
子对象
.assign(…groups.map(k=>({[k]:o[k]})),{[valueKey]:0});
r、 推(儿童);
}否则{
child=新地图;
}
m、 set(o[k],子对象);
返回儿童;
},map)[valueKey]+=+o[valueKey];
返回r;
}, [])
};
var数据=[{阶段:“阶段1”,步骤:“步骤1”,任务:“任务1”,值:“5”},{阶段:“阶段1”,步骤:“步骤1”,任务:“任务2”,值:“10”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务1”,值:“15”},{阶段:“阶段1”,步骤:“步骤2”,任务:“任务:“任务2”,值:“20”},{阶段:“阶段:“阶段2”,步骤:“步骤:“步骤1”,任务:“任务1”,值:“25”},{阶段:“阶段2”,步骤:“步骤1”,任务:“任务2”,值:“30”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务1”,值:“35”},{阶段:“阶段2”,步骤:“步骤2”,任务:“任务2”,值:“40”};
log(groupBy(数据,'Phase','Value');
log(groupBy(数据,['Phase','Step'],'Value');
.as控制台包装{max height:100%!重要;top:0;}
使用ES6映射对象:
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]
};
});
};
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
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;
}, []);
}
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"]}
Array.prototype.groupBy = function (funcProp) {
return this.reduce(function (acc, val) {
(acc[funcProp(val)] = acc[funcProp(val)] || []).push(val);
return acc;
}, {});
};
[{a:1,b:"b"},{a:1,c:"c"},{a:2,d:"d"}].groupBy(function(c){return c.a;})
[{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"}]
}
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;
}, {});
};
const groupBy = (items, key) => items.reduce(
(result, item) => ({
...result,
[item[key]]: [
...(result[item[key]] || []),
item,
],
}),
{},
);
const groupedMap = initialArray.reduce(
(entryMap, e) => entryMap.set(e.id, [...entryMap.get(e.id)||[], e]),
new Map()
);
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"}]
const mergedArray = Array.from(
objsToMerge.reduce(
(entryMap, e) => entryMap.set(e.id, {...entryMap.get(e.id)||{}, ...e}),
new Map()
).values()
);
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)}
);
});
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;
}());
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],
}), {});
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);
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);
// 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 }]
// }
const groupBy = <T, K extends keyof any>(list: T[], getKey: (item: T) => K) =>
list.reduce((previous, currentItem) => {
const group = getKey(currentItem);
if (!previous[group]) previous[group] = [];
previous[group].push(currentItem);
return previous;
}, {} as Record<K, T[]>);
import { Reducers } from 'declarative-js';
import groupBy = Reducers.groupBy;
import Map = Reducers.Map;
const 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" }
];
data.reduce(groupBy(element=> element.Step), Map());
data.reduce(groupBy('Step'), Map());
function agg(data, indices, reducer) {
// helper to create unique index as an array
function getUniqueIndexHash(row, indices) {
return indices.reduce((acc, curr) => acc + row[curr], "");
}
// reduce data to single object, whose values will be each of the new rows
// structure is an object whose values are arrays
// [{}] -> {{}}
// no operation performed, simply grouping
let groupedObj = data.reduce((acc, curr) => {
let currIndex = getUniqueIndexHash(curr, indices);
// if key does not exist, create array with current row
if (!Object.keys(acc).includes(currIndex)) {
acc = {...acc, [currIndex]: [curr]}
// otherwise, extend the array at currIndex
} else {
acc = {...acc, [currIndex]: acc[currIndex].concat(curr)};
}
return acc;
}, {})
// reduce the array into a single object by applying the reducer
let reduced = Object.values(groupedObj).map(arr => {
// for each sub-array, reduce into single object using the reducer function
let reduceValues = arr.reduce(reducer, {});
// reducer returns simply the aggregates - add in the indices here
// each of the objects in "arr" has the same indices, so we take the first
let indexObj = indices.reduce((acc, curr) => {
acc = {...acc, [curr]: arr[0][curr]};
return acc;
}, {});
reduceValues = {...indexObj, ...reduceValues};
return reduceValues;
});
return reduced;
}
reducer = (acc, curr) => {
acc.count = 1 + (acc.count || 0);
acc.value = +curr.Value + (acc.value|| 0);
return acc;
}
agg(tasks, ["Phase"], reducer);
// yields:
Array(2) [
0: Object {Phase: "Phase 1", count: 4, value: 50}
1: Object {Phase: "Phase 2", count: 4, value: 130}
]
agg(tasks, ["Phase", "Step"], reducer);
// yields:
Array(4) [
0: Object {Phase: "Phase 1", Step: "Step 1", count: 2, value: 15}
1: Object {Phase: "Phase 1", Step: "Step 2", count: 2, value: 35}
2: Object {Phase: "Phase 2", Step: "Step 1", count: 2, value: 55}
3: Object {Phase: "Phase 2", Step: "Step 2", count: 2, value: 75}
]