在下面我已经包含了我的整个程序代码,这是一个数学表达式评估器。我用代码注释我的代码来解释一切。
我采取的方法如下:
首先,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
答案 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。