F#用牛津逗号连接字符串

Bre*_*rry 3 .net string collections f#

我想使用牛津(或串行)逗号将字符串集合加入单个字符串中。

给定

let ss = [ "a"; "b"; "c"; "d" ]
Run Code Online (Sandbox Code Playgroud)

我想要

"a, b, c, and d"
Run Code Online (Sandbox Code Playgroud)

这是我想出的。

let oxford (strings: seq<string>) =
  let ss = Seq.toArray strings
  match ss.Length with
  | 0 -> ""
  | 1 -> ss.[0]
  | 2 -> sprintf "%s and %s" ss.[0] ss.[1]
  | _ ->
    let allButLast = ss.[0 .. ss.Length - 2]
    let commaSeparated = System.String.Join(", ", allButLast)
    sprintf "%s, and %s" commaSeparated (Seq.last ss)
Run Code Online (Sandbox Code Playgroud)

如何改善呢?

---编辑---
关于多次遍历序列的注释就很重要了。下面的两种实现都避免了转换为数组。

如果使用seq,我会很喜欢:

open System.Linq
let oxfordSeq (ss: seq<string>) =
  match ss.Count() with
  | 0 -> ""
  | 1 -> ss.First()
  | 2 -> sprintf "%s and %s" (ss.ElementAt(0)) (ss.ElementAt(1))
  | _ ->
    let allButLast = ss.Take(ss.Count() - 1)
    let commaSeparated = System.String.Join(", ", allButLast)
    sprintf "%s, and %s" commaSeparated (ss.Last())
Run Code Online (Sandbox Code Playgroud)

如果使用array,我还可以通过利用索引来避免Last()的迭代。

let oxfordArray (ss: string[]) =
  match ss.Length with
  | 0 -> ""
  | 1 -> ss.[0]
  | 2 -> sprintf "%s and %s" ss.[0] ss.[1]
  | _ ->
    let allButLast = ss.[0 .. ss.Length - 2]
    let commaSeparated = System.String.Join(", ", allButLast)
    sprintf "%s, and %s" commaSeparated (ss.[ss.Length - 1]
Run Code Online (Sandbox Code Playgroud)

---编辑---
看到来自@CaringDev的链接,我认为这很好。没有通配符,处理空值,减少索引编制的正确性,并且仅在Join()方法中遍历数组一次。

let oxford = function
    | null | [||] -> ""
    | [| a |] -> a
    | [| a; b |] -> sprintf "%s and %s" a b
    | ss ->
        let allButLast = System.ArraySegment(ss, 0, ss.Length - 1)
        let sb = System.Text.StringBuilder()
        System.String.Join(", ", allButLast) |> sb.Append |> ignore
        ", and " + ss.[ss.Length - 1] |> sb.Append |> ignore
        string sb
Run Code Online (Sandbox Code Playgroud)

这曾经也很不错,甚至跳来跳去:

let oxford2 = function
    | null | [||] -> ""
    | [| a |] -> a
    | [| a; b |] -> sprintf "%s and %s" a b
    | ss ->
        let sb = System.Text.StringBuilder()
        let action i (s: string) : unit = 
            if i < ss.Length - 1 
            then 
                sb.Append s |> ignore
                sb.Append ", " |> ignore
            else 
                sb.Append "and " |> ignore
                sb.Append s |> ignore
        Array.iteri action ss          
        string sb
Run Code Online (Sandbox Code Playgroud)

Dev*_*iss 5

您可以直接查看列表,并在列表上使用模式匹配。也许可以改善这一点,但它给出了想法。

let rec oxford (s:string) (ss:string list) =
    match ss with
    | [] -> s
    | [x;y] -> sprintf "%s, %s, and %s" s x y
    | h::t when String.length s = 0 -> oxford h t
    | h::t -> oxford (sprintf "%s, %s" s h) t
Run Code Online (Sandbox Code Playgroud)

它使用较小的列表递归地调用自己,并使用逗号。当列表的大小仅为2时,它将使用when不幸的是,但是发现我第一次打电话时需要一个空字符串,所以不要以,结尾。

编辑

因此,就我个人而言,我希望上面的选项适用于少量单词。但是,每次调用的字符串concat对于较大的数字而言效果都不理想。

// collect into a list including the *,* and *and*, then just concat that to string
let oxfordDrct (ss:string list) =
    let l = ss |> List.length
    let map i s = if(i < l-1) then [s;", "] else ["and ";s]        
    match ss with
    | [] -> ""
    | [x] -> x
    | [x;y] -> sprintf "%s, and %s" x y
    | _ -> ss |> List.mapi map |> List.concat |> String.concat ""

// Recursive like the original but instead pass a StringBuilder instead of string
let oxfordSb xs =        
    let rec collect (s:StringBuilder) (ss:string list) =
        match ss with
        | [] -> s
        | [x;y] -> sprintf ", %s, and %s" x y |> s.Append
        | h::t when s.Length = 0 -> collect (s.Append(h)) t
        | h::t -> collect (s.Append(sprintf ", %s" h)) t
    let sb = new StringBuilder()     
    (collect sb xs) |> string
Run Code Online (Sandbox Code Playgroud)

这两个选项的性能与原始选项非常相似,所有这些都比recby 更好string