[英]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.