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 _.isEqual
chứ 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 sum
chỉ 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)"}
]
DataGrouper
chí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: key
là tập hợp các thuộc tính được nhóm, vals
là 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.register
chấ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 DataGrouper
theo 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!