【问题标题】:Ramda length deepRamda 长度深
【发布时间】:2017-11-12 10:14:35
【问题描述】:

我有一个深度为一层的数组数组,需要计算嵌套数组的长度之和,即长度深度
试图找出一个好的惯用方式来使用 Ramda。
我目前的解决方案感觉不够简洁。可能我错过了一些东西。
你能推荐更好的吗?

const arr = [[1], [2, 3], [4, 5, 6]]

const lengthDeep = R.pipe(
  R.map(R.prop('length')),
  R.sum
)
console.log(lengthDeep(arr)) // 6
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.24.1/ramda.min.js"></script>

PS:我正在通过尝试将 Ramda 应用于日常编码来学习它。

【问题讨论】:

  • 当你实际上 fold 一个数据结构时,你不应该使用 map

标签: javascript functional-programming lodash ramda.js


【解决方案1】:

首先,您可以使用R.length 代替R.prop('length')。您还可以考虑展平数组,之后所需的结果是该数组的长度:

R.pipe(R.flatten, R.length)

【讨论】:

  • 看起来很棒!谢谢。唯一的缺点可能是大型阵列的性能。我的意思是为了获得长度而扁平化太多了。
【解决方案2】:

虽然这是一个非常直截了当的问题,但我认为它揭示了一个有趣的观点。

目前有三个建议。我跳过了@ftor 的回答,因为它忽略了你关于这是学习 Ramda 的一部分的评论。但我包括他关于弃牌的评论。

以下是解决方案:

const lengthDeep = R.compose(R.sum, R.map(R.length));
const lengthDeep = R.compose(R.length, R.flatten);
const lengthDeep = R.reduce((total, xs) => total + R.length(xs), 0);

(请注意,我从pipe 切换到compose。当函数适合一行时,我通常使用compose,但我不认为pipecompose 是根本不同的解决方案。 )

这些对应于对问题的不同理解。

版本 A (R.compose(R.sum, R.map(R.length))) 是最直接的,我相信它最接近问题的原始表示:我们想要找到“嵌套数组的长度之和”。这个版本是最直接的:它找到这些长度,然后将它们加在一起。 (这是根据@trincot 的观察增强的版本,R.length 将代替R.prop('length')。)这是我会选择的。这很简单,它的作用很明显。

版本 B (R.compose(R.length, R.flatten)) 对应于一个非常不同的问题概念。它回答了这样一个问题:“如果我将所有这些数组合二为一,它会持续多久?”据我所知,它的唯一优点是它是最简单的代码。不利的一面是,运行可能需要更长的时间,而且肯定需要更多的空间。

版本 C (R.reduce((total, xs) => total + R.length(xs), 0)) 涉及另一个概念。描述此解决方案的最佳方式是使用递归描述。空数组的深度长度为零。第一个元素的长度为n 的数组数组的深度长度是n 加上数组数组其余部分的深度长度。如果这就是您对问题的看法,那么此版本可能适合您。还有一次你可能会选择使用它:虽然我没有测试过,但我希望它的性能更高,因为它只在外部列表上循环一次。因此,如果您发现此函数是代码中的瓶颈(您在引入任何性能优化之前确实测试了性能,对吗?),您可能会切换到它,即使代码要复杂得多。 (不知道有没有合理的免积分版本,看不到简单的,这样已经够可读了。)

同样,我会选择Version A,除非有重要的事情促使我切换到Version C。不过,这似乎不太可能。


所有这一切可能是不同意@ftor 评论的一种非常冗长的方式:“当您实际上fold 数据结构时,您不应该使用map。”相反,我会说你应该使用最简单的代码来匹配你对问题的心理模型。这必须通过其他考虑因素(例如性能)来缓和,但它应该是默认值。我对这个问题的理解完全符合“把所有的长度加在一起”的模型。

【讨论】:

  • 嘿斯科特,我的评论只是基于意见,不值得适当回应。当然,OP 可以用一个中间数组组成他自己的折叠。只想说“最好的”代码不一定是最简洁的。
  • @ftor,我一开始并没有考虑回复您的评论。只有当我写下大部分答案时,我才意识到它与您的评论相矛盾。我当然不认为这总体上是一个坏主意,我确实喜欢你对问题的分解。我绝对同意简洁不是代码的主要优点。
  • 感谢您的详尽解释。我没想到这个问题会有这么多活动。
  • @OlegMaslov 如果您认为自己有足够的答案,请接受其中一个答案:-)
【解决方案3】:

这是另一种方法,您可以使用一个名为 mapReduce 的小助手来实现它——我们可以使用 Ramda 的 curry 来实现它,这样它就可以像其他 Rambda 库成员一样共享一个神奇的咖喱界面。

mapReduce 有效地采用映射函数m 和归约函数r 并创建一个新的归约函数。这是一个有用的通用函数,因为它可以在任何你想生成 reducer 的地方使用

作为一个额外的好处,这个解决方案只会遍历输入数组一次(计算答案的最低要求)

// mapReduce :: (a -> b) -> ((c, b) -> c) -> ((c, a) -> c)
const mapReduce = curry ((m, r) => 
  (x, y) => r (x, m (y)))

// deepLength :: [[a]] -> Integer
const deepLength = xs =>
  reduce (mapReduce (length, add), 0, xs)

// arr :: [[Integer]]
const arr = [[1], [2, 3], [4, 5, 6]]

console.log (deepLength (arr))
// 6

为了演示 mapReduce 的多样化实用性,我将向您展示它如何能够处理稍微复杂一些的事情——同时仍然保持程序可读性

// mapReduce :: (a -> b) -> ((c, b) -> c) -> ((c, a) -> c)
const mapReduce = curry ((m, r) => 
  (x, y) => r (x, m (y)))

// omap :: (a -> b) -> {k : a} -> {k : b}
const omap = curry ((f, o) =>
  reduce (mapReduce (k => ({ [k]: f(o[k]) }), Object.assign), {}, keys(o)))

console.log (omap (add(10), {a: 1, b: 2, c: 3}))
// {"a": 11, "b": 12, "c": 13}

【讨论】:

  • 地图传感器是这里的正确工具。由于这本质上是第二个参数中的函数组合,我想知道是否有比mapReducemapper 更通用的名称。
  • 我也很好奇我们是否可以通过使用右折叠来简化代码,其中累加器是 reducer 的第二个参数,我们可以简单地组合第一个。这当然需要一个咖喱解决方案。我试试看。
  • @ftor,我想这可能就是您所描述的:https://repl.it/IgNi/1 – 是吗?
  • 我不太确定你在那里做了什么。不过看起来很酷。实际上我的意思是使用带有固定参数顺序的Array.prototype.reduceRight(它实际上已损坏):https://repl.it/IgUW/1
  • 感谢您的回复。但对我来说,它看起来非常复杂。对于已经将注意力集中在函数式编程并习惯于这样思考的人来说,这可能没关系。
【解决方案4】:

const arr = [[1], [2, 3], [4, 5, 6]]

const lengthDeep = R.reduce(R.useWith(R.add, [R.identity, R.prop('length')]), 0)     
console.log(lengthDeep(arr)) // 6
<script src="//cdnjs.cloudflare.com/ajax/libs/ramda/0.24.1/ramda.min.js"></script>

【讨论】:

    猜你喜欢
    • 2015-03-26
    • 2018-07-29
    • 2016-06-02
    • 2020-06-13
    • 1970-01-01
    • 2017-07-24
    • 2016-10-25
    • 2019-03-05
    • 2017-08-18
    相关资源
    最近更新 更多