def test_iterator(self): stack = Stack(int) for i in range(20, 42, 2): stack.push(i) integers = list(range(20, 42, 2)) self.assertEqual(len(stack), len(integers), "Size method doesn't work") index = len(integers) - 1 for num in stack: self.assertEqual(num, integers[index], "Iterator doesn't work") index -= 1 self.assertEqual(len(stack), 0, "iterator doesn't adjust stack size")
def check_parenthesis(expression): # the stack for the parenthesis parenthesis = Stack() # pushing all the left parenthesis in a stack for token in tokenize(expression): if token == "(": parenthesis.push(token) # if there's a right parenthesis, check if the stack is empty, that is there is no left parenthesis left elif token == ")": if parenthesis.is_empty(): return False else: parenthesis.pop() # at the end, if the stack is empty, return true, all the parenthesis are valid; otherwise, return false return parenthesis.is_empty()
def test_push(self): stack = Stack() stack.push(23) stack.push(20) self.assertEqual(stack.peek(), 20, "Wrong stack push implementation") self.assertEqual(len(stack), 2, "Wrong stack push implementation") stack = Stack(bool) with self.assertRaises(TypeError): stack.push("word") for i in range(10): if i % 2 == 0: stack.push(True) else: stack.push(False) self.assertEqual(stack.size(), 10, "Wrong stack push implementation")
def test_pop(self): stack = Stack() with self.assertRaises(ValueError): stack.pop() stack.push([1, 2, 3]) self.assertEqual(stack.pop()[1], 2, "Stack pops wrong element") stack.push("pushed string") self.assertEqual(stack.pop(), "pushed string", "Stack pops wrong element") stack = Stack(bool) with self.assertRaises(ValueError): stack.pop() stack.push(True) stack.push(False) stack.pop() self.assertTrue(stack.pop(), "Stack pops wrong element")
def test_peek(self): stack = Stack() self.assertEqual( stack.peek(), None, "Top element of stack should be None at initialization") stack.push(2) stack.push("Tests") self.assertEqual(stack.peek(), "Tests", "Stack gives wrong peek") stack = Stack(float) self.assertEqual( stack.peek(), None, "Top element of stack should be None at initialization") stack.push(3.5) stack.push(1.27) stack.push(2.0) self.assertEqual(stack.peek(), 2.0, "Stack gives wrong peek")
def test_empty(self): stack = Stack() self.assertTrue(stack.is_empty(), "Stack should be empty") stack.push("word") stack.push("sentence") stack.pop() self.assertFalse(stack.is_empty(), "Stack should not be empty") stack = Stack(int) self.assertTrue(stack.is_empty(), "Stack should be empty") stack.push(0) self.assertFalse(stack.is_empty(), "Stack should not be empty")
def test_size(self): stack = Stack() self.assertEqual(stack.size(), 0, "Stack size should be 0 at initialization") for i in range(1, 41): stack.push(i) stack.push(i + 1) stack.pop() self.assertEqual(stack.size(), 40, "Incorrect stack size") stack = Stack(str) self.assertEqual(stack.size(), 0, "Stack size should be 0 at initialization") for l in ["a", "d", "b", "m"]: stack.push(l) stack.pop() self.assertEqual(stack.size(), 3, "Incorrect stack size")
def test_contains(self): stack = Stack() self.assertFalse(1 in stack, "Stack contains method doesn't work") stack.push("word") self.assertEqual("word" in stack, stack.contains("word"), "Stack contains method doesn't work") self.assertTrue(stack.contains("word"), "Stack contains method doesn't work") stack = Stack(elements_type=float) stack.push(1.55) stack.push(2.3) self.assertTrue(2.3 in stack, "Stack contains method doesn't work") self.assertTrue(stack.contains(1.55), "Stack contains method doesn't work") with self.assertRaises(TypeError): boolean = 4 in stack with self.assertRaises(TypeError): boolean = stack.contains("word")
def test_str(self): stack = Stack(int) self.assertEqual( str(stack), "[]", "String representation of stack doesn't work with empty stacks") stack.push(0) self.assertEqual( str(stack), "[0]", "String representation of stack doesn't work with singleton stacks" ) for i in [5, 20, 11, 34, 2]: stack.push(i) self.assertEqual( str(stack), "[0, 5, 20, 11, 34, 2]", "String representation of stack doesn't work with many elements") stack.peek() stack.pop() stack.pop() self.assertEqual( str(stack), "[0, 5, 20, 11]", "String representation of stack doesn't work after pop and peek operations" ) stack = Stack() stack.push(2.5) stack.push(0) self.assertEqual(str(stack), "[2.5, 0]")
def test_type(self): stack = Stack() self.assertEqual(stack.type(), None) with self.assertRaises(TypeError): stack = Stack(elements_type=3) stack = Stack(elements_type=list) self.assertEqual(stack.type(), list) with self.assertRaises(TypeError): stack.push("hey") stack = Stack(elements_type=str) stack.push("world") stack.push("hello") test_string = stack.pop() + " " + stack.pop() self.assertEqual(test_string, "hello world", "Stack with strings doesn't pop correctly") with self.assertRaises(TypeError): stack.push(123)
def evaluate_expression(expression): assert type(expression) is str, "Expression must be a string object" # check for errors in parenthesis if not check_parenthesis(expression): raise ArithmeticError("Invalid parenthesis") # using the global variable operators_priorities global operators_priorities # tokenizing the expression and initializing the two stack for the operands and the operators tokens = tokenize(expression) operands = Stack(elements_type=float) operators = Stack(elements_type=str) # iterating through the the tokens of the expression for token in tokens: # pushing the token into the right stack try: # if the token is a number push into operands stack operands.push(float(token)) # if the token is one of these '(', ')', '*', '/', '+' or '-' except ValueError: try: # if the operators' stack is empty, add the token there if operators.is_empty(): operators.push(token) # if the operator is a '(', add the token to the stack elif token == "(": operators.push(token) # if the operator is a ')', call the adjust_parenthesis method elif token == ")": adjust_parenthesis(operands, operators) # if the last operator's priority is less than or equal to the priority of the current operator, # add the current operator to the stack again elif operators_priorities[operators.peek()] <= operators_priorities[token]: operators.push(token) # if the last operator's priority is greater than the priority of the current operator, # call the method adjust_stacks and push the operator into the operators' stack elif operators_priorities[operators.peek()] > operators_priorities[token]: adjust_stacks(operands, operators, token) operators.push(token) # if the token is not supported, raise a Value error except KeyError: raise ValueError("Non supported attribute in the expression") # when we went through all the tokens, check if there's only one value left in the operands' stack and 0 operators # in the operators' stack, only then we know we are finished if operands.size() != 1 and operators.size() != 0: # if the finishing property is not true, then we repeat the operation from the adjust_stacks method, # while we know that we are finished while operands.size() != 1 and operators.size() != 0: # popping the last operator with the last two operands operator = operators.pop() b = operands.pop() a = operands.pop() # evaluating the value of the simple expression and pushing it back in the stack if operator == "*": operands.push(a * b) elif operator == "/": operands.push(a / b) elif operator == "+": operands.push(a + b) elif operator == "-": operands.push(a - b) # if the operator is not from the supported ones, raise an exception else: raise ValueError("The attribute you are specifying is not supported") # when we are finished, the operands' stack contains only the result from the expression, so we pop it return operands.pop() # if we know we are finished, we directly pop the result, since the operands' stack contains only the # result from the expression else: return operands.pop()