Mặc dù câu trả lời linq rất thú vị, nhưng nó cũng khá nặng. Cách tiếp cận của tôi có hơi khác:
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)});
});
Bạn có thể thấy nó hoạt động trên JSBin .
Tôi đã không thấy bất cứ điều gì trong Underscore làm những gì has, mặc dù tôi có thể thiếu nó. Nó giống như _.contains, nhưng sử dụng _.isEqualchứ không phải ===để so sánh. Ngoài ra, phần còn lại của vấn đề này là dành riêng cho vấn đề, mặc dù với một nỗ lực chung chung.
Bây giờ DataGrouper.sum(data, ["Phase"])trở lại
[
{Phase: "Phase 1", Value: 50},
{Phase: "Phase 2", Value: 130}
]
Và DataGrouper.sum(data, ["Phase", "Step"])trả lại
[
{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}
]
Nhưng sumchỉ có một chức năng tiềm năng ở đây. Bạn có thể đăng ký người khác như bạn muốn:
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)});
});
và bây giờ DataGrouper.max(data, ["Phase", "Step"])sẽ trở lại
[
{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}
]
hoặc nếu bạn đã đăng ký này:
DataGrouper.register("tasks", function(item) {
return _.extend({}, item.key, {Tasks: _.map(item.vals, function(item) {
return item.Task + " (" + item.Value + ")";
}).join(", ")});
});
sau đó gọi DataGrouper.tasks(data, ["Phase", "Step"])sẽ giúp bạn
[
{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)"}
]
DataGrouperchính nó là một chức năng. Bạn có thể gọi nó với dữ liệu của bạn và một danh sách các thuộc tính bạn muốn nhóm theo. Nó trả về một mảng có các phần tử là đối tượng có hai thuộc tính: keylà tập hợp các thuộc tính được nhóm, valslà một mảng các đối tượng chứa các thuộc tính còn lại không có trong khóa. Ví dụ: DataGrouper(data, ["Phase", "Step"])sẽ mang lại:
[
{
"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.registerchấp nhận một chức năng và tạo ra một chức năng mới chấp nhận dữ liệu ban đầu và các thuộc tính để nhóm theo. Hàm mới này sau đó lấy định dạng đầu ra như trên và lần lượt chạy hàm của bạn theo từng hàm, trả về một mảng mới. Hàm được tạo được lưu trữ dưới dạng một thuộc tính DataGroupertheo tên bạn cung cấp và cũng được trả về nếu bạn chỉ muốn tham chiếu cục bộ.
Vâng, đó là rất nhiều lời giải thích. Mã này là hợp lý đơn giản, tôi hy vọng!