【问题标题】:Please walk me through this "Erlang Programming" recursive sample请带我浏览这个“Erlang Programming”递归示例
【发布时间】:2021-09-30 18:51:48
【问题描述】:

在 Cesarini 和 Thomson 的 Erlang Programming 第 90 页中,有一个没有详细讨论的示例。我是函数式编程和递归思维的新手,所以我不熟悉用这种方式解决问题。

"例如下面的函数通过交错合并两个列表(长度相同) 他们的价值观:“

merge(Xs,Ys) -> lists:reverse(mergeL(Xs,Ys,[])).

mergeL([X|Xs],Ys,Zs) ->  mergeR(Xs,Ys,[X|Zs]);
mergeL([],[],Zs) ->  Zs.

mergeR(Xs,[Y|Ys],Zs) ->  mergeL(Xs,Ys,[Y|Zs]);
mergeR([],[],Zs) ->  Zs.

这是如何工作的?谢谢!

【问题讨论】:

    标签: recursion erlang


    【解决方案1】:

    单步执行

    merge([1,2],[3,4])
    reverse(mergeL([1,2],[3,4],[]))
    reverse(mergeR([2],[3,4],[1]))
    reverse(mergeL([2],[4],[3,1]))
    reverse(mergeR([], [4], [2,3,1]))
    reverse(mergeL([], [], [4,2,3,1]))
    reverse([4,2,3,1])
    [1,3,2,4]
    

    在一张纸上手动操作这些功能总是很好的,只需少量输入即可。您将很快了解它是如何工作的。

    【讨论】:

      【解决方案2】:

      首先调用这个函数:

      merge(Xs,Ys) -> lists:reverse(mergeL(Xs,Ys,[])).
      

      传递给 mergeL 的空列表 [] 是累加器 - 这就是答案的来源。注意第一个函数调用mergeL——左边的merge。

      让我们假设这个函数是这样调用的:

      merge([1, 2, 3], [a, b, c])
      

      两个相同长度的列表。然后第一个函数调用mergeL:

      mergeL([X|Xs],Ys,Zs) ->  mergeR(Xs,Ys,[X|Zs]);
      mergeL([],[],Zs) ->  Zs.
      

      左合并有 2 个子句。使用参数调用 mergeL 将按自上而下的顺序匹配这些子句。

      第二个子句有三个参数——前两个是空列表[]。然而,第一次调用 mergeL 时,这两个列表不是空的,它们是列表 Xs 和 Ys,所以第一个子句匹配。

      让我们打破比赛。这是对 mergeL 的调用:

      mergeL([1, 2, 3], [a, b, c], [])

      它以下列方式匹配第一个子句:

      X = 1
      Xs = [2, 3]
      Ys = [a, b, c]
      Zs = []
      

      这是因为列表的特殊形式:

      [X | Xs]
      

      这意味着将 X 匹配到列表的头部(单个项目),并使 Xs 成为列表的尾部(一个列表)。

      然后我们构建新的函数调用。我们可以将值 X 添加到列表 Zs 的开头,就像我们模式匹配它一样,所以我们得到第一个 mergeR 调用:

      合并([2, 3], [a, b, c], [1])

      最后一个参数是一个单项列表,它是在一个空列表的开头添加一个项目。

      这个一直到最后。

      其实mergeL的最后一个子句是多余的。根据定义,此函数将在 mergeR 的最后一个子句中耗尽(但我将把它留给读者练习)。

      【讨论】:

        【解决方案3】:

        该示例所做的是定义递归将经历的一些状态。定义了 3 个“功能”: 合并、合并 L 和合并 R。

        要合并的列表是 Xs 和 Ys,而 Zs 是合并的结果。

        合并将从调用“合并”并提供两个列表开始。第一步是使用两个要合并的列表和一个空结果集调用 mergeL。

        [X|Xs] 获取列表的第一个元素(非常类似于 array_shift )。该元素被添加到结果集的头部([X|Zs] 执行此操作)。这个结果集(现在包含一个元素)然后被传递给下一个调用,mergeR。 mergeR 做同样的事情,只是它从第二个列表中获取一个元素。只要提供给 mergeL 或 mergeR 的列表不为空,此行为就会继续。

        当使用两个空列表 ([]) 和一个结果集 (Zs) 调用 mergeL 或 mergeR 时,它将返回结果集(并且不再执行另一次运行,从而停止递归)。

        总结:

        递归的开始是第一行,它定义了“合并”。这个开始将通过调用第一个 mergeL 来启动整个事情。

        递归的主体是第2行和第4行,它们定义了行为或mergeL和mergeR,它们都相互调用。

        递归的停止由第 3 行和第 5 行定义,它们基本上告诉整个事情当数组中没有更多元素时要做什么。

        希望这会有所帮助!

        【讨论】:

          【解决方案4】:

          我总是寻找那些将首先终止递归的函数,在这种情况下:

          mergeL([],[],Zs) ->  Zs.
          

          mergeR([],[],Zs) ->  Zs.
          

          当前两个参数为空列表时,这两者基本上都会完成“合并”。

          那我看一下函数的第一次调用:

          merge(Xs,Ys) -> lists:reverse(mergeL(Xs,Ys,[])).
          

          忽略反向一秒钟,您将看到最后一个参数是一个空列表。所以我希望各种 mergeL 和 mergeR 函数将该数组的元素移动到最终参数中 - 当它们全部移动时,该函数将基本上终止(尽管最终调用了 reverse 函数)

          这正是其余函数所做的:

          mergeL([X|Xs],Ys,Zs) ->  mergeR(Xs,Ys,[X|Zs]);
          

          取出 X 的第一个元素并将其放入 Z 数组中,然后

          mergeR(Xs,[Y|Ys],Zs) ->  mergeL(Xs,Ys,[Y|Zs]);
          

          获取 Y 的第一个元素并将其放入 Z 数组中。从mergeL 调用mergeR (反之亦然)正在执行交错部分。

          有趣的是(并且易于修复)数组 X 和 Y 必须具有相同的长度,否则您最终会使用 X 或 Y 中的空数组调用 mergeL 或 mergeR - 而这不会匹配任一 [ X | Xs] 或 [ Y |是的]。

          而相反的原因仅仅是围绕 [ X | 的相对效率。 Zs] 与 [ Zs | X]。前者效率更高。

          【讨论】:

            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2017-10-05
            • 2018-02-26
            • 1970-01-01
            • 2021-04-08
            • 2021-05-04
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多