如何在Javascript中用递归替换循环?

我有2个for循环,可以很好地创建具有行和列的网格,但是我想使用递归来改进解决方案,因为它更简洁,更推荐(在函数式编程中)。

所需的输出是CSS网格中使用的成对的单个数组

const createGrid = (rows,columns) => {
    let grid=[]
    for (let y = 3; y <= rows; y += 2){
        let row = []
        for (let x = 3; x <= columns; x += 2){
            let col = [y, x]
            row = [...row,col]
        }
        grid =[...grid, ...row]
    }
    return grid
}

是否有关于如何将for循环转换为递归解决方案的准则?

斯科特·索耶(Scott Sauyet)

最初,在阅读代码时,我认为您生成了一种样式的网格,因此makeGrid (7, 9)将导致如下所示:

[
  [[3, 3], [3, 5], [3, 7], [3, 9]], 
  [[5, 3], [5, 5], [5, 7], [5, 9]], 
  [[7, 3], [7, 5], [7, 7], [7, 9]]
]

相反,它返回一个成对的数组:

[[3, 3], [3, 5], [3, 7], [3, 9], [5, 3], [5, 5], [5, 7], [5, 9], [7, 3], [7, 5], [7, 7], [7, 9]]

我敢肯定我不是唯一的一个。Bergi建议在评论中进行修复,以将其更改为前者。(这就是改变grid =[...grid, ...row]grid =[...grid, row]会做。)和三江源精彩的答案的前提是相同的假设。

这是个问题。

当读者无法快速了解您的代码的功能时,维护起来就变得更加困难……即使是几周后的您也是如此。

您可能会听到建议用递归替换循环的原因与此相关。循环都是与明确的命令式指令有关的,这些指令式指令根据变量的变化来获取所需的内容,然后您必须跟踪这些变量,并且很容易遭受一次性错误的影响。递归通常更具声明性,这意味着您要查找的结果只是将这些简单的结果与我们的当前数据结合在一起,并指出如何通过基本案例或递归获得简单的结果呼叫。

但是,可读性和可理解性的优点是关键,而不是解决方案是递归的事实。

不要误会我的意思,递归是我最喜欢的编程技术之一。谢谢的回答是美丽而优雅的。但这不是唯一可以解决显式for循环问题的技术通常,在尝试将初级程序员提升到中级或更高水平时,我要做的第一件事就是for用更有意义的结构替换-loops。大多数循环都试图做以下几件事之一。他们正在尝试将列表中的每个元素转换为新的(map),试图选择元素的一些重要子集(filter),试图找到第一个重要元素(find),或者试图将所有元素组合为一个值(reduce)。通过使用这些,代码变得更加明确。

从Thankyou的答案中也可以看出,将代码的可重用部分分开是很重要的,以便您的主要功能可以专注于重要部分。下面的版本提取了一个function rangeBy,它step向我通常的range函数中添加了一个参数range创建整数范围,例如,range (3, 12)yields[3, 4, 5, 6, 7, 8, 9, 10, 11, 12] rangeBy添加一个初始step参数,从而range (2) (3, 12)yields [3, 5, 7, 9, 11]

我们将该rangeBy函数与map,及其表亲一起使用flatMap以使您的循环函数更明确地版本:

const rangeBy = (step) => (lo, hi) => 
  [... Array (Math .ceil ((hi - lo + 1) / step))] 
    .map ((_, i) => i * step  + lo)

const createGrid = (rows, columns) => 
  rangeBy (2) (3, rows) .flatMap (y => 
    rangeBy (2) (3, columns) .map (x => 
      [y, x]
    )
  )

console .log (createGrid (7, 9))

知道是什么rangeBy,我们可以将其理解为

const createGrid = (rows, columns) => 
  [3, 5, 7, ..., rows] .flatMap (y => 
    [3, 5, 7, ..., columns] .map (x => 
      [y, x]
    )
  )

请注意,如果您想要我所期望的行为,则只需flatMapmapin替换即可实现createGrid另外,如果你这样做,是微不足道的添加更通用的行为三江源计划书,通过更换[y, x]f (x, y)和传递f作为参数。剩下的硬编码在这个版本的转换rows,并columns与3开始我们可以使实际阵列的参数我们的功能,并应用奇数阵列rangeBy之外。但是到那时,我们可能正在寻找理想地名为的其他函数cartesianProduct


因此,递归是一种了不起的实用工具。但这是一种工具,而不是目标。但是,简单易读的代码是一个重要目标。

更新资料

我本来想提这件事,只是忘了。下面的版本表明,这种计算rangeBy远非根本。我们可以轻松地使用一个电话:

const rangeBy = (step, lo, hi) => 
  [... Array (Math .ceil ((hi - lo + 1) / step))] 
    .map ((_, i) => i * step  + lo)

const createGrid = (rows, columns) => 
  rangeBy (2, 3, rows) .flatMap (y => 
    rangeBy (2, 3, columns) .map (x => 
      [y, x]
    )
  )

console .log (createGrid (7, 9))

currying的主要原理rangeBy是,当这样编写时:

const rangeBy = (step) => (lo, hi) => 
  [... Array (Math .ceil ((hi - lo + 1) / step))] 
    .map ((_, i) => i * step  + lo)

我们可以range通过简单地应用1上面的内容来编写更常见的内容那是,

const range = rangeBy (1)

range (3, 12) //=> [3, 4, 5, 6, 7, 8, 9, 10, 11, 12]

这是如此有用,以至于它已成为我编写函数的常用样式。但这不是简化问题的重要部分。

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章