Skip to content

Instantly share code, notes, and snippets.

@Myvar
Created September 17, 2017 11:52
Show Gist options
  • Save Myvar/6eae09ff4d7d4ed2a4d74c082bc401a9 to your computer and use it in GitHub Desktop.
Save Myvar/6eae09ff4d7d4ed2a4d74c082bc401a9 to your computer and use it in GitHub Desktop.
/*
More info here: https://www.reddit.com/r/dailyprogrammer/comments/7096nu/20170915_challenge_331_hard_interactive/
The Calulator uses 64-bit floating-point values internaly that is acuret up to 15-16 digits.
Handles Variables, Brackets, /, *, -, + and handles Ordering Mathematical Operations corectly
Constants include:
PI = 3.14159265358979
E = 2.71828182845905
Credit:
https://www.skillsyouneed.com/num/bodmas.html - refrance for Ordering Mathematical Operations
Some Examples Include:
calc: 9 + 10
awnser: 19
calc: (2 * 5 + 1) / 10
awnser: 1.1
calc: x = 1 / 2
awnser: 0.5
calc: y = x * 2
awnser: 1
calc: (x + 2) * (y * (5 - 100))
awnser: -237.5
calc: z = 5*-3.14
awnser: -15.7
calc: 2.6^(2 + 3/2) * (2-z)
awnser: 501.625937331698
calc: PI
awnser: 3.14159265358979
calc: E
awnser: 2.71828182845905
calc: 10 + PI
awnser: 13.1415926535898
*/
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
namespace InteractiveInterpreter
{
class Program
{
static void Main(string[] args)
{
//create new interpeter
var intp = new Interpreter();
while (true)
{
Console.Write("calc: ");
var l = Console.ReadLine();
Console.Write("awnser: ");
try
{
Console.WriteLine(intp.Execute(l));//parse and execute the expretion
}
catch (Exception e)
{
Console.WriteLine("Your expretion is not valid pleae reivaluate it and try again;");
}
}
}
}
/*
We use a layers ring style arch to acomidate extending fetures
the layers/rings are as follows
1. CalculateAwnser eg. 5 + 5
2. CalculateAwnserWithBrackets eg. (2 * 5 + 1) / 10
3. CalculateAwnserWithBracketsAndVariables eg. x = 1 / 2
The system is designed to be used in a recerive manner
*/
public class Interpreter
{
/// <summary>
/// this Dictionery stores all the variables
/// </summary>
private Dictionary<string, double> _variablesDictionary = new Dictionary<string, double>();
/// <summary>
/// Creates new Interpeter and adds PI and E constants
/// </summary>
public Interpreter()
{
_variablesDictionary.Add("PI", Math.PI);
_variablesDictionary.Add("E", Math.E);
}
/// <summary>
/// Executes the given expretion and returns the awnser
/// </summary>
/// <param name="rawExpression"></param>
/// <returns></returns>
public double Execute(string rawExpression)
{
//first we will steralize the input
var src = rawExpression.Trim().Replace("\t", "");
//get tokens
var tokens = Parse(rawExpression);
//wasing my hands
tokens.RemoveAll((x) => string.IsNullOrEmpty(x));
// eg. "10" should return 10
if (tokens.Count == 1)
{
if (!char.IsLetter(tokens[0][0]))
{
return double.Parse(tokens[0]);
}
else
{
return _variablesDictionary[tokens[0]];
}
}
//now iterate and return the awnser
CalculateAwnserWithBracketsAndVariables(tokens);
return double.Parse(tokens[0]);
}
/// <summary>
/// Calculates the awnser from the tokens, with brackets and variables
/// </summary>
/// <param name="tokens">tokens (hint, use parse() to get tokens)</param>
private void CalculateAwnserWithBracketsAndVariables(List<string> tokens)
{
if (tokens.Contains("="))
{
var (l, r) = SplitOnToken(tokens, "=");
//determin if left or right is var name eg. x = 10 or 10 = x
if (l.Count == 1)
{
CalculateAwnserWithBrackets(r);
_variablesDictionary.Add(l[0], double.Parse(r[0]));
tokens.Clear();
tokens.Add(r[0]);
}
else
{
CalculateAwnserWithBrackets(l);
_variablesDictionary.Add(r[0], double.Parse(l[0]));
tokens.Add(l[0]);
}
}
else
{
CalculateAwnserWithBrackets(tokens);
}
}
/// <summary>
/// Calculates the awnser from the tokens, with brackets
/// </summary>
/// <param name="tokens">tokens (hint, use parse() to get tokens)</param>
private void CalculateAwnserWithBrackets(List<string> tokens)
{
//only try to deal with brackets if there are brackets in the first place
if (!tokens.Contains("("))
{
CalculateAwnser(tokens);
return;
}
var buffer = new List<string>();
bool flag = false;
var tmp = new List<string>();
int depth = 0;
for (int i = 0; i < tokens.Count; i++)
{
var token = tokens[i];
if (token == "(")
{
flag = true;
if (depth == 0)
{
depth++;
continue;
}
depth++;
}
else if (token == ")")
{
depth--;
if (depth == 0)
{
CalculateAwnserWithBrackets(tmp);
buffer.AddRange(tmp);
tmp.Clear();
flag = false;
continue;
}
}
if (flag)
{
tmp.Add(token);
}
else
{
buffer.Add(token);
}
}
buffer.Remove("");
CalculateAwnser(buffer);
tokens.Clear();
tokens.AddRange(buffer);
}
/*
Ordering Mathematical Operations, is baced on this information:
https://www.skillsyouneed.com/num/bodmas.html
*/
/// <summary>
/// Calculates the awnser from the tokens
/// </summary>
/// <param name="tokens">tokens (hint, use parse() to get tokens)</param>
private void CalculateAwnser(List<string> tokens)
{
var bomas = GetCurrentBodmas(tokens);
if (bomas == '\0') return;
for (int i = 0; i < tokens.Count; i++)
{
var token = tokens[i];
if (token[0] == bomas)
{
tokens[i] = Calulate(tokens[i - 1], tokens[i + 1], bomas);
tokens.RemoveAt(i - 1);
tokens.RemoveAt(i);
break;
}
}
CalculateAwnser(tokens);
}
/// <summary>
/// Calucalates the value of an expretion, in its simplest form a left and right and operator, alsow takes variables into acount eg. 5 + 10
/// </summary>
/// <param name="left">left side of operator (eg. 5)</param>
/// <param name="right">right side of operator (eg. 10)</param>
/// <param name="aOperator">the operator (eg. +)</param>
/// <returns>The awnser of the evaluated expretion (eg. 15)</returns>
/// <exception cref="Exception">Only ^, /, *, -, + are valid operators</exception>
private string Calulate(string left, string right, char aOperator)
{
var l = !char.IsLetter(left[0]) ? double.Parse(left) : _variablesDictionary[left];
var r = !char.IsLetter(right[0]) ? double.Parse(right) : _variablesDictionary[right];
switch (aOperator)
{
case '^':
return Math.Pow(l, r).ToString();
case '/':
return (l / r).ToString();
case '*':
return (l * r).ToString();
case '-':
return (l - r).ToString();
case '+':
return (l + r).ToString();
case '=':
return (l + r).ToString();
}
throw new Exception($"The operator {aOperator} is not valid");
}
/// <summary>
/// Parse a raw string into tokens
/// </summary>
/// <param name="raw">the raw expretion</param>
/// <returns>the tokens</returns>
public List<string> Parse(string raw)
{
var re = new List<string>();
var sb = new StringBuilder();
for (int i = 0; i < raw.Length; i++)
{
var c = raw[i];
switch (c)
{
case ' ':
re.Add(sb.ToString());
sb.Clear();
break;
case ')':
case '/':
case '*':
case '+':
case '^':
case '=':
re.Add(sb.ToString());
sb.Clear();
re.Add(c.ToString());
break;
case '-':
if (re.Last().Length == 1 && "/*-+^".Contains(re.Last()[0]))
{
sb.Append(c);
break;
}
re.Add(sb.ToString());
sb.Clear();
re.Add(c.ToString());
break;
case '(':
re.Add(c.ToString());
break;
default:
sb.Append(c);
break;
}
}
re.Add(sb.ToString());
return re;
}
/// <summary>
/// Determins the order of calulation
/// </summary>
/// <param name="tokens">tokens to use</param>
/// <returns>the operator returns \0 if operator is not valid</returns>
private char GetCurrentBodmas(List<string> tokens)
{
if (tokens.Contains("^")) return '^';
if (tokens.Contains("/")) return '/';
if (tokens.Contains("*")) return '*';
if (tokens.Contains("+")) return '+';
if (tokens.Contains("-")) return '-';
return '\0';
}
/// <summary>
/// Splits the tokens into to sepret token colections baced on a spesific operator
/// </summary>
/// <param name="tokens">the tokens</param>
/// <param name="spliter">the operator to split on</param>
/// <returns>the 2 token colections</returns>
private (List<string> left, List<string> right) SplitOnToken(List<string> tokens, string spliter)
{
var left = new List<string>();
var right = new List<string>();
var flag = false;
foreach (var token in tokens)
{
if (token == spliter)
{
flag = true;
continue;
}
if (!flag)
{
left.Add(token);
}
else
{
right.Add(token);
}
}
return (left, right);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment