Created
September 17, 2017 11:52
-
-
Save Myvar/6eae09ff4d7d4ed2a4d74c082bc401a9 to your computer and use it in GitHub Desktop.
My submission for https://www.reddit.com/r/dailyprogrammer/comments/7096nu/20170915_challenge_331_hard_interactive/
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
/* | |
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