diff --git a/ParsingTree/ParsingTree.sln b/ParsingTree/ParsingTree.sln new file mode 100644 index 0000000..413afc6 --- /dev/null +++ b/ParsingTree/ParsingTree.sln @@ -0,0 +1,30 @@ +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.4.33403.182 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "ParsingTree", "ParsingTree\ParsingTree.csproj", "{E7D3B03D-F74E-4781-9DF2-0CB24880162A}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestsForParsingTree", "TestsForParsingTree\TestsForParsingTree.csproj", "{92559259-B410-4FB3-B4A7-0A8AE15F68B1}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {E7D3B03D-F74E-4781-9DF2-0CB24880162A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {E7D3B03D-F74E-4781-9DF2-0CB24880162A}.Debug|Any CPU.Build.0 = Debug|Any CPU + {E7D3B03D-F74E-4781-9DF2-0CB24880162A}.Release|Any CPU.ActiveCfg = Release|Any CPU + {E7D3B03D-F74E-4781-9DF2-0CB24880162A}.Release|Any CPU.Build.0 = Release|Any CPU + {92559259-B410-4FB3-B4A7-0A8AE15F68B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {92559259-B410-4FB3-B4A7-0A8AE15F68B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {92559259-B410-4FB3-B4A7-0A8AE15F68B1}.Release|Any CPU.ActiveCfg = Release|Any CPU + {92559259-B410-4FB3-B4A7-0A8AE15F68B1}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {4AF94C07-C87B-41B8-9B20-62E8562E599A} + EndGlobalSection +EndGlobal diff --git a/ParsingTree/ParsingTree/Divider.cs b/ParsingTree/ParsingTree/Divider.cs new file mode 100644 index 0000000..7537d5e --- /dev/null +++ b/ParsingTree/ParsingTree/Divider.cs @@ -0,0 +1,33 @@ +namespace ParsingTree; + +/// +/// A class for dividing numbers +/// +public class Divider : Operator +{ + private double delta = 0.0000001; + + /// + /// Inherits the ancestor's method + /// + /// Operator + public Divider(char symbol) : base(symbol) {} + + /// + /// Counts the division of two numbers + /// + /// Throws an exception when dividing by zero + public override double Calcuate(double firstValue, double secondValue) + { + if (secondValue - Math.Abs(secondValue) < delta) + { + throw new ArgumentException(); + } + return firstValue / secondValue; + } + + /// + /// Prints the division sign in the console + /// + public override void Print() => Console.Write(" / "); +} diff --git a/ParsingTree/ParsingTree/InvalidExpressionException.cs b/ParsingTree/ParsingTree/InvalidExpressionException.cs new file mode 100644 index 0000000..0a544e0 --- /dev/null +++ b/ParsingTree/ParsingTree/InvalidExpressionException.cs @@ -0,0 +1,6 @@ +namespace ParsingTree; + +/// +/// Throws an exception in case of an invalid expression +/// +public class InvalidExpressionException : Exception {} \ No newline at end of file diff --git a/ParsingTree/ParsingTree/Minus.cs b/ParsingTree/ParsingTree/Minus.cs new file mode 100644 index 0000000..82ab853 --- /dev/null +++ b/ParsingTree/ParsingTree/Minus.cs @@ -0,0 +1,25 @@ +namespace ParsingTree; + +/// +/// Subtracts from one number another +/// +public class Minus : Operator +{ + /// + /// Inherits the method of the ancestor operator + /// + public Minus(char symbol) : base(symbol) {} + + /// + /// Calculates the difference + /// + public override double Calcuate(double firstValue, double secondValue) + { + return secondValue - firstValue; + } + + /// + /// Prints the minus sign + /// + public override void Print() => Console.Write(" - "); +} \ No newline at end of file diff --git a/ParsingTree/ParsingTree/Multiplication.cs b/ParsingTree/ParsingTree/Multiplication.cs new file mode 100644 index 0000000..5a32f08 --- /dev/null +++ b/ParsingTree/ParsingTree/Multiplication.cs @@ -0,0 +1,25 @@ +namespace ParsingTree; + +/// +/// Counts the multiplication of two numbers +/// +public class Multiplication : Operator +{ + /// + /// Inherits the method of the ancestor operator + /// + public Multiplication(char symbol) : base(symbol) {} + + /// + /// Multiplies two numbers by each other + /// + public override double Calcuate(double firstValue, double secondValue) + { + return firstValue * secondValue; + } + + /// + /// Prints the multiply sign + /// + public override void Print() => Console.Write(" * "); +} diff --git a/ParsingTree/ParsingTree/Operand.cs b/ParsingTree/ParsingTree/Operand.cs new file mode 100644 index 0000000..7571f53 --- /dev/null +++ b/ParsingTree/ParsingTree/Operand.cs @@ -0,0 +1,30 @@ +namespace ParsingTree; + +/// +/// A class based on numbers +/// +public class Operand : PartOfExpression +{ + /// + /// Returns a stored number + /// + public double Calcuate(double firstValue, double secondValue) + { + return Number; + } + + /// + /// Prints a number + /// + public void Print() => Console.Write(Number + ' '); + + /// + /// saves a number + /// + public Operand(double number) + { + Number = number; + } + + public double Number { get; set; } +} diff --git a/ParsingTree/ParsingTree/Operator.cs b/ParsingTree/ParsingTree/Operator.cs new file mode 100644 index 0000000..ae33c5c --- /dev/null +++ b/ParsingTree/ParsingTree/Operator.cs @@ -0,0 +1,27 @@ +namespace ParsingTree; + +/// +/// A class that includes multiply divide add and subtract +/// +abstract public class Operator : PartOfExpression +{ + /// + /// Abstract type for an action account + /// + public abstract double Calcuate(double firstValue, double secondValue); + + /// + /// Abstract type for printing characters + /// + public abstract void Print(); + + /// + /// Stores a symbol in itself + /// + public Operator(char symbol) + { + Symbol = symbol; + } + + public char Symbol { get; set; } +} diff --git a/ParsingTree/ParsingTree/ParsingTree.csproj b/ParsingTree/ParsingTree/ParsingTree.csproj new file mode 100644 index 0000000..f02677b --- /dev/null +++ b/ParsingTree/ParsingTree/ParsingTree.csproj @@ -0,0 +1,10 @@ + + + + Exe + net7.0 + enable + enable + + + diff --git a/ParsingTree/ParsingTree/PartOfExpression.cs b/ParsingTree/ParsingTree/PartOfExpression.cs new file mode 100644 index 0000000..37d97e4 --- /dev/null +++ b/ParsingTree/ParsingTree/PartOfExpression.cs @@ -0,0 +1,17 @@ +namespace ParsingTree; + +/// +/// Interface for implementing different parts of expressions +/// +public interface PartOfExpression +{ + /// + /// Counts two numbers + /// + public double Calcuate(double firstValue, double secondValue); + + /// + /// Prints a character or number + /// + public void Print(); +} diff --git a/ParsingTree/ParsingTree/Plus.cs b/ParsingTree/ParsingTree/Plus.cs new file mode 100644 index 0000000..3a07efb --- /dev/null +++ b/ParsingTree/ParsingTree/Plus.cs @@ -0,0 +1,23 @@ +namespace ParsingTree; + +/// +/// A class that implements amount +/// +public class Plus : Operator +{ + /// + /// Inherits the method of the ancestor of the operator + /// + /// + public Plus(char symbol) : base(symbol) {} + + /// + /// Counts the Amount of two numbers + /// + public override double Calcuate(double firstValue, double secondValue) => secondValue + firstValue; + + /// + /// Prints a plus sign with spaces + /// + public override void Print() => Console.Write(" + "); +} diff --git a/ParsingTree/ParsingTree/Program.cs b/ParsingTree/ParsingTree/Program.cs new file mode 100644 index 0000000..e953f27 --- /dev/null +++ b/ParsingTree/ParsingTree/Program.cs @@ -0,0 +1,27 @@ +using ParsingTree; + +Tree tree = new Tree(); +Console.WriteLine("Input your string"); +string? stringExpression = Console.ReadLine(); +try +{ + if (stringExpression == null) + { + throw new ArgumentNullException(nameof(stringExpression)); + } + tree.TreeExpression(stringExpression); + Console.WriteLine(tree.Calcuate()); +} +catch (InvalidExpressionException) +{ + Console.WriteLine("Incorrect input"); +} +catch (ArgumentException) +{ + Console.WriteLine("Try to divide by zero"); +} +catch (NullReferenceException) +{ + Console.WriteLine("Try to Calcuate without tree"); +} +tree.PrintExpression(); \ No newline at end of file diff --git a/ParsingTree/ParsingTree/Tree.cs b/ParsingTree/ParsingTree/Tree.cs new file mode 100644 index 0000000..3d4e0af --- /dev/null +++ b/ParsingTree/ParsingTree/Tree.cs @@ -0,0 +1,253 @@ +namespace ParsingTree; + +/// +/// The container is a "tree" with a hierarchical structure +/// +public class Tree +{ + private Node Root { get; set; } + + private void AddToTree(Node root, ref bool isAdded, double value = 0, char symbol = '\0') + { + if (root != null) + { + if (root.IsEmpty && !isAdded) + { + if (symbol != '\0') + { + switch (symbol) + { + case '-': + root.Symbol = new Minus(symbol); + break; + case '+': + root.Symbol = new Plus(symbol); + break; + case '*': + root.Symbol = new Multiplication(symbol); + break; + case '/': + root.Symbol = new Divider(symbol); + break; + } + root.IsEmpty = false; + root.Left = new Node(); + root.Right = new Node(); + root.Left.Value = new Operand(0); + root.Right.Value = new Operand(0); + ++Root.Size; + } + else + { + root.Value.Number = value; + root.IsEmpty = false; + --Root.Size; + + } + isAdded = true; + } + AddToTree(root.Left, ref isAdded, value, symbol); + AddToTree(root.Right, ref isAdded, value, symbol); + } + } + + private void AddToTreeNumber(double value) + { + if (Root == null) + { + throw new InvalidExpressionException(); + } + bool isAdded = false; + AddToTree(Root, ref isAdded, value); + if (!isAdded) + { + throw new InvalidExpressionException(); + } + } + + private void AddToTreeSymbol(char symbol) + { + if (Root == null) + { + Root = new Node(); + switch (symbol) + { + case '-': + Root.Symbol = new Minus(symbol); + break; + case '+': + Root.Symbol = new Plus(symbol); + break; + case '*': + Root.Symbol = new Multiplication(symbol); + break; + case '/': + Root.Symbol = new Divider(symbol); + break; + } + Root.Symbol.Symbol = symbol; + Root.Value = new Operand(0); + Root.Left = new Node(); + Root.Right = new Node(); + Root.Right.Value = new Operand(0); + Root.Left.Value = new Operand(0); + Root.IsEmpty = false; + Root.Size += 2; + return; + } + bool isAdded = false; + AddToTree(Root, ref isAdded, 0, symbol); + if (!isAdded) + { + throw new InvalidExpressionException(); + } + } + + private bool isSymbolOperation(char symbol) + { + return symbol == '+' + || symbol == '-' + || symbol == '*' + || symbol == '/'; + } + + /// + /// Builds an expression tree by expression + /// + /// Throws an exception if the string is not correct + public void TreeExpression(string stringExpression) + { + for (int i = 0; i < stringExpression.Length; i++) + { + if(Char.IsNumber(stringExpression[i]) || (i < stringExpression.Length - 1 && stringExpression[i] == '-' && Char.IsNumber(stringExpression[i + 1]))) + { + double number = 0; + bool isMinus = false; + if (stringExpression[i] == '-') + { + ++i; + isMinus = true; + } + while (i < stringExpression.Length && Char.IsNumber(stringExpression[i])) + { + number += stringExpression[i] - 48; + number *= 10; + ++i; + } + number /= 10; + if (isMinus) + { + number *= -1; + } + AddToTreeNumber(number); + } + else if (isSymbolOperation(stringExpression[i])) + { + AddToTreeSymbol(stringExpression[i]); + } + else if (stringExpression[i] != ' ' && stringExpression[i] != ')' && stringExpression[i] != '(') + { + throw new InvalidExpressionException(); + } + } + if (Root.Size != 0 || Root == null) + { + throw new InvalidExpressionException(); + } + } + + private void Order(Node root) + { + if (root.Symbol != null) + { + Order(root.Left); + Order(root.Right); + if (root.Left.Symbol == null && root.Right.Symbol == null) + { + root.Value.Number = root.Symbol.Calcuate(root.Left.Value.Number, root.Right.Value.Number); + root.IsEmpty = true; + } + else + { + root.Value.Number = root.Symbol.Calcuate(root.Left.Value.Number, root.Right.Value.Number); + root.Left.IsEmpty = false; + root.Right.IsEmpty = false; + } + } + } + + /// + /// Counts an expression in the tree + /// + /// + /// Throws an exception if the tree is empty + public double Calcuate() + { + if (Root == null) + { + throw new NullReferenceException(); + } + Order(Root); + Root.IsEmpty = false; + return Root.Value.Number; + } + + private void PostOrderPrint(Node root, ref int isPreviousNumber, ref int sizeBackStaples) + { + if (root != null) + { + if (root.Symbol == null) + { + ++isPreviousNumber; + root.Value.Print(); + if (isPreviousNumber % 2 == 0 && isPreviousNumber != 0) + { + Console.Write(") "); + --sizeBackStaples; + isPreviousNumber = 0; + } + } + else + { + isPreviousNumber = 0; + Console.Write('('); + ++sizeBackStaples; + root.Symbol.Print(); + } + PostOrderPrint(root.Left, ref isPreviousNumber, ref sizeBackStaples); + PostOrderPrint(root.Right, ref isPreviousNumber, ref sizeBackStaples); + } + } + + /// + /// Outputs the expression stored in the tree to the screen + /// + public void PrintExpression() + { + int isPreviousNumber = 0; + int sizeBackStaples = 0; + PostOrderPrint(Root, ref isPreviousNumber, ref sizeBackStaples); + for (int i = 0; i < sizeBackStaples; ++i) + { + Console.Write(')'); + } + } + + private class Node + { + public Node() + { + IsEmpty = true; + } + + public Operand Value { get; set; } + + public Operator Symbol { get; set; } + public Node Left { get; set; } + public Node Right { get; set; } + + public bool IsEmpty { get; set; } + + public int Size { get; set; } + } +} diff --git a/ParsingTree/TestsForParsingTree/TestsForParsingTree.csproj b/ParsingTree/TestsForParsingTree/TestsForParsingTree.csproj new file mode 100644 index 0000000..82281fa --- /dev/null +++ b/ParsingTree/TestsForParsingTree/TestsForParsingTree.csproj @@ -0,0 +1,23 @@ + + + + net7.0 + enable + enable + + false + + + + + + + + + + + + + + + diff --git a/ParsingTree/TestsForParsingTree/TestsTree.cs b/ParsingTree/TestsForParsingTree/TestsTree.cs new file mode 100644 index 0000000..2e73f7b --- /dev/null +++ b/ParsingTree/TestsForParsingTree/TestsTree.cs @@ -0,0 +1,77 @@ +namespace TestsParsingTree; + +using ParsingTree; + +public class Tests +{ + Tree tree; + [SetUp] + public void Setup() + { + tree = new Tree(); + } + + [Test] + public void InTheUsualExampleTheTreeShouldCorrectlyCalculateTheValue() + { + tree.TreeExpression("+ 2 3"); + Assert.True(tree.Calcuate() == 5); + } + + [Test] + public void InTheNormalExampleTheTreeShouldCorrectlyCalculateTheValue() + { + tree.TreeExpression("(* (+ 2 3) (+ 5 7)"); + Assert.True(tree.Calcuate() == 60); + } + + [Test] + public void WhenAnEmptyStrinIsReceivedTheTreeShouldThrowAnException() + { + Assert.Throws(() => tree.TreeExpression("")); + } + + [Test] + public void WhenReceivingAnIncorrectStringWithTheAbsenceOfASignTheTreeShouldThrowAnException() + { + Assert.Throws(() => tree.TreeExpression(" 1 2")); + } + + [Test] + public void WhenReceivingAnIncorrectMoreDifficultStringWithTheAbsenceOfASignTheTreeShouldThrowAnException() + { + Assert.Throws(() => tree.TreeExpression("(* (4 5) 2)")); + } + + [Test] + public void WhenReceivingAnIncorrectStringWithTheTheAbsenceOfANumberTheTreeShouldThrowAnException() + { + Assert.Throws(() => tree.TreeExpression("+ 2")); + } + + [Test] + public void WhenReceivingAnIncorrectMoreDifficultStringWithTheTheAbsenceOfANumberTheTreeShouldThrowAnException() + { + Assert.Throws(() => tree.TreeExpression("(* (+ 2 3) )")); + } + + [Test] + public void WhenReceivingAnDifficultStringWithInvalidCharactersTheTreeShouldThrowAnException() + { + Assert.Throws(() => tree.TreeExpression("(* (+ 2 3) p 2)")); + } + + [Test] + public void WhenTryingToDivideByZeroTheTreeShouldThrowAnException() + { + tree.TreeExpression("/ 2 0"); + Assert.Throws(() => tree.Calcuate()); + } + + [Test] + public void TheTreeShouldWorkCorrectlyWithNegativeNumbers() + { + tree.TreeExpression("+ 2 -3"); + Assert.True(tree.Calcuate() == -1); + } +} \ No newline at end of file diff --git a/ParsingTree/TestsForParsingTree/Usings.cs b/ParsingTree/TestsForParsingTree/Usings.cs new file mode 100644 index 0000000..cefced4 --- /dev/null +++ b/ParsingTree/TestsForParsingTree/Usings.cs @@ -0,0 +1 @@ +global using NUnit.Framework; \ No newline at end of file