关于递归:递归拆卸字符串时,避免堆栈溢出

Avoid stackoverflow when recursively dismantling a string

我正在为代码2018年(扰流器警报)出现的问题寻求解决方案,其中我需要一个函数,该函数需要一个字符串(或char list)并在它们反应时删除每对字符。本练习描述了两个字符,即"聚合物"中的"元素",当它们是相同字母但大小写不同时会发生反应。因此从AbBc开始将使您离开Ac。请记住,在发生反应后,两个字符可能会彼此相邻(以前不存在),并引起新的反应。

我认为我可以通过使用仅处理前两个字符并递归调用自身的递归函数来解决此问题,但是由于输入字符串很大,因此会导致stackoverflow exception

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
let rec react polymer =
    match polymer with
    | [] -> []
    | [x] -> [x]
    | head::tail ->
        let left = head
        let right = List.head tail
        let rest =  List.tail tail
        // 'reacts' takes two chars and
        // returns 'true' when they react
        match reacts left right with
        // when reacts we go further with
        // the rest as these two chars are
        // obliterated
        | true -> react rest
        // no reaction means the left char
        // remains intact and the right one
        // could react with the first char
        // of the rest
        | false -> [left] @ react tail

然后,我只是想解决这个问题以针对单元测试找到正确的答案,我不得不进行此操作,但这很快就变得一团糟,现在我有点卡住了。我正在自学f#,因此欢迎您使用任何指针。任何人都可以通过功能方式解决此问题吗?


您可以通过重写函数以使用尾部递归来避免堆栈溢出,这仅意味着递归调用应该是要执行的最后一个操作。

执行[left] @ react tail时,首先进行递归调用,然后将[left]附加到该结果中。这意味着它必须在执行递归调用时保留当前的函数上下文(称为堆栈框架),如果该上下文也递归,则堆栈框架会累加直到出现堆栈溢出。但是,如果在当前函数上下文中没有更多工作要做,则可以释放(或重用)堆栈帧,因此不会出现堆栈溢出。

您可以通过添加另一个函数参数使其尾部递归,该参数通常称为acc,因为它的" accumulates "值。我们没有将left添加到递归调用的返回值中,而是将其添加到累加器中并将其传递。然后,当我们用尽输入时,我们将返回累加器而不是空列表。

我也将附录[left] @ ...的自由作为缺点,left::...,因为后者比前者更有效率。我也将leftrightrest移至该模式,因为这更加整洁和安全。通常应该避免使用List.headList.tail,因为它们在空列表上失败,并且是等待发生的错误。

1
2
3
4
5
6
7
8
let rec react acc polymer =
    match polymer with
    | [] -> acc
    | [x] -> x::acc
    | left::right::rest ->
        match reacts left right with
        | true -> react acc rest
        | false -> react (left::acc) (right::rest)

您也可以使用防护代替嵌套的match es(无论如何实际上应该是if):

1
2
3
4
5
6
7
8
9
10
let rec react acc polymer =
    match polymer with
    | [] ->
        acc
    | [x] ->
        x::acc
    | left::right::rest when reacts left right ->
        react acc rest
    | left::rest ->
        react (left::acc) rest