如何在数学表达式求值程序中修复此错误

时间:2014-12-19 21:40:41

标签: f# pattern-matching eval rpn

我在F#中为简单的数学表达式(使用一些自定义函数算术)编写了一个典型的求值器。虽然它似乎工作正常,但有些表达式没有按预期进行评估,例如,这些工作正常:

  • eval" 5 + 2" - > 7
  • eval" sqrt(25)^ 2" - > 25
  • eval" 1 /(sqrt(4))" - > 0.5
  • eval" 1 /(2 ^ 2 + 2)" - > 1 / 6~0.1666 ......

但这些不是:

  • eval" 1 /(sqrt(4)+2)" - >评估为1 / sqrt(6)~0.408 ...
  • eval" 1 /(sqrt 4 + 2)" - >也将评估为1 / sqrt(6)
  • eval" 1 /( - 1 + 3)" - >评估为1 /( - 4)〜-0.25

代码如下工作,标记化(字符串作为输入) - > rev-polish-notation(RPN) - > evalRpn

我认为问题似乎发生在一元函数(函数接受一个运算符)的某处,这些是sqrt函数和否定( - )函数。我真的不知道我的代码中出了什么问题。有人可以指出我在这里缺少的东西吗?

这是我在F#中的实现

open System.Collections
open System.Collections.Generic
open System.Text.RegularExpressions

type Token = 
    | Num of float
    | Plus
    | Minus
    | Star
    | Hat
    | Sqrt
    | Slash
    | Negative
    | RParen
    | LParen

let hasAny (list: Stack<'T>) = 
    list.Count <> 0

let tokenize (input:string) = 
    let tokens = new Stack<Token>()
    let push tok = tokens.Push tok
    let regex = new Regex(@"[0-9]+(\.+\d*)?|\+|\-|\*|\/|\^|\)|\(|pi|e|sqrt")
    for x in regex.Matches(input.ToLower()) do
        match x.Value with
        | "+" -> push Plus
        | "*" -> push Star
        | "/" -> push Slash
        | ")" -> push LParen
        | "(" -> push RParen
        | "^" -> push Hat
        | "sqrt" -> push Sqrt
        | "pi" -> push (Num System.Math.PI)
        | "e" -> push (Num System.Math.E)
        | "-" ->
            if tokens |> hasAny then
                match tokens.Peek() with
                | LParen -> push Minus
                | Num v -> push Minus
                | _ -> push Negative
            else 
                push Negative

        | value -> push (Num (float value))
    tokens.ToArray() |> Array.rev |> Array.toList



let isUnary = function
    | Negative | Sqrt -> true
    | _ -> false


let prec = function
    | Hat -> 3
    | Star | Slash -> 2
    | Plus | Minus -> 1
    | _ -> 0



let toRPN src =
    let output = new ResizeArray<Token>()
    let stack = new Stack<Token>()
    let rec loop = function
        | Num v::tokens -> 
            output.Add(Num v)
            loop tokens
        | RParen::tokens ->
            stack.Push RParen
            loop tokens
        | LParen::tokens ->
            while stack.Peek() <> RParen do
                output.Add(stack.Pop())
            stack.Pop() |> ignore // pop the "("
            loop tokens
        | op::tokens when op |> isUnary ->
            stack.Push op
            loop tokens
        | op::tokens ->
            if stack |> hasAny then
                if prec(stack.Peek()) >= prec op then
                    output.Add(stack.Pop())
            stack.Push op
            loop tokens
        | [] -> 
            output.AddRange(stack.ToArray())
            output 
    (loop src).ToArray()

let (@) op tok = 
    match tok with
    | Num v -> 
        match op with
        | Sqrt -> Num (sqrt v)
        | Negative -> Num (v * -1.0)
        | _ -> failwith "input error"
    | _ -> failwith "input error"

let (@@) op toks = 
    match toks with
    | Num v,Num u -> 
        match op with
        | Plus -> Num(v + u)
        | Minus -> Num(v - u)
        | Star -> Num(v * u)
        | Slash -> Num(u / v)
        | Hat -> Num(u ** v)
        | _ -> failwith "input error"
    | _ -> failwith "inpur error"


let evalRPN src = 
    let stack = new Stack<Token>()
    let rec loop = function
        | Num v::tokens -> 
            stack.Push(Num v)
            loop tokens
        | op::tokens when op |> isUnary ->
            let result = op @ stack.Pop()
            stack.Push result
            loop tokens
        | op::tokens ->
            let result = op @@ (stack.Pop(),stack.Pop())
            stack.Push result
            loop tokens
        | [] -> stack
    if loop src |> hasAny then
        match stack.Pop() with 
        | Num v -> v 
        | _ -> failwith "input error"
    else failwith "input error"  

let eval input = 
    input |> (tokenize >> toRPN >> Array.toList >> evalRPN) 

2 个答案:

答案 0 :(得分:7)

在回答您的具体问题之前,您是否注意到还有其他错误?试试eval "2-4"2.0代替-2.0

这可能是因为沿着这些方向:

match op with
    | Plus -> Num(v + u)
    | Minus -> Num(v - u)
    | Star -> Num(v * u)
    | Slash -> Num(u / v)
    | Hat -> Num(u ** v)
交换

uv,在交换操作中您没有注意到差异,因此只需将它们还原为u -v

现在关于你提到的错误,原因对我来说显而易见,通过查看你的代码,你错过了那些一元操作的优先权:

let prec = function
    | Hat -> 3
    | Star | Slash -> 2
    | Plus | Minus -> 1
    | _ -> 0

我尝试用这种方式添加它们:

let prec = function
    | Negative -> 5
    | Sqrt -> 4
    | Hat -> 3
    | Star | Slash -> 2
    | Plus | Minus -> 1
    | _ -> 0

现在看起来很好。

答案 1 :(得分:1)

编辑:嗯,好像我迟到了,Gustavo在我想知道括号时发布了答案。好吧。

  • 一元运算符的优先级错误。将主要案例| a when isUnary a -> 4添加到prec

  • LParenRParen的名称在整个代码中始终互换。 (映射到RParen)LParen

考虑到适当的优先级,它会正确运行问题中的所有测试,但我还没有检查代码的正确性。

相关问题