我需要将字符串转换为单词列表 没有内置函数,这是我到目前为止所做的,显然是错误的:
let rec convert word =
match word with
|"." ->[]
|word -> ["word"]
|word + " " + words -> [word]@convert words
答案 0 :(得分:5)
由于你的问题有点学术性,我分别会接近解决方案。
暂时搁置不使用内置库的要求,该解决方案可能遵循经典的folding模式,该模式易于从头开始实现,假设稍后要实现某些split
属性:
let string2Words s =
let rec fold acc s =
match split s with
| [x] -> acc @ [x] // append the last word; done
| [head;tail] -> fold (acc @ [head]) tail // append the head word; continue splitting
| _ -> acc // done
fold [] s
所以,我们的任务现在缩减为实现这样的split
,它接受一个字符串,返回一个包含单个单词的列表,或者一个包含一个主题词和其余字符串的元素列表,或者任何信号没有别的东西需要进一步分裂,时间就是提供结果。
使用string
为char[]
的二元性我们现在可以实现split
依赖于string
索引器和切片而不是F#库阿森纳:
let split s =
let rec scan (s:string) i =
if s.Length = 0 then []
elif s.[i] = ' ' && i = 0 then scan s.[i+1..] 0
elif s.[i] = ' ' then [s.[..i-1]; s.[i+1..]]
elif i = (s.Length - 1) then [s]
else scan s (i+1)
scan s 0
内部递归scan
函数使用字符串切片和索引器完成我们的fold
(ab)所期望的工作,并在路上考虑角落情况。
现在把所有人放在一起
let string2Words s =
let split s =
let rec scan (s:string) i =
if s.Length = 0 then []
elif s.[i] = ' ' && i = 0 then scan s.[i+1..] 0
elif s.[i] = ' ' then [s.[..i-1]; s.[i+1..]]
elif i = (s.Length - 1) then [s]
else scan s (i+1)
scan s 0
let rec fold acc s =
match split s with
| [x] -> acc @ [x]
| [head;tail] -> fold (acc @ [head]) tail
| _ -> acc
fold [] s
并快速检查fsi:
> string2Words "life without libraries is tough";;
val it : string list = ["life"; "without"; "libraries"; "is"; "tough"]
答案 1 :(得分:3)
试试这个:
let rec words word text =
[ match text with
| [] -> yield word
| c :: tail ->
match c with
| ' ' -> yield word
yield! words "" tail
| _ -> yield! words (sprintf "%s%c" word c) tail ]
printfn "%A" ("hello my friend"
|> Seq.toList
|> words "")
["hello"; "my"; "friend"]
虽然很简单但效率不高......
答案 2 :(得分:2)
这是一种使用递归函数的方法,该函数将字符串上的模式匹配作为字符列表:
let charsToString chars = chars |> Array.ofSeq |> System.String
let split (s: string) =
let rec loop acc words rest =
match rest with
| ' '::xs ->
if Seq.isEmpty acc then
loop Seq.empty words xs
else
let newWord = charsToString acc
loop Seq.empty (Seq.append words [newWord]) xs
| x::xs -> loop (Seq.append acc [x]) words xs
| [] -> // terminal case, we've reached end of string
if Seq.isEmpty acc then
words
else
let newWord = charsToString acc
Seq.append words [newWord]
loop Seq.empty Seq.empty (List.ofSeq s)
> split "Hello my friend"
val it : seq<System.String> = seq ["Hello"; "my"; "friend"]
在这种情况下使用纯递归的关键是你需要跟踪状态:
acc
:当您到达某个空格或整个字符串的末尾时,您正在累积以填充下一个单词的字符words
你已经从字符串中分离出来了;这将在每次遇到空格时更改(并且我们的acc
缓冲区中有字符)rest
:我们尚未在字符串中检查的字符;每次递归时,这会缩小一个字符这是内部loop
函数作为其参数的内容:用于构建单词的字符序列acc
,已经拆分的words
序列,以及我们尚未处理的字符串的rest
。请注意,对loop
的第一次调用会将这两种状态的空序列和整个字符串(作为char
的列表作为rest
)传递。
内部loop
函数仅用于隐藏两个状态值的实现细节,以方便调用者。
此实现不是特别有效或优雅,它旨在显示基本的递归和模式匹配概念。
答案 3 :(得分:2)
为了理解递归,首先必须了解递归。但是你可以留下裸金属并做一些线性管道。链接预定义的库函数,每个函数在一系列中进行一次转换,以达到所需的结果。
"Hello, World ! "
|> fun s ->
(s.ToCharArray(), ([], []))
||> Array.foldBack (fun c (cs, css) ->
if c = ' ' then [], cs::css else c::cs, css )
|> List.Cons
|> List.filter (not << List.isEmpty)
|> List.map (fun s -> System.String(Array.ofList s))
// val it : System.String list = ["Hello,"; "World"; "!"]
我们将string
转换为字符数组char[]
并将一个文件夹应用于数组的每个元素,将累加器作为char list
的元组,当前单词的字符,和char list list
,到目前为止的话。这是以相反的顺序,从前到后完成,以正确的顺序构造元组的列表。此步骤的结果是consed到单个char list list
,过滤了空列表,并重新组合成字符串。