对于不需要的括号的错误实现(在Expression求值程序中)

时间:2013-11-15 21:35:05

标签: parsing recursion f# expression f#-3.0

在下面我已经包含了我的整个程序代码,这是一个数学表达式评估器。我用代码注释我的代码来解释一切。

我采取的方法如下:

首先,lexing部分,只需使用Regex完成,然后返回Token list(请参阅下面的Token类型)。

第二:实现一个带有令牌列表并评估所有内容的函数(参见下面的函数evalExpr),但该表达式(Token list)不与任何parantheses嵌套。

然后正如我在quistion中提到的关于如何解决嵌套的parenthses问题,我无法实现 良好的 功能,因为我有一个令牌的一维列表,我发现很难将它转换为一个树并以后续的方式评估它,所以我做了以下(见下面笨拙的unnest2函数):

我尝试找到括号中最嵌套的表达式并将其评估为数字标记(值)(参见令牌类型 - 数字),然后使用另一个函数来保持最嵌套的表达式,直到没有括号。

我正在使用F#Interactive测试这个功能,并对功能进行了更改,直到我得到了我想要的内容:

所以我的程序现在可以这样做:“5 +((5-2)+ 4)”=“5+(3 + 4)”=“5 + 7”= 12(yeeessss,它有效)

但是当我对此进行测试时:“4+(5+(3))”=“4 +(3)”=“4 + 3”= 7 - >请注意,在取消阶段

中如何忽略两个“打开”括号之间的5

所以我需要你帮助的人找出unnest2函数中的错误。

请注意,我是F#的初学者,请原谅我试图解决这个问题,因为我有C#背景。

注意:很多人告诉我,我需要使用Shunting-yard算法,我不喜欢它,我想用我自己的算法来解决这个问题

open System.Text.RegularExpressions

// Token types
type Token = 
    | Digit of float
    | Open 
    | Close 
    | Hat
    | Plus
    | Minus
    | Star
    | DivBy
    | Cos
    | Sin
    | Tan
    | Fact
    | Sqrt

let regex pattern = new Regex(pattern)

let tokenRegex = regex @"[0-9]+(\.+\d*)?|\+|\-|\*|\/|\(|\)|\^|cos|sin|tan|sqrt|fact"

// a factorial function along the way
let rec fact n = if n < 2 then 1 else n * fact(n-1)

// The lexing part, pretty straightforward
let tokenize input = 
    [for x in tokenRegex.Matches(input) do
        let token = 
            match x.Value with
            | "+" -> Plus
            | "-" -> Minus
            | "*" -> Star
            | "/" -> DivBy
            | "^" -> Hat
            | "(" -> Open 
            | ")" -> Close
            | "cos" -> Cos
            | "sin" -> Sin
            | "tan" -> Tan
            | "sqrt" -> Sqrt
            | "fact" -> Fact
            | s   -> Digit (float s)
        yield token]


let decompose (src:Token list) = 
    match src with 
    | head::tail -> Some(head,tail)
    | _ -> None

// evaluates a Token list to a float, it calculates an unnested expression!
let rec evalExpr src = 
    match src with
    | [Digit value] -> value
    | _ ->
        match decompose src with
        | Some(Digit tok, rest) ->
            match decompose rest with 
            | Some(Plus, rest) -> tok + evalExpr rest
            | Some(Minus, rest) -> tok - evalExpr rest
            | Some(Star, rest) -> tok * evalExpr rest
            | Some(DivBy, rest) -> tok / evalExpr rest
            | Some(Hat, rest) ->
                match decompose rest with
                    | Some(Digit index,rest) -> evalExpr (Digit(tok**index)::rest)
                    | _ -> failwith "Expected a number after ^"
            | None -> failwith "expected an arthimetic operation (+, -, *, or /) or power (^) after a number"
        | Some(Tan,rest) ->
            match rest with
                | [Digit value] -> evalExpr [Digit(tan value)]
                | Digit(value)::rest -> evalExpr (Digit(tan value)::rest)
                | _ -> failwith "Exprected a number after tan"
        | Some(Cos,rest) ->
            match rest with
                | [Digit value] -> evalExpr [Digit(cos value)]
                | Digit(value)::rest -> evalExpr (Digit(cos value)::rest)
                | _ -> failwith "Exprected a number after cos"
        | Some(Sin,rest) ->
            match rest with
                | [Digit value] -> evalExpr [Digit(sin value)]
                | Digit(value)::rest -> evalExpr (Digit(sin value)::rest)
                | _ -> failwith "Exprected a number after sin"     
        | Some(Sqrt,rest) ->
            match rest with
                | [Digit value] -> evalExpr [Digit(sqrt value)]
                | Digit(value)::rest -> evalExpr (Digit(sqrt value)::rest)
                | _ -> failwith "Exprected a number after sqaure root"
        | Some(Fact,rest) ->
            match rest with
                | [Digit value] -> evalExpr [Digit(float (fact (int value)))]
                | Digit(value)::rest -> evalExpr (Digit(float (fact (int value)))::rest)
                | _ -> failwith "Exprected a number after factorial"          
        | _ -> failwith "input error"

// returns a Token type of Digit with an evaluated Token list
let parseExpr src = 
    src |> evalExpr |> Digit 

// checks if a Token list has some token
let has (list:Token list) tok =
    list |> List.exists (fun x -> x = tok)

let rev list = 
    List.rev list

// here it is, pretty clumsy implementation on finding 
// the most nested expression, and evaluating it with 'parseExpr'
// "5+((9-4)+3)" becomes "5+(5+3)"
let rec unnest2 src acc (src2:Token list) = 
    match src with 
    | [] -> []
    | [Close] -> 
        if src2.Head = Open then (rev src2.Tail) @ [parseExpr (rev acc)] 
        else (rev src2) @ [parseExpr (rev acc)]
    | Close::rest -> 
        if src2.Head = Open then (rev src2.Tail) @ [parseExpr (rev acc)] @ rest
        else (rev src2) @ [parseExpr (rev acc)] @ rest
    | tok::rest ->
        match tok with
        | token -> 
            if (src2 |> has <| Open) && token <> Open then unnest2 rest (token::acc) src2
            else unnest2 rest [] (token::src2)

// wrapper function
let unnest src =
    unnest2 src [] []

// if a Token list has 'Open' (open parentheses) then keep unnesting it 
let rec parse2 src =
    if src |> has <| Open then parse2 (unnest src)
    else evalExpr src

// wrapper function
let parse input = 
    input |> tokenize |> parse2

1 个答案:

答案 0 :(得分:3)

您可能没有做过的一件事就是用笔和纸来完成书籍中描述的算法,然后使用测试用例构建函数,直到您拥有整个解决方案。

您需要回到刚才数字的原始问题和基本运算符+, - ,*,/ 无需parens 以更改优先级并学习构建Abstract Syntax Trees

一旦你正确构建了AST,你会发现AST中不需要parens。在输入中仍然需要使用parens来对操作数进行分组,并使用运算符作为前缀表示法。

然后,当您构建正确的AST时,您可以通过走树来轻松评估它。

当您从输入开始时,从prefix notation,AKA polish notation开始,因为这有助于直接构建树,在本例中为AST。

Infix  2*3  
Prefix *(2,3)  


AST

   *
  / \
 2   3

请注意,使用parens时,它们可用于更改优先级和分组。以前缀表示法开始时,仅使用parens进行分组而不更改优先级。

一旦你获得解析前缀表示法的基本算术运算符,转换为AST并进行评估,你就可以添加更多的功能,例如
1. Pretty printers
2. REPL
3. infix符号。
4. operator precedence以及用于更改运算符优先级的parens 5. Associativity运营商
6. Syntactic sugar
7.简化规则,例如x + 0 = x或x * 1 = x 8. Term rewriting