Skip to content

Commit

Permalink
Added checks to ensure all symbols are unique
Browse files Browse the repository at this point in the history
  • Loading branch information
BLM16 committed Aug 3, 2022
1 parent ebca484 commit df75349
Show file tree
Hide file tree
Showing 8 changed files with 118 additions and 25 deletions.
2 changes: 1 addition & 1 deletion Calculator.Examples/Calculator.Examples.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
<Copyright>Copyright © 2021 Bradley Myers. All rights reserved.</Copyright>
<RepositoryUrl>https://github.com/BLM16/Tokenized-Calculator</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Version>2.0.0</Version>
<Version>2.1.0</Version>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageTags>calculator</PackageTags>
<PackageIcon>icon.png</PackageIcon>
Expand Down
6 changes: 5 additions & 1 deletion Calculator.Examples/ConsoleIO.cs
Original file line number Diff line number Diff line change
@@ -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)
{
Expand All @@ -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");
Expand Down
35 changes: 23 additions & 12 deletions Calculator/Calculator.cs
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -40,15 +41,21 @@ public class Calculator
/// <param name="functions">The list of functions the calculator recognizes. Defaults to <see cref="DefaultFunctionList"/> if no value is provided.</param>
public Calculator(Operator[] operators = null, Constant[] constants = null, Function[] functions = null)
{
var _ops = new List<Operator>(BuiltinOperatorList); // Add default operators
_ops.AddRange(operators ?? System.Array.Empty<Operator>()); // Add provided operators if any
// Verify operators are unique
this.operators = new UniqueList<Operator>((a, b) => a.Symbol.Equals(b.Symbol))
.With(BuiltinOperatorList) // Add default operators
.With(operators ?? Array.Empty<Operator>()) // 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<Constant>((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<Function>((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);
Expand All @@ -72,10 +79,12 @@ public double Calculate(string equation)
return result;
}

#region Default Symbols

/// <summary>
/// A list of the default operators used by the calculator
/// </summary>
public static Operator[] BuiltinOperatorList => new Operator[]
public static Operator[] BuiltinOperatorList => new[]
{
DefaultOperators.Addition,
DefaultOperators.Subtraction,
Expand All @@ -87,7 +96,7 @@ public double Calculate(string equation)
/// <summary>
/// A list of the default constants used by the calculator
/// </summary>
public static Constant[] DefaultConstantList => new Constant[]
public static Constant[] DefaultConstantList => new[]
{
DefaultConstants.PI,
DefaultConstants.E
Expand All @@ -96,7 +105,7 @@ public double Calculate(string equation)
/// <summary>
/// A list of the default functions used by the calculator
/// </summary>
public static Function[] DefaultFunctionList => new Function[]
public static Function[] DefaultFunctionList => new[]
{
DefaultFunctions.Sqrt,
DefaultFunctions.Cbrt,
Expand All @@ -115,4 +124,6 @@ public double Calculate(string equation)
DefaultFunctions.Deg,
DefaultFunctions.Rad
};

#endregion
}
2 changes: 1 addition & 1 deletion Calculator/Calculator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<Copyright>Copyright © 2021 Bradley Myers. All rights reserved.</Copyright>
<RepositoryUrl>https://github.com/BLM16/Tokenized-Calculator</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Version>4.1.2</Version>
<Version>4.2.0</Version>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<PackageTags>calculator; math; solve</PackageTags>
<PackageId>BLM16.Util.$(AssemblyName)</PackageId>
Expand Down
1 change: 0 additions & 1 deletion Calculator/Models/Constant.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System;
using System.Collections.Generic;
using System.Linq;

namespace BLM16.Util.Calculator.Models;
Expand Down
22 changes: 14 additions & 8 deletions Calculator/Models/Operator.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@ public class Operator
/// </summary>
private Delegate Operation { get; set; }

/// <summary>
/// Creates a new binary operator used by the <see cref="Calculator"/>
/// </summary>
/// <param name="op">The operator's symbol</param>
/// <param name="order">The operator's order of precedence (+- = 10, */ = 20, ^ = 30)</param>
/// <param name="operation">The binary operation to be performed</param>
public Operator(char op, int order, Func<double, double, double> operation)
{
Symbol = op;
Expand Down Expand Up @@ -48,9 +54,9 @@ public Operator(char op, int order, Func<double, double, double> 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<Delegate>.Default.Equals(Operation, @operator.Operation);
&& Symbol.Equals(@operator.Symbol)
&& Order.Equals(@operator.Order)
&& EqualityComparer<Delegate>.Default.Equals(Operation, @operator.Operation);

public override int GetHashCode() => HashCode.Combine(Symbol, Order, Operation);

Expand All @@ -66,29 +72,29 @@ public static class DefaultOperators
/// The default addition operator
/// </summary>
public static Operator Addition
=> new('+', 1, (double num1, double num2) => num1 + num2);
=> new('+', 10, (double num1, double num2) => num1 + num2);

/// <summary>
/// The default subtraction operator
/// </summary>
public static Operator Subtraction
=> new('-', 1, (double num1, double num2) => num1 - num2);
=> new('-', 10, (double num1, double num2) => num1 - num2);

/// <summary>
/// The default multiplication operator
/// </summary>
public static Operator Multiplication
=> new('*', 2, (double num1, double num2) => num1 * num2);
=> new('*', 20, (double num1, double num2) => num1 * num2);

/// <summary>
/// The default division operator
/// </summary>
public static Operator Division
=> new('/', 2, (double num1, double num2) => num1 / num2);
=> new('/', 20, (double num1, double num2) => num1 / num2);

/// <summary>
/// The default exponent operator
/// </summary>
public static Operator Exponent
=> new('^', 3, (double num1, double exponent) => Math.Pow(num1, exponent));
=> new('^', 30, (double num1, double exponent) => Math.Pow(num1, exponent));
}
3 changes: 2 additions & 1 deletion Calculator/Standardizer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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];
Expand Down
72 changes: 72 additions & 0 deletions Calculator/UniqueList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
using System;
using System.Collections;
using System.Collections.Generic;

namespace BLM16.Util.Calculator;

/// <summary>
/// Represents a strongly typed list of unique objects according to a <see cref="comparator"/>
/// </summary>
/// <typeparam name="T">The type of unique elements in the list</typeparam>
public class UniqueList<T> : IEnumerable<T>
{
/// <summary>
/// Contains the list of unique values
/// </summary>
private readonly List<T> _values = new();

/// <summary>
/// Compares the equality between 2 values to determine if they are unique
/// </summary>
/// <remarks>
/// Returns the equality of <typeparamref name="T"/>1 and <typeparamref name="T"/>2
/// </remarks>
private readonly Func<T, T, bool> comparator;

/// <summary>
/// Creates a new <see cref="UniqueList{T}"/> with an equality comparator
/// </summary>
/// <param name="comparator">A delegate that returns the equality of 2 <typeparamref name="T"/> values</param>
public UniqueList(Func<T, T, bool> comparator)
{
this.comparator = comparator;
}

/// <summary>
/// Adds a value to the list that is unique according to the <see cref="comparator"/>
/// </summary>
/// <param name="value">The unique value to be added</param>
/// <exception cref="ArgumentException">Thrown when <paramref name="value"/> is not unique</exception>
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);
}

/// <summary>
/// Adds a range of values to the list that are unique according to the <see cref="comparator"/>
/// </summary>
/// <param name="enumerable">The range of unique values to be added</param>
/// <returns><see langword="this"/></returns>
/// <exception cref="ArgumentException">Thrown when <paramref name="enumerable"/> contains a value that is not unique</exception>
public UniqueList<T> With(IEnumerable<T> enumerable)
{
foreach (var val in enumerable)
Add(val);

return this;
}

public IEnumerator<T> GetEnumerator()
{
foreach (var val in _values)
yield return val;
}

IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

0 comments on commit df75349

Please sign in to comment.