Updated TreeNode system to use abstract base class and inheritence

This commit is contained in:
Jim
2023-09-07 22:08:56 +01:00
parent eaeb64b314
commit 0225ae1f84
7 changed files with 58 additions and 231 deletions

View File

@@ -1,13 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<IsPackable>false</IsPackable> <IsPackable>false</IsPackable>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Parser Tests\TreeNodeTests.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" /> <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
<PackageReference Include="MSTest.TestAdapter" Version="2.2.8" /> <PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />

View File

@@ -16,15 +16,8 @@ namespace EngineTests
public void TestExpressionTreeSimpleExpression() public void TestExpressionTreeSimpleExpression()
{ {
string testExp = "3+4"; string testExp = "3+4";
TreeNode exptectedTree = new(Token.Plus);
Token tokfour = new("4", Token.Type.Numeric, Token.NumericType.Decimal, 0);
Token tokthree = new("3", Token.Type.Numeric, Token.NumericType.Decimal, 0);
TreeNode four = new(tokfour);
TreeNode three = new(tokthree);
exptectedTree.AddChildNode(four);
exptectedTree.AddChildNode(three);
ExpressionTree returnedTree = new(testExp); ExpressionTree returnedTree = new(testExp);
Assert.IsTrue(returnedTree.Equals(exptectedTree)); Assert.IsTrue(returnedTree.ToString() == "7");
} }
/// <summary> /// <summary>
@@ -34,11 +27,8 @@ namespace EngineTests
public void TestExpressionTreeSimpleExpressionEvaluation() public void TestExpressionTreeSimpleExpressionEvaluation()
{ {
string testExp = "3+4*7"; string testExp = "3+4*7";
Token tok31 = new("31", Token.Type.Numeric, Token.NumericType.Decimal, 0);
TreeNode exptectedTree = new(tok31);
ExpressionTree returnedTree = new(testExp); ExpressionTree returnedTree = new(testExp);
ExpressionTree? evaluatedTree = returnedTree.Evaluate(); Assert.IsTrue(returnedTree.ToString() == "31");
Assert.IsTrue(evaluatedTree.Equals(exptectedTree));
} }
/// <summary> /// <summary>
@@ -49,11 +39,17 @@ namespace EngineTests
{ {
string testExp = "3+4*7-8/7"; string testExp = "3+4*7-8/7";
decimal testValue = decimal.Divide(209 , 7); decimal testValue = decimal.Divide(209 , 7);
Token tok31 = new(testValue.ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0);
TreeNode exptectedTree = new(tok31);
ExpressionTree returnedTree = new(testExp); ExpressionTree returnedTree = new(testExp);
ExpressionTree? evaluatedTree = returnedTree.Evaluate(); Assert.IsTrue(returnedTree.ToString() == testValue.ToString());
Assert.IsTrue(evaluatedTree.Equals(exptectedTree)); }
[TestMethod]
public void TestExpressionTreeGetHashCodeReturnsHashCode()
{
string testExp = "1+1";
ExpressionTree returnedTree1 = new(testExp);
int hash = returnedTree1.GetHashCode();
Assert.IsInstanceOfType(hash, typeof(int));
} }
} }
} }

View File

@@ -4,11 +4,11 @@ using MathEngine.Parser.Tokeniser;
namespace MathEngine.Parser.Evaluator namespace MathEngine.Parser.Evaluator
{ {
/// <summary> /// <summary>
/// /// Class that evaluate TreeNodes
/// </summary> /// </summary>
internal class Evaluator internal class Evaluator
{ {
public static TreeNode? Evaluate(TreeNode rootNode) /*public static TreeNode? Evaluate(TreeNode rootNode)
{ {
if (rootNode == null) if (rootNode == null)
{ {
@@ -69,6 +69,6 @@ namespace MathEngine.Parser.Evaluator
Token.Type.Division => new TreeNode(new Token(((decimal)(lhs / rhs)).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)), Token.Type.Division => new TreeNode(new Token(((decimal)(lhs / rhs)).ToString(), Token.Type.Numeric, Token.NumericType.Decimal, 0)),
_ => throw new Exception("Potentially invalid token?"), _ => throw new Exception("Potentially invalid token?"),
}; };
} }*/
} }
} }

View File

@@ -1,10 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net6.0</TargetFramework> <TargetFramework>net7.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<EnforceCodeStyleInBuild>True</EnforceCodeStyleInBuild>
</PropertyGroup> </PropertyGroup>
<ItemGroup>
<Compile Remove="Parser\Nodes\TreeNode.cs" />
</ItemGroup>
<ItemGroup> <ItemGroup>
<AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo"> <AssemblyAttribute Include="System.Runtime.CompilerServices.InternalsVisibleTo">
<_Parameter1>EngineTests</_Parameter1> <_Parameter1>EngineTests</_Parameter1>

View File

@@ -1,4 +1,5 @@
using MathEngine.Parser.Tokeniser; using MathEngine.Parser.Parser.Node;
using MathEngine.Parser.Tokeniser;
namespace MathEngine.Parser.Parser namespace MathEngine.Parser.Parser
{ {
/// <summary> /// <summary>
@@ -9,8 +10,8 @@ namespace MathEngine.Parser.Parser
/// <summary> /// <summary>
/// The root node of the expression tree; /// The root node of the expression tree;
/// </summary> /// </summary>
private readonly TreeNode rootNode; private readonly BaseNode rootNode;
private TreeNode? evaluated_expression; private readonly BaseNode? evaluated_expression;
/// <summary> /// <summary>
/// Initialises a new instance of the MathEngine.Parser.Parser.Node class with a given Token /// Initialises a new instance of the MathEngine.Parser.Parser.Node class with a given Token
@@ -22,9 +23,10 @@ namespace MathEngine.Parser.Parser
List<Token> tokens = Tokeniser.Tokeniser.Tokenise(Expression); List<Token> tokens = Tokeniser.Tokeniser.Tokenise(Expression);
Stack<Token> rpnForm = Parser.Parse(tokens); Stack<Token> rpnForm = Parser.Parse(tokens);
rootNode = TreeGenerator.TreeFromRPN(rpnForm); rootNode = TreeGenerator.TreeFromRPN(rpnForm);
evaluated_expression = rootNode.Evaluate();
} }
private ExpressionTree(TreeNode rootNode) private ExpressionTree(BaseNode rootNode)
{ {
this.rootNode = rootNode; this.rootNode = rootNode;
} }
@@ -35,7 +37,7 @@ namespace MathEngine.Parser.Parser
/// <returns>Returns an update of the current instance which the expression Evaluated</returns> /// <returns>Returns an update of the current instance which the expression Evaluated</returns>
public ExpressionTree Evaluate() public ExpressionTree Evaluate()
{ {
return new(Evaluator.Evaluator.Evaluate(rootNode)); return new ExpressionTree(rootNode.Evaluate());
} }
/// <summary> /// <summary>
@@ -45,9 +47,9 @@ namespace MathEngine.Parser.Parser
/// <returns>True if they are equal, False otherwise</returns> /// <returns>True if they are equal, False otherwise</returns>
public override bool Equals(object? other) public override bool Equals(object? other)
{ {
if (other is TreeNode) if (other is BaseNode)
{ {
ExpressionTree otherTree = new((TreeNode)other); ExpressionTree otherTree = new((BaseNode)other);
return this.Equals(otherTree); return this.Equals(otherTree);
} }
return false; return false;
@@ -73,7 +75,12 @@ namespace MathEngine.Parser.Parser
public override int GetHashCode() public override int GetHashCode()
{ {
return System.HashCode.Combine(this.rootNode); return System.HashCode.Combine(rootNode, evaluated_expression);
}
public override string ToString()
{
return evaluated_expression.ToString();
} }
} }
} }

View File

@@ -1,158 +0,0 @@
using MathEngine.Parser.Tokeniser;
namespace MathEngine.Parser.Parser
{
/// <summary>
/// Represents a node in a Tree structure
/// </summary>
internal class TreeNode
{
private TreeNode? Parent;
private List<TreeNode>? Children;
private readonly Token Value;
/// <summary>
/// Initialises a new instance of the MathEngine.Parser.Parser.Node class with a given Token
/// </summary>
/// <param name="value">The token for the nodes value</param>
public TreeNode(Token value)
{
Parent = null;
Children = null;
Value = value;
}
/// <summary>
/// Returns the value of the node
/// </summary>
public Token NodeValue
{
get { return Value; }
}
/// <summary>
/// Returns the parent node of the current node, or null if it does not exist
/// </summary>
public TreeNode? ParentNode
{
get
{
if (Parent == null)
{
return null;
}
else
{
return Parent;
}
}
}
/// <summary>
/// Returns all of the child nodes of the current node, or null if it odes not exist
/// </summary>
public List<TreeNode>? GetChildrenNodes
{
get
{
if (Children == null)
{
return null;
}
else
{
return Children;
}
}
}
/// <summary>
/// Returns the child node specified by the index, if there are no children nodes or if the index is out of bounds than null is returned
/// </summary>
/// <param name="index">The index of the child node to get</param>
/// <returns>The ChildNode at the specified index, null if it is null or the index is out-of-bounds</returns>
public TreeNode? GetChildNode(int index)
{
if (Children == null)
{
return null;
}
if (index < 0 || index >= Children.Count)
{
return null;
}
return Children[index];
}
/// <summary>
/// Adds a child node to the current root node, if there are no children nodes a list is created
/// </summary>
/// <param name="Node">The value for the child node that is to be added</param>
public void AddChildNode(TreeNode Node)
{
if (Children == null)
{
Children = new()
{
Node
};
}
else
{
Children.Add(Node);
}
}
/// <summary>
/// Returns a value that indicates if the given object is equal to the current instance of TreeNode
/// </summary>
/// <param name="other">The object to compare to the current instance of TreeNode</param>
/// <returns>True if the object innstace is equal to the current ExpressionTree instance, False otherwise</returns>
public override bool Equals(object? other)
{
if (other is ExpressionTree)
{
return other.Equals(this);
}
return false;
}
/// <summary>
/// Returns a value that indicates if the given TreeNode instance is equal to another
/// </summary>
/// <param name="other">The TreeNode to check for equality</param>
/// <returns>True if they are equal, False otherwise</returns>
public bool Equals(TreeNode other)
{
if (this.Value != other.Value) //If the root values are not equal we are done
{
return false;
}
// otherwise,
if (this.Children != null && other.Children != null) // If both children are NOT null then we reursively check the child nodes
{
//Covered all nullable cases, we now need to recursively check the child nodes
if (Children.Count != other.Children.Count)
{
return false;
}
for (int childNodeIndex = 0; childNodeIndex < Children.Count; childNodeIndex++)
{
if (!Children[childNodeIndex].Equals(other.Children[childNodeIndex]))
{
return false;
}
}
return true;
}
else if (this.Children == null && other.Children == null) //Special case is if both children lists are null then the TreeNodes are equal
{
return true;
}
else // otherwise at least one is null and the other is not so they can't be equal
{
return false; // if both children are not null than at least one is null so they can't be equal
}
}
}
}

View File

@@ -7,44 +7,15 @@ namespace MathEngine.Parser.Parser
/// </summary> /// </summary>
internal class TreeGenerator internal class TreeGenerator
{ {
/// <summary>
/// Creates a binary TreeNode, that is a node with a root value and two children
/// </summary>
/// <param name="CurrentToken">The token to be the root node of the TreeNode</param>
/// <param name="LeftToken">TreeNode that is the left branch of the current node</param>
/// <param name="RightToken">TreeNode that is the right branch of the current node</param>
/// <returns>A TreeNode with CurrentToken as the root value and LeftBranch and RightBranch as Children</returns>
private static TreeNode CreateBinaryNode(Token CurrentToken, TreeNode LeftBranch, TreeNode RightBranch)
{
TreeNode root = new(CurrentToken);
root.AddChildNode(LeftBranch);
root.AddChildNode(RightBranch);
return root;
}
/// <summary>
/// Creates a unary TreeNode, that is a node with a root value and two children
/// </summary>
/// <param name="CurrentToken">The token to be the root node of the TreeNode</param>
/// <param name="LeftToken">TreeNode that is the child of the current node</param>
/// <returns>A TreeNode with CurrentToken as the root value and ChildNode as the sole child node</returns>
private static TreeNode CreateUnaryNode(Token CurrentToken, TreeNode ChildNode)
{
TreeNode root = new(CurrentToken);
root.AddChildNode(ChildNode);
return root;
}
/// <summary> /// <summary>
/// Generates the full expression tree given an RPN expression stack /// Generates the full expression tree given an RPN expression stack
/// </summary> /// </summary>
/// <param name="rpnExpression">RPN expression stack to generate an expression tree from</param> /// <param name="rpnExpression">RPN expression stack to generate an expression tree from</param>
/// <returns>An expression Tree that represents the Mathematical expression given by rpnExpression</returns> /// <returns>An expression Tree that represents the Mathematical expression given by rpnExpression</returns>
public static TreeNode TreeFromRPN(Stack<Token> rpnExpression) public static BaseNode TreeFromRPN(Stack<Token> rpnExpression)
{ {
Stack<TreeNode> OutputStack = new(rpnExpression.Count); Stack<BaseNode> OutputStack = new(rpnExpression.Count);
TreeNode Node; BaseNode Node;
Token CurrentToken; Token CurrentToken;
while (rpnExpression.Count != 0) while (rpnExpression.Count != 0)
{ {
@@ -52,7 +23,7 @@ namespace MathEngine.Parser.Parser
switch (CurrentToken.Token_Type) switch (CurrentToken.Token_Type)
{ {
case Token.Type.Numeric: case Token.Type.Numeric:
Node = new TreeNode(CurrentToken); Node = NodeFactory.CreateNumericNode(CurrentToken);
OutputStack.Push(Node); OutputStack.Push(Node);
break; break;
// We need to preserve "Left handness" of the ExpressionTree // We need to preserve "Left handness" of the ExpressionTree
@@ -63,15 +34,15 @@ namespace MathEngine.Parser.Parser
case Token.Type.Multiplication: case Token.Type.Multiplication:
case Token.Type.Division: case Token.Type.Division:
case Token.Type.Exponentiation: case Token.Type.Exponentiation:
TreeNode Right = OutputStack.Pop(); BaseNode Right = OutputStack.Pop();
TreeNode Left = OutputStack.Pop(); BaseNode Left = OutputStack.Pop();
Node = CreateBinaryNode(CurrentToken, Left, Right); Node = NodeFactory.CreateBinaryNode(CurrentToken, Left, Right);
OutputStack.Push(Node); OutputStack.Push(Node);
break; break;
case Token.Type.UnaryPlus: case Token.Type.UnaryPlus:
case Token.Type.UnaryMinus: case Token.Type.UnaryMinus:
Node = CreateUnaryNode(CurrentToken, OutputStack.Pop()); //Node = NodeFactory.CreateUnaryNode(CurrentToken, OutputStack.Pop());
OutputStack.Push(Node); //OutputStack.Push(Node);
break; break;
} }
} }