如何操作F#中的列表元素

is0*_*is0 9 f# functional-programming f#-interactive f#-3.0

我目前正在使用F#完成一个项目.我对函数式编程很陌生,虽然我熟悉列表项不可变的想法,但我仍然遇到一些问题:

我有一个格式的字符串列表

["(states, (1,2,3,4,5))"; "(alpha, (1,2))"; "(final, (1))"]
Run Code Online (Sandbox Code Playgroud)

我想要做的是将每个列表元素转换为自己的列表,而不使用最初的逗号分隔字符串.输出应该如下所示:

["1"; "2"; "3"; "4"; "5"]
["1"; "2"]
["1"]
Run Code Online (Sandbox Code Playgroud)

我已经找到了无数种方法来连接列表元素,到目前为止我的最佳猜测(展开,或类似的东西)都没有结果.任何帮助或正确方向的一点将非常感激.谢谢!

Mar*_*ann 7

只是为了它的乐趣,这里是一个如何使用解析器组合库FParsec解析字符串的大纲.

首先,导入一些模块:

open FParsec.Primitives
open FParsec.CharParsers
Run Code Online (Sandbox Code Playgroud)

然后,您可以定义一个解析器,它将匹配括号括起的所有字符串:

let betweenParentheses p s = between (pstring "(") (pstring ")") p s
Run Code Online (Sandbox Code Playgroud)

这将匹配用括号括起来,例如任何串"(42)","(foo)","(1,2,3,4,5)"等等,这取决于具体的分析器p作为第一个参数传递.

为了解析像"(1,2,3,4,5)"或的数字"(1,2)",你可以结合betweenParenthesesFParsec的内置sepBypint32:

let pnumbers s = betweenParentheses (sepBy pint32 (pstring ",")) s
Run Code Online (Sandbox Code Playgroud)

pint32是一个整数sepBy解析器,是一个解析器,它读取一个由字符串分隔的值列表 - 在本例中",".

为了解析整个"组"值,例如"(states, (1,2,3,4,5))""(alpha, (1,2))",您可以再次使用betweenParenthesespnumbers:

let pgroup s =
    betweenParentheses
        (manyTill anyChar (pstring ",") >>. spaces >>. pnumbers) s
Run Code Online (Sandbox Code Playgroud)

manyTill组合会解析任何char值,直到遇到它为止,.接下来,pgroup解析器需要任意数量的空格,然后是定义的格式pnumbers.

最后,您可以定义一个pgroup在字符串上运行解析器的函数:

// string -> int32 list option
let parseGroup s =
    match run pgroup s with
    | Success (result, _, _) -> Some result
    | Failure _              -> None
Run Code Online (Sandbox Code Playgroud)

由于此函数返回一个选项,因此您可以使用List.choose映射可以解析的字符串:

> ["(states, (1,2,3,4,5))"; "(alpha, (1,2))"; "(final, (1))"]
  |> List.choose parseGroup;;
val it : int32 list list = [[1; 2; 3; 4; 5]; [1; 2]; [1]]
Run Code Online (Sandbox Code Playgroud)

使用FParsec很可能是过度杀伤,除非你有一些比使用.NET标准stringAPI 可以轻松解决的更灵活的格式化规则.


Jas*_*own 5

您也可以使用Char.IsDigit(至少基于您的示例数据),如下所示:

open System

// Signature is string -> string list
let getDigits (input : string) =
    input.ToCharArray()
    |> Array.filter Char.IsDigit
    |> Array.map (fun c -> c.ToString())
    |> List.ofArray

// signature is string list -> string list list
let convertToDigits input =
    input
    |> List.map getDigits
Run Code Online (Sandbox Code Playgroud)

并在F#interactive中测试它:

> let sampleData = ["(states, (1,2,3,4,5))"; "(alpha, (1,2))"; "(final, (1))"];;

val sampleData : string list =
  ["(states, (1,2,3,4,5))"; "(alpha, (1,2))"; "(final, (1))"]

> let test = convertToDigits sampleData;;

val test : string list list = [["1"; "2"; "3"; "4"; "5"]; ["1"; "2"]; ["1"]]
Run Code Online (Sandbox Code Playgroud)

注意:如果您有超过1位数字,这将把它们分成列表中的单个元素.如果你不想要你必须使用正则表达式或string.split或其他东西.


Mar*_*ann 4

您可以使用 .NET 中内置的字符串操作 API来实现此目的。您不必让它特别花哨,但它有助于通过stringAPI 提供一些精简的、柯里化的适配器:

open System

let removeWhitespace (x : string) = x.Replace(" ", "")

let splitOn (separator : string) (x : string) =
    x.Split([| separator |], StringSplitOptions.RemoveEmptyEntries)

let trim c (x : string) = x.Trim [| c |]
Run Code Online (Sandbox Code Playgroud)

唯一有点棘手的步骤是一旦您习惯了splitOn拆分"(states, (1,2,3,4,5))"[|"(states"; "1,2,3,4,5))"|]. 现在您有一个包含两个元素的数组,并且您需要第二个元素。您可以通过首先获取Seq.tail该数组,丢弃第一个元素,然后获取Seq.head结果序列,给出剩余序列的第一个元素来完成此操作。

使用这些构建块,您可以像这样提取所需的数据:

let result =
    ["(states, (1,2,3,4,5))"; "(alpha, (1,2))"; "(final, (1))"]
    |> List.map (
        removeWhitespace
        >> splitOn ",("
        >> Seq.tail
        >> Seq.head
        >> trim ')'
        >> splitOn ","
        >> Array.toList)
Run Code Online (Sandbox Code Playgroud)

结果:

val result : string list list = [["1"; "2"; "3"; "4"; "5"]; ["1"; "2"]; ["1"]]
Run Code Online (Sandbox Code Playgroud)

最不安全的部分是Seq.tail >> Seq.head组合。如果输入列表的元素少于两个,则可能会失败。更安全的替代方法是使用类似以下trySecond辅助函数的内容:

let trySecond xs =
    match xs |> Seq.truncate 2 |> Seq.toList with
    | [_; second] -> Some second
    | _ -> None
Run Code Online (Sandbox Code Playgroud)

使用此函数,您可以重写数据提取函数以使其更加健壮:

let result' =
    ["(states, (1,2,3,4,5))"; "(alpha, (1,2))"; "(final, (1))"]
    |> List.map (removeWhitespace >> splitOn ",(" >> trySecond)
    |> List.choose id
    |> List.map (trim ')' >> splitOn "," >> Array.toList)
Run Code Online (Sandbox Code Playgroud)

结果和以前一样。