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
表达式读起来更好,因为布尔条件表达式的形状更可预测。如果您需要进一步的条件如果。。。否则如果。。。否则…
。例外情况可能是,如果您需要更像真值表的东西,我认为它作为匹配项读起来更好。