我希望以纯函数的方式合并F#中的两个列表。我很难理解语法。
假设我有一个元组([5;3;8],[2;9;4])
当我调用函数时,它应该返回[5;2;3;9;8;4]
这就是我到目前为止的原因,我确信这是错误的。如果有人能简单地解释,我将不胜感激。
let rec interleave (xs,ys) = function
|([], ys) -> ys
|(x::xs, y::ys) -> x :: y:: interleave (xs,ys)
您的函数几乎是对的。let f = function
是let f x = match x with
的简写,因此不需要显式参数。此外,你的算法需要一些调整。
let rec interleave = function //same as: let rec interleave (xs, ys) = match xs, ys with
|([], ys) -> ys
|(xs, []) -> xs
|(x::xs, y::ys) -> x :: y :: interleave (xs,ys)
interleave ([5;3;8],[2;9;4]) //output: [5; 2; 3; 9; 8; 4]
一个重要的点是函数不正确。它在输入([1;2;3], [])
时失败,因为您在模式匹配中错过了(xs, [])
的情况。此外,参数最好采用curried形式,以便更容易与部分应用程序一起使用。以下是更正后的版本:
let rec interleave xs ys =
match xs, ys with
| [], ys -> ys
| xs, [] -> xs
| x::xs', y::ys' -> x::y::interleave xs' ys'
您可以看到,该函数不是尾递归,因为它在递归调用返回后两次应用cons (::)
构造函数。使其尾部递归的一种有趣的方法是使用序列表达式:
let interleave xs ys =
let rec loop xs ys =
seq {
match xs, ys with
| [], ys -> yield! ys
| xs, [] -> yield! xs
| x::xs', y::ys' ->
yield x
yield y
yield! loop xs' ys'
}
loop xs ys |> List.ofSeq
您可以利用这个机会定义一个更通用的高阶函数zipWith
,然后使用它实现interleave
。
let rec zipWith f xlist ylist =
match f, xlist, ylist with
| f, (x :: xs), (y :: ys) -> f x y :: zipWith f xs ys
| _, _, _ -> []
let interleave xs ys = zipWith (fun a b -> [a; b]) xs ys |> List.concat
编辑:
正如@pad下面所说,F#在名称List.map2
下已经有了zipWith
。因此,您可以按如下方式重写interleave
:
let interleave xs ys = List.map2 (fun a b -> [a; b]) xs ys |> List.concat
由于F#4.5(我认为),假设当较短的序列用完时,您想继续从较长的序列中生成元素,那么您可以执行以下操作:
let interleave = Seq.transpose >> Seq.concat >> Seq.toList
> interleave [ [5;3;8]; [2;9;4] ];;
val it : int list = [5; 2; 3; 9; 8; 4]
> interleave [ [1;2;3]; [4;5]; [6;7;8;9] ];; // also works for any number of lists
val it : int list = [1; 4; 6; 2; 5; 7; 3; 8; 9]
(注意,如果序列的长度不同,List.transpose
会抛出,但Seq.transpose
不会,所以您需要使用后者。)
从OP来看,如果列表的长度不同,会发生什么还不清楚,但这里有一个通用的尾部递归实现,它完全消耗了这两个列表:
// 'a list -> 'a list -> 'a list
let interleave xs ys =
let rec imp xs ys acc =
match xs, ys with
| [], [] -> acc
| x::xs, [] -> imp xs [] (x::acc)
| [], y::ys -> imp [] ys (y::acc)
| x::xs, y::ys -> imp xs ys (y::x::acc)
imp xs ys [] |> List.rev
示例:
> interleave [5;3;8] [2;9;4];;
val it : int list = [5; 2; 3; 9; 8; 4]
> interleave [] [1..3];;
val it : int list = [1; 2; 3]
> interleave [1..3] [42];;
val it : int list = [1; 42; 2; 3]
> interleave [1..3] [42;1337];;
val it : int list = [1; 42; 2; 1337; 3]
> interleave [42; 1337] [1..3];;
val it : int list = [42; 1; 1337; 2; 3]
将在组合中加入另一个变体。这通过简单地将较长列表的剩余部分追加到末尾来处理不同长度的列表。
let rec interleave lst1 lst2 = [
match lst1 with
| lst1H :: lst1T ->
yield lst1H
yield! interleave lst2 lst1T
| [] -> yield! lst2
]
工作原理:
假设我们有let a = [1;2;3]; let b = [4;5;6]
并调用interleave a b
。
- 我们在第一个分支上匹配,因为左边的列表不是空的
- 我们得到第一个列表(
1
)的头 - 然后,我们递归到具有第二个列表和第一个列表尾部的函数中。请注意,参数的顺序已交换
在这个中间点上,我们有两个列表:第一个列表的剩余部分[2;3]
和第二个列表[4;5;6]
。由于我们交换了论点的顺序,现在我们将重点放在第二个列表上。
- 该列表不是空的,所以我们在第一个分支上匹配
- 我们得到列表(
4
)的头 - 然后我们再次递归,再次切换参数
此时的列表是[2;3]
和[5;6]
,而输出包含[1;4]
。
这个过程重复进行,直到操作的列表为空,匹配到第二个分支,生成另一个列表的剩余部分。