在重复使用已经编写好的代码(即Underscore)的同时,我们希望全面回答原始问题。如果您结合其100多个函数,您可以做更多事情。以下解决方案演示了这一点。
步骤1:按任意组合的属性将数组中的对象分组。这利用了
_.groupBy
接受返回对象组的函数的事实。它还使用了
_.chain
,
_.pick
,
_.values
,
_.join
和
_.value
。请注意,
_.value
在此处并非严格必要,因为链接值在用作属性名称时将自动解包。我包含它是为了防止混淆,以防有人尝试在不发生自动解包的情况下编写类似的代码。
function category(obj) {
return _.chain(obj).pick(propertyNames).values().join(' ').value();
}
const intermediate = _.groupBy(arrayOfObjects, category);
假设原问题中有一个arrayOfObjects
,并将propertyNames
设置为['Phase', 'Step']
,那么intermediate
将得到以下值:
{
"Phase 1 Step 1": [
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 1", Value: "5" },
{ Phase: "Phase 1", Step: "Step 1", Task: "Task 2", Value: "10" }
],
"Phase 1 Step 2": [
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 1", Value: "15" },
{ Phase: "Phase 1", Step: "Step 2", Task: "Task 2", Value: "20" }
],
"Phase 2 Step 1": [
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 1", Value: "25" },
{ Phase: "Phase 2", Step: "Step 1", Task: "Task 2", Value: "30" }
],
"Phase 2 Step 2": [
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 1", Value: "35" },
{ Phase: "Phase 2", Step: "Step 2", Task: "Task 2", Value: "40" }
]
}
步骤2:将每个组缩减为单个扁平对象,并在数组中返回结果。除了我们之前看到的函数外,以下代码还使用了
_.pluck
,
_.first
,
_.pick
,
_.extend
,
_.reduce
和
_.map
。在这种情况下,
_.first
保证返回一个对象,因为
_.groupBy
不会产生空组。此时需要使用
_.value
。
const addNumeric = (a, b) => +a + +b;
function summarize(group) {
const valuesToSum = _.pluck(group, aggregateProperty);
return _.chain(group).first().pick(propertyNames).extend({
[aggregateProperty]: _.reduce(valuesToSum, addNumeric)
}).value();
}
const result = _.map(intermediate, summarize);
给定之前获得的“ intermediate ”,并将“ aggregateProperty ”设置为“ Value ”,我们得到了问题提问者所需的“ result ”:
[
{ 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 }
]
我们可以将所有内容放在一个函数中,该函数以
arrayOfObjects
,
propertyNames
和
aggregateProperty
作为参数。请注意,
arrayOfObjects
实际上也可以是具有字符串键的普通对象,因为
_.groupBy
接受任何一种类型。因此,我已将
arrayOfObjects
重命名为
collection
。
function aggregate(collection, propertyNames, aggregateProperty) {
function category(obj) {
return _.chain(obj).pick(propertyNames).values().join(' ');
}
const addNumeric = (a, b) => +a + +b;
function summarize(group) {
const valuesToSum = _.pluck(group, aggregateProperty);
return _.chain(group).first().pick(propertyNames).extend({
[aggregateProperty]: _.reduce(valuesToSum, addNumeric)
}).value();
}
return _.chain(collection).groupBy(category).map(summarize).value();
}
aggregate(arrayOfObjects, ['Phase', 'Step'], 'Value')
现在会再次给我们相同的result
。
我们可以更进一步,使调用者能够计算每个组中值的任何统计信息。我们可以做到这一点,还可以使调用者向每个组的摘要添加任意属性。我们可以在代码更短的情况下完成所有这些操作。我们通过将aggregateProperty
参数替换为iteratee
参数并将其直接传递给_.reduce
来实现这一点:
function aggregate(collection, propertyNames, iteratee) {
function category(obj) {
return _.chain(obj).pick(propertyNames).values().join(' ');
}
function summarize(group) {
return _.chain(group).first().pick(propertyNames)
.extend(_.reduce(group, iteratee)).value();
}
return _.chain(collection).groupBy(category).map(summarize).value();
}
实际上,我们将一些责任转移给调用者;她必须提供一个可以传递给
_.reduce
的
iteratee
,以便调用
_.reduce
将生成一个带有她想要添加的聚合属性的对象。例如,我们使用以下表达式获得与之前相同的
result
:
aggregate(arrayOfObjects, ['Phase', 'Step'], (memo, value) => ({
Value: +memo.Value + +value.Value
}));
举个稍微复杂一些的iteratee
的例子,假设我们想计算每个组的最大Value
而不是总和,并且我们想添加一个Tasks
属性来列出组中出现的所有Task
的值。下面是一种使用上面提到的aggregate
的最后一个版本(以及_.union
)的方法:
aggregate(arrayOfObjects, ['Phase', 'Step'], (memo, value) => ({
Value: Math.max(memo.Value, value.Value),
Tasks: _.union(memo.Tasks || [memo.Task], [value.Task])
}));
我们得到以下结果:
[
{ Phase: "Phase 1", Step: "Step 1", Value: 10, Tasks: [ "Task 1", "Task 2" ] },
{ Phase: "Phase 1", Step: "Step 2", Value: 20, Tasks: [ "Task 1", "Task 2" ] },
{ Phase: "Phase 2", Step: "Step 1", Value: 30, Tasks: [ "Task 1", "Task 2" ] },
{ Phase: "Phase 2", Step: "Step 2", Value: 40, Tasks: [ "Task 1", "Task 2" ] }
]
感谢@much2learn提供了一个答案,可以处理任意的归约函数。我写了更多的SO答案,演示了如何通过组合多个Underscore函数来实现复杂的操作:
function groupBy(data, key){ return data.reduce( (acc, cur) => { acc[cur[key]] = acc[cur[key]] || []; // 如果键是新的,则将其值初始化为数组,否则保留其自己的数组值 acc[cur[key]].push(cur); return acc; } , []) }
- aderchox