From df75349ea2a3aa9d2ab1ae6d5be1e6261754b5c1 Mon Sep 17 00:00:00 2001 From: Bradley Myers Date: Wed, 3 Aug 2022 15:55:16 -0400 Subject: [PATCH] Added checks to ensure all symbols are unique --- .../Calculator.Examples.csproj | 2 +- Calculator.Examples/ConsoleIO.cs | 6 +- Calculator/Calculator.cs | 35 +++++---- Calculator/Calculator.csproj | 2 +- Calculator/Models/Constant.cs | 1 - Calculator/Models/Operator.cs | 22 +++--- Calculator/Standardizer.cs | 3 +- Calculator/UniqueList.cs | 72 +++++++++++++++++++ 8 files changed, 118 insertions(+), 25 deletions(-) create mode 100644 Calculator/UniqueList.cs diff --git a/Calculator.Examples/Calculator.Examples.csproj b/Calculator.Examples/Calculator.Examples.csproj index 573cce1..a763c94 100644 --- a/Calculator.Examples/Calculator.Examples.csproj +++ b/Calculator.Examples/Calculator.Examples.csproj @@ -10,7 +10,7 @@ Copyright © 2021 Bradley Myers. All rights reserved. https://github.com/BLM16/Tokenized-Calculator git - 2.0.0 + 2.1.0 LICENSE calculator icon.png diff --git a/Calculator.Examples/ConsoleIO.cs b/Calculator.Examples/ConsoleIO.cs index 839150e..249dd09 100644 --- a/Calculator.Examples/ConsoleIO.cs +++ b/Calculator.Examples/ConsoleIO.cs @@ -1,5 +1,10 @@ using System; using BLM16.Util.Calculator; +using BLM16.Util.Calculator.Models; + +var modulusOperator = new Operator('%', 20, (a, b) => a % b); + +var calculator = new Calculator(new[] { modulusOperator }); while (true) { @@ -8,7 +13,6 @@ Console.Write("Enter your equation: "); var eq = Console.ReadLine(); - var calculator = new Calculator(); var res = calculator.Calculate(eq); Console.WriteLine($"Result: {res}\n"); diff --git a/Calculator/Calculator.cs b/Calculator/Calculator.cs index 20c143c..49c4276 100644 --- a/Calculator/Calculator.cs +++ b/Calculator/Calculator.cs @@ -1,5 +1,6 @@ using BLM16.Util.Calculator.Models; -using System.Collections.Generic; +using System; +using System.Linq; [assembly: System.Runtime.CompilerServices.InternalsVisibleTo("Calculator.Tests")] namespace BLM16.Util.Calculator; @@ -40,15 +41,21 @@ public class Calculator /// The list of functions the calculator recognizes. Defaults to if no value is provided. public Calculator(Operator[] operators = null, Constant[] constants = null, Function[] functions = null) { - var _ops = new List(BuiltinOperatorList); // Add default operators - _ops.AddRange(operators ?? System.Array.Empty()); // Add provided operators if any + // Verify operators are unique + this.operators = new UniqueList((a, b) => a.Symbol.Equals(b.Symbol)) + .With(BuiltinOperatorList) // Add default operators + .With(operators ?? Array.Empty()) // Add provided operators if there are any + .ToArray(); - // Use the default operators if no operators are provided - this.operators = _ops.ToArray(); - // Use the default constants if no constants are provided - this.constants = constants ?? DefaultConstantList; - // Use the default functions if no functions are provided - this.functions = functions ?? DefaultFunctionList; + // Verify constants are unique + this.constants = new UniqueList((a, b) => a.Symbols.Intersect(b.Symbols).Any()) + .With(constants ?? DefaultConstantList) // Use the default constants if no constants are provided + .ToArray(); + + // Verify functions are unique + this.functions = new UniqueList((a, b) => a.Symbols.Intersect(b.Symbols).Any()) + .With(functions ?? DefaultFunctionList) // Use the default functions if no functions are provided + .ToArray(); standardizer = new Standardizer(this.operators, this.constants, this.functions); lexer = new Lexer(this.operators); @@ -72,10 +79,12 @@ public double Calculate(string equation) return result; } + #region Default Symbols + /// /// A list of the default operators used by the calculator /// - public static Operator[] BuiltinOperatorList => new Operator[] + public static Operator[] BuiltinOperatorList => new[] { DefaultOperators.Addition, DefaultOperators.Subtraction, @@ -87,7 +96,7 @@ public double Calculate(string equation) /// /// A list of the default constants used by the calculator /// - public static Constant[] DefaultConstantList => new Constant[] + public static Constant[] DefaultConstantList => new[] { DefaultConstants.PI, DefaultConstants.E @@ -96,7 +105,7 @@ public double Calculate(string equation) /// /// A list of the default functions used by the calculator /// - public static Function[] DefaultFunctionList => new Function[] + public static Function[] DefaultFunctionList => new[] { DefaultFunctions.Sqrt, DefaultFunctions.Cbrt, @@ -115,4 +124,6 @@ public double Calculate(string equation) DefaultFunctions.Deg, DefaultFunctions.Rad }; + + #endregion } diff --git a/Calculator/Calculator.csproj b/Calculator/Calculator.csproj index 7b680e4..e0b20e3 100644 --- a/Calculator/Calculator.csproj +++ b/Calculator/Calculator.csproj @@ -8,7 +8,7 @@ Copyright © 2021 Bradley Myers. All rights reserved. https://github.com/BLM16/Tokenized-Calculator git - 4.1.2 + 4.2.0 LICENSE calculator; math; solve BLM16.Util.$(AssemblyName) diff --git a/Calculator/Models/Constant.cs b/Calculator/Models/Constant.cs index 20ef1a5..682ce1a 100644 --- a/Calculator/Models/Constant.cs +++ b/Calculator/Models/Constant.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Generic; using System.Linq; namespace BLM16.Util.Calculator.Models; diff --git a/Calculator/Models/Operator.cs b/Calculator/Models/Operator.cs index 0aa0ce2..05913ed 100644 --- a/Calculator/Models/Operator.cs +++ b/Calculator/Models/Operator.cs @@ -21,6 +21,12 @@ public class Operator /// private Delegate Operation { get; set; } + /// + /// Creates a new binary operator used by the + /// + /// The operator's symbol + /// The operator's order of precedence (+- = 10, */ = 20, ^ = 30) + /// The binary operation to be performed public Operator(char op, int order, Func operation) { Symbol = op; @@ -48,9 +54,9 @@ public Operator(char op, int order, Func operation) public static bool operator <(Operator left, Operator right) => left.Order < right.Order; public override bool Equals(object obj) => obj is Operator @operator - && Symbol == @operator.Symbol - && Order == @operator.Order - && EqualityComparer.Default.Equals(Operation, @operator.Operation); + && Symbol.Equals(@operator.Symbol) + && Order.Equals(@operator.Order) + && EqualityComparer.Default.Equals(Operation, @operator.Operation); public override int GetHashCode() => HashCode.Combine(Symbol, Order, Operation); @@ -66,29 +72,29 @@ public static class DefaultOperators /// The default addition operator /// public static Operator Addition - => new('+', 1, (double num1, double num2) => num1 + num2); + => new('+', 10, (double num1, double num2) => num1 + num2); /// /// The default subtraction operator /// public static Operator Subtraction - => new('-', 1, (double num1, double num2) => num1 - num2); + => new('-', 10, (double num1, double num2) => num1 - num2); /// /// The default multiplication operator /// public static Operator Multiplication - => new('*', 2, (double num1, double num2) => num1 * num2); + => new('*', 20, (double num1, double num2) => num1 * num2); /// /// The default division operator /// public static Operator Division - => new('/', 2, (double num1, double num2) => num1 / num2); + => new('/', 20, (double num1, double num2) => num1 / num2); /// /// The default exponent operator /// public static Operator Exponent - => new('^', 3, (double num1, double exponent) => Math.Pow(num1, exponent)); + => new('^', 30, (double num1, double exponent) => Math.Pow(num1, exponent)); } diff --git a/Calculator/Standardizer.cs b/Calculator/Standardizer.cs index 43c71e5..c91fb55 100644 --- a/Calculator/Standardizer.cs +++ b/Calculator/Standardizer.cs @@ -151,7 +151,8 @@ private string ComputeFunctions(string equation) } // Create a new calculator with the same operators, constants, and functions to recursively evalutate the function's contents - var calc = new Calculator(Operators, Constants, Functions); + // We must use Except to remove the builtin operators as they will be added by default leading to duplicated operators + var calc = new Calculator(Operators.Except(Calculator.BuiltinOperatorList).ToArray(), Constants, Functions); // Get the value captured by the function var sub = equation[(startIndex + f.Key.Length + 1)..endIndex]; diff --git a/Calculator/UniqueList.cs b/Calculator/UniqueList.cs new file mode 100644 index 0000000..32c1037 --- /dev/null +++ b/Calculator/UniqueList.cs @@ -0,0 +1,72 @@ +using System; +using System.Collections; +using System.Collections.Generic; + +namespace BLM16.Util.Calculator; + +/// +/// Represents a strongly typed list of unique objects according to a +/// +/// The type of unique elements in the list +public class UniqueList : IEnumerable +{ + /// + /// Contains the list of unique values + /// + private readonly List _values = new(); + + /// + /// Compares the equality between 2 values to determine if they are unique + /// + /// + /// Returns the equality of 1 and 2 + /// + private readonly Func comparator; + + /// + /// Creates a new with an equality comparator + /// + /// A delegate that returns the equality of 2 values + public UniqueList(Func comparator) + { + this.comparator = comparator; + } + + /// + /// Adds a value to the list that is unique according to the + /// + /// The unique value to be added + /// Thrown when is not unique + public void Add(T value) + { + foreach (var val in _values) + { + if (comparator(value, val)) + throw new ArgumentException($"{typeof(T)} must be unique"); + } + + _values.Add(value); + } + + /// + /// Adds a range of values to the list that are unique according to the + /// + /// The range of unique values to be added + /// + /// Thrown when contains a value that is not unique + public UniqueList With(IEnumerable enumerable) + { + foreach (var val in enumerable) + Add(val); + + return this; + } + + public IEnumerator GetEnumerator() + { + foreach (var val in _values) + yield return val; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); +}