【问题标题】:Are list comprehensions with for and yield! tail-recursive in F#?是带有 for 和 yield 的列表推导! F#中的尾递归?
【发布时间】:2018-10-01 11:56:40
【问题描述】:

我写了这个little function,为了便于参考,我在这里重复一遍:

/// Take a list of lists, go left-first, and return each combination,
/// then apply a function to the resulting sublists, each length of main list
let rec nestedApply f acc inp =
    match inp with
    | [] -> f acc
    | head::tail -> 
        [
            for x in head do
                yield! nestedApply f (x::acc) tail
        ]

这让我想知道在这种情况下使用 yield! 是否是尾递归的,或者通常与列表推导一起使用。我实际上认为不是,这使得上述函数会创建一个等于主列表大小的堆栈深度。

如果不是,我如何以尾递归的方式编写相同的代码?我已经尝试使用List.collect(在提到的问题中提出了一个想法),但我并没有完全做到。

【问题讨论】:

    标签: list f# yield tail-recursion computation-expression


    【解决方案1】:

    不,它不是尾递归的,实际上会炸毁堆栈:

    let lists = 
        [1 .. 10000]
        |> List.map (fun i -> List.replicate 100 i)
    
    nestedApply id [] lists
    

    您可以通过以连续传递样式重写nestedApply 来使其成为尾递归,但它不只是一个 n 元笛卡尔积后跟一个映射吗?

    【讨论】:

      【解决方案2】:

      为了简化事情,我将列表的乘法与函数的映射分开。所以nestedApply 看起来像这样:

      let nestedApply f lsts = mult lsts |> List.collect f
      

      mult 将列表相乘并返回所有组合。

      我通常发现做尾递归最好先从简单递归开始:

      let rec mult lsts =
          match lsts with
          | [ ]       ->  [[]]
          | h :: rest ->  let acc = mult rest
                          h |> List.collect (fun e -> acc |> List.map (fun l -> e :: l ) ) 
      

      所以这个版本的mult 可以完成这项工作,但它不使用尾递归。 它确实用作创建尾递归版本的模板,我可以检查两者是否返回相同的值:

      let mult lsts =
          let rec multT lsts acc =
              match lsts with
              | h :: rest -> h 
                             |> List.collect (fun e -> acc |> List.map (fun l -> e :: l ) ) 
                             |> multT rest
              | [ ]       -> acc
          multT (List.rev lsts) [[]]
      

      尾递归版本multT 使用内部累加器参数。为了隐藏它,我将递归部分嵌套在函数mult 中。我也颠倒了列表,因为这个版本向后工作。

      很多时候,当你有一个尾递归函数时,你可以使用fold 函数来消除递归:

      let mult lsts  = 
          List.rev lsts 
          |> List.fold  (fun acc h -> 
                 h 
                 |> List.collect (fun e -> acc |> List.map (fun l -> e :: l ) ) 
               ) [[]]
      

      foldBack:

      let mult lsts =
          List.foldBack (fun h acc -> 
              h 
              |> List.collect (fun e -> acc |> List.map (fun l -> e :: l ) ) 
            ) lsts [[]]
      

      注意相似之处。

      这是小提琴中的解决方案:

      https://dotnetfiddle.net/sQOI7q

      【讨论】:

      • 我完全忘记了我问这个问题,但它实际上是一个非常好的答案,谢谢!
      猜你喜欢
      • 2015-08-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-03-31
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多