Warning: file_get_contents(/data/phpspider/zhask/data//catemap/4/fsharp/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Recursion 在递归拆卸字符串时避免堆栈溢出_Recursion_F#_Stack Overflow_Tail Recursion - Fatal编程技术网

Recursion 在递归拆卸字符串时避免堆栈溢出

Recursion 在递归拆卸字符串时避免堆栈溢出,recursion,f#,stack-overflow,tail-recursion,Recursion,F#,Stack Overflow,Tail Recursion,我正在研究(扰流板警报)问题的解决方案,其中我需要一个函数,该函数接受一个字符串(或字符列表),并在它们做出反应时删除每对字符。该练习描述了两个字符,或“聚合物”中的“元素”,当它们是同一个字母但只是大小写不同时发生反应;因此,从AbBc开始将给您留下Ac。请记住,在一次反应后,两个字符可能会彼此相邻,在之前没有的地方,并引起新的反应 我想我可以通过使用一个递归函数来解决这个问题,该函数只处理前两个字符并递归地调用自身,但是由于输入字符串非常大,这会导致堆栈溢出异常: let rec react

我正在研究(扰流板警报)问题的解决方案,其中我需要一个函数,该函数接受一个字符串(或
字符列表
),并在它们做出反应时删除每对字符。该练习描述了两个字符,或“聚合物”中的“元素”,当它们是同一个字母但只是大小写不同时发生反应;因此,从
AbBc
开始将给您留下
Ac
。请记住,在一次反应后,两个字符可能会彼此相邻,在之前没有的地方,并引起新的反应

我想我可以通过使用一个递归函数来解决这个问题,该函数只处理前两个字符并递归地调用自身,但是由于输入字符串非常大,这会导致
堆栈溢出异常

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
)使其尾部递归,因为它“累加”值。我们将递归调用的返回值添加到累加器中并传递它,而不是将
left
添加到递归调用的返回值中。然后当我们耗尽输入时,我们返回累加器而不是空列表

我还随意添加了,
[left]@…
,作为缺点,
left::…
,因为后者比前者效率更高。我还将
向左
向右
休息
移动到了该模式,因为这样更整洁、更安全。通常应该避免使用
List.head
List.tail
,因为它们在空列表中失败,只是等待发生的bug

让rec与acc聚合物反应=
配聚合物
|[]->acc
|[x]->x::acc
|左::右::休息->
match的反应为左-右
|正确->反应acc rest
|false->react(左::acc)(右::rest)
您还可以使用一个保护,而不是嵌套的
match
es(无论如何,它应该是
if
):

让rec与acc聚合物反应=
配聚合物
| [] ->
行政协调会
|[x]>
x::acc
|left::right::rest when Response left right->
根据休息作出反应
|左::rest->
反应(左::acc)休息

尾部递归是避免堆栈溢出的一种方法。另外,我认为您可以像这样对左右进行模式匹配
| left::right::tail->
感谢您花时间深入解释这一点,并指出了最佳实践。只有一个问题,当你说
这应该是一个if,无论如何
,这是否意味着当一个函数的结果是一个
布尔值
时,你应该只使用
if/then/else
?虽然我不是glennsl,但我有信心回答“是”。如果看到
将(某个表达式)与| true->x | false->y
匹配,那么编写
If(某个表达式)然后x else y
几乎总是更好的。匹配表达式更好的少数情况是,如果布尔值不是唯一要匹配的对象,例如,
将Int32.TryParse(str)与| true,value->value+5 | false,0->0
匹配。事实上,
if
表达式读起来更好,因为布尔条件表达式的形状更可预测。如果您需要进一步的条件
如果。。。否则如果。。。否则…
。例外情况可能是,如果您需要更像真值表的东西,我认为它作为
匹配项读起来更好。