【问题标题】:How to parse a boolean expression如何解析布尔表达式
【发布时间】:2018-05-08 18:07:27
【问题描述】:

我对 F# 很陌生,我正在尝试使用递归来解决问题。

函数接收一个字符串,并返回一个布尔值。字符串被解析和评估。这是布尔逻辑,所以

  • (T|F) 返回真
  • (T&(T&T)) 返回真
  • ((T|T)&(T&F)) 返回假
  • (F) = 返回错误

我的想法是,每次找到 a ) 时,将字符串中前一个 ( to that ) 的部分替换为比较匹配的结果。一遍又一遍地这样做,直到只剩下 T 或 F,以返回 true 或 false。

编辑: 我希望它能够获取字符串,并不断用比较结果替换 ( 和 ) 之间的内容,直到它归结为 T 或 F。发生的情况是关于不完整的结构化构造的错误。错误在for循环中。

由于我对这种语言还很陌生,我不确定自己做错了什么。看到了吗?

let ComparisonSolver (comp:string) =

    let mutable trim = comp
    trim <- trim.Replace("(", "")
    trim <- trim.Replace(")", "")

    match trim with
    | "T" -> "T"
    | "F" -> "F"
    | "!T" -> "F"
    | "!F" -> "T"
    | "T&T" -> "T"
    | "F&F" -> "T"
    | "T&F" -> "F"
    | "F&T" -> "F"
    | "T|T" -> "T"
    | "F|F" -> "F"
    | "T|F" -> "T"
    | "F|T" -> "T"
    | _ -> ""

let rec BoolParser arg =
    let mutable args = arg

    if String.length arg = 1 then 
        match arg with
        | "T" -> true
        | "F" -> false
    else
        let mutable ParseStart = 0
        let endRange = String.length args

        for letter in [0 .. endRange]
            if args.[letter] = "(" then
                ParseStart <- letter
            else if args.[letter] = ")" then
                args <- args.Replace(args.[ParseStart .. letter], ComparisonSolver args.[ParseStart .. letter])
                BoolParser args

let result = BoolParser "(T)&(F)"

【问题讨论】:

  • 您预计会发生什么,以及正在发生什么?
  • 我希望它获取字符串,并不断用比较结果交换 ( 和 ) 之间的内容,直到它归结为 T 或 F。正在发生的事情是关于不完整的结构化构造的错误。
  • 不,我的意思是你的具体直接问题是 - 你的代码甚至没有编译,因为你在 for 循环周围遇到错误。把它放在问题中很重要,因为它可以让人们知道他们所看到的是否就是你所看到的,这样他们就不会开始试图在错误的地方帮助你。 -- 编辑:是的,就像那样。始终尽可能准确地说明您要解决的问题。
  • 感谢您的提示,我现在将其编辑到主帖中。

标签: parsing recursion f# boolean


【解决方案1】:

您需要更正一些事情。

  • for letter in [0 .. endRange] 的末尾缺少 do - 它应该是 for letter in [0 .. endRange] do
  • for 循环中的if 比较将charsstrings 进行比较。您需要将"("")" 替换为'('')'
  • for letter in [0 .. endRange] 将超出范围:在 F# 中,数组构造 [x..y] 将从 x 变为 y 包含。如果你有for (int i = 0; i &lt;= array.Length; i++),这有点像在 C# 中。在 F# 中,您还可以像这样声明循环:for i = 0 to endRange - 1 do
  • for letter in [0 .. endRange] 将再次超出范围:从0 变为endrange,即args 的长度。但是argsfor 循环中被缩短,因此它最终会尝试从args 中获取超出范围的字符。

现在,if..then..else 语句的问题,我认为你从一开始就看到了。

if args.[letter] = '(' then
 ParseStart <- letter
else if args.[letter] = ')' then
 args <- args.Replace(args.[ParseStart .. letter], ComparisonSolver args.[ParseStart .. letter])
 BoolParser args

让我们将两个分支中的代码作为两个独立的函数。

第一个是ParseStart &lt;- letter,它将letter 分配给ParseStart。这个函数返回unit,它是void的F#等价物。

第二个:

args <- args.Replace(args.[ParseStart .. letter], ComparisonSolver args.[ParseStart .. letter])
BoolParser args

这个函数返回一个bool

现在,当您将它们放在if..then..else 语句中时,您会在一个分支中产生unit,而在另一个分支中产生bool。在这种情况下,它不知道要返回哪一个,因此会显示“表达式应该具有类型”错误。

我强烈怀疑你想从外面打电话给BoolParser args for/if 循环。但它已缩进,以便 F# 将其视为 else if 语句的一部分。

【讨论】:

    【解决方案2】:

    解析布尔表达式的方法有很多。看看优秀的库 FParsec 可能是个好主意。

    http://www.quanttec.com/fparsec/

    在 F# 中实现解析器的另一种方法是使用可生成可读代码的活动模式

    https://docs.microsoft.com/en-us/dotnet/fsharp/language-reference/active-patterns

    很难通过主动模式提供良好的错误报告,但也许您可以从以下示例中找到一些启发:

    let next s i = struct (s, i) |> Some
    
    // Skips whitespace characters
    let (|SkipWhitespace|_|) struct (s, i) =
      let rec loop j =
        if j < String.length s && s.[j] = ' ' then
          loop (j + 1)
        else
          next s j
      loop i
    
    // Matches a specific character: ch
    let (|Char|_|) ch struct (s, i) =
      if i < String.length s && s.[i] = ch then
        next s (i + 1)
      else
        None
    
    // Matches a specific character: ch
    //  and skips trailing whitespaces
    let (|Token|_|) ch =
      function
      | Char ch (SkipWhitespace ps) -> Some ps
      | _                           -> None
    
    // Parses the boolean expressions
    let parse s =
      let rec term =
        function
        | Token 'T' ps                        -> Some (true, ps)
        | Token 'F' ps                        -> Some (false, ps)
        | Token '(' (Parse (v, Token ')' ps)) -> Some (v, ps)
        | _ -> None
      and opReducer p ch reducer = 
        let (|P|_|) ps = p ps
        let rec loop l =
          function
          | Token ch (P (r, ps))  -> loop (reducer l r) ps
          | Token ch _            -> None
          | ps                    -> Some (l, ps)
        function
        | P (l, ps) -> loop l ps
        | _         -> None 
      and andExpression ps  = opReducer term           '&' (&&) ps
      and orExpression ps   = opReducer andExpression  '|' (||) ps
      and parse ps          = orExpression ps
      and (|Parse|_|) ps    = parse ps
      match (struct (s, 0)) with
      | SkipWhitespace (Parse (v, _)) -> Some v
      | _                             -> None
    
    module Tests = 
      // FsCheck allows us to get better confidence in that the parser actually works
      open FsCheck
    
      type Whitespace = 
        | Space
    
      type Ws = Ws of (Whitespace [])*(Whitespace [])
    
      type Expression =
        | Term  of Ws*bool
        | And   of Expression*Ws*Expression
        | Or    of Expression*Ws*Expression
    
        override x.ToString () =
          let orPrio              = 1
          let andPrio             = 2
          let sb                  = System.Text.StringBuilder 16
          let ch c                = sb.Append (c : char)  |> ignore
          let token (Ws (l, r)) c = 
            sb.Append (' ', l.Length) |> ignore
            sb.Append (c : char)      |> ignore
            sb.Append (' ', r.Length) |> ignore
          let enclose p1 p2 f     = 
            if p1 > p2 then ch '('; f (); ch ')'
            else f ()
          let rec loop prio =
            function
            | Term (ws, v)    -> token ws (if v then 'T' else 'F')
            | And  (l, ws, r) -> enclose prio andPrio <| fun () -> loop andPrio l; token ws '&' ;loop andPrio r
            | Or   (l, ws, r) -> enclose prio orPrio  <| fun () -> loop orPrio l ; token ws '|' ;loop orPrio r
          loop andPrio x
          sb.ToString ()
    
        member x.ToBool () =
          let rec loop =
            function
            | Term (_, v)     -> v
            | And  (l, _, r)  -> loop l && loop r
            | Or   (l, _, r)  -> loop l || loop r
          loop x
    
      type Properties() =
        static member ``Parsing expression shall succeed`` (expr : Expression) =
          let expected  = expr.ToBool ()  |> Some
          let str       = expr.ToString ()
          let actual    = str             |> parse
          expected      = actual
    
      let fscheck () =
        let config = { Config.Quick with MaxTest = 1000; MaxRejected = 1000 }
        Check.All<Properties> config
    

    【讨论】:

      猜你喜欢
      • 2022-07-05
      • 2021-12-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-08-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多