如何将字符串转换为F#中的列表单词

时间:2018-02-05 23:39:40

标签: string list f#

我需要将字符串转换为单词列表 没有内置函数,这是我到目前为止所做的,显然是错误的:

let rec convert word = 
    match word with
    |"."      ->[]
    |word -> ["word"]
    |word + " " + words -> [word]@convert words

4 个答案:

答案 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,它接受​​一个字符串,返回一个包含单个单词的列表,或者一个包含一个主题词和其余字符串的元素列表,或者任何信号没有别的东西需要进一步分裂,时间就是提供结果。

使用stringchar[]的二元性我们现在可以实现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"]

在这种情况下使用纯递归的关键是你需要跟踪状态:

  1. acc:当您到达某个空格或整个字符串的末尾时,您正在累积以填充下一个单词的字符
  2. words你已经从字符串中分离出来了;这将在每次遇到空格时更改(并且我们的acc缓冲区中有字符)
  3. rest:我们尚未在字符串中检查的字符;每次递归时,这会缩小一个字符
  4. 这是内部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,过滤了空列表,并重新组合成字符串。