【问题标题】:Accessing data inside compose function Ramda在 compose 函数 Ramda 中访问数据
【发布时间】:2017-11-23 23:33:36
【问题描述】:

我正在使用 Ramda 为一些数据提供结构。但是,我一直无法访问compose 中的数据。

  1. 它应该映射 level 大于 2 的项目,但这不起作用

[keys, compose(map(map(find(propEq('level', > 2)))), values)]

  1. 我正在尝试将 typeChild 中的所有项目保留为 unique

这是用于测试它的 ramda 控制台(必须通过那里的链接,所以不允许 goo.gl 链接):http://dpaste.com/0SATTZK

const result = pipe(
  pluck('type'),
  groupBy(
    pipe(
      find(propEq('level', 1)),
      propOr('NoLevel', 'name'),
    )
  ),
  converge(
    zipWith(unapply(zipObj(['name', 'typeChild']))),
    [keys, compose(map(map(find(propEq('level', 2)))), values)]
  ),
);
result(data)

输入数据

[{
    "title": "Apple",
    "type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
  }, {
    "title": "Tomato",
    "type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
  }, {
    "title": "Potato",
    "type": [{"name": "Food", "level": 1}, {"name": "Vegetable", "level": 2}]
  }, {
    "title": "The Alchemist",
    "type": [{"name": "Entertainment", "level": 1}, { "name": "Book", "level": 2}]
  }, {
    "title": "Superman",
    "type": [{"name": "Entertainment", "level": 1}, {"name": "Movie", "level": 2}]
  }, {
    "title": "More facts",
    "type": [{"name": "Foo", "level": 2}]
  }, {
    "title": "Superman",
    "type": [{"name": "Bar", "level": 1}]
  }
];

期望的输出

[

{name: "Food", typechild: [{level: 2, name: "Fruit"}, {level: 2, name: "Vegetable"}]},
 {name: "Entertainment", typechild: [{level: 2, name: "Book"}, {level: 2, name: "Movie"}]},
 {name: "NoName", typechild: [{level: 2, name: "Foo"}]},
 {name: "Bar", typechild: []}
]

【问题讨论】:

  • 请在问题本身中包含一些数据结构的描述。外部链接作为附加信息很好,但问题应该能够独立存在。
  • 我有几个问题。 (1) “访问compose 中的数据”是什么意思? (2) 你想过滤什么? (goo .gl /VuBdR5) 有propEq('level', 1)propEq('level', 2),但没有像propEq('level', > 2) 这样的东西(反正不是真正的JS。)(3)(最根本的是),你一般想做什么?我知道您想更改数据的形状,但是您要过滤掉什么,其余的将是什么形状?你介意完全失去'title'属性吗?当你的compose/pipe的第一步是pluck('type')时会发生这种情况?
  • 更新了问题以便澄清,但感谢您的出色回应。

标签: javascript ramda.js


【解决方案1】:

好的,我来猜猜你在找什么。


显示数据

首先,您确实需要展示输入数据的某些部分。我把它简化为:

const data =[{
    "title": "Apple",
    "type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
  }, {
    "title": "Tomato",
    "type": [{"name": "Food", "level": 1}, {"name": "Fruit", "level": 2}]
  }, {
    "title": "Potato",
    "type": [{"name": "Food", "level": 1}, {"name": "Vegetable", "level": 2}]
  }, {
    "title": "The Alchemist",
    "type": [{"name": "Entertainment", "level": 1}, { "name": "Book", "level": 2}]
  }, {
    "title": "Superman",
    "type": [{"name": "Entertainment", "level": 1}, {"name": "Movie", "level": 2}]
  }, {
    "title": "More facts",
    "type": [{"name": "Foo", "level": 2}]
  }, {
    "title": "Superman",
    "type": [{"name": "Bar", "level": 1}]
  }
];

(请注意,我从每种类型中删除了 color 属性,因为它们似乎与讨论无关,但它们不会改变任何东西。)

我从您的尝试中猜想会需要这样的输出:

[
 {name: "Food", typechild: [{level: 2, name: "Fruit"}, {level: 2, name: "Vegetable"}]},
 {name: "Entertainment", typechild: [{level: 2, name: "Book"}, {level: 2, name: "Movie"}]},
 {name: "NoName", typechild: [{level: 2, name: "Foo"}]},
 {name: "Bar", typechild: []}
]

通过组合函数来解决这个问题

这是一种方法:

const levelEq = (n) => pipe(prop('level'), equals(n));
const topLevel = pipe(prop('type'), find(levelEq(1)));
const topLevelName = pipe(topLevel, propOr('NoName', 'name'));
const extract2ndLevel = pipe(pluck('type'), flatten, filter(levelEq(2)));

const convert = pipe(
  groupBy(topLevelName),
  map(extract2ndLevel),
  map(uniq),
  toPairs,
  map(zipObj(['name', 'typechild']))
);

convert(data); //=> (the first output format above)

(通常对于那些单行,而不是pipe,我会使用compose,并颠倒顺序,但我也不太喜欢在同一个脚本中混合composepipe。对于更长的convert 函数,我绝对更喜欢pipe。但是切换其中任何一个,或将它们组合起来都不会改变任何必要的东西。)

关键是这是建立在函数组合之上的。我没有尝试一次构建它,而是编写单独的函数来完成小工作并将它们组合成更复杂的工作。

请注意,此代码不会优雅地处理不良数据,更改为执行此操作可能是一项繁重的工作。

还要注意,在主函数中,我一次只工作一小步。我可以注释掉后续步骤以查看每个单独步骤的结果。如果我愿意,我也可以使用R.tap

重新组合很少是个好主意

除了相对简单的levelEq 之外,这些辅助函数中的每一个都只使用一次。所以他们可以很容易地内联。我们可以这样重写这段代码:

const convert = pipe(
  groupBy(pipe(prop('type'), find(pipe(prop('level'), equals(1))), propOr('NoName', 'name'))),
  map(pipe(pluck('type'), flatten, filter(pipe(prop('level'), gte(__, 2))), uniq)),
  toPairs,
  map(zipObj(['name', 'typechild']))
);

但对我来说,这是一个难以理解的混乱,我不会打扰。

更好的文档

如果你习惯了Hindley-Milnar style type annotation,给这些函数添加类型签名可能会有所帮助,可能是这样的:

// Type :: {name: String, level: Int}

// :: Int -> (Type -> Bool)
const levelEq = (n) => pipe(prop('level'), equals(n));
// :: {type: [Type]} -> Type
const topLevel = pipe(prop('type'), find(levelEq(1)));
// :: {type: [Type]} -> String
const topLevelName = pipe(topLevel, propOr('NoName', 'name'));
// :: [{title: String, type: [Type}]}] -> [Type]
const extract2ndLevel = pipe(pluck('type'), flatten, filter(levelEq(2)));

// [{title: String, type: [Type]}] -> [{name: String, typechild: [Type]}]
const convert = pipe( /* ... */ )

(如果这些对您来说毫无意义,请不要担心。)

更改输出格式

但也许你真的想要这样的东西:

[
 {"name": "Food", "typechild": ["Fruit", "Vegetable"]}, 
 {"name": "Entertainment", "typechild": ["Book", "Movie"]}, 
 {"name": "NoName", "typechild": ["Foo"]}, 
 {"name": "Bar", "typechild": []}
]

事实证明这是一个简单的变化:

const convert = pipe(
  groupBy(topLevelName),
  map(extract2ndLevel),
  map(uniq),
  map(pluck('name')), // <--- A single addition
  toPairs,
  map(zipObj(['name', 'typechild']))
);

map的优点

我们在最后一个 sn-p 中看到的一件事是一系列连续的 map 调用。其中每一个都分别循环遍历列表。这使得代码干净,但如果在您的性能测试中,您发现这个额外的循环导致您的问题,您可以利用composition law associated with map,它经过适当的翻译,说

pipe(map(f), map(g)) ≍ map(pipe(f, g))

所以你可以添加这个:

// :: [{title: String, type: [Type}]}] -> [String]
const foo = pipe(extract2ndLevel, uniq, pluck('name'));

并像这样重写main函数:

// [{title: String, type: [Type]}] -> [{name: String, typechild: [Type]}]
const convert = pipe(
  groupBy(topLevelName),
  map(foo),
  toPairs,
  map(zipObj(['name', 'typechild']))
);

但我想不出这个新函数的好名字,这让我觉得这不是一个很好的抽象;如果实际性能测试表明多次迭代是一个现实问题,我只会选择这样做。

结论

函数式编程涉及很多方面,但其中一项关键技术是将所有内容不断分解为易于理解的部分。这就是我试图用这个解决方案做的事情。虽然我们可以打破这一点来创建没有依赖关系的单个函数(“重新组合...”上面),但很少可读。另一方面,这种方法很容易改变我们的方法(“更改输出格式”),并在必要时修复性能问题(map 的优点”)。

哇,那应该是一篇博文!


您可以在 Ramda REPL 上看到其中的大部分内容。

【讨论】:

  • 谢谢你——这值得写一篇博文!使用“提取 2 级”功能,它实际上应该提取 2 级以上的任何内容。我该怎么做?我可以看到有更优雅的方法,但我想测试所有示例。
  • 使用名为levelAtLeastlevelEq 的轻微变体(可能是(n) =&gt; pipe(prop('level'), gte(__, n)))来构建它很容易。我看到您提出了这一建议,但您的示例数据不包含任何相关项目。我唯一的问题是,如果您添加,例如,Russet,类型为 FoodVegetablePotatoPotato 仍然直接显示在 Food 下的输出中吗?还是有一些嵌套结构?
  • 是的,明白了。改变数据结构有很多好处吗?我需要保留color 属性,因此除非有不错的性能提升,否则无法在数组中列出。此外,看起来 `filter(pipe(prop('level'), gte(__, 2)))` 效果很好!如果我想在 2-4 级之间说呢?
  • 我并不是建议您更改数据结构。 color 属性根本与讨论无关。添加回来不应该改变任何东西。这是我唯一改变的,我相信。我不知道您所说的“将其列在数组中”是什么意思。写一个 2 - 4 很容易,尽管在那时最好不要去无分。 (n) =&gt; n &gt;= 2 &amp;&amp; n n &lt;= 4
  • 嗨,你帮了我很多:)它只有一个问题,它只返回第一个数组。我开了一个新问题-stackoverflow.com/questions/46845461
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-06-06
  • 2018-04-06
  • 2021-01-19
  • 2019-04-04
  • 2012-06-10
  • 2023-03-08
相关资源
最近更新 更多