diff --git a/.gitignore b/.gitignore index 72364f9..3c25e9a 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,13 @@ var/ *.egg-info/ .installed.cfg *.egg +data_structures_venv/ +bin/ +lib64 +pyvenv.cfg +share/ +pip-selfcheck.json + # PyInstaller # Usually these files are written by a python script from a template diff --git a/README.md b/README.md index 46203ab..444adae 100644 --- a/README.md +++ b/README.md @@ -1 +1,9 @@ -# data_structures \ No newline at end of file +# data_structures + +##### This repository contains python implementations of various fundamental abstract data structures. These include: + + +##### 1. Linked List (src/linked_list.py) + +##### 2. Stack (src/stack.py) + diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..e4ca4e1 --- /dev/null +++ b/setup.py @@ -0,0 +1,15 @@ +"""The setup for Mailroom distribution.""" + +from setuptools import setup + +setup( + name='data_structures', + description='Implementation of Data Structures.', + version=0.1, + author='Jordan Schatzman, Julien Wilson', + author_email='j.schatzman@outlook.com, julienawilson@gmail.com', + license='MIT', + package_dir={'': 'src'}, + py_modules=['linked_list', 'stack'], + extras_require={'test': ['pytest', 'pytest-watch', 'pytest-cov', 'tox']}, +) diff --git a/src/dll.py b/src/dll.py new file mode 100644 index 0000000..b5532a3 --- /dev/null +++ b/src/dll.py @@ -0,0 +1,94 @@ +"""Implementation of Doubly Linked List data type.""" + + +class DoublyLinkedList(object): + """Class representation of doubly linked list.""" + + def __init__(self, iterable=None): + """Instantiate linked list.""" + self.head_node = None + self.tail_node = None + self.length = 0 + try: + for item in iterable: + self.push(item) + except TypeError: + if iterable: + print("Please only enter iterable values") + + def push(self, contents): + """Add node to this dll.""" + if self.length == 0: + self.head_node = Node(contents, None, None) + self.tail_node = self.head_node + else: + self.head_node = Node(contents, self.head_node, None) + self.length += 1 + + def append(self, contents): + """Add node to this dll.""" + if self.length == 0: + self.tail_node = Node(contents, None, None) + self.head_node = self.tail_node + else: + self.tail_node = Node(contents, None, self.tail_node) + self.length += 1 + + def pop(self): + """Remove and return the current head node.""" + if not self.head_node: + print("Linked list is already empty") + return + old_head_node = self.head_node + self.head_node = self.head_node.next_node + self.length -= 1 + return old_head_node + + def shift(self): + """Remove the end of the dll.""" + if not self.tail_node: + print("Linked list is already empty") + return + old_tail_node = self.tail_node + self.tail_node = self.tail_node.previous_node + self.length -= 1 + return old_tail_node + + def remove(self, contents): + """Remove the first node with input contents if it exists.""" + if not self.length: + raise NameError('This dll is empty.') + if self.head_node.contents == contents: + if self.length == 1: + self.head_node = None + self.tail_node = None + else: + self.head_node = self.head_node.next_node + self.head_node.previous_node = None + self.length -= 1 + return + test_node = self.head_node + while test_node.contents != contents and test_node.next_node: + last_node = test_node + test_node = test_node.next_node + if test_node.contents == contents and not test_node.next_node: + last_node.next_node = None + self.tail_node = last_node + self.length -= 1 + return + elif test_node.contents == contents: + last_node.next_node = test_node.next_node + test_node.next_node.previous_node = last_node + self.length -= 1 + return + raise NameError('{0} is not in the dll'.format(contents)) + + +class Node(object): + """Class representation of doubly linked list node.""" + + def __init__(self, contents, next_node, previous_node): + """Instantiate doubly linked list node.""" + self.contents = contents + self.next_node = next_node + self.previous_node = previous_node diff --git a/src/linked_list.py b/src/linked_list.py new file mode 100644 index 0000000..f1b91db --- /dev/null +++ b/src/linked_list.py @@ -0,0 +1,84 @@ +"""Implementation of Linked_List data type.""" + + +class LinkedList(object): + """Class representation of linked list.""" + + def __init__(self, iterable=None): + """Instantiate linked list.""" + self.head_node = None + self.length = 0 + try: + for item in iterable: + self.push(item) + except TypeError: + if iterable: + raise TypeError("Please only enter iterable values") + + def push(self, contents): + """Add node to this linked list.""" + self.head_node = Node(contents, self.head_node) + self.length += 1 + + def pop(self): + """Remove and return the current head node.""" + if not self.head_node: + raise IndexError("List is already empty") + old_head_node = self.head_node + self.head_node = self.head_node.next_node + self.length -= 1 + return old_head_node.contents + + def size(self): + """Return the current size of this linked list.""" + return self.length + + def search(self, search_value): + """Return the node with the searched contents if found.""" + if self.length: + if search_value == self.head_node.contents: + return self.head_node + current_node = self.head_node + while current_node.contents != search_value: + if current_node.next_node is None: + return None + current_node = current_node.next_node + return current_node + else: + return None + + def remove(self, remove_value): + """Remove a node from linked list.""" + last_node = None + current_node = self.head_node + while current_node: + if current_node.contents == remove_value: + if last_node: + last_node.next_node = current_node.next_node + else: + self.head_node = current_node.next_node + self.length -= 1 + return + last_node = current_node + current_node = current_node.next_node + + def display(self): + """Return the tuple of all values in linked list.""" + if self.length == 0: + return None + else: + new_list = [self.head_node.contents] + current_node = self.head_node + while current_node.next_node is not None: + current_node = current_node.next_node + new_list.append(current_node.contents) + return str(tuple(new_list)) + + +class Node(object): + """Class representation of linked list node.""" + + def __init__(self, contents, next_node): + """Instantiate linked list node.""" + self.contents = contents + self.next_node = next_node diff --git a/src/stack.py b/src/stack.py new file mode 100644 index 0000000..7c6e988 --- /dev/null +++ b/src/stack.py @@ -0,0 +1,24 @@ +"""Implementation of Stack data type.""" + +from linked_list import LinkedList + + +class Stack(object): + """Class representation of a stack.""" + + def __init__(self, iterable=None): + """Instantiate stack.""" + self.linked_list = LinkedList(iterable) + self.length = self.linked_list.length + self.head_node = self.linked_list.head_node + + def push(self, contents): + """Add node to this stack.""" + self.linked_list.push(contents) + self.head_node = self.linked_list.head_node + + def pop(self): + """Remove and return the current head node.""" + old_head_node_value = self.linked_list.pop() + self.head_node = self.linked_list.head_node + return old_head_node_value diff --git a/src/test_dll.py b/src/test_dll.py new file mode 100644 index 0000000..c3ba5d5 --- /dev/null +++ b/src/test_dll.py @@ -0,0 +1,108 @@ +"""Tests for dll.py.""" + +import pytest + + +@pytest.fixture +def sample_dll(): + """Create testing dlls.""" + from dll import DoublyLinkedList + one_dll = DoublyLinkedList([1]) + empty_dll = DoublyLinkedList() + new_dll = DoublyLinkedList([1, 2, 3, 4, 5]) + return one_dll, empty_dll, new_dll + + +def test_node_init(): + """Test node class init.""" + from dll import Node + new_node = Node(0, None, None) + assert new_node.contents == 0 + assert new_node.next_node is None + assert new_node.previous_node is None + + +def test_dll_init(): + """Test for dll init.""" + from dll import DoublyLinkedList + one_dll, empty_dll, new_dll = sample_dll() + assert empty_dll.length == 0 + assert empty_dll.head_node is None + assert empty_dll.tail_node is None + + +def test_dll_push(): + """Test for dll push.""" + from dll import DoublyLinkedList + one_dll, empty_dll, new_dll = sample_dll() + new_dll.push("new") + assert new_dll.length == 6 + assert new_dll.head_node.contents == "new" + empty_dll.push("second") + assert empty_dll.length == 1 + assert empty_dll.head_node.contents == "second" + + +def test_dll_append(): + """Test for dll push.""" + from dll import DoublyLinkedList + one_dll, empty_dll, new_dll = sample_dll() + empty_dll.append("new") + assert empty_dll.length == 1 + assert empty_dll.tail_node.contents == "new" + assert empty_dll.head_node.contents == "new" + new_dll.append("second") + assert new_dll.length == 6 + assert new_dll.tail_node.contents == "second" + one_dll.append('2') + assert one_dll.length == 2 + assert one_dll.tail_node.contents == '2' + assert one_dll.head_node.contents == 1 + + +def test_dll_pop(): + """Test for dll pop.""" + from dll import DoublyLinkedList + one_dll, empty_dll, new_dll = sample_dll() + assert new_dll.pop().contents == 5 + assert new_dll.length == 4 + assert one_dll.pop().contents == 1 + assert one_dll.length == 0 + + +def test_dll_shift(): + """Test for dll shift.""" + from dll import DoublyLinkedList + one_dll, empty_dll, new_dll = sample_dll() + assert new_dll.shift().contents == 1 + assert empty_dll.shift() is None + assert one_dll.shift().contents == 1 + assert one_dll.length == 0 + + +def test_dll_remove(): + """Test for dll remove.""" + from dll import DoublyLinkedList + one_dll, empty_dll, new_dll = sample_dll() + new_dll.remove(3) + assert new_dll.length == 4 + assert new_dll.head_node.next_node.next_node.contents == 2 + assert new_dll.head_node.next_node.next_node.previous_node.contents == 4 + try: + new_dll.remove(10) + except NameError: + assert True + new_dll.remove(5) + assert new_dll.head_node.contents == 4 + new_dll.remove(1) + assert new_dll.tail_node.contents == 2 + empty_dll = DoublyLinkedList() + try: + empty_dll.remove(100) + except NameError: + assert True + one_dll = DoublyLinkedList([1]) + one_dll.remove(1) + assert one_dll.head_node is None + assert one_dll.tail_node is None + assert one_dll.length == 0 diff --git a/src/test_linked_list.py b/src/test_linked_list.py new file mode 100644 index 0000000..085a8b8 --- /dev/null +++ b/src/test_linked_list.py @@ -0,0 +1,133 @@ +"""Tests for linked_list.py.""" + +import pytest + + +@pytest.fixture +def sample_linked_list(): + """Create testing linked list.""" + from linked_list import LinkedList + one_llist = LinkedList([1]) + empty_llist = LinkedList() + new_llist = LinkedList([1, 2, 3, 4, 5]) + return (empty_llist, one_llist, new_llist) + + +def test_node_init(): + """Test node class init.""" + from linked_list import Node + new_node = Node(0, None) + assert new_node.contents == 0 and new_node.next_node is None + + +def test_linkedlist_init_empty_size(sample_linked_list): + """Test for empty LinkedList init.""" + assert sample_linked_list[0].length == 0 + + +def test_linkedlist_init_empty_head(sample_linked_list): + """Test head in empty LinkedList init.""" + assert sample_linked_list[0].head_node is None + + +def test_linkedlist_init_one_size(sample_linked_list): + """Test for LinkedList init single item.""" + assert sample_linked_list[1].length == 1 + + +def test_linkedlist_init_one_head(sample_linked_list): + """Test head in LinkedList init single item.""" + assert sample_linked_list[1].head_node.contents == 1 + + +def test_linkedlist_init_list_size(sample_linked_list): + """Test for LinkedList init with list.""" + assert sample_linked_list[2].length == 5 + + +def test_linkedlist_init_list_head(sample_linked_list): + """Test head in LinkedList init with list.""" + assert sample_linked_list[2].head_node.contents == 5 + + +def test_linkedlist_push_size(sample_linked_list): + """Test for LinkedList size after push.""" + test_linkedlist = sample_linked_list[2] + test_linkedlist.push("new") + assert test_linkedlist.length == 6 + + +def test_linkedlist_push_val(sample_linked_list): + """Test for LinkedList head value after push.""" + test_linkedlist = sample_linked_list[2] + test_linkedlist.push("new") + assert test_linkedlist.head_node.contents == 'new' + + +def test_linkedlist_pop_empty(sample_linked_list): + """Test for Linked List pop on empty.""" + with pytest.raises(IndexError): + sample_linked_list[0].pop() + + +def test_linkedlist_pop_one(sample_linked_list): + """Test for Linked List pop on list with one item.""" + assert sample_linked_list[1].pop() == 1 + + +def test_linkedlist_pop_list(sample_linked_list): + """Test for Linked List pop on multi-item list.""" + assert sample_linked_list[2].pop() == 5 + + +def test_linkedlist_search_list(sample_linked_list): + """Test for LinkedList search list.""" + assert sample_linked_list[2].search(2).contents == 2 + + +def test_linkedlist_search_empty(sample_linked_list): + """Test for LinkedList search empty list.""" + assert sample_linked_list[1].search(2) is None + + +def test_linkedlist_search_list_false(sample_linked_list): + """Test for LinkedList search list when search value is not in list.""" + assert sample_linked_list[2].search(100) is None + + +def test_linkedlist_remove(sample_linked_list): + """Test that removed value is gone after remove().""" + sample_linked_list[2].remove(3) + assert sample_linked_list[2].search(3) is None + + +def test_linkedlist_remove_pointers(sample_linked_list): + """Test that previous node points to correct node after remove().""" + sample_linked_list[2].remove(3) + assert sample_linked_list[2].search(4).next_node.contents == 2 + + +def test_linkedlist_remove_head(sample_linked_list): + """Test LinkedList remove() the head on a list.""" + sample_linked_list[2].remove(5) + assert sample_linked_list[2].head_node.contents == 4 + + +def test_linkedlist_remove_empty(sample_linked_list): + """Test LinkedList remove() on an empty linked list.""" + sample_linked_list[1].remove(5) is None + + +def test_linkedlist_display(sample_linked_list): + """Test for LinkedList display.""" + assert sample_linked_list[2].display() == '(5, 4, 3, 2, 1)' + + +def test_linkedlist_display_one(sample_linked_list): + """Test for LinkedList display on single item list.""" + assert sample_linked_list[1].display() == '(1,)' + + +def test_linkedlist_display_empty(sample_linked_list): + """Test for LinkedList display on empty list.""" + assert sample_linked_list[0].display() is None diff --git a/src/test_stack.py b/src/test_stack.py new file mode 100644 index 0000000..c690dfa --- /dev/null +++ b/src/test_stack.py @@ -0,0 +1,78 @@ +"""Tests for stack.py.""" + +import pytest + + +@pytest.fixture +def sample_stack(): + """Create testing stacks.""" + from stack import Stack + empty_stack = Stack() + one_stack = Stack([1]) + new_stack = Stack([1, 2, 3, 4, 5]) + return (empty_stack, one_stack, new_stack) + + +def test_stack_empty_length(sample_stack): + """Test for empty stack length.""" + assert sample_stack[0].length == 0 + + +def test_stack_empty_head_node(sample_stack): + """Test for empty stack head node.""" + assert sample_stack[0].head_node is None + + +def test_stack_new_length(sample_stack): + """Test for new stack length.""" + assert sample_stack[2].length == 5 + + +def test_stack_new_contents(sample_stack): + """Test for new stack head node contents.""" + assert sample_stack[2].head_node.contents == 5 + + +def test_stack_new_push(sample_stack): + """Test for new stack head node contents.""" + sample_stack[2].push(34) + assert sample_stack[2].head_node.contents == 34 + + +def test_stack_one_push(sample_stack): + """Test for new stack head node contents.""" + sample_stack[1].push(34) + assert sample_stack[1].head_node.contents == 34 + + +def test_stack_empty_push(sample_stack): + """Test for empty stack head node contents.""" + sample_stack[0].push(34) + assert sample_stack[0].head_node.contents == 34 + + +def test_stack_new_pop(sample_stack): + """Test that pop the new stack returns 5.""" + assert sample_stack[2].pop() == 5 + + +def test_stack_new_pop_length(sample_stack): + """Test that pop the new stack decreases length.""" + assert sample_stack[2].pop() == 5 + + +def test_stack_one_pop(sample_stack): + """Test that pop of one stack returns 1.""" + assert sample_stack[1].pop() == 1 + + +def test_stack_one_pop_contents(sample_stack): + """Test that pop of one stack behaves as expeted.""" + sample_stack[1].pop() + assert sample_stack[1].head_node is None + + +def test_stack_empty_pop(sample_stack): + """Test that pop empty stack throws correct error.""" + with pytest.raises(IndexError): + sample_stack[0].pop() diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..936b849 --- /dev/null +++ b/tox.ini @@ -0,0 +1,7 @@ +[tox] +envlist = py27, py35 + +[testenv] +commands = py.test +deps = + pytest \ No newline at end of file