Skip to content

Instantly share code, notes, and snippets.

@miceiken
Last active February 18, 2016 18:08
Show Gist options
  • Save miceiken/7c5242b3b9fb479fe577 to your computer and use it in GitHub Desktop.
Save miceiken/7c5242b3b9fb479fe577 to your computer and use it in GitHub Desktop.
Infix to postfix converter and postfix evaluator
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();
}
}
}
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