1 changed files with 149 additions and 0 deletions
@ -0,0 +1,149 @@ |
|||||
|
import deques, parseutils, tables, strutils, math |
||||
|
|
||||
|
type |
||||
|
RPNKind = enum |
||||
|
rpnkNum, |
||||
|
rpnkOp |
||||
|
RPNVal = ref object |
||||
|
case kind: RPNKind |
||||
|
of rpnkNum: num: float |
||||
|
of rpnkOp: op: string |
||||
|
|
||||
|
proc tokenize(expr : string) : seq[string] = |
||||
|
var currentTok : string |
||||
|
for c in expr: |
||||
|
if c in Whitespace: |
||||
|
if currentTok.len > 0: |
||||
|
result = result & currentTok |
||||
|
currentTok = "" |
||||
|
continue |
||||
|
|
||||
|
if c == '(' or c == ')': |
||||
|
if currentTok.len > 0: |
||||
|
result = result & currentTok |
||||
|
currentTok = "" |
||||
|
|
||||
|
result = result & $c |
||||
|
|
||||
|
if c in Digits: |
||||
|
currentTok = currentTok & c |
||||
|
|
||||
|
if c in "+-*/^": |
||||
|
currentTok = "" |
||||
|
result = result & $c |
||||
|
|
||||
|
if currentTok.len > 0: |
||||
|
result = result & currentTok |
||||
|
|
||||
|
proc toRPN(expr : string) : seq[RPNVal] = |
||||
|
let prec = { |
||||
|
"^" : 0, |
||||
|
"+" : 2, |
||||
|
"-" : 2, |
||||
|
"*" : 1, |
||||
|
"/" : 1 |
||||
|
}.toTable |
||||
|
|
||||
|
let assoc = { |
||||
|
"+" : "left", |
||||
|
"-" : "left", |
||||
|
"*" : "left", |
||||
|
"/" : "left", |
||||
|
"^" : "right" |
||||
|
}.toTable |
||||
|
|
||||
|
var stack : seq[string] |
||||
|
var q = initDeque[string]() |
||||
|
|
||||
|
var tokens = expr.tokenize |
||||
|
|
||||
|
while tokens.len > 0: |
||||
|
let tok = tokens[0] |
||||
|
|
||||
|
if tok[0] in Digits: |
||||
|
q.addLast(tok) |
||||
|
|
||||
|
if not (tok[0] in Digits): |
||||
|
if tok == ")": |
||||
|
# Drain the stack onto the queue |
||||
|
while stack.len > 0: |
||||
|
let op = stack[0] |
||||
|
stack = stack[1..^1] |
||||
|
if op == "(": |
||||
|
break |
||||
|
q.addLast(op) |
||||
|
tokens = tokens[1..^1] |
||||
|
continue |
||||
|
|
||||
|
if stack.len > 0 and (stack[0] in prec) and (tok in prec): |
||||
|
# there are ops the op on the stack has higher prec |
||||
|
# or the op on the stack is left assoc and == prec |
||||
|
while (stack.len > 0 and |
||||
|
((prec[stack[0]] < prec[tok]) or |
||||
|
(assoc[stack[0]] == "left" and prec[stack[0]] == prec[tok]))): |
||||
|
# We want to evaluate higher precedence sub-expressions first |
||||
|
# This should also check the associativity |
||||
|
# If we have 3 ^ 2 + 4, it should be 3 2 ^ 4 +, not 3 2 4 + ^ |
||||
|
# If the operator is right-associative, that means we will leave it |
||||
|
q.addLast(stack[0]) |
||||
|
stack = stack[1..^1] |
||||
|
|
||||
|
stack = @[tok] & stack |
||||
|
tokens = tokens[1..^1] |
||||
|
|
||||
|
while stack.len > 0: |
||||
|
q.addLast(stack[0]) |
||||
|
stack = stack[1..^1] |
||||
|
|
||||
|
while q.len > 0: |
||||
|
var numRes : float |
||||
|
let tok = q.popFirst |
||||
|
let num = tok.parseBiggestFloat(numRes) |
||||
|
if numRes == 0: |
||||
|
# It's an operator |
||||
|
result = result & RPNVal(kind: rpnkOp, op: $tok) |
||||
|
else: |
||||
|
# It's a number |
||||
|
result = result & RPNVal(kind: rpnkNum, num: numRes) |
||||
|
|
||||
|
proc operate(stack : seq[float], op : RPNVal) : seq[float] = |
||||
|
let a : float = stack[1] |
||||
|
let b : float = stack[0] |
||||
|
|
||||
|
var res : float |
||||
|
|
||||
|
if op.op == "+": |
||||
|
res = a + b |
||||
|
if op.op == "-": |
||||
|
res = a - b |
||||
|
if op.op == "*": |
||||
|
res = a * b |
||||
|
if op.op == "/": |
||||
|
res = a / b |
||||
|
if op.op == "^": |
||||
|
res = a.pow(b) |
||||
|
|
||||
|
result = stack[2..^1] |
||||
|
result = @[res] & result |
||||
|
|
||||
|
proc calculate(expr : string) : float = |
||||
|
let tokens = expr.toRPN |
||||
|
var stack : seq[float] |
||||
|
|
||||
|
for token in tokens: |
||||
|
if token.kind == rpnkNum: |
||||
|
# If it's a number, push it onto the stack |
||||
|
stack = @[token.num] & stack |
||||
|
if token.kind == rpnkOp: |
||||
|
# If it's an operator, pop two elements off |
||||
|
# Then push the result back on |
||||
|
stack = stack.operate(token) |
||||
|
|
||||
|
assert(stack.len == 1) |
||||
|
stack[0] |
||||
|
|
||||
|
echo "7 / (2 ^ 3) ^ (4 * 2) + 10".calculate |
||||
|
echo "2 ^ 3 ^ 4".calculate |
||||
|
echo "3 ^ 2 + 4".calculate |
||||
|
for tok in "2 ^ 3 ^ 4".toRPN: |
||||
|
echo tok.repr |
Loading…
Reference in new issue