javascript - 使用 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 所做的尝试,尽管我不确定这是解决问题的最佳方法。任何帮助都非常感谢!
解决方案
它将帮助您开始从类型方面考虑您的数据。
aPerson
是具有四个属性的对象,
this
这是一个Stat
对象(我们很快就会定义)that
这是另一个Stat
对象thos
这是一个Rank
对象(我们也将定义它)them
这是另一个Rank
对象
要继续,我们指定Stat
一个具有三个属性的对象,
makes
这是一个数字attempts
这是另一个数字pct
这是另一个数字
最后,Rank
是一个具有两个属性的对象,
value
这是一个数字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
因为a
它b
是c
JavaScriptd
中this
的一个特殊变量,我认为它使示例更容易理解;无论如何,您问题中的数据看起来都被嘲笑了。这里需要注意的重要一点是,该Person.concat
函数依赖于Stat.concat
and 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
只接受makes
andattempts
并且该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.empty
并Rank.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有了基本的了解。
推荐阅读
- javascript - 如何从 .mp3 音乐文件中检测音符,然后显示 midi 时间图
- python - 我无法从后端 Django 检索 OneToOneField 数据
- linux - 如何修复 linter-flake8 中的“Spawn EACCES”错误
- javascript - React 构建在页面上显示 javascript
- javascript - DOM 未使用 Angular 5 使用 EventListener 更新
- http - 在响应 401 UNAUTHORIZED 之前,HTTP Web 服务器必须接受完整的多部分上传吗?
- python - 沿numpy tensordot中最后一个轴的收缩
- aws-amplify - 放大推送产生“您提供的 AWS 访问密钥 ID 不存在于我们的记录中。”
- php - 如何在按钮单击时运行操作而不呈现另一个页面 Yii2?
- c# - 如何解析 KML 文件以从 Placemark 元素中检索坐标点?