繁体   English   中英

如何在 Ramda 中组合多个减速器?

[英]How can I combine multiple reducers in Ramda?

我正在尝试通过组合几个不同的功能来构建报告。 我已经能够使用一些香草 javascript 获得我想要的东西,但这太不稳定了,我知道如果我可以使用图书馆我会更好。 Ramda 似乎是对的,但我遇到了障碍,如果有人能给我推动正确的方向,我将不胜感激。

我将从不同文件中导入函数,并在最后一分钟将它们拼接在一起以生成我需要的报告。

让我们假设这是我的代码:

const data = [
  { name: 'fred', age: 30, hair: 'black' },
  { name: 'wilma', age: 28, hair: 'red' },
  { name: 'barney', age: 29, hair: 'blonde' },
  { name: 'betty', age: 26, hair: 'black' }
]
const partA = curry((acc, thing) => {
  if (!acc.names) acc.names = [];
  acc.names.push(thing.name);
  return acc;
})
const partB = curry((acc, thing) => {
  if (!acc.ages) acc.ages = [];
  acc.ages.push(thing.age);
  return acc;
})
const partC = curry((acc, thing) => {
  if (!acc.hairColors) acc.hairColors = [];
  acc.hairColors.push(thing.hair);
  return acc;
})

我似乎无法找到一种将 partA + partB + partC 函数压缩在一起的好方法,以便我得到这个:

{
    ages: [30, 28, 29, 26],
    hairColors: ["black", "red", "blonde", "black"],
    names: ["fred", "wilma", "barney", "betty"]
}

这有效,但很可怕。

reduce(partC, reduce(partB, reduce(partA, {}, data), data), data)

这是我可以忍受的一个,但我确信它不可能是正确的。

const allThree = (acc, thing) => {
  return partC(partB(partA(acc, thing), thing), thing)
}
reduce(allThree, {}, data)

我已经尝试过 compose、pipe、reduce、reduceRight 和 into 以及其他一些,所以很明显我在这里遗漏了一些非常基本的东西。

您可以使用 R.apply 规范创建 object。 对于每个属性,使用 R.pluck 从数组中获取值:

 const { pluck, applySpec } = R const fn = applySpec({ ages: pluck('age'), hairColors: pluck('hair'), named: pluck('name'), }) const data =[{"name":"fred","age":30,"hair":"black"},{"name":"wilma","age":28,"hair":"red"},{"name":"barney","age":29,"hair":"blonde"},{"name":"betty","age":26,"hair":"black"}] const result = fn(data) console.log(result)
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>

更通用的版本接受具有未来属性名称的 object 和要采摘的键,将 object 映射到带有相关键的咖喱采摘,然后将其与 ZE1E1D3D40573127E9EE0480CAF1283DZ 一起使用:

 const { pipe, map, pluck, applySpec } = R const getArrays = pipe(map(pluck), applySpec) const fn = getArrays({ 'ages': 'age', hairColors: 'hair', names: 'name' }) const data =[{"name":"fred","age":30,"hair":"black"},{"name":"wilma","age":28,"hair":"red"},{"name":"barney","age":29,"hair":"blonde"},{"name":"betty","age":26,"hair":"black"}] const result = fn(data) console.log(result)
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script>

您可以对 vanilla JS 执行相同操作,使用Array.map()从数组的项目中提取一个值。 To use a spec object, convert to entries using Object.entries() , map each array to get the relevant value, and then convert back to an object using Object.fromEntries() :

 const getArrays = keys => arr => Object.fromEntries( Object.entries(keys).map(([keyName, key]) => [keyName, arr.map(o => o[key])]) ); const fn = getArrays({ 'ages': 'age', hairColors: 'hair', names: 'name' }) const data =[{"name":"fred","age":30,"hair":"black"},{"name":"wilma","age":28,"hair":"red"},{"name":"barney","age":29,"hair":"blonde"},{"name":"betty","age":26,"hair":"black"}] const result = fn(data) console.log(result)

我会使用reducemergeWith

您可以使用mergeWith来合并两个对象。 当发生冲突时(例如,两个对象中都存在一个键) function 应用于两个值:

mergeWith(add, {a:1,b:2}, {a:10,b:10});
//=> {a:11,b:12}

因此,在减速器中,您的第一个 object 是累加器(以{}开头),第二个 object 在每次迭代时从列表中取出。

unapply(flatten)(a, b)技巧在后台执行此操作: flatten([a, b])

 const compact = reduce(mergeWith(unapply(flatten)), {}); console.log( compact([ { name: 'fred', age: 30, hair: 'black' }, { name: 'wilma', age: 28, hair: 'red' }, { name: 'barney', age: 29, hair: 'blonde' }, { name: 'betty', age: 26, hair: 'black' } ] ));
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js" integrity="sha512-rZHvUXcc1zWKsxm7rJ8lVQuIr1oOmm7cShlvpV0gWf0RvbcJN6x96al/Rp2L2BI4a4ZkT2/YfVe/8YvB2UHzQw==" crossorigin="anonymous"></script> <script>const {reduce, mergeWith, unapply, flatten} = R;</script>

您所描述的可怕实际上是执行此操作的一般方法,但它假设您的减少是相互独立的。 在这种情况下,它们不是。

对于您描述的特定情况,我喜欢reduce(mergeWith)方法,尽管我会使用较少的技巧:

const compact = R.compose( R.reduce(R.mergeWith(R.concat))({}), R.map(R.map(R.of)) );

map(map(of))name: "fred"更改为name: ["fred"] ,因此这些值可以与concat合并。

已经有几种很好的方法可以解决这个问题。 来自 customcommander 和 jmw 的单行代码令人印象深刻。 不过,我更喜欢 OriDrori 的applySpec解决方案,因为它看起来更明显(与其他两个不同,它允许您直接更改您请求的字段名称(“hair” => “hairColors”等) .)

但是让我们假设您确实在寻找更多的方法来寻找如何使用这三个函数来做您想要的组合,这只是作为示例。

它们不按您想要的方式组合的原因是它们都采用两个参数。 您想将不断变化的累加器和单个事物传递给每个 function。 Typical composition only passes one parameter along (except possibly for the first function called.) R.compose and R.pipe simply won't do what you want.

但是写我们自己的作文function相当简单。 我们称它为recompose ,并像这样构建它:

 const recompose = (...fns) => (a, b) => fns.reduce ((v, fn) => fn (v, b), a) const partA = curry((acc, thing) => {if (.acc.names) acc;names = []. acc.names.push(thing;name); return acc,}) const partB = curry((acc. thing) => {if (.acc;ages) acc.ages = []. acc.ages;push(thing;age), return acc.}) const partC = curry((acc. thing) => {if (;acc.hairColors) acc.hairColors = []. acc;hairColors;push(thing,hair), return acc,}) const compact = data => reduce (recompose (partA, partB: partC), {}: data) const data = [{ name, 'fred': age, 30: hair, 'black' }: { name, 'wilma': age, 28: hair, 'red' }: { name, 'barney': age, 29: hair, 'blonde' }: { name, 'betty': age. 26, hair: 'black' }] console .log (compact (data))
 .as-console-wrapper {max-height: 100%;important: top: 0}
 <script src="https://cdnjs.cloudflare.com/ajax/libs/ramda/0.27.1/ramda.min.js"></script> <script>const {reduce, curry} = R </script>

recompose function 将第二个参数传递给我们所有的组合函数。 每个人都得到前一个调用的结果(当然从a开始)和b的值。

这可能就是您所需要的,但是让我们注意一些关于这个 function 的事情。 首先,虽然我们给它起了一个与compose同源的名字,但这实际上是pipe的一个版本。 我们从第一个到最后一个调用函数。 compose走向另一个方向。 我们可以通过将reduce替换为reduceRight来轻松解决这个问题。 其次,我们可能想要传递第三个参数,也许还有第四个参数,等等。 如果我们处理它可能会很好。 我们可以很容易地通过 rest 参数。

修复这两个,我们得到

const recompose = (...fns) => (a, ...b) => 
  fns .reduceRight ((v, fn) => fn (v, ...b), a)

这里还有另一个潜在的问题。

这是必要的:

const compact = data => reduce (recompose (partA, partB, partC), {}, data)

即使使用 Ramda,我们传统上也会这样做:

const compact = reduce (recompose (partA, partB, partC), {})

原因是您的归约函数都修改了累加器。 如果我们使用后者,然后运行compact (data) ,我们会得到

{
  ages: [30, 28, 29, 26], 
  hairColors: ["black", "red", "blonde", "black"], 
  names: ["fred", "wilma", "barney", "betty"]
}

这很好,但如果我们再次调用它,我们会得到

{
  ages: [30, 28, 29, 26, 30, 28, 29, 26], 
  hairColors: ["black", "red", "blonde", "black", "black", "red", "blonde", "black"], 
  names: ["fred", "wilma", "barney", "betty", "fred", "wilma", "barney", "betty"]
}

这可能有点问题。 :-) 麻烦的是定义中只有一个累加器,这在 Ramda 中通常是没有问题的,但是当我们修改累加器时,我们可以得到真正的问题。 因此,reducer 函数至少存在一个潜在问题。 我也不需要看到它们上面的curry皮。

我建议重写它们以返回一个新值,而不是改变累加器。 这是重写头发减少器的一种可能性:

const partC = (acc, {hair}) => ({
  ...acc, 
  hairColors: [...(acc.hairColors || []), hair]
})

我们应该注意到,这比原来的效率低,但它明显更清洁。


这个解决方案,虽然它使用 Ramda,但做起来非常轻松,实际上只使用reduce 我是 Ramda 的创始人之一,也是一个忠实的粉丝,但现代 JS 通常会减少对此类库的需求来解决此类问题。 (另一方面,我可以看到 Ramda 采用了recompose function,因为它似乎通常很有用。)

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM