繁体   English   中英

使用ES6 reduce或map来计算Array的嵌套对象中的总和和百分比

[英]Use ES6 reduce or map to compute sums and percentages in nested objects of an Array

如果标题不是特别清楚,请道歉。 我在下面简要介绍了我正在尝试做的事情:

var arrayOfPeopleStats = [
  person1 = { 
    this: { makes: 10, attempts: 15, pct: .666667 },
    that: { makes: 12, attempts: 16, pct: .75 },
    thos: { value: 10, rank: 24 },
    them: { value: 15, rank: 11 }
  },
  person2 = { 
    this: { makes: 5, attempts: 15, pct: .333333 },
    that: { makes: 10, attempts: 12, pct: .83333 },
    thos: { value: 4, rank: 40 },
    them: { value: 3, rank: 32 }
  },
  person3 = { 
    this: { makes: 14, attempts: 14, pct: 1 },
    that: { makes: 7, attempts: 8, pct: .875 },
    thos: { value: 12, rank: 12 },
    them: { value: 13, rank: 10 }
  }
]


var desiredOutput = [
  allPeople: {
    this: { makes: 29, attempts: 44, pct: .65909 },
    that: { makes: 29, attempts: 36, pct: .80555 },
    thos: { value: 26, rank: doesnt matter },
    them: { value: 31, rank: doesnt matter }
  }
]

arrayOfPeopleStats是一个包含person对象的数组。 每个人对象都有一些统计数据(在这个例子中,这个,他们,在这个例子中),并且这些统计数据中的每一个都是具有三个makes, attempts, stealsvalue, rank

我想对每个人的stat对象求和,尝试和值,并计算总和数据的新pct值。

我将很快发布我使用ES6 reduce完成的尝试,虽然我不确定这是解决问题的最佳方法。 任何帮助都非常感谢!

它将帮助您开始考虑类型方面的数据。

一个Person是一个有四个属性的对象,

  1. this是一个Stat对象(我们很快就会定义)
  2. that是另一个Stat对象
  3. thos这是一个Rank的对象(我们定义为好)
  4. them是另一个Rank对象

要继续,我们将Stat指定为具有三个属性的对象,

  1. makes这是一个数字
  2. attempts是另一个数字
  3. pct是另一个号码

最后, Rank是一个具有两个属性的对象,

  1. value是一个数字
  2. rank是另一个数字

我们现在可以将arrayOfPeopleStats视为一个项目数组,其中每个项目都是Person类型。 为了组合类似的类型,我们定义了一种create类型值的方法以及concat (添加)它们的方法。

我将首先定义较小的类型, Stat -

const Stat =
  { create: (makes, attempts) =>
      ({ makes, attempts, pct: makes / attempts })

  , concat: (s1, s2) =>
      Stat .create
        ( s1.makes + s2.makes
        , s1.attempts + s2.attempts
        )
  }

Rank类型类似。 你说rank的总和是“无所谓” ,但这表明你必须做出选择。 您可以选择一个或另一个,将两个值一起添加,或完全添加其他选项。 在此实现中,我们选择最高rank值 -

const Rank =
  { create: (value, rank) =>
      ({ value, rank })

  , concat: (r1, r2) =>
      Rank .create
        ( r1.value + r2.value
        , Math .max (r1.rank, r2.rank)
        ) 
  }

最后,我们实施Person 我改名thisthatthosthemabcd ,因为this是在JavaScript中一个特殊的变量,我认为它使例子更容易一些遵循; 无论如何,你问题中的数据看起来都是嘲笑的。 这里需要注意的是Person.concat函数依赖于Stat.concatRank.concat ,保持每个类型的组合逻辑很好地分开 -

const Person =
  { create: (a, b, c, d) =>
      ({ a, b, c, d })

  , concat: (p1, p2) =>
      Person .create
        ( Stat .concat (p1.a, p2.a)
        , Stat .concat (p1.b, p2.b)
        , Rank .concat (p1.c, p2.c)
        , Rank .concat (p1.d, p2.d)
        )
  }

现在要组合数组中的所有person项,只需减少使用Person.concat操作 -

arrayOfPeopleStat .reduce (Person.concat)

// { a: { makes: 29, attempts: 44, pct: 0.6590909090909091 }
// , b: { makes: 29, attempts: 36, pct: 0.8055555555555556 }
// , c: { value: 26, rank: 40 }
// , d: { value: 31, rank: 32 }
// }

注意,当你有一个很好定义的数据构造函数时,使用{ ... }手工编写数据很麻烦且容易出错。 而是使用您的类型的create功能 -

var arrayOfPeopleStat =
  [ Person .create
      ( Stat .create (10, 15)
      , Stat .create (12, 16)
      , Rank .create (10, 24)
      , Rank .create (15, 11)
      )
  , Person .create
      ( Stat .create (5, 15)
      , Stat .create (10, 12)
      , Rank .create (4, 40)
      , Rank .create (3, 32)
      )
  , Person .create
      ( Stat .create (14, 14)
      , Stat .create (7, 8)
      , Rank .create (12, 12)
      , Rank .create (13, 10)
      )
  ]

使用create函数而不是使用{ ... }手动写出数据的另一个好处是构造函数可以为您提供帮助。 请注意如何自动为Stat计算pct 这可以防止有人编写无效的统计信息,例如{ makes: 1, attempts: 2, pct: 3 } ,其中pct属性不等于makes/attempts 如果此数据的接收方未检查,我们无法确保pct属性的完整性。 在另一方面利用我们的构造, Stat.create只接受makesattemptspct领域是自动计算的,保证正确的pct值。

构造函数还可以阻止某人创建会导致错误的stat,例如Stat .create (10, 0) ,它会尝试为pct属性计算10/0 (除零错误) -

const Stat =
  { create: (makes = 0, attempts = 0) =>
      attempts === 0
        ? { makes, attempts, pct: 0 }                // instead of throwing error
        : { makes, attempts, pct: makes / attempts } // safe to divide

  , concat: ...
  }

最终,这些选择取决于您。 另一位程序员可能会在这种情况下支持错误。 如 -

Stat .create (10, 0)
// Error: invalid Stat. makes (10) cannot be greater than attempts (0). attempts cannot be zero.

reduce的结果当然是相同的 -

arrayOfPeopleStat .reduce (Person.concat)

// { a: { makes: 29, attempts: 44, pct: 0.6590909090909091 }
// , b: { makes: 29, attempts: 36, pct: 0.8055555555555556 }
// , c: { value: 26, rank: 40 }
// , d: { value: 31, rank: 32 }
// }

展开下面的代码段以在您自己的浏览器中验证结果 -

 const Stat = { create: (makes, attempts) => ({ makes, attempts, pct: makes / attempts }) , concat: (s1, s2) => Stat .create ( s1.makes + s2.makes , s1.attempts + s2.attempts ) } const Rank = { create: (value, rank) => ({ value, rank }) , concat: (r1, r2) => Rank .create ( r1.value + r2.value , Math .max (r1.rank, r2.rank) ) } const Person = { create: (a, b, c, d) => ({ a, b, c, d }) , concat: (p1, p2) => Person .create ( Stat .concat (p1.a, p2.a) , Stat .concat (p1.b, p2.b) , Rank .concat (p1.c, p2.c) , Rank .concat (p1.d, p2.d) ) } var data = [ Person .create ( Stat .create (10, 15) , Stat .create (12, 16) , Rank .create (10, 24) , Rank .create (15, 11) ) , Person .create ( Stat .create (5, 15) , Stat .create (10, 12) , Rank .create (4, 40) , Rank .create (3, 32) ) , Person .create ( Stat .create (14, 14) , Stat .create (7, 8) , Rank .create (12, 12) , Rank .create (13, 10) ) ] console .log (data .reduce (Person.concat)) // { a: { makes: 29, attempts: 44, pct: 0.6590909090909091 } // , b: { makes: 29, attempts: 36, pct: 0.8055555555555556 } // , c: { value: 26, rank: 40 } // , d: { value: 31, rank: 32 } // } 


但是如果data是空数组会发生什么?

[] .reduce (Person.concat)
// Uncaught TypeError: Reduce of empty array with no initial value

如果我们的代码中的一段代码适用于某些数组但会破坏其他数组,那就太糟糕了。 数组用于表示零个或多个值,因此我们希望涵盖所有基础,包括零统计的情况。 为其输入的所有可能值定义的函数是一个总函数 ,我们尽可能地以这种方式编写程序。

错误消息为修复提供了智慧; 我们必须提供初始值 比如0 in -

 const add = (x, y) => x + y console .log ( [] .reduce (add, 0) // 0 , [ 1, 2, 3 ] .reduce (add, 0) // 6 ) 

使用初始值,即使数组为空,我们也总是收到正确的结果。 为了添加数字,我们使用零的初始值,因为它是标识元素 您可以将此视为一种值。

如果我们试图reduce Person类型的数组,那么Person的初始值是多少?

arrayOfPeopleStat .reduce (Person.concat, ???)

如果可能,我们为每个类型定义一个值。 我们将从Stat开始 -

const Stat = 
  { empty:
      { makes: 0
      , attempts: 0
      , pct: 0
      }
  , create: ...
  , concat: ...
  }

接下来我们会做Rank -

const Rank =
  { empty:
      { value: 0
      , rank: 0
      }
  , create: ...
  , concat: ...
  }

同样,我们将Person视为复合数据 ,即由其他数据构建的数据。 我们依靠Stat.emptyRank.empty来创建Person.empty -

const Person =
  { empty:
      { a: Stat.empty
      , b: Stat.empty
      , c: Rank.empty
      , d: Rank.empty
      }
  , create: ...
  , concat: ...
  }

现在我们可以指定Person.empty作为初始值,并防止加剧错误弹出 -

arrayOfPeopleStat .reduce (Person.concat, Person.empty)
// { a: { makes: 29, attempts: 44, pct: 0.6590909090909091 }
// , b: { makes: 29, attempts: 36, pct: 0.8055555555555556 }
// , c: { value: 26, rank: 40 }
// , d: { value: 31, rank: 32 }
// }

[] .reduce (Person.concat, Person.empty)
// { a: { makes: 0, attempts: 0, pct: 0 }
// , b: { makes: 0, attempts: 0, pct: 0 }
// , c: { value: 0, rank: 0 }
// , d: { value: 0, rank: 0 }
// }

作为奖励,您现在对幺半群有了基本的了解。

mapreduce之间进行决定时,您可以认为map将返回一个与原始 array 长度相同的新array 这不是你想要的,所以你不能使用它。 实际上,您希望将数组减少为对象。 所以只需使用reduce

关于减少它的功能,这里有一个可能的:

 var arrayOfPeopleStats=[person1={this:{makes:10,attempts:15,pct:.666667},that:{makes:12,attempts:16,pct:.75},thos:{value:10,rank:24},them:{value:15,rank:11}},person2={this:{makes:5,attempts:15,pct:.333333},that:{makes:10,attempts:12,pct:.83333},thos:{value:4,rank:40},them:{value:3,rank:32}},person3={this:{makes:14,attempts:14,pct:1},that:{makes:7,attempts:8,pct:.875},thos:{value:12,rank:12},them:{value:13,rank:10}}]; const resp = Object.values(arrayOfPeopleStats).reduce((obj, { this: localThis, that, thos, them }) => { obj.this = { makes: obj.this.makes + localThis.makes, attempts: obj.this.attempts + localThis.attempts }; obj.this.pct = obj.this.makes / obj.this.attempts; obj.that = { makes: obj.that.makes + that.makes, attempts: obj.that.attempts + that.attempts }; obj.that.pct = obj.that.makes / obj.that.attempts; obj.thos = { value: obj.thos.value + thos.value, rank: obj.thos.rank + thos.rank }; obj.them = { value: obj.them.value + them.value, rank: obj.thos.rank + them.rank }; return obj; }) const formattedResp = [{ allPeople: resp }] console.log(formattedResp) 

一旦它与this关键字冲突,我建议您不要将this用作属性名称。

arrayOfPeopleStats.reduce((a, v) => {
  a.allPeople.this = {
    makes: a.allPeople.this.makes + v.this,
    attempts: a.allPeople.this.attempts + v.this.attempts,
  };
  a.allPeople.that = {
    makes: a.allPeople.that.makes + v.that.makes,
    attempts: a.allPeople.that.attempts + v.that.attempts,
  };
  a.allPeople.thos = {
    value: a.allPeople.thos.value + v.thos.value,
  };
  a.allPeople.them = {
    value: a.allPeople.them.value + v.them.value,
  };
  a.allPeople.this.pct = a.allPeople.this.makes / a.allPeople.this.attempts
  a.allPeople.that.pct = a.allPeople.that.makes / a.allPeople.that.attempts
  return a;
}, {allPeople: {
    this: { makes: 0, attempts: 0, pct: 0 },
    that: { makes: 0, attempts: 0, pct: 0 },
    thos: { value: 0, rank: 0 },
    them: { value: 0, rank: 0 }
  }
}});

我已经忽略了排名,因为你已经说过没关系,它总是会出现上述解决方案。 我还选择不像你在你想要的结果中那样将结果对象包装在一个数组中。 有关此API的说明,请参阅https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM