Разобрать строку в список кортежей - PullRequest
1 голос
/ 29 марта 2020

Я ищу фрагмент кода на F #, который может анализировать этот тип строки:

"x = 1, y = 42, A = [1,3,4,8]"

в список кортежей, который выглядит следующим образом:

[("x", 1); ("y", 42); ("A", 1); ("A", 3); («А», 4); («А», 8)]

Заранее спасибо:)

Ответы [ 4 ]

5 голосов
/ 29 марта 2020

Вы можете легко решить эту проблему, используя библиотеку комбинатора синтаксического анализатора FParse c. Это можно сделать с помощью регулярных выражений, но это не очень элегантно. Комбинаторы парсера очень ясно дают понять, какова грамматика входных данных, которые вы можете обрабатывать. Вы также можете легко добавить другие функции, такие как пробел.

Следующее фактически создает список пар string * Value, где Value - это новый тип данных, соответствующий возможным правым сторонам ввода:

type Value = Int of int | List of int list

Теперь вы можете выполнить разбор, используя следующее:

let ident = identifier (IdentifierOptions())

let rhs = 
  // Right-hand-side is either an integer...
  ( pint32 |>> Int ) <|>
  // Or a list [ .. ] of integers separated by ','
  ( pchar '[' >>. (sepBy pint32 (pchar ',')) .>> pchar ']' |>> List ) 

let tuple = 
  // A single tuple is an identifier = right-hand-side
  ident .>> pchar '=' .>>. rhs

let p = 
  // The input is a comma separated list of tuples
  sepBy tuple (pchar ',')

run p "x=1,y=42,A=[1,3,4,8]"
2 голосов
/ 29 марта 2020

Иногда именованное регулярное выражение создает читабельный код, даже если это не регулярное выражение.

(?<id>\w+)=((\[((?<list>(\d+))*,?\s*)*\])|(?<number>\d+))

Это выглядит так: Идентификатор = [ Число , за которым следует запятая или пробел, ноль или более] | Число

let parse input =
    [
        let regex = Regex("(?<id>\w+)=((\[((?<list>(\d+))*,?\s*)*\])|(?<number>\d+))")
        let matches = regex.Matches input

        for (expr : Match) in matches do
            let group name = expr.Groups.[string name]
            let id = group "id"
            let list = group "list"
            let number = group "number"
            if list.Success then
                for (capture : Capture) in list.Captures do
                    yield (id.Value, int capture.Value)
            else if number.Success then
                yield (id.Value, int number.Value)
    ]

Тест

let input = "var1=1, var2=2, list=[1, 2, 3, 4], single=[1], empty=[], bad=[,,], bad=var"    
printfn "%A" (parse input)

Выход

[("var1", 1); ("var2", 2); ("list", 1); ("list", 2); ("list", 3); ("list", 4); "single", 1)]
1 голос
/ 30 марта 2020

Целесообразно следовать подходу, изложенному Ответом Томаса Петрисека , использующим установленную библиотеку синтаксических анализаторов FParse c.

В образовательных целях вы, возможно, захотите свернуть свой собственный комбинатор синтаксических анализаторов, и для этого усилия блог Скотта В. ( «Понимание комбинаторов синтаксического анализа» * и «Создание полезного набора комбинаторов синтаксических анализаторов» ) содержит ценную информацию.

Синтаксический анализ выглядит очень похоже:

// parse a list of integers enclosed in brackets and separated by ','
let plist = pchar '[' >>. sepBy1 pint (pchar ',') .>> pchar ']'
// parser for the right hand side, singleton integer or a list of integers
let intOrList = pint |>> (fun x -> [x]) <|> plist
// projection for generation of string * integer tuples
let ungroup p =
    p |>> List.collect (fun (key, xs) -> xs |> List.map (fun x -> key, x))
// parser for an input of zero or more string value pairs separated by ','
let parser = 
    sepBy (letters .>> pchar '=' .>>. intOrList) (pchar ',')
    |> ungroup

"x=1,y=42,A=[1,3,4,8]"
|> run parser
// val it : ((String * int) list * string) option =
//   Some ([("x", 1); ("y", 42); ("A", 1); ("A", 3); ("A", 4); ("A", 8)], "")

Эта простая грамматика все еще требует 15 или около того комбинаторов синтаксического анализа. Другое отличие состоит в том, что для простоты тип Parser был смоделирован на тип Option FSharp.

type Parser<'T,'U> = Parser of ('T -> ('U * 'T) option)

let run (Parser f1) x =     // run the parser with input
    f1 x

let returnP arg =           // lift a value to a Parser
    Parser (fun x -> Some(arg, x))

let (>>=) (Parser f1) f =   // apply parser-producing function
    Parser(f1 >> Option.bind (fun (a, b) -> run (f a) b))

let (|>>) p f =             // apply function to value inside Parser
    p >>= (f >> returnP)

let (.>>.) p1 p2 =          // andThen combinator
    p1 >>= fun r1 ->
    p2 >>= fun r2 ->
    returnP (r1, r2)

let (.>>) p1 p2 =           // andThen, but keep first value only
    (p1 .>>. p2) |>> fst

let (>>.) p1 p2 =           // andThen, keep second value only
    (p1 .>>. p2) |>> snd

let pchar c =               // parse a single character
    Parser (fun s -> 
        if String.length s > 0 && s.[0] = c then Some(c, s.[1..])
        else None )

let (<|>) (Parser f1) (Parser f2) =     // orElse combinator
    Parser(fun arg ->
        match f1 arg with None -> f2 arg | res -> res )

let choice parsers =        // choose any of a list of combinators
    List.reduce (<|>) parsers

let anyOf =                 // choose any of a list of characters
    List.map pchar >> choice

let many (Parser f) =       // matches zero or more occurrences
    let rec aux input =
        match f input with
        | None -> [], input
        | Some (x, rest1) ->
            let xs, rest2 = aux rest1
            x::xs, rest2
    Parser (fun arg -> Some(aux arg))

let many1 p =           // matches one or more occurrences of p
    p >>= fun x ->
    many p >>= fun xs ->
    returnP (x::xs)

let stringP p =         // converts list of characters to string
    p |>> (fun xs -> System.String(List.toArray xs))

let letters =           // matches one or more letters
    many1 (anyOf ['A'..'Z'] <|> anyOf ['a'..'z']) |> stringP

let pint =              // matches an integer
    many1 (anyOf ['0'..'9']) |> stringP |>> int

let sepBy1 p sep =  // matches p one or more times, separated by sep
    p .>>. many (sep >>. p) |>> (fun (x,xs) -> x::xs)

let sepBy p sep =   // matches p zero or more times, separated by sep
    sepBy1 p sep <|> returnP []
0 голосов
/ 29 марта 2020

Попробуйте это:

open System.Text.RegularExpressions

let input = "x=1,y=42,A=[1,3,4,8]"

Regex.Split(input,",(?=[A-Za-z])")  //output: [|"x=1"; "y=42"; "A=[1,3,4,8]"|]
|> Array.collect (fun x ->
    let l,v = Regex.Split(x,"=") |> fun t -> Array.head t,Array.last t  //label and value
    Regex.Split(v,",") |> Array.map (fun x -> l,Regex.Replace(x,"\[|\]","") |> int))
|> List.ofArray
...