Last active
February 18, 2016 18:08
-
-
Save miceiken/7c5242b3b9fb479fe577 to your computer and use it in GitHub Desktop.
Infix to postfix converter and postfix evaluator
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
using System.Collections.Generic; | |
using System.Linq; | |
// References | |
// * http://scriptasylum.com/tutorials/infix_postfix/algorithms/infix-postfix/ | |
// * http://scriptasylum.com/tutorials/infix_postfix/algorithms/postfix-evaluation/ | |
namespace DoUEvenPostfixM8 | |
{ | |
// We abuse derivatives for type-checking later on | |
public abstract class ExpressionNode { } | |
// Operand i.e. numbers | |
public class OperandNode : ExpressionNode | |
{ | |
public double Value { get; set; } | |
} | |
// Operators i.e. +, -, *, / | |
public class OperatorNode : ExpressionNode | |
{ | |
public char Operator { get; set; } | |
} | |
public static class Calc | |
{ | |
// This defines the order of precedence for operations | |
// Example: | |
// 2+2*3 should result in 2+(2*3)=8 NOT (2+2)*3=12 | |
// Lower number means higher precedence | |
private static Dictionary<char, int> s_OperatorPrecedence = new Dictionary<char, int>() | |
{ | |
['+'] = 2, | |
['-'] = 2, | |
['*'] = 1, | |
['/'] = 1, | |
['('] = 0, | |
[')'] = 0 | |
}; | |
// Feeling smart? Add power (^) and modulus (%) with proper precedence | |
private static Dictionary<char, Func<double, double, double>> s_Operations = new Dictionary<char, Func<double, double, double>>() | |
{ | |
['+'] = (l, r) => l + r, | |
['-'] = (l, r) => l - r, | |
['*'] = (l, r) => l * r, | |
['/'] = (l, r) => l / r | |
}; | |
// This method builds a postfix expression from infix string | |
public static IEnumerable<ExpressionNode> BuildPostfixExpression(string infix) | |
{ | |
// The postfix expression is contained in a list | |
var postfix = new List<ExpressionNode>(); | |
// We have a stack for temporary storage of operators | |
var stack = new Stack<OperatorNode>(); | |
var operand = string.Empty; | |
foreach (var c in infix) | |
{ | |
// Ignore whitespace because who cares | |
// Also enables the user to write operands as i.e. "200 000" | |
if (char.IsWhiteSpace(c)) | |
continue; | |
// If char is a digit or seperator add it to the temporary operand string | |
// TODO: Add sanity check to make sure operand only contains one seperator i.e. "3.1.4" is not valid, but "3.14" is | |
if (char.IsDigit(c) || c == '.') | |
operand += c; | |
// Check whether char is an operator or not | |
if (s_OperatorPrecedence.ContainsKey(c)) | |
{ | |
// Since we received an operator it means we need to push the temporary operand string | |
if (!string.IsNullOrEmpty(operand)) | |
{ | |
postfix.Add(new OperandNode() { Value = double.Parse(operand) }); | |
// Empty variable to accept next operand | |
operand = string.Empty; | |
} | |
// Special case: LeftParantheses | |
// We don't want to pop anything when this comes in, so just push it and continue | |
if (c == '(') | |
{ | |
stack.Push(new OperatorNode() { Operator = c }); | |
continue; | |
} | |
// Special case: RightParantheses | |
// Pop until we find matching (Left) parantheses | |
if (c == ')') | |
{ | |
while (stack.Count > 0) | |
{ | |
var pop = stack.Pop(); | |
if (pop.Operator == '(') | |
break; | |
postfix.Add(pop); | |
} | |
continue; | |
} | |
// Push to postfix stack from popping operator stack while our current operator has higher or equal precedence | |
while (stack.Count > 0 | |
// We have this requirement because of parantheses | |
// they are isolated and we shouldn't keep popping | |
&& s_Operations.ContainsKey(stack.Peek().Operator) | |
&& s_OperatorPrecedence[c] >= s_OperatorPrecedence[stack.Peek().Operator]) | |
postfix.Add(stack.Pop()); | |
// Push our new operator | |
stack.Push(new OperatorNode() { Operator = c }); | |
} | |
} | |
// "2+2" | |
// Our in-loop operand pusher won't pick up the last "2" as it's not followed by an operator, flush it manually | |
// TODO: Better solution for this? | |
if (!string.IsNullOrEmpty(operand)) | |
postfix.Add(new OperandNode() { Value = double.Parse(operand) }); | |
// Push remaining operators to the postfix stack | |
while (stack.Count > 0) | |
postfix.Add(stack.Pop()); | |
return postfix; | |
} | |
// This method evaluates postfix expressions | |
public static OperandNode EvaluatePostfix(IEnumerable<ExpressionNode> postfix) | |
{ | |
var result = new Stack<ExpressionNode>(); | |
foreach (var node in postfix) | |
{ | |
var actualNode = node; | |
// Check if current node is an operator | |
if (node is OperatorNode) | |
{ | |
// Since this is postfix we can expect the operator to have two operands ready on the stack ("2 2 +") | |
var r = (OperandNode)result.Pop(); | |
var l = (OperandNode)result.Pop(); | |
// Create a new operand node | |
// the input parser already took care of precedence so don't worry about that | |
actualNode = new OperandNode() { Value = s_Operations[((OperatorNode)node).Operator](l.Value, r.Value) }; | |
} | |
// Push the node to the stack | |
result.Push(actualNode); | |
} | |
// If the stack has more than 1 element when we are finished iterating it means that the expression was invalid i.e. "2 +" | |
// If the stack is empty the cookie monster ate your numbers | |
if (result.Count != 1) | |
throw new InvalidOperationException("Invalid expression"); | |
// We know that we have 1 node left, which is the answer of our evaluated expression | |
// If you follow the actualNode variable in the loop you can see that it is always an OperandNode | |
// Which in turn means you can safely cast it as an OperandNode and return it | |
return result.Cast<OperandNode>().FirstOrDefault(); | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
namespace DoUEvenPostfixM8 | |
{ | |
class Program | |
{ | |
static void Main(string[] args) | |
{ | |
string input; | |
while (!string.IsNullOrEmpty(input = Console.ReadLine())) | |
{ | |
var postfixExpression = Calc.BuildPostfixExpression(input); | |
var evaluated = Calc.EvaluatePostfix(postfixExpression); | |
Console.WriteLine(evaluated.Value); | |
} | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment