【问题标题】:Merge two lists合并两个列表
【发布时间】:2012-02-23 20:04:43
【问题描述】:

我希望以纯粹的功能方式合并 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) 

【问题讨论】:

    标签: list recursion f# f#-scripting


    【解决方案1】:

    您的功能几乎是正确的。 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]
    

    【讨论】:

    • 感谢您的快速响应,但我不太明白为什么没有争论。 > ] 我将如何调用该函数? [
    • 您可以像往常一样调用该函数。最后一行代码演示了用法。请参阅this MSDN article(页面顶部)。它显示了两种形式的(等效)函数声明。
    【解决方案2】:

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

    【讨论】:

    • +1 用于提供尾递归解决方案,尽管我个人会使用延续或累加器 + List.reverse 而不是序列表达式。
    • @ildjarn:您可能对this answer 中的发现感兴趣(无论算法如何,它们往往都是一致的)。简而言之,使用累加器 + List.rev 通常比继续执行好得多。
    • 酷,感谢@Daniel 的链接。延续和累加器 + List.rev 是有趣的可能性,但我使用 Seq 编写此版本以使其接近非尾递归。
    • 有关@ildjarn 建议的示例,请参阅my answer
    【解决方案3】:

    你可以利用这个机会定义一个更通用的高阶函数——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
    

    【讨论】:

    • List.map2 与 Haskell 中的 zipWith 做同样的事情。而且 F# 列表并不懒惰,因此在您的解决方案中使用 zipWith 将创建一个临时列表。
    • @pad,啊,谢谢。我以前见过List.map2,但不知何故忘记了。关于中间集合的创建,是的,我知道这一点,但这对于List 上的几乎所有高阶函数都是如此。 :-)
    【解决方案4】:

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

    【讨论】:

      【解决方案5】:

      从 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不会,所以你需要使用后者。)

      【讨论】:

      • +1 虽然 OP 只需要合并/交错两个列表,但这具有能够合并多个列表/序列的优点,并且只是使用函数组合。似乎适用于 Mono F# 4.0(repl.it 上的演示:repl.it/@codybartfast/SO-Interleave-Sequences-F
      【解决方案6】:

      我会加入另一种变体。这通过简单地在末尾附加较长列表的其余部分来处理不同长度的列表。

      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]。由于我们交换了参数的顺序,我们现在关注第二个列表。

      • 列表不为空,所以我们匹配第一个分支。
      • 我们生成列表的 head (4)。
      • 然后我们再次递归,再次切换参数。

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

      这个过程一直重复,直到被操作的列表为空,它匹配到第二个分支,产生另一个列表的剩余部分。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2014-03-19
        • 1970-01-01
        • 2014-12-18
        • 2016-01-12
        • 2011-02-05
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多