Created
October 29, 2018 20:05
-
-
Save holly-hacker/1280006d9c0c349004d6a4d0456b2235 to your computer and use it in GitHub Desktop.
Mathematical expression parser
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; | |
namespace test20181029_matheval { | |
public static class Evaluator | |
{ | |
private static Dictionary<char, Func<double, double, double>>[] functions; | |
static Evaluator() | |
{ | |
functions = new[] { | |
new Dictionary<char, Func<double, double, double>> { | |
{'^', Math.Pow}, | |
}, | |
new Dictionary<char, Func<double, double, double>> { | |
{'*', (x, y) => x * y}, | |
{'/', (x, y) => x / y}, | |
}, | |
new Dictionary<char, Func<double, double, double>> { | |
{'+', (x, y) => x + y}, | |
{'-', (x, y) => x - y}, | |
}, | |
}; | |
} | |
public static double Evaluate(string input) => Flatten(Tokenize(input.AsSpan())); // input is span to prevent string copies (heap allocations) on recursion | |
internal static double Flatten(List<object> root) | |
{ | |
// Recursively flatten lists (replaces parens with the evaluation of their content) | |
for (int i = 0; i < root.Count; i++) | |
if (root[i] is List<object> objList) | |
root[i] = Flatten(objList); | |
// Console.Write($"Evaluating {string.Join(string.Empty, root.Select(x => x.ToString()))} to "); | |
// Check every level of operator precedence | |
foreach (Dictionary<char, Func<double, double, double>> dic in functions) { | |
for (int i = 0; i < root.Count; i++) { | |
if (!(root[i] is char c)) continue; | |
double prev; | |
double next; | |
if (root[i - 1] is double d1 && root[i + 1] is double d2) { | |
prev = d1; | |
next = d2; | |
} else throw new Exception(); | |
if (!dic.ContainsKey(c)) continue; | |
root[--i] = dic[c](prev, next); | |
root.RemoveAt(i + 2); | |
root.RemoveAt(i + 1); | |
} | |
} | |
if (root.Count != 1 || !(root[0] is double result)) | |
throw new Exception("Could not reduce flat token list to a single value: " + string.Join(string.Empty, root.Select(x => x.ToString()))); | |
// Console.WriteLine(result); | |
return result; | |
} | |
internal static List<object> Tokenize(ReadOnlySpan<char> buffer) | |
{ | |
var root = new List<object>(); | |
for (int i = 0; i < buffer.Length;) { | |
char c = buffer[i++]; | |
if (c.IsDigit()) { | |
double d = c.ParseInt(); | |
int div = 0; | |
while (i != buffer.Length) { | |
c = buffer[i++]; | |
if (c == '.') { | |
div = div == 0 ? 1 : throw new Exception("Multiple periods in int"); // set div to 0, or throw | |
} else if (c.IsDigit()) { | |
if (div == 0) { | |
d = d * 10 + c.ParseInt(); | |
} else { | |
d += c.ParseInt() * (1f / (div *= 10)); | |
} | |
} | |
// else if (c.IsWhitespace()) continue; | |
else { | |
--i; | |
break; | |
} | |
} | |
root.Add(Math.Round(d, div == 0 ? 0 : (int)Math.Log10(div))); | |
} else if (c.IsOperator()) | |
root.Add(c); | |
else if (c.IsWhitespace()) | |
continue; | |
else if (c == '(') { | |
int startIdx = i; | |
int level = 1; | |
while (i != buffer.Length) { | |
c = buffer[i++]; | |
if (c == ')') level--; | |
if (c == '(') level++; | |
if (level == 0) break; | |
} | |
if (level != 0) | |
throw new Exception(); | |
root.Add(Tokenize(buffer.Slice(startIdx, i - startIdx - 1))); | |
} | |
} | |
return root; | |
} | |
private static bool IsDigit(this char c) => c >= '0' && c <= '9'; | |
private static bool IsOperator(this char c) => c == '/' || c == '*' || c == '-' || c == '+'; | |
private static bool IsWhitespace(this char c) => c == ' ' || c == '\t' || c == '\r' || c == '\n'; | |
private static int ParseInt(this char c) => c - '0'; | |
} | |
internal static class Program | |
{ | |
private static void Main(string[] args) | |
{ | |
string toParse = "27+(0.5*(4*2.5))*3"; | |
Console.WriteLine($"Parsing: '{toParse}'\n"); | |
Console.WriteLine(TokenToString(Evaluator.Tokenize(toParse))); | |
Console.WriteLine("Evaluated: " + Evaluator.Evaluate(toParse)); | |
Console.ReadLine(); | |
// quick method to print out tokens | |
string TokenToString(IEnumerable<object> tlist, int tab = 0) => string.Join("\n", tlist.Select(x => x is List<object> tl ? TokenToString(tl, tab+1) : $"{new string('\t', tab)}'{x}'")); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment