【发布时间】:2016-06-05 02:22:58
【问题描述】:
fun p(L) =
[L] @ p( tl(L) @ [hd(L)] );
如果 L 是 [1,2,3] 那么我想要一个 [ [1,2,3], [2,3,1], [3,1,2] ]。
因为每次我将第一个 num 附加到末尾时,如果 L = [] then [] 在这里不起作用。
一旦有三个列表,如何停止函数?
【问题讨论】:
fun p(L) =
[L] @ p( tl(L) @ [hd(L)] );
如果 L 是 [1,2,3] 那么我想要一个 [ [1,2,3], [2,3,1], [3,1,2] ]。
因为每次我将第一个 num 附加到末尾时,如果 L = [] then [] 在这里不起作用。
一旦有三个列表,如何停止函数?
【问题讨论】:
你可以在函数中有一个参数 x 来跟踪你在递归中的深度。
fun p(L, x) =
if x < length(L) then [L] @ p(tl(L) @ [hd(L)], x+1)
else [];
然后调用 x=0 的函数。
p([1, 2, 3], 0)
如果你不喜欢额外的参数,那么你可能知道你可以定义另一个函数,并使其等于 p 函数,并将参数强制为 0。
fun p0(L) = p(L, 0);
p0([1, 2, 3]); (* same result as p([1, 2, 3], 0); *)
【讨论】:
让我展示更多的实现变体。 首先,我们定义一个辅助函数,将列表向左旋转1个位置:
(* operates on non-empty lists only *)
fun rot1_left (h :: tl) = tl @ [h]
那么p函数可以定义如下:
fun p xs =
let
(* returns reversed result *)
fun loop [] _ _ = []
| loop xs n res =
if n = 0
then res
else loop (rot1_left xs) (n-1) (xs :: res)
in
List.rev (loop xs (length xs) [])
end
在列表的开头添加新元素然后将结果列表反转一次通常比多次附加到末尾要好(从性能方面)。 注意:这个版本在最后做了一个虚假的旋转,我本可以优化它,但没有优化,以使代码更清晰。
我们已经计算了给定列表的长度以使其旋转“副本”,但我们不必事先遍历xs,我们可以在旋转它时进行。所以,我们可以使用xs作为一种计数器,递归调用xs列表尾部的loop辅助函数。
fun p xs =
let
(* returns reversed result *)
fun loop [] _ _ = []
| loop xs [] res = res
| loop xs (_::tl) res =
loop (rot1_left xs) tl (xs :: res)
in
List.rev (loop xs xs [])
end
完成之后,我们现在更接近于将p 实现为foldl 函数:
fun p xs =
(List.rev o #1)
(List.foldl
(fn (_, (res, rot)) => (rot::res, rot1_left rot))
([], xs)
xs)
List.foldl 函数的第二个参数是我们的“累加器”,它在此处表示为当前(部分)结果的 pair,如之前的实现和当前旋转列表。这解释了(List.rev o #1) 部分:我们需要获取累加器的第一个组件并将其反转。至于([], xs) 部分——当前结果一开始是空的(因此[]),我们开始旋转初始xs 列表。此外,(_, (res, rot)) 中的_ 表示给定xs 的当前元素,我们不关心它,因为它只是用作计数器(参见上一个变体)。
注意:o 代表标准 ML 中的函数组合。
【讨论】: