add collatz exercise, simplify names, update readme
This commit is contained in:
102
stack/stack.py
Normal file
102
stack/stack.py
Normal file
@@ -0,0 +1,102 @@
|
||||
import operator
|
||||
import readline
|
||||
from typing import Callable
|
||||
|
||||
|
||||
class FifthStack:
|
||||
def __init__(self):
|
||||
self.stack: list[int] = []
|
||||
self.commands: dict[str, Callable] = {
|
||||
"help": self.help,
|
||||
"push": self.push,
|
||||
"pop": self.pop,
|
||||
"swap": self.swap,
|
||||
"dup": self.dup,
|
||||
}
|
||||
self.binary_ops: dict[str, Callable[[int, int], int]] = {
|
||||
"+": operator.add,
|
||||
"-": operator.sub,
|
||||
"*": operator.mul,
|
||||
"/": operator.floordiv,
|
||||
}
|
||||
|
||||
def _require(self, number, message):
|
||||
if len(self.stack) < number:
|
||||
print(f"ERROR: {message}")
|
||||
return False
|
||||
return True
|
||||
|
||||
def _get_binary_op(self, command):
|
||||
return self.binary_ops.get(command)
|
||||
|
||||
def _binary_op(self, operator):
|
||||
if not self._require(2, "two numbers required"):
|
||||
return
|
||||
b, a = self.stack.pop(), self.stack.pop()
|
||||
try:
|
||||
self.stack.append(operator(a, b))
|
||||
except ZeroDivisionError as e:
|
||||
print(f"ERROR: division by zero")
|
||||
self.stack.extend([a, b])
|
||||
|
||||
def help(self):
|
||||
print("Available commands:", ", ".join(cmd.upper() for cmd in self.commands.keys()))
|
||||
print("Available operations:", ", ".join(self.binary_ops.keys()))
|
||||
|
||||
def push(self, value):
|
||||
try:
|
||||
self.stack.append(int(value))
|
||||
except ValueError:
|
||||
print("ERROR: integer required")
|
||||
|
||||
def pop(self):
|
||||
if not self.stack:
|
||||
print("ERROR: stack empty")
|
||||
return
|
||||
self.stack.pop()
|
||||
|
||||
def swap(self):
|
||||
if not self._require(2, "two numbers required"):
|
||||
return
|
||||
self.stack[-1], self.stack[-2] = self.stack[-2], self.stack[-1]
|
||||
|
||||
def dup(self):
|
||||
if self._require(1, "stack empty"):
|
||||
self.stack.append(self.stack[-1])
|
||||
|
||||
def execute(self, command: str):
|
||||
tokens = command.lower().strip().split()
|
||||
if not tokens:
|
||||
return
|
||||
|
||||
command, argument = tokens[0], tokens[1] if len(tokens) > 1 else None
|
||||
function = self.commands.get(command)
|
||||
|
||||
if function:
|
||||
if command == "push":
|
||||
function(argument)
|
||||
else:
|
||||
function()
|
||||
elif command in self.binary_ops:
|
||||
self._binary_op(self._get_binary_op(command))
|
||||
else:
|
||||
print("ERROR: unknown command")
|
||||
|
||||
|
||||
def main():
|
||||
fifth = FifthStack()
|
||||
fifth.help()
|
||||
|
||||
while True:
|
||||
print(f"stack is {fifth.stack}")
|
||||
try:
|
||||
if (command := input().strip().lower()) == "exit":
|
||||
break
|
||||
if command:
|
||||
fifth.execute(command)
|
||||
except (EOFError, KeyboardInterrupt):
|
||||
break
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
173
stack/test_stack.py
Normal file
173
stack/test_stack.py
Normal file
@@ -0,0 +1,173 @@
|
||||
import pytest
|
||||
from stack import FifthStack
|
||||
|
||||
|
||||
# Test the functions
|
||||
def test_push_and_pop():
|
||||
stack = FifthStack()
|
||||
stack.push(10)
|
||||
stack.push(20)
|
||||
assert stack.stack == [10, 20]
|
||||
stack.pop()
|
||||
assert stack.stack == [10]
|
||||
stack.pop()
|
||||
assert stack.stack == []
|
||||
|
||||
|
||||
def test_push_invalid_value(capsys):
|
||||
stack = FifthStack()
|
||||
stack.push("abc")
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: integer required" in captured.out
|
||||
assert stack.stack == []
|
||||
|
||||
|
||||
def test_pop_empty_stack(capsys):
|
||||
stack = FifthStack()
|
||||
stack.pop()
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: stack empty" in captured.out
|
||||
|
||||
|
||||
def test_swap():
|
||||
stack = FifthStack()
|
||||
stack.push(1)
|
||||
stack.push(2)
|
||||
stack.swap()
|
||||
assert stack.stack == [2, 1]
|
||||
|
||||
|
||||
def test_swap_insufficient_elements(capsys):
|
||||
stack = FifthStack()
|
||||
stack.push(1)
|
||||
stack.swap()
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: two numbers required" in captured.out
|
||||
assert stack.stack == [1]
|
||||
|
||||
|
||||
def test_dup():
|
||||
stack = FifthStack()
|
||||
stack.push(5)
|
||||
stack.dup()
|
||||
assert stack.stack == [5, 5]
|
||||
|
||||
|
||||
def test_dup_empty_stack(capsys):
|
||||
stack = FifthStack()
|
||||
stack.dup()
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: stack empty" in captured.out
|
||||
|
||||
|
||||
def test_addition():
|
||||
stack = FifthStack()
|
||||
stack.push(2)
|
||||
stack.push(3)
|
||||
stack.execute("+")
|
||||
assert stack.stack == [5]
|
||||
|
||||
|
||||
def test_subtraction():
|
||||
stack = FifthStack()
|
||||
stack.push(5)
|
||||
stack.push(2)
|
||||
stack.execute("-")
|
||||
assert stack.stack == [3]
|
||||
|
||||
|
||||
def test_multiplication():
|
||||
stack = FifthStack()
|
||||
stack.push(4)
|
||||
stack.push(3)
|
||||
stack.execute("*")
|
||||
assert stack.stack == [12]
|
||||
|
||||
|
||||
def test_division():
|
||||
stack = FifthStack()
|
||||
stack.push(8)
|
||||
stack.push(2)
|
||||
stack.execute("/")
|
||||
assert stack.stack == [4]
|
||||
|
||||
|
||||
def test_division_by_zero(capsys):
|
||||
stack = FifthStack()
|
||||
stack.push(8)
|
||||
stack.push(0)
|
||||
stack.execute("/")
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: division by zero" in captured.out
|
||||
assert stack.stack == [8, 0]
|
||||
|
||||
|
||||
def test_binary_op_insufficient_elements(capsys):
|
||||
stack = FifthStack()
|
||||
stack.push(1)
|
||||
stack.execute("+")
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: two numbers required" in captured.out
|
||||
assert stack.stack == [1]
|
||||
|
||||
|
||||
# Test executions
|
||||
def test_execute_unknown_command(capsys):
|
||||
stack = FifthStack()
|
||||
stack.execute("foobar")
|
||||
captured = capsys.readouterr()
|
||||
assert "ERROR: unknown command" in captured.out
|
||||
|
||||
|
||||
def test_execute_push_command():
|
||||
stack = FifthStack()
|
||||
stack.execute("push 42")
|
||||
assert stack.stack == [42]
|
||||
|
||||
|
||||
def test_execute_pop_command():
|
||||
stack = FifthStack()
|
||||
stack.push(99)
|
||||
stack.execute("pop")
|
||||
assert stack.stack == []
|
||||
|
||||
|
||||
def test_execute_swap_command():
|
||||
stack = FifthStack()
|
||||
stack.push(1)
|
||||
stack.push(2)
|
||||
stack.execute("swap")
|
||||
assert stack.stack == [2, 1]
|
||||
|
||||
|
||||
def test_execute_dup_command():
|
||||
stack = FifthStack()
|
||||
stack.push(7)
|
||||
stack.execute("dup")
|
||||
assert stack.stack == [7, 7]
|
||||
|
||||
|
||||
# Help and edge cases
|
||||
def test_help(capsys):
|
||||
stack = FifthStack()
|
||||
stack.help()
|
||||
captured = capsys.readouterr()
|
||||
assert "Available commands:" in captured.out
|
||||
assert "Available operations:" in captured.out
|
||||
|
||||
def test_execute_empty_command():
|
||||
stack = FifthStack()
|
||||
stack.execute("") # Should do nothing
|
||||
assert stack.stack == []
|
||||
|
||||
|
||||
def test_execute_whitespace_command():
|
||||
stack = FifthStack()
|
||||
stack.execute(" ") # Should do nothing
|
||||
assert stack.stack == []
|
||||
|
||||
|
||||
def test_case_insensitivity():
|
||||
stack = FifthStack()
|
||||
stack.execute("PUSH 42")
|
||||
assert stack.stack == [42]
|
||||
Reference in New Issue
Block a user