尾递归图f#
我想写一个尾递归函数,将F#中列表中的所有值乘以2。 我知道有很多方法可以做到这一点,但我想知道这是否是一种可行的方法。 这纯粹是为了教育目的。 我意识到有一个内置函数可以为我做到这一点。
let multiply m =
let rec innerfunct ax = function
| [] -> printfn "%A" m
| (car::cdr) -> (car <- car*2 innerfunct cdr);
innerfunct m;;
let mutable a = 1::3::4::[]
multiply a
我得到了两个错误,虽然我怀疑他们是唯一的问题。
这个值在我的第二个匹配条件下是不可变的
和
这个表达式是一个函数值,即缺少参数。 它的类型是'列表 - >单元。 因为当我叫length a
。
我对F#相当陌生,意识到即时通讯可能没有正确调用函数,但我无法弄清楚为什么。 这对我来说主要是一种学习体验,所以解释比修复代码更重要。 语法显然是关闭的,但是我可以将* 2映射到列表中,只需执行相同的操作即可
car = car*2
,然后调用列表的cdr上的内部函数。
如果不显示中间代码,我不能轻易解释一些问题,所以我会尝试遍历一个注释的重构:
首先,我们将沿着可变的路径走下去 :
由于F#列表是不可变的,原始的int
也是如此,所以我们需要一种方法来改变列表中的东西:
let mutable a = [ref 1; ref 3; ref 4]
摆脱多余的ax
并安排一些案例,我们可以利用这些参考单元:
let multiply m =
let rec innerfunct = function
| [] -> printfn "%A" m
| car :: cdr ->
car := !car*2
innerfunct cdr
innerfunct m
我们看到,乘法只会调用它的内部函数,所以我们最终得出第一个解决方案:
let rec multiply m =
match m with
| [] -> printfn "%A" m
| car :: cdr ->
car := !car*2
multiply cdr
这真的只是为了它自己的目的。 如果你想要可变性,可以使用数组和传统的for循环。
然后,我们走上不变的道路 :
正如我们在可变的世界中学到的,第一个错误是由于car
不可变。 它仅仅是一个不可变列表中的原始int
。 生活在一个不可改变的世界意味着我们只能通过我们的输入创造新的东西。 我们想要的是构造一个新的列表,以car*2
为头,然后递归调用innerfunct
。 像往常一样,函数的所有分支都需要返回一些相同类型的东西:
let multiply m =
let rec innerfunct = function
| [] ->
printfn "%A" m
[]
| car :: cdr ->
car*2 :: innerfunct cdr
innerfunct m
了解m
是不可变的,我们可以摆脱printfn
。 如果需要,我们可以将它放在函数之外,我们可以访问列表的任何地方。 它将始终打印相同。
我们通过使列表的引用不可变并获得第二个(中间)解决方案来完成:
let multiply m =
let rec innerfunct = function
| [] -> []
| car :: cdr -> car*2 :: innerfunct cdr
innerfunct m
let a = [1; 3; 4]
printfn "%A" a
let multiplied = multiply a
printfn "%A" multiplied
也可以乘以不同的值(这个函数毕竟叫做multiply
而不是double
)。 此外,现在innerfunct
函数非常小,我们可以使名称与小范围匹配(范围越小,名称越短):
let multiply m xs =
let rec inner = function
| [] -> []
| x :: tail -> x*m :: inner tail
inner xs
请注意,我把这个因子放在第一位,最后放到列表中。 这与其他List
功能类似,并允许使用部分应用程序创建预先定制的功能:
let double = multiply 2
let doubled = double a
现在剩下的就是进行multiply
尾递归:
let multiply m xs =
let rec inner acc = function
| [] -> acc
| x :: tail -> inner (x*m :: acc) tail
inner [] xs |> List.rev
所以我们最终得到(用于教育目的) let multiply' m = List.map ((*) m)
的硬编码版本,
F#是一个'单通'编译器,所以你可以期望任何编译错误在错误之下产生级联效应。 当出现编译错误时,请关注该单一错误。 虽然你的代码可能有更多的错误(你这样做),但也可能是后来的错误只是第一个错误的后果。
正如编译器所说, car
是不可变的,所以你可以给它分配一个值。
在函数式编程中,映射可以很容易地实现为递归函数:
// ('a -> 'b) -> 'a list -> 'b list
let rec map f = function
| [] -> []
| h::t -> f h :: map f t
然而,这个版本不是尾递归的,因为它在将头部放到尾部之前递归调用map
。
通常可以通过引入一个使用累加器作为结果的'内部'实现函数来重构尾部递归实现。 这是一种方法:
// ('a -> 'b) -> 'a list -> 'b list
let map' f xs =
let rec mapImp f acc = function
| [] -> acc
| h::t -> mapImp f (acc @ [f h]) t
mapImp f [] xs
这里, mapImp
是在h::t
情况下要调用的最后一个操作。
这个实现有点低效,因为它在每次迭代中连接两个列表( acc @ [fh]
)。 根据要映射的列表大小,利用累加器可能会更高效,然后在最后执行一次反向操作:
// ('a -> 'b) -> 'a list -> 'b list
let map'' f xs =
let rec mapImp f acc = function
| [] -> acc
| h::t -> mapImp f (f h :: acc) t
mapImp f [] xs |> List.rev
然而,无论如何,做这一切的唯一理由是练习,因为这个功能已经内置了。
在所有情况下,您都可以使用映射函数将列表中的所有元素乘以二:
> let mdouble = List.map ((*) 2);;
val mdouble : (int list -> int list)
> mdouble [1..10];;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]
但是,通常情况下,我甚至不会在乎明确定义这样的函数。 相反,你可以直接使用它:
> List.map ((*) 2) [1..10];;
val it : int list = [2; 4; 6; 8; 10; 12; 14; 16; 18; 20]
你可以用同样的方法使用上面所有的地图功能。
您在匹配语句中创建的符号不可变,所以当您与(car::cdr)
匹配时,您无法更改其值。
标准的功能方法是用计算出的值生成一个新的列表。 为此,你可以写这样的东西:
let multiplyBy2 = List.map (fun x -> x * 2)
multiplyBy2 [1;2;3;4;5]
这本身不是尾递归(但List.map是)。 如果你真的想改变列表的值,你可以改用数组。 然后你的函数不会产生任何新的对象,只需遍历数组:
let multiplyArrayBy2 arr =
arr
|> Array.iteri (fun index value -> arr.[index] <- value * 2)
let someArray = [| 1; 2; 3; 4; 5 |]
multiplyArrayBy2 someArray
链接地址: http://www.djcxy.com/p/80559.html