Ramda.js - 如何查看嵌套数组中的许多值



我有这个代码:

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]

但我相信拉姆达本地人知道如何做得更好。

使用镜头(遍历)可以实现这一点,尽管可能不值得增加复杂性。

这个想法是,我们可以将R.traverseConst类型的应用实例一起使用,作为可与镜头组合并将零个或多个目标组合在一起的东西。

Const类型允许您包装一个在映射时不会更改的值(即它保持不变)。我们如何将两个常量值组合在一起以支持应用ap?我们要求常量值有一个幺半群实例,这意味着它们是可以组合在一起的值,并且具有表示空实例的一些值(例如,两个列表可以连接起来,空列表是空实例,两个数字可以相加,零是空的 instace,等等)

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.viewR.over一样使用,接受镜头作为其第一个参数,然后是一个将目标包裹在幺半群实例中的函数,该实例将值组合在一起。最后,它接受你想用镜头钻进去的东西。

接下来,我们将创建一个简单的帮助程序函数,该函数可用于创建遍历,捕获将用于聚合最终目标的幺半群类型。

const aggregate = empty => traverse(_ => Const(empty))

这是一个不幸的泄漏,我们需要知道在编写遍历时最终结果将如何聚合,而不是简单地知道它是需要遍历的东西。其他语言可以使用静态类型来推断这些信息,但是在不改变Ramda中镜头定义方式的情况下,JS就没有这样的运气了。

鉴于您提到您希望将目标相加,我们可以创建一个幺半群实例来做到这一点。

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]

根据您对自定义指挥官答案的评论,我认为您可以相当简单地写这个。 我不知道你是如何接收模式的,但如果你能把通往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)调用生成的函数,可能会保存中间函数以供重用。

你想在这里使用镜头有什么特别的原因吗?不要误会我的意思;镜片很好,但它们似乎不会在您的情况下增加太多价值。

最终,这就是您要完成的(据我所知):

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>

最新更新