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)
我已经找到了无数种方法来连接列表元素,到目前为止我的最佳猜测(展开,或类似的东西)都没有结果.任何帮助或正确方向的一点将非常感激.谢谢!
只是为了它的乐趣,这里是一个如何使用解析器组合库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的内置sepBy和pint32:
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))",您可以再次使用betweenParentheses和pnumbers:
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 可以轻松解决的更灵活的格式化规则.
您也可以使用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或其他东西.
您可以使用 .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)
结果和以前一样。
| 归档时间: |
|
| 查看次数: |
641 次 |
| 最近记录: |