简体   繁体   English

是for和yield的列表理解! F#中的尾递归?

[英]Are list comprehensions with for and yield! tail-recursive in F#?

I wrote this little function , I'll repeat it here for ease-of-reference: 我写了这个小函数 ,为了便于参考,我在这里重复一下:

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

It made me wonder whether using yield! 这让我想知道是否使用yield! in this context, or in general with list comprehensions, is tail-recursive. 在这种情况下,或者通常具有列表理解性,它是尾递归的。 I actually think it isn't, which makes that the above function would create a stack-depth equal to the size of the main list. 我实际上认为不是,这使得上面的函数将创建等于主列表大小的堆栈深度。

If it isn't, how can I write this same code in a tail-recursive way? 如果不是,我该如何以尾递归的方式编写相同的代码? I've tried with List.collect (a rolled out idea is in the referred-to question), but I didn't quite get there. 我已经尝试过使用List.collect (在提到的问题中有一个推广的想法),但是我还没有到那儿。

No, it's not tail-recursive, and will in fact blow up the stack: 不,它不是尾递归的,实际上会炸毁堆栈:

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

nestedApply id [] lists

You could make nestedApply tail-recursive by rewriting it in continuation-passing style, but isn't it just an n-ary cartesian product followed by a map? 您可以通过以延续传递样式重写它来使nestedApply尾递归,但这不是只是n元笛卡尔乘积后跟地图吗?

To simplify things I am going to separate the multiplication of lists from the mapping of the function. 为简化起见,我将把列表的乘法与函数的映射分开。 So nestedApply will look like this: 因此nestedApply将如下所示:

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

Where mult does the multiplication of the lists and returns all the combinations. mult在哪里对列表进行乘法运算并返回所有组合。

I usually find that to do tail recursion is better to start with the simple recursion first: 我通常会发现执行尾递归最好先从简单递归开始:

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 ) ) 

So this version of mult does the job but it does not use tail recursion. 因此,此版本的mult可以完成工作,但不使用尾递归。 It does serves as a template to create the tail recursion version and I can check that both return the same value: 它确实用作创建尾部递归版本的模板,我可以检查它们是否返回相同的值:

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) [[]]

The tail recursion version multT uses an internal accumulator parameter. 尾递归版本multT使用内部累加器参数。 To hide it, I nest the recursive part inside the function mult . 为了隐藏它,我将递归部分嵌套在函数mult I also reverse the list because this version works backwards. 我也将列表反向,因为此版本向后工作。

Many times when you have a tail recursive function you can eliminate the recursion by using the fold function: 很多时候,当您拥有尾部递归函数时,可以使用fold函数消除递归:

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

or foldBack : foldBack

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

Notice the similarities. 注意相似之处。

Here is the solution in fiddle: 这是小提琴中的解决方案:

https://dotnetfiddle.net/sQOI7q https://dotnetfiddle.net/sQOI7q

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM