diff --git a/README.md b/README.md index 87c500a..c8dd3d7 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,224 @@ -# data-structures -Data-Structures +# Data-Structures + +Where a variety of data-structures found in python are being explored, such as: +* A simple class LinkedList for building linked lists as well as a subclass Stack to implement a stack. + +## Authors + +ChaiChaitanya Narukulla and Mark Reynoso created this code base as a project for Python 401 at Code Fellows Seattle. + +### Requirements for testing and development + +In order to create a development and testing environment, you will need the following: + +``` +python2, python3, ipython, pytest, pytest-watch, pytest-cov, tox +``` + +``` +To install environment: + +pip install -e .[testing][development] +``` + +### Linkded List Class Usage + +``` +To create an instance of a the LinkedList() class contained in package, from python: + +new = LinkedList() *you may choose and optional parameter of a single value or an iterable of one of the following: a tuple, a list, or a string.* + +LinkedList() contains the following methods: +* _push(val) (O1)_ - will insert the value ‘val’ at the head of the list. +* _pop() (O1)_ - will pop the first value off the head of the list and return it. Raises an exception with an appropriate message if there are no values to return. +* _size() (01)_ - will return the length of the list. +* _search(val) (On)_ - will return the node containing ‘val’ in the list, if present, else None. +* _remove(node) (On)_ - will remove the given node from the list, wherever it might be (node must be an item in the list). If the node is not in the list, it will raise an exception with an appropriate message. +* _display() (O1)_ - will return a unicode string representing the list as if it were a Python tuple literal: “(12, ‘sam’, 37, ‘tango’)” + +To access any contained methods: +new.push(val) +new.pop() +new.size() +new.search(val) +new.remove(node) +new.display() + +Additionally, LinkedList() will interact with these built-in Python functions: + +* _len(new)_ - returns the size of the list. +* _print(new)_ - returns what the display() method returns. + +``` + +### Stack Class Usage + +``` +To create an instance of a Stack() class contained in package, from python: + +new = Stack() *you may choose and optional parameter of a single value or an iterable of one of the following: a tuple, a list, or a string.* + +Stack() contains the following methods: +* _push(val) (O1)_ - will insert the value ‘val’ at the head of the stack. +* _pop() (O1)_ - will pop the first value off the head of the stack and return it. Raises an exception with an appropriate message if there are no values to return. + +To access any contained methods: +new.push(val) +new.pop() + +Additionally, Stack() will interact with these built-in Python functions: + +* _len(new)_ - returns the size of the list. + +``` + +### Doubly Linked List Class Usage + +``` +To create an instance of a Dll() class contained in package, from python: + +new = Dll() *you may not call Dll() with any parameters.* + +Dll() contains the following methods: +* _push(val) (O1)_ - will insert the value ‘val’ at the head of the list. +* _append(val) (O1)_ - will insert the value ‘val’ at the tail of the list. +* _pop() (O1)_ - will pop the first value off the head of the stack and return it. Raises an exception with an appropriate message if there are no values to return. +* _shift() (O1)_ - will remove the last value off the tail of the list and return it. Raises an exception with an appropriate message if there are no values to return. +* _remove(node) (On)_ - will remove the given node from the list, wherever it might be (node must be an item in the list). If the node is not in the list, it will raise an exception with an appropriate message. + +To access any contained methods: +new.push(val) +new.append(val) +new.pop() +new.shift() +new.remove() + +Additionally, Dll() will interact with these built-in Python functions: + +* _len(new)_ - returns the size of the list. + +``` + +### Queue Class Usage + +``` +To create an instance of a Queue() class contained in package, from python: + +new = Queue() *you may not call Queue() with any parameters.* + +Queue() contains the following methods: +* _enqueue(val) (O1)_ - will insert the value ‘val’ at the end of the queue. +* _dequeue() (O1)_ - will remove the last value off the front of the queue and return it. Raises an exception with an appropriate message if there are no values to return. +* _peek() (O1)_ - shows the value of the node at the end of the queue. +* _size() (O1)_ - displays the number of node in the queue. + +To access any contained methods: +new.enqueue(val) +new.dequeue() +new.peek() +new.size() + +Additionally, Queue will interact with these built-in Python functions: + +* _len(new)_ - returns the size of the list. + +``` + +### Deque Class Usage + +``` +To create an instance of a Deque() class contained in package, from python: + +new = Deque() *you may not call Deque() with any parameters.* + +Deque() contains the following methods: +* _append(val) (O1)_ - will insert the value ‘val’ at the end of the deque. +* _appendleft() (O1)_ - will insert a value to the front of the deque. +*_pop() (01)_ - removes the first val from the end of the deque. +*_popleft() (01)_ - removes the first val from the front of the deque. +* _peek() (O1)_ - shows the value of the item at the end of the deque. +* _peekleft() (O1)_ - shows the value of the item at the font of the deque. +* _size() (O1)_ - displays the number items in the deque. + +To access any contained methods: +new.append(val) +new.appendleft(val) +new.pop() +new.popleft() +new.peek() +new.peekleft() +new.size() + +``` + +### Binaary Heap (Binheap) Class Usage + +``` +To create an instance of a Binheap() class contained in package, from python: + +new = Binheap() *you may call Binheap() with any uniqe number or iterable of unique numbers.* + +Binheap() contains the following methods: +* _push(val) (Olog(n))_ - will insert the value ‘val’ at the end of the heap. +*_pop() (0log(n))_ - removes the first val from the end of the heap. + +To access any contained methods: +new.push(val) +new.pop() + +``` + +### Priorityq (Priorityq) Class Usage + +``` +To create an instance of a Priorityq() class contained in package, from python: + +new = Priorityq() *you may call Priorityq() with any uniqe number or iterable of unique numbers.* + +Priorityq() contains the following methods: +*_insert(val,priority) (O1)_ - will insert the value ‘val’ at the end of the heap. +*_pop() (01)_ - removes the higest priority and returns the higest priority. +*_peek() (01)_ - returns the higest priority. + +To access any contained methods: +new.insert(val) +new.pop() +new.peek() + +``` + +### Graph Class (weighted) Usage + +``` +To create an instance of a Graph() class contained in package, from python: + +new = Graph() *you may not call Graph() with any parameters.* + +Graph() contains the following methods: +* _nodes() (O1)_ - will return a list of all nodes in the graph. +* _edges() (On2)_ - will return a dictionary of edges in graph with weights. +*_add_node(val) (01)_ - adds a new node to the graph. +*_add_edge(val1, val2, weight) (0n)_ - add a new edge to nodes in the graph. +* _del_node(val) (On2)_ - delete node from graph. +* _del_edge(val1, val2) (On)_ - delete edge between two nodes. +* _has_node(val) (O1)_ - returns true if node exists and false if not. +* _neighbors(val) (On2)_ - return a list of nodes with edges to input node. +* _adjacent(val1, val2) (On)_ - return true if edge exists between two inputs, + otherwise false. +* _depth_first_traversal(start_val) (Ologn)_ - will return full visited path of traversal completed. +* _breadth_first_traversal(start_val)(Ologn)_ - will return full visited path of traversal completed. + +To access any contained methods: +new.nodes() +new.edges() +new.add_node(val) +new.add_edge(val1, val2, weight) +new.del_node(val) +new.del_edge(val1, val2) +new.has_node(val) +new.neighbors(val) +new.adjacent(val1, val2) +new.depth_first_traversal(start_val) +new.breadth_first_traversal(start_val) + +``` \ No newline at end of file diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..8011f7b --- /dev/null +++ b/setup.py @@ -0,0 +1,16 @@ +from setuptools import setup + +setup( + name='data-structures', + description=('A package for building and ' + 'running the data-structures module'), + package_dir={'': 'src'}, + author='Mark and chai', + author_email='chaitanyanarukulla@gmail.com mreynoso@spu.edu', + py_modules=['binheap'], + install_requires=[], + extras_require={ + 'testing': ['pytest', 'pytest-cov', 'pytest-watch', 'tox'], + 'development': ['ipython'] + }, +) diff --git a/src/binheap.py b/src/binheap.py new file mode 100644 index 0000000..2bc2ae6 --- /dev/null +++ b/src/binheap.py @@ -0,0 +1,80 @@ +"""Binary heap class.""" + + +class Binheap(object): + """Iniitialize the class node.""" + + def __init__(self, iterable=None): + """Create a new min heap.""" + self.heaplist = [] + self._size = 0 + if isinstance(iterable, (list, tuple)): + for item in iterable: + self.push(item) + + def push(self, val): + """Push a value to the end of heap and sort up.""" + if type(val) == int or type(val) == float: + self.heaplist.append(val) + self._size += 1 + sort_up = True + idx = self._size - 1 + while sort_up: + if idx > 0: + if idx % 2 == 0: + parent = (idx - 2) // 2 + else: + parent = (idx - 1) // 2 + if val < self.heaplist[parent]: + self.heaplist[idx] = self.heaplist[parent] + self.heaplist[parent] = val + idx = parent + else: + sort_up = False + else: + sort_up = False + else: + raise ValueError('You must input numbers only.') + + def pop(self): + """Remove first value in heap and sort down.""" + if self._size == 0: + raise IndexError('There are no values to pop.') + elif self._size == 1: + self._size -= 1 + return self.heaplist.pop() + elif self._size > 1: + pop_val = self.heaplist[0] + self.heaplist[0] = self.heaplist.pop() + self._size -= 1 + idx = 0 + sort_down = True + while sort_down: + l_child = idx * 2 + 1 + r_child = idx * 2 + 2 + if r_child <= self._size - 1 and l_child <= self._size - 1: + if self.heaplist[l_child] > self.heaplist[r_child]: + if self.heaplist[r_child] < self.heaplist[idx]: + self.heaplist[r_child], self.heaplist[idx] =\ + self.heaplist[idx], self.heaplist[r_child] + idx = r_child + else: + sort_down = False + else: + if self.heaplist[l_child] < self.heaplist[r_child]: + if self.heaplist[l_child] < self.heaplist[idx]: + self.heaplist[idx], self.heaplist[l_child] =\ + self.heaplist[l_child], self.heaplist[idx] + idx = l_child + else: + sort_down = False + elif l_child == self._size - 1: + if self.heaplist[l_child] < self.heaplist[idx]: + self.heaplist[idx], self.heaplist[l_child] =\ + self.heaplist[l_child], self.heaplist[idx] + sort_down = False + else: + sort_down = False + else: + sort_down = False + return pop_val diff --git a/src/conftest.py b/src/conftest.py new file mode 100644 index 0000000..81d0af3 --- /dev/null +++ b/src/conftest.py @@ -0,0 +1,23 @@ +"""Fixutres for test_binheap.py.""" +import pytest + + +@pytest.fixture +def ebh(): + """Initialize an empty binary heap.""" + from binheap import Binheap + return Binheap() + + +@pytest.fixture +def pq(): + """Initialize a empty pq.""" + from priorityq import Priorityq + return Priorityq() + + +@pytest.fixture +def g(): + """Initialize a empty pq.""" + from graph import Graph + return Graph() diff --git a/src/graph.py b/src/graph.py new file mode 100644 index 0000000..8a5fdd4 --- /dev/null +++ b/src/graph.py @@ -0,0 +1,102 @@ +"""Implement a class for graph data structure with weighted edges.""" + + +class Graph(object): + """Graph class.""" + + def __init__(self): + """Initialize a graph.""" + self._graph = {} + + def nodes(self): + """Return a list of all nodes in graph.""" + return list(self._graph.keys()) + + def edges(self): + """Return a list of edges in graph.""" + edges = [] + for key in self._graph: + for child in self._graph[key]: + edges.append((key, child, self._graph[key][child])) + return edges + + def add_node(self, val): + """Add a node with value of val to graph.""" + self._graph.setdefault(val, {}) + + def add_edge(self, val1, val2, weight=0): + """Add a new edge to graph between val1 & val2 as well as add vals.""" + self.add_node(val1) + self.add_node(val2) + self._graph[val1][val2] = weight + + def del_node(self, val): + """Delete node w/val from graph, raises exception if not exist.""" + if val in self._graph: + del self._graph[val] + for key in self._graph: + if val in self._graph[key]: + del self._graph[key][val] + else: + raise ValueError('There is no node of that value in the graph.') + + def del_edge(self, val1, val2): + """Delete edge between val1 & val2 from graph.""" + try: + if val2 in self._graph[val1]: + del self._graph[val1][val2] + else: + raise ValueError('These edges do not exist.') + except KeyError: + raise ValueError('This edge does not exist.') + + def has_node(self, val): + """Return true or false if node has value.""" + return val in self._graph + + def neighbors(self, val): + """Return list of nodes connected node(val).""" + try: + return list(self._graph[val].keys()) + except KeyError: + raise ValueError('This node does not exist') + + def adjacent(self, val1, val2): + """Return true if edge between vals, else false, & error if no val.""" + try: + self._graph[val1] + self._graph[val2] + if val2 in self._graph[val1]: + return True + else: + return False + except KeyError: + raise ValueError('Node is not a node in this graph') + + def depth_first_traversal(self, start_val): + """Traverse the graph from first edge of each node until ultimate.""" + if start_val in self._graph: + depth_traversal = [] + path = [start_val] + while path: + val = path.pop() + if val not in depth_traversal: + depth_traversal.append(val) + path += list(self._graph[val].keys()) + return depth_traversal + else: + raise ValueError('Value is not in graph.') + + def breadth_first_traversal(self, start_val): + """Traverse the graph by node's edges before moving to next node.""" + if start_val in self._graph: + depth_traversal = [] + path = [start_val] + while path: + val = path.pop(0) + if val not in depth_traversal: + depth_traversal.append(val) + path = path + list(self._graph[val].keys()) + return depth_traversal + else: + raise ValueError('Value is not in graph.') diff --git a/src/test_binheap.py b/src/test_binheap.py new file mode 100644 index 0000000..7b4ac0f --- /dev/null +++ b/src/test_binheap.py @@ -0,0 +1,78 @@ +"""Test binheap.py.""" +import pytest + + +def test_initialize_empty_heap(ebh): + """Test if init binary is empty.""" + assert ebh + + +def test_push_one_value(ebh): + """Test size after push one value into heap.""" + ebh.push(9) + assert ebh._size == 1 + + +def test_push_mulitp_value(ebh): + """Test size after push multiple values into heap.""" + for i in range(20): + ebh.push(i) + assert ebh._size == 20 + + +def test_push_one_non_num(ebh): + """Test for erron if pushing non numerical value into heap.""" + with pytest.raises(ValueError): + ebh.push('NOOOOOOOO!!!!!!!!!') + + +def test_push_iterable(): + """Test if push successfully takes iterable.""" + from binheap import Binheap + heap = Binheap([1, 2, 3, 4, 5, 6, 7, 8]) + assert heap._size == 8 + + +def test_pop_on_empty_heap_raises_indexerror(ebh): + """Test for index error trying to pop from empty heap.""" + with pytest.raises(IndexError): + ebh.pop() + + +def test_pop_on_heap_with_one_item(ebh): + """Test pop returns item when only one item in heap.""" + ebh.push(1) + assert ebh.pop() == 1 + + +def test_pop_twice_on_heap_with_one_item(ebh): + """Test pop on fully popped heap.""" + ebh.push(1) + ebh.pop() + with pytest.raises(IndexError): + ebh.pop() + + +def test_pop_returns_sorted_values(): + """Test pop entire bin returns sorted values.""" + from binheap import Binheap + import random + rand_nums = list(set([random.randint(0, 1000) + for i in range(20)])) + heap = Binheap(rand_nums) + # import pdb; pdb.set_trace() + all_popped = [heap.pop() for i in range(heap._size)] + assert all_popped == sorted(rand_nums) + + +def test_pop_returns_sorted_values_limited(ebh): + """Test pop in controlled environment.""" + ebh.push(23), + ebh.push(2), + ebh.push(30), + ebh.push(50), + ebh.push(6), + ebh.push(17), + ebh.push(29) + all_popped = [ebh.pop() for i in range(7)] + assert all_popped == [2, 6, 17, 23, 29, 30, 50] diff --git a/src/test_graph.py b/src/test_graph.py new file mode 100644 index 0000000..692bc32 --- /dev/null +++ b/src/test_graph.py @@ -0,0 +1,425 @@ +"""Testing graph.py.""" +import pytest + + +def test_graph_iinitialize_empty_dict(g): + """Test initialize an empty pq.""" + assert g._graph == {} + + +def test_add_node(g): + """Test it adds note.""" + g.add_node(5) + assert g._graph[5] == {} + + +def test_nodes_returns_list_of_all_nodes(g): + """Test nodes return list of all nodes.""" + g.add_node(5) + g.add_node(7) + g.add_node(8) + assert 5 in g.nodes() + assert 7 in g.nodes() + assert 8 in g.nodes() + assert type(g.nodes()) == list + + +def test_edges_return_lis_of_tuples_of_all_edge(g): + """Test if edges returns dict of tuples as edge and weight as value.""" + g.add_node(5) + g.add_node(7) + g.edges() + assert g.edges() == [] + + +def test_add_edges_returns_dict_of_edges_as_tuple_keys(g): + """Test if edges are added if 2 val are given with default weight.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + assert g.edges() == [(5, 7, 0)] + + +def test_add_two_edges(g): + """Test if edges are added if 2 val are given.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_edge(5, 7) + g.add_edge(7, 9) + assert g.edges() == [(5, 7, 0), (7, 9, 0)] + + +def test_add_edges_will_add_mutipule_edges_to_a_node(g): + """Test if edges are added if 2 val are given.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_node(10) + g.add_edge(5, 7) + g.add_edge(5, 9) + g.add_edge(5, 10) + assert g.edges() == [(5, 7, 0), (5, 9, 0), (5, 10, 0)] + + +def test_add_edges_with_one_val_in_graph_add_second_val(g): + """Test if edges are added if 2 val are given.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_node(10) + g.add_edge(5, 7) + g.add_edge(5, 9) + g.add_edge(5, 15) + assert g.edges() == [(5, 7, 0), (5, 9, 0), (5, 15, 0)] + + +def test_add_edges_with_first_val_new_and_second_val_in_graph(g): + """Test if edges are added if 2 val are given.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_node(10) + g.add_edge(5, 7) + g.add_edge(5, 9) + g.add_edge(15, 5) + assert (15, 5, 0) in g.edges() + assert (5, 7, 0) in g.edges() + assert (5, 9, 0) in g.edges() + + +def test_add_edges_with_two_new_nodes(g): + """Test add edges when both nodes are new to graph.""" + g.add_edge(19, 100) + assert g.edges() == [(19, 100, 0)] + + +def test_del_node_deletes_the_node_given(g): + """Test del node delets the node.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.del_node(7) + assert 7 not in g.nodes() + + +def test_del_node_raises_value_error(g): + """Test del node raises value error if node has been deleted.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.del_node(5) + g.del_node(7) + g.del_node(9) + with pytest.raises(ValueError): + g.del_node(5) + + +def test_del_edge_deletes_all_edges(g): + """Test if del_edges deletes all edges.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + g.del_edge(5, 7) + assert g.edges() == [] + + +def test_del_edge_raises_value_error_if_too_many_edges_deleted(g): + """Test if del_edges raises value error.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + g.del_edge(5, 7) + with pytest.raises(ValueError): + g.del_edge(5, 7) + + +def test_del_edge_raises_value_error_if_values_not_in_dict(g): + """Test if del_edges raises value error for values not in dict.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + g.del_edge(5, 7) + with pytest.raises(ValueError): + g.del_edge(9, 0) + + +def test_has_node_is_false_on_empty_graph(g): + """Test false if there are no nodes in graph.""" + assert g.has_node(2) is False + + +def test_has_node_if_value_in_graph(g): + """Test false if node does not exist.""" + g.add_node(5) + assert g.has_node(5) is True + + +def test_neighbors_return_list_of_nodes_connected_to_input(g): + """Test neighbor retutns the list of nodes connected input value.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + g.add_node(8) + g.add_node(9) + g.add_edge(5, 9) + g.add_edge(5, 8) + assert sorted(g.neighbors(5)) == [7, 8, 9] + + +def test_neighbors_raises_valueerror_if_node_not_exist(g): + """Test neighbor raises value error if input not a node.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + g.add_node(8) + g.add_node(9) + g.add_edge(5, 9) + with pytest.raises(ValueError): + g.neighbors(20) + + +def test_adjacent_if_two_nodes_have_edge_is_true(g): + """Test adjacent has edges returns true when edge exists.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7) + assert g.adjacent(5, 7) is True + + +def test_adjacent_when_edge_when_no_edges(g): + """Test adjacent has no edges returns false.""" + g.add_node(5) + g.add_node(7) + assert g.adjacent(5, 7) is False + + +def test_adjacent_raises_valueerror_if_input_not_node(g): + """Test adjacent raises value error if node does not exist.""" + g.add_node(5) + g.add_node(7) + with pytest.raises(ValueError): + g.adjacent(9, 0) + + +def test_depth_first_one_node_many_edges(g): + """Test depth first traversal returns all paths from one node.""" + g.add_node(5) + g.add_node(6) + g.add_node(7) + g.add_node(8) + g.add_node(9) + g.add_edge(5, 6) + g.add_edge(5, 7) + g.add_edge(5, 8) + g.add_edge(5, 9) + assert 5 in g.depth_first_traversal(5) + assert 9 in g.depth_first_traversal(5) + assert 6 in g.depth_first_traversal(5) + assert 7 in g.depth_first_traversal(5) + assert 8 in g.depth_first_traversal(5) + assert type(g.depth_first_traversal(5)) == list + + +def test_depth_first_node_no_edge_return_only_self(g): + """Test depth first traversal returns start val if no edges.""" + g.add_node(8) + g.add_node(9) + g.add_node(10) + g.add_edge(10, 8) + assert g.depth_first_traversal(8) == [8] + + +def test_depth_first_one_edge_per_node(g): + """Test depth first traversal returns all paths from one node.""" + g.add_node(5) + g.add_node(6) + g.add_node(7) + g.add_node(8) + g.add_node(9) + g.add_edge(5, 6) + g.add_edge(6, 7) + g.add_edge(7, 8) + g.add_edge(8, 9) + assert g.depth_first_traversal(5) == [5, 6, 7, 8, 9] + + +def test_df_raises_value_error_node_not_exist(g): + """Test df traversal raises value error if val not in graph.""" + with pytest.raises(ValueError): + g.depth_first_traversal('blerg') + + +def test_depth_first_many_node_many_edges(g): + """Test depth first traversal returns all paths from one node.""" + g.add_node(5) + g.add_node(6) + g.add_node(7) + g.add_node(8) + g.add_node(9) + g.add_edge(8, 9) + g.add_edge(8, 5) + g.add_edge(5, 9) + g.add_edge(6, 9) + g.add_edge(7, 8) + g.add_edge(9, 5) + g.add_edge(9, 6) + g.add_edge(9, 8) + g.add_edge(6, 5) + g.add_edge(7, 5) + g.add_edge(6, 8) + dt = g.depth_first_traversal(8) + assert dt == [8, 5, 9, 6] or dt == [8, 9, 6, 5] or dt == [8, 9, 5, 6] + + +def test_breadth_first_one_node_many_edges(g): + """Test breadth first traversal returns all paths from one node.""" + g.add_node(5) + g.add_node(6) + g.add_node(7) + g.add_node(8) + g.add_node(9) + g.add_edge(5, 6) + g.add_edge(5, 7) + g.add_edge(5, 8) + g.add_edge(5, 9) + assert 5 in g.breadth_first_traversal(5) + assert 9 in g.breadth_first_traversal(5) + assert 6 in g.breadth_first_traversal(5) + assert 7 in g.breadth_first_traversal(5) + assert 8 in g.breadth_first_traversal(5) + assert type(g.breadth_first_traversal(5)) == list + + +def test_breadth_first_node_no_edge_return_only_self(g): + """Test breadth first traversal returns start val if no edges.""" + g.add_node(8) + g.add_node(9) + g.add_node(10) + g.add_edge(10, 8) + assert g.depth_first_traversal(8) == [8] + + +def test_depth_first_one_edge_per_node(g): + """Test depth first traversal returns all paths from one node.""" + g.add_node(5) + g.add_node(6) + g.add_node(7) + g.add_node(8) + g.add_node(9) + g.add_edge(5, 6) + g.add_edge(6, 7) + g.add_edge(7, 8) + g.add_edge(8, 9) + assert g.depth_first_traversal(5) == [5, 6, 7, 8, 9] + + +def test_bf_raises_value_error_node_not_exist(g): + """Test bf traversal raises value error if val not in graph.""" + with pytest.raises(ValueError): + g.depth_first_traversal('blerg') + + +def test_bf_first_many_node_many_edges(g): + """Test bf first traversal returns all paths from one node.""" + g.add_node(5) + g.add_node(6) + g.add_node(7) + g.add_node(8) + g.add_node(9) + g.add_edge(5, 9) + g.add_edge(6, 9) + g.add_edge(7, 8) + g.add_edge(8, 5) + g.add_edge(9, 5) + g.add_edge(9, 6) + g.add_edge(9, 8) + g.add_edge(6, 5) + g.add_edge(7, 5) + g.add_edge(8, 9) + g.add_edge(6, 8) + assert 5 in g.depth_first_traversal(8) + assert 9 in g.depth_first_traversal(8) + assert 6 in g.depth_first_traversal(8) + assert 5 in g.depth_first_traversal(8) + # assert g.depth_first_traversal(8) == [8, 9, 6, 5] + + +def test_breadth_first_works_on_cyclic_graph(g): + """Test breadth first traversal returns all paths from one node.""" + g.add_node(5) + g.add_node(6) + g.add_edge(5, 6) + g.add_edge(6, 5) + assert g.breadth_first_traversal(5) == [5, 6] + + +def test_depth_first_works_on_cyclic_graph(g): + """Test breadth first traversal returns all paths from one node.""" + g.add_node(5) + g.add_node(6) + g.add_edge(5, 6) + g.add_edge(6, 5) + assert g.breadth_first_traversal(5) == [5, 6] + + +def test_add_edges_w_weights_returns_dict_of_edges_as_tuple_keys(g): + """Test if edges are added if 2 val are given with default weight.""" + g.add_node(5) + g.add_node(7) + g.add_edge(5, 7, 6) + assert g.edges() == [(5, 7, 6)] + + +def test_add_two_edges_w_weights_(g): + """Test if edges are added if 2 val are given with weights returns dict.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_edge(5, 7, 100) + g.add_edge(7, 9, 1) + assert g.edges() == [(5, 7, 100), (7, 9, 1)] + + +def test_add_edges_w_weights_will_add_mutipule_edges_to_a_node(g): + """Test if edges are added if 2 val are given with weights.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_node(10) + g.add_edge(5, 7, 4) + g.add_edge(5, 9) + g.add_edge(5, 10, 12039) + assert g.edges() == [(5, 7, 4), (5, 9, 0), (5, 10, 12039)] + + +def test_add_edges_with_weights_with_one_val_in_graph_add_second_val(g): + """Test if edges are added if 2 val are given.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_node(10) + g.add_edge(5, 7, 0) + g.add_edge(5, 9, 3) + g.add_edge(5, 15, 6) + assert g.edges() == [(5, 7, 0), (5, 9, 3), (5, 15, 6)] + + +def test_add_edges_w_weights_with_first_val_new_and_second_val_in_graph(g): + """Test if edges are added if 2 val are given.""" + g.add_node(5) + g.add_node(7) + g.add_node(9) + g.add_node(10) + g.add_edge(5, 7, 4) + g.add_edge(5, 9, 7) + g.add_edge(15, 5, 9) + assert (15, 5, 9) in g.edges() + assert (5, 7, 4) in g.edges() + assert (5, 9, 7) in g.edges() + + +def test_add_edges_with_weights_with_two_new_nodes(g): + """Test add edges when both nodes are new to graph.""" + g.add_edge(19, 100, 1000) + assert g.edges() == [(19, 100, 1000)] diff --git a/tox.ini b/tox.ini new file mode 100644 index 0000000..06c5283 --- /dev/null +++ b/tox.ini @@ -0,0 +1,9 @@ +[tox] + +envlist = py27, py36 + +[testenv] +commands = py.test src --cov=src --cov-report term-missing +deps = + pytest + pytest-cov \ No newline at end of file