Ramda.js - 如何从嵌套数组中查看多个值
Ramda.js - how to view many values from a nested array
我有这个代码:
import {compose, view, lensProp, lensIndex, over, map} from "rambda";
let order = {
lineItems:[
{name:"A", total:33},
{name:"B", total:123},
{name:"C", total:777},
]
};
let lineItems = lensProp("lineItems");
let firstLineItem = lensIndex(0);
let total = lensProp("total");
我的目标是获得所有 lineItems
中的所有 totals
(因为我想对它们求和)。我是这样逐步解决这个问题的:
console.log(view(lineItems, order)); // -> the entire lineItems array
console.log(view(compose(lineItems, firstLineItem), order)); // -> { name: 'A', total: 33 }
console.log(view(compose(lineItems, firstLineItem, total), order)); // -> 33
但我想不出正确的表达式来取回总计数组
console.log(view(?????, order)); // -> [33,123,777]
这是我的问题 - ?????
在哪里?
我通过这样做来弥补我的无知:
let collector = [];
function collect(t) {
collector.push(t);
}
over(lineItems, map(over(total, collect)), order);
console.log(collector); // -> [33,123,777]
但我相信 ramda 本地人知道如何做得更好。
您想在这里使用镜头有什么特别的原因吗?不要误会我的意思;镜片不错,但它们似乎对您的情况没有太大帮助。
最终这就是你试图完成的(据我所知):
map(prop('total'), order.lineItems)
您可以通过以下方式稍微重构一下:
const get_total = compose(map(prop('total')), propOr([], 'lineItems'));
get_total(order);
您可以使用 R.pluck 从对象数组中获取值数组:
const order = {"lineItems":[{"name":"A","total":33},{"name":"B","total":123},{"name":"C","total":777}]};
const result = R.pluck('total', order.lineItems);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
可以使用镜头(遍历)实现此目的,但可能不值得额外的复杂性。
我们的想法是,我们可以将 R.traverse
与 Const
类型的应用实例一起使用,作为可与镜头组合并将零个或多个目标组合在一起的东西。
Const
类型允许您包装一个在映射时不会改变的值(即它保持不变)。我们如何将两个常量值组合在一起以支持应用 ap
?我们要求常量值有一个幺半群实例,这意味着它们是可以组合在一起的值,并且有一些值表示一个空实例(例如,两个列表可以连接起来,空列表是空实例,两个数字可以添加零是空实例等)
const Const = x => ({
value: x,
map: function (_) { return this },
ap: other => Const(x.concat(other.value))
})
接下来我们可以创建一个函数,让我们以不同的方式组合镜头目标,具体取决于提供的将目标值包装在某个幺半群实例中的函数。
const foldMapOf = (theLens, toMonoid) => thing =>
theLens(compose(Const, toMonoid))(thing).value
这个函数将像 R.view
和 R.over
一样使用,接受一个镜头作为它的第一个参数,然后一个函数将目标包装在一个将值组合在一起的幺半群实例中.最后它接受了你想用镜头钻进去的东西。
接下来我们将创建一个简单的辅助函数,可用于创建我们的遍历,捕获将用于聚合最终目标的幺半群类型。
const aggregate = empty => traverse(_ => Const(empty))
这是一个不幸的泄漏,我们需要知道在编写遍历时最终结果将如何聚合,而不是简单地知道它是需要遍历的东西。其他语言可以使用静态类型来推断此信息,但如果不更改镜头在 Ramda 中的定义方式,JS 就没有这样的运气。
鉴于你提到你想将目标加在一起,我们可以创建一个 monoid 实例来执行此操作。
const Sum = x => ({
value: x,
concat: other => Sum(x + other.value)
})
这只是说您可以将两个数字组合在一起,当合并时,它们将产生一个新的 Sum
,其中包含将它们加在一起的值。
我们现在拥有将它们组合在一起所需的一切。
const sumItemTotals = order => foldMapOf(
compose(
lensProp('lineItems'),
aggregate(Sum(0)),
lensProp('total')
),
Sum
)(order).value
sumItemTotals({
lineItems: [
{ name: "A", total: 33 },
{ name: "B", total: 123 },
{ name: "C", total: 777 }
]
}) //=> 933
如果你只是想提取一个列表而不是直接对它们求和,我们可以使用 monoid 实例代替列表(例如 [].concat
)。
const itemTotals = foldMapOf(
compose(
lensProp('lineItems'),
aggregate([]),
lensProp('total')
),
x => [x]
)
itemTotals({
lineItems: [
{ name: "A", total: 33 },
{ name: "B", total: 123 },
{ name: "C", total: 777 }
]
}) //=> [33, 123, 777]
根据您对customcommander 的回答的评论,我认为您可以将此写得相当简单。我不知道你是如何接收你的模式的,但是如果你可以将你的 lineItems
节点的路径转换成一个字符串数组,那么你就可以编写一个相当简单的函数:
const lineItemTotal = compose (sum, pluck ('total'), path)
let order = {
path: {
to: {
lineItems: [
{name: "A", total: 33},
{name: "B", total: 123},
{name: "C", total: 777},
]
}
}
}
console .log (
lineItemTotal (['path', 'to', 'lineItems'], order)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {compose, sum, pluck, path} = R </script>
您可以围绕此包裹 curry
并使用 lineItemTotal (['path', 'to', 'lineItems']) (order)
调用生成的函数,可能会保存中间函数以供重用。
我有这个代码:
import {compose, view, lensProp, lensIndex, over, map} from "rambda";
let order = {
lineItems:[
{name:"A", total:33},
{name:"B", total:123},
{name:"C", total:777},
]
};
let lineItems = lensProp("lineItems");
let firstLineItem = lensIndex(0);
let total = lensProp("total");
我的目标是获得所有 lineItems
中的所有 totals
(因为我想对它们求和)。我是这样逐步解决这个问题的:
console.log(view(lineItems, order)); // -> the entire lineItems array
console.log(view(compose(lineItems, firstLineItem), order)); // -> { name: 'A', total: 33 }
console.log(view(compose(lineItems, firstLineItem, total), order)); // -> 33
但我想不出正确的表达式来取回总计数组
console.log(view(?????, order)); // -> [33,123,777]
这是我的问题 - ?????
在哪里?
我通过这样做来弥补我的无知:
let collector = [];
function collect(t) {
collector.push(t);
}
over(lineItems, map(over(total, collect)), order);
console.log(collector); // -> [33,123,777]
但我相信 ramda 本地人知道如何做得更好。
您想在这里使用镜头有什么特别的原因吗?不要误会我的意思;镜片不错,但它们似乎对您的情况没有太大帮助。
最终这就是你试图完成的(据我所知):
map(prop('total'), order.lineItems)
您可以通过以下方式稍微重构一下:
const get_total = compose(map(prop('total')), propOr([], 'lineItems'));
get_total(order);
您可以使用 R.pluck 从对象数组中获取值数组:
const order = {"lineItems":[{"name":"A","total":33},{"name":"B","total":123},{"name":"C","total":777}]};
const result = R.pluck('total', order.lineItems);
console.log(result);
<script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
可以使用镜头(遍历)实现此目的,但可能不值得额外的复杂性。
我们的想法是,我们可以将 R.traverse
与 Const
类型的应用实例一起使用,作为可与镜头组合并将零个或多个目标组合在一起的东西。
Const
类型允许您包装一个在映射时不会改变的值(即它保持不变)。我们如何将两个常量值组合在一起以支持应用 ap
?我们要求常量值有一个幺半群实例,这意味着它们是可以组合在一起的值,并且有一些值表示一个空实例(例如,两个列表可以连接起来,空列表是空实例,两个数字可以添加零是空实例等)
const Const = x => ({
value: x,
map: function (_) { return this },
ap: other => Const(x.concat(other.value))
})
接下来我们可以创建一个函数,让我们以不同的方式组合镜头目标,具体取决于提供的将目标值包装在某个幺半群实例中的函数。
const foldMapOf = (theLens, toMonoid) => thing =>
theLens(compose(Const, toMonoid))(thing).value
这个函数将像 R.view
和 R.over
一样使用,接受一个镜头作为它的第一个参数,然后一个函数将目标包装在一个将值组合在一起的幺半群实例中.最后它接受了你想用镜头钻进去的东西。
接下来我们将创建一个简单的辅助函数,可用于创建我们的遍历,捕获将用于聚合最终目标的幺半群类型。
const aggregate = empty => traverse(_ => Const(empty))
这是一个不幸的泄漏,我们需要知道在编写遍历时最终结果将如何聚合,而不是简单地知道它是需要遍历的东西。其他语言可以使用静态类型来推断此信息,但如果不更改镜头在 Ramda 中的定义方式,JS 就没有这样的运气。
鉴于你提到你想将目标加在一起,我们可以创建一个 monoid 实例来执行此操作。
const Sum = x => ({
value: x,
concat: other => Sum(x + other.value)
})
这只是说您可以将两个数字组合在一起,当合并时,它们将产生一个新的 Sum
,其中包含将它们加在一起的值。
我们现在拥有将它们组合在一起所需的一切。
const sumItemTotals = order => foldMapOf(
compose(
lensProp('lineItems'),
aggregate(Sum(0)),
lensProp('total')
),
Sum
)(order).value
sumItemTotals({
lineItems: [
{ name: "A", total: 33 },
{ name: "B", total: 123 },
{ name: "C", total: 777 }
]
}) //=> 933
如果你只是想提取一个列表而不是直接对它们求和,我们可以使用 monoid 实例代替列表(例如 [].concat
)。
const itemTotals = foldMapOf(
compose(
lensProp('lineItems'),
aggregate([]),
lensProp('total')
),
x => [x]
)
itemTotals({
lineItems: [
{ name: "A", total: 33 },
{ name: "B", total: 123 },
{ name: "C", total: 777 }
]
}) //=> [33, 123, 777]
根据您对customcommander 的回答的评论,我认为您可以将此写得相当简单。我不知道你是如何接收你的模式的,但是如果你可以将你的 lineItems
节点的路径转换成一个字符串数组,那么你就可以编写一个相当简单的函数:
const lineItemTotal = compose (sum, pluck ('total'), path)
let order = {
path: {
to: {
lineItems: [
{name: "A", total: 33},
{name: "B", total: 123},
{name: "C", total: 777},
]
}
}
}
console .log (
lineItemTotal (['path', 'to', 'lineItems'], order)
)
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.27.0/ramda.js"></script>
<script> const {compose, sum, pluck, path} = R </script>
您可以围绕此包裹 curry
并使用 lineItemTotal (['path', 'to', 'lineItems']) (order)
调用生成的函数,可能会保存中间函数以供重用。