首页 > 解决方案 > 使用 ES6 reduce 或 map 计算数组嵌套对象的总和和百分比

问题描述

如果标题不是特别清楚,请致歉。我在下面做了一个简短的可重复的例子来说明我正在尝试做的事情:

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 对象都有几个统计信息(在本例中为 this、that thos、them),每个统计信息都是一个包含 或 的三重奏的makes, attempts, steals对象value, rank

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

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

标签: javascriptecmascript-6reduce

解决方案


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

aPerson是具有四个属性的对象,

  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. 我将 , , , 重命名为this, that, thos,them因为abcJavaScriptdthis的一个特殊变量,我认为它使示例更容易理解;无论如何,您问题中的数据看起来都被嘲笑了。这里需要注意的重要一点是,该Person.concat函数依赖于Stat.concatand Rank.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函数而不是手动写出数据的另一个优点{ ... }是构造函数可以为您提供帮助。请注意如何pct自动计算Stat. 这可以防止某人写入无效的统计信息{ makes: 1, attempts: 2, pct: 3 },例如pct属性不等于makes/attempts。如果这些数据的接收者不检查,我们就无法确保pct财产的完整性。另一方面,使用我们的构造函数,Stat.create只接受makesandattempts并且该pct字段被自动计算,保证一个正确的pct值。

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

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在——

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 }
// }

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


推荐阅读