List 合并两个列表
我希望以一种纯功能的方式在F#中合并两个列表。我很难理解语法 假设我有一个元组List 合并两个列表,list,recursion,f#,f#-scripting,List,Recursion,F#,F# Scripting,我希望以一种纯功能的方式在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=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,[])
的情况。此外,参数最好采用咖喱格式,以便更容易与部分应用程序一起使用。以下是更正的版本:
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
。因此,您可以按如下方式重写交错
:
let interleave xs ys = List.map2 (fun a b -> [a; b]) xs ys |> List.concat
从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]
由于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
不会,因此您需要使用后者。)将向混合中抛出另一个变体。它只需将较长列表的其余部分追加到末尾,即可处理不同长度的列表
让rec交错lst1 lst2=[
将lst1与
|lst1H::lst1T->
产量lst1H
屈服!交错lst2 lst1T
|[]->产量!lst2
]
工作原理:
假设我们有a=[1;2;3];让b=[4;5;6]
并调用交错a b
- 我们在第一个分支上进行匹配,因为左侧列表不是空的
- 我们得到第一个列表的标题(
)1
- 然后,我们使用第二个列表和第一个列表的尾部递归到函数中。请注意,参数的顺序已交换
[2;3]
,第二个列表[4;5;6]
。由于我们交换了参数的顺序,现在我们将重点放在第二个列表上
- 列表不是空的,因此我们匹配第一个分支
- 我们在列表中排名第一(
)4
- 然后我们再次递归,再次切换参数
[2;3]
和[5;6]
,而输出包含[1;4]
此过程重复进行,直到操作的列表为空,与第二个分支匹配,生成另一个列表的其余部分。感谢您的快速响应,但我不太明白为什么没有参数。>]我将如何调用该函数?[您可以像平常一样调用函数。最后一行代码演示了用法。请参阅(页面顶部)。它显示了两种形式的(等效)函数声明。+1用于给出尾部递归解决方案,但就我个人而言,我会使用continuations或累加器+
列表。反向
而不是序列表达式。@ildjarn:您可能对中的结果感兴趣(无论算法如何,它们往往是一致的)简言之,使用累加器+List.rev
通常比continuations性能好得多。很酷,感谢@Daniel的链接。continuations和accumulator+List.rev
都是有趣的可能性,但我编写这个版本时使用了Seq
,以使其接近非尾部递归的版本。例如@ildjarn的建议,请参见。List.map2
与Haskell中的zipWith
做的事情相同。而F#List并不懒惰,因此在解决方案中使用zipWith
将创建一个临时列表。@pad,啊,谢谢。我以前看过List.map2
,但不知怎么忘记了它。关于中间集合的创建,是的,我是一个注意这一点,但是这几乎适用于列表
:-)+1上的所有高阶函数,尽管OP只需要合并/交错两个列表,但它的优点是能够合并多个列表/序列,并且只使用函数组合。似乎可以与Mono F#4.0一起使用(repl.it:上的演示)