我是Ocaml初学者,无法理解尾递归或列表迭代。我们如何遍历列表是2s并交换对?
let rec swap = function
| a :: (b :: _ as t) -> b::a::swap t | smaller -> smaller;;
let newlist = swap [1;2;3;4];;
List.iter print_int newlist;
例如,1234,交换函数交换1和2,然后列表头仍然是2,并且它交换2和3,而它应该是3并且交换3和4。
答案 0 :(得分:0)
您在swap
尝试做的事情应该是
let rec swap = function
| a :: b :: tail -> b :: a :: (swap tail)
| whatever -> whatever
通过这种方式,您可以在头部交换两个元素并继续执行之后的操作。
但是,这里存在问题。让我们看看在swap [1; 2; 3; 4; 5; 6]
电话:
swap
调用[1; 2; 3; 4; 5; 6]
a
与1匹配,b
与2匹配,并且...... .... 2.1。使用swap
[3; 4; 5; 6]
.... 2.2。在第二次通话中a
与3匹配,b
与4匹配,并且......
........ 2.2.1。使用swap
[5; 6]
........ 2.2.2。第三次调用a
内部匹配5,b
匹配6,并且...
............ 2.2.2.1。使用swap
(空列表)
[]
............ 2.2.2.2。此调用返回空列表
........ 2.2.3。 ...并在之前的swap
调用中将其添加到b
和a
的开头,生成6 :: 5 :: []
或[6; 5]
........ 2.2.4。返回
.... 2.3。 ...并在之前的swap
调用中将其添加到其开头添加(自己的)b
和a
,生成4 :: 3 :: [6; 5]
或{{1 }}
.... 2.4。返回
[4; 3; 6; 5]
调用中将其添加到其开头添加(自己的)swap
和b
,生成a
或2 :: 1 :: [4; 3; 6; 5]
请注意,[2; 1; 4; 3; 6; 5]
来电的整个链条必须在两个方向上进行:
致电1,swap
=>致电2,swap [1; 2; 3; 4; 5; 6]
=>致电3,swap [3; 4; 5; 6]
=>致电4 swap [5; 6]
,返回swap []
,现在返回=>致电3,返回[]
=>致电2,返回[6; 5]
=>调用1,返回[4; 3; 6; 5]
。
这意味着程序整个时间“向下”,它应该保留内存中的前一个路径,因此它可以返回。如果您的列表是200万个条目,则整个路径将是一百万个调用,并且应该保存在内存中。如果你运行这样的东西,你将耗尽堆栈空间。
如何解决这个问题?通常的技术是使用某种“累加器”。
[2; 1; 4; 3; 6; 5]
这些功能是什么?让我们试着看看如果我们拨打let rec swap_helper acc = function
| a :: b :: tail -> swap_helper (a :: b :: acc) tail
| a :: [] -> a :: acc
| [] -> acc ;;
let swap2 lst = List.rev (swap_helper [] lst)
会发生什么。首先是调用swap2 [1; 2; 3; 4; 5; 6]
。
swap_helper [] [1; 2; 3; 4; 5; 6]
与1匹配,a
与2匹配,b
与tail
匹配,因此我们调用[3; 4; 5; 6]
。但是,看哪!当下一个调用返回时,我们根本没有对结果进行任何操作,因此没有理由返回 - 编译器将生成代码以立即返回我们将在swap_helper [1; 2] [3; 4; 5; 6]
中获得的结果,而不会回到原始代码 - 因为原始调用中的结果没有任何关系。 这称为尾调用优化。
现在我们正在swap_helper [1; 2] [3; 4; 5; 6]
来电,其中swap_helper [1; 2] [3; 4; 5; 6]
与3匹配,a
与4匹配,b
与{{1}匹配},所以我们打电话给下一个tail
,之后再没有什么可做的了,所以我们不会再回到这里了!
所以,[5; 6]
致电。此处swap_helper [3; 4; 1; 2] [5; 6]
为5,swap_helper [3; 4; 1; 2] [5; 6]
为6,a
为b
。我们继续tail
,我们不再回到这里,因为没有什么可做的了!
[]
点击最后一场比赛并立即返回swap_helper [5; 6; 3; 4; 1; 2] []
- 但不是任何中间swap_helper [5; 6; 3; 4; 1; 2] []
来电,它们已经被遗忘,没有工作要做剩下的 - 但直接原来[5; 6; 3; 4; 1; 2]
来电!
现在在swap_helper
我们反转了我们从swap2
获得的列表,并根据需要获取swap2
。
所以,重复:在swap_helper
调用自身的代码中,没有什么可做的,所以没有理由返回,所以我没有理由花时间记住“回来”,就像我们一样不会回去。在原始代码中,我们无法做到这一点,因为[2; 1; 4; 3; 6; 5]
正在调用自己,并且在准备好之后仍然必须将swap_helper
和swap
粘贴到第二个调用的结果。