我目前正在使用F#完成一个项目。我对函数式编程很陌生,虽然我熟悉列表项不可变的想法,但我仍然有一些问题:
我有一个格式为
的字符串列表["(states, (1,2,3,4,5))"; "(alpha, (1,2))"; "(final, (1))"]
我想要做的是将每个列表元素转换为自己的列表,而不使用逗号分隔的初始字符串。输出应该如下所示:
["1"; "2"; "3"; "4"; "5"]
["1"; "2"]
["1"]
我找到了无数种方法来连接列表元素,到目前为止我的最佳猜测(展开,或类似的东西)都没有结果。任何帮助或正确方向的一点将非常感激。谢谢!
答案 0 :(得分:7)
只是为了它的乐趣,这里概述了如何使用解析器组合库FParsec来解析字符串。
首先,导入一些模块:
open FParsec.Primitives
open FParsec.CharParsers
然后,您可以定义一个解析器,它将匹配括号括起来的所有字符串:
let betweenParentheses p s = between (pstring "(") (pstring ")") p s
这将匹配括号中的任何字符串,例如"(42)"
,"(foo)"
,"(1,2,3,4,5)"
等,具体取决于作为第一个参数传递的特定解析器p
为了解析"(1,2,3,4,5)"
或"(1,2)"
等数字,您可以将betweenParentheses
与FParsec的内置sepBy
和pint32
结合使用:
let pnumbers s = betweenParentheses (sepBy pint32 (pstring ",")) s
pint32
是一个整数解析器,sepBy
是一个解析器,它读取一个由字符串分隔的值列表 - 在本例中为","
。
为了解析整个小组'的值,例如"(states, (1,2,3,4,5))"
或"(alpha, (1,2))"
,您可以再次使用betweenParentheses
和pnumbers
:
let pgroup s =
betweenParentheses
(manyTill anyChar (pstring ",") >>. spaces >>. pnumbers) s
manyTill
组合解析任何char
值,直至遇到,
。接下来,pgroup
解析器需要任意数量的空格,然后是pnumbers
定义的格式。
最后,您可以定义一个在字符串上运行pgroup
解析器的函数:
// string -> int32 list option
let parseGroup s =
match run pgroup s with
| Success (result, _, _) -> Some result
| Failure _ -> None
由于此函数返回一个选项,您可以使用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]]
使用FParsec很可能是一种过度杀伤,除非你有一些比使用.NET的标准string
API易于解决的更灵活的格式规则。
答案 1 :(得分: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
在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"]]
注意:如果您有超过1位数字,这将把它们分成列表中的单个元素。如果您不想要,则必须使用正则表达式或string.split或其他内容。
答案 2 :(得分:4)
正如@JWosty建议的那样,从单个列表项开始,并使用正则表达式匹配它。
let text = "(states, (1,2,3,4,5))"
// Match all numbers into group "number"
let pattern = @"^\(\w+,\s*\((?:(?<number>\d+),)*(?<number>\d+)\)$"
let numberMatch = System.Text.RegularExpressions.Regex.Match(text, pattern)
let values =
numberMatch.Groups.["number"].Captures // get all matches from the group
|> Seq.cast<Capture> // cast each item because regex captures are non-generic (i.e. IEnumerable instead of IEnumerable<'a>)
|> Seq.map (fun m -> m.Value) // get the matched (string) value for each capture
|> Seq.map int // parse as int
|> Seq.toList // listify
对输入文本列表执行此操作只需将此逻辑传递给List.map
。
我喜欢这个解决方案的是它不使用幻数,但它的核心只是一个正则表达式。同样将每个匹配解析为整数是非常安全的,因为我们只匹配数字。
答案 3 :(得分:4)
您可以使用.NET中的内置字符串操作API 来实现此目的。你不必特别喜欢它,但它有助于在string
API上提供一些纤细,有条理的适配器:
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 |]
唯一稍微棘手的一步是,您使用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)
结果:
val result : string list list = [["1"; "2"; "3"; "4"; "5"]; ["1"; "2"]; ["1"]]
最不安全的部分是Seq.tail >> Seq.head
组合。如果输入列表少于两个元素,则可能会失败。更安全的替代方法是使用类似以下trySecond
辅助函数:
let trySecond xs =
match xs |> Seq.truncate 2 |> Seq.toList with
| [_; second] -> Some second
| _ -> None
使用此功能,您可以重写数据提取功能,使其更加健壮:
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)
结果与以前相同。
答案 4 :(得分:2)
与Luiso的答案类似,但应避免例外。请注意,我在'('
和')'
上分开,因此我可以隔离元组。然后我尝试在将','
分割之前获取元组以获得最终结果。我使用模式匹配来避免异常。
open System
let values = ["(states, (1,2,3,4,5))"; "(alpha, (1,2))"; "(final, (1))"]
let new_list = values |> List.map(fun i -> i.Split([|'(';')'|], StringSplitOptions.RemoveEmptyEntries))
|> List.map(fun i -> i|> Array.tryItem(1))
|> List.map(function x -> match x with
| Some i -> i.Split(',') |> Array.toList
| None -> [])
printfn "%A" new_list
给你:
[["1"; "2"; "3"; "4"; "5"]; ["1"; "2"]; ["1"]]
答案 5 :(得分:1)
这个代码片段可以解决你的问题:
let values = ["(states, (1,2,3,4,5))"; "(alpha, (1,2))"; "(final, (1))"]
let mapper (value:string) =
let index = value.IndexOf('(', 2) + 1;
value.Substring(index, value.Length - index - 2).Split(',') |> Array.toList
values |> List.map mapper
<强>输出:强>
val it : string list list = [["1"; "2"; "3"; "4"; "5"]; ["1"; "2"]; ["1"]]
正如我所看到的,原始列表中的每个项目都是string
的元组和可变大小的int
元组,无论如何,上面的代码是删除第一个元组的,然后使用剩余的变量大小元组(parens中的数字),然后调用.Net string.Split()
函数并将结果数组转换为列表。希望这有帮助