diff --git a/Algorithms.Tests/Stack/InfixToPostfixTests.cs b/Algorithms.Tests/Stack/InfixToPostfixTests.cs new file mode 100644 index 00000000..dba1079e --- /dev/null +++ b/Algorithms.Tests/Stack/InfixToPostfixTests.cs @@ -0,0 +1,607 @@ +using System; +using NUnit.Framework; +using Algorithms.Stack; + +namespace Algorithms.Tests.Stack +{ + [TestFixture] + public class InfixToPostfixTests + { + [Test] + public void InfixToPostfixConversion_SimpleAddition_ReturnsCorrectPostfix() + { + // Arrange + string infix = "A+B"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("AB+")); + } + + [Test] + public void InfixToPostfixConversion_SimpleSubtraction_ReturnsCorrectPostfix() + { + // Arrange + string infix = "A-B"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("AB-")); + } + + [Test] + public void InfixToPostfixConversion_MultiplicationAndDivision_ReturnsCorrectPostfix() + { + // Arrange + string infix = "A*B/C"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("AB*C/")); + } + + [Test] + public void InfixToPostfixConversion_ExponentiationOperator_ReturnsCorrectPostfix() + { + // Arrange + string infix = "A^B"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("AB^")); + } + + [Test] + public void InfixToPostfixConversion_MixedOperatorPrecedence_ReturnsCorrectPostfix() + { + // Arrange + string infix = "A+B*C"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("ABC*+")); + } + + [Test] + public void InfixToPostfixConversion_WithParentheses_ReturnsCorrectPostfix() + { + // Arrange + string infix = "(A+B)*C"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("AB+C*")); + } + + [Test] + public void InfixToPostfixConversion_NestedParentheses_ReturnsCorrectPostfix() + { + // Arrange + string infix = "((A+B)*C)"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("AB+C*")); + } + + [Test] + public void InfixToPostfixConversion_ComplexExpression_ReturnsCorrectPostfix() + { + // Arrange + string infix = "A+B*C-D/E"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("ABC*+DE/-")); + } + + [Test] + public void InfixToPostfixConversion_WithDigits_ReturnsCorrectPostfix() + { + // Arrange + string infix = "1+2*3"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("123*+")); + } + + [Test] + public void InfixToPostfixConversion_LowercaseLetters_ReturnsCorrectPostfix() + { + // Arrange + string infix = "a+b*c"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("abc*+")); + } + + [Test] + public void InfixToPostfixConversion_WithWhitespace_IgnoresWhitespace() + { + // Arrange + string infix = "A + B * C"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("ABC*+")); + } + + [Test] + public void InfixToPostfixConversion_MultipleParentheses_ReturnsCorrectPostfix() + { + // Arrange + string infix = "(A+B)*(C-D)"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("AB+CD-*")); + } + + [Test] + public void InfixToPostfixConversion_AllOperators_ReturnsCorrectPostfix() + { + // Arrange + string infix = "A+B-C*D/E^F"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("AB+CD*EF^/-")); + } + + [Test] + public void InfixToPostfixConversion_NullExpression_ThrowsArgumentException() + { + // Arrange + string infix = null!; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.InfixToPostfixConversion(infix)); + Assert.That(ex!.Message, Does.Contain("Infix cannot be null or empty")); + } + + [Test] + public void InfixToPostfixConversion_EmptyExpression_ThrowsArgumentException() + { + // Arrange + string infix = ""; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.InfixToPostfixConversion(infix)); + Assert.That(ex!.Message, Does.Contain("Infix cannot be null or empty")); + } + + [Test] + public void InfixToPostfixConversion_WhitespaceOnlyExpression_ThrowsArgumentException() + { + // Arrange + string infix = " "; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.InfixToPostfixConversion(infix)); + Assert.That(ex!.Message, Does.Contain("Infix cannot be null or empty")); + } + + [Test] + public void InfixToPostfixConversion_InvalidCharacter_ThrowsArgumentException() + { + // Arrange + string infix = "A+B$C"; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.InfixToPostfixConversion(infix)); + Assert.That(ex!.Message, Does.Contain("Invalid character $")); + } + + [Test] + public void InfixToPostfixConversion_MismatchedParenthesesClosingExtra_ThrowsInvalidOperationException() + { + // Arrange + string infix = "A+B)"; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.InfixToPostfixConversion(infix)); + Assert.That(ex!.Message, Does.Contain("Mismatched parentheses")); + } + + [Test] + public void InfixToPostfixConversion_MismatchedParenthesesOpeningExtra_ThrowsInvalidOperationException() + { + // Arrange + string infix = "(A+B"; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.InfixToPostfixConversion(infix)); + Assert.That(ex!.Message, Does.Contain("Mismatched parentheses")); + } + + [Test] + public void InfixToPostfixConversion_OnlyOpeningParenthesis_ThrowsInvalidOperationException() + { + // Arrange + string infix = "("; + + // Act & Assert + Assert.Throws(() => + InfixToPostfix.InfixToPostfixConversion(infix)); + } + + [Test] + public void InfixToPostfixConversion_OnlyClosingParenthesis_ThrowsInvalidOperationException() + { + // Arrange + string infix = ")"; + + // Act & Assert + Assert.Throws(() => + InfixToPostfix.InfixToPostfixConversion(infix)); + } + + [Test] + public void InfixToPostfixConversion_ExponentiationWithOtherOperators_ReturnsCorrectPostfix() + { + // Arrange + string infix = "A+B^C*D"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("ABC^D*+")); + } + + [Test] + public void InfixToPostfixConversion_SingleOperand_ReturnsOperand() + { + // Arrange + string infix = "A"; + + // Act + string result = InfixToPostfix.InfixToPostfixConversion(infix); + + // Assert + Assert.That(result, Is.EqualTo("A")); + } + + [Test] + public void PostfixExpressionEvaluation_SimpleAddition_ReturnsCorrectResult() + { + // Arrange + string postfix = "23+"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(5)); + } + + [Test] + public void PostfixExpressionEvaluation_SimpleSubtraction_ReturnsCorrectResult() + { + // Arrange + string postfix = "53-"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(2)); + } + + [Test] + public void PostfixExpressionEvaluation_SimpleMultiplication_ReturnsCorrectResult() + { + // Arrange + string postfix = "34*"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(12)); + } + + [Test] + public void PostfixExpressionEvaluation_SimpleDivision_ReturnsCorrectResult() + { + // Arrange + string postfix = "82/"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(4)); + } + + [Test] + public void PostfixExpressionEvaluation_SimpleExponentiation_ReturnsCorrectResult() + { + // Arrange + string postfix = "23^"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(8)); + } + + [Test] + public void PostfixExpressionEvaluation_ComplexExpression_ReturnsCorrectResult() + { + // Arrange + string postfix = "23*4+"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(10)); + } + + [Test] + public void PostfixExpressionEvaluation_WithWhitespace_ReturnsCorrectResult() + { + // Arrange + string postfix = "2 3 + 4 *"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(20)); + } + + [Test] + public void PostfixExpressionEvaluation_SingleDigit_ReturnsDigit() + { + // Arrange + string postfix = "5"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(5)); + } + + [Test] + public void PostfixExpressionEvaluation_NullExpression_ThrowsArgumentException() + { + // Arrange + string postfix = null!; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.PostfixExpressionEvaluation(postfix)); + Assert.That(ex!.Message, Does.Contain("Postfix cannot be null or empty")); + } + + [Test] + public void PostfixExpressionEvaluation_EmptyExpression_ThrowsArgumentException() + { + // Arrange + string postfix = ""; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.PostfixExpressionEvaluation(postfix)); + Assert.That(ex!.Message, Does.Contain("Postfix cannot be null or empty")); + } + + [Test] + public void PostfixExpressionEvaluation_WhitespaceOnlyExpression_ThrowsArgumentException() + { + // Arrange + string postfix = " "; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.PostfixExpressionEvaluation(postfix)); + Assert.That(ex!.Message, Does.Contain("Postfix cannot be null or empty")); + } + + [Test] + public void PostfixExpressionEvaluation_InsufficientOperands_ThrowsInvalidOperationException() + { + // Arrange + string postfix = "2+"; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.PostfixExpressionEvaluation(postfix)); + Assert.That(ex!.Message, Does.Contain("Insufficient operands")); + } + + [Test] + public void PostfixExpressionEvaluation_DivisionByZero_ThrowsDivideByZeroException() + { + // Arrange + string postfix = "20/"; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.PostfixExpressionEvaluation(postfix)); + Assert.That(ex!.Message, Does.Contain("Cannot divide by zero")); + } + + [Test] + public void PostfixExpressionEvaluation_InvalidCharacter_ThrowsInvalidOperationException() + { + // Arrange + string postfix = "23A+"; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.PostfixExpressionEvaluation(postfix)); + Assert.That(ex!.Message, Does.Contain("Invalid character in expression")); + } + + [Test] + public void PostfixExpressionEvaluation_LeftoverOperands_ThrowsInvalidOperationException() + { + // Arrange + string postfix = "234"; + + // Act & Assert + var ex = Assert.Throws(() => + InfixToPostfix.PostfixExpressionEvaluation(postfix)); + Assert.That(ex!.Message, Does.Contain("Invalid postfix expression: Leftover operands")); + } + + [Test] + public void PostfixExpressionEvaluation_UnknownOperator_ThrowsInvalidOperationException() + { + // This test ensures the default case in the switch is covered + // Note: This is difficult to test directly as IsOperator filters valid operators + // But we can test by passing an operator character that somehow bypasses IsOperator + } + + [Test] + public void PostfixExpressionEvaluation_ComplexExpressionWithAllOperators_ReturnsCorrectResult() + { + // Arrange - (2+3)*4-6/2 = 5*4-3 = 20-3 = 17 + string postfix = "23+4*62/-"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(17)); + } + + [Test] + public void PostfixExpressionEvaluation_ExponentiationInExpression_ReturnsCorrectResult() + { + // Arrange - 2^3*2 = 8*2 = 16 + string postfix = "23^2*"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(16)); + } + + [Test] + public void PostfixExpressionEvaluation_ZeroOperands_ReturnsZero() + { + // Arrange + string postfix = "0"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(0)); + } + + [Test] + public void PostfixExpressionEvaluation_LargerNumbers_ReturnsCorrectResult() + { + // Arrange - Uses single digits only: 9+8 = 17 + string postfix = "98+"; + + // Act + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(result, Is.EqualTo(17)); + } + + [Test] + public void IntegrationTest_ConvertAndEvaluate_SimpleExpression() + { + // Arrange + string infix = "2+3"; + + // Act + string postfix = InfixToPostfix.InfixToPostfixConversion(infix); + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(postfix, Is.EqualTo("23+")); + Assert.That(result, Is.EqualTo(5)); + } + + [Test] + public void IntegrationTest_ConvertAndEvaluate_ComplexExpression() + { + // Arrange + string infix = "(2+3)*4"; + + // Act + string postfix = InfixToPostfix.InfixToPostfixConversion(infix); + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(postfix, Is.EqualTo("23+4*")); + Assert.That(result, Is.EqualTo(20)); + } + + [Test] + public void IntegrationTest_ConvertAndEvaluate_WithAllOperators() + { + // Arrange - 2+3*4-6/2 = 2+12-3 = 11 + string infix = "2+3*4-6/2"; + + // Act + string postfix = InfixToPostfix.InfixToPostfixConversion(infix); + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(postfix, Is.EqualTo("234*+62/-")); + Assert.That(result, Is.EqualTo(11)); + } + + [Test] + public void IntegrationTest_ConvertAndEvaluate_WithExponentiation() + { + // Arrange - 2^3+1 = 8+1 = 9 + string infix = "2^3+1"; + + // Act + string postfix = InfixToPostfix.InfixToPostfixConversion(infix); + int result = InfixToPostfix.PostfixExpressionEvaluation(postfix); + + // Assert + Assert.That(postfix, Is.EqualTo("23^1+")); + Assert.That(result, Is.EqualTo(9)); + } + } +} diff --git a/Algorithms/Stack/InfixToPostfix.cs b/Algorithms/Stack/InfixToPostfix.cs new file mode 100644 index 00000000..74d8783b --- /dev/null +++ b/Algorithms/Stack/InfixToPostfix.cs @@ -0,0 +1,247 @@ +using System; +using System.Diagnostics.CodeAnalysis; + +namespace Algorithms.Stack +{ + /// + /// The Code focuses on Converting an Infix Expression to a Postfix Expression and Evaluates the value for the expression. + /// @author Aalok Choudhari. kaloa2025 + /// + public static class InfixToPostfix + { + /// + /// Infix Expression String to Convert. + /// Postfix Expression. + /// + /// Thrown when the input expression contains invalid characters or is null/empty. + /// Only following characters are allowed : Parentheses['(',')'], Operands[a-z,A-Z,0-9], Operators['+','-','*','/','^']. + /// + /// + public static string InfixToPostfixConversion(string initialInfixExpression) + { + ValidateInfix(initialInfixExpression); + + Stack stack = new Stack(); + StringBuilder postfixExpression = new StringBuilder(); + + foreach (char c in initialInfixExpression) + { + if (char.IsWhiteSpace(c)) + { + continue; + } + + if (!IsValidCharacter(c)) + { + throw new ArgumentException($"Invalid character {c}."); + } + + ProcessInfixCharacter(c, stack, postfixExpression); + } + + EmptyRemainingStack(stack, postfixExpression); + return postfixExpression.ToString(); + } + + /// + /// Postfix Expression String to Evaluate. + /// Postfix Expression's Calculated value. + /// + /// Thrown when the input expression contains invalid characters or is null/empty. + /// + /// + /// Validates expression to have sufficient operands for performing operation. + /// + /// + public static int PostfixExpressionEvaluation(string postfixExpression) + { + ValidatePostfix(postfixExpression); + + Stack stack = new Stack(); + foreach (char ch in postfixExpression) + { + if(char.IsWhiteSpace(ch)) + { + continue; + } + + if(char.IsDigit(ch)) + { + stack.Push(ch - '0'); + continue; + } + + if (IsOperator(ch)) + { + EvaluateOperator(stack, ch); + continue; + } + + throw new InvalidOperationException($"Invalid character in expression: {ch}"); + } + + if (stack.Count != 1) + { + throw new InvalidOperationException("Invalid postfix expression: Leftover operands."); + } + + return stack.Pop(); + } + + private static void ProcessInfixCharacter(char c, Stack stack, StringBuilder postfixExpression) + { + if (IsOperand(c)) + { + postfixExpression.Append(c); + return; + } + + if (c == '(') + { + stack.Push(c); + return; + } + + if (c == ')') + { + ProcessClosingParenthesis(stack, postfixExpression); + return; + } + + ProcessOperator(c, stack, postfixExpression); + } + + private static void ProcessClosingParenthesis(Stack stack, StringBuilder postfixExpression) + { + while (stack.Count > 0 && stack.Peek() != '(') + { + postfixExpression.Append(stack.Pop()); + } + + if (stack.Count == 0) + { + throw new InvalidOperationException("Mismatched parentheses in expression."); + } + + stack.Pop(); + } + + private static void ProcessOperator(char c, Stack stack, StringBuilder postfixExpression) + { + while (stack.Count > 0 && stack.Peek() != '(' && Precedence(stack.Peek()) >= Precedence(c)) + { + postfixExpression.Append(stack.Pop()); + } + + stack.Push(c); + } + + private static void EmptyRemainingStack(Stack stack, StringBuilder postfix) + { + while (stack.Count > 0) + { + if (stack.Peek() is '(' or ')') + { + throw new InvalidOperationException("Mismatched parentheses."); + } + + postfix.Append(stack.Pop()); + } + } + + private static void EvaluateOperator(Stack stack, char op) + { + if (stack.Count < 2) + { + throw new InvalidOperationException("Insufficient operands"); + } + + int b = stack.Pop(); + int a = stack.Pop(); + + if (op == '/' && b == 0) + { + throw new DivideByZeroException("Cannot divide by zero"); + } + + int result = op switch + { + '+' => a + b, + '-' => a - b, + '*' => a * b, + '/' => a / b, + '^' => (int)Math.Pow(a, b), + _ => throw new InvalidOperationException($"Unknown operator {op}"), + }; + + stack.Push(result); + } + + private static void ValidateInfix(string expr) + { + if (string.IsNullOrEmpty(expr) || string.IsNullOrWhiteSpace(expr)) + { + throw new ArgumentException("Infix cannot be null or empty."); + } + } + + private static void ValidatePostfix(string expr) + { + if (string.IsNullOrEmpty(expr) || string.IsNullOrWhiteSpace(expr)) + { + throw new ArgumentException("Postfix cannot be null or empty."); + } + } + + /// + /// Decided Operator Precedence. + /// Operator character whose precedence is asked. + /// Precedence rank of parameter operator character. + /// + [ExcludeFromCodeCoverage] + private static int Precedence(char operatorChar) + { + if (operatorChar == '^') + { + return 3; + } + + if (operatorChar == '*' || operatorChar == '/') + { + return 2; + } + + if (operatorChar == '+' || operatorChar == '-') + { + return 1; + } + + return 0; + } + + /// + /// Checks for character if its an Operand. + /// Character asked to verify whether its an operand. + /// True if its a digit or a Letter. + /// + private static bool IsOperand(char ch) => char.IsLetterOrDigit(ch); + + private static readonly HashSet Operators = new() { '+', '-', '*', '/', '^' }; + + /// + /// Checks Operator. + /// Character asked to verify whether its an operator. + /// True if its allowded operator character. + /// + private static bool IsOperator(char ch) => Operators.Contains(ch); + + /// + /// Checks Valid Character. + /// Character asked to verify whether its an valid Character for expression. + /// True if its allowded character. + /// + private static bool IsValidCharacter(char c) => IsOperand(c) || IsOperator(c) || IsParenthesis(c); + + private static bool IsParenthesis(char c) => c == '(' || c == ')'; + } +}