List 合并两个列表

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

我希望以一种纯功能的方式在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:上的演示)