javascript - 如何以堆栈安全的方式映射树?

标签 javascript recursion functional-programming iterator tail-recursion

递归映射树有不同的方法:

const reduceTree = (f, node) => {
  const go = ([x, xs]) => f(x, xs.map(go));
  return go(node);
};

const mapTree = (f, node) =>
  reduceTree((x, xs) => Node_(f(x), xs), node);

const Node_ = (x, xs) => ([x, xs]);
const Node = (x, ...xs) => ([x, xs]);

const tree = Node(1,
  Node(2,
    Node(3),
    Node(4,
      Node(5))),
  Node(6),
  Node(7,
    Node(8),
    Node(9),
    Node(10),
    Node(11)));

console.log(
  mapTree(x => 2 * x, tree));

这实际上是一个优雅的解决方案,但是,它不是堆栈安全的。由于递归调用 (xs.map(go)) 不在尾部位置,我不能退回到蹦床,而且在尾部转换算法对我来说似乎并不简单递归形式。

执行此操作的通常方法可能是 CPS 转换,这会混淆计算。也许还有另一种实现堆栈安全的方法——例如使用生成器?这种转换是否存在可以几乎机械地应用的一般规则?

我主要对进行此转换的过程感兴趣,而不是最终结果。

最佳答案

从一个格式良好的相互递归对开始-

// map :: (a -> b, Tree a) -> Tree b
const map = (f, [ v, children ]) =>
  Node (f (v), ...mapAll (f, children))

// mapAll :: (a -> b, [ Tree a ]) -> [ Tree b ]
const mapAll = (f, nodes = []) =>
  nodes .map (n => map (f, n))

我们首先删除急切的 Array.prototype.map ,它不可能允许尾部形式 -

const map = (f, [ v, children ]) =>
  Node (f (v), ...mapAll (f, children))

const mapAll = (f, [ node, ...more ]) =>
  node === undefined
    ? []
    : [ map (f, node), ...mapAll (f, more) ]

接下来添加CPS转换的参数-

const identity = x =>
  x

const map = (f, [ v, children ], k = identity) =>
  mapAll (f, children, newChilds =>        // tail
    k (Node (f (v), ...newChilds)))        // tail

const mapAll = (f, [ node, ...more ], k = identity) =>
  node === undefined
    ? k ([])                               // tail
    : map (f, node, newNode =>             // tail
        mapAll (f, more, moreChilds =>     // tail
          k ([ newNode, ...moreChilds ]))) // tail

下面在自己的浏览器中验证结果

const Node = (x, ...xs) =>
  [ x, xs ]

const identity = x =>
  x

const map = (f, [ v, children ], k = identity) =>
  mapAll (f, children, newChilds =>
    k (Node (f (v), ...newChilds)))
  
const mapAll  = (f, [ node, ...more ], k = identity) =>
  node === undefined
    ? k ([])
    : map (f, node, newNode =>
        mapAll (f, more, moreChilds =>
          k ([ newNode, ...moreChilds ])))

const tree = 
  Node
    ( 1
    , Node
        ( 2
        , Node (3)
        , Node
            ( 4
            , Node (5)
            )
        )
    , Node (6)
    , Node
        ( 7
        , Node (8)
        , Node (9)
        , Node (10)
        , Node (11)
        )
    )

console.log (map (x => x * 10, tree))

请注意,CPS 本身并不能使程序堆栈安全。这只是将代码放在您选择的蹦床上所需的形式。

关于javascript - 如何以堆栈安全的方式映射树?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/55938607/

相关文章:

algorithm - Dijkstra 和负边

haskell - 做MapReduce的最佳功能语言?

python - 不使用类时使用映射或类似方法更新字典

javascript - 用 HTML 制作一个简单的终端

基于用户输入数字的Javascript计算代码

javascript - jsonloader 纹理未加载

c++ - 在 C++ 中对象移动后如何更新四叉树?

javascript - $ 在 onload 中不可用

java - 如何在 Java 的递归算法中保持 "things done"计数?

从函数式编程范式中记录