合并两个列表

时间:2012-02-01 04:28:35

标签: list recursion f# f#-scripting

我希望以纯粹的功能方式合并F#中的2个列表。我很难理解语法。

假设我有一个元组([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) 

6 个答案:

答案 0 :(得分:12)

您的功能几乎正确。 let f = functionlet 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 :(得分:8)

重要的一点是功能不正确。输入([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

答案 2 :(得分:2)

您可以利用此机会定义更一般的高阶函数 - 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#已经名为zipWith List.map2。因此,您可以按如下方式重写interleave

let interleave xs ys = List.map2 (fun a b -> [a; b]) xs ys |> List.concat

答案 3 :(得分:1)

从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]

答案 4 :(得分:1)

从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不会,因此您需要使用后者。)

答案 5 :(得分:0)

将在混合中添加另一个变体。只需在结尾处附加较长列表的其余部分,即可处理不同长度的列表。

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)的 head
  • 然后,使用第二个列表和第一个列表的末尾递归到函数中。请注意,参数顺序已交换。

在此中间点,我们有两个列表:第一个[2;3]的其余部分和第二个[4;5;6]。由于我们交换了参数的顺序,因此我们现在专注于第二个列表。

  • 列表不为空,因此我们在第一个分支上进行匹配。
  • 我们产生列表(4 head
  • 然后我们再次递归,再次切换参数。

此时的列表为[2;3][5;6],而输出包含[1;4]

重复此过程,直到操作列表为空为止,该列表与第二个分支匹配,从而产生另一个列表的其余部分。