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

时间:2019-03-14 04:02:25

标签: javascript ecmascript-6 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 }
  }
]


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是包含人员对象的数组。每个人对象都有几个统计信息(在此示例中,这就是它们),并且每个统计信息都是具有makes, attempts, stealsvalue, rank三重奏的对象。

我想对每个人的统计对象的构成,尝试和值进行求和,并为汇总后的数据计算新的pct值。

尽管我不确定这是否是解决问题的最佳方法,但我将很快发布使用ES6 reduce进行的尝试。任何帮助都将不胜感激!

3 个答案:

答案 0 :(得分:3)

这将帮助您开始根据类型考虑数据。

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

  1. thisStat对象(我们将在稍后定义)
  2. that,这是另一个Stat对象
  3. thosRank对象(我们也会定义)
  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类型相似。您说{em>“没关系” 是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。我将thisthatthosthem重命名为abcd,因为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手动写数据相比,使用{ ... }函数的另一个优点是构造函数可以为您提供帮助。请注意如何自动为pct计算Stat。这样可以防止某人编写无效的统计信息,例如{ makes: 1, attempts: 2, pct: 3 },其中pct属性不等于makes/attempts。如果不检查此数据的接收者,则无法确保pct属性的完整性。另一方面,使用我们的构造函数,Stat.create仅接受makesattempts,并且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

如果我们的代码片段可以在某些数组上工作,但在其他数组上坏了,那就不好了。数组用于表示 zero 或更多值,因此我们希望涵盖所有基础,包括零统计的情况。为所有个输入可能值定义的函数是total function,我们力求以这种方式编写程序。

错误消息提供了解决办法;我们必须提供初始值。例如-

中的0

const add = (x, y) =>
  x + y

console .log
  ( [] .reduce (add, 0)           // 0
  , [ 1, 2, 3 ] .reduce (add, 0)  // 6
  )

使用初始值,即使数组为空,我们也始终会收到正确的结果。对于加号,我们使用初始值零,因为它是identity element。您可以将其视为一种值。

如果我们尝试reduce一组Person类型,那么Person的初始值是什么?

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

如果可能,我们为每种类型定义一个 empty 值。我们将从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有了基本的了解。

答案 1 :(得分:1)

mapreduce之间进行选择时,您可以认为map将返回一个新的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关键字冲突。

答案 2 :(得分:-1)

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 }
  }
}});

我已经忽略了等级,因为您说的没关系,在上述解决方案中,等级始终为0。我还选择了不像将目标对象包装在数组中那样包装结果对象。有关该API的说明,请参见https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/Reduce