虽然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 上看到它的实际效果:http://jsbin.com/usepej/1/edit。
我没有在 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
的属性,并且如果只想要一个本地引用,也会返回该函数。
好吧,这是很多解释。不过代码相当简单,我希望如此!
function groupBy(data, key){ return data.reduce( (acc, cur) => { acc[cur[key]] = acc[cur[key]] || []; // 如果键是新的,则将其值初始化为数组,否则保留其自己的数组值 acc[cur[key]].push(cur); return acc; } , []) }
- aderchox