Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/database/8.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
F# 如何在数学表达式计算器中修复此错误_F#_Pattern Matching_Eval_Rpn - Fatal编程技术网

F# 如何在数学表达式计算器中修复此错误

F# 如何在数学表达式计算器中修复此错误,f#,pattern-matching,eval,rpn,F#,Pattern Matching,Eval,Rpn,我已经为简单数学表达式算术编写了一个典型的计算器,其中包含F中的一些自定义函数。虽然它似乎工作正常,但有些表达式的计算结果并不符合预期,例如,这些表达式工作正常: 评估5+2->7 评估sqrt25^2->25 评估1/sqrt4->0.5 评估1/2^2+2->1/6~0.1666。。。 但这些并不是: 求值1/sqrt4+2->求值为1/sqrt6~0.408。。。 评估1/sqrt 4+2->也将评估为1/sqrt 6 求值1/-1+3->求值为1/-4~-0.25 代码的工作原理如下:标

我已经为简单数学表达式算术编写了一个典型的计算器,其中包含F中的一些自定义函数。虽然它似乎工作正常,但有些表达式的计算结果并不符合预期,例如,这些表达式工作正常:

评估5+2->7 评估sqrt25^2->25 评估1/sqrt4->0.5 评估1/2^2+2->1/6~0.1666。。。 但这些并不是:

求值1/sqrt4+2->求值为1/sqrt6~0.408。。。 评估1/sqrt 4+2->也将评估为1/sqrt 6 求值1/-1+3->求值为1/-4~-0.25 代码的工作原理如下:标记化字符串作为输入->到修订波兰符号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) 

在回答您的具体问题之前,您是否注意到您有另一个bug?试试评估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)
u和v是交换的,在交换运算中你不会注意到差别,所以只需将它们还原为u-v即可

关于您提到的bug,原因对我来说似乎很明显,通过查看您的代码,您忽略了这些一元操作的优先级:

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
现在一切都好了。

编辑:嗯,好像我迟到了,古斯塔沃在我想知道括号的时候发布了答案。哦,好吧

一元运算符的优先级错误。将一元a->4时的主事例| a添加到prec

LParen和RParen的名称在整个代码中始终交换。映射到RParen和LParen


它以适当的优先级为我正确地运行问题中的所有测试,但我没有检查代码的正确性。

您应该删减此代码。我们真的不需要看到所有这些,只需要看到与sqrt和-,有关的东西。@mydogisbox我不确定问题是否特定于函数sqrt和-,我发布了代码以便人们可以看到整个画面,它非常简洁,所以可读性没有问题。@ZaidAjaj-这个想法是为了给出一个最小的例子。如果你只需要显示bug是/unary-and+,那么就删除所有其余的代码以获得更好的帮助。我将其从伪代码“翻译”为F,并做了一些修改,使其尽可能小。然而,peudo代码没有为一元运算符添加优先级,在转换为RPN时也没有对它们做任何处理。对于混淆,RPARE表示OpenParen,LParen表示CloseParen,代码的其余部分与它们的使用是一致的!我没有注意到,对我来说,这样写是很自然的,因为我通常是从右向左读的,所以如果从左向右读,开头部分实际上是左边的,如果从右向左读,它就是右边的。谢谢你的回答。