F# 在没有第一个或最后一个元素的情况下优雅地折叠?
F# folding gracefully without the first or last element?
假设您有一个字符串列表:[ "a"; "b"; "c" ] 并且您想将其转换为单个字符串,如下所示: "a,b,c" 注意最后一个逗号丢失了。
我发现这种情况对我来说一次又一次地出现,想想所有不允许尾随逗号的编程语言,你正在构建某种代码生成器。
我通常会得到这样的结果:
let listOfThings = ["a";"b";"c"]
let folded =
listOfThings
|> List.map (fun i -> i + ",")
|> List.fold (+) ""
|> (fun s -> s.Substring(0, s.Length - 1))
我觉得已经有一些类似 fold 的函数了,因为这似乎是一个基本的用例,我只是想不出它的名字是什么,或者用什么名字来搜索它。
A fold
将折叠函数递归地应用于列表的所有值,从初始状态开始,在这种情况下您并不是特别想要。
使用将列表的头部作为其起始状态的 reduce 更简单:
listOfThings |> List.reduce (fun sum cur -> sum + "," + cur) // "a,b,c"
一个小缺点是,由于它使用列表头,因此使用空列表调用 reduce 会失败。您可以通过检查空列表来缓解这种情况。
如您所述,在没有任何内置函数的情况下,我们跳过为最后一个元素添加尾随逗号:
let rec join = function
| [] -> ""
| [x] -> x
| x::xs -> x + "," + join xs
["a"; "b"; "c"] |> join // a,b,c
然而,最有效的方法是使用 String.Join
,它在内部使用 StringBuilder
,而 reduce
为每次调用分配一个新字符串:
String.Join(",", listOfThings) // "a,b,c"
应用于列表元素的缩减必须与这些元素属于同一类型。相比之下,a fold
的累加器(也称为状态)可以是不同类型的,这样更通用。签名使其显而易见:
val reduce: ('a -> 'a -> 'a) -> 'a list -> 'a
val fold: ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a
一种可能的方法可能包括为列表的第一个元素(或最后一个元素,在 foldBack
的情况下)提供不同的折叠功能。在这里检查空列表也是明智的,因为它是 reduce
.
let fold1 folderN folder0 state = function
| [] -> state
| x::xs -> List.fold folderN (folder0 state x) xs
// val fold1 :
// folderN:('a -> 'b -> 'a) ->
// folder0:('a -> 'b -> 'a) -> state:'a -> _arg1:'b list -> 'a
现在我们可以折叠成一个列表,甚至可以使用 StringBuilder
:
([], ["a";"b";"c"])
||> fold1
(fun s t -> t::", "::s)
(fun s t -> t::s)
|> List.rev
// val it : string list = ["a"; ", "; "b"; ", "; "c"]
(System.Text.StringBuilder(), ["a";"b";"c"])
||> fold1
(fun s t -> s.Append(", ").Append t)
(fun s t -> s.Append t)
|> string
// val it : string = "a, b, c"
确实,有一个内置函数可以执行此操作:
let s = [ "a"; "b"; "c" ]
String.concat ", " s // "a, b, c"
假设您有一个字符串列表:[ "a"; "b"; "c" ] 并且您想将其转换为单个字符串,如下所示: "a,b,c" 注意最后一个逗号丢失了。 我发现这种情况对我来说一次又一次地出现,想想所有不允许尾随逗号的编程语言,你正在构建某种代码生成器。 我通常会得到这样的结果:
let listOfThings = ["a";"b";"c"]
let folded =
listOfThings
|> List.map (fun i -> i + ",")
|> List.fold (+) ""
|> (fun s -> s.Substring(0, s.Length - 1))
我觉得已经有一些类似 fold 的函数了,因为这似乎是一个基本的用例,我只是想不出它的名字是什么,或者用什么名字来搜索它。
A fold
将折叠函数递归地应用于列表的所有值,从初始状态开始,在这种情况下您并不是特别想要。
使用将列表的头部作为其起始状态的 reduce 更简单:
listOfThings |> List.reduce (fun sum cur -> sum + "," + cur) // "a,b,c"
一个小缺点是,由于它使用列表头,因此使用空列表调用 reduce 会失败。您可以通过检查空列表来缓解这种情况。
如您所述,在没有任何内置函数的情况下,我们跳过为最后一个元素添加尾随逗号:
let rec join = function
| [] -> ""
| [x] -> x
| x::xs -> x + "," + join xs
["a"; "b"; "c"] |> join // a,b,c
然而,最有效的方法是使用 String.Join
,它在内部使用 StringBuilder
,而 reduce
为每次调用分配一个新字符串:
String.Join(",", listOfThings) // "a,b,c"
应用于列表元素的缩减必须与这些元素属于同一类型。相比之下,a fold
的累加器(也称为状态)可以是不同类型的,这样更通用。签名使其显而易见:
val reduce: ('a -> 'a -> 'a) -> 'a list -> 'a val fold: ('a -> 'b -> 'a) -> 'a -> 'b list -> 'a
一种可能的方法可能包括为列表的第一个元素(或最后一个元素,在 foldBack
的情况下)提供不同的折叠功能。在这里检查空列表也是明智的,因为它是 reduce
.
let fold1 folderN folder0 state = function
| [] -> state
| x::xs -> List.fold folderN (folder0 state x) xs
// val fold1 :
// folderN:('a -> 'b -> 'a) ->
// folder0:('a -> 'b -> 'a) -> state:'a -> _arg1:'b list -> 'a
现在我们可以折叠成一个列表,甚至可以使用 StringBuilder
:
([], ["a";"b";"c"])
||> fold1
(fun s t -> t::", "::s)
(fun s t -> t::s)
|> List.rev
// val it : string list = ["a"; ", "; "b"; ", "; "c"]
(System.Text.StringBuilder(), ["a";"b";"c"])
||> fold1
(fun s t -> s.Append(", ").Append t)
(fun s t -> s.Append t)
|> string
// val it : string = "a, b, c"
确实,有一个内置函数可以执行此操作:
let s = [ "a"; "b"; "c" ]
String.concat ", " s // "a, b, c"