试图了解用于对象映射的reduce函数吗?

时间:2019-03-28 18:04:43

标签: javascript node.js angular typescript

我正在通过this tutorial,它正在使用reduce来获取Array<Student>并将其变成{ [key: string]: Array<string | number> },其中包含该表达式。这是我第一次看到这样的内容,因此我想检查一下我是否正确。这是整个表达式:

    export interface Student {
        id: string;
        name: string;
        sex: 'Male' | 'Female';
        standard: number;
        quarterlyScore: number;
        halfyearlyScore: number;
        annualScore: number;
    }

    export function getStudentGraphData(students:Array<Student>): { [key: string]: Array<string |number> } {
        return students.reduce((
            { names: nArray, quarterly: qArray,halfyearly: hArray, annual: aArray },
            { name, quarterlyScore, halfyearlyScore,annualScore }) => {
            return {
                names: [...nArray, name],
                quarterly: [...qArray,quarterlyScore],
                halfyearly: [...hArray,halfyearlyScore],
                annual: [...aArray, annualScore]
            };
        }, { names: [], quarterly: [], halfyearly: [, annual: [] });
    }

IIUC这是我们要减少到的部分(返回值):

 { names: nArray, quarterly: qArray, halfyearly: hArray, annual: aArray }

这是学生对象:

{ name, quarterlyScore, halfyearlyScore, annualScore }

这是实际的减少量。它使用上一步中的数组,并使用spread(...)运算符将其分解为新数组,然后将来自学生对象的参数(如name放在新数组的末尾)

return {
            names: [...nArray, name],
            quarterly: [...qArray, quarterlyScore],
            halfyearly: [...hArray, halfyearlyScore],
            annual: [...aArray, annualScore]
        };

这是返回值的初始值:

{ names: [], quarterly: [], halfyearly: [], annual: [] }

我大致正确吗?

3 个答案:

答案 0 :(得分:2)

  

我大致正确吗?

是的。每次对reduce回调的调用都会创建一个新对象,并将原始对象中的数组扩展为新数组,其中每个对象都具有name(等等)。因此,对于students中的每个条目,代码都会创建并丢弃五个对象(容器对象和四个数组)。

没有所有这些临时对象的for-of版本将是:

const names = [];
const quarterly = [];
const halfyearly = [];
const annual = [];
for (const { name, quarterlyScore, halfyearlyScore, annualScore } of students) {
    names.push(name);
    quarterly.push(quarterlyScore);
    halfyearly.push(halfyearlyScore);
    annual.push(annualScore);
}
return {names, quarterly, halfyearly, annual};

或者,如果map数组的时间不长,以至于您要遍历四遍而不是遍历一次,则可以进行四个students调用(通常不会):

return {
    names: students.map(({name}) => name),
    quarterly: students.map(({quarterlyScore}) => quarterlyScore),
    halfyearly: students.map(({halfyearlyScore}) => halfyearlyScore),
    annual: students.map(({annualScore}) => annualScore)
};

答案 1 :(得分:2)

即使您使用reduce技术,您的解决方案也过于复杂。罪魁祸首是对累加器属性进行重命名,只是在返回新的累加器时才对其重命名。更简单的是只保留累加器名称,尤其是当它们与您的项目属性名称不冲突时。

此版本进行了清理,删除了TS注释(您可能必须添加回去;但它们确实会使事情变得混乱),并用箭头替换外部函数声明:

const getStudentGraphData = (students) => students.reduce( 
   ( { names, quarterly, halfyearly, annual }, 
     { name, quarterlyScore, halfyearlyScore, annualScore }
   ) => ({ 
     names: [...names, name],
     quarterly: [...quarterly, quarterlyScore],
     halfyearly: [...halfyearly, halfyearlyScore],
     annual: [...annual, annualScore]
   }),
   { names: [], quarterly: [], halfyearly: [], annual: [] }
)

const students = [{id: 1, name: 'Barney', sex: 'Male', quarterlyScore: 83, halfyearlyScore: 88, annualScore: 91}, {id: 2, name: 'Betty', sex: 'Female', quarterlyScore: 92, halfyearlyScore: 89, annualScore: 95}, {id: 3, name: 'Fred', sex: 'Male', quarterlyScore: 69, halfyearlyScore: 73, annualScore: 68}, {id: 4, name: 'Wilma', sex: 'Female', quarterlyScore: 85, halfyearlyScore: 78, annualScore: 80}]

console.log(getStudentGraphData(students))

但是有时候正确的抽象可以自行清理。这是另一个版本,其最终工作方式类似于多个map版本,但将其抽象为更具声明性的功能:

const collect = (fields) => (objs) => Object.entries(fields).reduce(
  (a, [k, v]) => ({...a, [k]: objs.map(o => o[v])}),
  {}
)                   

const getStudentGraphData = collect({
  names: 'name',
  quarterly: 'quarterlyScore',
  halfyearly: 'halfyearlyScore',
  annual: 'annualScore',
})

  
const students = [{id: 1, name: 'Barney', sex: 'Male', quarterlyScore: 83, halfyearlyScore: 88, annualScore: 91}, {id: 2, name: 'Betty', sex: 'Female', quarterlyScore: 92, halfyearlyScore: 89, annualScore: 95}, {id: 3, name: 'Fred', sex: 'Male', quarterlyScore: 69, halfyearlyScore: 73, annualScore: 68}, {id: 4, name: 'Wilma', sex: 'Female', quarterlyScore: 85, halfyearlyScore: 78, annualScore: 80}]

console.log(getStudentGraphData(students))

collect函数可能在代码库中的其他地方很有用,但是即使不是,getStudentGraphData现在更具声明性的版本也可能值得添加collect

我在创建collect时遇到的唯一API问题是如何确定目标名称(namesquarterly等)是否应为键和源名称({ {1}},name等),或者相反。感觉稍微正确一些,但是两种版本都使quarterlyScore更加容易理解。

更新

由于某种原因,它一直卡在我的脑海中。我一直在思考像getStudentGraphData这样的API应该具有什么样的API。虽然我对最后一个版本感到非常满意,但这是一个截然不同的版本,并且它不会混淆正在收集的内容以及这些结果称为什么:

collect

这是一种有点不寻常的技术,我只在生产代码中使用过几次,但是效果很好,const collect = field => { let fields = [[field, field]] const fn = (objs) => fields.reduce( (a, [k, v]) => ({...a, [k]: objs.map(o => o[v])}), {} ) fn.and = (field) => { fields.push([field, field]) return fn } fn.as = (field) => { fields[fields.length - 1][0] = field return fn; } return fn; } const getStudentGraphData = collect('id') .and('name') .and('quarterlyScore').as('quarterly') .and('halfyearlyScore').as('halfyearly') .and('annualScore').as('annual') const students = [{id: 1, name: 'Barney', sex: 'Male', quarterlyScore: 83, halfyearlyScore: 88, annualScore: 91}, {id: 2, name: 'Betty', sex: 'Female', quarterlyScore: 92, halfyearlyScore: 89, annualScore: 95}, {id: 3, name: 'Fred', sex: 'Male', quarterlyScore: 69, halfyearlyScore: 73, annualScore: 68}, {id: 4, name: 'Wilma', sex: 'Female', quarterlyScore: 85, halfyearlyScore: 78, annualScore: 80}] console.log(getStudentGraphData(students))的定义几乎可以想象得到。


我对getStudentGraphData的看法是它功能强大且必要,但如果可以使用reducemapfilter,{{ 1}},find等。这些可以清楚地说明您在代码中的工作。 some更像是every循环:通常不言自明。

答案 2 :(得分:1)

  

看到更高效,更优雅的答案很酷。

我猜想,一种更有效(不确定“优雅”的方法)的方法是制作一个 generic 函数,该函数抽象手头的计算并且不将其自身局限于特定的数据结构。例如:

let collect = objects => {

    let res = {};

    for (let obj of objects)
        for (let [k, v] of Object.entries(obj))
            res[k] = (res[k] || []).concat(v)

    return res;
};

//

students = [
    {id:1, name:'1', sex:'X', standard: 1, propName: 11, anotherSillyPropName:111,},
    {id:2, name:'2', sex:'Y', standard: 2, propName: 22, anotherSillyPropName:222,},
    {id:3, name:'3', sex:'Z', standard: 3, propName: 33, anotherSillyPropName:333,},
]


console.log(collect(students))

请注意,我们的collect对“学生”及其“分数”一无所知,它只是对值矩阵进行转置。因此,您可以在需要此类功能的任何地方重用它。

总的来说,如果像getStudentGraphData这样的特定于域的函数的增长往往超过几行,则有力地表明必须将基础计算从特定问题域中分解出来。