From b0ae7115c6413bea4abb6e422c0191fdcbd19a34 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 24 Jun 2015 12:40:04 -0700 Subject: [PATCH 001/330] Add files --- linked_list.py | 0 test_linked_list.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 linked_list.py create mode 100644 test_linked_list.py diff --git a/linked_list.py b/linked_list.py new file mode 100644 index 0000000..e69de29 diff --git a/test_linked_list.py b/test_linked_list.py new file mode 100644 index 0000000..e69de29 From 776bb9ac4ae39fa104f84ab9ee0bd5a72e73f595 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 24 Jun 2015 12:41:34 -0700 Subject: [PATCH 002/330] Add README --- README.md | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..387bb7f --- /dev/null +++ b/README.md @@ -0,0 +1,2 @@ +# data-structures +An assignment to implement a singly-linked list in Python From 778fb4fb8f4a5fb646b9e0cef745c823b9bb6f43 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 24 Jun 2015 14:05:30 -0700 Subject: [PATCH 003/330] Begin work on tests --- linked_list.py | 11 +++++++++ test_linked_list.py | 59 +++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) diff --git a/linked_list.py b/linked_list.py index e69de29..d5f5f08 100644 --- a/linked_list.py +++ b/linked_list.py @@ -0,0 +1,11 @@ +from __future__ import unicode_literals + + +class LinkedList(object): + """Class for a singly-linked list""" + def __init__(self, arg): + self.arg = arg + + def function(): + pass + diff --git a/test_linked_list.py b/test_linked_list.py index e69de29..5faae61 100644 --- a/test_linked_list.py +++ b/test_linked_list.py @@ -0,0 +1,59 @@ +import pytest +import linked_list as ll + + +LLIST = ll.LinkedList([1, 2, 3]) + + +class TestConstructor(): + def test_construct_none(): + with pytest.raises(TypeError): + ll.LinkedList(None) + + def test_construct_single_integer(): + with pytest.raises(TypeError): + ll.LinkedList(2) + + def test_construct_valid(): + _input = [1, 2, 3] + expected_output = "(1, 2, 3)" + assert ll.LinkedList(_input).display() == expected_output + + def test_construct_valid2(): + _input = ([1, 2, 3], 'string') + expected_output = "([1, 2, 3], 'string')" + assert ll.LinkedList([_input]) == expected_output + + def test_construct_valid3(): + _input = "string" + expected_output = "('s', 't', 'r', 'i', 'n', 'g')" + assert ll.LinkedList([_input]) == expected_output + + +class TestInsertVal(): + def test_single_value(): + LLIST.insert(4) + assert LLIST == "(4, 1, 2, 3)" + + +class TestPop(): + def test_pop(): + LLIST.pop() + assert LLIST == "(2, 3)" + + +def test_size(): + pass + + +def test_search_val(): + pass + + +def test_remove_node(): + new_list.remove(1) + assert ne + + +def test_display(): + pass From eee2e8abaa038f2e8789e7cc15602836ad2bd081 Mon Sep 17 00:00:00 2001 From: jay-tyler Date: Wed, 24 Jun 2015 15:35:27 -0700 Subject: [PATCH 004/330] added init Linkedlist --- linked_list.py | 93 +++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 5 deletions(-) diff --git a/linked_list.py b/linked_list.py index d5f5f08..d0c26d2 100644 --- a/linked_list.py +++ b/linked_list.py @@ -3,9 +3,92 @@ class LinkedList(object): """Class for a singly-linked list""" - def __init__(self, arg): - self.arg = arg + def __init__(self, iterable): + self.length = len(iterable) ##Will resize for pop, insert, remove - def function(): - pass - + for item in zip(range(self.length, 0, -1), iterable.reverse()): + + #Case: last item + Node(val) + + #Case middle items + #Node(val, stored_for_next) + + #Case first item; use None as ptr + #self.header = Node(val, stored_for_next) + # + + stored_for_next = # last + + + + def insert(val): + #Will insert val at head of LinkedList + self.header = Node(val, self.header) + self.length += 1 + return None + + def pop(): + #Pop the first val off the head and return it + to_return = self.header + self.header = to_return.next + to_return.next = None + self.length -= 1 + return to_return + + def size(self): + return self.length + + def search(val): + #Return the node containing val if present, else None + node, left = _find(val) + return node + + def remove(val): + #Remove given node from list, return None + node_to_remove = search(val) + + + def display(): + #Will print LinkedList as Tuple literal + end_flag = False + vals = [] + current_node = self.header + + while not end_flag: + vals.append(current_node) + if current_node.next: + current_node = current_node.next + else: + end_flag = True + break + + vals = tuple(vals) + return str(vals) + + def _find(val): + #Private to return a Node and left-neighboor by val + val_present = False + node_inspected = self.header + + while not val_present: + #Interrogate each Node + if node_inspected.val = val: + break + else: + #Keeping track of node to left; incrementing node + node_inspected = left_node + node_inspected = node_inspected.next + + return node_inspected, left_node + + + class Node(object): + + + def __init__(self, val, next=None): + self.val = val + self.next = next + + def __repr__(self): + #Code here \ No newline at end of file From cca25751fdce04d961d1d10ff647365e769b06ca Mon Sep 17 00:00:00 2001 From: jay-tyler Date: Wed, 24 Jun 2015 16:11:07 -0700 Subject: [PATCH 005/330] finished constructor for linked list --- linked_list.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/linked_list.py b/linked_list.py index d0c26d2..ab0a476 100644 --- a/linked_list.py +++ b/linked_list.py @@ -6,19 +6,21 @@ class LinkedList(object): def __init__(self, iterable): self.length = len(iterable) ##Will resize for pop, insert, remove - for item in zip(range(self.length, 0, -1), iterable.reverse()): + for index, item in zip(range(self.length, 1, -1), reversed(iterable)): - #Case: last item - Node(val) + if index == self.length: + #Case: right-most item in linked list; implicit None for next + Node(val) - #Case middle items - #Node(val, stored_for_next) + elif index == 0: + #Case of needing to store list header + self.header = Node(val, stored_for_next) - #Case first item; use None as ptr - #self.header = Node(val, stored_for_next) - # + else: + #All other linked nodes + Node(val, stored_for_next) - stored_for_next = # last + stored_for_next = item # last @@ -66,7 +68,7 @@ def display(): vals = tuple(vals) return str(vals) - def _find(val): + def _find(val): #Private to return a Node and left-neighboor by val val_present = False node_inspected = self.header From c84598f503ac50183adf43568efb7159286b4364 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 24 Jun 2015 17:09:58 -0700 Subject: [PATCH 006/330] Clean up tests --- test_linked_list.py | 73 +++++++++++++++++++++++++-------------------- 1 file changed, 40 insertions(+), 33 deletions(-) diff --git a/test_linked_list.py b/test_linked_list.py index 5faae61..fca81a2 100644 --- a/test_linked_list.py +++ b/test_linked_list.py @@ -2,58 +2,65 @@ import linked_list as ll -LLIST = ll.LinkedList([1, 2, 3]) +@pytest.fixture +def base_llist(): + return ll.linked_list([1, 2, 3]) class TestConstructor(): - def test_construct_none(): - with pytest.raises(TypeError): - ll.LinkedList(None) - - def test_construct_single_integer(): - with pytest.raises(TypeError): - ll.LinkedList(2) - - def test_construct_valid(): - _input = [1, 2, 3] + def test_construct_from_iterable_valid(base_llist): expected_output = "(1, 2, 3)" - assert ll.LinkedList(_input).display() == expected_output + assert base_llist.display() == expected_output - def test_construct_valid2(): - _input = ([1, 2, 3], 'string') + def test_construct_from_nested_iterable_valid(): + arg = ([1, 2, 3], 'string') expected_output = "([1, 2, 3], 'string')" - assert ll.LinkedList([_input]) == expected_output + assert ll.LinkedList([arg]) == expected_output - def test_construct_valid3(): - _input = "string" + def test_construct_from_string_valid(): + arg = "string" expected_output = "('s', 't', 'r', 'i', 'n', 'g')" - assert ll.LinkedList([_input]) == expected_output + assert ll.LinkedList([arg]) == expected_output + + def test_construct_from_none_fails(): + with pytest.raises(TypeError): + ll.LinkedList(None) + + def test_construct_from_single_integer_fails(): + with pytest.raises(TypeError): + ll.LinkedList(2) class TestInsertVal(): - def test_single_value(): - LLIST.insert(4) - assert LLIST == "(4, 1, 2, 3)" + def test_single_value(base_llist): + base_llist.insert(4) + assert base_llist == "(4, 1, 2, 3)" class TestPop(): - def test_pop(): - LLIST.pop() - assert LLIST == "(2, 3)" + def test_pop(base_llist): + base_llist.pop() + assert base_llist == "(2, 3)" -def test_size(): - pass +class TestSize(): + def test_size(base_llist): + assert base_llist.size() == 3 -def test_search_val(): - pass +class TestSearchVal(): + def test_search_val(base_llist): + searched_node = base_llist.seach(2) + assert isinstance(searched_node, ll.Node) + assert searched_node.val == 2 -def test_remove_node(): - new_list.remove(1) - assert ne +class TestRemoveNode(): + def test_remove_node(base_llist): + base_llist.remove(2) + assert base_llist == "(1, 3)" -def test_display(): - pass +class TestDisplay(): + def test_display(base_llist): + assert base_llist.display == "(1, 2, 3)" From 74d4193a8929fbb39319a16cda9281b76af1c460 Mon Sep 17 00:00:00 2001 From: jay-tyler Date: Wed, 24 Jun 2015 17:10:06 -0700 Subject: [PATCH 007/330] fixed broken stuff; still need repr for Node --- linked_list.py | 64 +++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/linked_list.py b/linked_list.py index ab0a476..174ff7a 100644 --- a/linked_list.py +++ b/linked_list.py @@ -4,34 +4,35 @@ class LinkedList(object): """Class for a singly-linked list""" def __init__(self, iterable): - self.length = len(iterable) ##Will resize for pop, insert, remove - - for index, item in zip(range(self.length, 1, -1), reversed(iterable)): + self.length = len(iterable) # Will resize for pop, insert, remove + self.header = None + stored_for_next = None # Seed value + import pdb; pdb.set_trace() #DEBUG + for index, val in zip(range(self.length, 0, -1), reversed(iterable)): + if index == self.length: - #Case: right-most item in linked list; implicit None for next - Node(val) + # Case: right-most item in linked list; implicit None for next + created = Node(val) - elif index == 0: - #Case of needing to store list header + elif index == 1: + # Case of needing to store list header self.header = Node(val, stored_for_next) else: - #All other linked nodes - Node(val, stored_for_next) - - stored_for_next = item # last - + # All other linked nodes + created = Node(val, stored_for_next) + stored_for_next = created # Store for next iteration - def insert(val): - #Will insert val at head of LinkedList + def insert(self, val): + # Will insert val at head of LinkedList self.header = Node(val, self.header) self.length += 1 return None - def pop(): - #Pop the first val off the head and return it + def pop(self): + # Pop the first val off the head and return it to_return = self.header self.header = to_return.next to_return.next = None @@ -41,17 +42,17 @@ def pop(): def size(self): return self.length - def search(val): + def search(self, val): #Return the node containing val if present, else None - node, left = _find(val) + node, left = self._find(val) return node - def remove(val): + def remove(self, val): #Remove given node from list, return None - node_to_remove = search(val) + node_to_remove = self.search(val) - def display(): + def display(self): #Will print LinkedList as Tuple literal end_flag = False vals = [] @@ -59,8 +60,10 @@ def display(): while not end_flag: vals.append(current_node) + if current_node.next: current_node = current_node.next + else: end_flag = True break @@ -68,14 +71,14 @@ def display(): vals = tuple(vals) return str(vals) - def _find(val): + def _find(self, val): #Private to return a Node and left-neighboor by val val_present = False node_inspected = self.header while not val_present: #Interrogate each Node - if node_inspected.val = val: + if node_inspected.val == val: break else: #Keeping track of node to left; incrementing node @@ -85,12 +88,13 @@ def _find(val): return node_inspected, left_node - class Node(object): - +class Node(object): - def __init__(self, val, next=None): - self.val = val - self.next = next + def __init__(self, val, next=None): + self.val = val + self.next = next - def __repr__(self): - #Code here \ No newline at end of file + def __repr__(self): + #Code here + return "Node({val}, {next})".format( + val=self.val, next=self.next) \ No newline at end of file From 480295fca4ff0c05fea4957f7971c54631f7557f Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 24 Jun 2015 17:21:27 -0700 Subject: [PATCH 008/330] Put tests into top level functinos --- test_linked_list.py | 79 ++++++++++++++++++++++----------------------- 1 file changed, 38 insertions(+), 41 deletions(-) diff --git a/test_linked_list.py b/test_linked_list.py index fca81a2..7df5983 100644 --- a/test_linked_list.py +++ b/test_linked_list.py @@ -7,60 +7,57 @@ def base_llist(): return ll.linked_list([1, 2, 3]) -class TestConstructor(): - def test_construct_from_iterable_valid(base_llist): - expected_output = "(1, 2, 3)" - assert base_llist.display() == expected_output +def test_construct_from_iterable_valid(base_llist): + expected_output = "(1, 2, 3)" + assert base_llist.display() == expected_output - def test_construct_from_nested_iterable_valid(): - arg = ([1, 2, 3], 'string') - expected_output = "([1, 2, 3], 'string')" - assert ll.LinkedList([arg]) == expected_output - def test_construct_from_string_valid(): - arg = "string" - expected_output = "('s', 't', 'r', 'i', 'n', 'g')" - assert ll.LinkedList([arg]) == expected_output +def test_construct_from_nested_iterable_valid(): + arg = ([1, 2, 3], 'string') + expected_output = "([1, 2, 3], 'string')" + assert ll.LinkedList([arg]) == expected_output - def test_construct_from_none_fails(): - with pytest.raises(TypeError): - ll.LinkedList(None) - def test_construct_from_single_integer_fails(): - with pytest.raises(TypeError): - ll.LinkedList(2) +def test_construct_from_string_valid(): + arg = "string" + expected_output = "('s', 't', 'r', 'i', 'n', 'g')" + assert ll.LinkedList([arg]) == expected_output -class TestInsertVal(): - def test_single_value(base_llist): - base_llist.insert(4) - assert base_llist == "(4, 1, 2, 3)" +def test_construct_from_none_fails(): + with pytest.raises(TypeError): + ll.LinkedList(None) -class TestPop(): - def test_pop(base_llist): - base_llist.pop() - assert base_llist == "(2, 3)" +def test_construct_from_single_integer_fails(): + with pytest.raises(TypeError): + ll.LinkedList(2) -class TestSize(): - def test_size(base_llist): - assert base_llist.size() == 3 +def test_single_value(base_llist): + base_llist.insert(4) + assert base_llist == "(4, 1, 2, 3)" -class TestSearchVal(): - def test_search_val(base_llist): - searched_node = base_llist.seach(2) - assert isinstance(searched_node, ll.Node) - assert searched_node.val == 2 +def test_pop(self, base_llist): + base_llist.pop() + assert base_llist == "(2, 3)" -class TestRemoveNode(): - def test_remove_node(base_llist): - base_llist.remove(2) - assert base_llist == "(1, 3)" +def test_size(base_llist): + assert base_llist.size() == 3 -class TestDisplay(): - def test_display(base_llist): - assert base_llist.display == "(1, 2, 3)" +def test_search_val(base_llist): + searched_node = base_llist.seach(2) + assert isinstance(searched_node, ll.Node) + assert searched_node.val == 2 + + +def test_remove_node(base_llist): + base_llist.remove(2) + assert base_llist == "(1, 3)" + + +def test_display(base_llist): + assert base_llist.display == "(1, 2, 3)" From 4834aeae2886c04a4f8619d6e8a1951c4a25bdf5 Mon Sep 17 00:00:00 2001 From: jay-tyler Date: Wed, 24 Jun 2015 17:23:13 -0700 Subject: [PATCH 009/330] fixed Node repr --- linked_list.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/linked_list.py b/linked_list.py index 174ff7a..dad2438 100644 --- a/linked_list.py +++ b/linked_list.py @@ -8,9 +8,8 @@ def __init__(self, iterable): self.header = None stored_for_next = None # Seed value - import pdb; pdb.set_trace() #DEBUG for index, val in zip(range(self.length, 0, -1), reversed(iterable)): - + if index == self.length: # Case: right-most item in linked list; implicit None for next created = Node(val) @@ -59,7 +58,7 @@ def display(self): current_node = self.header while not end_flag: - vals.append(current_node) + vals.append(current_node.__repr__()) if current_node.next: current_node = current_node.next @@ -95,6 +94,5 @@ def __init__(self, val, next=None): self.next = next def __repr__(self): - #Code here - return "Node({val}, {next})".format( - val=self.val, next=self.next) \ No newline at end of file + # Just display value + return "{val}".format(val=self.val) \ No newline at end of file From b89e6c61ceac1b70f2f36a94b5e763b327a9caf9 Mon Sep 17 00:00:00 2001 From: jay-tyler Date: Wed, 24 Jun 2015 18:11:01 -0700 Subject: [PATCH 010/330] ALL TESTS PASSgit add .git add . WOOHOO! --- linked_list.py | 53 +++++++++++++++++++++++++++++---------------- test_linked_list.py | 23 ++++++++++---------- 2 files changed, 46 insertions(+), 30 deletions(-) diff --git a/linked_list.py b/linked_list.py index dad2438..fa9c571 100644 --- a/linked_list.py +++ b/linked_list.py @@ -24,6 +24,25 @@ def __init__(self, iterable): stored_for_next = created # Store for next iteration + def __repr__(self): + #Will print LinkedList as Tuple literal + end_flag = False + vals = [] + current_node = self.header + + while not end_flag: + vals.append(current_node.val) + + if current_node.next: + current_node = current_node.next + + else: + end_flag = True + break + + vals = tuple(vals) + return str(vals) + def insert(self, val): # Will insert val at head of LinkedList self.header = Node(val, self.header) @@ -48,40 +67,36 @@ def search(self, val): def remove(self, val): #Remove given node from list, return None - node_to_remove = self.search(val) + node_to_remove, left_neighbor = self._find(val) + if self.header == node_to_remove: + header_to_remove = self.pop() + self.header = header_to_remove.next + + else: + left_neighbor.next = node_to_remove.next + node_to_remove.next = None + + return None def display(self): #Will print LinkedList as Tuple literal - end_flag = False - vals = [] - current_node = self.header - - while not end_flag: - vals.append(current_node.__repr__()) - - if current_node.next: - current_node = current_node.next - - else: - end_flag = True - break - - vals = tuple(vals) - return str(vals) + return self.__repr__() def _find(self, val): - #Private to return a Node and left-neighboor by val + #Private to return a Node and left-neighboor by val; val_present = False node_inspected = self.header + left_node = None while not val_present: #Interrogate each Node if node_inspected.val == val: + val_present = True break else: #Keeping track of node to left; incrementing node - node_inspected = left_node + left_node = node_inspected node_inspected = node_inspected.next return node_inspected, left_node diff --git a/test_linked_list.py b/test_linked_list.py index 7df5983..d912edc 100644 --- a/test_linked_list.py +++ b/test_linked_list.py @@ -1,10 +1,11 @@ +from __future__ import unicode_literals import pytest import linked_list as ll @pytest.fixture def base_llist(): - return ll.linked_list([1, 2, 3]) + return ll.LinkedList([1, 2, 3]) def test_construct_from_iterable_valid(base_llist): @@ -14,14 +15,14 @@ def test_construct_from_iterable_valid(base_llist): def test_construct_from_nested_iterable_valid(): arg = ([1, 2, 3], 'string') - expected_output = "([1, 2, 3], 'string')" - assert ll.LinkedList([arg]) == expected_output + expected_output = "([1, 2, 3], u'string')" + assert ll.LinkedList(arg).__repr__() == expected_output def test_construct_from_string_valid(): arg = "string" - expected_output = "('s', 't', 'r', 'i', 'n', 'g')" - assert ll.LinkedList([arg]) == expected_output + expected_output = "(u's', u't', u'r', u'i', u'n', u'g')" + assert ll.LinkedList(arg).__repr__() == expected_output def test_construct_from_none_fails(): @@ -36,12 +37,12 @@ def test_construct_from_single_integer_fails(): def test_single_value(base_llist): base_llist.insert(4) - assert base_llist == "(4, 1, 2, 3)" + assert base_llist.__repr__() == "(4, 1, 2, 3)" -def test_pop(self, base_llist): +def test_pop(base_llist): base_llist.pop() - assert base_llist == "(2, 3)" + assert base_llist.__repr__() == "(2, 3)" def test_size(base_llist): @@ -49,15 +50,15 @@ def test_size(base_llist): def test_search_val(base_llist): - searched_node = base_llist.seach(2) + searched_node = base_llist.search(2) assert isinstance(searched_node, ll.Node) assert searched_node.val == 2 def test_remove_node(base_llist): base_llist.remove(2) - assert base_llist == "(1, 3)" + assert base_llist.__repr__() == "(1, 3)" def test_display(base_llist): - assert base_llist.display == "(1, 2, 3)" + assert base_llist.display() == "(1, 2, 3)" From f3cce8646dc2824cb32088764b191e22b9c85c43 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 24 Jun 2015 18:19:22 -0700 Subject: [PATCH 011/330] Improve test function names --- test_linked_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_linked_list.py b/test_linked_list.py index d912edc..bc78853 100644 --- a/test_linked_list.py +++ b/test_linked_list.py @@ -35,7 +35,7 @@ def test_construct_from_single_integer_fails(): ll.LinkedList(2) -def test_single_value(base_llist): +def test_insert_single_value(base_llist): base_llist.insert(4) assert base_llist.__repr__() == "(4, 1, 2, 3)" From 7d589279055c1d8447e34f92135754a1476cbfe3 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 13:41:17 -0700 Subject: [PATCH 012/330] Clean up some style issues --- linked_list.py | 37 +++++++++++++++++-------------------- 1 file changed, 17 insertions(+), 20 deletions(-) diff --git a/linked_list.py b/linked_list.py index fa9c571..f06b6e0 100644 --- a/linked_list.py +++ b/linked_list.py @@ -2,22 +2,19 @@ class LinkedList(object): - """Class for a singly-linked list""" + """Class for a singly-linked list.""" def __init__(self, iterable): self.length = len(iterable) # Will resize for pop, insert, remove self.header = None - stored_for_next = None # Seed value + stored_for_next = None # Seed value for index, val in zip(range(self.length, 0, -1), reversed(iterable)): - if index == self.length: # Case: right-most item in linked list; implicit None for next created = Node(val) - elif index == 1: # Case of needing to store list header self.header = Node(val, stored_for_next) - else: # All other linked nodes created = Node(val, stored_for_next) @@ -25,7 +22,7 @@ def __init__(self, iterable): stored_for_next = created # Store for next iteration def __repr__(self): - #Will print LinkedList as Tuple literal + """Print LinkedList as Tuple literal.""" end_flag = False vals = [] current_node = self.header @@ -44,13 +41,13 @@ def __repr__(self): return str(vals) def insert(self, val): - # Will insert val at head of LinkedList + """Insert val at head of LinkedList.""" self.header = Node(val, self.header) self.length += 1 return None def pop(self): - # Pop the first val off the head and return it + """Pop the first val off the head and return it.""" to_return = self.header self.header = to_return.next to_return.next = None @@ -58,44 +55,44 @@ def pop(self): return to_return def size(self): + """Return current length of LinkedList.""" return self.length def search(self, val): - #Return the node containing val if present, else None + """Return the node containing val if present, else None""" node, left = self._find(val) return node - def remove(self, val): - #Remove given node from list, return None + def remove(self, val): # Check Spec: Pass node vs val + """Remove given node from list, return None""" node_to_remove, left_neighbor = self._find(val) if self.header == node_to_remove: - header_to_remove = self.pop() - self.header = header_to_remove.next - + self.pop() + else: left_neighbor.next = node_to_remove.next node_to_remove.next = None return None - def display(self): - #Will print LinkedList as Tuple literal + def display(self): + """Print LinkedList as Tuple literal""" return self.__repr__() def _find(self, val): - #Private to return a Node and left-neighboor by val; + """Return a Node and left-neighboor by val.""" val_present = False node_inspected = self.header left_node = None while not val_present: - #Interrogate each Node + # Interrogate each Node if node_inspected.val == val: val_present = True break else: - #Keeping track of node to left; incrementing node + # Keeping track of node to left; incrementing node left_node = node_inspected node_inspected = node_inspected.next @@ -110,4 +107,4 @@ def __init__(self, val, next=None): def __repr__(self): # Just display value - return "{val}".format(val=self.val) \ No newline at end of file + return "{val}".format(val=self.val) From b69d4af0f55f1ba19041d91ff4145e8d090e5276 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 14:00:40 -0700 Subject: [PATCH 013/330] Mark some points for code review --- linked_list.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/linked_list.py b/linked_list.py index f06b6e0..cf95491 100644 --- a/linked_list.py +++ b/linked_list.py @@ -2,8 +2,8 @@ class LinkedList(object): - """Class for a singly-linked list.""" - def __init__(self, iterable): + """Class for a singly-linked list.""" # Refactor and simplify + def __init__(self, iterable): # Needs to be able to make empty LList self.length = len(iterable) # Will resize for pop, insert, remove self.header = None stored_for_next = None # Seed value @@ -24,7 +24,7 @@ def __init__(self, iterable): def __repr__(self): """Print LinkedList as Tuple literal.""" end_flag = False - vals = [] + vals = [] # Can't use list! current_node = self.header while not end_flag: @@ -37,7 +37,7 @@ def __repr__(self): end_flag = True break - vals = tuple(vals) + vals = tuple(vals) # No tuples, even for formatting. return str(vals) def insert(self, val): @@ -48,7 +48,7 @@ def insert(self, val): def pop(self): """Pop the first val off the head and return it.""" - to_return = self.header + to_return = self.header # Use tuple reassignment self.header = to_return.next to_return.next = None self.length -= 1 @@ -80,7 +80,7 @@ def display(self): """Print LinkedList as Tuple literal""" return self.__repr__() - def _find(self, val): + def _find(self, val): # Check with spec re: this. """Return a Node and left-neighboor by val.""" val_present = False node_inspected = self.header @@ -93,7 +93,7 @@ def _find(self, val): break else: # Keeping track of node to left; incrementing node - left_node = node_inspected + left_node = node_inspected # use tuple assignment node_inspected = node_inspected.next return node_inspected, left_node From 1068b3bca46ce93b5b672ff08ff7dd82695531d2 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 14:15:27 -0700 Subject: [PATCH 014/330] Refactor init --- linked_list.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/linked_list.py b/linked_list.py index cf95491..6e362da 100644 --- a/linked_list.py +++ b/linked_list.py @@ -2,24 +2,11 @@ class LinkedList(object): - """Class for a singly-linked list.""" # Refactor and simplify - def __init__(self, iterable): # Needs to be able to make empty LList - self.length = len(iterable) # Will resize for pop, insert, remove - self.header = None - stored_for_next = None # Seed value - - for index, val in zip(range(self.length, 0, -1), reversed(iterable)): - if index == self.length: - # Case: right-most item in linked list; implicit None for next - created = Node(val) - elif index == 1: - # Case of needing to store list header - self.header = Node(val, stored_for_next) - else: - # All other linked nodes - created = Node(val, stored_for_next) - - stored_for_next = created # Store for next iteration + """Class for a singly-linked list.""" + def __init__(self, iterable=()): + self.length = 0 + for val in iterable: + self.insert(val) def __repr__(self): """Print LinkedList as Tuple literal.""" From 8d438da54a15fa213c5b57899505e040a42548bf Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 14:21:47 -0700 Subject: [PATCH 015/330] Fix init for tests --- linked_list.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/linked_list.py b/linked_list.py index 6e362da..d07430e 100644 --- a/linked_list.py +++ b/linked_list.py @@ -1,11 +1,23 @@ from __future__ import unicode_literals +class Node(object): + + def __init__(self, val, next=None): + self.val = val + self.next = next + + def __repr__(self): + # Just display value + return "{val}".format(val=self.val) + + class LinkedList(object): """Class for a singly-linked list.""" def __init__(self, iterable=()): + self.header = None self.length = 0 - for val in iterable: + for val in reversed(iterable): self.insert(val) def __repr__(self): @@ -84,14 +96,3 @@ def _find(self, val): # Check with spec re: this. node_inspected = node_inspected.next return node_inspected, left_node - - -class Node(object): - - def __init__(self, val, next=None): - self.val = val - self.next = next - - def __repr__(self): - # Just display value - return "{val}".format(val=self.val) From dac74cc3987581a3500f75168b6f8524a39f8007 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 14:54:01 -0700 Subject: [PATCH 016/330] Refactor __repr__ --- linked_list.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/linked_list.py b/linked_list.py index d07430e..ea44eca 100644 --- a/linked_list.py +++ b/linked_list.py @@ -22,22 +22,12 @@ def __init__(self, iterable=()): def __repr__(self): """Print LinkedList as Tuple literal.""" - end_flag = False - vals = [] # Can't use list! - current_node = self.header - - while not end_flag: - vals.append(current_node.val) - - if current_node.next: - current_node = current_node.next - - else: - end_flag = True - break - - vals = tuple(vals) # No tuples, even for formatting. - return str(vals) + node = self.header + output = "" + while node is not None: + output += "{!r}, ".format(node.val) + node = node.next + return "({})".format(output.rstrip(' ,')) def insert(self, val): """Insert val at head of LinkedList.""" @@ -49,6 +39,7 @@ def pop(self): """Pop the first val off the head and return it.""" to_return = self.header # Use tuple reassignment self.header = to_return.next + # to_return, self.header = self.header, to_return.next to_return.next = None self.length -= 1 return to_return @@ -92,7 +83,6 @@ def _find(self, val): # Check with spec re: this. break else: # Keeping track of node to left; incrementing node - left_node = node_inspected # use tuple assignment - node_inspected = node_inspected.next + left_node, node_inspected = node_inspected, node_inspected.next return node_inspected, left_node From d8f595d75703c06690858a2574dddaf8abd66277 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 15:23:03 -0700 Subject: [PATCH 017/330] Add appropriate exception handling to pop() --- linked_list.py | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/linked_list.py b/linked_list.py index ea44eca..361f5ec 100644 --- a/linked_list.py +++ b/linked_list.py @@ -37,12 +37,15 @@ def insert(self, val): def pop(self): """Pop the first val off the head and return it.""" - to_return = self.header # Use tuple reassignment - self.header = to_return.next - # to_return, self.header = self.header, to_return.next - to_return.next = None - self.length -= 1 - return to_return + if self.header is None: + raise IndexError + else: + to_return = self.header # Use tuple reassignment + self.header = to_return.next + # to_return, self.header = self.header, to_return.next + to_return.next = None + self.length -= 1 + return to_return def size(self): """Return current length of LinkedList.""" From 226342b27f24ef0ba4e9c065323b5e002a033889 Mon Sep 17 00:00:00 2001 From: jay-tyler Date: Thu, 25 Jun 2015 15:35:39 -0700 Subject: [PATCH 018/330] start to Stack class --- stack.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 stack.py diff --git a/stack.py b/stack.py new file mode 100644 index 0000000..ee378b4 --- /dev/null +++ b/stack.py @@ -0,0 +1,23 @@ +from __future__ import unicode_literals +from linked_list import LinkedList, Node + +class Stack(): + + def __init__(self, iterable=()): + self.other = LinkedList() + self.other.__init__(iterable) + + def __repr__(self): + return self.other.__repr__() + + def push(self, value): + """Will add a value to the stack""" + self.other.insert(value) + + def pop(self): + """Will remove val from stack and return""" + try: + val = self.other.pop() + + return val + From bf98ab35c568b825b89f206ae572b8fa147b5620 Mon Sep 17 00:00:00 2001 From: jay-tyler Date: Thu, 25 Jun 2015 15:39:31 -0700 Subject: [PATCH 019/330] cleanup in pop() --- stack.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/stack.py b/stack.py index ee378b4..0f509da 100644 --- a/stack.py +++ b/stack.py @@ -16,8 +16,6 @@ def push(self, value): def pop(self): """Will remove val from stack and return""" - try: - val = self.other.pop() + val = self.other.pop() return val - From 523b4e80d9fda242fded05072a3eb51f14832686 Mon Sep 17 00:00:00 2001 From: jay-tyler Date: Thu, 25 Jun 2015 15:41:37 -0700 Subject: [PATCH 020/330] PEP8 triviality --- stack.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stack.py b/stack.py index 0f509da..d749bba 100644 --- a/stack.py +++ b/stack.py @@ -1,5 +1,6 @@ from __future__ import unicode_literals -from linked_list import LinkedList, Node +from linked_list import LinkedList + class Stack(): From af7433ac5d7354c50ffa9c3fdf55d3b725f43941 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 15:45:25 -0700 Subject: [PATCH 021/330] Clean up code --- linked_list.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/linked_list.py b/linked_list.py index 361f5ec..18ca83e 100644 --- a/linked_list.py +++ b/linked_list.py @@ -42,8 +42,6 @@ def pop(self): else: to_return = self.header # Use tuple reassignment self.header = to_return.next - # to_return, self.header = self.header, to_return.next - to_return.next = None self.length -= 1 return to_return @@ -65,7 +63,6 @@ def remove(self, val): # Check Spec: Pass node vs val else: left_neighbor.next = node_to_remove.next - node_to_remove.next = None return None From 7010e0c318999d3eea4a3a62c00fca2aa8c6a0e5 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 16:07:39 -0700 Subject: [PATCH 022/330] Refactor find and remove functions --- linked_list.py | 22 ++++++++++++++-------- test_linked_list.py | 2 +- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/linked_list.py b/linked_list.py index 18ca83e..c08da78 100644 --- a/linked_list.py +++ b/linked_list.py @@ -54,9 +54,9 @@ def search(self, val): node, left = self._find(val) return node - def remove(self, val): # Check Spec: Pass node vs val + def remove(self, node): # Check Spec: Pass node vs val """Remove given node from list, return None""" - node_to_remove, left_neighbor = self._find(val) + node_to_remove, left_neighbor = self._find(node.val, node) if self.header == node_to_remove: self.pop() @@ -70,17 +70,23 @@ def display(self): """Print LinkedList as Tuple literal""" return self.__repr__() - def _find(self, val): # Check with spec re: this. - """Return a Node and left-neighboor by val.""" - val_present = False + def _find(self, val, node=None): + """ + Return a node and previous by matching against value or node.""" + matched = False node_inspected = self.header left_node = None - while not val_present: + while not matched: # Interrogate each Node if node_inspected.val == val: - val_present = True - break + if node is not None: + if node_inspected is node: + matched = True + break + else: + matched = True + break else: # Keeping track of node to left; incrementing node left_node, node_inspected = node_inspected, node_inspected.next diff --git a/test_linked_list.py b/test_linked_list.py index bc78853..e1da263 100644 --- a/test_linked_list.py +++ b/test_linked_list.py @@ -56,7 +56,7 @@ def test_search_val(base_llist): def test_remove_node(base_llist): - base_llist.remove(2) + base_llist.remove(base_llist.search(2)) assert base_llist.__repr__() == "(1, 3)" From 08fe87e6f3a22f9c551587cc02fe7d4f570aa8eb Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 16:10:11 -0700 Subject: [PATCH 023/330] Improve readability --- linked_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linked_list.py b/linked_list.py index c08da78..53fab7d 100644 --- a/linked_list.py +++ b/linked_list.py @@ -51,7 +51,7 @@ def size(self): def search(self, val): """Return the node containing val if present, else None""" - node, left = self._find(val) + node, _ = self._find(val) return node def remove(self, node): # Check Spec: Pass node vs val From 01a5f56238b7a0906414b538ee21d463dd583d98 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 16:28:36 -0700 Subject: [PATCH 024/330] Improve stack tests --- test_stack.py | 51 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 51 insertions(+) create mode 100644 test_stack.py diff --git a/test_stack.py b/test_stack.py new file mode 100644 index 0000000..fccc8d7 --- /dev/null +++ b/test_stack.py @@ -0,0 +1,51 @@ +from __future__ import unicode_literals +import pytest + +from stack import Stack + + +@pytest.fixture +def base_stack(): + return Stack([1, 2, 3]) + + +def test_construct_from_iterable_valid(base_stack): + expected_output = "(1, 2, 3)" + assert base_stack.__repr__() == expected_output + + +def test_construct_from_nested_iterable_valid(): + arg = ([1, 2, 3], 'string') + expected_output = "([1, 2, 3], u'string')" + assert Stack(arg).__repr__() == expected_output + + +def test_construct_from_string_valid(): + arg = "string" + expected_output = "(u's', u't', u'r', u'i', u'n', u'g')" + assert Stack(arg).__repr__() == expected_output + + +def test_construct_empty_valid(): + expected_output = "()" + assert Stack().__repr__() == expected_output + + +def test_construct_from_none_fails(): + with pytest.raises(TypeError): + Stack(None) + + +def test_construct_from_single_integer_fails(): + with pytest.raises(TypeError): + Stack(2) + + +def test_push(base_stack): + base_stack.push(4) + assert base_stack.__repr__() == "(4, 1, 2, 3)" + + +def test_pop(base_stack): + assert base_stack.pop().__repr__() == u"1" + assert base_stack.__repr__() == "(2, 3)" From ee5c83750092292c9f8a5203c011e857527ac877 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 16:29:36 -0700 Subject: [PATCH 025/330] Improve associated linked list tests --- test_linked_list.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/test_linked_list.py b/test_linked_list.py index e1da263..63e7d33 100644 --- a/test_linked_list.py +++ b/test_linked_list.py @@ -25,6 +25,11 @@ def test_construct_from_string_valid(): assert ll.LinkedList(arg).__repr__() == expected_output +def test_construct_empty_valid(): + expected_output = "()" + assert ll.LinkedList().__repr__() == expected_output + + def test_construct_from_none_fails(): with pytest.raises(TypeError): ll.LinkedList(None) @@ -41,7 +46,7 @@ def test_insert_single_value(base_llist): def test_pop(base_llist): - base_llist.pop() + assert base_llist.pop().__repr__() == u"1" assert base_llist.__repr__() == "(2, 3)" From 266166b542554d36560c64deb8466dadbe870d21 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 16:32:10 -0700 Subject: [PATCH 026/330] Improve docstrings --- stack.py | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/stack.py b/stack.py index d749bba..8652645 100644 --- a/stack.py +++ b/stack.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals + from linked_list import LinkedList @@ -12,11 +13,15 @@ def __repr__(self): return self.other.__repr__() def push(self, value): - """Will add a value to the stack""" + """Add a value to the head of the stack. + + args: + value: The value to add to the stack + """ self.other.insert(value) def pop(self): - """Will remove val from stack and return""" + """Remove a value from head of stack and return.""" val = self.other.pop() return val From 4f88255fab9d073153a30e27fe2560f1fcc4811e Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 16:43:49 -0700 Subject: [PATCH 027/330] Improve docstrings; fix not found case in find method --- linked_list.py | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/linked_list.py b/linked_list.py index 53fab7d..b6bae6a 100644 --- a/linked_list.py +++ b/linked_list.py @@ -8,7 +8,7 @@ def __init__(self, val, next=None): self.next = next def __repr__(self): - # Just display value + """Print representation of node.""" return "{val}".format(val=self.val) @@ -21,7 +21,7 @@ def __init__(self, iterable=()): self.insert(val) def __repr__(self): - """Print LinkedList as Tuple literal.""" + """Print representation of LinkedList.""" node = self.header output = "" while node is not None: @@ -30,7 +30,11 @@ def __repr__(self): return "({})".format(output.rstrip(' ,')) def insert(self, val): - """Insert val at head of LinkedList.""" + """Insert value at head of LinkedList. + + args: + val: the value to add + """ self.header = Node(val, self.header) self.length += 1 return None @@ -40,7 +44,7 @@ def pop(self): if self.header is None: raise IndexError else: - to_return = self.header # Use tuple reassignment + to_return = self.header self.header = to_return.next self.length -= 1 return to_return @@ -50,12 +54,20 @@ def size(self): return self.length def search(self, val): - """Return the node containing val if present, else None""" + """Return the node containing val if present, else None. + + args: + val: the value to add + """ node, _ = self._find(val) return node - def remove(self, node): # Check Spec: Pass node vs val - """Remove given node from list, return None""" + def remove(self, node): + """Remove given node from list, return None. + + args: + node: the node to be removed + """ node_to_remove, left_neighbor = self._find(node.val, node) if self.header == node_to_remove: @@ -72,7 +84,12 @@ def display(self): def _find(self, val, node=None): """ - Return a node and previous by matching against value or node.""" + Return a node and previous by matching against value or node. + + args: + val: the value to find + node: optionally, node to match identity against + """ matched = False node_inspected = self.header left_node = None @@ -87,8 +104,10 @@ def _find(self, val, node=None): else: matched = True break - else: - # Keeping track of node to left; incrementing node + # Keeping track of node to left; incrementing node + elif node_inspected.next is not None: left_node, node_inspected = node_inspected, node_inspected.next + else: + return None return node_inspected, left_node From d867fdc53a6e9f9a2441f06c7c6847bd66d8b3e1 Mon Sep 17 00:00:00 2001 From: jay-tyler Date: Thu, 25 Jun 2015 16:47:02 -0700 Subject: [PATCH 028/330] Update to README.md --- README.md | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 387bb7f..3406eb5 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,22 @@ -# data-structures -An assignment to implement a singly-linked list in Python +#Data Structures +Implementation of LinkedList and Stack data structures in Python. + +##LinkedList +The LinkedList class is composed of a Node base class. +Available methods inlude: +* insert(val) +* pop() +* size() +* search(val) +* remove(node) +* display() + +See the doc strings for implementation details. + +##Stack +The Stack data class is a first-in-first-out data structure built via composition from LinkedList. +Available methods include: +* push(value) +* pop() + +See the doc strings for implementation details. From 372daaa6f97c55e9a1c354631a79738542a04907 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 18:19:00 -0700 Subject: [PATCH 029/330] Bugfix: pop method now returns value instead of node --- linked_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/linked_list.py b/linked_list.py index b6bae6a..f482d72 100644 --- a/linked_list.py +++ b/linked_list.py @@ -47,7 +47,7 @@ def pop(self): to_return = self.header self.header = to_return.next self.length -= 1 - return to_return + return to_return.val def size(self): """Return current length of LinkedList.""" From b03c166b573c952b5e5e9e7b40660c7871105509 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 18:22:15 -0700 Subject: [PATCH 030/330] Improve linked list test --- test_linked_list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_linked_list.py b/test_linked_list.py index 63e7d33..f8d5e40 100644 --- a/test_linked_list.py +++ b/test_linked_list.py @@ -46,7 +46,7 @@ def test_insert_single_value(base_llist): def test_pop(base_llist): - assert base_llist.pop().__repr__() == u"1" + assert base_llist.pop() == 1 assert base_llist.__repr__() == "(2, 3)" From 8e5d9299d61bcfb0b9655a16640fbdd45ba57b0c Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 18:47:27 -0700 Subject: [PATCH 031/330] Clean up code --- stack.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/stack.py b/stack.py index 8652645..11d387c 100644 --- a/stack.py +++ b/stack.py @@ -10,7 +10,7 @@ def __init__(self, iterable=()): self.other.__init__(iterable) def __repr__(self): - return self.other.__repr__() + return repr(self.other) def push(self, value): """Add a value to the head of the stack. @@ -22,6 +22,4 @@ def push(self, value): def pop(self): """Remove a value from head of stack and return.""" - val = self.other.pop() - - return val + return self.other.pop() From 9e0f70fdac198ac0c5aeab3e5e64a98e8c121be5 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 25 Jun 2015 18:47:50 -0700 Subject: [PATCH 032/330] Improve tests --- test_stack.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test_stack.py b/test_stack.py index fccc8d7..ff4e483 100644 --- a/test_stack.py +++ b/test_stack.py @@ -47,5 +47,11 @@ def test_push(base_stack): def test_pop(base_stack): - assert base_stack.pop().__repr__() == u"1" + assert base_stack.pop() == 1 assert base_stack.__repr__() == "(2, 3)" + + +def test_pop_after_multi_push(base_stack): + for x in range(10): + base_stack.push(x) + assert base_stack.pop() == 9 From 2d612223091f9b891206fe98db9e3a7d2e503806 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Mon, 29 Jun 2015 13:52:16 -0700 Subject: [PATCH 033/330] new, empty files --- queue.py | 0 test_queue.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 queue.py create mode 100644 test_queue.py diff --git a/queue.py b/queue.py new file mode 100644 index 0000000..e69de29 diff --git a/test_queue.py b/test_queue.py new file mode 100644 index 0000000..e69de29 From cc0dc04539a77037462342cd7a5f42687c325ccf Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 29 Jun 2015 13:59:56 -0700 Subject: [PATCH 034/330] Sketch out methods and docstrings. --- queue.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/queue.py b/queue.py index e69de29..0cf3fc5 100644 --- a/queue.py +++ b/queue.py @@ -0,0 +1,29 @@ +from __future__ import unicode_literals + +from linked_list import LinkedList + + +class Queue(): + + def __init__(self, iterable=()): + self.other = LinkedList() + self.other_init__(iterable) + self.tail = None + + def __repr__(self): + pass + + def __len__(self): + pass + + def enqueue(self, value): + """Add a value to the tail of a queue + + args: + value: The value to add to the queue + """ + pass + + def dequeue(self): + """Remove a value from the head of the queue""" + pass From ca19a982f5302fa0aefbaad2b97fa338b01103b3 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 29 Jun 2015 14:16:32 -0700 Subject: [PATCH 035/330] Complete first pass of functions --- queue.py | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/queue.py b/queue.py index 0cf3fc5..cd0128f 100644 --- a/queue.py +++ b/queue.py @@ -1,29 +1,38 @@ from __future__ import unicode_literals -from linked_list import LinkedList +from linked_list import LinkedList, Node class Queue(): def __init__(self, iterable=()): self.other = LinkedList() - self.other_init__(iterable) + self.header = None self.tail = None + self.length = None + for val in (iterable): + self.enqueue(val) def __repr__(self): - pass + return repr(self.other) def __len__(self): - pass + return self.length def enqueue(self, value): - """Add a value to the tail of a queue + """Add a value to the tail of a queue. args: value: The value to add to the queue """ - pass + new_node = Node(value) + self.tail.next = new_node + self.tail = new_node + self.length += 1 def dequeue(self): - """Remove a value from the head of the queue""" - pass + """Remove and return a value from the head of the queue.""" + return self.other.pop() + + def size(self): + return len(self) From 9958c72f6b0dded9c90592a93805379ccaf9abdc Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Mon, 29 Jun 2015 14:17:11 -0700 Subject: [PATCH 036/330] Start to tests --- test_queue.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/test_queue.py b/test_queue.py index e69de29..562c20f 100644 --- a/test_queue.py +++ b/test_queue.py @@ -0,0 +1,3 @@ +from __future__ import unicode_literals +import pytest + From de6ac088859cd1cccedee7c5f0a6435c6545a998 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 29 Jun 2015 14:29:47 -0700 Subject: [PATCH 037/330] Save work on functions. Need to fix enqueue --- queue.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/queue.py b/queue.py index cd0128f..f35efc1 100644 --- a/queue.py +++ b/queue.py @@ -9,7 +9,7 @@ def __init__(self, iterable=()): self.other = LinkedList() self.header = None self.tail = None - self.length = None + self.length = 0 for val in (iterable): self.enqueue(val) @@ -25,8 +25,11 @@ def enqueue(self, value): args: value: The value to add to the queue """ - new_node = Node(value) - self.tail.next = new_node + new_node = Node(value) # Need to fix this logic + if len(self) == 0: + self.header = new_node + if self.tail is not None: + self.tail.next = new_node self.tail = new_node self.length += 1 From 80ae888a543d43cc992a13b5188bec2bf435d423 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Mon, 29 Jun 2015 14:43:03 -0700 Subject: [PATCH 038/330] working on parametrizing tests; need to figure out how to do tuple unpacking inside parametrize --- test_queue.py | 75 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 75 insertions(+) diff --git a/test_queue.py b/test_queue.py index 562c20f..5b449ad 100644 --- a/test_queue.py +++ b/test_queue.py @@ -1,3 +1,78 @@ from __future__ import unicode_literals import pytest +from linked_list import LinkedList +from queue import Queue + +# TODO: check if unpackaging a single tuple +# These are valid constructors that will support subsequent deque +valid_constructor_args = [ + (1,2,3), + ([1,2,3,], "string" ), + ("string"), + () +] + +invalid_constructor_args = [ + ((None)), + ((1)) +] + +@pytest.mark.parametrize("args", valid_constructor_args) +def test_valid_contructor(*args): + assert Queue(args).deque() == valid_constructor_args[0] + + + +# ****** +# @pytest.fixture +# def base_stack(): +# return Stack([1, 2, 3]) + + +# def test_construct_from_iterable_valid(base_stack): +# expected_output = "(1, 2, 3)" +# assert base_stack.__repr__() == expected_output + + +# def test_construct_from_nested_iterable_valid(): +# arg = ([1, 2, 3], 'string') +# expected_output = "([1, 2, 3], u'string')" +# assert Stack(arg).__repr__() == expected_output + + +# def test_construct_from_string_valid(): +# arg = "string" +# expected_output = "(u's', u't', u'r', u'i', u'n', u'g')" +# assert Stack(arg).__repr__() == expected_output + + +# def test_construct_empty_valid(): +# expected_output = "()" +# assert Stack().__repr__() == expected_output + + +# def test_construct_from_none_fails(): +# with pytest.raises(TypeError): +# Stack(None) + + +# def test_construct_from_single_integer_fails(): +# with pytest.raises(TypeError): +# Stack(2) + + +# def test_push(base_stack): +# base_stack.push(4) +# assert base_stack.__repr__() == "(4, 1, 2, 3)" + + +# def test_pop(base_stack): +# assert base_stack.pop() == 1 +# assert base_stack.__repr__() == "(2, 3)" + + +# def test_pop_after_multi_push(base_stack): +# for x in range(10): +# base_stack.push(x) +# assert base_stack.pop() == 9 \ No newline at end of file From 5c29a7d76b24ab26a0202e52f1bcdbb4201b1439 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Mon, 29 Jun 2015 19:58:20 -0700 Subject: [PATCH 039/330] current --- test_queue.py | 1 + 1 file changed, 1 insertion(+) diff --git a/test_queue.py b/test_queue.py index 5b449ad..d7e791b 100644 --- a/test_queue.py +++ b/test_queue.py @@ -24,6 +24,7 @@ def test_valid_contructor(*args): + # ****** # @pytest.fixture # def base_stack(): From 4ce091ac426b6877e848c28e2109bb1685e5fb66 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 29 Jun 2015 19:59:19 -0700 Subject: [PATCH 040/330] Fix composition --- queue.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/queue.py b/queue.py index f35efc1..903fb4f 100644 --- a/queue.py +++ b/queue.py @@ -7,9 +7,9 @@ class Queue(): def __init__(self, iterable=()): self.other = LinkedList() - self.header = None - self.tail = None - self.length = 0 + self.other.header = None + self.other.tail = None + self.other.length = 0 for val in (iterable): self.enqueue(val) @@ -17,7 +17,7 @@ def __repr__(self): return repr(self.other) def __len__(self): - return self.length + return self.other.length def enqueue(self, value): """Add a value to the tail of a queue. @@ -26,12 +26,12 @@ def enqueue(self, value): value: The value to add to the queue """ new_node = Node(value) # Need to fix this logic - if len(self) == 0: - self.header = new_node - if self.tail is not None: - self.tail.next = new_node - self.tail = new_node - self.length += 1 + if self.other.tail is None: + self.other.header = self.other.tail = new_node + else: + self.other.tail.next = new_node + self.other.tail = new_node + self.other.length += 1 def dequeue(self): """Remove and return a value from the head of the queue.""" From fdb988290b155331272a72b9509753d818d1ebac Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Mon, 29 Jun 2015 21:35:25 -0700 Subject: [PATCH 041/330] Should have a pretty decent set of tests now. Sorry for the extra wait. --- test_queue.py | 91 +++++++++++++++++---------------------------------- 1 file changed, 30 insertions(+), 61 deletions(-) diff --git a/test_queue.py b/test_queue.py index d7e791b..55d25b5 100644 --- a/test_queue.py +++ b/test_queue.py @@ -4,76 +4,45 @@ from linked_list import LinkedList from queue import Queue -# TODO: check if unpackaging a single tuple -# These are valid constructors that will support subsequent deque -valid_constructor_args = [ - (1,2,3), - ([1,2,3,], "string" ), - ("string"), - () +# (Input, expected) for well constructed instantiation arguments, +# and one subsequent dequeue +valid_constructor_args_dequeue = [ + ([1,2,3], 1), + ([[1,2,3,], "string" ], [1,2,3]), + ("string", 's') ] +# Invalid instantiation arguments invalid_constructor_args = [ - ((None)), - ((1)) + (None), + (1), + (4.5235) ] -@pytest.mark.parametrize("args", valid_constructor_args) -def test_valid_contructor(*args): - assert Queue(args).deque() == valid_constructor_args[0] +@pytest.mark.parametrize("input,deque",valid_constructor_args_dequeue) +def test_valid_contructor(input, deque): + """Test valid constructor using by dequeuing after instantiation""" + assert Queue(input).dequeue() == deque +def test_empty_constructor(): + """Test valid empty constructor via dequeuing after instantiation""" + with pytest.raises(IndexError): + Queue(()).dequeue() -# ****** -# @pytest.fixture -# def base_stack(): -# return Stack([1, 2, 3]) +@pytest.mark.parametrize("input", invalid_constructor_args) +def test_invalid_constructor(input): + """Test invalid constuctor arguments""" + with pytest.raises(TypeError): + Queue(input) -# def test_construct_from_iterable_valid(base_stack): -# expected_output = "(1, 2, 3)" -# assert base_stack.__repr__() == expected_output - -# def test_construct_from_nested_iterable_valid(): -# arg = ([1, 2, 3], 'string') -# expected_output = "([1, 2, 3], u'string')" -# assert Stack(arg).__repr__() == expected_output - - -# def test_construct_from_string_valid(): -# arg = "string" -# expected_output = "(u's', u't', u'r', u'i', u'n', u'g')" -# assert Stack(arg).__repr__() == expected_output - - -# def test_construct_empty_valid(): -# expected_output = "()" -# assert Stack().__repr__() == expected_output - - -# def test_construct_from_none_fails(): -# with pytest.raises(TypeError): -# Stack(None) - - -# def test_construct_from_single_integer_fails(): -# with pytest.raises(TypeError): -# Stack(2) - - -# def test_push(base_stack): -# base_stack.push(4) -# assert base_stack.__repr__() == "(4, 1, 2, 3)" - - -# def test_pop(base_stack): -# assert base_stack.pop() == 1 -# assert base_stack.__repr__() == "(2, 3)" - - -# def test_pop_after_multi_push(base_stack): -# for x in range(10): -# base_stack.push(x) -# assert base_stack.pop() == 9 \ No newline at end of file +def test_enqueue(): + """Fill an empty contructor argument and assert len, deque)""" + filling_this = Queue(()) + for i in xrange(40): + filling_this.enqueue(i) + assert len(filling_this) == 40 + assert filling_this.dequeue() == 0 From 2e1698a167010fd31c1ebd6d28af94401f098732 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Mon, 29 Jun 2015 21:41:18 -0700 Subject: [PATCH 042/330] Added to README.md --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 3406eb5..4c287de 100644 --- a/README.md +++ b/README.md @@ -19,4 +19,11 @@ Available methods include: * push(value) * pop() +##Dequeque +The Dequeue data class is a first-in-last-out data structure built via encapsulation of a LinkedList. +Available methods inlude: +* enqueque(value) +* dequeque() +* len() + See the doc strings for implementation details. From 393b9a7c25c20fc6969dc9c9f8943d792fe301ae Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 29 Jun 2015 22:02:07 -0700 Subject: [PATCH 043/330] Remove comment --- queue.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/queue.py b/queue.py index 903fb4f..3f9fd30 100644 --- a/queue.py +++ b/queue.py @@ -25,7 +25,7 @@ def enqueue(self, value): args: value: The value to add to the queue """ - new_node = Node(value) # Need to fix this logic + new_node = Node(value) if self.other.tail is None: self.other.header = self.other.tail = new_node else: From abbe91634109b2554c368851439b0934fda2a85f Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 29 Jun 2015 22:10:31 -0700 Subject: [PATCH 044/330] Fix style --- test_queue.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/test_queue.py b/test_queue.py index 55d25b5..653a7d9 100644 --- a/test_queue.py +++ b/test_queue.py @@ -1,14 +1,13 @@ from __future__ import unicode_literals import pytest -from linked_list import LinkedList from queue import Queue # (Input, expected) for well constructed instantiation arguments, # and one subsequent dequeue valid_constructor_args_dequeue = [ - ([1,2,3], 1), - ([[1,2,3,], "string" ], [1,2,3]), + ([1, 2, 3], 1), + ([[1, 2, 3], "string"], [1, 2, 3]), ("string", 's') ] @@ -20,7 +19,7 @@ ] -@pytest.mark.parametrize("input,deque",valid_constructor_args_dequeue) +@pytest.mark.parametrize("input,deque", valid_constructor_args_dequeue) def test_valid_contructor(input, deque): """Test valid constructor using by dequeuing after instantiation""" assert Queue(input).dequeue() == deque From d6c337f56ea09be1070af7493b2bfcdf89c0c5e1 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 29 Jun 2015 22:44:18 -0700 Subject: [PATCH 045/330] Fix dequeue method tail handling. --- queue.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/queue.py b/queue.py index 3f9fd30..cb8d89a 100644 --- a/queue.py +++ b/queue.py @@ -35,6 +35,8 @@ def enqueue(self, value): def dequeue(self): """Remove and return a value from the head of the queue.""" + if len(self) == 1: + self.other.tail = None return self.other.pop() def size(self): From 1ea16a78bd404c24e0cd9623a703863b3a8a8f6d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 29 Jun 2015 23:09:43 -0700 Subject: [PATCH 046/330] Add test for dequeue --- test_queue.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test_queue.py b/test_queue.py index 653a7d9..e8e9ec2 100644 --- a/test_queue.py +++ b/test_queue.py @@ -1,3 +1,4 @@ +# -*- coding: utf-8 -*- from __future__ import unicode_literals import pytest @@ -45,3 +46,17 @@ def test_enqueue(): filling_this.enqueue(i) assert len(filling_this) == 40 assert filling_this.dequeue() == 0 + + +def test_dequeue(): + q = Queue([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) + for x in range(5): + q.dequeue() + assert q.dequeue() == 6 + assert q.other.header.val == 7 + assert q.other.tail.val == 10 + assert len(q) == 4 + while len(q): + q.dequeue() + assert q.other.header is None + assert q.other.tail is None From b34443336bfe2b4f845d67799905da78878ff76d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 08:54:13 -0700 Subject: [PATCH 047/330] Implement linked list as iterable; need to refactor other data structures --- linked_list.py | 95 +++++++++++++++++++++----------------------------- 1 file changed, 40 insertions(+), 55 deletions(-) diff --git a/linked_list.py b/linked_list.py index f482d72..37998af 100644 --- a/linked_list.py +++ b/linked_list.py @@ -15,99 +15,84 @@ def __repr__(self): class LinkedList(object): """Class for a singly-linked list.""" def __init__(self, iterable=()): - self.header = None + self._current = None + self.head = None self.length = 0 for val in reversed(iterable): self.insert(val) def __repr__(self): """Print representation of LinkedList.""" - node = self.header + node = self.head output = "" - while node is not None: + for node in self: output += "{!r}, ".format(node.val) - node = node.next return "({})".format(output.rstrip(' ,')) + def __len__(self): + return self.length + + def __iter__(self): + if self.head is not None: + self._current = self.head + return self + + def next(self): + if self._current is None: + raise StopIteration + node = self._current + self._current = self._current.next + return node + def insert(self, val): """Insert value at head of LinkedList. args: val: the value to add """ - self.header = Node(val, self.header) + self.head = Node(val, self.head) self.length += 1 return None def pop(self): """Pop the first val off the head and return it.""" - if self.header is None: + if self.head is None: raise IndexError else: - to_return = self.header - self.header = to_return.next + to_return = self.head + self.head = to_return.next self.length -= 1 return to_return.val def size(self): """Return current length of LinkedList.""" - return self.length + return len(self) - def search(self, val): + def search(self, search_val): """Return the node containing val if present, else None. args: - val: the value to add - """ - node, _ = self._find(val) - return node - - def remove(self, node): - """Remove given node from list, return None. + search_val: the value to search by - args: - node: the node to be removed + returns: a node object or None """ - node_to_remove, left_neighbor = self._find(node.val, node) - - if self.header == node_to_remove: - self.pop() - + for node in self: + if node.val == search_val: + return node else: - left_neighbor.next = node_to_remove.next - - return None + return None - def display(self): - """Print LinkedList as Tuple literal""" - return self.__repr__() - - def _find(self, val, node=None): - """ - Return a node and previous by matching against value or node. + def remove(self, search_node): + """Remove given node from list, return None. args: - val: the value to find - node: optionally, node to match identity against + search_node: the node to be removed """ - matched = False - node_inspected = self.header - left_node = None - - while not matched: - # Interrogate each Node - if node_inspected.val == val: - if node is not None: - if node_inspected is node: - matched = True - break - else: - matched = True - break - # Keeping track of node to left; incrementing node - elif node_inspected.next is not None: - left_node, node_inspected = node_inspected, node_inspected.next - else: + for node in self: + if node.next == search_node: + node.next = node.next.next return None - return node_inspected, left_node + def display(self): + """Shows representation of LinkedList.""" + return repr(self) From 3a074a43f6e979854de3e2f80dfc5dd733b7d64c Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 12:38:14 -0700 Subject: [PATCH 048/330] Add first pass for double link list --- double_link_list.py | 103 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) create mode 100644 double_link_list.py diff --git a/double_link_list.py b/double_link_list.py new file mode 100644 index 0000000..1efcc74 --- /dev/null +++ b/double_link_list.py @@ -0,0 +1,103 @@ +from __future__ import unicode_literals + + +class Node(object): + + def __init__(self, val, prev=None, next_=None): + self.val = val + self.prev = prev + self.next = next_ + + def __repr__(self): + """Print representation of node.""" + return "{val}".format(val=self.val) + + +class DoubleLinkList(object): + """Class for a doubly-linked list.""" + def __init__(self, iterable=()): + self._current = None + self.head = None + self.length = 0 + for val in reversed(iterable): + self.insert(val) + + def __repr__(self): + """Print representation of DoubleLinkList.""" + node = self.head + output = "" + for node in self: + output += "{!r}, ".format(node.val) + return "({})".format(output.rstrip(' ,')) + + def __len__(self): + return self.length + + def __iter__(self): + if self.head is not None: + self._current = self.head + return self + + def next(self): + if self._current is None: + raise StopIteration + node = self._current + self._current = self._current.next + return node + + def insert(self, val): + """Insert value at head of DoubleLinkList. + + args: + val: the value to add + """ + current_head = self.head + self.head = Node(val, prev=None, next_=current_head) + current_head.prev = self.head + self.length += 1 + return None + + def pop(self): + """Pop the first val off the head and return it.""" + if self.head is None: + raise IndexError + else: + to_return = self.head + self.head = to_return.next + self.head.prev = None + self.length -= 1 + return to_return.val + + def size(self): + """Return current length of DoubleLinkList.""" + return len(self) + + def search(self, search_val): + """Return the node containing val if present, else None. + + args: + search_val: the value to search by + + returns: a node object or None + """ + for node in self: + if node.val == search_val: + return node + else: + return None + + def remove(self, search_node): + """Remove given node from list, return None. + + args: + search_node: the node to be removed + """ + for node in self: + if node == search_node: + node.prev.next = node.next + node.next.prev = node.prev + return None + + def display(self): + """Shows representation of DoubleLinkList.""" + return repr(self) From 6178d660d9424afd2e40ce973001a5c7de52a1d3 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 12:43:23 -0700 Subject: [PATCH 049/330] Complete old methods. --- double_link_list.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/double_link_list.py b/double_link_list.py index 1efcc74..6a891d0 100644 --- a/double_link_list.py +++ b/double_link_list.py @@ -53,8 +53,9 @@ def insert(self, val): """ current_head = self.head self.head = Node(val, prev=None, next_=current_head) - current_head.prev = self.head - self.length += 1 + if current_head is not None: + current_head.prev = self.head + self.length += 1pre return None def pop(self): From 79efd8ff082d6b2e6f5caa3203fa640d9a7451ea Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 12:45:42 -0700 Subject: [PATCH 050/330] Sketch docstrings for new methods --- double_link_list.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/double_link_list.py b/double_link_list.py index 6a891d0..3695694 100644 --- a/double_link_list.py +++ b/double_link_list.py @@ -55,7 +55,7 @@ def insert(self, val): self.head = Node(val, prev=None, next_=current_head) if current_head is not None: current_head.prev = self.head - self.length += 1pre + self.length += 1 return None def pop(self): @@ -99,6 +99,14 @@ def remove(self, search_node): node.next.prev = node.prev return None + def append(self, val): + """Append a node with value to end of list.""" + pass + + def shift(self): + """Remove the last value from the tail and return.""" + pass + def display(self): """Shows representation of DoubleLinkList.""" return repr(self) From e21690868bc91a9e14598227776bacd62be6c869 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 12:51:41 -0700 Subject: [PATCH 051/330] Complete basic methods, need to fix head and tail management. --- double_link_list.py | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/double_link_list.py b/double_link_list.py index 3695694..62a2e11 100644 --- a/double_link_list.py +++ b/double_link_list.py @@ -18,6 +18,7 @@ class DoubleLinkList(object): def __init__(self, iterable=()): self._current = None self.head = None + self.tail = None # update methods for tail self.length = 0 for val in reversed(iterable): self.insert(val) @@ -101,11 +102,23 @@ def remove(self, search_node): def append(self, val): """Append a node with value to end of list.""" - pass + current_tail = self.tail # update with head management + self.tail = Node(val, prev=current_tail, next_=None) + if current_tail is not None: + current_tail.next = self.tail + self.length += 1 + return None def shift(self): """Remove the last value from the tail and return.""" - pass + if self.tail is None: # Update with head management + raise IndexError + else: + to_return = self.tail + self.tail = to_return.prev + self.tail.next = None + self.length -= 1 + return to_return.val def display(self): """Shows representation of DoubleLinkList.""" From 5fcba41fa70dc50cd186e6a9e1fd3d9672ca6949 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 12:59:04 -0700 Subject: [PATCH 052/330] Mark further points for review --- double_link_list.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/double_link_list.py b/double_link_list.py index 62a2e11..e8423b1 100644 --- a/double_link_list.py +++ b/double_link_list.py @@ -18,7 +18,7 @@ class DoubleLinkList(object): def __init__(self, iterable=()): self._current = None self.head = None - self.tail = None # update methods for tail + self.tail = None self.length = 0 for val in reversed(iterable): self.insert(val) @@ -54,7 +54,9 @@ def insert(self, val): """ current_head = self.head self.head = Node(val, prev=None, next_=current_head) - if current_head is not None: + if current_head is None: + self.head = self.tail + else: current_head.prev = self.head self.length += 1 return None @@ -94,7 +96,7 @@ def remove(self, search_node): args: search_node: the node to be removed """ - for node in self: + for node in self: # Check head and tail if node == search_node: node.prev.next = node.next node.next.prev = node.prev From 5e3f65b0543015ea9bb297b27b343e6fedb0de1a Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 13:36:58 -0700 Subject: [PATCH 053/330] Refactor remove --- double_link_list.py | 32 +++++++++++++++++++++----------- 1 file changed, 21 insertions(+), 11 deletions(-) diff --git a/double_link_list.py b/double_link_list.py index e8423b1..b7a9392 100644 --- a/double_link_list.py +++ b/double_link_list.py @@ -52,23 +52,25 @@ def insert(self, val): args: val: the value to add """ - current_head = self.head - self.head = Node(val, prev=None, next_=current_head) - if current_head is None: - self.head = self.tail + old_head = self.head + self.head = Node(val, prev=None, next_=old_head) + if old_head is None: + self.tail = self.head else: - current_head.prev = self.head + old_head.prev = self.head self.length += 1 return None def pop(self): """Pop the first val off the head and return it.""" - if self.head is None: + if self.head is None: # Add tail management raise IndexError else: to_return = self.head self.head = to_return.next self.head.prev = None + if len(self) == 0: + self.tail = None self.length -= 1 return to_return.val @@ -96,11 +98,19 @@ def remove(self, search_node): args: search_node: the node to be removed """ - for node in self: # Check head and tail - if node == search_node: - node.prev.next = node.next - node.next.prev = node.prev - return None + if search_node == self.head: + self.header = search_node.next + self.header.prev = None + elif search_node == self.tail: + self.tail = search_node.prev + self.tail.next = None + else: + for node in self: + if node == search_node: + node.prev.next = node.next + node.next.prev = node.prev + return None + def append(self, val): """Append a node with value to end of list.""" From dc92d037b821285d44b59703a0d847c85e012783 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 13:45:49 -0700 Subject: [PATCH 054/330] Complete methods shift and pop --- double_link_list.py | 41 ++++++++++++++++++++++------------------- 1 file changed, 22 insertions(+), 19 deletions(-) diff --git a/double_link_list.py b/double_link_list.py index b7a9392..ea4c9d7 100644 --- a/double_link_list.py +++ b/double_link_list.py @@ -61,19 +61,6 @@ def insert(self, val): self.length += 1 return None - def pop(self): - """Pop the first val off the head and return it.""" - if self.head is None: # Add tail management - raise IndexError - else: - to_return = self.head - self.head = to_return.next - self.head.prev = None - if len(self) == 0: - self.tail = None - self.length -= 1 - return to_return.val - def size(self): """Return current length of DoubleLinkList.""" return len(self) @@ -111,16 +98,29 @@ def remove(self, search_node): node.next.prev = node.prev return None - def append(self, val): """Append a node with value to end of list.""" - current_tail = self.tail # update with head management - self.tail = Node(val, prev=current_tail, next_=None) - if current_tail is not None: - current_tail.next = self.tail + old_tail = self.tail # update with head management + self.tail = Node(val, prev=old_tail, next_=None) + if old_tail is not None: + old_tail.next = self.tail self.length += 1 return None + def pop(self): + """Pop the first val off the head and return it.""" + if self.head is None: + raise IndexError + else: + to_return = self.head + self.head = to_return.next + try: + self.head.prev = None + except AttributeError: + self.tail = None + self.length -= 1 + return to_return.val + def shift(self): """Remove the last value from the tail and return.""" if self.tail is None: # Update with head management @@ -128,7 +128,10 @@ def shift(self): else: to_return = self.tail self.tail = to_return.prev - self.tail.next = None + try: + self.tail.next = None + except AttributeError: + self.head = None self.length -= 1 return to_return.val From fc30c3261f363d4531f89bb4f396c53b2d4ec1d2 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 13:49:32 -0700 Subject: [PATCH 055/330] Add comments --- double_link_list.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/double_link_list.py b/double_link_list.py index ea4c9d7..0adcd3f 100644 --- a/double_link_list.py +++ b/double_link_list.py @@ -116,21 +116,21 @@ def pop(self): self.head = to_return.next try: self.head.prev = None - except AttributeError: + except AttributeError: # List is now empty self.tail = None self.length -= 1 return to_return.val def shift(self): """Remove the last value from the tail and return.""" - if self.tail is None: # Update with head management + if self.tail is None: raise IndexError else: to_return = self.tail self.tail = to_return.prev try: self.tail.next = None - except AttributeError: + except AttributeError: # List is now empty self.head = None self.length -= 1 return to_return.val From c15b89843407dfddf86ec1e14c82695178e4a105 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 14:19:45 -0700 Subject: [PATCH 056/330] Fix bugs --- double_link_list.py | 48 +++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 19 deletions(-) diff --git a/double_link_list.py b/double_link_list.py index 0adcd3f..acc01ab 100644 --- a/double_link_list.py +++ b/double_link_list.py @@ -46,21 +46,6 @@ def next(self): self._current = self._current.next return node - def insert(self, val): - """Insert value at head of DoubleLinkList. - - args: - val: the value to add - """ - old_head = self.head - self.head = Node(val, prev=None, next_=old_head) - if old_head is None: - self.tail = self.head - else: - old_head.prev = self.head - self.length += 1 - return None - def size(self): """Return current length of DoubleLinkList.""" return len(self) @@ -79,30 +64,55 @@ def search(self, search_val): else: return None - def remove(self, search_node): + def remove(self, search_val): """Remove given node from list, return None. args: search_node: the node to be removed """ + search_node = self.search(search_val) if search_node == self.head: self.header = search_node.next self.header.prev = None + self.length -= 1 elif search_node == self.tail: self.tail = search_node.prev self.tail.next = None + self.length -= 1 else: for node in self: if node == search_node: node.prev.next = node.next node.next.prev = node.prev + self.length -= 1 return None + def insert(self, val): + """Insert value at head of list. + + args: + val: the value to add + """ + old_head = self.head + self.head = Node(val, prev=None, next_=old_head) + if old_head is None: + self.tail = self.head + else: + old_head.prev = self.head + self.length += 1 + return None + def append(self, val): - """Append a node with value to end of list.""" - old_tail = self.tail # update with head management + """Append a node with value to end of list. + + args: + val: the value to add + """ + old_tail = self.tail self.tail = Node(val, prev=old_tail, next_=None) - if old_tail is not None: + if old_tail is None: + self.head = self.tail + else: old_tail.next = self.tail self.length += 1 return None From 0eb644b276ca40dd62265e30328f9470de9dde91 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 14:25:01 -0700 Subject: [PATCH 057/330] Bugfix: remove method head case --- double_link_list.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/double_link_list.py b/double_link_list.py index acc01ab..c06613a 100644 --- a/double_link_list.py +++ b/double_link_list.py @@ -72,8 +72,8 @@ def remove(self, search_val): """ search_node = self.search(search_val) if search_node == self.head: - self.header = search_node.next - self.header.prev = None + self.head = search_node.next + self.head.prev = None self.length -= 1 elif search_node == self.tail: self.tail = search_node.prev From 0f51e36ff28c62c367ee26d77578affd90cec362 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Tue, 30 Jun 2015 18:51:05 -0700 Subject: [PATCH 058/330] tests for insert, append, pop, shift, instantiation --- test_double_link_list.py | 330 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 330 insertions(+) create mode 100644 test_double_link_list.py diff --git a/test_double_link_list.py b/test_double_link_list.py new file mode 100644 index 0000000..d24195f --- /dev/null +++ b/test_double_link_list.py @@ -0,0 +1,330 @@ +# -*- coding: utf-8 -*- +from __future__ import unicode_literals +import pytest +from double_link_list import DoubleLinkList + +# (Input, expected) for well constructed instantiation arguments, +# and one subsequent pop +valid_constructor_args = [ + ([1, 2, 3], 1), + ([[1, 2, 3], "string"], [1, 2, 3]), + ("string", 's') +] + +# Invalid instantiation arguments +invalid_constructor_args = [ + (None), + (1), + (4.5235) +] + +# (Input, remove, expected) for well constructed instantiation arguments, +# remove arg, and expected repr +remove_val_args = [ + ([1, 2, 3, 4, 5, 6, 7], 6) +] + + +@pytest.mark.parametrize("input, pop_val", valid_constructor_args) +def test_valid_contructor(input, pop_val): + """Test valid constructor using by dequeuing after instantiation""" + assert DoubleLinkList(input).pop() == pop_val + + +def test_empty_constructor(): + """Test valid empty constructor via dequeuing after instantiation""" + with pytest.raises(IndexError): + DoubleLinkList(()).pop() + + +@pytest.mark.parametrize("input", invalid_constructor_args) +def test_invalid_constructor(input): + """Test invalid constuctor arguments""" + with pytest.raises(TypeError): + DoubleLinkList(input) + + +def test_insert_via_len(): + """Tests insert function for doublelinked list; using len""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.insert(i) + + assert len(adding_to) == 40 + assert adding_to.pop() == 39 + + +def test_insert_via_pop(): + """Tests insert function for doublelinked list; using pop""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.insert(i) + + assert adding_to.pop() == 39 + + +def test_insert_via_shift(): + """Tests insert function for doublelinked list; using shift""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.insert(i) + + assert adding_to.shift() == 0 + + +def test_append_via_len(): + """Tests append method for doublelinked list; using len""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.append(i) + + assert len(adding_to) == 40 + + +def test_append_via_pop(): + """Tests append method for doublelinked list; using len""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.append(i) + + assert adding_to.pop() == 0 + + +def test_append_via_shift(): + """Tests append method for doublelinked list; using len""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.append(i) + + assert adding_to.shift() == 39 + + +def test_pop_1(): + """Fill a DoubleLinkList with (1,2,3), check first pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + assert adding_to.pop() == 2 + + +def test_pop_2(): + """Fill a DoubleLinkList with (1,2,3), check second pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.pop() # First action covered by test_pop_1 + assert adding_to.pop() == 1 + + +def test_pop_3(): + """Fill a DoubleLinkList with (1,2,3), check third pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.pop() # First action covered by test_pop_1 + adding_to.pop() # Second action covered by test_pop_2 + assert adding_to.pop() == 0 + + +def test_pop_4(): + """Fill a DoubleLinkList with (1,2,3), check fourth pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.pop() # First action covered by test_pop_1 + adding_to.pop() # Second action covered by test_pop_2 + adding_to.pop() + with pytest.raises(IndexError): + adding_to.pop() + + +def test_shift_1(): + """Fill a DoubleLinkList with (1,2,3), check first pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + assert adding_to.shift() == 0 + + +def test_shift_2(): + """Fill a DoubleLinkList with (1,2,3), check second pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.shift() # First action covered by test_pop_1 + assert adding_to.shift() == 1 + + +def test_shift_3(): + """Fill a DoubleLinkList with (1,2,3), check third pop""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + + adding_to.shift() # First action covered by test_pop_1 + adding_to.shift() # Second action covered by test_pop_2 + assert adding_to.shift() == 2 + + +# def test_pop_4(): +# """Fill a DoubleLinkList with (1,2,3), check fourth pop""" +# adding_to = DoubleLinkList(()) +# for i in xrange(3): +# adding_to.insert(i) + +# adding_to.pop() # First action covered by test_pop_1 +# adding_to.pop() # Second action covered by test_pop_2 +# adding_to.pop() +# with pytest.raises(IndexError): +# adding_to.pop() + + + +def test_shift(): + """Fill a DoubleLinkList with values, check pop off each one, then + test that one more pop raises IndexError""" + adding_to = DoubleLinkList(()) + for i in xrange(40): + adding_to.insert(i) + # now pop'ing off each value successively + for i in xrange(40): + assert adding_to.shift() == i + with pytest.raises(IndexError): + adding_to.shift() + + +# # -*- coding: utf-8 -*- +# from __future__ import unicode_literals +# import pytest + +# from queue import Queue + +# # (Input, expected) for well constructed instantiation arguments, +# # and one subsequent dequeue +# valid_constructor_args_dequeue = [ +# ([1, 2, 3], 1), +# ([[1, 2, 3], "string"], [1, 2, 3]), +# ("string", 's') +# ] + +# # Invalid instantiation arguments +# invalid_constructor_args = [ +# (None), +# (1), +# (4.5235) +# ] + + +# @pytest.mark.parametrize("input,deque", valid_constructor_args_dequeue) +# def test_valid_contructor(input, deque): +# """Test valid constructor using by dequeuing after instantiation""" +# assert Queue(input).dequeue() == deque + + +# def test_empty_constructor(): +# """Test valid empty constructor via dequeuing after instantiation""" +# with pytest.raises(IndexError): +# Queue(()).dequeue() + + +# @pytest.mark.parametrize("input", invalid_constructor_args) +# def test_invalid_constructor(input): +# """Test invalid constuctor arguments""" +# with pytest.raises(TypeError): +# Queue(input) + + +# def test_enqueue(): +# """Fill an empty contructor argument and assert len, deque)""" +# filling_this = Queue(()) +# for i in xrange(40): +# filling_this.enqueue(i) +# assert len(filling_this) == 40 +# assert filling_this.dequeue() == 0 + + +# def test_dequeue(): +# q = Queue([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) +# for x in range(5): +# q.dequeue() +# assert q.dequeue() == 6 +# assert q.other.header.val == 7 +# assert q.other.tail.val == 10 +# assert len(q) == 4 +# while len(q): +# q.dequeue() +# assert q.other.header is None +# assert q.other.tail is None + + + + +# ******** +# @pytest.fixture +# def base_llist(): +# return ll.LinkedList([1, 2, 3]) + + +# def test_construct_from_iterable_valid(base_llist): +# expected_output = "(1, 2, 3)" +# assert base_llist.display() == expected_output + + +# def test_construct_from_nested_iterable_valid(): +# arg = ([1, 2, 3], 'string') +# expected_output = "([1, 2, 3], u'string')" +# assert ll.LinkedList(arg).__repr__() == expected_output + + +# def test_construct_from_string_valid(): +# arg = "string" +# expected_output = "(u's', u't', u'r', u'i', u'n', u'g')" +# assert ll.LinkedList(arg).__repr__() == expected_output + + +# def test_construct_empty_valid(): +# expected_output = "()" +# assert ll.LinkedList().__repr__() == expected_output + + +# def test_construct_from_none_fails(): +# with pytest.raises(TypeError): +# ll.LinkedList(None) + + +# def test_construct_from_single_integer_fails(): +# with pytest.raises(TypeError): +# ll.LinkedList(2) + + +# def test_insert_single_value(base_llist): +# base_llist.insert(4) +# assert base_llist.__repr__() == "(4, 1, 2, 3)" + + +# def test_pop(base_llist): +# assert base_llist.pop() == 1 +# assert base_llist.__repr__() == "(2, 3)" + + +# def test_size(base_llist): +# assert base_llist.size() == 3 + + +# def test_search_val(base_llist): +# searched_node = base_llist.search(2) +# assert isinstance(searched_node, ll.Node) +# assert searched_node.val == 2 + + +# def test_remove_node(base_llist): +# base_llist.remove(base_llist.search(2)) +# assert base_llist.__repr__() == "(1, 3)" + + +# def test_display(base_llist): +# assert base_llist.display() == "(1, 2, 3)" From 2fcb0ff57cf811a2b2ba83935e01e79855208329 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Tue, 30 Jun 2015 19:12:46 -0700 Subject: [PATCH 059/330] Tests finished; now inlcudes remove() tests; also added ValueError exception to DoubleLinkList for remove(val) where val isn't present --- double_link_list.py | 3 + test_double_link_list.py | 177 +++++++-------------------------------- 2 files changed, 33 insertions(+), 147 deletions(-) diff --git a/double_link_list.py b/double_link_list.py index c06613a..9cff55a 100644 --- a/double_link_list.py +++ b/double_link_list.py @@ -86,6 +86,9 @@ def remove(self, search_val): node.next.prev = node.prev self.length -= 1 return None + if True: + # search value doesn't exist in DoubleLinkList + raise ValueError def insert(self, val): """Insert value at head of list. diff --git a/test_double_link_list.py b/test_double_link_list.py index d24195f..b1891ee 100644 --- a/test_double_link_list.py +++ b/test_double_link_list.py @@ -170,161 +170,44 @@ def test_shift_3(): assert adding_to.shift() == 2 -# def test_pop_4(): -# """Fill a DoubleLinkList with (1,2,3), check fourth pop""" -# adding_to = DoubleLinkList(()) -# for i in xrange(3): -# adding_to.insert(i) - -# adding_to.pop() # First action covered by test_pop_1 -# adding_to.pop() # Second action covered by test_pop_2 -# adding_to.pop() -# with pytest.raises(IndexError): -# adding_to.pop() - - - -def test_shift(): - """Fill a DoubleLinkList with values, check pop off each one, then - test that one more pop raises IndexError""" +def test_remove_0(): + """Fill a DoubleLinkList with (1,2,3), check removal of 0""" adding_to = DoubleLinkList(()) - for i in xrange(40): + for i in xrange(3): adding_to.insert(i) - # now pop'ing off each value successively - for i in xrange(40): - assert adding_to.shift() == i - with pytest.raises(IndexError): - adding_to.shift() - - -# # -*- coding: utf-8 -*- -# from __future__ import unicode_literals -# import pytest - -# from queue import Queue - -# # (Input, expected) for well constructed instantiation arguments, -# # and one subsequent dequeue -# valid_constructor_args_dequeue = [ -# ([1, 2, 3], 1), -# ([[1, 2, 3], "string"], [1, 2, 3]), -# ("string", 's') -# ] - -# # Invalid instantiation arguments -# invalid_constructor_args = [ -# (None), -# (1), -# (4.5235) -# ] - - -# @pytest.mark.parametrize("input,deque", valid_constructor_args_dequeue) -# def test_valid_contructor(input, deque): -# """Test valid constructor using by dequeuing after instantiation""" -# assert Queue(input).dequeue() == deque - - -# def test_empty_constructor(): -# """Test valid empty constructor via dequeuing after instantiation""" -# with pytest.raises(IndexError): -# Queue(()).dequeue() - - -# @pytest.mark.parametrize("input", invalid_constructor_args) -# def test_invalid_constructor(input): -# """Test invalid constuctor arguments""" -# with pytest.raises(TypeError): -# Queue(input) - - -# def test_enqueue(): -# """Fill an empty contructor argument and assert len, deque)""" -# filling_this = Queue(()) -# for i in xrange(40): -# filling_this.enqueue(i) -# assert len(filling_this) == 40 -# assert filling_this.dequeue() == 0 - - -# def test_dequeue(): -# q = Queue([1, 2, 3, 4, 5, 6, 7, 8, 9, 10]) -# for x in range(5): -# q.dequeue() -# assert q.dequeue() == 6 -# assert q.other.header.val == 7 -# assert q.other.tail.val == 10 -# assert len(q) == 4 -# while len(q): -# q.dequeue() -# assert q.other.header is None -# assert q.other.tail is None - - - - -# ******** -# @pytest.fixture -# def base_llist(): -# return ll.LinkedList([1, 2, 3]) - - -# def test_construct_from_iterable_valid(base_llist): -# expected_output = "(1, 2, 3)" -# assert base_llist.display() == expected_output - -# def test_construct_from_nested_iterable_valid(): -# arg = ([1, 2, 3], 'string') -# expected_output = "([1, 2, 3], u'string')" -# assert ll.LinkedList(arg).__repr__() == expected_output - - -# def test_construct_from_string_valid(): -# arg = "string" -# expected_output = "(u's', u't', u'r', u'i', u'n', u'g')" -# assert ll.LinkedList(arg).__repr__() == expected_output - - -# def test_construct_empty_valid(): -# expected_output = "()" -# assert ll.LinkedList().__repr__() == expected_output - - -# def test_construct_from_none_fails(): -# with pytest.raises(TypeError): -# ll.LinkedList(None) - - -# def test_construct_from_single_integer_fails(): -# with pytest.raises(TypeError): -# ll.LinkedList(2) - - -# def test_insert_single_value(base_llist): -# base_llist.insert(4) -# assert base_llist.__repr__() == "(4, 1, 2, 3)" - - -# def test_pop(base_llist): -# assert base_llist.pop() == 1 -# assert base_llist.__repr__() == "(2, 3)" + adding_to.remove(0) + assert adding_to.pop() == 2 + assert adding_to.shift() == 1 -# def test_size(base_llist): -# assert base_llist.size() == 3 +def test_remove_1(): + """Fill a DoubleLinkList with (1,2,3), check removal of 1""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + adding_to.remove(1) + assert adding_to.pop() == 2 + assert adding_to.shift() == 0 -# def test_search_val(base_llist): -# searched_node = base_llist.search(2) -# assert isinstance(searched_node, ll.Node) -# assert searched_node.val == 2 +def test_remove_2(): + """Fill a DoubleLinkList with (1,2,3), check removal of 3""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) -# def test_remove_node(base_llist): -# base_llist.remove(base_llist.search(2)) -# assert base_llist.__repr__() == "(1, 3)" + adding_to.remove(2) + assert adding_to.pop() == 1 + assert adding_to.shift() == 0 -# def test_display(base_llist): -# assert base_llist.display() == "(1, 2, 3)" +def test_remove_5(): + """Fill a DoubleLinkList with (1,2,3), check removal of 5; + should result in ValueError""" + adding_to = DoubleLinkList(()) + for i in xrange(3): + adding_to.insert(i) + with pytest.raises(ValueError): + adding_to.remove(5) From 2d48426837d79f5b9b0f6484758b0dfed0d61036 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 30 Jun 2015 22:22:44 -0700 Subject: [PATCH 060/330] Improve docstrings, exceptions; fix none case for remove method. --- double_link_list.py | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/double_link_list.py b/double_link_list.py index 9cff55a..0e70e69 100644 --- a/double_link_list.py +++ b/double_link_list.py @@ -65,19 +65,26 @@ def search(self, search_val): return None def remove(self, search_val): - """Remove given node from list, return None. + """Remove the first node from list matching the search value. args: - search_node: the node to be removed + search_val: the val to be removed """ search_node = self.search(search_val) + if search_node == self.head: self.head = search_node.next - self.head.prev = None + try: + self.head.prev = None + except AttributeError: + pass self.length -= 1 elif search_node == self.tail: self.tail = search_node.prev - self.tail.next = None + try: + self.tail.next = None + except AttributeError: + pass self.length -= 1 else: for node in self: @@ -86,9 +93,7 @@ def remove(self, search_val): node.next.prev = node.prev self.length -= 1 return None - if True: - # search value doesn't exist in DoubleLinkList - raise ValueError + raise ValueError('value not in list') def insert(self, val): """Insert value at head of list. @@ -123,7 +128,7 @@ def append(self, val): def pop(self): """Pop the first val off the head and return it.""" if self.head is None: - raise IndexError + raise IndexError('pop from empty list') else: to_return = self.head self.head = to_return.next @@ -137,7 +142,7 @@ def pop(self): def shift(self): """Remove the last value from the tail and return.""" if self.tail is None: - raise IndexError + raise IndexError('pop from empty list') else: to_return = self.tail self.tail = to_return.prev From 779b54f18e1f91ebc026152b24d24991d862fd4e Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 1 Jul 2015 14:01:33 -0700 Subject: [PATCH 061/330] Add initial files --- binary_heap.py | 0 test_binary_heap.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 binary_heap.py create mode 100644 test_binary_heap.py diff --git a/binary_heap.py b/binary_heap.py new file mode 100644 index 0000000..e69de29 diff --git a/test_binary_heap.py b/test_binary_heap.py new file mode 100644 index 0000000..e69de29 From 6282282dc77e8940f4cd13d17846272a7cfc36ea Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 1 Jul 2015 14:22:35 -0700 Subject: [PATCH 062/330] Sketch methods and docstrings --- binary_heap.py | 65 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/binary_heap.py b/binary_heap.py index e69de29..2298a14 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -0,0 +1,65 @@ +from __future__ import unicode_literals + + +class BinaryHeap(object): + """A class for a binary heap.""" + def __init__(self, arg): + pass + + def __repr__(): + pass + + def pop(): + """Pop the head from the heap and return.""" + pass + + def push(value): + """Push a value onto a stack. + + args: + value: the value to add + """ + pass + + def bubbleup(): + """Perform a heap sort from end of tree upwards.""" + pass + + def bubbledown(): + """Perform a heap sort from end of tree downwards.""" + pass + + def find_parent(index): + """Returns the index of the parent on the tree. + + args: + index: the index to inspect from + + Returns: index of the parent + """ + pass + + def find_left_child(index): + """Returns the index of the left child. + + args: + index: the index to inspect from + + Returns: index of the left child + """ + pass + + def compare_values(child_index, parent_index, max=True): + """Compares the values of child and parent. + + args: + child_index: the index of the child + parent_index: the index of the parent + max: heap type, defaults to True + Returns: True child value is *greater* than parent value # True means pair must swap + """ + pass + + def swap_values(index1, index2): + """Swaps the values of the two indexed positions.""" + pass From 6e3c86d8bf8d23ecf2e1978ae7450b995f30ac98 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 1 Jul 2015 14:38:13 -0700 Subject: [PATCH 063/330] Improve docstrings --- binary_heap.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 2298a14..6dbe2c5 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -49,14 +49,18 @@ def find_left_child(index): """ pass - def compare_values(child_index, parent_index, max=True): - """Compares the values of child and parent. + def compare_values(child_index, parent_index, min=True): + """Compares the values of child and parent according to heap type. + + For a minheap, checks if child value is greater than parent value. + For a maxheap, checks if child value is less than parent value. args: child_index: the index of the child parent_index: the index of the parent - max: heap type, defaults to True - Returns: True child value is *greater* than parent value # True means pair must swap + min: heap type comparison, defaults to minheap + + Returns: True if heap type comparison matches """ pass From d1be6c935d23f2139502c2506e69d689e6baeaf9 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Wed, 1 Jul 2015 15:26:38 -0700 Subject: [PATCH 064/330] Adding a few initial push/pop tests --- test_binary_heap.py | 121 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) diff --git a/test_binary_heap.py b/test_binary_heap.py index e69de29..0254b3a 100644 --- a/test_binary_heap.py +++ b/test_binary_heap.py @@ -0,0 +1,121 @@ +from __future__ import unicode_literals +import pytest +import binary_heap + +# (in, expected) for constructors +valid_constructor_args = [ + ["qzdfwqefnsadnfoiaweod", "a"] + [[6, 7, 9, 4, 2, 1, 56, 8, 0, 43523], 0] + [[1243.12235, 13262.3, 26523.15, 98653.234], 1243.12235] +] + +pop_constructors = [ + [5, 8, 98, 43, 21, 1, 3, 7, 0, 3, 4, 7, 2345, 4, 64], + [33, 5314, 124, 243, 234, 1324, 2, 342, 1243, 134], + ] + +invalid_constructors = [ + 8, + None, + 4.523423 + ] + + +@pytest.mark.parametrize("input, output", valid_constructor_args) +def test_valid_instantiation(input, output): + """Test instantiation by creating and doing one pop""" + heap_under_test = binary_heap(input) + assert heap_under_test.pop() == output + + +@pytest.mark.parametrize("bad_input", invalid_constructors) +def test_invalid_instantiation(bad_input): + """Test that bad by creating and doing one pop""" + with pytest.raises(TypeError): + binary_heap(bad_input) + + +def test_push1(): + """ First push single list of [9, 5, 2, 1, 0, 7] """ + heap_under_test = binary_heap() + heap_under_test.push(9) + assert heap_under_test.pop() == 9 + + +def test_push2(): + """ First push two items from list of [9, 5, 2, 1, 0, 7]; current + test for min heap """ + heap_under_test = binary_heap() + heap_under_test.push(5) + heap_under_test.push(9) + assert heap_under_test.pop() == 5 + + +def test_push3(): + """ First push three items from list of [9, 5, 2, 1, 0, 7]; current + test for min heap """ + heap_under_test = binary_heap() + heap_under_test.push(5) + heap_under_test.push(9) + heap_under_test.push(2) + assert heap_under_test.pop() == 2 + + +def test_push4(): + """ First push three items from list of [9, 5, 2, 1, 0, 7]; current + test for min heap """ + heap_under_test = binary_heap() + heap_under_test.push(5) + heap_under_test.push(9) + heap_under_test.push(2) + heap_under_test.push(1) + assert heap_under_test.pop() == 1 + + +def test_push4(): + """ First push three items from list of [9, 5, 2, 1, 0, 7]; current + test for min heap """ + heap_under_test = binary_heap() + heap_under_test.push(5) + heap_under_test.push(9) + heap_under_test.push(2) + heap_under_test.push(1) + heap_under_test.push(0) + assert heap_under_test.pop() == 0 + + +def test_push5(): + """ First push three items from list of [9, 5, 2, 1, 0, 7]; current + test for min heap """ + heap_under_test = binary_heap() + heap_under_test.push(5) + heap_under_test.push(9) + heap_under_test.push(2) + heap_under_test.push(1) + heap_under_test.push(0) + heap_under_test.push(7) + assert heap_under_test.pop() == 7 + +def test_pop1(): + # Will pass argumnents, instantiate, heap, check for expected pop + pass + + +def test_pop2(): + # Pop off of an emptied heap + pass + + +def test_pop3(): + pass + + +def test_pop4(): + pass + +def test_push(): + # Create range of numbers + # Shuffle that range + # Push each in a for loop + # Check that pop + pass From 89b99a477c860e65cb376188652ba2e514738115 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 1 Jul 2015 15:27:44 -0700 Subject: [PATCH 065/330] Work on methods --- binary_heap.py | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 6dbe2c5..ddfad01 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -3,33 +3,36 @@ class BinaryHeap(object): """A class for a binary heap.""" - def __init__(self, arg): - pass + def __init__(self, iterable=()): + self.tree = [] + for val in iterable: + self.push(val) - def __repr__(): - pass + def __repr__(self): + repr(self.tree) - def pop(): + def pop(self): """Pop the head from the heap and return.""" pass - def push(value): + def push(self, value): """Push a value onto a stack. args: value: the value to add """ - pass + self.tree.append(value) # Add protecion for different types case + self.bubbleup() - def bubbleup(): + def bubbleup(self): """Perform a heap sort from end of tree upwards.""" pass - def bubbledown(): + def bubbledown(self): """Perform a heap sort from end of tree downwards.""" pass - def find_parent(index): + def find_parent(self, index): """Returns the index of the parent on the tree. args: @@ -37,9 +40,10 @@ def find_parent(index): Returns: index of the parent """ - pass + parent_index = (index - 1) // 2 + return parent_index - def find_left_child(index): + def find_left_child(self, index): """Returns the index of the left child. args: @@ -49,7 +53,7 @@ def find_left_child(index): """ pass - def compare_values(child_index, parent_index, min=True): + def compare_values(self, child_index, parent_index, min=True): """Compares the values of child and parent according to heap type. For a minheap, checks if child value is greater than parent value. @@ -64,6 +68,6 @@ def compare_values(child_index, parent_index, min=True): """ pass - def swap_values(index1, index2): + def swap_values(self, ind1, ind2): """Swaps the values of the two indexed positions.""" - pass + self.tree[ind1], self.tree[ind2] = (self.tree[ind2], self.tree[ind1]) From eb0f069e6a0225961a4d1b34af4adeefffcb7713 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Wed, 1 Jul 2015 15:29:00 -0700 Subject: [PATCH 066/330] resolving some name conflicts --- test_binary_heap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_binary_heap.py b/test_binary_heap.py index 0254b3a..35f8d15 100644 --- a/test_binary_heap.py +++ b/test_binary_heap.py @@ -72,7 +72,7 @@ def test_push4(): assert heap_under_test.pop() == 1 -def test_push4(): +def test_push5(): """ First push three items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ heap_under_test = binary_heap() @@ -84,7 +84,7 @@ def test_push4(): assert heap_under_test.pop() == 0 -def test_push5(): +def test_push6(): """ First push three items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ heap_under_test = binary_heap() From c06552691dab811c209c121c6abc7aff52105335 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 1 Jul 2015 15:44:26 -0700 Subject: [PATCH 067/330] Continue work on methods --- binary_heap.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index ddfad01..b118652 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -51,9 +51,10 @@ def find_left_child(self, index): Returns: index of the left child """ - pass + left_child_index = (index * 2) + 1 + return left_child_index - def compare_values(self, child_index, parent_index, min=True): + def compare_values(self, child_index, parent_index, minheap=True): """Compares the values of child and parent according to heap type. For a minheap, checks if child value is greater than parent value. @@ -66,7 +67,12 @@ def compare_values(self, child_index, parent_index, min=True): Returns: True if heap type comparison matches """ - pass + child_value = self.tree[child_index].val + parent_value = self.tree[parent_index].val + if minheap is True: + return child_value > parent_value + else: + return child_value < parent_value def swap_values(self, ind1, ind2): """Swaps the values of the two indexed positions.""" From 1713b270eae9ce010bc366b9ce5874e4d721943c Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 1 Jul 2015 15:56:17 -0700 Subject: [PATCH 068/330] Add first pass pop method --- binary_heap.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/binary_heap.py b/binary_heap.py index b118652..3867ae6 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -11,9 +11,16 @@ def __init__(self, iterable=()): def __repr__(self): repr(self.tree) + def __len__(self): + len(self.tree) + def pop(self): """Pop the head from the heap and return.""" - pass + if len(self) == 1: + return self.tree.pop() + else: + self.swap_values(self.tree[0], self.tree[1]) + return self.tree.pop() # Should naturally raise error on empty def push(self, value): """Push a value onto a stack. From 09cff979d30e395574e5d23e47434c48b8ee85eb Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 1 Jul 2015 15:58:49 -0700 Subject: [PATCH 069/330] Update pop method with bubbledown sort --- binary_heap.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 3867ae6..1977d2d 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -17,10 +17,14 @@ def __len__(self): def pop(self): """Pop the head from the heap and return.""" if len(self) == 1: - return self.tree.pop() + to_return = self.tree.pop() + self.bubbledown() + return to_return else: self.swap_values(self.tree[0], self.tree[1]) - return self.tree.pop() # Should naturally raise error on empty + to_return = self.tree.pop() # Should raise error on empty + self.bubbledown() + return to_return def push(self, value): """Push a value onto a stack. From 6bb6ce3090dce4ea64476ffaed11360de0035e0f Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 1 Jul 2015 16:48:23 -0700 Subject: [PATCH 070/330] First pass on bubble down; needs to compare vs right node --- binary_heap.py | 24 +++++++++++++++++++----- 1 file changed, 19 insertions(+), 5 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 1977d2d..28488d9 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -14,6 +14,9 @@ def __repr__(self): def __len__(self): len(self.tree) + def __iter__(): + pass + def pop(self): """Pop the head from the heap and return.""" if len(self) == 1: @@ -39,9 +42,22 @@ def bubbleup(self): """Perform a heap sort from end of tree upwards.""" pass - def bubbledown(self): + def bubbledown(self, index=0): """Perform a heap sort from end of tree downwards.""" - pass + parent = self.tree[index] + left_child_index = self.find_left_child(parent) + right_child_index = left_child_index + 1 + try: + left_child = self.tree[left_child_index] + except IndexError: + return + try: + right_child = self.tree[right_child_index] + except IndexError: + pass + if self.compare_values(parent_value=parent, child_value=left_child): + self.swap_values(parent, left_child) + self.bubbledown(index=left_child_index) def find_parent(self, index): """Returns the index of the parent on the tree. @@ -65,7 +81,7 @@ def find_left_child(self, index): left_child_index = (index * 2) + 1 return left_child_index - def compare_values(self, child_index, parent_index, minheap=True): + def compare_values(self, parent_value=None, child_value=None, minheap=True): """Compares the values of child and parent according to heap type. For a minheap, checks if child value is greater than parent value. @@ -78,8 +94,6 @@ def compare_values(self, child_index, parent_index, minheap=True): Returns: True if heap type comparison matches """ - child_value = self.tree[child_index].val - parent_value = self.tree[parent_index].val if minheap is True: return child_value > parent_value else: From 6fa04a145058a9ac1f4948114e6105b0cafeefd1 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 1 Jul 2015 17:10:02 -0700 Subject: [PATCH 071/330] Complete first pass of functions --- binary_heap.py | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 28488d9..793d938 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -49,15 +49,22 @@ def bubbledown(self, index=0): right_child_index = left_child_index + 1 try: left_child = self.tree[left_child_index] + try: + right_child = self.tree[right_child_index] + except IndexError: + if self.compare_values(parent_value=parent, child_value=left_child): + self.swap_values(parent, left_child) + if left_child < right_child: + target_child = left_child + target_child_index = left_child_index + else: + target_child = right_child + target_child_index = right_child_index + self.compare_values(parent_value=parent, child_value=target_child) + self.swap_values(parent, target_child) + self.bubbledown(index=target_child_index) except IndexError: return - try: - right_child = self.tree[right_child_index] - except IndexError: - pass - if self.compare_values(parent_value=parent, child_value=left_child): - self.swap_values(parent, left_child) - self.bubbledown(index=left_child_index) def find_parent(self, index): """Returns the index of the parent on the tree. From 9b51840d87720b07c2420504bd19b34d07a01996 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 1 Jul 2015 17:26:55 -0700 Subject: [PATCH 072/330] Save work for the day --- binary_heap.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 793d938..047d2be 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -21,7 +21,6 @@ def pop(self): """Pop the head from the heap and return.""" if len(self) == 1: to_return = self.tree.pop() - self.bubbledown() return to_return else: self.swap_values(self.tree[0], self.tree[1]) @@ -36,11 +35,20 @@ def push(self, value): value: the value to add """ self.tree.append(value) # Add protecion for different types case - self.bubbleup() + if len(self.tree) > 1: + self.bubbleup(len(self.tree)-1) - def bubbleup(self): + def bubbleup(self, index): """Perform a heap sort from end of tree upwards.""" - pass + parent_index = self.find_parent(index) + try: + parent_value = self.tree[parent_index] + except IndexError: + return + child = self.tree[index] + if self.compare_values(parent_value=parent_value, child_value=child): + self.swap_values(parent_value, child) + self.bubbleup(parent_index) def bubbledown(self, index=0): """Perform a heap sort from end of tree downwards.""" From 41c67263b8463770caa1f3026b5f734f0f883e49 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Thu, 2 Jul 2015 13:57:41 -0700 Subject: [PATCH 073/330] Can instantiate BinaryHeap; bubbleup and bubbledown aren't working yet --- binary_heap.py | 43 ++++++++++++++++++++++++------------------- test_binary_heap.py | 23 ++++++++++++----------- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 047d2be..0263f15 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -6,10 +6,11 @@ class BinaryHeap(object): def __init__(self, iterable=()): self.tree = [] for val in iterable: + import pdb; pdb.set_trace() self.push(val) def __repr__(self): - repr(self.tree) + return repr(self.tree) def __len__(self): len(self.tree) @@ -19,11 +20,11 @@ def __iter__(): def pop(self): """Pop the head from the heap and return.""" - if len(self) == 1: + if len(self.tree) == 1: to_return = self.tree.pop() return to_return else: - self.swap_values(self.tree[0], self.tree[1]) + self.swap_values(0, len(self.tree) - 1) # Swap values at end of tree with start to_return = self.tree.pop() # Should raise error on empty self.bubbledown() return to_return @@ -46,31 +47,34 @@ def bubbleup(self, index): except IndexError: return child = self.tree[index] - if self.compare_values(parent_value=parent_value, child_value=child): - self.swap_values(parent_value, child) + if child < parent_value: + self.swap_values(parent_index, index) self.bubbleup(parent_index) def bubbledown(self, index=0): """Perform a heap sort from end of tree downwards.""" - parent = self.tree[index] - left_child_index = self.find_left_child(parent) + parent_value = self.tree[index] + left_child_index = self.find_left_child(index) right_child_index = left_child_index + 1 try: left_child = self.tree[left_child_index] try: right_child = self.tree[right_child_index] - except IndexError: - if self.compare_values(parent_value=parent, child_value=left_child): - self.swap_values(parent, left_child) - if left_child < right_child: - target_child = left_child - target_child_index = left_child_index - else: - target_child = right_child - target_child_index = right_child_index - self.compare_values(parent_value=parent, child_value=target_child) - self.swap_values(parent, target_child) - self.bubbledown(index=target_child_index) + except IndexError: # Case of left_child only + if left_child < parent_value: + self.swap_values(index, left_child_index) + self.bubbledown(index=left_child_index) + else: # Case of left_child and right_child + if left_child < right_child: + target_child = left_child + target_child_index = left_child_index + else: + target_child = right_child + target_child_index = right_child_index + if target_child < parent_value: + self.swap_values(index, target_child_index) + self.bubbledown(index=target_child_index) + except IndexError: return @@ -96,6 +100,7 @@ def find_left_child(self, index): left_child_index = (index * 2) + 1 return left_child_index + def compare_values(self, parent_value=None, child_value=None, minheap=True): """Compares the values of child and parent according to heap type. diff --git a/test_binary_heap.py b/test_binary_heap.py index 35f8d15..8fc6865 100644 --- a/test_binary_heap.py +++ b/test_binary_heap.py @@ -1,11 +1,11 @@ from __future__ import unicode_literals import pytest -import binary_heap +from binary_heap import BinaryHeap # (in, expected) for constructors valid_constructor_args = [ - ["qzdfwqefnsadnfoiaweod", "a"] - [[6, 7, 9, 4, 2, 1, 56, 8, 0, 43523], 0] + ["qzdfwqefnsadnfoiaweod", "a"], + [[6, 7, 9, 4, 2, 1, 56, 8, 0, 43523], 0], [[1243.12235, 13262.3, 26523.15, 98653.234], 1243.12235] ] @@ -24,7 +24,7 @@ @pytest.mark.parametrize("input, output", valid_constructor_args) def test_valid_instantiation(input, output): """Test instantiation by creating and doing one pop""" - heap_under_test = binary_heap(input) + heap_under_test = BinaryHeap(input) assert heap_under_test.pop() == output @@ -32,12 +32,12 @@ def test_valid_instantiation(input, output): def test_invalid_instantiation(bad_input): """Test that bad by creating and doing one pop""" with pytest.raises(TypeError): - binary_heap(bad_input) + BinaryHeap(bad_input) def test_push1(): """ First push single list of [9, 5, 2, 1, 0, 7] """ - heap_under_test = binary_heap() + heap_under_test = BinaryHeap() heap_under_test.push(9) assert heap_under_test.pop() == 9 @@ -45,7 +45,7 @@ def test_push1(): def test_push2(): """ First push two items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ - heap_under_test = binary_heap() + heap_under_test = BinaryHeap() heap_under_test.push(5) heap_under_test.push(9) assert heap_under_test.pop() == 5 @@ -54,7 +54,7 @@ def test_push2(): def test_push3(): """ First push three items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ - heap_under_test = binary_heap() + heap_under_test = BinaryHeap() heap_under_test.push(5) heap_under_test.push(9) heap_under_test.push(2) @@ -64,7 +64,7 @@ def test_push3(): def test_push4(): """ First push three items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ - heap_under_test = binary_heap() + heap_under_test = BinaryHeap() heap_under_test.push(5) heap_under_test.push(9) heap_under_test.push(2) @@ -75,7 +75,7 @@ def test_push4(): def test_push5(): """ First push three items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ - heap_under_test = binary_heap() + heap_under_test = BinaryHeap() heap_under_test.push(5) heap_under_test.push(9) heap_under_test.push(2) @@ -87,7 +87,7 @@ def test_push5(): def test_push6(): """ First push three items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ - heap_under_test = binary_heap() + heap_under_test = BinaryHeap() heap_under_test.push(5) heap_under_test.push(9) heap_under_test.push(2) @@ -96,6 +96,7 @@ def test_push6(): heap_under_test.push(7) assert heap_under_test.pop() == 7 + def test_pop1(): # Will pass argumnents, instantiate, heap, check for expected pop pass From 2451495e50e9cb3bfce6323ecf46a46a6cdff666 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Thu, 2 Jul 2015 15:05:00 -0700 Subject: [PATCH 074/330] push() and bubbleup() are working as expect; no formal testing yet. --- binary_heap.py | 75 +++++++++++++++++++++++--------------------------- 1 file changed, 34 insertions(+), 41 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 0263f15..580a1d2 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -6,7 +6,6 @@ class BinaryHeap(object): def __init__(self, iterable=()): self.tree = [] for val in iterable: - import pdb; pdb.set_trace() self.push(val) def __repr__(self): @@ -24,7 +23,7 @@ def pop(self): to_return = self.tree.pop() return to_return else: - self.swap_values(0, len(self.tree) - 1) # Swap values at end of tree with start + self.swap_values(0, len(self.tree)-1) # Swap values at end of tree with start to_return = self.tree.pop() # Should raise error on empty self.bubbledown() return to_return @@ -39,66 +38,64 @@ def push(self, value): if len(self.tree) > 1: self.bubbleup(len(self.tree)-1) - def bubbleup(self, index): + def bubbleup(self, pos): """Perform a heap sort from end of tree upwards.""" - parent_index = self.find_parent(index) - try: - parent_value = self.tree[parent_index] - except IndexError: + parent = self.find_parent(pos) + if pos == 0: # find_parent will return -1 at end of list return - child = self.tree[index] - if child < parent_value: - self.swap_values(parent_index, index) - self.bubbleup(parent_index) + elif self.tree[pos] < self.tree[parent]: + self.tree[pos], self.tree[parent] = self.tree[parent], self.tree[pos] + self.bubbleup(parent) - def bubbledown(self, index=0): + + def bubbledown(self, pos=0): """Perform a heap sort from end of tree downwards.""" - parent_value = self.tree[index] - left_child_index = self.find_left_child(index) - right_child_index = left_child_index + 1 + parent_value = self.tree[pos] + lchild = self.find_lchild(pos) + rchild = lchild + 1 try: - left_child = self.tree[left_child_index] + left_child = self.tree[lchild] try: - right_child = self.tree[right_child_index] + right_child = self.tree[rchild] except IndexError: # Case of left_child only if left_child < parent_value: - self.swap_values(index, left_child_index) - self.bubbledown(index=left_child_index) + self.swap_values(pos, lchild) + self.bubbledown(pos=lchild) else: # Case of left_child and right_child if left_child < right_child: target_child = left_child - target_child_index = left_child_index + target_child_pos = lchild else: target_child = right_child - target_child_index = right_child_index + target_child_pos = rchild if target_child < parent_value: - self.swap_values(index, target_child_index) - self.bubbledown(index=target_child_index) + self.swap_values(pos, target_child_pos) + self.bubbledown(pos=target_child_pos) except IndexError: return - def find_parent(self, index): - """Returns the index of the parent on the tree. + def find_parent(self, pos): + """Returns the pos of the parent on the tree. args: - index: the index to inspect from + pos: the pos to inspect from - Returns: index of the parent + Returns: pos of the parent """ - parent_index = (index - 1) // 2 - return parent_index + parent = (pos - 1) // 2 + return parent - def find_left_child(self, index): - """Returns the index of the left child. + def find_lchild(self, pos): + """Returns the pos of the left child. args: - index: the index to inspect from + pos: the pos to inspect from - Returns: index of the left child + Returns: pos of the left child """ - left_child_index = (index * 2) + 1 - return left_child_index + lchild = (pos * 2) + 1 + return lchild def compare_values(self, parent_value=None, child_value=None, minheap=True): @@ -108,8 +105,8 @@ def compare_values(self, parent_value=None, child_value=None, minheap=True): For a maxheap, checks if child value is less than parent value. args: - child_index: the index of the child - parent_index: the index of the parent + child_pos: the pos of the child + parent: the pos of the parent min: heap type comparison, defaults to minheap Returns: True if heap type comparison matches @@ -118,7 +115,3 @@ def compare_values(self, parent_value=None, child_value=None, minheap=True): return child_value > parent_value else: return child_value < parent_value - - def swap_values(self, ind1, ind2): - """Swaps the values of the two indexed positions.""" - self.tree[ind1], self.tree[ind2] = (self.tree[ind2], self.tree[ind1]) From 842bd392cbcb393eb72f12d156d7462cac41d0ec Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Thu, 2 Jul 2015 15:30:35 -0700 Subject: [PATCH 075/330] Working, kinda; values get transformed when pop() --- binary_heap.py | 37 ++++++++++++++++--------------------- 1 file changed, 16 insertions(+), 21 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 580a1d2..da7ed40 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -21,12 +21,11 @@ def pop(self): """Pop the head from the heap and return.""" if len(self.tree) == 1: to_return = self.tree.pop() - return to_return else: - self.swap_values(0, len(self.tree)-1) # Swap values at end of tree with start + self.tree[0], self.tree[len(self.tree) - 1] = self.tree[len(self.tree) - 1], self.tree[0] to_return = self.tree.pop() # Should raise error on empty - self.bubbledown() - return to_return + self.bubbledown(0) + return to_return def push(self, value): """Push a value onto a stack. @@ -48,31 +47,27 @@ def bubbleup(self, pos): self.bubbleup(parent) - def bubbledown(self, pos=0): + def bubbledown(self, pos): """Perform a heap sort from end of tree downwards.""" - parent_value = self.tree[pos] lchild = self.find_lchild(pos) rchild = lchild + 1 - try: - left_child = self.tree[lchild] + try: # Evaluating whether lchild exists; may refactor + lval = self.tree[lchild] try: - right_child = self.tree[rchild] + rval = self.tree[rchild] except IndexError: # Case of left_child only - if left_child < parent_value: - self.swap_values(pos, lchild) - self.bubbledown(pos=lchild) + if lval < self.tree[pos]: + lval, self.tree[pos] = self.tree[pos], lval else: # Case of left_child and right_child - if left_child < right_child: - target_child = left_child - target_child_pos = lchild + if lval < rval: + target = lchild else: - target_child = right_child - target_child_pos = rchild - if target_child < parent_value: - self.swap_values(pos, target_child_pos) - self.bubbledown(pos=target_child_pos) + target = rchild + if self.tree[target] < self.tree[pos]: + self.tree[target], self.tree[pos] = self.tree[pos], self.tree[target] + self.bubbledown(target) - except IndexError: + except IndexError: # Case of no lchild return def find_parent(self, pos): From 67fa964b1625dc7c76f5b628cf11367755baee95 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Thu, 2 Jul 2015 16:18:54 -0700 Subject: [PATCH 076/330] Fixed variable reference problem. --- binary_heap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index da7ed40..5c4066b 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -55,9 +55,9 @@ def bubbledown(self, pos): lval = self.tree[lchild] try: rval = self.tree[rchild] - except IndexError: # Case of left_child only + except IndexError: # Case of left_child only if lval < self.tree[pos]: - lval, self.tree[pos] = self.tree[pos], lval + self.tree[lchild], self.tree[pos] = self.tree[pos], self.tree[lchild] else: # Case of left_child and right_child if lval < rval: target = lchild @@ -67,7 +67,7 @@ def bubbledown(self, pos): self.tree[target], self.tree[pos] = self.tree[pos], self.tree[target] self.bubbledown(target) - except IndexError: # Case of no lchild + except IndexError: # Case of no lchild return def find_parent(self, pos): From a443b034465da237051ca01bfa85c34839fad42e Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Thu, 2 Jul 2015 16:24:16 -0700 Subject: [PATCH 077/330] Fixed test assert --- test_binary_heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_binary_heap.py b/test_binary_heap.py index 8fc6865..2ebc5ab 100644 --- a/test_binary_heap.py +++ b/test_binary_heap.py @@ -94,7 +94,7 @@ def test_push6(): heap_under_test.push(1) heap_under_test.push(0) heap_under_test.push(7) - assert heap_under_test.pop() == 7 + assert heap_under_test.pop() == 0 def test_pop1(): From 65b44b81dc18df930034ded8a8dd1f19798cfa1d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 12:26:21 -0700 Subject: [PATCH 078/330] Add __iter__ method to binheap --- binary_heap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 5c4066b..3dcda26 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -14,8 +14,8 @@ def __repr__(self): def __len__(self): len(self.tree) - def __iter__(): - pass + def __iter__(self): + return iter(self.tree) def pop(self): """Pop the head from the heap and return.""" From f04db589b06659ff9cad82f33db97098cf43b5b9 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 12:32:02 -0700 Subject: [PATCH 079/330] Fix __len__ method --- binary_heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binary_heap.py b/binary_heap.py index 3dcda26..672ed26 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -12,7 +12,7 @@ def __repr__(self): return repr(self.tree) def __len__(self): - len(self.tree) + return len(self.tree) def __iter__(self): return iter(self.tree) From 6d0ad12a2fbd6a04691b6af808df2f55ebfbe290 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 12:38:01 -0700 Subject: [PATCH 080/330] Mark private methods --- binary_heap.py | 32 +++++++++++++++----------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 672ed26..c4c63ea 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -24,7 +24,7 @@ def pop(self): else: self.tree[0], self.tree[len(self.tree) - 1] = self.tree[len(self.tree) - 1], self.tree[0] to_return = self.tree.pop() # Should raise error on empty - self.bubbledown(0) + self._bubbledown(0) return to_return def push(self, value): @@ -35,42 +35,41 @@ def push(self, value): """ self.tree.append(value) # Add protecion for different types case if len(self.tree) > 1: - self.bubbleup(len(self.tree)-1) + self._bubbleup(len(self.tree)-1) - def bubbleup(self, pos): + def _bubbleup(self, pos): """Perform a heap sort from end of tree upwards.""" - parent = self.find_parent(pos) - if pos == 0: # find_parent will return -1 at end of list + parent = self._find_parent(pos) + if pos == 0: # find_parent will return -1 at end of list return elif self.tree[pos] < self.tree[parent]: self.tree[pos], self.tree[parent] = self.tree[parent], self.tree[pos] - self.bubbleup(parent) + self._bubbleup(parent) - - def bubbledown(self, pos): + def _bubbledown(self, pos): """Perform a heap sort from end of tree downwards.""" - lchild = self.find_lchild(pos) + lchild = self._find_lchild(pos) rchild = lchild + 1 - try: # Evaluating whether lchild exists; may refactor + try: # Evaluating whether lchild exists; may refactor lval = self.tree[lchild] try: rval = self.tree[rchild] - except IndexError: # Case of left_child only + except IndexError: # Case of left_child only if lval < self.tree[pos]: self.tree[lchild], self.tree[pos] = self.tree[pos], self.tree[lchild] - else: # Case of left_child and right_child + else: # Case of left_child and right_child if lval < rval: target = lchild else: target = rchild if self.tree[target] < self.tree[pos]: self.tree[target], self.tree[pos] = self.tree[pos], self.tree[target] - self.bubbledown(target) + self._bubbledown(target) - except IndexError: # Case of no lchild + except IndexError: # Case of no lchild return - def find_parent(self, pos): + def _find_parent(self, pos): """Returns the pos of the parent on the tree. args: @@ -81,7 +80,7 @@ def find_parent(self, pos): parent = (pos - 1) // 2 return parent - def find_lchild(self, pos): + def _find_lchild(self, pos): """Returns the pos of the left child. args: @@ -92,7 +91,6 @@ def find_lchild(self, pos): lchild = (pos * 2) + 1 return lchild - def compare_values(self, parent_value=None, child_value=None, minheap=True): """Compares the values of child and parent according to heap type. From 59208e69c6c815d0d3fd2f307a87d8c7eb88e7f1 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 12:47:15 -0700 Subject: [PATCH 081/330] Upate docstrings --- binary_heap.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index c4c63ea..efa0904 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -38,7 +38,11 @@ def push(self, value): self._bubbleup(len(self.tree)-1) def _bubbleup(self, pos): - """Perform a heap sort from end of tree upwards.""" + """Perform one step of heap sort up the tree. + + args: + pos: the index position to inspect + """ parent = self._find_parent(pos) if pos == 0: # find_parent will return -1 at end of list return @@ -47,7 +51,11 @@ def _bubbleup(self, pos): self._bubbleup(parent) def _bubbledown(self, pos): - """Perform a heap sort from end of tree downwards.""" + """Perform one step of heap sort down the tree. + + args: + pos: the index position to inspect + """ lchild = self._find_lchild(pos) rchild = lchild + 1 try: # Evaluating whether lchild exists; may refactor @@ -70,23 +78,23 @@ def _bubbledown(self, pos): return def _find_parent(self, pos): - """Returns the pos of the parent on the tree. + """Returns the parent index of given position. args: - pos: the pos to inspect from + pos: the index position to inspect - Returns: pos of the parent + Returns: index of the parent """ parent = (pos - 1) // 2 return parent def _find_lchild(self, pos): - """Returns the pos of the left child. + """Returns the left child index of given position. args: - pos: the pos to inspect from + pos: the index position to inspect - Returns: pos of the left child + Returns: index of the left child """ lchild = (pos * 2) + 1 return lchild From fc6694686b5b928580c3e8d682b3b6496b12d006 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 12:55:26 -0700 Subject: [PATCH 082/330] Refactor pop method --- binary_heap.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index efa0904..0cdc21a 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -19,11 +19,11 @@ def __iter__(self): def pop(self): """Pop the head from the heap and return.""" - if len(self.tree) == 1: + if len(self.tree) <= 1: to_return = self.tree.pop() else: self.tree[0], self.tree[len(self.tree) - 1] = self.tree[len(self.tree) - 1], self.tree[0] - to_return = self.tree.pop() # Should raise error on empty + to_return = self.tree.pop() self._bubbledown(0) return to_return From 8c9c938e8cf7080861de051f641b8981397c74f2 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 13:13:15 -0700 Subject: [PATCH 083/330] Refactor utility methods --- binary_heap.py | 47 +++++++++++++++++++++++++++++------------------ 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 0cdc21a..97d36ae 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -3,8 +3,9 @@ class BinaryHeap(object): """A class for a binary heap.""" - def __init__(self, iterable=()): + def __init__(self, iterable=(), heaptype='minheap'): self.tree = [] + self.heaptype = heaptype for val in iterable: self.push(val) @@ -56,8 +57,7 @@ def _bubbledown(self, pos): args: pos: the index position to inspect """ - lchild = self._find_lchild(pos) - rchild = lchild + 1 + lchild, rchild = self._find_children(pos) try: # Evaluating whether lchild exists; may refactor lval = self.tree[lchild] try: @@ -88,31 +88,42 @@ def _find_parent(self, pos): parent = (pos - 1) // 2 return parent - def _find_lchild(self, pos): - """Returns the left child index of given position. + def _find_children(self, pos): + """Returns the indexes of children from given position. args: pos: the index position to inspect - Returns: index of the left child + Returns: index of left child and right child """ lchild = (pos * 2) + 1 - return lchild + rchild = lchild + 1 + return lchild, rchild - def compare_values(self, parent_value=None, child_value=None, minheap=True): - """Compares the values of child and parent according to heap type. + def _is_unsorted(self, val1, val2): + """Compare two values according to heaptype. - For a minheap, checks if child value is greater than parent value. - For a maxheap, checks if child value is less than parent value. + For a minheap, checks if first value is less than second value. + For a maxheap, checks if first value is greater than second value. args: - child_pos: the pos of the child - parent: the pos of the parent - min: heap type comparison, defaults to minheap + val1: first value + val2: second value - Returns: True if heap type comparison matches + Returns: True if heaptype comparison matches, else False """ - if minheap is True: - return child_value > parent_value + if self.heaptype == 'minheap': + return val1 < val2 + elif self.heaptype == 'maxheap': + return val1 > val2 else: - return child_value < parent_value + raise AttributeError('heaptype not assigned') + + def _swap(self, pos1, pos2): + """Swap the values at to index positions. + + args: + pos1: the index of the first item + pos2: the index of the second item' + """ + self.tree[pos1], self.tree[pos2] = self.tree[pos2], self.tree[pos1] From c293d9e1c1402d5e4b7b926a6a4b7ef0106fc214 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 13:20:59 -0700 Subject: [PATCH 084/330] Refactor using _swap method for readability --- binary_heap.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 97d36ae..693359d 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -23,7 +23,8 @@ def pop(self): if len(self.tree) <= 1: to_return = self.tree.pop() else: - self.tree[0], self.tree[len(self.tree) - 1] = self.tree[len(self.tree) - 1], self.tree[0] + endpos = len(self.tree) - 1 + self._swap(0, endpos) to_return = self.tree.pop() self._bubbledown(0) return to_return @@ -36,7 +37,8 @@ def push(self, value): """ self.tree.append(value) # Add protecion for different types case if len(self.tree) > 1: - self._bubbleup(len(self.tree)-1) + endpos = len(self.tree) - 1 + self._bubbleup(endpos) def _bubbleup(self, pos): """Perform one step of heap sort up the tree. @@ -48,7 +50,7 @@ def _bubbleup(self, pos): if pos == 0: # find_parent will return -1 at end of list return elif self.tree[pos] < self.tree[parent]: - self.tree[pos], self.tree[parent] = self.tree[parent], self.tree[pos] + self._swap(pos, parent) self._bubbleup(parent) def _bubbledown(self, pos): @@ -64,14 +66,14 @@ def _bubbledown(self, pos): rval = self.tree[rchild] except IndexError: # Case of left_child only if lval < self.tree[pos]: - self.tree[lchild], self.tree[pos] = self.tree[pos], self.tree[lchild] + self._swap(lchild, pos) else: # Case of left_child and right_child if lval < rval: target = lchild else: target = rchild if self.tree[target] < self.tree[pos]: - self.tree[target], self.tree[pos] = self.tree[pos], self.tree[target] + self._swap(target, pos) self._bubbledown(target) except IndexError: # Case of no lchild From 9ddb794d37054ba9592fcb8a9f9551063432f2f7 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 13:31:57 -0700 Subject: [PATCH 085/330] Refactor with _is_sorted method to allow max or minheap --- binary_heap.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 693359d..abc31a0 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -49,7 +49,7 @@ def _bubbleup(self, pos): parent = self._find_parent(pos) if pos == 0: # find_parent will return -1 at end of list return - elif self.tree[pos] < self.tree[parent]: + elif self._is_unsorted(self.tree[pos], self.tree[parent]): self._swap(pos, parent) self._bubbleup(parent) @@ -65,14 +65,14 @@ def _bubbledown(self, pos): try: rval = self.tree[rchild] except IndexError: # Case of left_child only - if lval < self.tree[pos]: + if self._is_unsorted(lval, self.tree[pos]): self._swap(lchild, pos) else: # Case of left_child and right_child - if lval < rval: + if self._is_unsorted(lval, rval): target = lchild else: target = rchild - if self.tree[target] < self.tree[pos]: + if self._is_unsorted(self.tree[target], self.tree[pos]): self._swap(target, pos) self._bubbledown(target) From 86bdab7160f7e9db8858fcf3fbc88bcc8ccf61db Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 13:43:44 -0700 Subject: [PATCH 086/330] Improve docstrings; change heaptype attribute name. --- binary_heap.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index abc31a0..378d73c 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -3,9 +3,15 @@ class BinaryHeap(object): """A class for a binary heap.""" - def __init__(self, iterable=(), heaptype='minheap'): + def __init__(self, iterable=(), minheap=True): + """Initializes a binary heap, optionally with items from an iterable. + + By default, the binary will sort as a minheap, with smallest values + at the head. If minheap is set to false, the binary heap with sort + as a maxheap, with largest values at the head. + """ self.tree = [] - self.heaptype = heaptype + self.minheap = minheap for val in iterable: self.push(val) @@ -114,9 +120,9 @@ def _is_unsorted(self, val1, val2): Returns: True if heaptype comparison matches, else False """ - if self.heaptype == 'minheap': + if self.minheap is True: return val1 < val2 - elif self.heaptype == 'maxheap': + elif self.minheap is False: return val1 > val2 else: raise AttributeError('heaptype not assigned') From 0783e5beeb8e79173b30a11547d305f490fa5e3d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 13:48:15 -0700 Subject: [PATCH 087/330] Fix typos --- binary_heap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 378d73c..48c4f87 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -41,7 +41,7 @@ def push(self, value): args: value: the value to add """ - self.tree.append(value) # Add protecion for different types case + self.tree.append(value) # Add protection for different types case if len(self.tree) > 1: endpos = len(self.tree) - 1 self._bubbleup(endpos) @@ -128,10 +128,10 @@ def _is_unsorted(self, val1, val2): raise AttributeError('heaptype not assigned') def _swap(self, pos1, pos2): - """Swap the values at to index positions. + """Swap the values at given index positions. args: pos1: the index of the first item - pos2: the index of the second item' + pos2: the index of the second item """ self.tree[pos1], self.tree[pos2] = self.tree[pos2], self.tree[pos1] From 0c1690ec8f0b42f41a1a39edf022f48bf07b4a39 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 14:12:19 -0700 Subject: [PATCH 088/330] Add __getitem__ method --- binary_heap.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/binary_heap.py b/binary_heap.py index 48c4f87..62024e7 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -24,6 +24,9 @@ def __len__(self): def __iter__(self): return iter(self.tree) + def __getitem__(self, item): + return self.tree[item] + def pop(self): """Pop the head from the heap and return.""" if len(self.tree) <= 1: From e5c8ddbfb4015e7675b7e87c68444e39261de089 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 14:12:41 -0700 Subject: [PATCH 089/330] Add test for maxheap sort --- test_binary_heap.py | 41 +++++++++++++++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 2 deletions(-) diff --git a/test_binary_heap.py b/test_binary_heap.py index 2ebc5ab..f2d1aca 100644 --- a/test_binary_heap.py +++ b/test_binary_heap.py @@ -9,6 +9,12 @@ [[1243.12235, 13262.3, 26523.15, 98653.234], 1243.12235] ] +valid_constructor_args_max = [ + ["qzdfwqefnsadnfoiaweod", "z"], + [[6, 7, 9, 4, 2, 1, 56, 8, 0, 43523], 43523], + [[1243.12235, 13262.3, 26523.15, 98653.234], 98653.234] +] + pop_constructors = [ [5, 8, 98, 43, 21, 1, 3, 7, 0, 3, 4, 7, 2345, 4, 64], [33, 5314, 124, 243, 234, 1324, 2, 342, 1243, 134], @@ -21,10 +27,41 @@ ] +def minheap_sorted(heap): + for i in range(len(heap)): + try: + if heap[i] > heap[(2*i + 1)]: + return False + if heap[i] > heap[(2*i) + 2]: + return False + except IndexError: + return True + + +def maxheap_sorted(heap): + for i in range(len(heap)): + try: + if heap[i] < heap[(2*i + 1)]: + return False + if heap[i] < heap[(2*i) + 2]: + return False + except IndexError: + return True + + @pytest.mark.parametrize("input, output", valid_constructor_args) -def test_valid_instantiation(input, output): +def test_valid_instantiation_min(input, output): """Test instantiation by creating and doing one pop""" heap_under_test = BinaryHeap(input) + assert minheap_sorted(heap_under_test) + assert heap_under_test.pop() == output + + +@pytest.mark.parametrize("input, output", valid_constructor_args_max) +def test_valid_instantiation_max(input, output): + """Test instantiation by creating and doing one pop""" + heap_under_test = BinaryHeap(input, minheap=False) + assert maxheap_sorted(heap_under_test) assert heap_under_test.pop() == output @@ -36,7 +73,7 @@ def test_invalid_instantiation(bad_input): def test_push1(): - """ First push single list of [9, 5, 2, 1, 0, 7] """ + """ First push single item from list of [9, 5, 2, 1, 0, 7] """ heap_under_test = BinaryHeap() heap_under_test.push(9) assert heap_under_test.pop() == 9 From 777896694d9d6d3ac17ddb92c7150027258e33d5 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 14:19:56 -0700 Subject: [PATCH 090/330] Add fixtures and helper methods to tests --- test_binary_heap.py | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/test_binary_heap.py b/test_binary_heap.py index f2d1aca..bb77f23 100644 --- a/test_binary_heap.py +++ b/test_binary_heap.py @@ -28,6 +28,11 @@ def minheap_sorted(heap): + """Confirm that heap is minheap sorted. + + Original idea from: + https://github.com/MigrantJ/data-structures/blob/binheap/binheap/binheap.py + """ for i in range(len(heap)): try: if heap[i] > heap[(2*i + 1)]: @@ -39,6 +44,7 @@ def minheap_sorted(heap): def maxheap_sorted(heap): + """Confirm that heap is maxheap sorted.""" for i in range(len(heap)): try: if heap[i] < heap[(2*i + 1)]: @@ -49,6 +55,21 @@ def maxheap_sorted(heap): return True +@pytest.fixture() +def empty_heap(): + return BinaryHeap() + + +@pytest.fixture() +def full_minheap(): + return BinaryHeap([6, 7, 9, 4, 2, 1, 56, 8, 0, 43523]) + + +@pytest.fixture() +def full_maxheap(): + return BinaryHeap([6, 7, 9, 4, 2, 1, 56, 8, 0, 43523], minheap=False) + + @pytest.mark.parametrize("input, output", valid_constructor_args) def test_valid_instantiation_min(input, output): """Test instantiation by creating and doing one pop""" From 5ba95005007da8ab6ce44b764cf65803d0354d75 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 14:43:17 -0700 Subject: [PATCH 091/330] Improve test coverage --- test_binary_heap.py | 114 ++++++++++++++++++++++---------------------- 1 file changed, 56 insertions(+), 58 deletions(-) diff --git a/test_binary_heap.py b/test_binary_heap.py index bb77f23..86410a5 100644 --- a/test_binary_heap.py +++ b/test_binary_heap.py @@ -56,17 +56,17 @@ def maxheap_sorted(heap): @pytest.fixture() -def empty_heap(): +def minheap_empty(): return BinaryHeap() @pytest.fixture() -def full_minheap(): +def minheap_full(): return BinaryHeap([6, 7, 9, 4, 2, 1, 56, 8, 0, 43523]) @pytest.fixture() -def full_maxheap(): +def maxheap_(): return BinaryHeap([6, 7, 9, 4, 2, 1, 56, 8, 0, 43523], minheap=False) @@ -93,88 +93,86 @@ def test_invalid_instantiation(bad_input): BinaryHeap(bad_input) -def test_push1(): +def test_push1(minheap_empty): """ First push single item from list of [9, 5, 2, 1, 0, 7] """ - heap_under_test = BinaryHeap() - heap_under_test.push(9) - assert heap_under_test.pop() == 9 + minheap_empty.push(9) + assert minheap_empty.pop() == 9 -def test_push2(): +def test_push2(minheap_empty): """ First push two items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ - heap_under_test = BinaryHeap() - heap_under_test.push(5) - heap_under_test.push(9) - assert heap_under_test.pop() == 5 + minheap_empty.push(5) + minheap_empty.push(9) + assert minheap_empty.pop() == 5 -def test_push3(): +def test_push3(minheap_empty): """ First push three items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ - heap_under_test = BinaryHeap() - heap_under_test.push(5) - heap_under_test.push(9) - heap_under_test.push(2) - assert heap_under_test.pop() == 2 + minheap_empty.push(5) + minheap_empty.push(9) + minheap_empty.push(2) + assert minheap_empty.pop() == 2 -def test_push4(): +def test_push4(minheap_empty): """ First push three items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ - heap_under_test = BinaryHeap() - heap_under_test.push(5) - heap_under_test.push(9) - heap_under_test.push(2) - heap_under_test.push(1) - assert heap_under_test.pop() == 1 + minheap_empty.push(5) + minheap_empty.push(9) + minheap_empty.push(2) + minheap_empty.push(1) + assert minheap_empty.pop() == 1 -def test_push5(): +def test_push5(minheap_empty): """ First push three items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ - heap_under_test = BinaryHeap() - heap_under_test.push(5) - heap_under_test.push(9) - heap_under_test.push(2) - heap_under_test.push(1) - heap_under_test.push(0) - assert heap_under_test.pop() == 0 + minheap_empty.push(5) + minheap_empty.push(9) + minheap_empty.push(2) + minheap_empty.push(1) + minheap_empty.push(0) + assert minheap_empty.pop() == 0 -def test_push6(): +def test_push6(minheap_empty): """ First push three items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ - heap_under_test = BinaryHeap() - heap_under_test.push(5) - heap_under_test.push(9) - heap_under_test.push(2) - heap_under_test.push(1) - heap_under_test.push(0) - heap_under_test.push(7) - assert heap_under_test.pop() == 0 + minheap_empty.push(5) + minheap_empty.push(9) + minheap_empty.push(2) + minheap_empty.push(1) + minheap_empty.push(0) + minheap_empty.push(7) + assert minheap_empty.pop() == 0 -def test_pop1(): - # Will pass argumnents, instantiate, heap, check for expected pop - pass +def test_find_parent(): + minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) + assert minheap._find_parent(2) == 0 + assert minheap._find_parent(6) == 2 -def test_pop2(): - # Pop off of an emptied heap - pass +def test_find_children(): + minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) + assert minheap._find_children(0) == (1, 2) + assert minheap._find_children(2) == (5, 6) -def test_pop3(): - pass +def test_is_unsorted_minheap_comparison(): + minheap = BinaryHeap(minheap=True) + assert minheap._is_unsorted(1, 2) -def test_pop4(): - pass +def test_is_unsorted_maxheap_comparison(): + minheap = BinaryHeap(minheap=False) + assert minheap._is_unsorted(2, 1) -def test_push(): - # Create range of numbers - # Shuffle that range - # Push each in a for loop - # Check that pop - pass + +def test_swap(): + minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) + minheap._swap(0, 6) + assert minheap.tree[0] == 6 + assert minheap.tree[6] == 0 From 147ae61ae49b93f3fdc2f548f2c8273c10fa833f Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 14:55:31 -0700 Subject: [PATCH 092/330] Add __setitem__ method --- binary_heap.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 62024e7..c53fe80 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -24,8 +24,11 @@ def __len__(self): def __iter__(self): return iter(self.tree) - def __getitem__(self, item): - return self.tree[item] + def __getitem__(self, index): + return self.tree[index] + + def __setitem__(self, index, value): + self.tree[index] = value def pop(self): """Pop the head from the heap and return.""" From 01189f708874941ccb12343d9e75d1ad2d1fd6e7 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 14:59:21 -0700 Subject: [PATCH 093/330] Add more test coverage; reorganize tests --- test_binary_heap.py | 84 ++++++++++++++++++++++++--------------------- 1 file changed, 45 insertions(+), 39 deletions(-) diff --git a/test_binary_heap.py b/test_binary_heap.py index 86410a5..24785fe 100644 --- a/test_binary_heap.py +++ b/test_binary_heap.py @@ -27,7 +27,7 @@ ] -def minheap_sorted(heap): +def is_minheap_sorted(heap): """Confirm that heap is minheap sorted. Original idea from: @@ -43,7 +43,7 @@ def minheap_sorted(heap): return True -def maxheap_sorted(heap): +def is_maxheap_sorted(heap): """Confirm that heap is maxheap sorted.""" for i in range(len(heap)): try: @@ -60,21 +60,56 @@ def minheap_empty(): return BinaryHeap() -@pytest.fixture() -def minheap_full(): - return BinaryHeap([6, 7, 9, 4, 2, 1, 56, 8, 0, 43523]) +def test_find_parent(): + minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) + assert minheap._find_parent(2) == 0 + assert minheap._find_parent(6) == 2 -@pytest.fixture() -def maxheap_(): - return BinaryHeap([6, 7, 9, 4, 2, 1, 56, 8, 0, 43523], minheap=False) +def test_find_children(): + minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) + assert minheap._find_children(0) == (1, 2) + assert minheap._find_children(2) == (5, 6) + + +def test_is_unsorted_minheap_comparison(): + minheap = BinaryHeap(minheap=True) + assert minheap._is_unsorted(1, 2) + + +def test_is_unsorted_maxheap_comparison(): + minheap = BinaryHeap(minheap=False) + assert minheap._is_unsorted(2, 1) + + +def test_swap(): + minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) + minheap._swap(0, 6) + assert minheap.tree[0] == 6 + assert minheap.tree[6] == 0 + + +def test_bubbledown_minheap(): + minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) + minheap[0] = 4000 + minheap._bubbledown(0) + assert minheap[0] == 1 + assert is_minheap_sorted(minheap) + + +def test_bubbledown_maxheap(): + maxheap = BinaryHeap([6, 5, 4, 3, 2, 1, 0], minheap=False) + maxheap[6] = 4000 + maxheap._bubbleup(6) + assert maxheap[0] == 4000 + assert is_maxheap_sorted(maxheap) @pytest.mark.parametrize("input, output", valid_constructor_args) def test_valid_instantiation_min(input, output): """Test instantiation by creating and doing one pop""" heap_under_test = BinaryHeap(input) - assert minheap_sorted(heap_under_test) + assert is_minheap_sorted(heap_under_test) assert heap_under_test.pop() == output @@ -82,7 +117,7 @@ def test_valid_instantiation_min(input, output): def test_valid_instantiation_max(input, output): """Test instantiation by creating and doing one pop""" heap_under_test = BinaryHeap(input, minheap=False) - assert maxheap_sorted(heap_under_test) + assert is_maxheap_sorted(heap_under_test) assert heap_under_test.pop() == output @@ -147,32 +182,3 @@ def test_push6(minheap_empty): minheap_empty.push(0) minheap_empty.push(7) assert minheap_empty.pop() == 0 - - -def test_find_parent(): - minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) - assert minheap._find_parent(2) == 0 - assert minheap._find_parent(6) == 2 - - -def test_find_children(): - minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) - assert minheap._find_children(0) == (1, 2) - assert minheap._find_children(2) == (5, 6) - - -def test_is_unsorted_minheap_comparison(): - minheap = BinaryHeap(minheap=True) - assert minheap._is_unsorted(1, 2) - - -def test_is_unsorted_maxheap_comparison(): - minheap = BinaryHeap(minheap=False) - assert minheap._is_unsorted(2, 1) - - -def test_swap(): - minheap = BinaryHeap([0, 1, 2, 3, 4, 5, 6]) - minheap._swap(0, 6) - assert minheap.tree[0] == 6 - assert minheap.tree[6] == 0 From a3abab0f2ca80e57b985ca84b69dc29779caedb9 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 15:02:37 -0700 Subject: [PATCH 094/330] Refactor for readability. --- binary_heap.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index c53fe80..fe6044d 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -32,10 +32,10 @@ def __setitem__(self, index, value): def pop(self): """Pop the head from the heap and return.""" - if len(self.tree) <= 1: + if len(self) <= 1: to_return = self.tree.pop() else: - endpos = len(self.tree) - 1 + endpos = len(self) - 1 self._swap(0, endpos) to_return = self.tree.pop() self._bubbledown(0) @@ -48,8 +48,8 @@ def push(self, value): value: the value to add """ self.tree.append(value) # Add protection for different types case - if len(self.tree) > 1: - endpos = len(self.tree) - 1 + if len(self) > 1: + endpos = len(self) - 1 self._bubbleup(endpos) def _bubbleup(self, pos): @@ -61,7 +61,7 @@ def _bubbleup(self, pos): parent = self._find_parent(pos) if pos == 0: # find_parent will return -1 at end of list return - elif self._is_unsorted(self.tree[pos], self.tree[parent]): + elif self._is_unsorted(self[pos], self[parent]): self._swap(pos, parent) self._bubbleup(parent) @@ -73,18 +73,18 @@ def _bubbledown(self, pos): """ lchild, rchild = self._find_children(pos) try: # Evaluating whether lchild exists; may refactor - lval = self.tree[lchild] + lval = self[lchild] try: - rval = self.tree[rchild] + rval = self[rchild] except IndexError: # Case of left_child only - if self._is_unsorted(lval, self.tree[pos]): + if self._is_unsorted(lval, self[pos]): self._swap(lchild, pos) else: # Case of left_child and right_child if self._is_unsorted(lval, rval): target = lchild else: target = rchild - if self._is_unsorted(self.tree[target], self.tree[pos]): + if self._is_unsorted(self[target], self[pos]): self._swap(target, pos) self._bubbledown(target) @@ -140,4 +140,4 @@ def _swap(self, pos1, pos2): pos1: the index of the first item pos2: the index of the second item """ - self.tree[pos1], self.tree[pos2] = self.tree[pos2], self.tree[pos1] + self[pos1], self[pos2] = self[pos2], self[pos1] From d2be166af3d586b0ee61c8bb72d71c3333ca93f1 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 15:22:02 -0700 Subject: [PATCH 095/330] Add further tests --- test_binary_heap.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/test_binary_heap.py b/test_binary_heap.py index 24785fe..61d414c 100644 --- a/test_binary_heap.py +++ b/test_binary_heap.py @@ -182,3 +182,35 @@ def test_push6(minheap_empty): minheap_empty.push(0) minheap_empty.push(7) assert minheap_empty.pop() == 0 + + +def test_pop_minheap(): + minheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6]) + minheap.push(0) + length = len(minheap) + assert minheap.pop() == 0 + assert len(minheap) == length - 1 + + +def test_multipop_minheap(): + minheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6]) + length = len(minheap) + minheap.pop() + minheap.pop() + minheap.push(0) + minheap.pop() + minheap.pop() + assert minheap.pop() == 7 + assert len(minheap) == length - 4 + + +def test_multipop_maxheap(): + maxheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6], minheap=False) + length = len(maxheap) + maxheap.pop() + maxheap.pop() + maxheap.push(400) + maxheap.pop() + maxheap.pop() + assert maxheap.pop() == 7 + assert len(maxheap) == length - 4 From 505d49323bff30b1aa01e8a767b23b4d2165a7b9 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 15:46:01 -0700 Subject: [PATCH 096/330] Change values in final tests for clarity --- test_binary_heap.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/test_binary_heap.py b/test_binary_heap.py index 61d414c..01d1ae4 100644 --- a/test_binary_heap.py +++ b/test_binary_heap.py @@ -192,8 +192,16 @@ def test_pop_minheap(): assert len(minheap) == length - 1 +def test_pop_maxheap(): + maxheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6], minheap=False) + maxheap.push(400) + length = len(maxheap) + assert maxheap.pop() == 400 + assert len(maxheap) == length - 1 + + def test_multipop_minheap(): - minheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6]) + minheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6, 200]) length = len(minheap) minheap.pop() minheap.pop() @@ -205,12 +213,12 @@ def test_multipop_minheap(): def test_multipop_maxheap(): - maxheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6], minheap=False) + maxheap = BinaryHeap([7, 9, 18, 1, 38, 5.4, 6, 200], minheap=False) length = len(maxheap) maxheap.pop() maxheap.pop() maxheap.push(400) maxheap.pop() maxheap.pop() - assert maxheap.pop() == 7 + assert maxheap.pop() == 9 assert len(maxheap) == length - 4 From d1afe516340551c34482741b367fd0a4b74463a6 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 16:04:59 -0700 Subject: [PATCH 097/330] Fix typo --- test_binary_heap.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_binary_heap.py b/test_binary_heap.py index 01d1ae4..ac0ff25 100644 --- a/test_binary_heap.py +++ b/test_binary_heap.py @@ -152,7 +152,7 @@ def test_push3(minheap_empty): def test_push4(minheap_empty): - """ First push three items from list of [9, 5, 2, 1, 0, 7]; current + """ First push four items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ minheap_empty.push(5) minheap_empty.push(9) @@ -162,7 +162,7 @@ def test_push4(minheap_empty): def test_push5(minheap_empty): - """ First push three items from list of [9, 5, 2, 1, 0, 7]; current + """ First push five items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ minheap_empty.push(5) minheap_empty.push(9) @@ -173,7 +173,7 @@ def test_push5(minheap_empty): def test_push6(minheap_empty): - """ First push three items from list of [9, 5, 2, 1, 0, 7]; current + """ First push six items from list of [9, 5, 2, 1, 0, 7]; current test for min heap """ minheap_empty.push(5) minheap_empty.push(9) From ac64f3e84fa1c0b91d643a16f8cb4e9fea25405f Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 16:09:59 -0700 Subject: [PATCH 098/330] Update README --- README.md | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4c287de..929b616 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Implementation of LinkedList and Stack data structures in Python. ##LinkedList The LinkedList class is composed of a Node base class. + Available methods inlude: * insert(val) * pop() @@ -11,19 +12,27 @@ Available methods inlude: * remove(node) * display() -See the doc strings for implementation details. - ##Stack The Stack data class is a first-in-first-out data structure built via composition from LinkedList. Available methods include: * push(value) * pop() -##Dequeque -The Dequeue data class is a first-in-last-out data structure built via encapsulation of a LinkedList. +##Queue +The Queue data class is a first-in-last-out data structure built via encapsulation of a LinkedList. + Available methods inlude: * enqueque(value) * dequeque() * len() +##Binary Heap +The Binary Heap data class is a binary tree data structure built implemented on a built-in Python +list. The binary heap default to a minheap sort, meaning that the smallest values will be sorted to +the top of the heap. Alternatively, the binary heap can be instantiated as a maxheap so that the +greates values will be sorted to the top. + +Available methods include: +* pop() +* push() See the doc strings for implementation details. From f54901d26b7762522ea47ebeea1086c6f625e356 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 16:11:08 -0700 Subject: [PATCH 099/330] Fix typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 929b616..477d186 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,7 @@ Available methods inlude: The Binary Heap data class is a binary tree data structure built implemented on a built-in Python list. The binary heap default to a minheap sort, meaning that the smallest values will be sorted to the top of the heap. Alternatively, the binary heap can be instantiated as a maxheap so that the -greates values will be sorted to the top. +greatest values will be sorted to the top. Available methods include: * pop() From 1b32b5112c96cb679d7db4a9852447132054fed3 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 16:20:04 -0700 Subject: [PATCH 100/330] Fix typo --- binary_heap.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binary_heap.py b/binary_heap.py index fe6044d..4b8c578 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -7,7 +7,7 @@ def __init__(self, iterable=(), minheap=True): """Initializes a binary heap, optionally with items from an iterable. By default, the binary will sort as a minheap, with smallest values - at the head. If minheap is set to false, the binary heap with sort + at the head. If minheap is set to false, the binary heap will sort as a maxheap, with largest values at the head. """ self.tree = [] From 77bc64e1b17b7384a12ff91ed95cde4eda36aa61 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 17:23:26 -0700 Subject: [PATCH 101/330] Fix queue references dued to linked list refactor --- queue.py | 4 ++-- test_queue.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/queue.py b/queue.py index cb8d89a..f8f06dd 100644 --- a/queue.py +++ b/queue.py @@ -7,7 +7,7 @@ class Queue(): def __init__(self, iterable=()): self.other = LinkedList() - self.other.header = None + self.other.head = None self.other.tail = None self.other.length = 0 for val in (iterable): @@ -27,7 +27,7 @@ def enqueue(self, value): """ new_node = Node(value) if self.other.tail is None: - self.other.header = self.other.tail = new_node + self.other.head = self.other.tail = new_node else: self.other.tail.next = new_node self.other.tail = new_node diff --git a/test_queue.py b/test_queue.py index e8e9ec2..f0a01af 100644 --- a/test_queue.py +++ b/test_queue.py @@ -53,10 +53,10 @@ def test_dequeue(): for x in range(5): q.dequeue() assert q.dequeue() == 6 - assert q.other.header.val == 7 + assert q.other.head.val == 7 assert q.other.tail.val == 10 assert len(q) == 4 while len(q): q.dequeue() - assert q.other.header is None + assert q.other.head is None assert q.other.tail is None From 5778de4ad1054501e1564409057224d90f780b56 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 17:37:13 -0700 Subject: [PATCH 102/330] Add files for priorityq --- priorityq.py | 0 test_priorityq.py | 0 2 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 priorityq.py create mode 100644 test_priorityq.py diff --git a/priorityq.py b/priorityq.py new file mode 100644 index 0000000..e69de29 diff --git a/test_priorityq.py b/test_priorityq.py new file mode 100644 index 0000000..e69de29 From 10a05e51fd1d969157f2ad68b1db2bec4a51d442 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 17:49:45 -0700 Subject: [PATCH 103/330] Sketch out methods. refs #10 --- priorityq.py | 44 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/priorityq.py b/priorityq.py index e69de29..1d7fac4 100644 --- a/priorityq.py +++ b/priorityq.py @@ -0,0 +1,44 @@ +from __future__ import unicode_literals +from functools import total_ordering + +from binary_heap import BinaryHeap + + +@total_ordering # Will build out the remaining comparison methods +class QueueNode(object): + """A class for a queue node.""" + def __init__(self, val, priority): + super(QueueNode, self).__init__() + self.val = val + self.priority = priority + + def __repr__(self): + """Print representation of node.""" + return "{val}".format(val=self.val) + + def __eq__(self, other): + """Implement this and following two methods with logic to compare + priority and value appropiately. + """ + pass + + def __lt__(self, other): + """Implement in tandem with __eq__.""" + pass + + +class PriorityQ(object): + """A class for a priority queue. Compose this from BinaryHeap.""" + def __init__(self, iterable=()): + pass + + def insert(item): + """Insert an item into the queue.""" + pass + + def pop(): + """Remove the most importan item from the queue.""" + pass + + def peek(): + """Returns the most important item from queue without removal.""" From 91e641ac5d4769ab0fab012357c535f39b9022f0 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 17:54:16 -0700 Subject: [PATCH 104/330] Change node class name --- priorityq.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/priorityq.py b/priorityq.py index 1d7fac4..156adc5 100644 --- a/priorityq.py +++ b/priorityq.py @@ -5,10 +5,10 @@ @total_ordering # Will build out the remaining comparison methods -class QueueNode(object): +class QNode(object): """A class for a queue node.""" def __init__(self, val, priority): - super(QueueNode, self).__init__() + super(QNode, self).__init__() self.val = val self.priority = priority From e90c10093a9948e87008c2cd9411f3abfda00a20 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 17:55:56 -0700 Subject: [PATCH 105/330] Set default priority level to None --- priorityq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priorityq.py b/priorityq.py index 156adc5..ae91ded 100644 --- a/priorityq.py +++ b/priorityq.py @@ -7,7 +7,7 @@ @total_ordering # Will build out the remaining comparison methods class QNode(object): """A class for a queue node.""" - def __init__(self, val, priority): + def __init__(self, val, priority=None): super(QNode, self).__init__() self.val = val self.priority = priority From b7cdf1540d8b23a90368618f0963382380560b6b Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 18:23:04 -0700 Subject: [PATCH 106/330] Add some QNode tests. refs #11 --- test_priorityq.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/test_priorityq.py b/test_priorityq.py index e69de29..29c774e 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -0,0 +1,41 @@ +from __future__ import unicode_literals +import pytest + +from priorityq import PriorityQ, QNode + + +def test_QNode_init_no_priority(): + r = QNode(10) + assert r.val == 10 + assert r.priority is None + + +def test_QNode_init_with_priority(): + p = QNode('string', 0) + assert p.val == 'string' + assert p.priority is 0 + assert p.val, p.priority == ('string', 0) + + +def test_QNode_val_comparison(): + p = QNode(10) + r = QNode(5) + assert p > r + + +def test_QNode_priority_comparison(): + p = QNode(10, 0) + r = QNode(10) + assert p < r + + +def test_QNode_equal_priority_comparison(): + p = QNode(10, 1) + r = QNode(5, 1) + assert p > r + + +def test_QNode_equality_comparison(): + p = QNode(10, 10) + r = QNode(10, 10) + assert p == r From bee27f2500bdc80a436499d2d356a87572065aeb Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 18:59:05 -0700 Subject: [PATCH 107/330] Add tests for insert method --- test_priorityq.py | 109 ++++++++++++++++++++++++++++++++++++++-------- 1 file changed, 90 insertions(+), 19 deletions(-) diff --git a/test_priorityq.py b/test_priorityq.py index 29c774e..63f8161 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -4,38 +4,109 @@ from priorityq import PriorityQ, QNode +@pytest.fixture() +def QNode_list(): + QNode_list = [ + QNode(10), + QNode(5, 2), + QNode(100, 1) + ] + return QNode_list + + +@pytest.fixture() +def base_pqueue(QNode_list): + return base_pqueue(QNode_list) + + def test_QNode_init_no_priority(): - r = QNode(10) - assert r.val == 10 - assert r.priority is None + node1 = QNode(10) + assert node1.val == 10 + assert node1.priority is None def test_QNode_init_with_priority(): - p = QNode('string', 0) - assert p.val == 'string' - assert p.priority is 0 - assert p.val, p.priority == ('string', 0) + node1 = QNode('string', 0) + assert node1.val == 'string' + assert node1.priority is 0 + assert node1.val, node1.priority == ('string', 0) def test_QNode_val_comparison(): - p = QNode(10) - r = QNode(5) - assert p > r + node1 = QNode(10) + node2 = QNode(5) + assert node1 > node2 def test_QNode_priority_comparison(): - p = QNode(10, 0) - r = QNode(10) - assert p < r + node1 = QNode(10, 0) + node2 = QNode(10) + assert node1 < node2 def test_QNode_equal_priority_comparison(): - p = QNode(10, 1) - r = QNode(5, 1) - assert p > r + node1 = QNode(10, 1) + node2 = QNode(5, 1) + assert node1 > node2 def test_QNode_equality_comparison(): - p = QNode(10, 10) - r = QNode(10, 10) - assert p == r + node1 = QNode(10, 10) + node2 = QNode(10, 10) + assert node1 == node2 + + +def test_PriorityQ_init_empty(): + pqueue = PriorityQ() + assert isinstance(pqueue, PriorityQ) + assert len(pqueue) == 0 + + +def test_PriorityQ_init_iterable_no_QNodes(): + pqueue = PriorityQ([10, 9, 8, 7, 6, 5]) + assert len(pqueue) == 6 + assert pqueue[0].val == 5 + assert pqueue[0].priority is None + + +def test_PriorityQ_init_iterable_with_QNodes(QNode_list): + pqueue = PriorityQ(QNode_list) + assert len(pqueue) == 3 + assert pqueue[0].val == 100 + assert pqueue[0].priority == 1 + + +def test_insert_item_not_QNode_to_empty(): + queue = PriorityQ() + queue.insert(50) + assert len(queue) == 1 + assert queue[0].val == 50 + assert queue[0].priority is None + + +def test_insert_item_QNode_to_empty(): + node1 = QNode(10, 0) + pqueue = PriorityQ() + pqueue.insert(node1) + assert len(pqueue) == 1 + assert pqueue[0].val == 10 + assert pqueue[0].priority == 0 + + +def test_insert_item_not_QNode_to_filled(base_pqueue): + base_pqueue.insert(500) + assert len(base_pqueue) == 4 + assert base_pqueue[0].val == 100 + assert base_pqueue[0].priority == 1 + + +def test_insert_QNode_to_filled(base_pqueue): + node1 = QNode(10, 0) + base_pqueue.insert(node1) + assert len(base_pqueue) == 4 + assert base_pqueue[0].val == 10 + assert base_pqueue[0].priority == 0 + + +def test_pop(base_pqueue): + From a959390c684363a144143776b6daabae6a4fbeee Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 19:03:25 -0700 Subject: [PATCH 108/330] Complete first pass simple tests --- test_priorityq.py | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/test_priorityq.py b/test_priorityq.py index 63f8161..c780b6b 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -109,4 +109,15 @@ def test_insert_QNode_to_filled(base_pqueue): def test_pop(base_pqueue): - + top_priority = QNode(9000, 0) + length = len(base_pqueue) + base_pqueue.insert(top_priority) + assert base_pqueue.pop() is top_priority + assert len(base_pqueue) == length - 1 + + +def test_peek(base_pqueue): + top_priority = QNode(9000, 0) + base_pqueue.insert(top_priority) + assert base_pqueue.peek() is top_priority + assert base_pqueue[0] is top_priority From f8e4334514a622fa7541e0b82800fdbc717e8838 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 19:10:13 -0700 Subject: [PATCH 109/330] Add notes to sketch for priorityq. refs #10 --- priorityq.py | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/priorityq.py b/priorityq.py index ae91ded..0c6f3ec 100644 --- a/priorityq.py +++ b/priorityq.py @@ -8,7 +8,6 @@ class QNode(object): """A class for a queue node.""" def __init__(self, val, priority=None): - super(QNode, self).__init__() self.val = val self.priority = priority @@ -30,14 +29,20 @@ def __lt__(self, other): class PriorityQ(object): """A class for a priority queue. Compose this from BinaryHeap.""" def __init__(self, iterable=()): + """We can iteratively use insert here.""" pass - def insert(item): - """Insert an item into the queue.""" + def insert(item): # Wamt to extend spec to include priority as 2nd arg + """Insert an item into the queue. Would be nice to examine item as follows: + If item is node: + add to PriorityQ + else: + init QNode with item as val and priority as None + """ pass def pop(): - """Remove the most importan item from the queue.""" + """Remove the most important item from the queue.""" pass def peek(): From 44bc17cd8b271fee4068de1eb1e575b062041425 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 19:12:59 -0700 Subject: [PATCH 110/330] Add notes to tests. refs #11 --- test_priorityq.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/test_priorityq.py b/test_priorityq.py index c780b6b..fc61d43 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -3,6 +3,9 @@ from priorityq import PriorityQ, QNode +# We could parameterize inputs for non-numeric types, but val sorting +# will be odd in binheap. + @pytest.fixture() def QNode_list(): @@ -26,10 +29,9 @@ def test_QNode_init_no_priority(): def test_QNode_init_with_priority(): - node1 = QNode('string', 0) - assert node1.val == 'string' + node1 = QNode(10, 0) + assert node1.val == 10 assert node1.priority is 0 - assert node1.val, node1.priority == ('string', 0) def test_QNode_val_comparison(): From e55cccb6f57f666b7608eb9f96ec023d28b1e737 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 19:21:36 -0700 Subject: [PATCH 111/330] Add to notes --- priorityq.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/priorityq.py b/priorityq.py index 0c6f3ec..340d9e6 100644 --- a/priorityq.py +++ b/priorityq.py @@ -16,7 +16,7 @@ def __repr__(self): return "{val}".format(val=self.val) def __eq__(self, other): - """Implement this and following two methods with logic to compare + """Implement this and following method with logic to compare priority and value appropiately. """ pass @@ -32,12 +32,13 @@ def __init__(self, iterable=()): """We can iteratively use insert here.""" pass - def insert(item): # Wamt to extend spec to include priority as 2nd arg + def insert(item): # Want to extend spec to add priority as 2nd optional arg """Insert an item into the queue. Would be nice to examine item as follows: If item is node: add to PriorityQ else: init QNode with item as val and priority as None + add to PriorityQ """ pass From 10c22e7e5d871d851bf57b7acb0b4a894198edd5 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 19:25:45 -0700 Subject: [PATCH 112/330] Fix error in test fixture --- test_priorityq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_priorityq.py b/test_priorityq.py index fc61d43..cabe89c 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -19,7 +19,7 @@ def QNode_list(): @pytest.fixture() def base_pqueue(QNode_list): - return base_pqueue(QNode_list) + return PriorityQ(QNode_list) def test_QNode_init_no_priority(): From aded5ae9effa1ff07cc6b536964e39582cfb6005 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 19:32:35 -0700 Subject: [PATCH 113/330] Use more universal argument names for _is_unsorted method --- binary_heap.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/binary_heap.py b/binary_heap.py index 4b8c578..6220261 100644 --- a/binary_heap.py +++ b/binary_heap.py @@ -103,7 +103,7 @@ def _find_parent(self, pos): return parent def _find_children(self, pos): - """Returns the indexes of children from given position. + """Returns the indices of children from given position. args: pos: the index position to inspect @@ -114,22 +114,22 @@ def _find_children(self, pos): rchild = lchild + 1 return lchild, rchild - def _is_unsorted(self, val1, val2): - """Compare two values according to heaptype. + def _is_unsorted(self, item1, item2): + """Compare two items according to heaptype. - For a minheap, checks if first value is less than second value. - For a maxheap, checks if first value is greater than second value. + For a minheap, checks if first item is less than second item. + For a maxheap, checks if first item is greater than second item. args: - val1: first value - val2: second value + item1: first item + item2: second item Returns: True if heaptype comparison matches, else False """ if self.minheap is True: - return val1 < val2 + return item1 < item2 elif self.minheap is False: - return val1 > val2 + return item1 > item2 else: raise AttributeError('heaptype not assigned') From c2a2da2c021d597979b448fa1544a52ea48d8b05 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 19:58:21 -0700 Subject: [PATCH 114/330] First pass at script --- priorityq.py | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/priorityq.py b/priorityq.py index 340d9e6..304b8d0 100644 --- a/priorityq.py +++ b/priorityq.py @@ -19,20 +19,32 @@ def __eq__(self, other): """Implement this and following method with logic to compare priority and value appropiately. """ - pass + if self.priority is None and other.priority is None: + self.value == other.value + elif self.priority is None or other.priority is None: + return False + else: + self.priority == other.priority def __lt__(self, other): """Implement in tandem with __eq__.""" - pass + if self.priority is None and other.priority is None: + self.value < other.value + elif self.priority is None or other.priority is None: + self.priority > other.priority # Since None is less than anything + else: + self.priority < other.priority class PriorityQ(object): """A class for a priority queue. Compose this from BinaryHeap.""" def __init__(self, iterable=()): """We can iteratively use insert here.""" - pass + self.heap = BinaryHeap(iterable=()) + for item in iterable: + self.insert(item) - def insert(item): # Want to extend spec to add priority as 2nd optional arg + def insert(self, item, priority=None): # Want to extend spec to add priority as 2nd optional arg """Insert an item into the queue. Would be nice to examine item as follows: If item is node: add to PriorityQ @@ -40,7 +52,10 @@ def insert(item): # Want to extend spec to add priority as 2nd optional arg init QNode with item as val and priority as None add to PriorityQ """ - pass + if isinstance(item, QNode): + self.heap.push(item) + else: + self.heap.push(QNode(item, priority)) def pop(): """Remove the most important item from the queue.""" From efffab3392cd7cbd8965678f07dd008f7ecc1935 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 20:53:05 -0700 Subject: [PATCH 115/330] Fill out methods --- priorityq.py | 42 ++++++++++++++++++++++++++++++------------ 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/priorityq.py b/priorityq.py index 304b8d0..f77e5f8 100644 --- a/priorityq.py +++ b/priorityq.py @@ -17,23 +17,25 @@ def __repr__(self): def __eq__(self, other): """Implement this and following method with logic to compare - priority and value appropiately. + priority and val appropiately. """ - if self.priority is None and other.priority is None: - self.value == other.value + if self.priority == other.priority: + return self.val == other.val elif self.priority is None or other.priority is None: return False else: - self.priority == other.priority + return self.priority == other.priority def __lt__(self, other): """Implement in tandem with __eq__.""" - if self.priority is None and other.priority is None: - self.value < other.value - elif self.priority is None or other.priority is None: - self.priority > other.priority # Since None is less than anything + if self.priority == other.priority: + return self.val < other.val + elif self.priority is None: + return False + elif other.priority is None: + return True else: - self.priority < other.priority + return self.priority < other.priority class PriorityQ(object): @@ -44,6 +46,21 @@ def __init__(self, iterable=()): for item in iterable: self.insert(item) + def __repr__(self): + return repr(self.heap) + + def __len__(self): + return len(self.heap) + + def __iter__(self): + return iter(self.heap) + + def __getitem__(self, index): + return self.heap[index] + + def __setitem__(self, index, value): + self.heap[index] = value + def insert(self, item, priority=None): # Want to extend spec to add priority as 2nd optional arg """Insert an item into the queue. Would be nice to examine item as follows: If item is node: @@ -57,9 +74,10 @@ def insert(self, item, priority=None): # Want to extend spec to add priority as else: self.heap.push(QNode(item, priority)) - def pop(): + def pop(self): """Remove the most important item from the queue.""" - pass + return self.heap.pop() - def peek(): + def peek(self): """Returns the most important item from queue without removal.""" + return self.heap[0] From eb4fb77c69e79683e5bb1a8f67dcb7dff4486878 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 21:04:23 -0700 Subject: [PATCH 116/330] Fix test error --- test_priorityq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_priorityq.py b/test_priorityq.py index cabe89c..c29a828 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -115,7 +115,7 @@ def test_pop(base_pqueue): length = len(base_pqueue) base_pqueue.insert(top_priority) assert base_pqueue.pop() is top_priority - assert len(base_pqueue) == length - 1 + assert len(base_pqueue) == length def test_peek(base_pqueue): From 46582ba85a34c801c63096ee41f5bd35f85e6bba Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 21:04:37 -0700 Subject: [PATCH 117/330] Complete first pass of script --- priorityq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priorityq.py b/priorityq.py index f77e5f8..b93026e 100644 --- a/priorityq.py +++ b/priorityq.py @@ -61,7 +61,7 @@ def __getitem__(self, index): def __setitem__(self, index, value): self.heap[index] = value - def insert(self, item, priority=None): # Want to extend spec to add priority as 2nd optional arg + def insert(self, item, priority=None): """Insert an item into the queue. Would be nice to examine item as follows: If item is node: add to PriorityQ From f513c573a11567562d52600da06dc8610f3991a7 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 21:09:50 -0700 Subject: [PATCH 118/330] Add pretty printing of node val and priority. --- priorityq.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/priorityq.py b/priorityq.py index b93026e..5c1c237 100644 --- a/priorityq.py +++ b/priorityq.py @@ -15,6 +15,10 @@ def __repr__(self): """Print representation of node.""" return "{val}".format(val=self.val) + def __str__(self): + """Pretty print node value and priority.""" + return "{val}, Priority:{p}".format(val=self.val, p=self.priority) + def __eq__(self, other): """Implement this and following method with logic to compare priority and val appropiately. From 784ea9b4fdfa2587e20fb12f4b80a9758f0a3e4d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 21:22:34 -0700 Subject: [PATCH 119/330] Improve docstrings --- priorityq.py | 41 +++++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 14 deletions(-) diff --git a/priorityq.py b/priorityq.py index 5c1c237..b345bb7 100644 --- a/priorityq.py +++ b/priorityq.py @@ -8,6 +8,10 @@ class QNode(object): """A class for a queue node.""" def __init__(self, val, priority=None): + """Initialize a QNode with a value and an optional priority. + + Priority must be an integer, highest being 0. + """ self.val = val self.priority = priority @@ -20,9 +24,7 @@ def __str__(self): return "{val}, Priority:{p}".format(val=self.val, p=self.priority) def __eq__(self, other): - """Implement this and following method with logic to compare - priority and val appropiately. - """ + """Overloads equality comparison to check priority, then value.""" if self.priority == other.priority: return self.val == other.val elif self.priority is None or other.priority is None: @@ -31,7 +33,7 @@ def __eq__(self, other): return self.priority == other.priority def __lt__(self, other): - """Implement in tandem with __eq__.""" + """Overloads lesser than comparison to check priority, then value.""" if self.priority == other.priority: return self.val < other.val elif self.priority is None: @@ -43,9 +45,17 @@ def __lt__(self, other): class PriorityQ(object): - """A class for a priority queue. Compose this from BinaryHeap.""" + """A class for a priority queue.""" def __init__(self, iterable=()): - """We can iteratively use insert here.""" + """Initialize a priority queue, optionally with items from iterable. + + The items in the priority queue are stored in a binary minheap. Items + first sorted by priority, then value. Priority is expressed as an + integer with 0 being the most important. + + args: + iterable: an optional iterable to add to the priority queue. + """ self.heap = BinaryHeap(iterable=()) for item in iterable: self.insert(item) @@ -66,12 +76,15 @@ def __setitem__(self, index, value): self.heap[index] = value def insert(self, item, priority=None): - """Insert an item into the queue. Would be nice to examine item as follows: - If item is node: - add to PriorityQ - else: - init QNode with item as val and priority as None - add to PriorityQ + """Insert an item into the priority queue. + + If the item is a QNode object, it will be added as is. + If not, a new QNode object is created to hold the item with optional + priority assigned. + + args: + item: the item to add (QNode or other value) + priority: the optional integer priority (0 is most important) """ if isinstance(item, QNode): self.heap.push(item) @@ -79,9 +92,9 @@ def insert(self, item, priority=None): self.heap.push(QNode(item, priority)) def pop(self): - """Remove the most important item from the queue.""" + """Remove and return the most important item from the queue.""" return self.heap.pop() def peek(self): - """Returns the most important item from queue without removal.""" + """Return the most important item from queue without removal.""" return self.heap[0] From 9184eb524b8cc842c11942c73fcf4e0a2c84d2e3 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 21:24:13 -0700 Subject: [PATCH 120/330] Change wording in docstring --- priorityq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priorityq.py b/priorityq.py index b345bb7..3ef4eec 100644 --- a/priorityq.py +++ b/priorityq.py @@ -10,7 +10,7 @@ class QNode(object): def __init__(self, val, priority=None): """Initialize a QNode with a value and an optional priority. - Priority must be an integer, highest being 0. + Priority must be an integer, most important being 0. """ self.val = val self.priority = priority From ff88ee350f72327f51a84307d776adbac1eefc04 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 21:26:55 -0700 Subject: [PATCH 121/330] Improve docstrings --- priorityq.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/priorityq.py b/priorityq.py index 3ef4eec..fe17044 100644 --- a/priorityq.py +++ b/priorityq.py @@ -54,7 +54,8 @@ def __init__(self, iterable=()): integer with 0 being the most important. args: - iterable: an optional iterable to add to the priority queue. + iterable: an optional iterable to add to the priority queue. Items + added this way will be given a priority of None. """ self.heap = BinaryHeap(iterable=()) for item in iterable: From cca2904913cb3e5dd62aeddb13fc81f123da082c Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 21:50:54 -0700 Subject: [PATCH 122/330] Start tracking insertion order after priority --- priorityq.py | 12 ++++++++---- test_priorityq.py | 28 ++++++++++++++-------------- 2 files changed, 22 insertions(+), 18 deletions(-) diff --git a/priorityq.py b/priorityq.py index fe17044..5ad7ae8 100644 --- a/priorityq.py +++ b/priorityq.py @@ -7,13 +7,14 @@ @total_ordering # Will build out the remaining comparison methods class QNode(object): """A class for a queue node.""" - def __init__(self, val, priority=None): + def __init__(self, val, priority=None, order=None): """Initialize a QNode with a value and an optional priority. Priority must be an integer, most important being 0. """ self.val = val self.priority = priority + self.order = order def __repr__(self): """Print representation of node.""" @@ -26,7 +27,7 @@ def __str__(self): def __eq__(self, other): """Overloads equality comparison to check priority, then value.""" if self.priority == other.priority: - return self.val == other.val + return self.order == other.order elif self.priority is None or other.priority is None: return False else: @@ -35,7 +36,7 @@ def __eq__(self, other): def __lt__(self, other): """Overloads lesser than comparison to check priority, then value.""" if self.priority == other.priority: - return self.val < other.val + return self.order < other.order elif self.priority is None: return False elif other.priority is None: @@ -58,6 +59,7 @@ def __init__(self, iterable=()): added this way will be given a priority of None. """ self.heap = BinaryHeap(iterable=()) + self._count = 0 for item in iterable: self.insert(item) @@ -88,9 +90,11 @@ def insert(self, item, priority=None): priority: the optional integer priority (0 is most important) """ if isinstance(item, QNode): + item.order = self._count self.heap.push(item) else: - self.heap.push(QNode(item, priority)) + self.heap.push(QNode(item, priority=priority, order=self._count)) + self._count += 1 def pop(self): """Remove and return the most important item from the queue.""" diff --git a/test_priorityq.py b/test_priorityq.py index c29a828..80115c6 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -11,8 +11,8 @@ def QNode_list(): QNode_list = [ QNode(10), - QNode(5, 2), - QNode(100, 1) + QNode(5, priority=2), + QNode(100, priority=1) ] return QNode_list @@ -29,32 +29,32 @@ def test_QNode_init_no_priority(): def test_QNode_init_with_priority(): - node1 = QNode(10, 0) + node1 = QNode(10, priority=0) assert node1.val == 10 assert node1.priority is 0 -def test_QNode_val_comparison(): - node1 = QNode(10) - node2 = QNode(5) - assert node1 > node2 +def test_QNode_order_comparison(): + node1 = QNode(10, order=1) + node2 = QNode(5, order=2) + assert node1 < node2 def test_QNode_priority_comparison(): - node1 = QNode(10, 0) + node1 = QNode(10, priority=0) node2 = QNode(10) assert node1 < node2 def test_QNode_equal_priority_comparison(): - node1 = QNode(10, 1) - node2 = QNode(5, 1) - assert node1 > node2 + node1 = QNode(10, priority=1, order=1) + node2 = QNode(5, priority=1, order=2) + assert node1 < node2 def test_QNode_equality_comparison(): - node1 = QNode(10, 10) - node2 = QNode(10, 10) + node1 = QNode(10, priority=10) + node2 = QNode(10, priority=10) assert node1 == node2 @@ -67,7 +67,7 @@ def test_PriorityQ_init_empty(): def test_PriorityQ_init_iterable_no_QNodes(): pqueue = PriorityQ([10, 9, 8, 7, 6, 5]) assert len(pqueue) == 6 - assert pqueue[0].val == 5 + assert pqueue[0].order == 0 assert pqueue[0].priority is None From 36c1769b4303b665276a757131846fba0d247231 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 21:52:50 -0700 Subject: [PATCH 123/330] Make priority and order explicit in tests --- test_priorityq.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/test_priorityq.py b/test_priorityq.py index 80115c6..7d5df7d 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -87,7 +87,7 @@ def test_insert_item_not_QNode_to_empty(): def test_insert_item_QNode_to_empty(): - node1 = QNode(10, 0) + node1 = QNode(10, priority=0) pqueue = PriorityQ() pqueue.insert(node1) assert len(pqueue) == 1 @@ -103,7 +103,7 @@ def test_insert_item_not_QNode_to_filled(base_pqueue): def test_insert_QNode_to_filled(base_pqueue): - node1 = QNode(10, 0) + node1 = QNode(10, priority=0) base_pqueue.insert(node1) assert len(base_pqueue) == 4 assert base_pqueue[0].val == 10 @@ -111,7 +111,7 @@ def test_insert_QNode_to_filled(base_pqueue): def test_pop(base_pqueue): - top_priority = QNode(9000, 0) + top_priority = QNode(9000, priority=0) length = len(base_pqueue) base_pqueue.insert(top_priority) assert base_pqueue.pop() is top_priority @@ -119,7 +119,7 @@ def test_pop(base_pqueue): def test_peek(base_pqueue): - top_priority = QNode(9000, 0) + top_priority = QNode(9000, priority=0) base_pqueue.insert(top_priority) assert base_pqueue.peek() is top_priority assert base_pqueue[0] is top_priority From 4a898a5f1e670dc30f9f2d247e9a667cc8430ccd Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 22:01:15 -0700 Subject: [PATCH 124/330] Improve pretty printing --- priorityq.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/priorityq.py b/priorityq.py index 5ad7ae8..af304fd 100644 --- a/priorityq.py +++ b/priorityq.py @@ -22,7 +22,9 @@ def __repr__(self): def __str__(self): """Pretty print node value and priority.""" - return "{val}, Priority:{p}".format(val=self.val, p=self.priority) + return "Value:{val}, Order:{o} Priority:{p}".format( + val=self.val, o=self.order, p=self.priority + ) def __eq__(self, other): """Overloads equality comparison to check priority, then value.""" From 3691c3be2fef96922a244da3c33faefb810b8514 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 22:07:28 -0700 Subject: [PATCH 125/330] Improve tests --- test_priorityq.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/test_priorityq.py b/test_priorityq.py index 7d5df7d..1c3fcb5 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -31,7 +31,7 @@ def test_QNode_init_no_priority(): def test_QNode_init_with_priority(): node1 = QNode(10, priority=0) assert node1.val == 10 - assert node1.priority is 0 + assert node1.priority == 0 def test_QNode_order_comparison(): @@ -67,7 +67,7 @@ def test_PriorityQ_init_empty(): def test_PriorityQ_init_iterable_no_QNodes(): pqueue = PriorityQ([10, 9, 8, 7, 6, 5]) assert len(pqueue) == 6 - assert pqueue[0].order == 0 + assert pqueue[0].val == 10 assert pqueue[0].priority is None @@ -96,10 +96,11 @@ def test_insert_item_QNode_to_empty(): def test_insert_item_not_QNode_to_filled(base_pqueue): - base_pqueue.insert(500) + base_pqueue.insert(500, priority=0) assert len(base_pqueue) == 4 - assert base_pqueue[0].val == 100 - assert base_pqueue[0].priority == 1 + assert base_pqueue[0].val == 500 + assert base_pqueue[0].order == 3 + assert base_pqueue[0].priority == 0 def test_insert_QNode_to_filled(base_pqueue): @@ -107,6 +108,7 @@ def test_insert_QNode_to_filled(base_pqueue): base_pqueue.insert(node1) assert len(base_pqueue) == 4 assert base_pqueue[0].val == 10 + assert base_pqueue[0].order == 3 assert base_pqueue[0].priority == 0 From 439d784384108711c59d78c49fdc21f9e7d52cd6 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 22:19:54 -0700 Subject: [PATCH 126/330] Fix docstring for queue order tracking --- priorityq.py | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/priorityq.py b/priorityq.py index af304fd..03b80d0 100644 --- a/priorityq.py +++ b/priorityq.py @@ -10,7 +10,10 @@ class QNode(object): def __init__(self, val, priority=None, order=None): """Initialize a QNode with a value and an optional priority. - Priority must be an integer, most important being 0. + args: + val: the value to store + priority: an integer with 0 being most important + order: integer to store queue insertion order """ self.val = val self.priority = priority @@ -27,7 +30,7 @@ def __str__(self): ) def __eq__(self, other): - """Overloads equality comparison to check priority, then value.""" + """Overloads equality comparison to check priority, then order.""" if self.priority == other.priority: return self.order == other.order elif self.priority is None or other.priority is None: @@ -36,7 +39,7 @@ def __eq__(self, other): return self.priority == other.priority def __lt__(self, other): - """Overloads lesser than comparison to check priority, then value.""" + """Overloads lesser than comparison to check priority, then order.""" if self.priority == other.priority: return self.order < other.order elif self.priority is None: @@ -53,8 +56,8 @@ def __init__(self, iterable=()): """Initialize a priority queue, optionally with items from iterable. The items in the priority queue are stored in a binary minheap. Items - first sorted by priority, then value. Priority is expressed as an - integer with 0 being the most important. + are first sorted by priority, then queue insertion order. Priority is + expressed as an integer with 0 being the most important. args: iterable: an optional iterable to add to the priority queue. Items @@ -83,9 +86,9 @@ def __setitem__(self, index, value): def insert(self, item, priority=None): """Insert an item into the priority queue. - If the item is a QNode object, it will be added as is. - If not, a new QNode object is created to hold the item with optional - priority assigned. + If the item is a QNode object, it will be added tracking queue order. + If not, a new QNode object is created to hold the item with queue order + and optional priority assigned. args: item: the item to add (QNode or other value) From 85aa656e5ea8ad348b9cc90dfbb5e07279b273ce Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 22:21:01 -0700 Subject: [PATCH 127/330] Remove commment from test --- test_priorityq.py | 3 --- 1 file changed, 3 deletions(-) diff --git a/test_priorityq.py b/test_priorityq.py index 1c3fcb5..27f431f 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -3,9 +3,6 @@ from priorityq import PriorityQ, QNode -# We could parameterize inputs for non-numeric types, but val sorting -# will be odd in binheap. - @pytest.fixture() def QNode_list(): From 0a5c6653827f1a372980c40cc165fc07749e41cc Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 22:48:06 -0700 Subject: [PATCH 128/330] Change pop and peek return to value instead of QNode --- priorityq.py | 4 ++-- test_priorityq.py | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/priorityq.py b/priorityq.py index 03b80d0..2356a84 100644 --- a/priorityq.py +++ b/priorityq.py @@ -103,8 +103,8 @@ def insert(self, item, priority=None): def pop(self): """Remove and return the most important item from the queue.""" - return self.heap.pop() + return self.heap.pop().val def peek(self): """Return the most important item from queue without removal.""" - return self.heap[0] + return self.heap[0].val diff --git a/test_priorityq.py b/test_priorityq.py index 27f431f..6787f3a 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -113,12 +113,12 @@ def test_pop(base_pqueue): top_priority = QNode(9000, priority=0) length = len(base_pqueue) base_pqueue.insert(top_priority) - assert base_pqueue.pop() is top_priority + assert base_pqueue.pop() is top_priority.val assert len(base_pqueue) == length def test_peek(base_pqueue): top_priority = QNode(9000, priority=0) base_pqueue.insert(top_priority) - assert base_pqueue.peek() is top_priority + assert base_pqueue.peek() is top_priority.val assert base_pqueue[0] is top_priority From 23030155c0309d6f007e32cc9aec0e0d3909f56f Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 22:52:11 -0700 Subject: [PATCH 129/330] Use '==' instead of 'is' where appropriate --- test_priorityq.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_priorityq.py b/test_priorityq.py index 6787f3a..ec82572 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -113,12 +113,12 @@ def test_pop(base_pqueue): top_priority = QNode(9000, priority=0) length = len(base_pqueue) base_pqueue.insert(top_priority) - assert base_pqueue.pop() is top_priority.val + assert base_pqueue.pop() == top_priority.val assert len(base_pqueue) == length def test_peek(base_pqueue): top_priority = QNode(9000, priority=0) base_pqueue.insert(top_priority) - assert base_pqueue.peek() is top_priority.val + assert base_pqueue.peek() == top_priority.val assert base_pqueue[0] is top_priority From 2c5f347a27f4005d5948993d51789a6ae7afd609 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 22:57:35 -0700 Subject: [PATCH 130/330] Refactor __eq__ --- priorityq.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/priorityq.py b/priorityq.py index 2356a84..9eefd7e 100644 --- a/priorityq.py +++ b/priorityq.py @@ -33,8 +33,6 @@ def __eq__(self, other): """Overloads equality comparison to check priority, then order.""" if self.priority == other.priority: return self.order == other.order - elif self.priority is None or other.priority is None: - return False else: return self.priority == other.priority From b66190059793137f480dfa3737a6301646e361f8 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Sat, 4 Jul 2015 23:09:13 -0700 Subject: [PATCH 131/330] Remove unneeded _count attribute --- priorityq.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/priorityq.py b/priorityq.py index 9eefd7e..79d3671 100644 --- a/priorityq.py +++ b/priorityq.py @@ -62,7 +62,6 @@ def __init__(self, iterable=()): added this way will be given a priority of None. """ self.heap = BinaryHeap(iterable=()) - self._count = 0 for item in iterable: self.insert(item) @@ -93,11 +92,10 @@ def insert(self, item, priority=None): priority: the optional integer priority (0 is most important) """ if isinstance(item, QNode): - item.order = self._count + item.order = len(self) self.heap.push(item) else: - self.heap.push(QNode(item, priority=priority, order=self._count)) - self._count += 1 + self.heap.push(QNode(item, priority=priority, order=len(self))) def pop(self): """Remove and return the most important item from the queue.""" From 67293fd67acb37d69a0a5bad87239f71f6dccb41 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Sun, 5 Jul 2015 11:36:22 -0700 Subject: [PATCH 132/330] Added iterable support for (val, priority) containerized pairs; issue #16 --- priorityq.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/priorityq.py b/priorityq.py index 79d3671..5f4efad 100644 --- a/priorityq.py +++ b/priorityq.py @@ -60,10 +60,25 @@ def __init__(self, iterable=()): args: iterable: an optional iterable to add to the priority queue. Items added this way will be given a priority of None. + + each item inside iterable can be either: + * A QNode object + * A container with value, priority + * A non-iterable value + """ self.heap = BinaryHeap(iterable=()) for item in iterable: - self.insert(item) + try: + is_container = len(item) == 2 + except TypeError: # Case of QNode or non-iterable item + self.insert(item) + else: + if is_container: # Case of value, iterable + self.insert(item[0], item[1]) + else: + raise TypeError("More than two args: instantiation supports\ + non-iter value or value, priority container") def __repr__(self): return repr(self.heap) From 3ed457b2bd0fe1e4a044ba50d71aafe4c4eb88f1 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Sun, 5 Jul 2015 11:56:32 -0700 Subject: [PATCH 133/330] updated QNode __repr__ to display '(val, priority)' --- priorityq.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/priorityq.py b/priorityq.py index 5f4efad..f851f83 100644 --- a/priorityq.py +++ b/priorityq.py @@ -21,7 +21,7 @@ def __init__(self, val, priority=None, order=None): def __repr__(self): """Print representation of node.""" - return "{val}".format(val=self.val) + return "({val}, {priority})".format(val=self.val, priority=self.priority) def __str__(self): """Pretty print node value and priority.""" From 2c76e0e8406da7b4b4a0be258bf3671fd3755dae Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Sun, 5 Jul 2015 12:22:27 -0700 Subject: [PATCH 134/330] Added README content for PriorityQ; issue #15 --- README.md | 14 ++++++++++++++ priorityq.py | 5 +++-- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 477d186..3df3bc9 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,17 @@ Available methods include: * pop() * push() See the doc strings for implementation details. + +##PriorityQ +The PriorityQ data class is a binary tree that implements sorting primarily by priority value and +secondarily by insertion order. The PriorityQ defaults to minheap sorting for both. A QNode is implemented +as a base class to containerize the value and priority and to provide convenient APIs for comparison. + +Available methods include: +* insert(item) +* pop() +* peek() +See the doc strings for implementation details. + +Instantiation of a PriorityQ takes an iterable which may contain (value, priority) iterables, +non-iterable values, or QNode objects. diff --git a/priorityq.py b/priorityq.py index f851f83..4bf4e37 100644 --- a/priorityq.py +++ b/priorityq.py @@ -21,7 +21,8 @@ def __init__(self, val, priority=None, order=None): def __repr__(self): """Print representation of node.""" - return "({val}, {priority})".format(val=self.val, priority=self.priority) + return "({val}, {priority})".format(val=self.val, + priority=self.priority) def __str__(self): """Pretty print node value and priority.""" @@ -78,7 +79,7 @@ def __init__(self, iterable=()): self.insert(item[0], item[1]) else: raise TypeError("More than two args: instantiation supports\ - non-iter value or value, priority container") + non-iter value or iter of value, priority") def __repr__(self): return repr(self.heap) From 7852e843aa66e002d483139d6424531818dfbce0 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Sun, 5 Jul 2015 12:50:52 -0700 Subject: [PATCH 135/330] Added instantiation testing --- test_priorityq.py | 42 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/test_priorityq.py b/test_priorityq.py index ec82572..b5d5190 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -18,6 +18,48 @@ def QNode_list(): def base_pqueue(QNode_list): return PriorityQ(QNode_list) +valid_instantiation_args = [ + [QNode(1, 0), QNode(1, 1), QNode(2, 2)], + [1, 2, 3, 4, 5, 6, 7, 8, 9, 10], + [(1, 2), (2, 3), (3, 4), (4, 5), (5, 6)], + [QNode(1, 0), QNode(1, 1), 1, 2, 3, 4, 5, 6], + [QNode(1, 0), QNode(1, 1), (1, 2), (2, 3), (3, 4)], + [1, 2, 3, 4, 5, 6, 7, (1, 2), (2, 3), (3, 4), (4, 5)] +] + +invalid_instantiation_args = [ + [(1, 2, 3), (2, 3), (3, 4), (4, 5), (5, 6)], + [(1, 2), (2, 3), (3, 4), (4, 5, 1), (5, 6)] +] + + +@pytest.mark.parametrize("input", valid_instantiation_args) +def test_valid_instantiation_args(input): + tpq = PriorityQ(input) + assert tpq.pop() is not None + + +def test_empty_instantiation_args(): + tpq = PriorityQ() + with pytest.raises(IndexError): + tpq.pop() + + +@pytest.mark.parametrize("input", invalid_instantiation_args) +def test_invalid_instantiation_args(input): + with pytest.raises(TypeError): + tpq = PriorityQ(input) + + +def test_invalid_number_args_priorityq(): + with pytest.raises(TypeError): + tpq = PriorityQ(1, 2) + + +def test_invalid_number_args_qnode(): + with pytest.raises(TypeError): + tpq = QNode(1, 2, 3, 4) + def test_QNode_init_no_priority(): node1 = QNode(10) From eb65555c5056e0786ea14b18c57d274bbcc68962 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Sun, 5 Jul 2015 13:11:09 -0700 Subject: [PATCH 136/330] Added incremental tests for peek() and pop() --- test_priorityq.py | 77 +++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 75 insertions(+), 2 deletions(-) diff --git a/test_priorityq.py b/test_priorityq.py index b5d5190..86011ee 100644 --- a/test_priorityq.py +++ b/test_priorityq.py @@ -151,7 +151,7 @@ def test_insert_QNode_to_filled(base_pqueue): assert base_pqueue[0].priority == 0 -def test_pop(base_pqueue): +def test_pop1(base_pqueue): top_priority = QNode(9000, priority=0) length = len(base_pqueue) base_pqueue.insert(top_priority) @@ -159,8 +159,81 @@ def test_pop(base_pqueue): assert len(base_pqueue) == length -def test_peek(base_pqueue): +def test_pop2(base_pqueue): + top_priority = QNode(9000, priority=0) + length = len(base_pqueue) + base_pqueue.insert(top_priority) + base_pqueue.pop() + assert base_pqueue.pop() == 100 + + +def test_pop2(base_pqueue): + top_priority = QNode(9000, priority=0) + length = len(base_pqueue) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + assert base_pqueue.pop() == 5 + + +def test_pop3(base_pqueue): + top_priority = QNode(9000, priority=0) + length = len(base_pqueue) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + base_pqueue.pop() + assert base_pqueue.pop() == 10 + + +def test_pop4(base_pqueue): + top_priority = QNode(9000, priority=0) + length = len(base_pqueue) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + base_pqueue.pop() + base_pqueue.pop() + with pytest.raises(IndexError): + base_pqueue.pop() + + +def test_peek1(base_pqueue): top_priority = QNode(9000, priority=0) base_pqueue.insert(top_priority) assert base_pqueue.peek() == top_priority.val assert base_pqueue[0] is top_priority + + +def test_peek2(base_pqueue): + top_priority = QNode(9000, priority=0) + base_pqueue.insert(top_priority) + base_pqueue.pop() + assert base_pqueue.peek() == base_pqueue.pop() + + +def test_peek3(base_pqueue): + top_priority = QNode(9000, priority=0) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + assert base_pqueue.peek() == base_pqueue.pop() + + +def test_peek4(base_pqueue): + top_priority = QNode(9000, priority=0) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + assert base_pqueue.peek() == base_pqueue.pop() + + +def test_peek5(base_pqueue): + top_priority = QNode(9000, priority=0) + base_pqueue.insert(top_priority) + base_pqueue.pop() + base_pqueue.pop() + base_pqueue.pop() + base_pqueue.pop() + with pytest.raises(IndexError): + base_pqueue.peek() From ce3880181d8d6f20be81c5c63f65b58988ef1201 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 21:53:49 -0700 Subject: [PATCH 137/330] Sketch out methods --- graph.py | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 graph.py diff --git a/graph.py b/graph.py new file mode 100644 index 0000000..6e67c6d --- /dev/null +++ b/graph.py @@ -0,0 +1,48 @@ +from __future__ import unicode_literals + + +class Graph(object): + """A class for a simple graph data structure.""" + def __init__(self): + self.nodes = {} + + def __repr__(self): + pass + + def nodes(self): + """Return a list of all nodes in the graph.""" + return [node for node in self.nodes] + + def edges(self): + """Return a list of all edges in the graph.""" + return "edge list" + + def add_node(self, n): + """Add a new node to the graph.""" + self.nodes[n] = set() + + def add_edge(self, n1, n2): + """Add a new edge connecting n1 to n2.""" + self.nodes[n1].add(n2) + + def del_node(self, n): + """Delete a node from the graph.""" + del self.nodes[n] + for edgeset in self.nodes.values: + edgeset.discard(n) + + def del_edge(self, n1, n2): + """Delete the edge connecting two nodes from graph.""" + self.nodes[n1].remove(n2) + + def has_node(self, n): + """Check if a given node is in the graph.""" + return n in self.nodes + + def neighbors(self, n): + """Return a list of all nodes connected to 'n' by edges.""" + neighbors = [] + for node in self.nodes: + if n in self.node: + neighbors.append(node) + return neighbors From 6b971de14fbd987286b02bf6e469a1fbb7ad8695 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 22:03:34 -0700 Subject: [PATCH 138/330] Change dictionary name to avoid collision; fix dict.values() call --- graph.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/graph.py b/graph.py index 6e67c6d..a63d16f 100644 --- a/graph.py +++ b/graph.py @@ -4,14 +4,14 @@ class Graph(object): """A class for a simple graph data structure.""" def __init__(self): - self.nodes = {} + self.graph = {} def __repr__(self): - pass + return repr(self.graph) def nodes(self): """Return a list of all nodes in the graph.""" - return [node for node in self.nodes] + return [node for node in self.graph] def edges(self): """Return a list of all edges in the graph.""" @@ -19,30 +19,30 @@ def edges(self): def add_node(self, n): """Add a new node to the graph.""" - self.nodes[n] = set() + self.graph[n] = set() def add_edge(self, n1, n2): """Add a new edge connecting n1 to n2.""" - self.nodes[n1].add(n2) + self.graph[n1].add(n2) def del_node(self, n): """Delete a node from the graph.""" - del self.nodes[n] - for edgeset in self.nodes.values: + del self.graph[n] + for edgeset in self.graph.values(): edgeset.discard(n) def del_edge(self, n1, n2): """Delete the edge connecting two nodes from graph.""" - self.nodes[n1].remove(n2) + self.graph[n1].remove(n2) def has_node(self, n): """Check if a given node is in the graph.""" - return n in self.nodes + return n in self.graph def neighbors(self, n): """Return a list of all nodes connected to 'n' by edges.""" neighbors = [] - for node in self.nodes: + for node in self.graph: if n in self.node: neighbors.append(node) return neighbors From ecd7253327bf31623155cc315a8ec19a0f12d450 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 22:12:37 -0700 Subject: [PATCH 139/330] Fill out some special methods --- graph.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/graph.py b/graph.py index a63d16f..8825cf6 100644 --- a/graph.py +++ b/graph.py @@ -9,6 +9,21 @@ def __init__(self): def __repr__(self): return repr(self.graph) + def __len__(self): + return len(self.graph) + + def __iter__(self): + return iter(self.graph) + + def __getitem__(self, index): + return self.graph[index] + + def __setitem__(self, index, value): + self.graph[index] = value + + def __delitem__(self, index): + del self.graph[index] + def nodes(self): """Return a list of all nodes in the graph.""" return [node for node in self.graph] From d6950bda561f0524d1f0c8c5955281995cba6daa Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 22:23:11 -0700 Subject: [PATCH 140/330] Refactor a bit --- graph.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/graph.py b/graph.py index 8825cf6..6006daf 100644 --- a/graph.py +++ b/graph.py @@ -26,7 +26,7 @@ def __delitem__(self, index): def nodes(self): """Return a list of all nodes in the graph.""" - return [node for node in self.graph] + return [node for node in self] def edges(self): """Return a list of all edges in the graph.""" @@ -34,30 +34,31 @@ def edges(self): def add_node(self, n): """Add a new node to the graph.""" - self.graph[n] = set() + self[n] = set() def add_edge(self, n1, n2): """Add a new edge connecting n1 to n2.""" - self.graph[n1].add(n2) + self[n1].add(n2) def del_node(self, n): """Delete a node from the graph.""" - del self.graph[n] + del self[n] for edgeset in self.graph.values(): edgeset.discard(n) def del_edge(self, n1, n2): """Delete the edge connecting two nodes from graph.""" - self.graph[n1].remove(n2) + self[n1].remove(n2) def has_node(self, n): """Check if a given node is in the graph.""" - return n in self.graph + return n in self def neighbors(self, n): """Return a list of all nodes connected to 'n' by edges.""" neighbors = [] - for node in self.graph: - if n in self.node: + for node in self: + if n in self[node]: neighbors.append(node) return neighbors + From 921a57aceb4f88946376a1f3e5eb4c4bd9aa44f9 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 22:33:21 -0700 Subject: [PATCH 141/330] Add helper for testing --- graph.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/graph.py b/graph.py index 6006daf..86883b9 100644 --- a/graph.py +++ b/graph.py @@ -62,3 +62,19 @@ def neighbors(self, n): neighbors.append(node) return neighbors + def adjacent(self, n1, n2): + """Check if there is an edge connecting 'n1' and 'n2'.""" + return n2 in self[n1] or n1 in self[n2] + + +# helper start conditions for testing +def helper(): + g = Graph() + g.add_node(5) + g.add_node(10) + g.add_node(20) + g.add_edge(10, 5) + g.add_edge(10, 20) + g.add_edge(5, 10) + return g + From 12b867b6705f6ddd139863fe9e1544b49f14962f Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 22:43:24 -0700 Subject: [PATCH 142/330] Protect existing node from being overwritten --- graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/graph.py b/graph.py index 86883b9..486eae4 100644 --- a/graph.py +++ b/graph.py @@ -34,7 +34,7 @@ def edges(self): def add_node(self, n): """Add a new node to the graph.""" - self[n] = set() + self.graph.setdefault(n, set()) # Works! But should warn on 2nd add? def add_edge(self, n1, n2): """Add a new edge connecting n1 to n2.""" From 942f7becf82baca71a9ce85aed9ce11a97f14901 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 23:25:44 -0700 Subject: [PATCH 143/330] Complete first pass edges method. --- graph.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/graph.py b/graph.py index 486eae4..5d79928 100644 --- a/graph.py +++ b/graph.py @@ -30,11 +30,15 @@ def nodes(self): def edges(self): """Return a list of all edges in the graph.""" - return "edge list" + edge_list = [] + for node in self: + for edge in self[node]: + edge_list.append([node, edge]) + return edge_list def add_node(self, n): """Add a new node to the graph.""" - self.graph.setdefault(n, set()) # Works! But should warn on 2nd add? + self.graph.setdefault(n, set()) # Good! But should warn on 2nd add? def add_edge(self, n1, n2): """Add a new edge connecting n1 to n2.""" From 3caf246265a00263b8f71d3d9b45300068f486cf Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 23:30:48 -0700 Subject: [PATCH 144/330] Use generator under edges method --- graph.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/graph.py b/graph.py index 5d79928..85591b5 100644 --- a/graph.py +++ b/graph.py @@ -28,13 +28,13 @@ def nodes(self): """Return a list of all nodes in the graph.""" return [node for node in self] - def edges(self): - """Return a list of all edges in the graph.""" - edge_list = [] + def _iter_edges(self): for node in self: for edge in self[node]: - edge_list.append([node, edge]) - return edge_list + yield [node, edge] + + def edges(self): + return list(self._iter_edges()) def add_node(self, n): """Add a new node to the graph.""" From 6385d7af006fad500e99aafc0d260eb65873d6e0 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 23:35:10 -0700 Subject: [PATCH 145/330] Add another generator under neighbors method --- graph.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/graph.py b/graph.py index 85591b5..f527868 100644 --- a/graph.py +++ b/graph.py @@ -28,13 +28,13 @@ def nodes(self): """Return a list of all nodes in the graph.""" return [node for node in self] - def _iter_edges(self): + def iter_edges(self): for node in self: for edge in self[node]: yield [node, edge] def edges(self): - return list(self._iter_edges()) + return list(self.iter_edges()) def add_node(self, n): """Add a new node to the graph.""" @@ -58,13 +58,13 @@ def has_node(self, n): """Check if a given node is in the graph.""" return n in self - def neighbors(self, n): - """Return a list of all nodes connected to 'n' by edges.""" - neighbors = [] + def iter_neighbors(self, n): for node in self: if n in self[node]: - neighbors.append(node) - return neighbors + yield node + + def neighbors(self, n): + return list(self.iter_neighbors(n)) def adjacent(self, n1, n2): """Check if there is an edge connecting 'n1' and 'n2'.""" From 80125ebc4bc58901fd364f55071b0864abc2f1ea Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 23:46:20 -0700 Subject: [PATCH 146/330] Reorganize --- graph.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/graph.py b/graph.py index f527868..92fc289 100644 --- a/graph.py +++ b/graph.py @@ -24,18 +24,6 @@ def __setitem__(self, index, value): def __delitem__(self, index): del self.graph[index] - def nodes(self): - """Return a list of all nodes in the graph.""" - return [node for node in self] - - def iter_edges(self): - for node in self: - for edge in self[node]: - yield [node, edge] - - def edges(self): - return list(self.iter_edges()) - def add_node(self, n): """Add a new node to the graph.""" self.graph.setdefault(n, set()) # Good! But should warn on 2nd add? @@ -58,6 +46,18 @@ def has_node(self, n): """Check if a given node is in the graph.""" return n in self + def nodes(self): + """Return a list of all nodes in the graph.""" + return [node for node in self] + + def iter_edges(self): + for node in self: + for edge in self[node]: + yield [node, edge] + + def edges(self): + return list(self.iter_edges()) + def iter_neighbors(self, n): for node in self: if n in self[node]: From 34a04d7df8753928045e76c82935bd53636064c9 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 23:50:55 -0700 Subject: [PATCH 147/330] Add nodes if not exist with add_edge method --- graph.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graph.py b/graph.py index 92fc289..7f612df 100644 --- a/graph.py +++ b/graph.py @@ -30,6 +30,10 @@ def add_node(self, n): def add_edge(self, n1, n2): """Add a new edge connecting n1 to n2.""" + if not self.has_node(n1): + self.add_node(n1) + if not self.has_node(n2): + self.add_node(n2) self[n1].add(n2) def del_node(self, n): From e0adfae456c952c0d5118701094b591290f6c8fd Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 23:54:41 -0700 Subject: [PATCH 148/330] Improve add_edge method --- graph.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/graph.py b/graph.py index 7f612df..90d400a 100644 --- a/graph.py +++ b/graph.py @@ -30,11 +30,13 @@ def add_node(self, n): def add_edge(self, n1, n2): """Add a new edge connecting n1 to n2.""" - if not self.has_node(n1): - self.add_node(n1) if not self.has_node(n2): self.add_node(n2) - self[n1].add(n2) + try: + self[n1].add(n2) + except KeyError: + self.add_node(n1) + self[n1].add(n2) def del_node(self, n): """Delete a node from the graph.""" From d3da8c4098fe79de2c523b2e8aa72406d4e15089 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 6 Jul 2015 23:59:32 -0700 Subject: [PATCH 149/330] Raise appropriate exception for nonexistant node in neighbors method --- graph.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/graph.py b/graph.py index 90d400a..f6f7ca7 100644 --- a/graph.py +++ b/graph.py @@ -70,6 +70,8 @@ def iter_neighbors(self, n): yield node def neighbors(self, n): + if not self.has_node(n): + raise KeyError('Node is not in the graph') return list(self.iter_neighbors(n)) def adjacent(self, n1, n2): From 2b9603fcf03f92febb11a04421e8921ed28a1978 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 7 Jul 2015 13:58:09 -0700 Subject: [PATCH 150/330] Mark areas for refactor --- graph.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/graph.py b/graph.py index f6f7ca7..db85934 100644 --- a/graph.py +++ b/graph.py @@ -6,7 +6,7 @@ class Graph(object): def __init__(self): self.graph = {} - def __repr__(self): + def __repr__(self): # Consider how we want to repr this. return repr(self.graph) def __len__(self): @@ -21,12 +21,14 @@ def __getitem__(self, index): def __setitem__(self, index, value): self.graph[index] = value - def __delitem__(self, index): + def __delitem__(self, index): # Add cleanup del self.graph[index] def add_node(self, n): """Add a new node to the graph.""" - self.graph.setdefault(n, set()) # Good! But should warn on 2nd add? + if not self.has_node(n): + raise KeyError('Node already in graph.') + self[n] = set() def add_edge(self, n1, n2): """Add a new edge connecting n1 to n2.""" @@ -41,7 +43,7 @@ def add_edge(self, n1, n2): def del_node(self, n): """Delete a node from the graph.""" del self[n] - for edgeset in self.graph.values(): + for edgeset in self.graph.values(): # Move cleanup to __delitem__ edgeset.discard(n) def del_edge(self, n1, n2): @@ -59,7 +61,7 @@ def nodes(self): def iter_edges(self): for node in self: for edge in self[node]: - yield [node, edge] + yield (node, edge) def edges(self): return list(self.iter_edges()) @@ -70,9 +72,7 @@ def iter_neighbors(self, n): yield node def neighbors(self, n): - if not self.has_node(n): - raise KeyError('Node is not in the graph') - return list(self.iter_neighbors(n)) + return self[n] def adjacent(self, n1, n2): """Check if there is an edge connecting 'n1' and 'n2'.""" From af205246543fbb874ebf20b530fac04a3ba9808c Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 7 Jul 2015 14:01:08 -0700 Subject: [PATCH 151/330] Add some notes to graph script --- graph.py | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) create mode 100644 graph.py diff --git a/graph.py b/graph.py new file mode 100644 index 0000000..db85934 --- /dev/null +++ b/graph.py @@ -0,0 +1,92 @@ +from __future__ import unicode_literals + + +class Graph(object): + """A class for a simple graph data structure.""" + def __init__(self): + self.graph = {} + + def __repr__(self): # Consider how we want to repr this. + return repr(self.graph) + + def __len__(self): + return len(self.graph) + + def __iter__(self): + return iter(self.graph) + + def __getitem__(self, index): + return self.graph[index] + + def __setitem__(self, index, value): + self.graph[index] = value + + def __delitem__(self, index): # Add cleanup + del self.graph[index] + + def add_node(self, n): + """Add a new node to the graph.""" + if not self.has_node(n): + raise KeyError('Node already in graph.') + self[n] = set() + + def add_edge(self, n1, n2): + """Add a new edge connecting n1 to n2.""" + if not self.has_node(n2): + self.add_node(n2) + try: + self[n1].add(n2) + except KeyError: + self.add_node(n1) + self[n1].add(n2) + + def del_node(self, n): + """Delete a node from the graph.""" + del self[n] + for edgeset in self.graph.values(): # Move cleanup to __delitem__ + edgeset.discard(n) + + def del_edge(self, n1, n2): + """Delete the edge connecting two nodes from graph.""" + self[n1].remove(n2) + + def has_node(self, n): + """Check if a given node is in the graph.""" + return n in self + + def nodes(self): + """Return a list of all nodes in the graph.""" + return [node for node in self] + + def iter_edges(self): + for node in self: + for edge in self[node]: + yield (node, edge) + + def edges(self): + return list(self.iter_edges()) + + def iter_neighbors(self, n): + for node in self: + if n in self[node]: + yield node + + def neighbors(self, n): + return self[n] + + def adjacent(self, n1, n2): + """Check if there is an edge connecting 'n1' and 'n2'.""" + return n2 in self[n1] or n1 in self[n2] + + +# helper start conditions for testing +def helper(): + g = Graph() + g.add_node(5) + g.add_node(10) + g.add_node(20) + g.add_edge(10, 5) + g.add_edge(10, 20) + g.add_edge(5, 10) + return g + From a249b705241b4f5fe6146530c69baac196d247a8 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 7 Jul 2015 14:10:15 -0700 Subject: [PATCH 152/330] Fix neighbors method; add cleanu to __delitem__; raise appropriate error on add_node --- graph.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/graph.py b/graph.py index db85934..bf30f2f 100644 --- a/graph.py +++ b/graph.py @@ -21,12 +21,14 @@ def __getitem__(self, index): def __setitem__(self, index, value): self.graph[index] = value - def __delitem__(self, index): # Add cleanup + def __delitem__(self, index): del self.graph[index] + for edgeset in self.graph.values(): + edgeset.discard(index) def add_node(self, n): """Add a new node to the graph.""" - if not self.has_node(n): + if self.has_node(n): raise KeyError('Node already in graph.') self[n] = set() @@ -43,8 +45,6 @@ def add_edge(self, n1, n2): def del_node(self, n): """Delete a node from the graph.""" del self[n] - for edgeset in self.graph.values(): # Move cleanup to __delitem__ - edgeset.discard(n) def del_edge(self, n1, n2): """Delete the edge connecting two nodes from graph.""" From 6aa479e63f7c6628079b5a97bdfc78826e713260 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 7 Jul 2015 14:11:35 -0700 Subject: [PATCH 153/330] Add test_graph for TAD; I promiss TDD next time! --- test_graph.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test_graph.py diff --git a/test_graph.py b/test_graph.py new file mode 100644 index 0000000..e69de29 From 022d3a0176502d66d0f24ea556a655721ee620ae Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Tue, 7 Jul 2015 15:07:45 -0700 Subject: [PATCH 154/330] Improved doc strings; re issue #21 --- graph.py | 26 ++++++++++++++++++++------ 1 file changed, 20 insertions(+), 6 deletions(-) diff --git a/graph.py b/graph.py index db85934..e71f4b7 100644 --- a/graph.py +++ b/graph.py @@ -25,13 +25,17 @@ def __delitem__(self, index): # Add cleanup del self.graph[index] def add_node(self, n): - """Add a new node to the graph.""" + """Add a new node to the graph. Will raise an error if node + already exists. + + Note that node name 'n' needs to be a hashable or immutable value""" if not self.has_node(n): raise KeyError('Node already in graph.') self[n] = set() def add_edge(self, n1, n2): - """Add a new edge connecting n1 to n2.""" + """Add a new edge connecting n1 to n2. Will implicitly create + n1 and n2 if either do not exist.""" if not self.has_node(n2): self.add_node(n2) try: @@ -41,17 +45,20 @@ def add_edge(self, n1, n2): self[n1].add(n2) def del_node(self, n): - """Delete a node from the graph.""" + """Delete a node from the graph. Will cleanup all edges pointing + towards the node being deleted""" del self[n] for edgeset in self.graph.values(): # Move cleanup to __delitem__ edgeset.discard(n) def del_edge(self, n1, n2): - """Delete the edge connecting two nodes from graph.""" + """Delete stated edge connecting node n1 to n2. Will raise a KeyError + if the edge does not exist""" self[n1].remove(n2) def has_node(self, n): - """Check if a given node is in the graph.""" + """Check if a given node is in the graph, return True if it exists, + False otherwise""" return n in self def nodes(self): @@ -59,23 +66,30 @@ def nodes(self): return [node for node in self] def iter_edges(self): + """Generate an iterator packed with all existing edges in + (node, edge) format""" for node in self: for edge in self[node]: yield (node, edge) def edges(self): + """Return a list of all edges in (node, edge_node) format, + where node points to edge_node""" return list(self.iter_edges()) def iter_neighbors(self, n): + """Generate an iterator packed with all existing edges for node + n""" for node in self: if n in self[node]: yield node def neighbors(self, n): + """Return the set of edges that node n points towards""" return self[n] def adjacent(self, n1, n2): - """Check if there is an edge connecting 'n1' and 'n2'.""" + """Check if there is an edge pointing from node n1 to n2""" return n2 in self[n1] or n1 in self[n2] From 13ff138b4c6416cd44fc8373ef584e6555b08bcb Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 7 Jul 2015 19:55:33 -0700 Subject: [PATCH 155/330] Add simple fixtures for tests. refs #22 --- test_graph.py | 23 +++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/test_graph.py b/test_graph.py index e69de29..0a840a2 100644 --- a/test_graph.py +++ b/test_graph.py @@ -0,0 +1,23 @@ +from __future__ import unicode_literals +import pytest + +from graph import Graph + + +@pytest.fixture() +def graph_empty(): + g = Graph() + return g + + +@pytest.fixture() +def graph_filled(): + g = Graph() + g.graph = { + 5: set([10]), + 10: set([5, 20, 15]), + 15: set(), + 20: set([5]), + 25: set(), + 30: set() + } From 32f316e74811298f4c37969262a92c7bec2f83ca Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 7 Jul 2015 21:58:59 -0700 Subject: [PATCH 156/330] Add some add_node tests --- test_graph.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test_graph.py b/test_graph.py index 0a840a2..2a6393c 100644 --- a/test_graph.py +++ b/test_graph.py @@ -21,3 +21,23 @@ def graph_filled(): 25: set(), 30: set() } + return g + + +def test_valid_constructor(): + g = Graph() + assert isinstance(g, Graph) + assert isinstance(g.graph, dict) + assert len(g.graph) == 0 and len(g) == 0 + + +def test_invalid_constructor(): + with pytest.raises(TypeError): + Graph(10) + + +def test_add_node_to_empty(graph_empty): + graph_empty.add_node(10) + assert 10 in graph_empty + assert isinstance(graph_empty[10], set) and len(graph_empty[10]) == 0 + From 71ef5d2994dbbf4aa993ba1110eb5404de1f6ac3 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 7 Jul 2015 22:04:27 -0700 Subject: [PATCH 157/330] Add further tests for add_node --- test_graph.py | 23 ++++++++++++++++++++--- 1 file changed, 20 insertions(+), 3 deletions(-) diff --git a/test_graph.py b/test_graph.py index 2a6393c..3952509 100644 --- a/test_graph.py +++ b/test_graph.py @@ -37,7 +37,24 @@ def test_invalid_constructor(): def test_add_node_to_empty(graph_empty): - graph_empty.add_node(10) - assert 10 in graph_empty - assert isinstance(graph_empty[10], set) and len(graph_empty[10]) == 0 + graph_empty.add_node(40) + assert 40 in graph_empty + assert isinstance(graph_empty[40], set) and len(graph_empty[40]) == 0 + + +def test_add_node_to_filled(graph_filled): + graph_filled.add_node(40) + assert 40 in graph_filled + assert isinstance(graph_filled[40], set) + assert len(graph_filled[40]) == 0 + + +def test_add_node_to_filled_existing_node(graph_filled): + with pytest.raises(KeyError): + graph_filled.add_node(5) + + +def test_add_node_wrong_type(graph_empty): + with pytest.raises(TypeError): + graph_empty.add_node([1, 2, 3]) From 663d950a77985d22fbed22961951ac9deac3d2e0 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Tue, 7 Jul 2015 22:07:46 -0700 Subject: [PATCH 158/330] test for .nodes() --- test_graph.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/test_graph.py b/test_graph.py index 0a840a2..e526a5c 100644 --- a/test_graph.py +++ b/test_graph.py @@ -21,3 +21,18 @@ def graph_filled(): 25: set(), 30: set() } + return g + + +def test_nodes_empty(graph_empty): + out = graph_empty.nodes() + assert str(out) == "[]" + + +def test_nodes_filled(graph_filled): + out = graph_filled.nodes() + expected_nodes = set([5, 10, 15, 20, 25, 30]) + assert set(out) == expected_nodes + assert len(out) == 6 + + From 30272f624bd73a96f1d9ef5cb056296738264e71 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Tue, 7 Jul 2015 22:14:10 -0700 Subject: [PATCH 159/330] test for .edges() --- test_graph.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/test_graph.py b/test_graph.py index e526a5c..c470866 100644 --- a/test_graph.py +++ b/test_graph.py @@ -27,6 +27,7 @@ def graph_filled(): def test_nodes_empty(graph_empty): out = graph_empty.nodes() assert str(out) == "[]" + assert len(out) == 0 def test_nodes_filled(graph_filled): @@ -36,3 +37,14 @@ def test_nodes_filled(graph_filled): assert len(out) == 6 +def test_edges_empty(graph_empty): + out = graph_empty.edges() + assert str(out) == "[]" + assert len(out) == 0 + + +def test_edges_filled(graph_filled): + out = graph_filled.edges() + expected_edges = set([(5, 10), (10, 5), (10, 20), (10, 15), (20, 5)]) + assert set(out) == expected_edges + assert len(out) == 5 \ No newline at end of file From 56897f2652eee80420a37c4d986feff226f2da6a Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Tue, 7 Jul 2015 22:15:32 -0700 Subject: [PATCH 160/330] test for .edges() --- test_graph.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/test_graph.py b/test_graph.py index c470866..9115071 100644 --- a/test_graph.py +++ b/test_graph.py @@ -47,4 +47,8 @@ def test_edges_filled(graph_filled): out = graph_filled.edges() expected_edges = set([(5, 10), (10, 5), (10, 20), (10, 15), (20, 5)]) assert set(out) == expected_edges - assert len(out) == 5 \ No newline at end of file + assert len(out) == 5 + + +def test_has_edge_empty(graph_empty): + assert graph_empty.has_edge() == False From 18fe13489d2fc4b221f72ee36373cbe1fd59c473 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 7 Jul 2015 22:21:27 -0700 Subject: [PATCH 161/330] Add further tests; reorganize --- test_graph.py | 53 ++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 46 insertions(+), 7 deletions(-) diff --git a/test_graph.py b/test_graph.py index 3952509..591c3ed 100644 --- a/test_graph.py +++ b/test_graph.py @@ -37,16 +37,20 @@ def test_invalid_constructor(): def test_add_node_to_empty(graph_empty): - graph_empty.add_node(40) - assert 40 in graph_empty - assert isinstance(graph_empty[40], set) and len(graph_empty[40]) == 0 + g = graph_empty + new = 40 + g.add_node(new) + assert new in g + assert isinstance(g[new], set) and len(g[new]) == 0 def test_add_node_to_filled(graph_filled): - graph_filled.add_node(40) - assert 40 in graph_filled - assert isinstance(graph_filled[40], set) - assert len(graph_filled[40]) == 0 + g = graph_filled + new = 40 + g.add_node(new) + assert new in g + assert isinstance(g[new], set) + assert len(g[new]) == 0 def test_add_node_to_filled_existing_node(graph_filled): @@ -58,3 +62,38 @@ def test_add_node_wrong_type(graph_empty): with pytest.raises(TypeError): graph_empty.add_node([1, 2, 3]) + +def test_add_edge_new_nodes(graph_empty): + g = graph_empty + n1, n2 = 30, 40 + g.add_edge(n1, n2) + assert n1 in g and n2 in g + assert n2 in g[n1] + assert len(g[n2]) == 0 + + +def test_add_edge_n2_new(graph_filled): + g = graph_filled + n1, n2 = 30, 40 + g.add_edge(n1, n2) + assert n1 in g and n2 in g + assert n2 in g[n1] + assert len(g[n2]) == 0 + + +def test_add_edge_n1_new(graph_filled): + g = graph_filled + n1, n2 = 1, 5 + g.add_edge(n1, n2) + assert n1 in g and n2 in g + assert n2 in g[n1] + assert len(g[n2]) == 1 + + +def test_add_edge_n1_n2_exist(graph_filled): + g = graph_filled + n1, n2 = 10, 15 + g.add_edge(n1, n2) + assert n1 in g and n2 in g + assert n2 in g[n1] + assert len(g[n2]) == 0 From 41bd2d85f7cc1b509a84342b1a549f28d2d1d4fb Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 7 Jul 2015 22:24:44 -0700 Subject: [PATCH 162/330] Complete add edge tests --- test_graph.py | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/test_graph.py b/test_graph.py index 591c3ed..3410e2d 100644 --- a/test_graph.py +++ b/test_graph.py @@ -90,10 +90,19 @@ def test_add_edge_n1_new(graph_filled): assert len(g[n2]) == 1 -def test_add_edge_n1_n2_exist(graph_filled): +def test_add_edge_n1_n2_exist_with_edges(graph_filled): g = graph_filled - n1, n2 = 10, 15 + n1, n2 = 20, 10 g.add_edge(n1, n2) assert n1 in g and n2 in g assert n2 in g[n1] - assert len(g[n2]) == 0 + assert len(g[n1]) == 2 and len(g[n2]) == 3 + + +def test_add_edge_n1_n2_exist_without_edges(graph_filled): + g = graph_filled + n1, n2 = 25, 30 + g.add_edge(n1, n2) + assert n1 in g and n2 in g + assert n2 in g[n1] + assert len(g[n1]) == 1 and len(g[n2]) == 0 From d3983108a4dd2545f13ccaef4d56c5b411ed2b6d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 7 Jul 2015 22:35:29 -0700 Subject: [PATCH 163/330] Complete outlined tests --- test_graph.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test_graph.py b/test_graph.py index 3410e2d..6fe44ed 100644 --- a/test_graph.py +++ b/test_graph.py @@ -106,3 +106,29 @@ def test_add_edge_n1_n2_exist_without_edges(graph_filled): assert n1 in g and n2 in g assert n2 in g[n1] assert len(g[n1]) == 1 and len(g[n2]) == 0 + + +def test_del_node_exists(graph_filled): + g = graph_filled + g.del_node(5) + assert 5 not in g + assert 5 not in g.graph.values() + + +def test_del_node_empty_error(graph_empty): + with pytest.raises(KeyError): + graph_empty.del_node(10) + + +def test_del_edge_exists(graph_filled): + g = graph_filled + n1, n2 = 10, 5 + g.del_edge(n1, n2) + assert n1 in g and n2 in g + assert n2 not in g[n1] + + +def test_del_edge_not_exist(graph_filled): + with pytest.raises(KeyError): + graph_filled.del_edge(100, 200) + From 68546d97f4595ecee6006fc57fd833cdca1738af Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Tue, 7 Jul 2015 22:56:33 -0700 Subject: [PATCH 164/330] .adjacent() was bidirectional, fixed now. Tests complete. --- graph.py | 2 +- test_graph.py | 62 +++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 61 insertions(+), 3 deletions(-) diff --git a/graph.py b/graph.py index 8856971..edc49b2 100644 --- a/graph.py +++ b/graph.py @@ -90,7 +90,7 @@ def neighbors(self, n): def adjacent(self, n1, n2): """Check if there is an edge pointing from node n1 to n2""" - return n2 in self[n1] or n1 in self[n2] + return n2 in self[n1] # helper start conditions for testing diff --git a/test_graph.py b/test_graph.py index 9115071..c124231 100644 --- a/test_graph.py +++ b/test_graph.py @@ -50,5 +50,63 @@ def test_edges_filled(graph_filled): assert len(out) == 5 -def test_has_edge_empty(graph_empty): - assert graph_empty.has_edge() == False +def test_host_node_empty(graph_empty): + unexpected_nodes = set([0, 2, 7, 13, 27, 33]) + for node in unexpected_nodes: + assert graph_empty.has_node(node) == False + + +def test_has_node_filled(graph_filled): + expected_nodes = set([5, 10, 15, 20, 25, 30]) + unexpected_nodes = set([0, 2, 7, 13, 27, 33]) + for node in expected_nodes: + assert graph_filled.has_node(node) == True + for node in unexpected_nodes: + assert graph_filled.has_node(node) == False + + +def test_neighbors_empty(graph_empty): + with pytest.raises(KeyError): + graph_empty.neighbors(3) + + +def test_neighbors_filled_not_present(graph_filled): + with pytest.raises(KeyError): + graph_filled.neighbors(3) + + +# input, expected output for neighbors in graph_filled +neighbor_params = [ + (5, set([10])), + (10, set([5, 20, 15])), + (20, set([5])), + (25, set()), + (30, set()) +] + + +@pytest.mark.parametrize("input, out", neighbor_params) +def test_neighbors_filled_present(input, out, graph_filled): + assert graph_filled.neighbors(input) == out + + +def test_adjacent_empty(graph_empty): + with pytest.raises(KeyError): + graph_empty.adjacent(4, 2) + + +def test_adjacent_filled_existing(graph_filled): + expected_edges = set([(5, 10), (10, 5), (10, 20), (10, 15), (20, 5)]) + for a, b in expected_edges: + assert graph_filled.adjacent(a, b) == True + + +def test_adjacent_filled_existing_node_unexisting_edge(graph_filled): + bad_edges = set([(5, 15), (20, 10), (5, 20)]) + for a, b in bad_edges: + assert graph_filled.adjacent(a, b) == False + + +def test_adjacent_filled_missing_node(graph_filled): + with pytest.raises(KeyError): + graph_filled.adjacent(7, 3) From 1b662343b897f10d115b288379472cbb02cda22b Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Tue, 7 Jul 2015 22:57:40 -0700 Subject: [PATCH 165/330] Lint fixes --- test_graph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_graph.py b/test_graph.py index c124231..3295244 100644 --- a/test_graph.py +++ b/test_graph.py @@ -87,7 +87,7 @@ def test_neighbors_filled_not_present(graph_filled): @pytest.mark.parametrize("input, out", neighbor_params) def test_neighbors_filled_present(input, out, graph_filled): - assert graph_filled.neighbors(input) == out + assert graph_filled.neighbors(input) == out def test_adjacent_empty(graph_empty): @@ -98,13 +98,13 @@ def test_adjacent_empty(graph_empty): def test_adjacent_filled_existing(graph_filled): expected_edges = set([(5, 10), (10, 5), (10, 20), (10, 15), (20, 5)]) for a, b in expected_edges: - assert graph_filled.adjacent(a, b) == True + assert graph_filled.adjacent(a, b) is True def test_adjacent_filled_existing_node_unexisting_edge(graph_filled): bad_edges = set([(5, 15), (20, 10), (5, 20)]) for a, b in bad_edges: - assert graph_filled.adjacent(a, b) == False + assert graph_filled.adjacent(a, b) is False def test_adjacent_filled_missing_node(graph_filled): From d9a5ecd7b24c4c002f59320857ec6497af9ab67d Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Wed, 8 Jul 2015 14:02:45 -0700 Subject: [PATCH 166/330] cleanup re: #27 --- graph.py | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/graph.py b/graph.py index edc49b2..0b40e60 100644 --- a/graph.py +++ b/graph.py @@ -6,7 +6,7 @@ class Graph(object): def __init__(self): self.graph = {} - def __repr__(self): # Consider how we want to repr this. + def __repr__(self): return repr(self.graph) def __len__(self): @@ -91,16 +91,3 @@ def neighbors(self, n): def adjacent(self, n1, n2): """Check if there is an edge pointing from node n1 to n2""" return n2 in self[n1] - - -# helper start conditions for testing -def helper(): - g = Graph() - g.add_node(5) - g.add_node(10) - g.add_node(20) - g.add_edge(10, 5) - g.add_edge(10, 20) - g.add_edge(5, 10) - return g - From c80e0f9cd52cc54f620f367f6d6256e74aab699d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 8 Jul 2015 14:05:00 -0700 Subject: [PATCH 167/330] Fix linting errors --- graph.py | 33 ++++++++++++++++++++------------- 1 file changed, 20 insertions(+), 13 deletions(-) diff --git a/graph.py b/graph.py index 0b40e60..66eb2b1 100644 --- a/graph.py +++ b/graph.py @@ -27,17 +27,19 @@ def __delitem__(self, index): edgeset.discard(index) def add_node(self, n): - """Add a new node to the graph. Will raise an error if node + """Add a new node to the graph. Will raise an error if node already exists. - Note that node name 'n' needs to be a hashable or immutable value""" + Note that node name 'n' needs to be a hashable or immutable value + """ if self.has_node(n): raise KeyError('Node already in graph.') self[n] = set() def add_edge(self, n1, n2): - """Add a new edge connecting n1 to n2. Will implicitly create - n1 and n2 if either do not exist.""" + """Add a new edge connecting n1 to n2. Will implicitly create + n1 and n2 if either do not exist. + """ if not self.has_node(n2): self.add_node(n2) try: @@ -48,17 +50,20 @@ def add_edge(self, n1, n2): def del_node(self, n): """Delete a node from the graph. Will cleanup all edges pointing - towards the node being deleted""" + towards the node being deleted. + """ del self[n] def del_edge(self, n1, n2): """Delete stated edge connecting node n1 to n2. Will raise a KeyError - if the edge does not exist""" + if the edge does not exist. + """ self[n1].remove(n2) def has_node(self, n): """Check if a given node is in the graph, return True if it exists, - False otherwise""" + False otherwise. + """ return n in self def nodes(self): @@ -67,27 +72,29 @@ def nodes(self): def iter_edges(self): """Generate an iterator packed with all existing edges in - (node, edge) format""" + (node, edge) format. + """ for node in self: for edge in self[node]: yield (node, edge) def edges(self): """Return a list of all edges in (node, edge_node) format, - where node points to edge_node""" + where node points to edge_node. + """ return list(self.iter_edges()) def iter_neighbors(self, n): - """Generate an iterator packed with all existing edges for node - n""" + """Generate an iterator packed with all existing edges for node.""" for node in self: if n in self[node]: yield node def neighbors(self, n): - """Return the set of edges that node n points towards""" + """Return the set of edges that node n points towards.""" return self[n] def adjacent(self, n1, n2): - """Check if there is an edge pointing from node n1 to n2""" + """Check if there is an edge pointing from node n1 to n2.""" return n2 in self[n1] + From c6e1ac38695740bf0e3b3a36ddee9f2d8db3bee2 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 8 Jul 2015 22:50:42 -0700 Subject: [PATCH 168/330] Fix remaining style issues --- test_graph.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/test_graph.py b/test_graph.py index 98c9cc1..c65e1ff 100644 --- a/test_graph.py +++ b/test_graph.py @@ -23,6 +23,7 @@ def graph_filled(): } return g + def test_valid_constructor(): g = Graph() assert isinstance(g, Graph) @@ -161,16 +162,16 @@ def test_edges_filled(graph_filled): def test_host_node_empty(graph_empty): unexpected_nodes = set([0, 2, 7, 13, 27, 33]) for node in unexpected_nodes: - assert graph_empty.has_node(node) == False + assert graph_empty.has_node(node) is False def test_has_node_filled(graph_filled): expected_nodes = set([5, 10, 15, 20, 25, 30]) unexpected_nodes = set([0, 2, 7, 13, 27, 33]) for node in expected_nodes: - assert graph_filled.has_node(node) == True + assert graph_filled.has_node(node) is True for node in unexpected_nodes: - assert graph_filled.has_node(node) == False + assert graph_filled.has_node(node) is False def test_neighbors_empty(graph_empty): From f7a9b5e133d2e7e166c575315073cc663378dbd6 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 9 Jul 2015 20:29:16 -0700 Subject: [PATCH 169/330] Add first-pass traversal methods --- graph.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/graph.py b/graph.py index 66eb2b1..f9cf7d1 100644 --- a/graph.py +++ b/graph.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals +from queue import Queue + class Graph(object): """A class for a simple graph data structure.""" @@ -98,3 +100,33 @@ def adjacent(self, n1, n2): """Check if there is an edge pointing from node n1 to n2.""" return n2 in self[n1] + def depth_first_traversal(self, start): + """Perform full depth-first traversal of graph from start.""" + path = [] + visited = set() + + def step(node, path, visited): + if node not in visited: + path.append(node) + visited.add(node) + for child in iter(self[node]): + step(child, path, visited) + return + + step(start, path, visited) + return path + + def breadth_first_traversal(self, start): + path = [] + visited = set() + temp = Queue([start]) + + while temp: + node = temp.dequeue() + if node not in visited: + path.append(node) + visited.add(node) + for child in self[node]: + if child not in visited: + temp.enqueue(child) + return path From eb442964d2d41c47afb1a5271820183177036090 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 9 Jul 2015 20:35:00 -0700 Subject: [PATCH 170/330] Improve docstrings. refs #31 --- graph.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/graph.py b/graph.py index f9cf7d1..0da981d 100644 --- a/graph.py +++ b/graph.py @@ -101,7 +101,11 @@ def adjacent(self, n1, n2): return n2 in self[n1] def depth_first_traversal(self, start): - """Perform full depth-first traversal of graph from start.""" + """Perform full depth-first traversal of graph from start. + + args: + start: the node to start traversal + """ path = [] visited = set() @@ -117,6 +121,11 @@ def step(node, path, visited): return path def breadth_first_traversal(self, start): + """Perform a full breadth first traversal of graph from start. + + args: + start: the node to start traversal + """ path = [] visited = set() temp = Queue([start]) From 62edc744e2b8ced8ccfac65d70d78a70d75092d8 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 9 Jul 2015 22:39:37 -0700 Subject: [PATCH 171/330] Improve docstrings --- graph.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/graph.py b/graph.py index 0da981d..dc47401 100644 --- a/graph.py +++ b/graph.py @@ -105,6 +105,8 @@ def depth_first_traversal(self, start): args: start: the node to start traversal + + returns: the list of nodes traversed """ path = [] visited = set() @@ -125,6 +127,8 @@ def breadth_first_traversal(self, start): args: start: the node to start traversal + + returns: a list of nodes traversed """ path = [] visited = set() From 9be6cff87095575b0faa5b66bdcd014b5df1c371 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 9 Jul 2015 22:41:27 -0700 Subject: [PATCH 172/330] Fix Sublime EOF --- graph.py | 1 + 1 file changed, 1 insertion(+) diff --git a/graph.py b/graph.py index dc47401..d839500 100644 --- a/graph.py +++ b/graph.py @@ -143,3 +143,4 @@ def breadth_first_traversal(self, start): if child not in visited: temp.enqueue(child) return path + From 20b939be85f2c2301e1479fb4442624a4e86e86d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 10 Jul 2015 09:48:45 -0700 Subject: [PATCH 173/330] Update readme. refs #30 --- README.md | 26 +++++++++++++++++++++++++- 1 file changed, 25 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 3df3bc9..a411bb5 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ Available methods include: See the doc strings for implementation details. ##PriorityQ -The PriorityQ data class is a binary tree that implements sorting primarily by priority value and +The PriorityQ data class is a binary tree that implements sorting primarily by priority value and secondarily by insertion order. The PriorityQ defaults to minheap sorting for both. A QNode is implemented as a base class to containerize the value and priority and to provide convenient APIs for comparison. @@ -50,3 +50,27 @@ See the doc strings for implementation details. Instantiation of a PriorityQ takes an iterable which may contain (value, priority) iterables, non-iterable values, or QNode objects. + +##Graph +The graph data class is a network consisting nodes with an arbitrary number of references (edges) to other +nodes in the graph. Methods allows abilities such as adding, removing, and checking the existance of nodes +and edges in the graph. Additionaly, the graph class contains to traversal methods. Given a start node, the +methods will traverse the entire network reachable from that node and return the path travelled as a list of +nodes travelled. Both [depth-first](https://en.wikipedia.org/wiki/Graph_traversal#Depth-first_search) and [breadth first](https://en.wikipedia.org/wiki/Graph_traversal#Breadth-first_search) traversal methods are available. + +Available methods include: + +* nodes() +* edges() +* add_node(n) +* add_edge(n1, n2) +* del_node(n) +* del_edge(n1, n2) +* has_node(n) +* neighbors(n) +* adjacent(n1, n2) +* depth_first_traversal(start) +* breadth_first_traversal(start) + +See the doc strings for implementation details. + From 439efce8bdbc7ca703683212bbd08a91c953ed43 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Sat, 11 Jul 2015 18:55:36 -0700 Subject: [PATCH 174/330] added tests for depth and breadth traversals --- test_graph.py | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test_graph.py b/test_graph.py index c65e1ff..f9c3420 100644 --- a/test_graph.py +++ b/test_graph.py @@ -24,6 +24,21 @@ def graph_filled(): return g +@pytest.fixture() +def graph_filled_for_traversal(): + g = Graph() + g.graph = { + 1: set([2, 3]), + 2: set([4, 5]), + 3: set([6, 7]), + 4: set([]), + 5: set([3]), + 6: set([5]), + 7: set([6]) + } + return g + + def test_valid_constructor(): g = Graph() assert isinstance(g, Graph) @@ -219,3 +234,41 @@ def test_adjacent_filled_existing_node_unexisting_edge(graph_filled): def test_adjacent_filled_missing_node(graph_filled): with pytest.raises(KeyError): graph_filled.adjacent(7, 3) + + +def test_depth_first_traversal(graph_filled_for_traversal): + level1 = set([1]) + level2 = set([2, 3]) + level3 = set([4, 5, 6, 7]) + output = graph_filled_for_traversal.depth_first_traversal(1) + assert len(output) == 7 + assert output[0] in level1 + assert output[1] in level2 + assert output[2] in level3 + assert output[3] in level3 + assert output[4] in level2 + assert output[5] in level3 + + +def test_breadth_first_traversal(graph_filled_for_traversal): + level1 = set([1]) + level2 = set([2, 3]) + level3 = set([4, 5, 6, 7]) + output = graph_filled_for_traversal.breadth_first_traversal(1) + assert len(output) == 7 + assert output[0] in level1 + assert output[1] in level2 + assert output[2] in level2 + assert output[3] in level3 + assert output[4] in level3 + assert output[5] in level3 + + +def test_depth_first_traversal_no_arg(graph_filled_for_traversal): + with pytest.raises(TypeError): + graph_filled_for_traversal.depth_first_traversal() + + +def test_breadth_first_traversal_no_arg(graph_filled_for_traversal): + with pytest.raises(TypeError): + graph_filled_for_traversal.breadth_first_traversal() \ No newline at end of file From db6e7e77e099a9de323969ab732954f7524720c7 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 20 Jul 2015 08:46:53 -0700 Subject: [PATCH 175/330] Add simple travis yml file --- .travis.yml | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 .travis.yml diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..c5473c0 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,28 @@ +# Set up notification options +notifications: + email: + # change is when the repo status goes from pass to fail or vice versa + on_success: change + on_failure: always + +# specify language +language: python +python: + - "2.7" + +## command to install dependencies +install: + - 'pip install -r requirements.txt' + +## Script to run +script: py.test + +branches: + ## whitelist + only: + - master + - staging + +# blacklist + except: + - /^.*test.*$/ From 8bac40789a44da837bf23e2ce47cae80d056cdc6 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 20 Jul 2015 08:53:48 -0700 Subject: [PATCH 176/330] Update README with travis badge --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index a411bb5..2f6f7d1 100644 --- a/README.md +++ b/README.md @@ -74,3 +74,4 @@ Available methods include: See the doc strings for implementation details. +[![Build Status](https://travis-ci.org/jonathanstallings/data-structures.svg?branch=feature%2Fbinheap%2Fjonathan)](https://travis-ci.org/jonathanstallings/data-structures) From df55cb9fac4ffb67b3487bbf8b9f2bd9c5b88717 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 20 Jul 2015 08:55:59 -0700 Subject: [PATCH 177/330] Add requirements.txt --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 requirements.txt diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b9fc0bd --- /dev/null +++ b/requirements.txt @@ -0,0 +1,2 @@ +py==1.4.29 +pytest==2.7.2 From f3890f6b96dc2d184bf743f600f797514829b678 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 20 Jul 2015 13:33:33 -0700 Subject: [PATCH 178/330] Test travis --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 2f6f7d1..55b39a5 100644 --- a/README.md +++ b/README.md @@ -75,3 +75,5 @@ Available methods include: See the doc strings for implementation details. [![Build Status](https://travis-ci.org/jonathanstallings/data-structures.svg?branch=feature%2Fbinheap%2Fjonathan)](https://travis-ci.org/jonathanstallings/data-structures) + +Test From 064b1d1f3a00f2e34d6bce9d3a95cbcd28b1f9ae Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 20 Jul 2015 13:36:25 -0700 Subject: [PATCH 179/330] Remove test line in readme --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 55b39a5..ef4ea5c 100644 --- a/README.md +++ b/README.md @@ -76,4 +76,3 @@ See the doc strings for implementation details. [![Build Status](https://travis-ci.org/jonathanstallings/data-structures.svg?branch=feature%2Fbinheap%2Fjonathan)](https://travis-ci.org/jonathanstallings/data-structures) -Test From 686f0245f333c59c0b4260e0aa701452071d3d3a Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 20 Jul 2015 13:58:59 -0700 Subject: [PATCH 180/330] Update test fixtures with weighted dictionary structure --- test_graph.py | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/test_graph.py b/test_graph.py index f9c3420..76d9367 100644 --- a/test_graph.py +++ b/test_graph.py @@ -14,12 +14,12 @@ def graph_empty(): def graph_filled(): g = Graph() g.graph = { - 5: set([10]), - 10: set([5, 20, 15]), - 15: set(), - 20: set([5]), - 25: set(), - 30: set() + 'A': {'B': 10}, + 'B': {'A': 5, 'D': 15, 'C': 20}, + 'C': {}, + 'D': {'A': 5}, + 'E': {}, + 'F': {} } return g @@ -28,13 +28,13 @@ def graph_filled(): def graph_filled_for_traversal(): g = Graph() g.graph = { - 1: set([2, 3]), - 2: set([4, 5]), - 3: set([6, 7]), - 4: set([]), - 5: set([3]), - 6: set([5]), - 7: set([6]) + 'A': {'B': 10, 'C': 15}, + 'B': {'D': 15, 'E': 5}, + 'C': {'F': 50, 'G': 25}, + 'D': {}, + 'E': {'C': 5}, + 'F': {'E': 10}, + 'G': {'F': 20} } return g @@ -271,4 +271,4 @@ def test_depth_first_traversal_no_arg(graph_filled_for_traversal): def test_breadth_first_traversal_no_arg(graph_filled_for_traversal): with pytest.raises(TypeError): - graph_filled_for_traversal.breadth_first_traversal() \ No newline at end of file + graph_filled_for_traversal.breadth_first_traversal() From 25c116594526f39f3e82fb70f73a673700d1b29e Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Mon, 20 Jul 2015 14:05:20 -0700 Subject: [PATCH 181/330] refactored from a dict>set to dict>dict for containerizing edges; edge weights can now be stored as inner dict value --- graph.py | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/graph.py b/graph.py index d839500..2aeb53c 100644 --- a/graph.py +++ b/graph.py @@ -26,7 +26,7 @@ def __setitem__(self, index, value): def __delitem__(self, index): del self.graph[index] for edgeset in self.graph.values(): - edgeset.discard(index) + edgeset.pop(index, None) def add_node(self, n): """Add a new node to the graph. Will raise an error if node @@ -36,19 +36,20 @@ def add_node(self, n): """ if self.has_node(n): raise KeyError('Node already in graph.') - self[n] = set() + self[n] = dict() - def add_edge(self, n1, n2): + def add_edge(self, node1, node2, edge_weight): """Add a new edge connecting n1 to n2. Will implicitly create n1 and n2 if either do not exist. """ - if not self.has_node(n2): - self.add_node(n2) + if not self.has_node(node2): + self.add_node(node2) try: - self[n1].add(n2) + self[node1].add(node2) except KeyError: - self.add_node(n1) - self[n1].add(n2) + self.add_node(node1) + self[node1].add(node2) + self[node1][node2] = edge_weight def del_node(self, n): """Delete a node from the graph. Will cleanup all edges pointing @@ -60,7 +61,7 @@ def del_edge(self, n1, n2): """Delete stated edge connecting node n1 to n2. Will raise a KeyError if the edge does not exist. """ - self[n1].remove(n2) + self[n1].pop(n2) def has_node(self, n): """Check if a given node is in the graph, return True if it exists, @@ -143,4 +144,3 @@ def breadth_first_traversal(self, start): if child not in visited: temp.enqueue(child) return path - From aad9c05051089722bf7cf53206ee531df276eb03 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 20 Jul 2015 14:20:51 -0700 Subject: [PATCH 182/330] Begin changing old tests to weighted dict structure --- test_graph.py | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/test_graph.py b/test_graph.py index 76d9367..4a6caab 100644 --- a/test_graph.py +++ b/test_graph.py @@ -53,24 +53,24 @@ def test_invalid_constructor(): def test_add_node_to_empty(graph_empty): g = graph_empty - new = 40 + new = 'A' g.add_node(new) assert new in g - assert isinstance(g[new], set) and len(g[new]) == 0 + assert isinstance(g[new], dict) and len(g[new]) == 0 def test_add_node_to_filled(graph_filled): g = graph_filled - new = 40 + new = 'G' g.add_node(new) assert new in g - assert isinstance(g[new], set) + assert isinstance(g[new], dict) assert len(g[new]) == 0 def test_add_node_to_filled_existing_node(graph_filled): with pytest.raises(KeyError): - graph_filled.add_node(5) + graph_filled.add_node('B') def test_add_node_wrong_type(graph_empty): @@ -80,35 +80,35 @@ def test_add_node_wrong_type(graph_empty): def test_add_edge_new_nodes(graph_empty): g = graph_empty - n1, n2 = 30, 40 - g.add_edge(n1, n2) + n1, n2 = 'A', 'B' + g.add_edge(n1, n2, 10) assert n1 in g and n2 in g assert n2 in g[n1] - assert len(g[n2]) == 0 + assert g[n1][n2] == 10 def test_add_edge_n2_new(graph_filled): g = graph_filled - n1, n2 = 30, 40 - g.add_edge(n1, n2) + n1, n2 = 'A', 'G' + g.add_edge(n1, n2, 10) assert n1 in g and n2 in g assert n2 in g[n1] - assert len(g[n2]) == 0 + assert g[n1][n2] == 10 def test_add_edge_n1_new(graph_filled): g = graph_filled - n1, n2 = 1, 5 - g.add_edge(n1, n2) + n1, n2 = 'G', 'A' + g.add_edge(n1, n2, 10) assert n1 in g and n2 in g assert n2 in g[n1] - assert len(g[n2]) == 1 + assert g[n1][n2] == 10 def test_add_edge_n1_n2_exist_with_edges(graph_filled): g = graph_filled - n1, n2 = 20, 10 - g.add_edge(n1, n2) + n1, n2 = 'D', 'A' + g.add_edge(n1, n2, 10) assert n1 in g and n2 in g assert n2 in g[n1] assert len(g[n1]) == 2 and len(g[n2]) == 3 From 76368ac8556b1a95b1e49a960e83fbb6275e26a1 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Mon, 20 Jul 2015 14:23:31 -0700 Subject: [PATCH 183/330] refactored some set.add() to dict.update() --- graph.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/graph.py b/graph.py index 2aeb53c..6b386a1 100644 --- a/graph.py +++ b/graph.py @@ -45,11 +45,10 @@ def add_edge(self, node1, node2, edge_weight): if not self.has_node(node2): self.add_node(node2) try: - self[node1].add(node2) + self[node1].update({node2: edge_weight}) except KeyError: self.add_node(node1) - self[node1].add(node2) - self[node1][node2] = edge_weight + self[node1].update({node2: edge_weight}) def del_node(self, n): """Delete a node from the graph. Will cleanup all edges pointing From 9b18d2d64e1dcf3a8ad4fbdcde7f62d01da0db49 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 20 Jul 2015 14:38:26 -0700 Subject: [PATCH 184/330] Complete work through line 202 on test conversion --- test_graph.py | 34 ++++++++++++++++++---------------- 1 file changed, 18 insertions(+), 16 deletions(-) diff --git a/test_graph.py b/test_graph.py index 4a6caab..6fb7e4e 100644 --- a/test_graph.py +++ b/test_graph.py @@ -111,33 +111,33 @@ def test_add_edge_n1_n2_exist_with_edges(graph_filled): g.add_edge(n1, n2, 10) assert n1 in g and n2 in g assert n2 in g[n1] - assert len(g[n1]) == 2 and len(g[n2]) == 3 + assert g[n1][n2] == 10 def test_add_edge_n1_n2_exist_without_edges(graph_filled): g = graph_filled - n1, n2 = 25, 30 - g.add_edge(n1, n2) + n1, n2 = 'E', 'F' + g.add_edge(n1, n2, 10) assert n1 in g and n2 in g assert n2 in g[n1] - assert len(g[n1]) == 1 and len(g[n2]) == 0 + assert g[n1][n2] == 10 def test_del_node_exists(graph_filled): g = graph_filled - g.del_node(5) - assert 5 not in g - assert 5 not in g.graph.values() + g.del_node('A') + assert 'A' not in g + assert 'A' not in g.graph.values() def test_del_node_empty_error(graph_empty): with pytest.raises(KeyError): - graph_empty.del_node(10) + graph_empty.del_node('A') def test_del_edge_exists(graph_filled): g = graph_filled - n1, n2 = 10, 5 + n1, n2 = 'B', 'A' g.del_edge(n1, n2) assert n1 in g and n2 in g assert n2 not in g[n1] @@ -145,7 +145,7 @@ def test_del_edge_exists(graph_filled): def test_del_edge_not_exist(graph_filled): with pytest.raises(KeyError): - graph_filled.del_edge(100, 200) + graph_filled.del_edge('X', 'Y') def test_nodes_empty(graph_empty): @@ -156,7 +156,7 @@ def test_nodes_empty(graph_empty): def test_nodes_filled(graph_filled): out = graph_filled.nodes() - expected_nodes = set([5, 10, 15, 20, 25, 30]) + expected_nodes = set(['A', 'B', 'C', 'D', 'E', 'F']) assert set(out) == expected_nodes assert len(out) == 6 @@ -169,7 +169,9 @@ def test_edges_empty(graph_empty): def test_edges_filled(graph_filled): out = graph_filled.edges() - expected_edges = set([(5, 10), (10, 5), (10, 20), (10, 15), (20, 5)]) + expected_edges = set([ + ('A', 'B'), ('B', 'A'), ('B', 'D'), ('B', 'C'), ('D', 'A') + ]) assert set(out) == expected_edges assert len(out) == 5 @@ -181,8 +183,8 @@ def test_host_node_empty(graph_empty): def test_has_node_filled(graph_filled): - expected_nodes = set([5, 10, 15, 20, 25, 30]) - unexpected_nodes = set([0, 2, 7, 13, 27, 33]) + expected_nodes = set(['A', 'B', 'C', 'D', 'E', 'F']) + unexpected_nodes = set(['G', 'H', 'I', 'J', 'K', 10]) for node in expected_nodes: assert graph_filled.has_node(node) is True for node in unexpected_nodes: @@ -191,12 +193,12 @@ def test_has_node_filled(graph_filled): def test_neighbors_empty(graph_empty): with pytest.raises(KeyError): - graph_empty.neighbors(3) + graph_empty.neighbors('G') def test_neighbors_filled_not_present(graph_filled): with pytest.raises(KeyError): - graph_filled.neighbors(3) + graph_filled.neighbors('G') # input, expected output for neighbors in graph_filled From b825b3ef16420d65bcd5e78b176acdabb8566310 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 20 Jul 2015 14:53:13 -0700 Subject: [PATCH 185/330] Complete test conversion up to traversal tests --- test_graph.py | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/test_graph.py b/test_graph.py index 6fb7e4e..d4e1cc2 100644 --- a/test_graph.py +++ b/test_graph.py @@ -203,11 +203,12 @@ def test_neighbors_filled_not_present(graph_filled): # input, expected output for neighbors in graph_filled neighbor_params = [ - (5, set([10])), - (10, set([5, 20, 15])), - (20, set([5])), - (25, set()), - (30, set()) + ('A', {'B'}), + ('B', {'A', 'D', 'C'}), + ('C', {}) + ('D', {'A'}), + ('E', {}), + ('F', {}) ] @@ -218,24 +219,26 @@ def test_neighbors_filled_present(input, out, graph_filled): def test_adjacent_empty(graph_empty): with pytest.raises(KeyError): - graph_empty.adjacent(4, 2) + graph_empty.adjacent('A', 'B') def test_adjacent_filled_existing(graph_filled): - expected_edges = set([(5, 10), (10, 5), (10, 20), (10, 15), (20, 5)]) + expected_edges = set([ + ('A', 'B'), ('B', 'A'), ('B', 'D'), ('B', 'C'), ('D', 'A') + ]) for a, b in expected_edges: assert graph_filled.adjacent(a, b) is True def test_adjacent_filled_existing_node_unexisting_edge(graph_filled): - bad_edges = set([(5, 15), (20, 10), (5, 20)]) + bad_edges = set([('A', 'C'), ('D', 'B'), ('A', 'D')]) for a, b in bad_edges: assert graph_filled.adjacent(a, b) is False def test_adjacent_filled_missing_node(graph_filled): with pytest.raises(KeyError): - graph_filled.adjacent(7, 3) + graph_filled.adjacent('G', 'H') def test_depth_first_traversal(graph_filled_for_traversal): From 4ef1bc36d6a132dce0e52a64c7eaf513e041841e Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Mon, 20 Jul 2015 15:00:27 -0700 Subject: [PATCH 186/330] refactored tests for traversal --- test_graph.py | 32 +++++++++++++++++--------------- 1 file changed, 17 insertions(+), 15 deletions(-) diff --git a/test_graph.py b/test_graph.py index 4a6caab..e21689b 100644 --- a/test_graph.py +++ b/test_graph.py @@ -29,7 +29,7 @@ def graph_filled_for_traversal(): g = Graph() g.graph = { 'A': {'B': 10, 'C': 15}, - 'B': {'D': 15, 'E': 5}, + 'B': {'D': 15, 'E': 5, 'C': 5}, 'C': {'F': 50, 'G': 25}, 'D': {}, 'E': {'C': 5}, @@ -237,32 +237,26 @@ def test_adjacent_filled_missing_node(graph_filled): def test_depth_first_traversal(graph_filled_for_traversal): - level1 = set([1]) - level2 = set([2, 3]) - level3 = set([4, 5, 6, 7]) - output = graph_filled_for_traversal.depth_first_traversal(1) + level1 = set(['A']) + level2 = set(['B', 'C']) + level3 = set(['D', 'E', 'F', 'G']) + output = graph_filled_for_traversal.depth_first_traversal('A') assert len(output) == 7 assert output[0] in level1 assert output[1] in level2 assert output[2] in level3 - assert output[3] in level3 - assert output[4] in level2 - assert output[5] in level3 def test_breadth_first_traversal(graph_filled_for_traversal): - level1 = set([1]) - level2 = set([2, 3]) - level3 = set([4, 5, 6, 7]) - output = graph_filled_for_traversal.breadth_first_traversal(1) + level1 = set(['A']) + level2 = set(['B', 'C']) + level3 = set(['D', 'E', 'F', 'G']) + output = graph_filled_for_traversal.breadth_first_traversal('A') assert len(output) == 7 assert output[0] in level1 assert output[1] in level2 assert output[2] in level2 assert output[3] in level3 - assert output[4] in level3 - assert output[5] in level3 - def test_depth_first_traversal_no_arg(graph_filled_for_traversal): with pytest.raises(TypeError): @@ -272,3 +266,11 @@ def test_depth_first_traversal_no_arg(graph_filled_for_traversal): def test_breadth_first_traversal_no_arg(graph_filled_for_traversal): with pytest.raises(TypeError): graph_filled_for_traversal.breadth_first_traversal() + + +def test_graph_weighted_edges(graph_filled): + assert graph_filled['A']['B'] == 10 + assert graph_filled['B']['A'] == 5 + assert graph_filled['B']['D'] == 15 + assert graph_filled['B']['C'] == 20 + assert graph_filled['D']['A'] == 5 From 36161311d5b80202208b8aab7d741e9692a419ab Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 20 Jul 2015 14:55:22 -0700 Subject: [PATCH 187/330] Fix typo --- test_graph.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test_graph.py b/test_graph.py index abb9cab..4345631 100644 --- a/test_graph.py +++ b/test_graph.py @@ -205,7 +205,7 @@ def test_neighbors_filled_not_present(graph_filled): neighbor_params = [ ('A', {'B'}), ('B', {'A', 'D', 'C'}), - ('C', {}) + ('C', {}), ('D', {'A'}), ('E', {}), ('F', {}) From ce667f048b52e42be8119cf8a80fcb710ed0bd1a Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 20 Jul 2015 15:00:19 -0700 Subject: [PATCH 188/330] Fix error with neighbors tests --- test_graph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test_graph.py b/test_graph.py index 4345631..6071492 100644 --- a/test_graph.py +++ b/test_graph.py @@ -203,10 +203,10 @@ def test_neighbors_filled_not_present(graph_filled): # input, expected output for neighbors in graph_filled neighbor_params = [ - ('A', {'B'}), - ('B', {'A', 'D', 'C'}), + ('A', {'B': 10}), + ('B', {'A': 5, 'D': 15, 'C': 20}), ('C', {}), - ('D', {'A'}), + ('D', {'A': 5}), ('E', {}), ('F', {}) ] From 7571c49b3436f9b84df9985d8df3d0d2b551a3c3 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 21 Jul 2015 16:16:28 -0700 Subject: [PATCH 189/330] Complete first pass at uniform cost search --- graph.py | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/graph.py b/graph.py index 6b386a1..08ed454 100644 --- a/graph.py +++ b/graph.py @@ -1,6 +1,7 @@ from __future__ import unicode_literals from queue import Queue +from priorityq import QNode, PriorityQ class Graph(object): @@ -143,3 +144,24 @@ def breadth_first_traversal(self, start): if child not in visited: temp.enqueue(child) return path + + def uniform_cost_search(self, start, goal): + """Return the shortest path from start to goal node.""" + q = PriorityQ() + q.insert((0, start, []), priority=0) + seen = {} + + while q: + cost, point, path = q.pop() + if point in seen and seen[point] < cost: + continue + path = path + [point] + if point == goal: + return path + for child in self[point]: + child_cost = self.point[child] + if child not in seen: + tot_cost = child_cost + cost + q.insert((tot_cost, child, path), priority=tot_cost) + seen[point] = cost + return None From 6a783881fe69bbfb26fcc6117ef2272b6fe50ec7 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 21 Jul 2015 16:43:49 -0700 Subject: [PATCH 190/330] Fix typo --- graph.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/graph.py b/graph.py index 08ed454..17e0254 100644 --- a/graph.py +++ b/graph.py @@ -1,7 +1,7 @@ from __future__ import unicode_literals from queue import Queue -from priorityq import QNode, PriorityQ +import priorityq as pq class Graph(object): @@ -147,7 +147,7 @@ def breadth_first_traversal(self, start): def uniform_cost_search(self, start, goal): """Return the shortest path from start to goal node.""" - q = PriorityQ() + q = pq.PriorityQ() q.insert((0, start, []), priority=0) seen = {} @@ -159,7 +159,7 @@ def uniform_cost_search(self, start, goal): if point == goal: return path for child in self[point]: - child_cost = self.point[child] + child_cost = self[point][child] if child not in seen: tot_cost = child_cost + cost q.insert((tot_cost, child, path), priority=tot_cost) From 47bd4f57c83fac4a383825466093300e029c32c6 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 21 Jul 2015 17:00:32 -0700 Subject: [PATCH 191/330] Improve docstring --- graph.py | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/graph.py b/graph.py index 17e0254..494f6ac 100644 --- a/graph.py +++ b/graph.py @@ -146,7 +146,12 @@ def breadth_first_traversal(self, start): return path def uniform_cost_search(self, start, goal): - """Return the shortest path from start to goal node.""" + """Return the shortest path from start to goal node. + + args: + start: the node to begin the path + goal: the node to end the path + """ q = pq.PriorityQ() q.insert((0, start, []), priority=0) seen = {} From 6e7171cfa31a21a54317dedc741f77591a9ec1a7 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 21 Jul 2015 17:32:00 -0700 Subject: [PATCH 192/330] Add simple test for uniform cost search --- test_graph.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/test_graph.py b/test_graph.py index 6071492..e30ead1 100644 --- a/test_graph.py +++ b/test_graph.py @@ -29,7 +29,7 @@ def graph_filled_for_traversal(): g = Graph() g.graph = { 'A': {'B': 10, 'C': 15}, - 'B': {'D': 15, 'E': 5, 'C': 5}, + 'B': {'D': 15, 'E': 5, 'C': 2}, 'C': {'F': 50, 'G': 25}, 'D': {}, 'E': {'C': 5}, @@ -263,6 +263,7 @@ def test_breadth_first_traversal(graph_filled_for_traversal): assert output[2] in level2 assert output[3] in level3 + def test_depth_first_traversal_no_arg(graph_filled_for_traversal): with pytest.raises(TypeError): graph_filled_for_traversal.depth_first_traversal() @@ -279,3 +280,10 @@ def test_graph_weighted_edges(graph_filled): assert graph_filled['B']['D'] == 15 assert graph_filled['B']['C'] == 20 assert graph_filled['D']['A'] == 5 + + +def test_uniform_cost_search(graph_filled_for_traversal): + g = graph_filled_for_traversal + expected = ['A', 'B', 'C', 'G', 'F'] + actual = g.uniform_cost_search('A', 'F') + assert expected == actual From fd58d809d638a454af3278db4746b1aac552d55e Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Tue, 21 Jul 2015 17:55:25 -0700 Subject: [PATCH 193/330] added bellmanford algorithm --- graph.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/graph.py b/graph.py index 6b386a1..5f73bd9 100644 --- a/graph.py +++ b/graph.py @@ -143,3 +143,50 @@ def breadth_first_traversal(self, start): if child not in visited: temp.enqueue(child) return path + + + def bellmanford(self, node1, node2): + """Find the shortest path from node1 to node2 + + It is possible to access a graph with negatives weights using this + algorithm + + This implementation is adapted for Python from the pseudocode + here: + https://en.wikipedia.org/wiki/Bellman%E2%80%93Ford_algorithm#Algorithm + + This algorithm is currently generating useful, extra data that + isn't being returned. May want to update the API to allow access + to this info. + """ + + distance = {node: float("inf") if node != node1 else 0 + for node in self} + + predecessor = {node: None for node in self} + + for _ in self: + for node in self: + # Point of this and inner loop is to grab each of the edges by + # node times; expect O( node * edge ); note that edges are nested + # under nodes dict ; hence two inner loops needed to grab these + for edgen, weight in self[node].iteritems(): + if distance[node] + weight < distance[edgen]: + distance[edgen] = distance[node] + weight + predecessor[edgen] = node + for node in self: + # Consistancy check per pseudo code; check for loops where + # cost is negative per cycle + for edgen, weight in self[node].iteritems(): + if distance[node] + weight < distance[edgen]: + raise ZeroDivisionError + # Now build a path from predecessor dict + rpath = [] + pointer = node2 + while True: + rpath.append(pointer) + if pointer == node1: + break + pointer = predecessor[pointer] + rpath.reverse() + return rpath From c50b93c466cd81a2166593f0fddd71b3c427885d Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Tue, 21 Jul 2015 18:04:52 -0700 Subject: [PATCH 194/330] added tests for bellmanford --- test_graph.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 52 insertions(+) diff --git a/test_graph.py b/test_graph.py index e30ead1..5ef2385 100644 --- a/test_graph.py +++ b/test_graph.py @@ -39,6 +39,36 @@ def graph_filled_for_traversal(): return g +@pytest.fixture() +def graph_filled_for_traversal_neg_edges(): + g = Graph() + g.graph = { + 'A': {'B': -5, 'C': 15}, + 'B': {'D': 15, 'E': 5, 'C': 2}, + 'C': {'F': 50, 'G': -20}, + 'D': {}, + 'E': {'C': 5}, + 'F': {'E': 10}, + 'G': {'F': 20} + } + return g + + +@pytest.fixture() +def graph_filled_for_traversal_neg_edges_loop(): + g = Graph() + g.graph = { + 'A': {'B': -5, 'C': 15}, + 'B': {'D': 15, 'E': 5, 'C': 2}, + 'C': {'F': 50, 'G': -20}, + 'D': {}, + 'E': {'C': -50}, + 'F': {'E': 10}, + 'G': {'F': 20} + } + return g + + def test_valid_constructor(): g = Graph() assert isinstance(g, Graph) @@ -287,3 +317,25 @@ def test_uniform_cost_search(graph_filled_for_traversal): expected = ['A', 'B', 'C', 'G', 'F'] actual = g.uniform_cost_search('A', 'F') assert expected == actual + + +def test_bellmanford(graph_filled_for_traversal): + g = graph_filled_for_traversal + expected = ['A', 'B', 'C', 'G', 'F'] + actual = g.bellmanford('A', 'F') + assert expected == actual + + +def test_bellmanford_neg_edges(graph_filled_for_traversal_neg_edges): + g = graph_filled_for_traversal_neg_edges + expected = ['A', 'B', 'C', 'G', 'F'] + actual = g.bellmanford('A', 'F') + assert expected == actual + + +def test_bellmanford_neg_edges_with_loop( + graph_filled_for_traversal_neg_edges_loop): + g = graph_filled_for_traversal_neg_edges_loop + expected = ['A', 'B', 'C', 'G', 'F'] + with pytest.raises(ZeroDivisionError): + g.bellmanford('A', 'F') \ No newline at end of file From ddae4300807983329479c3e6553b0d929b697188 Mon Sep 17 00:00:00 2001 From: Jason Tyler Date: Tue, 21 Jul 2015 18:22:04 -0700 Subject: [PATCH 195/330] Updates to readme --- README.md | 15 ++++++++++++++- 1 file changed, 14 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ef4ea5c..21d95e8 100644 --- a/README.md +++ b/README.md @@ -71,8 +71,21 @@ Available methods include: * adjacent(n1, n2) * depth_first_traversal(start) * breadth_first_traversal(start) +* uniform_cost_search(n1, n2) +* bellmanford(n1, n2) -See the doc strings for implementation details. +The uniform_cost_search method returns a path that corresponds to the +shortest path between n1 and n2. This algorithm tracks historical paths +and is able to search for the shortest path in a relatively uncostly way. +Time complexity is O(edges + node log nodes) with relatively low memory +overhead. + +The bellmanfor search method also returns the same path, but has the added +ability to handle edges with negative values and detect negative feedback +loops. It is relatively robust, but at added time complexity cost of +O(nodes * edges). + +See the doc strings for additional implementation details. [![Build Status](https://travis-ci.org/jonathanstallings/data-structures.svg?branch=feature%2Fbinheap%2Fjonathan)](https://travis-ci.org/jonathanstallings/data-structures) From ea787298b8617741f89ff83a78ac00b5937a9b23 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 21 Jul 2015 21:13:34 -0700 Subject: [PATCH 196/330] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 21d95e8..76a403c 100644 --- a/README.md +++ b/README.md @@ -87,5 +87,5 @@ O(nodes * edges). See the doc strings for additional implementation details. -[![Build Status](https://travis-ci.org/jonathanstallings/data-structures.svg?branch=feature%2Fbinheap%2Fjonathan)](https://travis-ci.org/jonathanstallings/data-structures) +[![Build Status](https://travis-ci.org/jonathanstallings/data-structures.svg?branch=master)](https://travis-ci.org/jonathanstallings/data-structures) From 8783857492024ec21c1f6fd934984e0dbe8d4fef Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 13:09:40 -0700 Subject: [PATCH 197/330] Add bst.py --- bst.py | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 bst.py diff --git a/bst.py b/bst.py new file mode 100644 index 0000000..7623238 --- /dev/null +++ b/bst.py @@ -0,0 +1,2 @@ +from __future__ import unicode_literals + From 2d6dfd0f5e41e19053f5cfa9a2a91432eb475d1d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 13:16:23 -0700 Subject: [PATCH 198/330] Frame out methods --- bst.py | 43 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/bst.py b/bst.py index 7623238..cf26ce7 100644 --- a/bst.py +++ b/bst.py @@ -1,2 +1,45 @@ from __future__ import unicode_literals + +class Node(object): + """A class for a binary search tree node.""" + def __init__(self, data): + self.data = data + self.left = None + self.right = None + + def insert(self, val): + """insert the value val into the BST. + + If val is already present, it will be ignored. + """ + pass + + def contains(self, val): + """return True if val is in the BST, False if not""" + pass + + def size(self): + """return the integer size of the BST. + + (equal to the total number of values stored in the tree), + 0 if the tree is empty. + """ + pass + + def depth(self): + """return an integer representing the total number of levels in the tree. + + If there is one value, the depth should be 1, if two values it'll be 2, + if three values it may be 2 or three, depending, etc. + """ + pass + + def balance(self): + """return an integer, positive or negative represents how balanced the tree is. + + Trees which are higher on the left than the right should return a positive value, + trees which are higher on the right than the left should return a negative value. + An ideallyl-balanced tree should return 0. + """ + pass From 81e73d029f1646df244f2f8371c2c5d30fb08539 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 13:21:06 -0700 Subject: [PATCH 199/330] Change data to val --- bst.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/bst.py b/bst.py index cf26ce7..9d83934 100644 --- a/bst.py +++ b/bst.py @@ -3,16 +3,19 @@ class Node(object): """A class for a binary search tree node.""" - def __init__(self, data): - self.data = data + def __init__(self, val): + self.val = val self.left = None self.right = None def insert(self, val): - """insert the value val into the BST. + """insert a node with val into the BST. If val is already present, it will be ignored. """ + if self.contains(val): + return + pass def contains(self, val): From bd1bf7bf4a6336d074359fa8e85d3304e290534d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 13:25:37 -0700 Subject: [PATCH 200/330] First pass insert method --- bst.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/bst.py b/bst.py index 9d83934..e480b83 100644 --- a/bst.py +++ b/bst.py @@ -15,8 +15,16 @@ def insert(self, val): """ if self.contains(val): return - - pass + if val < self.val: + if self.left is None: + self.left = Node(val) + else: + self.left.insert(val) + elif val > self.val: + if self.right is None: + self.right = Node(val) + else: + self.right.insert(val) def contains(self, val): """return True if val is in the BST, False if not""" From ed531831301a746758d8a86a837954b99df8d560 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 13:36:38 -0700 Subject: [PATCH 201/330] Add contains method --- bst.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/bst.py b/bst.py index e480b83..c8ecd52 100644 --- a/bst.py +++ b/bst.py @@ -28,7 +28,16 @@ def insert(self, val): def contains(self, val): """return True if val is in the BST, False if not""" - pass + if val < self.val: + if self.left is None: + return False + return self.left.contains(val) + elif val > self.val: + if self.right is None: + return False + return self.left.contains(val) + else: + return True def size(self): """return the integer size of the BST. From 80756e43b7ae942cf1c71b611b7f5855a95335bf Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 13:52:27 -0700 Subject: [PATCH 202/330] Implement size using count; balance method --- bst.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/bst.py b/bst.py index c8ecd52..44a9bfd 100644 --- a/bst.py +++ b/bst.py @@ -7,6 +7,7 @@ def __init__(self, val): self.val = val self.left = None self.right = None + self.count = 0 def insert(self, val): """insert a node with val into the BST. @@ -15,6 +16,7 @@ def insert(self, val): """ if self.contains(val): return + self.count += 1 if val < self.val: if self.left is None: self.left = Node(val) @@ -45,7 +47,7 @@ def size(self): (equal to the total number of values stored in the tree), 0 if the tree is empty. """ - pass + return self.count def depth(self): """return an integer representing the total number of levels in the tree. @@ -62,4 +64,9 @@ def balance(self): trees which are higher on the right than the left should return a negative value. An ideallyl-balanced tree should return 0. """ - pass + if self.left.depth() > self.right.depth(): + return 1 + elif self.left.depth() < self.right.depth(): + return -1 + else: + return 0 From 3e73a07694e389825b48678c9394fce6df8686fc Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Wed, 22 Jul 2015 14:14:12 -0700 Subject: [PATCH 203/330] Commit initial tests to repo --- test_bst.py | 74 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 test_bst.py diff --git a/test_bst.py b/test_bst.py new file mode 100644 index 0000000..01aa04c --- /dev/null +++ b/test_bst.py @@ -0,0 +1,74 @@ +from __future__ import unicode_literals +from bst import Node +from random import randint +import pytest + + +@pytest.fixture() +def rand_setup(): + root = Node(randint(1, 100)) + for idx in range(20): + val = randint(1, 100) + root.insert(val) + + return root + + +@pytest.fixture() +def fixed_setup(): + root = Node(25) + root.insert(15) + root.insert(30) + root.insert(9) + root.insert(17) + root.insert(21) + root.insert(39) + root.insert(12) + root.insert(24) + root.insert(40) + root.insert(14) + + +def test_insert(rand_setup): + pre_size = rand_setup.size() + rand = randint(1, 100) + rand_setup.insert(rand) + assert rand_setup.insert(rand) is None + rand_setup.insert(rand) + post_size = rand_setup.size() + assert post_size > pre_size + assert post_size == pre_size + 1 + + +def test_contains(rand_setup): + rand = randint(1, 100) + rand_setup.insert(rand) + assert rand_setup.contains(rand) is True + + +def test_size(rand_setup): + pre_size = rand_setup.size() + rand = randint(1, 100) + rand_setup.insert(rand) + rand_setup.insert(rand) + post_size = rand_setup.size() + assert post_size > pre_size + assert post_size == pre_size + 1 + + +def test_depth(fixed_setup): + assert fixed_setup.left.depth() == 5 + assert fixed_setup.right.depth() == 4 + fixed_setup.insert(13) + assert fixed_setup.left.depth() == 6 + + +def test_balance(rand_setup): + left = rand_setup.left.depth() + right = rand_setup.right.depth() + if left > right: + assert rand_setup.balance() == -int + elif right > left: + assert rand_setup.balance() == abs(int) + else: + assert rand_setup.balance() == 0 From f6254e28329fc6d340cea1fcf7f8b3723e88b457 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 14:16:19 -0700 Subject: [PATCH 204/330] Fix typo --- bst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bst.py b/bst.py index 44a9bfd..12bc976 100644 --- a/bst.py +++ b/bst.py @@ -7,7 +7,7 @@ def __init__(self, val): self.val = val self.left = None self.right = None - self.count = 0 + self.count = 1 def insert(self, val): """insert a node with val into the BST. From ebd8c3622028fc03b9f008a7e13310b454d864f7 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 14:22:43 -0700 Subject: [PATCH 205/330] First pass depth method --- bst.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/bst.py b/bst.py index 12bc976..518a82d 100644 --- a/bst.py +++ b/bst.py @@ -55,7 +55,9 @@ def depth(self): If there is one value, the depth should be 1, if two values it'll be 2, if three values it may be 2 or three, depending, etc. """ - pass + left_depth = self.left.depth() if self.left else 0 + right_depth = self.right.depth() if self.right else 0 + return max(left_depth, right_depth) + 1 def balance(self): """return an integer, positive or negative represents how balanced the tree is. From 3c1e6b5b583560d8e65c5bc6ae84af1467f4b70b Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Wed, 22 Jul 2015 14:55:19 -0700 Subject: [PATCH 206/330] Update tests --- test_bst.py | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/test_bst.py b/test_bst.py index 01aa04c..f9cdf6c 100644 --- a/test_bst.py +++ b/test_bst.py @@ -9,7 +9,10 @@ def rand_setup(): root = Node(randint(1, 100)) for idx in range(20): val = randint(1, 100) - root.insert(val) + try: + root.insert(val) + except AttributeError: + continue return root @@ -28,6 +31,8 @@ def fixed_setup(): root.insert(40) root.insert(14) + return root + def test_insert(rand_setup): pre_size = rand_setup.size() @@ -67,8 +72,8 @@ def test_balance(rand_setup): left = rand_setup.left.depth() right = rand_setup.right.depth() if left > right: - assert rand_setup.balance() == -int + assert rand_setup.balance() < 0 elif right > left: - assert rand_setup.balance() == abs(int) + assert rand_setup.balance() > 0 else: assert rand_setup.balance() == 0 From 12c6b2c317fac121fede8c8b0a6cc7cd50575873 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 15:43:38 -0700 Subject: [PATCH 207/330] Add repr; fix insert method --- bst.py | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/bst.py b/bst.py index 518a82d..db7c0b0 100644 --- a/bst.py +++ b/bst.py @@ -9,13 +9,16 @@ def __init__(self, val): self.right = None self.count = 1 + def __repr__(self): + return '{}'.format(self.val) + def insert(self, val): """insert a node with val into the BST. If val is already present, it will be ignored. """ - if self.contains(val): - return + if val == self.val: + return None self.count += 1 if val < self.val: if self.left is None: @@ -30,16 +33,17 @@ def insert(self, val): def contains(self, val): """return True if val is in the BST, False if not""" - if val < self.val: + # import pdb; pdb.set_trace() + if val == self.val: + return True + elif val < self.val: if self.left is None: return False return self.left.contains(val) elif val > self.val: if self.right is None: return False - return self.left.contains(val) - else: - return True + return self.right.contains(val) def size(self): """return the integer size of the BST. From b011e06cf66e596d2adbbf6794d970c4f7646579 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 15:52:40 -0700 Subject: [PATCH 208/330] Fix none case in balance method --- bst.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bst.py b/bst.py index db7c0b0..eea203a 100644 --- a/bst.py +++ b/bst.py @@ -70,9 +70,11 @@ def balance(self): trees which are higher on the right than the left should return a negative value. An ideallyl-balanced tree should return 0. """ - if self.left.depth() > self.right.depth(): + left_depth = self.left.depth() if self.left else 0 + right_depth = self.right.depth() if self.right else 0 + if left_depth > right_depth: return 1 - elif self.left.depth() < self.right.depth(): + elif left_depth < right_depth: return -1 else: return 0 From ff36ffff3c44474961d6c6c3c9c2b165553de5f7 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 16:14:11 -0700 Subject: [PATCH 209/330] Change size method; fix none checks --- bst.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/bst.py b/bst.py index eea203a..233e794 100644 --- a/bst.py +++ b/bst.py @@ -7,7 +7,6 @@ def __init__(self, val): self.val = val self.left = None self.right = None - self.count = 1 def __repr__(self): return '{}'.format(self.val) @@ -19,7 +18,6 @@ def insert(self, val): """ if val == self.val: return None - self.count += 1 if val < self.val: if self.left is None: self.left = Node(val) @@ -51,7 +49,9 @@ def size(self): (equal to the total number of values stored in the tree), 0 if the tree is empty. """ - return self.count + left_size = self.left.size() if self.left is not None else 0 + right_size = self.right.size() if self.right is not None else 0 + return left_size + right_size + 1 def depth(self): """return an integer representing the total number of levels in the tree. @@ -59,8 +59,8 @@ def depth(self): If there is one value, the depth should be 1, if two values it'll be 2, if three values it may be 2 or three, depending, etc. """ - left_depth = self.left.depth() if self.left else 0 - right_depth = self.right.depth() if self.right else 0 + left_depth = self.left.depth() if self.left is not None else 0 + right_depth = self.right.depth() if self.right is not None else 0 return max(left_depth, right_depth) + 1 def balance(self): From cb38133ec520fbad0e99d839d258bfc09c567de2 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 16:14:27 -0700 Subject: [PATCH 210/330] Fix test errors --- test_bst.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/test_bst.py b/test_bst.py index f9cdf6c..900a980 100644 --- a/test_bst.py +++ b/test_bst.py @@ -53,27 +53,27 @@ def test_contains(rand_setup): def test_size(rand_setup): pre_size = rand_setup.size() - rand = randint(1, 100) - rand_setup.insert(rand) - rand_setup.insert(rand) + new = 200 + rand_setup.insert(new) + rand_setup.insert(new) post_size = rand_setup.size() assert post_size > pre_size assert post_size == pre_size + 1 def test_depth(fixed_setup): - assert fixed_setup.left.depth() == 5 - assert fixed_setup.right.depth() == 4 + assert fixed_setup.left.depth() == 4 + assert fixed_setup.right.depth() == 3 fixed_setup.insert(13) - assert fixed_setup.left.depth() == 6 + assert fixed_setup.left.depth() == 5 def test_balance(rand_setup): left = rand_setup.left.depth() right = rand_setup.right.depth() if left > right: - assert rand_setup.balance() < 0 + assert rand_setup.balance() == 1 elif right > left: - assert rand_setup.balance() > 0 + assert rand_setup.balance() == -1 else: assert rand_setup.balance() == 0 From 0eb978853dbe12b00c80c4a5c50dbf0104f76530 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 16:26:13 -0700 Subject: [PATCH 211/330] Fix test for none checks --- test_bst.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/test_bst.py b/test_bst.py index 900a980..c5c9c76 100644 --- a/test_bst.py +++ b/test_bst.py @@ -36,10 +36,10 @@ def fixed_setup(): def test_insert(rand_setup): pre_size = rand_setup.size() - rand = randint(1, 100) - rand_setup.insert(rand) - assert rand_setup.insert(rand) is None - rand_setup.insert(rand) + new = 200 + rand_setup.insert(new) + assert rand_setup.insert(new) is None + rand_setup.insert(new) post_size = rand_setup.size() assert post_size > pre_size assert post_size == pre_size + 1 @@ -69,8 +69,8 @@ def test_depth(fixed_setup): def test_balance(rand_setup): - left = rand_setup.left.depth() - right = rand_setup.right.depth() + left = rand_setup.left.depth() if rand_setup.left is not None else 0 + right = rand_setup.right.depth() if rand_setup.right is not None else 0 if left > right: assert rand_setup.balance() == 1 elif right > left: From 85b821160d34d3963775c1e47d410fb2e4d7aec6 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 16:42:41 -0700 Subject: [PATCH 212/330] Improve docstrings --- bst.py | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/bst.py b/bst.py index 233e794..0506d65 100644 --- a/bst.py +++ b/bst.py @@ -12,9 +12,12 @@ def __repr__(self): return '{}'.format(self.val) def insert(self, val): - """insert a node with val into the BST. + """Insert a node with a value into the tree. If val is already present, it will be ignored. + + args: + val: the value to insert """ if val == self.val: return None @@ -30,8 +33,13 @@ def insert(self, val): self.right.insert(val) def contains(self, val): - """return True if val is in the BST, False if not""" - # import pdb; pdb.set_trace() + """Check tree for node with given value. + + args: + val: the value to check for + + returns: True if val is in the tree, False if not. + """ if val == self.val: return True elif val < self.val: @@ -44,31 +52,34 @@ def contains(self, val): return self.right.contains(val) def size(self): - """return the integer size of the BST. + """Return the total number of nodes in the tree. - (equal to the total number of values stored in the tree), - 0 if the tree is empty. + returns: integer of total node; 0 if empty """ left_size = self.left.size() if self.left is not None else 0 right_size = self.right.size() if self.right is not None else 0 return left_size + right_size + 1 def depth(self): - """return an integer representing the total number of levels in the tree. + """Return an the total number of levels in the tree. If there is one value, the depth should be 1, if two values it'll be 2, if three values it may be 2 or three, depending, etc. + + returns: integer of level number """ left_depth = self.left.depth() if self.left is not None else 0 right_depth = self.right.depth() if self.right is not None else 0 return max(left_depth, right_depth) + 1 def balance(self): - """return an integer, positive or negative represents how balanced the tree is. + """Return a positive or negative number representing tree balance. + + Trees higher on the left than the right should return a positive value, + trees higher on the right than the left should return a negative value. + An ideally-balanced tree should return 0. - Trees which are higher on the left than the right should return a positive value, - trees which are higher on the right than the left should return a negative value. - An ideallyl-balanced tree should return 0. + returns: integer """ left_depth = self.left.depth() if self.left else 0 right_depth = self.right.depth() if self.right else 0 From aa62fe05de472ccd416d808fcfd1835069a5feda Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 17:03:47 -0700 Subject: [PATCH 213/330] Add futher none check to balance method --- bst.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bst.py b/bst.py index 0506d65..ee59a09 100644 --- a/bst.py +++ b/bst.py @@ -81,11 +81,12 @@ def balance(self): returns: integer """ - left_depth = self.left.depth() if self.left else 0 - right_depth = self.right.depth() if self.right else 0 + left_depth = self.left.depth() if self.left is not None else 0 + right_depth = self.right.depth() if self.right is not None else 0 if left_depth > right_depth: return 1 elif left_depth < right_depth: return -1 else: return 0 + From 678c3796cf74b0ef94a4989a2e6051dd2db664a2 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 17:47:07 -0700 Subject: [PATCH 214/330] Document best and worst case in main section --- bst.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/bst.py b/bst.py index ee59a09..7b25eed 100644 --- a/bst.py +++ b/bst.py @@ -90,3 +90,31 @@ def balance(self): else: return 0 + +if __name__ == '__main__': + from timeit import Timer + + """Document the best and worst cases for searching for a value in the tree. + The worst case consists of a tree with one long linear branch. + the best case is a perfectly balanced tree. + """ + worst = Node(1) + for val in range(2, 32): + worst.insert(val) + + best = Node(16) + best_values = [ + 8, 24, 4, 12, 20, 28, 2, 6, 10, 14, 18, 22, 26, 30, 1, 3, 5, + 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 + ] + for val in best_values: + best.insert(val) + + worst_case = Timer( + 'worst.contains(31)', 'from __main__ import worst').timeit(1000) + best_case = Timer( + 'best.contains(31)', 'from __main__ import best').timeit(1000) + + print "The worst case took {}.".format(worst_case) + print "The best case took {}.".format(best_case) + From 7be0ff354aa6ba259c84ed05f1adfe0a1f1735fe Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 17:47:39 -0700 Subject: [PATCH 215/330] Fix typo --- bst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bst.py b/bst.py index 7b25eed..06fe8ea 100644 --- a/bst.py +++ b/bst.py @@ -96,7 +96,7 @@ def balance(self): """Document the best and worst cases for searching for a value in the tree. The worst case consists of a tree with one long linear branch. - the best case is a perfectly balanced tree. + The best case is a perfectly balanced tree. """ worst = Node(1) for val in range(2, 32): From be8ae50aeba9cd8b5041967276377b309690fb32 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 22 Jul 2015 20:59:19 -0700 Subject: [PATCH 216/330] Update README.md --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 76a403c..f8e7c83 100644 --- a/README.md +++ b/README.md @@ -87,5 +87,21 @@ O(nodes * edges). See the doc strings for additional implementation details. +##Binary Search Tree +The included node class implements a [binary search tree](https://en.wikipedia.org/wiki/Binary_search_tree). Binary search trees allow lookup operations using binary search, which allows operations such as search and insert to be completed with an average case time complexity of O(log n) and worst case O(n). + +This module was completed with reference to the very helpful post [Binary Search Tree libary in Python](http://www.laurentluce.com/posts/binary-search-tree-library-in-python/) by Laurent Luce. + +Available methods include: + +* insert(val) +* contains(val) +* size() +* depth() +* balance() + +See the doc strings for additional implementation details. + + [![Build Status](https://travis-ci.org/jonathanstallings/data-structures.svg?branch=master)](https://travis-ci.org/jonathanstallings/data-structures) From f67b8c62390ba65590bcc88bc96c5a51053fbc2c Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Thu, 23 Jul 2015 14:07:47 -0700 Subject: [PATCH 217/330] Add traversals to tree. --- bst.py | 54 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 53 insertions(+), 1 deletion(-) diff --git a/bst.py b/bst.py index 06fe8ea..a2da7cc 100644 --- a/bst.py +++ b/bst.py @@ -1,4 +1,5 @@ from __future__ import unicode_literals +from queue import Queue class Node(object): @@ -90,6 +91,58 @@ def balance(self): else: return 0 + def in_order(self): + stack = [] + node = self + while stack or node: + if node: + stack.append(node) + node = node.left + else: + node = stack.pop() + yield node.val + node = node.right + + def pre_order(self): + stack = [] + node = self + while stack or node: + if node: + yield node.val + stack.append(node) + node = node.left + else: + node = stack.pop() + node = node.right + + def post_order(self): + stack = [] + node = self + last = None + while stack or node: + if node: + stack.append(node) + node = node.left + else: + peek = stack[-1] + if peek.right is not None and last != peek.right: + node = peek.right + else: + yield peek.val + last = stack.pop() + node = None + + def breadth_first(self): + q = Queue() + q.enqueue(self) + while q.size() > 0: + node = q.dequeue() + yield node.val + if node.left: + q.enqueue(node.left) + if node.right: + q.enqueue(node.right) + if __name__ == '__main__': from timeit import Timer @@ -117,4 +170,3 @@ def balance(self): print "The worst case took {}.".format(worst_case) print "The best case took {}.".format(best_case) - From b152a632ffefe32a5d13763632ce1be2a0bb70f6 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 23 Jul 2015 14:16:15 -0700 Subject: [PATCH 218/330] Fix balance method --- bst.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/bst.py b/bst.py index a2da7cc..7465dee 100644 --- a/bst.py +++ b/bst.py @@ -84,12 +84,7 @@ def balance(self): """ left_depth = self.left.depth() if self.left is not None else 0 right_depth = self.right.depth() if self.right is not None else 0 - if left_depth > right_depth: - return 1 - elif left_depth < right_depth: - return -1 - else: - return 0 + return left_depth - right_depth def in_order(self): stack = [] From 4975392de2b0e951bd003fb9b80349ec9ca73889 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 23 Jul 2015 14:17:36 -0700 Subject: [PATCH 219/330] Fix test balance; need to improve test architecture. --- test_bst.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_bst.py b/test_bst.py index c5c9c76..6757c9c 100644 --- a/test_bst.py +++ b/test_bst.py @@ -72,8 +72,8 @@ def test_balance(rand_setup): left = rand_setup.left.depth() if rand_setup.left is not None else 0 right = rand_setup.right.depth() if rand_setup.right is not None else 0 if left > right: - assert rand_setup.balance() == 1 + assert rand_setup.balance() > 0 elif right > left: - assert rand_setup.balance() == -1 + assert rand_setup.balance() < 0 else: assert rand_setup.balance() == 0 From ed8718fb46e1d7ecd44b2397ed7de053460be486 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 23 Jul 2015 14:51:03 -0700 Subject: [PATCH 220/330] Add __len__ and __iter__ --- bst.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/bst.py b/bst.py index 7465dee..0286c59 100644 --- a/bst.py +++ b/bst.py @@ -12,6 +12,12 @@ def __init__(self, val): def __repr__(self): return '{}'.format(self.val) + def __len__(self): + return self.size() + + def __iter__(self): + return self.in_order() + def insert(self, val): """Insert a node with a value into the tree. From 5bd3a8c6aa150c50236b9b6606d325c780d30609 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 23 Jul 2015 14:53:55 -0700 Subject: [PATCH 221/330] Allow empty node creation --- bst.py | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/bst.py b/bst.py index 0286c59..1dba42b 100644 --- a/bst.py +++ b/bst.py @@ -4,7 +4,7 @@ class Node(object): """A class for a binary search tree node.""" - def __init__(self, val): + def __init__(self, val=None): self.val = val self.left = None self.right = None @@ -26,18 +26,21 @@ def insert(self, val): args: val: the value to insert """ - if val == self.val: - return None - if val < self.val: - if self.left is None: - self.left = Node(val) - else: - self.left.insert(val) - elif val > self.val: - if self.right is None: - self.right = Node(val) - else: - self.right.insert(val) + if self.val is not None: + if val == self.val: + return None + if val < self.val: + if self.left is None: + self.left = Node(val) + else: + self.left.insert(val) + elif val > self.val: + if self.right is None: + self.right = Node(val) + else: + self.right.insert(val) + else: + self.val = val def contains(self, val): """Check tree for node with given value. From 990f9cac9c3f16e501acf58b9a3f1fed6bbe4f25 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 23 Jul 2015 14:56:59 -0700 Subject: [PATCH 222/330] Fix size method to match spec with empty node --- bst.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/bst.py b/bst.py index 1dba42b..a9bf4ec 100644 --- a/bst.py +++ b/bst.py @@ -66,6 +66,8 @@ def size(self): returns: integer of total node; 0 if empty """ + if self.val is None: + return 0 left_size = self.left.size() if self.left is not None else 0 right_size = self.right.size() if self.right is not None else 0 return left_size + right_size + 1 From ce06efac7366de3abdc36fee30b2965317600d11 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 23 Jul 2015 16:47:34 -0700 Subject: [PATCH 223/330] Add function to generate a balanced BST for testing --- bst.py | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/bst.py b/bst.py index a9bf4ec..87a8112 100644 --- a/bst.py +++ b/bst.py @@ -157,17 +157,24 @@ def breadth_first(self): The worst case consists of a tree with one long linear branch. The best case is a perfectly balanced tree. """ - worst = Node(1) - for val in range(2, 32): + + def sorted_list_to_BST(items=[], start=None, end=None): + if start > end: + return None + mid = start + (end - start) / 2 + node = Node(items[mid]) + node.left = sorted_list_to_BST(items, start, mid-1) + node.right = sorted_list_to_BST(items, mid+1, end) + return node + + def create_best_case(n): + return sorted_list_to_BST(range(n), 0, n-1) + + worst = Node() + for val in range(100): worst.insert(val) - best = Node(16) - best_values = [ - 8, 24, 4, 12, 20, 28, 2, 6, 10, 14, 18, 22, 26, 30, 1, 3, 5, - 7, 9, 11, 13, 15, 17, 19, 21, 23, 25, 27, 29, 31 - ] - for val in best_values: - best.insert(val) + best = create_best_case(100) worst_case = Timer( 'worst.contains(31)', 'from __main__ import worst').timeit(1000) From 3d23651c8725f9fd9b82bf0d763f4e48de6146f0 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 23 Jul 2015 22:48:44 -0700 Subject: [PATCH 224/330] Improve best worst case demo --- bst.py | 36 +++++++++++++++++++++++++++++------- 1 file changed, 29 insertions(+), 7 deletions(-) diff --git a/bst.py b/bst.py index 87a8112..c4d2147 100644 --- a/bst.py +++ b/bst.py @@ -158,6 +158,9 @@ def breadth_first(self): The best case is a perfectly balanced tree. """ + size = 900 + lookup = 700 + def sorted_list_to_BST(items=[], start=None, end=None): if start > end: return None @@ -171,15 +174,34 @@ def create_best_case(n): return sorted_list_to_BST(range(n), 0, n-1) worst = Node() - for val in range(100): + for val in range(size): worst.insert(val) - best = create_best_case(100) + best = create_best_case(size) worst_case = Timer( - 'worst.contains(31)', 'from __main__ import worst').timeit(1000) - best_case = Timer( - 'best.contains(31)', 'from __main__ import best').timeit(1000) + 'worst.contains({})', 'from __main__ import worst' + .format(lookup) + ).timeit(1000) - print "The worst case took {}.".format(worst_case) - print "The best case took {}.".format(best_case) + best_case = Timer( + 'best.contains({})', 'from __main__ import best' + .format(lookup) + ).timeit(1000) + + print( + "\nLookup Time Comparison: Best and Worst Case\n" + "\nGiven a tree of {n} items, find a node with value of {l}.\n" + .format(n=size, l=lookup) + ) + + print ( + "Worst case, with tree balanced at {b}.\n" + "Time: {t}\n" + .format(b=worst.balance(), t=worst_case) + ) + print ( + "Best case, with tree balanced at {b}.\n" + "Time: {t}\n" + .format(b=best.balance(), t=best_case) + ) From a915ee96ee2a0357580d66b4d6c373c1b951c6e2 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 23 Jul 2015 23:03:58 -0700 Subject: [PATCH 225/330] Improve docstrings --- bst.py | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/bst.py b/bst.py index c4d2147..6a46a2a 100644 --- a/bst.py +++ b/bst.py @@ -159,19 +159,35 @@ def breadth_first(self): """ size = 900 - lookup = 700 + lookup = 900 - def sorted_list_to_BST(items=[], start=None, end=None): + def _sorted_list_to_BST(items=[], start=None, end=None): + """Create a balanced binary search tree from sorted list. + + args: + items: the sorted list of items to insert into tree + start: the start of the list + end: the end of the list + + returns: a balanced binary search tree (node) + """ if start > end: return None mid = start + (end - start) / 2 node = Node(items[mid]) - node.left = sorted_list_to_BST(items, start, mid-1) - node.right = sorted_list_to_BST(items, mid+1, end) + node.left = _sorted_list_to_BST(items, start, mid-1) + node.right = _sorted_list_to_BST(items, mid+1, end) return node def create_best_case(n): - return sorted_list_to_BST(range(n), 0, n-1) + """Create a balanced binary search tree from a given range. + + args: + n: the range on integers to insert into the tree + + returns: a balanced binary search tree (node) + """ + return _sorted_list_to_BST(range(n), 0, n-1) worst = Node() for val in range(size): From a6b82a9ff782a42829b50a7969b4fe7caa7c15b2 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 23 Jul 2015 23:19:31 -0700 Subject: [PATCH 226/330] Add dot methods; reorganize helper class methods --- bst.py | 67 ++++++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 49 insertions(+), 18 deletions(-) diff --git a/bst.py b/bst.py index 6a46a2a..f653597 100644 --- a/bst.py +++ b/bst.py @@ -1,4 +1,6 @@ from __future__ import unicode_literals +import random + from queue import Queue @@ -149,19 +151,36 @@ def breadth_first(self): if node.right: q.enqueue(node.right) - -if __name__ == '__main__': - from timeit import Timer - - """Document the best and worst cases for searching for a value in the tree. - The worst case consists of a tree with one long linear branch. - The best case is a perfectly balanced tree. - """ - - size = 900 - lookup = 900 - - def _sorted_list_to_BST(items=[], start=None, end=None): + def get_dot(self): + """Return the tree with root as a dot graph for visualization.""" + return "digraph G{\n%s}" % ("" if self.val is None else ( + "\t%s;\n%s\n" % ( + self.val, + "\n".join(self._get_dot()) + ) + )) + + def _get_dot(self): + """recursively prepare a dot graph entry for this node.""" + if self.left is not None: + yield "\t%s -> %s;" % (self.val, self.left.val) + for i in self.left._get_dot(): + yield i + elif self.right is not None: + r = random.randint(0, 1e9) + yield "\tnull%s [shape=point];" % r + yield "\t%s -> null%s;" % (self.val, r) + if self.right is not None: + yield "\t%s -> %s;" % (self.val, self.right.val) + for i in self.right._get_dot(): + yield i + elif self.left is not None: + r = random.randint(0, 1e9) + yield "\tnull%s [shape=point];" % r + yield "\t%s -> null%s;" % (self.val, r) + + @classmethod + def _sorted_list_to_BST(cls, items=[], start=None, end=None): """Create a balanced binary search tree from sorted list. args: @@ -175,11 +194,12 @@ def _sorted_list_to_BST(items=[], start=None, end=None): return None mid = start + (end - start) / 2 node = Node(items[mid]) - node.left = _sorted_list_to_BST(items, start, mid-1) - node.right = _sorted_list_to_BST(items, mid+1, end) + node.left = cls._sorted_list_to_BST(items, start, mid-1) + node.right = cls._sorted_list_to_BST(items, mid+1, end) return node - def create_best_case(n): + @classmethod + def create_best_case(cls, n): """Create a balanced binary search tree from a given range. args: @@ -187,13 +207,24 @@ def create_best_case(n): returns: a balanced binary search tree (node) """ - return _sorted_list_to_BST(range(n), 0, n-1) + return cls._sorted_list_to_BST(range(n), 0, n-1) + +if __name__ == '__main__': + from timeit import Timer + + """Document the best and worst cases for searching for a value in the tree. + The worst case consists of a tree with one long linear branch. + The best case is a perfectly balanced tree. + """ + + size = 900 + lookup = 900 worst = Node() for val in range(size): worst.insert(val) - best = create_best_case(size) + best = Node.create_best_case(size) worst_case = Timer( 'worst.contains({})', 'from __main__ import worst' From 1e25fcc8a9d17b3ab2731461253d90944260188e Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 23 Jul 2015 23:45:12 -0700 Subject: [PATCH 227/330] Allow saving graphviz output; update .gitignore to exclude --- .gitignore | 3 +++ bst.py | 6 ++++++ 2 files changed, 9 insertions(+) diff --git a/.gitignore b/.gitignore index ba74660..bbf5af0 100644 --- a/.gitignore +++ b/.gitignore @@ -55,3 +55,6 @@ docs/_build/ # PyBuilder target/ + +# Graphviz Output +graphviz/ diff --git a/bst.py b/bst.py index f653597..9f2b8ea 100644 --- a/bst.py +++ b/bst.py @@ -160,6 +160,12 @@ def get_dot(self): ) )) + def save_render(self): + """Render and save a represntation of the tree.""" + from graphviz import Source + src = Source(self.get_dot()) + src.render('graphviz/tree.gv') + def _get_dot(self): """recursively prepare a dot graph entry for this node.""" if self.left is not None: From 719af5e99370b9613e0e03369f80007e8481a2b8 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 00:00:59 -0700 Subject: [PATCH 228/330] Update requirements for graphviz --- requirements.txt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/requirements.txt b/requirements.txt index b9fc0bd..e995f16 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,4 @@ +graphviz==0.4.5 +ipython==3.0.0 py==1.4.29 pytest==2.7.2 From 9d8dcc94e5f5e555e53491361ec3cb25f7e72c2d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 00:01:15 -0700 Subject: [PATCH 229/330] Add __init__.py --- __init__.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 __init__.py diff --git a/__init__.py b/__init__.py new file mode 100644 index 0000000..e69de29 From bd18ed13ac5825cd6a6a08210174ec87a8e670c6 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 00:07:52 -0700 Subject: [PATCH 230/330] Improve save render method --- bst.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/bst.py b/bst.py index 9f2b8ea..3818b4b 100644 --- a/bst.py +++ b/bst.py @@ -160,11 +160,12 @@ def get_dot(self): ) )) - def save_render(self): + def save_render(self, savefile="tree.gv"): """Render and save a represntation of the tree.""" from graphviz import Source src = Source(self.get_dot()) - src.render('graphviz/tree.gv') + path = 'graphviz/{}'.format(savefile) + src.render(path) def _get_dot(self): """recursively prepare a dot graph entry for this node.""" From 12d9996f1612fc6e2a6534a747776858ecc3093a Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 00:18:54 -0700 Subject: [PATCH 231/330] Improve repr --- bst.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/bst.py b/bst.py index 3818b4b..8d62762 100644 --- a/bst.py +++ b/bst.py @@ -12,6 +12,9 @@ def __init__(self, val=None): self.right = None def __repr__(self): + return ''.format(self.val) + + def __str__(self): return '{}'.format(self.val) def __len__(self): From 883a637ea3609b3bcf44fffd358e528a95a002df Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 00:22:03 -0700 Subject: [PATCH 232/330] Add simple init tests --- test_bst.py | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/test_bst.py b/test_bst.py index 6757c9c..0adb742 100644 --- a/test_bst.py +++ b/test_bst.py @@ -1,18 +1,16 @@ from __future__ import unicode_literals -from bst import Node from random import randint import pytest +from bst import Node + @pytest.fixture() def rand_setup(): root = Node(randint(1, 100)) for idx in range(20): val = randint(1, 100) - try: - root.insert(val) - except AttributeError: - continue + root.insert(val) return root @@ -34,6 +32,18 @@ def fixed_setup(): return root +def test_init_empty(): + new = Node() + assert new.val is None + assert new.left is None and new.right is None + + +def test_init_with_val(): + new = Node(10) + assert new.val == 10 + assert new.left is None and new.right is None + + def test_insert(rand_setup): pre_size = rand_setup.size() new = 200 From 53354200eb8f758550bef33af3e5f3a44953110b Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 00:46:44 -0700 Subject: [PATCH 233/330] Add further insert tests --- test_bst.py | 70 ++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 64 insertions(+), 6 deletions(-) diff --git a/test_bst.py b/test_bst.py index 0adb742..f237ed7 100644 --- a/test_bst.py +++ b/test_bst.py @@ -33,15 +33,73 @@ def fixed_setup(): def test_init_empty(): - new = Node() - assert new.val is None - assert new.left is None and new.right is None + root = Node() + assert root.val is None + assert root.left is None and root.right is None def test_init_with_val(): - new = Node(10) - assert new.val == 10 - assert new.left is None and new.right is None + root = Node(10) + assert root.val == 10 + assert root.left is None and root.right is None + + +def test_insert_in_empty_root(): + root = Node() + expected = 10 + root.insert(expected) + actual = root.val + assert expected == actual + + +def test_insert_lesser_in_filled_root(): + root = Node(10) + expected = 5 + root.insert(expected) + actual = root.left.val + assert expected == actual + assert root.right is None + + +def test_insert_greater_in_filled_root(): + root = Node(10) + expected = 15 + root.insert(expected) + actual = root.right.val + assert expected == actual + assert root.left is None + + +def test_insert_lesser_in_filled_tree1(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.insert(1) + assert root.left.left.val == 1 + assert root.left.right is None + + +def test_insert_lesser_in_filled_tree2(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.insert(7) + assert root.left.right.val == 7 + assert root.left.left is None + + +def test_insert_greater_in_filled_tree1(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.insert(17) + assert root.right.right.val == 17 + assert root.right.left is None + + +def test_insert_greater_in_filled_tree2(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.insert(12) + assert root.right.left.val == 12 + assert root.right.right is None def test_insert(rand_setup): From 7200e68a76f67b1b856dcc4e5e6ff1d0fa034791 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 01:11:14 -0700 Subject: [PATCH 234/330] Add further contains, size, depth, and balance tests --- test_bst.py | 70 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/test_bst.py b/test_bst.py index f237ed7..75998a4 100644 --- a/test_bst.py +++ b/test_bst.py @@ -113,12 +113,28 @@ def test_insert(rand_setup): assert post_size == pre_size + 1 +def test_contains_val(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + assert root.contains(15) + assert not root.contains(27) + + def test_contains(rand_setup): rand = randint(1, 100) rand_setup.insert(rand) assert rand_setup.contains(rand) is True +def test_size_with_filling(): + root = Node() + assert root.size() == 0 + root.val = 10 + assert root.size() == 1 + root.left, root.right = Node(5), Node(15) + assert root.size() == 3 + + def test_size(rand_setup): pre_size = rand_setup.size() new = 200 @@ -129,6 +145,30 @@ def test_size(rand_setup): assert post_size == pre_size + 1 +def test_depth1(): + root = Node() + assert root.depth() == 1 + root.val = 10 + assert root.depth() == 1 + + +def test_depth2(): + root = Node(10) + root.left = Node(5) + assert root.depth() == 2 + root.right = Node(15) + assert root.depth() == 2 + + +def test_depth3(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.left.left, root.left.right = Node(3), Node(7) + assert root.depth() == 3 + root.right.left, root.right.right = Node(12), Node(17) + assert root.depth() == 3 + + def test_depth(fixed_setup): assert fixed_setup.left.depth() == 4 assert fixed_setup.right.depth() == 3 @@ -136,6 +176,36 @@ def test_depth(fixed_setup): assert fixed_setup.left.depth() == 5 +def test_balance_equal(): + root = Node(10) + assert root.balance() == 0 + root.left, root.right = Node(5), Node(15) + assert root.balance() == 0 + root.left.left, root.left.right = Node(3), Node(7) + root.right.right = Node(17) + assert root.balance() == 0 + + +def test_balance_positive(): + root = Node(10) + root.left = Node(5) + assert root.balance() == 1 + root.left.left, root.left.right = Node(3), Node(7) + assert root.balance() == 2 + root.right = Node(15) + assert root.balance() == 1 + + +def test_balance_negative(): + root = Node(10) + root.right = Node(5) + assert root.balance() == -1 + root.right.left, root.right.right = Node(3), Node(7) + assert root.balance() == -2 + root.left = Node(15) + assert root.balance() == -1 + + def test_balance(rand_setup): left = rand_setup.left.depth() if rand_setup.left is not None else 0 right = rand_setup.right.depth() if rand_setup.right is not None else 0 From befeabeed14509ca90c951227f3eddd13f1b656d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 01:11:54 -0700 Subject: [PATCH 235/330] Move method for organization --- bst.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/bst.py b/bst.py index 8d62762..82b54a5 100644 --- a/bst.py +++ b/bst.py @@ -163,13 +163,6 @@ def get_dot(self): ) )) - def save_render(self, savefile="tree.gv"): - """Render and save a represntation of the tree.""" - from graphviz import Source - src = Source(self.get_dot()) - path = 'graphviz/{}'.format(savefile) - src.render(path) - def _get_dot(self): """recursively prepare a dot graph entry for this node.""" if self.left is not None: @@ -189,6 +182,13 @@ def _get_dot(self): yield "\tnull%s [shape=point];" % r yield "\t%s -> null%s;" % (self.val, r) + def save_render(self, savefile="tree.gv"): + """Render and save a represntation of the tree.""" + from graphviz import Source + src = Source(self.get_dot()) + path = 'graphviz/{}'.format(savefile) + src.render(path) + @classmethod def _sorted_list_to_BST(cls, items=[], start=None, end=None): """Create a balanced binary search tree from sorted list. From 33810ffb84fff279bcfd48c993ab98e98619ba62 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 01:14:55 -0700 Subject: [PATCH 236/330] Add docstrings to traversal methods --- bst.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/bst.py b/bst.py index 82b54a5..e03dca6 100644 --- a/bst.py +++ b/bst.py @@ -103,6 +103,7 @@ def balance(self): return left_depth - right_depth def in_order(self): + """Return a generator with tree values from in-order traversal""" stack = [] node = self while stack or node: @@ -115,6 +116,7 @@ def in_order(self): node = node.right def pre_order(self): + """Return a generator with tree values from pre-order traversal""" stack = [] node = self while stack or node: @@ -127,6 +129,7 @@ def pre_order(self): node = node.right def post_order(self): + """Return a generator with tree values from post-order traversal""" stack = [] node = self last = None @@ -144,6 +147,7 @@ def post_order(self): node = None def breadth_first(self): + """Return a generator with tree values from breadth first traversal""" q = Queue() q.enqueue(self) while q.size() > 0: From e321d81b9dfad3875c1d5c3bcc712f67b9710656 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 01:21:38 -0700 Subject: [PATCH 237/330] Fix implementation of balance method --- bst.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/bst.py b/bst.py index 06fe8ea..b900326 100644 --- a/bst.py +++ b/bst.py @@ -83,12 +83,7 @@ def balance(self): """ left_depth = self.left.depth() if self.left is not None else 0 right_depth = self.right.depth() if self.right is not None else 0 - if left_depth > right_depth: - return 1 - elif left_depth < right_depth: - return -1 - else: - return 0 + return left_depth - right_depth if __name__ == '__main__': From 2b723d76f43e7510da3eb6a9198121a82cbb613c Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 01:24:13 -0700 Subject: [PATCH 238/330] Fix test for balance --- test_bst.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test_bst.py b/test_bst.py index c5c9c76..6757c9c 100644 --- a/test_bst.py +++ b/test_bst.py @@ -72,8 +72,8 @@ def test_balance(rand_setup): left = rand_setup.left.depth() if rand_setup.left is not None else 0 right = rand_setup.right.depth() if rand_setup.right is not None else 0 if left > right: - assert rand_setup.balance() == 1 + assert rand_setup.balance() > 0 elif right > left: - assert rand_setup.balance() == -1 + assert rand_setup.balance() < 0 else: assert rand_setup.balance() == 0 From 905730ca493efe4e8d244da869dc60dfe5f2cf2c Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 01:41:34 -0700 Subject: [PATCH 239/330] Add traversal tests --- test_bst.py | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/test_bst.py b/test_bst.py index 75998a4..8af0927 100644 --- a/test_bst.py +++ b/test_bst.py @@ -1,5 +1,7 @@ from __future__ import unicode_literals from random import randint +import types + import pytest from bst import Node @@ -32,6 +34,15 @@ def fixed_setup(): return root +@pytest.fixture() +def traversal_setup(): + root = Node() + setup = ['F', 'B', 'A', 'D', 'C', 'E', 'G', 'I', 'H'] + for char in setup: + root.insert(char) + return root + + def test_init_empty(): root = Node() assert root.val is None @@ -215,3 +226,39 @@ def test_balance(rand_setup): assert rand_setup.balance() < 0 else: assert rand_setup.balance() == 0 + + +def test_in_order(traversal_setup): + root = traversal_setup + expected = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I'] + generator = root.in_order() + assert isinstance(generator, types.GeneratorType) + actual = list(generator) + assert expected == actual + + +def test_pre_order(traversal_setup): + root = traversal_setup + expected = ['F', 'B', 'A', 'D', 'C', 'E', 'G', 'I', 'H'] + generator = root.pre_order() + assert isinstance(generator, types.GeneratorType) + actual = list(generator) + assert expected == actual + + +def test_post_order(traversal_setup): + root = traversal_setup + expected = ['A', 'C', 'E', 'D', 'B', 'H', 'I', 'G', 'F'] + generator = root.post_order() + assert isinstance(generator, types.GeneratorType) + actual = list(generator) + assert expected == actual + + +def test_breadth_first(traversal_setup): + root = traversal_setup + expected = ['F', 'B', 'G', 'A', 'D', 'I', 'C', 'E', 'H'] + generator = root.breadth_first() + assert isinstance(generator, types.GeneratorType) + actual = list(generator) + assert expected == actual From 37f5193c901f1bf6c5d3d6c5a25d25ed740443e2 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 01:46:39 -0700 Subject: [PATCH 240/330] Add simple tests for helper methods --- test_bst.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test_bst.py b/test_bst.py index 8af0927..346008f 100644 --- a/test_bst.py +++ b/test_bst.py @@ -262,3 +262,17 @@ def test_breadth_first(traversal_setup): assert isinstance(generator, types.GeneratorType) actual = list(generator) assert expected == actual + + +def test_sorted_list_to_BST(): + nodes = range(100) + root = Node._sorted_list_to_BST(nodes, 0, 99) + assert isinstance(root, Node) + assert root.size() == 100 and root.balance() == 0 + + +def test_create_best_case(): + root = Node.create_best_case(100) + assert isinstance(root, Node) + assert root.size() == 100 and root.balance() == 0 + From 9351093c54d0565014de9ec413a323c55951ba6a Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 01:47:33 -0700 Subject: [PATCH 241/330] Fix typo --- bst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bst.py b/bst.py index e03dca6..1e070f8 100644 --- a/bst.py +++ b/bst.py @@ -217,7 +217,7 @@ def create_best_case(cls, n): """Create a balanced binary search tree from a given range. args: - n: the range on integers to insert into the tree + n: the range of integers to insert into the tree returns: a balanced binary search tree (node) """ From 3e8584571f0c4dfcb92858059dc3538a6156c4f6 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 02:02:26 -0700 Subject: [PATCH 242/330] Add a few more tests --- test_bst.py | 30 +++++++++++++++++++++++++----- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/test_bst.py b/test_bst.py index 346008f..1600234 100644 --- a/test_bst.py +++ b/test_bst.py @@ -113,6 +113,15 @@ def test_insert_greater_in_filled_tree2(): assert root.right.right is None +def test_insert_duplicates(): + root = Node(10) + root.left, root.right = Node(5), Node(15) + root.insert(5) + assert root.size() == 3 + root.insert(15) + assert root.size() == 3 + + def test_insert(rand_setup): pre_size = rand_setup.size() new = 200 @@ -127,8 +136,9 @@ def test_insert(rand_setup): def test_contains_val(): root = Node(10) root.left, root.right = Node(5), Node(15) - assert root.contains(15) - assert not root.contains(27) + assert root.contains(15) and not root.contains(27) + root.left.left = Node(2) + assert root.contains(2) def test_contains(rand_setup): @@ -156,14 +166,14 @@ def test_size(rand_setup): assert post_size == pre_size + 1 -def test_depth1(): +def test_depth_1(): root = Node() assert root.depth() == 1 root.val = 10 assert root.depth() == 1 -def test_depth2(): +def test_depth_2(): root = Node(10) root.left = Node(5) assert root.depth() == 2 @@ -171,7 +181,7 @@ def test_depth2(): assert root.depth() == 2 -def test_depth3(): +def test_depth_3(): root = Node(10) root.left, root.right = Node(5), Node(15) root.left.left, root.left.right = Node(3), Node(7) @@ -180,6 +190,16 @@ def test_depth3(): assert root.depth() == 3 +def test_depth_n(): + rand = randint(1, 100) + root = Node(0) + curr = root + for i in range(rand): + curr.right = Node(i) + curr = curr.right + assert root.depth() == rand + 1 + + def test_depth(fixed_setup): assert fixed_setup.left.depth() == 4 assert fixed_setup.right.depth() == 3 From 1d73c054af00adfe6c2ae4f4a0c493126ca58514 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 02:08:44 -0700 Subject: [PATCH 243/330] Update README --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index f8e7c83..372d771 100644 --- a/README.md +++ b/README.md @@ -99,6 +99,14 @@ Available methods include: * size() * depth() * balance() +* in_order() +* pre_order() +* post_order() +* breadth_first() +* get_dot() +* save_render() +* create_best_case() + See the doc strings for additional implementation details. From ea26dabc8ad8348a041b122d328d0fe7f477d6f9 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 02:19:30 -0700 Subject: [PATCH 244/330] Update README.md --- README.md | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 372d771..4e2ef0b 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,6 @@ +[![Build Status](https://travis-ci.org/jonathanstallings/data-structures.svg?branch=master)](https://travis-ci.org/jonathanstallings/data-structures) + + #Data Structures Implementation of LinkedList and Stack data structures in Python. @@ -90,6 +93,12 @@ See the doc strings for additional implementation details. ##Binary Search Tree The included node class implements a [binary search tree](https://en.wikipedia.org/wiki/Binary_search_tree). Binary search trees allow lookup operations using binary search, which allows operations such as search and insert to be completed with an average case time complexity of O(log n) and worst case O(n). +The node class supports four traversal methods which return generators: `in_order`, `pre_order`, `post_order`, and `breadth_first`. Further details are available at the Wikipedia entry for [Tree Traversal](https://en.wikipedia.org/wiki/Tree_traversal). + +Additionally, methods are included to help visualize the tree structure. `get_dot` returns DOT source code, suitable for use with programs such as [Graphviz](http://graphviz.readthedocs.org/en/stable/index.html), and `save_render` saves a rendering of the tree structure to the file system. + +Finally, the helper method `create_best_case' facilitates creation of a balance tree composed of _n_ integers. + This module was completed with reference to the very helpful post [Binary Search Tree libary in Python](http://www.laurentluce.com/posts/binary-search-tree-library-in-python/) by Laurent Luce. Available methods include: @@ -111,5 +120,4 @@ Available methods include: See the doc strings for additional implementation details. -[![Build Status](https://travis-ci.org/jonathanstallings/data-structures.svg?branch=master)](https://travis-ci.org/jonathanstallings/data-structures) From f8a003bec4d684c6d1c4635723a4ac45c73518d1 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 02:20:32 -0700 Subject: [PATCH 245/330] Improve docstring --- bst.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/bst.py b/bst.py index 1e070f8..0dafee3 100644 --- a/bst.py +++ b/bst.py @@ -187,7 +187,11 @@ def _get_dot(self): yield "\t%s -> null%s;" % (self.val, r) def save_render(self, savefile="tree.gv"): - """Render and save a represntation of the tree.""" + """Render and save a represntation of the tree. + + args: + savefile: the optional filename + """ from graphviz import Source src = Source(self.get_dot()) path = 'graphviz/{}'.format(savefile) From 64e0bfb9193fa4929285215b9b42f61a7691a066 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 16:25:28 -0700 Subject: [PATCH 246/330] Fix some style issues --- bst.py | 16 +++++++++------- test_bst.py | 2 +- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/bst.py b/bst.py index 0dafee3..665bbbf 100644 --- a/bst.py +++ b/bst.py @@ -1,3 +1,4 @@ +from __future__ import print_function from __future__ import unicode_literals import random @@ -198,7 +199,7 @@ def save_render(self, savefile="tree.gv"): src.render(path) @classmethod - def _sorted_list_to_BST(cls, items=[], start=None, end=None): + def _sorted_list_to_bst(cls, items=[], start=None, end=None): """Create a balanced binary search tree from sorted list. args: @@ -210,10 +211,10 @@ def _sorted_list_to_BST(cls, items=[], start=None, end=None): """ if start > end: return None - mid = start + (end - start) / 2 + mid = start + (end - start) // 2 node = Node(items[mid]) - node.left = cls._sorted_list_to_BST(items, start, mid-1) - node.right = cls._sorted_list_to_BST(items, mid+1, end) + node.left = cls._sorted_list_to_BST(items, start, mid - 1) + node.right = cls._sorted_list_to_BST(items, mid + 1, end) return node @classmethod @@ -225,7 +226,7 @@ def create_best_case(cls, n): returns: a balanced binary search tree (node) """ - return cls._sorted_list_to_BST(range(n), 0, n-1) + return cls._sorted_list_to_BST(range(n), 0, n - 1) if __name__ == '__main__': from timeit import Timer @@ -260,13 +261,14 @@ def create_best_case(cls, n): .format(n=size, l=lookup) ) - print ( + print( "Worst case, with tree balanced at {b}.\n" "Time: {t}\n" .format(b=worst.balance(), t=worst_case) ) - print ( + print( "Best case, with tree balanced at {b}.\n" "Time: {t}\n" .format(b=best.balance(), t=best_case) ) + diff --git a/test_bst.py b/test_bst.py index 1600234..2c4ec0e 100644 --- a/test_bst.py +++ b/test_bst.py @@ -284,7 +284,7 @@ def test_breadth_first(traversal_setup): assert expected == actual -def test_sorted_list_to_BST(): +def test_sorted_list_to_bst(): nodes = range(100) root = Node._sorted_list_to_BST(nodes, 0, 99) assert isinstance(root, Node) From 9c9183eb10e1c9ae700b992dc7d590d2b5fd3b4d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 24 Jul 2015 16:28:20 -0700 Subject: [PATCH 247/330] Clean up typos after linter fix --- bst.py | 6 +++--- test_bst.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bst.py b/bst.py index 665bbbf..c5ba12e 100644 --- a/bst.py +++ b/bst.py @@ -213,8 +213,8 @@ def _sorted_list_to_bst(cls, items=[], start=None, end=None): return None mid = start + (end - start) // 2 node = Node(items[mid]) - node.left = cls._sorted_list_to_BST(items, start, mid - 1) - node.right = cls._sorted_list_to_BST(items, mid + 1, end) + node.left = cls._sorted_list_to_bst(items, start, mid - 1) + node.right = cls._sorted_list_to_bst(items, mid + 1, end) return node @classmethod @@ -226,7 +226,7 @@ def create_best_case(cls, n): returns: a balanced binary search tree (node) """ - return cls._sorted_list_to_BST(range(n), 0, n - 1) + return cls._sorted_list_to_bst(range(n), 0, n - 1) if __name__ == '__main__': from timeit import Timer diff --git a/test_bst.py b/test_bst.py index 2c4ec0e..1098c26 100644 --- a/test_bst.py +++ b/test_bst.py @@ -286,7 +286,7 @@ def test_breadth_first(traversal_setup): def test_sorted_list_to_bst(): nodes = range(100) - root = Node._sorted_list_to_BST(nodes, 0, 99) + root = Node._sorted_list_to_bst(nodes, 0, 99) assert isinstance(root, Node) assert root.size() == 100 and root.balance() == 0 From c942bcd7efb7a3f8e3724302f08948c58d7be213 Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Mon, 27 Jul 2015 13:15:45 -0700 Subject: [PATCH 248/330] Update bst with delete functionality. --- bst.py | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) diff --git a/bst.py b/bst.py index c5ba12e..19a0df0 100644 --- a/bst.py +++ b/bst.py @@ -159,6 +159,66 @@ def breadth_first(self): if node.right: q.enqueue(node.right) + def _lookup(self, val, parent=None): + if val < self.val: + if self.left is None: + return None, None + return self.left._lookup(val, self) + elif val > self.val: + if self.right is None: + return None, None + return self.right._lookup(val, self) + else: + return self, parent + + def children_count(self): + cnt = 0 + if self.left: + cnt += 1 + if self.right: + cnt += 1 + return cnt + + def delete(self, val): + node, parent = self._lookup(val) + if node is not None: + children_count = node.children_count() + if children_count == 0: + if parent: + if parent.left is node: + parent.left = None + else: + parent.right = None + del node + else: + self.val = None + elif children_count == 1: + if node.left: + child = node.left + else: + child = node.right + if parent: + if parent.left is node: + parent.left = child + else: + parent.right = child + del node + else: + self.left = child.left + self.right = child.right + self.val = child.val + else: + parent = node + successor = node.right + while successor.left: + parent = successor + successor = successor.left + node.val = successor.val + if parent.left == successor: + parent.left = successor.right + else: + parent.right = successor.right + def get_dot(self): """Return the tree with root as a dot graph for visualization.""" return "digraph G{\n%s}" % ("" if self.val is None else ( From 8d43fec1b4da6eb52fc58757729fb9c2b21eb305 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 27 Jul 2015 13:38:55 -0700 Subject: [PATCH 249/330] Add some tests for delete method --- test_bst.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/test_bst.py b/test_bst.py index 1098c26..5ac24a3 100644 --- a/test_bst.py +++ b/test_bst.py @@ -296,3 +296,32 @@ def test_create_best_case(): assert isinstance(root, Node) assert root.size() == 100 and root.balance() == 0 + +def test_delete_root_only(): + root = Node('A') + root.delete('A') + assert root.val is None + assert root.left is None and root.right is None + + +def test_delete_node_without_children(traversal_setup): + root = traversal_setup + root.delete('A') + parent = root.left + assert parent.left is None + + +def test_delete_node_with_one_child(traversal_setup): + root = traversal_setup + root.delete('G') + parent = root + assert parent.right.val == 'I' + + +def test_delete_node_with_two_children(traversal_setup): + root = traversal_setup + root.delete('B') + parent = root + assert parent.left.val == 'C' + successor = parent.left + assert successor.left.val == 'A' and successor.right.val == 'D' From f85ec953628e5869c871f32b24c10602df493004 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 27 Jul 2015 13:44:41 -0700 Subject: [PATCH 250/330] Add simple test for private lookup method --- test_bst.py | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/test_bst.py b/test_bst.py index 5ac24a3..b2e5458 100644 --- a/test_bst.py +++ b/test_bst.py @@ -297,6 +297,14 @@ def test_create_best_case(): assert root.size() == 100 and root.balance() == 0 +def test_lookup(traversal_setup): + root = traversal_setup + expected_root, expected_parent = root.left, root + actual_root, actual_parent = root._lookup('B') + assert expected_root is actual_root + assert expected_parent is actual_parent + + def test_delete_root_only(): root = Node('A') root.delete('A') @@ -325,3 +333,5 @@ def test_delete_node_with_two_children(traversal_setup): assert parent.left.val == 'C' successor = parent.left assert successor.left.val == 'A' and successor.right.val == 'D' + + From bc09e78b26df03f20ccff56fb6857dd7ef580457 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 27 Jul 2015 14:19:03 -0700 Subject: [PATCH 251/330] Add a few more tests for deletion of root --- test_bst.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test_bst.py b/test_bst.py index b2e5458..81680c9 100644 --- a/test_bst.py +++ b/test_bst.py @@ -335,3 +335,17 @@ def test_delete_node_with_two_children(traversal_setup): assert successor.left.val == 'A' and successor.right.val == 'D' +def test_delete_root_with_one_child(): + root = Node('F') + root.left = Node('B') + root.left.left, root.left.right = Node('A'), Node('D') + root.delete('F') + assert root.val == 'B' + assert root.left.val == 'A' and root.right.val == 'D' + + +def test_delete_root_with_two_children(traversal_setup): + root = traversal_setup + root.delete('F') + assert root.val == 'G' + assert root.left.val == 'B' and root.right.val == 'I' From ddd3ed725158c990cf7156b42f0a1a7393dca2df Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 27 Jul 2015 14:43:07 -0700 Subject: [PATCH 252/330] Improve docstrings --- bst.py | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/bst.py b/bst.py index 19a0df0..96ed1c4 100644 --- a/bst.py +++ b/bst.py @@ -160,6 +160,14 @@ def breadth_first(self): q.enqueue(node.right) def _lookup(self, val, parent=None): + """Find a node by value and return that node and its parent. + + args: + val: the value to search by + parent: the parent of the node (for recursion) + + returns: a tuple with node and its parent + """ if val < self.val: if self.left is None: return None, None @@ -172,6 +180,7 @@ def _lookup(self, val, parent=None): return self, parent def children_count(self): + """Return a node's number of children.""" cnt = 0 if self.left: cnt += 1 @@ -180,6 +189,14 @@ def children_count(self): return cnt def delete(self, val): + """Delete a node matching value and reorganize tree as needed. + + If the matched node is the only node in the tree, only its value + will be deleted. + + args: + val: the value of the node to delete + """ node, parent = self._lookup(val) if node is not None: children_count = node.children_count() From 44b374e74e75f1cb93e5e8e4a6aa8ebd8e58cb27 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 27 Jul 2015 15:49:47 -0700 Subject: [PATCH 253/330] Remove unneeded deletion of node; Note: Linter bug identified here --- bst.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/bst.py b/bst.py index 96ed1c4..5e2247e 100644 --- a/bst.py +++ b/bst.py @@ -206,7 +206,6 @@ def delete(self, val): parent.left = None else: parent.right = None - del node else: self.val = None elif children_count == 1: @@ -219,7 +218,6 @@ def delete(self, val): parent.left = child else: parent.right = child - del node else: self.left = child.left self.right = child.right From d8a64cb26955409e69497ba906fe7b23dbfd6602 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 27 Jul 2015 15:51:14 -0700 Subject: [PATCH 254/330] Fix string formatting syntax --- bst.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/bst.py b/bst.py index 5e2247e..2c6b3bc 100644 --- a/bst.py +++ b/bst.py @@ -321,13 +321,11 @@ def create_best_case(cls, n): best = Node.create_best_case(size) worst_case = Timer( - 'worst.contains({})', 'from __main__ import worst' - .format(lookup) + 'worst.contains({})'.format(lookup), 'from __main__ import worst' ).timeit(1000) best_case = Timer( - 'best.contains({})', 'from __main__ import best' - .format(lookup) + 'best.contains({})'.format(lookup), 'from __main__ import best' ).timeit(1000) print( From f096e37d71a8c51cbc8eb9c448d3d9b3a92215f4 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 27 Jul 2015 16:26:42 -0700 Subject: [PATCH 255/330] Add module docstring; fix some style issues. --- bst.py | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/bst.py b/bst.py index 2c6b3bc..46198dd 100644 --- a/bst.py +++ b/bst.py @@ -1,3 +1,25 @@ +"""Contains a Node class with implements a binary search tree. + +Each node can be considered a binary search tree and has the usual +methods to insert, delete, and check membership of nodes. The class also +supports four traversal methods which return generators: + +in_order, pre_order, post_order, and breadth_first. + +Additionally, methods are included to help visualize the tree structure. +get_dot returns DOT source code, suitable for use with programs such as +Graphviz (http://graphviz.readthedocs.org/en/stable/index.html), and +save_render saves a rendering of the tree structure to the file system. + +Finally, the helper method `create_best_case' facilitates creation of a +balanced tree composed of _n_ integers. + +This module was completed with reference to the very helpful post +'Binary Search Tree libary in Python' +(http://www.laurentluce.com/posts/binary-search-tree-library-in-python/) +by Laurent Luce. + +""" from __future__ import print_function from __future__ import unicode_literals import random @@ -311,27 +333,26 @@ def create_best_case(cls, n): The best case is a perfectly balanced tree. """ - size = 900 - lookup = 900 + SIZE = 900 + LOOKUP = 900 worst = Node() - for val in range(size): - worst.insert(val) - - best = Node.create_best_case(size) + for i in range(SIZE): + worst.insert(i) + best = Node.create_best_case(SIZE) worst_case = Timer( - 'worst.contains({})'.format(lookup), 'from __main__ import worst' + 'worst.contains({})'.format(LOOKUP, SIZE), 'from __main__ import worst' ).timeit(1000) best_case = Timer( - 'best.contains({})'.format(lookup), 'from __main__ import best' + 'best.contains({})'.format(LOOKUP), 'from __main__ import best' ).timeit(1000) print( "\nLookup Time Comparison: Best and Worst Case\n" "\nGiven a tree of {n} items, find a node with value of {l}.\n" - .format(n=size, l=lookup) + .format(n=SIZE, l=LOOKUP) ) print( From 412fbd7f41eb5a3f01f1badbf3a16919211202aa Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 27 Jul 2015 16:27:10 -0700 Subject: [PATCH 256/330] Fix typo --- bst.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bst.py b/bst.py index 46198dd..5dd5abf 100644 --- a/bst.py +++ b/bst.py @@ -1,4 +1,4 @@ -"""Contains a Node class with implements a binary search tree. +"""Contains a Node class which implements a binary search tree. Each node can be considered a binary search tree and has the usual methods to insert, delete, and check membership of nodes. The class also From 85f38589cd64f070d9c96afa01bf15d14f2a0476 Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Wed, 29 Jul 2015 13:17:32 -0700 Subject: [PATCH 257/330] Push new balance branch to repo --- bst.py | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/bst.py b/bst.py index 5dd5abf..ede0355 100644 --- a/bst.py +++ b/bst.py @@ -325,6 +325,28 @@ def create_best_case(cls, n): """ return cls._sorted_list_to_bst(range(n), 0, n - 1) + def _is_left(self): + node, parent = self._lookup(self.val) + if parent is None: + return parent + else: + return self is parent.left + + def rotate_left(self): + pass + + def rotate_right(self): + pass + + def rotate_right_then_left(self): + pass + + def rotate_left_then_right(self): + pass + + def self_balance(self): + pass + if __name__ == '__main__': from timeit import Timer @@ -365,4 +387,3 @@ def create_best_case(cls, n): "Time: {t}\n" .format(b=best.balance(), t=best_case) ) - From 0b3ef04cb2cfcd872f65c66004c84b0db084a967 Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Wed, 29 Jul 2015 13:43:27 -0700 Subject: [PATCH 258/330] Update insert method to accept parent argument. --- bst.py | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/bst.py b/bst.py index ede0355..2927ef5 100644 --- a/bst.py +++ b/bst.py @@ -29,10 +29,12 @@ class Node(object): """A class for a binary search tree node.""" - def __init__(self, val=None): + def __init__(self, val=None, parent=None): self.val = val + self.parent = parent self.left = None self.right = None + # self.root = None def __repr__(self): return ''.format(self.val) @@ -46,7 +48,7 @@ def __len__(self): def __iter__(self): return self.in_order() - def insert(self, val): + def insert(self, val, parent=None): """Insert a node with a value into the tree. If val is already present, it will be ignored. @@ -59,14 +61,14 @@ def insert(self, val): return None if val < self.val: if self.left is None: - self.left = Node(val) + self.left = Node(val, self) else: - self.left.insert(val) + self.left.insert(val, self) elif val > self.val: if self.right is None: - self.right = Node(val) + self.right = Node(val, self) else: - self.right.insert(val) + self.right.insert(val, self) else: self.val = val From 889712c81b833ac2683f04a468a09e3f7a84dd21 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 29 Jul 2015 13:40:30 -0700 Subject: [PATCH 259/330] Extend _sorted_list_to_bst to use node parent --- bst.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bst.py b/bst.py index 2927ef5..8266108 100644 --- a/bst.py +++ b/bst.py @@ -298,7 +298,7 @@ def save_render(self, savefile="tree.gv"): src.render(path) @classmethod - def _sorted_list_to_bst(cls, items=[], start=None, end=None): + def _sorted_list_to_bst(cls, items=[], start=None, end=None, parent=None): """Create a balanced binary search tree from sorted list. args: @@ -311,9 +311,9 @@ def _sorted_list_to_bst(cls, items=[], start=None, end=None): if start > end: return None mid = start + (end - start) // 2 - node = Node(items[mid]) - node.left = cls._sorted_list_to_bst(items, start, mid - 1) - node.right = cls._sorted_list_to_bst(items, mid + 1, end) + node = Node(items[mid], parent) + node.left = cls._sorted_list_to_bst(items, start, mid - 1, node) + node.right = cls._sorted_list_to_bst(items, mid + 1, end, node) return node @classmethod From 2bb64bb48b6ee2ee0dd639f1b334bacba14fcae8 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 29 Jul 2015 14:31:46 -0700 Subject: [PATCH 260/330] Adapt delete method for parent references --- bst.py | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/bst.py b/bst.py index 8266108..a9d3002 100644 --- a/bst.py +++ b/bst.py @@ -242,9 +242,15 @@ def delete(self, val): parent.left = child else: parent.right = child + child.parent = parent else: self.left = child.left self.right = child.right + try: + self.right.parent = self + self.left.parent = self + except AttributeError: + pass self.val = child.val else: parent = node @@ -255,8 +261,10 @@ def delete(self, val): node.val = successor.val if parent.left == successor: parent.left = successor.right + parent.left.parent = parent else: parent.right = successor.right + parent.right.parent = parent def get_dot(self): """Return the tree with root as a dot graph for visualization.""" From ca74dfa48fb39da44ab835a15b0b48e2d6c060e8 Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Wed, 29 Jul 2015 14:59:27 -0700 Subject: [PATCH 261/330] Add rotate and self_balance methods to bst." --- bst.py | 66 +++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 52 insertions(+), 14 deletions(-) diff --git a/bst.py b/bst.py index a9d3002..ff58176 100644 --- a/bst.py +++ b/bst.py @@ -336,26 +336,64 @@ def create_best_case(cls, n): return cls._sorted_list_to_bst(range(n), 0, n - 1) def _is_left(self): - node, parent = self._lookup(self.val) - if parent is None: - return parent + if self.parent is None: + return self.parent else: - return self is parent.left - - def rotate_left(self): - pass + return self is self.parent.left def rotate_right(self): - pass - - def rotate_right_then_left(self): - pass + left = self._is_left() + pivot = self.left + if pivot is None: + return + self.left = pivot.right + if pivot.right is not None: + self.left.parent = self + pivot.right = self + pivot.parent = self.parent + self.parent = pivot + if left: + pivot.parent.left = pivot + else: + pivot.parent.right = pivot - def rotate_left_then_right(self): - pass + def rotate_left(self): + left = self._is_left() + pivot = self.right + if pivot is None: + return + self.right = pivot.left + if pivot.left is not None: + self.right.parent = self + pivot.left = self + pivot.parent = self.parent + self.parent = pivot + if left: + pivot.parent.left = pivot + else: + pivot.parent.right = pivot def self_balance(self): - pass + balance = self.balance() + if balance >= 2: + if self.left.balance() != -1: + self.rotate_right() + if self.parent.parent is not None: + self.parent.parent.self_balance() + else: + self.left.rotate_left() + self.self_balance() + elif balance <= -2: + if self.right.balance() != 1: + self.rotate_left() + if self.parent.parent is not None: + self.parent.parent.self_bal() + else: + self.right.rotate_right() + self.self_balance() + else: + if self.parent is not None: + self.parent.self_balance() if __name__ == '__main__': from timeit import Timer From d44a8761b0ea0b9674515391dc0cf1b485ed2875 Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Wed, 29 Jul 2015 15:15:28 -0700 Subject: [PATCH 262/330] Add balancing to insert and delete. --- bst.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/bst.py b/bst.py index ff58176..017fed0 100644 --- a/bst.py +++ b/bst.py @@ -62,11 +62,13 @@ def insert(self, val, parent=None): if val < self.val: if self.left is None: self.left = Node(val, self) + self.left.self_balance() else: self.left.insert(val, self) elif val > self.val: if self.right is None: self.right = Node(val, self) + self.right.self_balance() else: self.right.insert(val, self) else: @@ -243,6 +245,7 @@ def delete(self, val): else: parent.right = child child.parent = parent + child.self_balance() else: self.left = child.left self.right = child.right @@ -252,6 +255,7 @@ def delete(self, val): except AttributeError: pass self.val = child.val + self.self_balance() else: parent = node successor = node.right @@ -262,9 +266,11 @@ def delete(self, val): if parent.left == successor: parent.left = successor.right parent.left.parent = parent + parent.self_balance() else: parent.right = successor.right parent.right.parent = parent + parent.self_balance() def get_dot(self): """Return the tree with root as a dot graph for visualization.""" @@ -387,7 +393,7 @@ def self_balance(self): if self.right.balance() != 1: self.rotate_left() if self.parent.parent is not None: - self.parent.parent.self_bal() + self.parent.parent.self_balance() else: self.right.rotate_right() self.self_balance() From 1c7ff68db290b6a5b0c5cb7daa4d28699d3dd361 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 29 Jul 2015 17:17:05 -0700 Subject: [PATCH 263/330] Fix bug with insert --- bst.py | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/bst.py b/bst.py index 017fed0..db3377d 100644 --- a/bst.py +++ b/bst.py @@ -266,11 +266,11 @@ def delete(self, val): if parent.left == successor: parent.left = successor.right parent.left.parent = parent - parent.self_balance() + parent.left.self_balance() else: parent.right = successor.right parent.right.parent = parent - parent.self_balance() + parent.right.self_balance() def get_dot(self): """Return the tree with root as a dot graph for visualization.""" @@ -343,7 +343,7 @@ def create_best_case(cls, n): def _is_left(self): if self.parent is None: - return self.parent + return None else: return self is self.parent.left @@ -358,7 +358,9 @@ def rotate_right(self): pivot.right = self pivot.parent = self.parent self.parent = pivot - if left: + if left is None: + pass + elif left: pivot.parent.left = pivot else: pivot.parent.right = pivot @@ -374,27 +376,35 @@ def rotate_left(self): pivot.left = self pivot.parent = self.parent self.parent = pivot - if left: + if left is None: + pass + elif left: pivot.parent.left = pivot else: pivot.parent.right = pivot def self_balance(self): balance = self.balance() - if balance >= 2: + if balance == 2: if self.left.balance() != -1: + # left-left case self.rotate_right() if self.parent.parent is not None: + # move up one level self.parent.parent.self_balance() else: + # left-right case self.left.rotate_left() self.self_balance() - elif balance <= -2: + elif balance == -2: if self.right.balance() != 1: + # right-right case self.rotate_left() if self.parent.parent is not None: + # Move up one level self.parent.parent.self_balance() else: + # right-left case self.right.rotate_right() self.self_balance() else: From 1110f5fad04e23480a8c88b6c85ab2ac313c0f1f Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 29 Jul 2015 18:53:06 -0700 Subject: [PATCH 264/330] Save work --- bst.py | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/bst.py b/bst.py index db3377d..045dd7e 100644 --- a/bst.py +++ b/bst.py @@ -265,12 +265,18 @@ def delete(self, val): node.val = successor.val if parent.left == successor: parent.left = successor.right - parent.left.parent = parent - parent.left.self_balance() + try: + parent.left.parent = parent + parent.left.self_balance() + except AttributeError: + pass else: parent.right = successor.right - parent.right.parent = parent - parent.right.self_balance() + try: + parent.right.parent = parent + parent.right.self_balance() + except AttributeError: + pass def get_dot(self): """Return the tree with root as a dot graph for visualization.""" From 21d6190abae30df6169be254941e46d062688610 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 29 Jul 2015 19:23:57 -0700 Subject: [PATCH 265/330] Add create_worst_case method for testing --- bst.py | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/bst.py b/bst.py index 045dd7e..64dbd08 100644 --- a/bst.py +++ b/bst.py @@ -347,6 +347,24 @@ def create_best_case(cls, n): """ return cls._sorted_list_to_bst(range(n), 0, n - 1) + @classmethod + def create_worst_case(cls, n): + """Create an unbalanced binary search tree from a given range. + + The tree will be one long linear branch to the right. + + args: + n: the range of integers to add to the tree + + returns: a (very) unbalanced binary search tree (node) + """ + node = Node(0) + parent = node + for i in range(1, n): + parent.right = Node(i, parent) + parent = parent.right + return node + def _is_left(self): if self.parent is None: return None @@ -428,11 +446,9 @@ def self_balance(self): SIZE = 900 LOOKUP = 900 - worst = Node() - for i in range(SIZE): - worst.insert(i) - + worst = Node.create_worst_case(SIZE) best = Node.create_best_case(SIZE) + worst_case = Timer( 'worst.contains({})'.format(LOOKUP, SIZE), 'from __main__ import worst' ).timeit(1000) From 8115677a0ca922924dc2940fc7a15756fa9844f1 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 29 Jul 2015 22:01:32 -0700 Subject: [PATCH 266/330] Refactor self_balance, delete --- bst.py | 126 ++++++++++++++++++++++++++++----------------------------- 1 file changed, 62 insertions(+), 64 deletions(-) diff --git a/bst.py b/bst.py index 64dbd08..4e6fc96 100644 --- a/bst.py +++ b/bst.py @@ -226,57 +226,58 @@ def delete(self, val): node, parent = self._lookup(val) if node is not None: children_count = node.children_count() - if children_count == 0: - if parent: - if parent.left is node: - parent.left = None + if children_count == 0: + if parent: + if parent.left is node: + parent.left = None + else: + parent.right = None + parent.self_balance() else: - parent.right = None - else: - self.val = None - elif children_count == 1: - if node.left: - child = node.left - else: - child = node.right - if parent: - if parent.left is node: - parent.left = child + self.val = None + elif children_count == 1: + if node.left: + child = node.left else: - parent.right = child - child.parent = parent - child.self_balance() - else: - self.left = child.left - self.right = child.right - try: - self.right.parent = self - self.left.parent = self - except AttributeError: - pass - self.val = child.val - self.self_balance() - else: - parent = node - successor = node.right - while successor.left: - parent = successor - successor = successor.left - node.val = successor.val - if parent.left == successor: - parent.left = successor.right - try: - parent.left.parent = parent - parent.left.self_balance() - except AttributeError: - pass + child = node.right + if parent: + if parent.left is node: + parent.left = child + else: + parent.right = child + child.parent = parent + child.self_balance() + else: + self.left = child.left + self.right = child.right + try: + self.right.parent = self + self.left.parent = self + except AttributeError: + pass + self.val = child.val + self.self_balance() else: - parent.right = successor.right - try: - parent.right.parent = parent - parent.right.self_balance() - except AttributeError: - pass + parent = node + successor = node.right + while successor.left: + parent = successor + successor = successor.left + node.val = successor.val + if parent.left == successor: + parent.left = successor.right + try: + parent.left.parent = parent + parent.left.self_balance() + except AttributeError: + pass + else: + parent.right = successor.right + try: + parent.right.parent = parent + parent.right.self_balance() + except AttributeError: + pass def get_dot(self): """Return the tree with root as a dot graph for visualization.""" @@ -409,28 +410,25 @@ def rotate_left(self): def self_balance(self): balance = self.balance() + # Tree is left heavy if balance == 2: - if self.left.balance() != -1: + if self.left.balance() <= -1: # left-left case - self.rotate_right() - if self.parent.parent is not None: - # move up one level - self.parent.parent.self_balance() - else: - # left-right case self.left.rotate_left() - self.self_balance() + # left-right case + self.rotate_right() + if self.parent.parent is not None: + self.parent.parent.self_balance() + + # Tree is right heavy elif balance == -2: - if self.right.balance() != 1: + if self.right.balance() >= 1: # right-right case - self.rotate_left() - if self.parent.parent is not None: - # Move up one level - self.parent.parent.self_balance() - else: - # right-left case self.right.rotate_right() - self.self_balance() + # right-left case + self.rotate_left() + if self.parent.parent is not None: + self.parent.parent.self_balance() else: if self.parent is not None: self.parent.self_balance() From 0997970bc4f4067d89636ee88fa0cace47b2fa11 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 29 Jul 2015 22:05:10 -0700 Subject: [PATCH 267/330] Refactor _lookup due to parent attribute --- bst.py | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/bst.py b/bst.py index 4e6fc96..1084010 100644 --- a/bst.py +++ b/bst.py @@ -185,7 +185,7 @@ def breadth_first(self): if node.right: q.enqueue(node.right) - def _lookup(self, val, parent=None): + def _lookup(self, val): """Find a node by value and return that node and its parent. args: @@ -197,13 +197,13 @@ def _lookup(self, val, parent=None): if val < self.val: if self.left is None: return None, None - return self.left._lookup(val, self) + return self.left._lookup(val) elif val > self.val: if self.right is None: return None, None - return self.right._lookup(val, self) + return self.right._lookup(val) else: - return self, parent + return self def children_count(self): """Return a node's number of children.""" @@ -223,7 +223,8 @@ def delete(self, val): args: val: the value of the node to delete """ - node, parent = self._lookup(val) + node = self._lookup(val) + parent = node.parent if node is not None: children_count = node.children_count() if children_count == 0: From ba50348b3138afb3e94ac9a939b46a1ed0d35e65 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 29 Jul 2015 22:51:22 -0700 Subject: [PATCH 268/330] Fix balance point in delete --- bst.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/bst.py b/bst.py index 1084010..8ff0a02 100644 --- a/bst.py +++ b/bst.py @@ -269,16 +269,16 @@ def delete(self, val): parent.left = successor.right try: parent.left.parent = parent - parent.left.self_balance() except AttributeError: pass + parent.self_balance() else: parent.right = successor.right try: parent.right.parent = parent - parent.right.self_balance() except AttributeError: pass + parent.self_balance() def get_dot(self): """Return the tree with root as a dot graph for visualization.""" @@ -381,8 +381,8 @@ def rotate_right(self): self.left = pivot.right if pivot.right is not None: self.left.parent = self - pivot.right = self - pivot.parent = self.parent + pivot.right = self # No. Swap vals instead + pivot.parent = self.parentlo self.parent = pivot if left is None: pass From 315a95488ef27d26ce46ce5eefa6fc0a4127ca7c Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 29 Jul 2015 23:14:59 -0700 Subject: [PATCH 269/330] Fix rotate methods to not lose root object --- bst.py | 44 ++++++++++++++++++++------------------------ 1 file changed, 20 insertions(+), 24 deletions(-) diff --git a/bst.py b/bst.py index 8ff0a02..aa5a263 100644 --- a/bst.py +++ b/bst.py @@ -374,40 +374,36 @@ def _is_left(self): return self is self.parent.left def rotate_right(self): - left = self._is_left() pivot = self.left if pivot is None: return - self.left = pivot.right - if pivot.right is not None: + self.val, pivot.val = pivot.val, self.val + self.left = pivot.left + if self.left is not None: self.left.parent = self - pivot.right = self # No. Swap vals instead - pivot.parent = self.parentlo - self.parent = pivot - if left is None: - pass - elif left: - pivot.parent.left = pivot - else: - pivot.parent.right = pivot + pivot.left = pivot.right + if pivot.left is not None: + pivot.left.parent = pivot + pivot.right = self.right + if pivot.right is not None: + pivot.right.parent = pivot + self.right, pivot.parent = pivot, self def rotate_left(self): - left = self._is_left() pivot = self.right if pivot is None: return - self.right = pivot.left - if pivot.left is not None: + self.val, pivot.val = pivot.val, self.val + self.right = pivot.right + if self.right is not None: self.right.parent = self - pivot.left = self - pivot.parent = self.parent - self.parent = pivot - if left is None: - pass - elif left: - pivot.parent.left = pivot - else: - pivot.parent.right = pivot + pivot.right = pivot.left + if pivot.right is not None: + pivot.right.parent = pivot + pivot.left = self.left + if pivot.left is not None: + pivot.left.parent = pivot + self.left, pivot.parent = pivot, self def self_balance(self): balance = self.balance() From e6ce53758e9c872ee4a535057694255eaf1fa125 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 29 Jul 2015 23:34:05 -0700 Subject: [PATCH 270/330] Add auto-render flag to insert and delete; fix parent level on self_balance --- bst.py | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/bst.py b/bst.py index aa5a263..ba1fc77 100644 --- a/bst.py +++ b/bst.py @@ -48,7 +48,7 @@ def __len__(self): def __iter__(self): return self.in_order() - def insert(self, val, parent=None): + def insert(self, val, render=False): """Insert a node with a value into the tree. If val is already present, it will be ignored. @@ -64,15 +64,17 @@ def insert(self, val, parent=None): self.left = Node(val, self) self.left.self_balance() else: - self.left.insert(val, self) + self.left.insert(val, render) elif val > self.val: if self.right is None: self.right = Node(val, self) self.right.self_balance() else: - self.right.insert(val, self) + self.right.insert(val, render) else: self.val = val + if render and self.parent is None: + self.save_render() def contains(self, val): """Check tree for node with given value. @@ -214,7 +216,7 @@ def children_count(self): cnt += 1 return cnt - def delete(self, val): + def delete(self, val, render=False): """Delete a node matching value and reorganize tree as needed. If the matched node is the only node in the tree, only its value @@ -279,6 +281,8 @@ def delete(self, val): except AttributeError: pass parent.self_balance() + if render and self.parent is None: + self.save_render() def get_dot(self): """Return the tree with root as a dot graph for visualization.""" @@ -414,8 +418,8 @@ def self_balance(self): self.left.rotate_left() # left-right case self.rotate_right() - if self.parent.parent is not None: - self.parent.parent.self_balance() + if self.parent is not None: + self.parent.self_balance() # Tree is right heavy elif balance == -2: @@ -424,8 +428,8 @@ def self_balance(self): self.right.rotate_right() # right-left case self.rotate_left() - if self.parent.parent is not None: - self.parent.parent.self_balance() + if self.parent is not None: + self.parent.self_balance() else: if self.parent is not None: self.parent.self_balance() From ea045b965fe84cadfb360974b263b46549e68cd5 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 29 Jul 2015 23:49:26 -0700 Subject: [PATCH 271/330] Add ability to shut off AVL balancing on insert or delete --- bst.py | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/bst.py b/bst.py index ba1fc77..11382f2 100644 --- a/bst.py +++ b/bst.py @@ -34,7 +34,6 @@ def __init__(self, val=None, parent=None): self.parent = parent self.left = None self.right = None - # self.root = None def __repr__(self): return ''.format(self.val) @@ -48,7 +47,7 @@ def __len__(self): def __iter__(self): return self.in_order() - def insert(self, val, render=False): + def insert(self, val, balanced=True, render=False): """Insert a node with a value into the tree. If val is already present, it will be ignored. @@ -62,15 +61,17 @@ def insert(self, val, render=False): if val < self.val: if self.left is None: self.left = Node(val, self) - self.left.self_balance() + if balanced: + self.left.self_balance() else: - self.left.insert(val, render) + self.left.insert(val, balanced, render) elif val > self.val: if self.right is None: self.right = Node(val, self) - self.right.self_balance() + if balanced: + self.right.self_balance() else: - self.right.insert(val, render) + self.right.insert(val, balanced, render) else: self.val = val if render and self.parent is None: @@ -216,7 +217,7 @@ def children_count(self): cnt += 1 return cnt - def delete(self, val, render=False): + def delete(self, val, balanced=True, render=False): """Delete a node matching value and reorganize tree as needed. If the matched node is the only node in the tree, only its value @@ -235,7 +236,8 @@ def delete(self, val, render=False): parent.left = None else: parent.right = None - parent.self_balance() + if balanced: + parent.self_balance() else: self.val = None elif children_count == 1: @@ -249,7 +251,8 @@ def delete(self, val, render=False): else: parent.right = child child.parent = parent - child.self_balance() + if balanced: + child.self_balance() else: self.left = child.left self.right = child.right @@ -259,7 +262,8 @@ def delete(self, val, render=False): except AttributeError: pass self.val = child.val - self.self_balance() + if balanced: + self.self_balance() else: parent = node successor = node.right @@ -280,7 +284,8 @@ def delete(self, val, render=False): parent.right.parent = parent except AttributeError: pass - parent.self_balance() + if balanced: + parent.self_balance() if render and self.parent is None: self.save_render() From 14b02efecc5e9c2d6b766f8c4d664592351121d5 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 00:01:41 -0700 Subject: [PATCH 272/330] Fix old non-AVL tests --- test_bst.py | 78 +++++++++++++++++++++++++++-------------------------- 1 file changed, 40 insertions(+), 38 deletions(-) diff --git a/test_bst.py b/test_bst.py index 81680c9..350233c 100644 --- a/test_bst.py +++ b/test_bst.py @@ -12,7 +12,7 @@ def rand_setup(): root = Node(randint(1, 100)) for idx in range(20): val = randint(1, 100) - root.insert(val) + root.insert(val, balanced=False) return root @@ -20,16 +20,19 @@ def rand_setup(): @pytest.fixture() def fixed_setup(): root = Node(25) - root.insert(15) - root.insert(30) - root.insert(9) - root.insert(17) - root.insert(21) - root.insert(39) - root.insert(12) - root.insert(24) - root.insert(40) - root.insert(14) + setup = [15, 30, 9, 17, 21, 39, 12, 24, 40, 14] + for item in setup: + root.insert(item, balanced=False) + # root.insert(15) + # root.insert(30) + # root.insert(9) + # root.insert(17) + # root.insert(21) + # root.insert(39) + # root.insert(12) + # root.insert(24) + # root.insert(40) + # root.insert(14) return root @@ -39,7 +42,7 @@ def traversal_setup(): root = Node() setup = ['F', 'B', 'A', 'D', 'C', 'E', 'G', 'I', 'H'] for char in setup: - root.insert(char) + root.insert(char, balanced=False) return root @@ -58,7 +61,7 @@ def test_init_with_val(): def test_insert_in_empty_root(): root = Node() expected = 10 - root.insert(expected) + root.insert(expected, balanced=False) actual = root.val assert expected == actual @@ -66,7 +69,7 @@ def test_insert_in_empty_root(): def test_insert_lesser_in_filled_root(): root = Node(10) expected = 5 - root.insert(expected) + root.insert(expected, balanced=False) actual = root.left.val assert expected == actual assert root.right is None @@ -75,7 +78,7 @@ def test_insert_lesser_in_filled_root(): def test_insert_greater_in_filled_root(): root = Node(10) expected = 15 - root.insert(expected) + root.insert(expected, balanced=False) actual = root.right.val assert expected == actual assert root.left is None @@ -84,7 +87,7 @@ def test_insert_greater_in_filled_root(): def test_insert_lesser_in_filled_tree1(): root = Node(10) root.left, root.right = Node(5), Node(15) - root.insert(1) + root.insert(1, balanced=False) assert root.left.left.val == 1 assert root.left.right is None @@ -92,7 +95,7 @@ def test_insert_lesser_in_filled_tree1(): def test_insert_lesser_in_filled_tree2(): root = Node(10) root.left, root.right = Node(5), Node(15) - root.insert(7) + root.insert(7, balanced=False) assert root.left.right.val == 7 assert root.left.left is None @@ -100,7 +103,7 @@ def test_insert_lesser_in_filled_tree2(): def test_insert_greater_in_filled_tree1(): root = Node(10) root.left, root.right = Node(5), Node(15) - root.insert(17) + root.insert(17, balanced=False) assert root.right.right.val == 17 assert root.right.left is None @@ -108,7 +111,7 @@ def test_insert_greater_in_filled_tree1(): def test_insert_greater_in_filled_tree2(): root = Node(10) root.left, root.right = Node(5), Node(15) - root.insert(12) + root.insert(12, balanced=False) assert root.right.left.val == 12 assert root.right.right is None @@ -116,18 +119,18 @@ def test_insert_greater_in_filled_tree2(): def test_insert_duplicates(): root = Node(10) root.left, root.right = Node(5), Node(15) - root.insert(5) + root.insert(5, balanced=False) assert root.size() == 3 - root.insert(15) + root.insert(15, balanced=False) assert root.size() == 3 def test_insert(rand_setup): pre_size = rand_setup.size() new = 200 - rand_setup.insert(new) - assert rand_setup.insert(new) is None - rand_setup.insert(new) + rand_setup.insert(new, balanced=False) + assert rand_setup.insert(new, balanced=False) is None + rand_setup.insert(new, balanced=False) post_size = rand_setup.size() assert post_size > pre_size assert post_size == pre_size + 1 @@ -143,7 +146,7 @@ def test_contains_val(): def test_contains(rand_setup): rand = randint(1, 100) - rand_setup.insert(rand) + rand_setup.insert(rand, balanced=False) assert rand_setup.contains(rand) is True @@ -159,8 +162,8 @@ def test_size_with_filling(): def test_size(rand_setup): pre_size = rand_setup.size() new = 200 - rand_setup.insert(new) - rand_setup.insert(new) + rand_setup.insert(new, balanced=False) + rand_setup.insert(new, balanced=False) post_size = rand_setup.size() assert post_size > pre_size assert post_size == pre_size + 1 @@ -203,7 +206,7 @@ def test_depth_n(): def test_depth(fixed_setup): assert fixed_setup.left.depth() == 4 assert fixed_setup.right.depth() == 3 - fixed_setup.insert(13) + fixed_setup.insert(13, balanced=False) assert fixed_setup.left.depth() == 5 @@ -299,36 +302,35 @@ def test_create_best_case(): def test_lookup(traversal_setup): root = traversal_setup - expected_root, expected_parent = root.left, root - actual_root, actual_parent = root._lookup('B') - assert expected_root is actual_root - assert expected_parent is actual_parent + expected = root.left + actual = root._lookup('B') + assert expected is actual def test_delete_root_only(): root = Node('A') - root.delete('A') + root.delete('A', balanced=False) assert root.val is None assert root.left is None and root.right is None def test_delete_node_without_children(traversal_setup): root = traversal_setup - root.delete('A') + root.delete('A', balanced=False) parent = root.left assert parent.left is None def test_delete_node_with_one_child(traversal_setup): root = traversal_setup - root.delete('G') + root.delete('G', balanced=False) parent = root assert parent.right.val == 'I' def test_delete_node_with_two_children(traversal_setup): root = traversal_setup - root.delete('B') + root.delete('B', balanced=False) parent = root assert parent.left.val == 'C' successor = parent.left @@ -339,13 +341,13 @@ def test_delete_root_with_one_child(): root = Node('F') root.left = Node('B') root.left.left, root.left.right = Node('A'), Node('D') - root.delete('F') + root.delete('F', balanced=False) assert root.val == 'B' assert root.left.val == 'A' and root.right.val == 'D' def test_delete_root_with_two_children(traversal_setup): root = traversal_setup - root.delete('F') + root.delete('F', balanced=False) assert root.val == 'G' assert root.left.val == 'B' and root.right.val == 'I' From 29d7527fe714a66d9d4727092c5a69e2740b307a Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 00:22:20 -0700 Subject: [PATCH 273/330] Add tests for AVL insert --- test_bst.py | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) diff --git a/test_bst.py b/test_bst.py index 350233c..b7f2c1c 100644 --- a/test_bst.py +++ b/test_bst.py @@ -351,3 +351,66 @@ def test_delete_root_with_two_children(traversal_setup): root.delete('F', balanced=False) assert root.val == 'G' assert root.left.val == 'B' and root.right.val == 'I' + + +# Tests for AVL Balanced Behavior Follow +def test_avl_insert_in_empty_root(): + root = Node() + expected = 10 + root.insert(expected) + actual = root.val + assert expected == actual + + +def test_avl_insert_lesser_in_filled_root(): + root = Node(10) + expected = 5 + root.insert(expected) + actual = root.left.val + assert expected == actual + assert root.right is None + + +def test_avl_insert_greater_in_filled_root(): + root = Node(10) + expected = 15 + root.insert(expected) + actual = root.right.val + assert expected == actual + assert root.left is None + + +def test_avl_insert_lesser_in_filled_tree1(): + root = Node(10) + root.left, root.right = Node(5, root.left), Node(15, root.right) + root.left.left = Node(2, root.left) + root.insert(1) + assert root.left.val == 2 + assert root.left.left.val == 1 and root.left.right.val == 5 + + +def test_avl_insert_lesser_in_filled_tree2(): + root = Node(10) + root.left, root.right = Node(5, root.left), Node(15, root.right) + root.left.left = Node(2, root.left) + root.insert(3) + assert root.left.val == 3 + assert root.left.left.val == 2 and root.left.right.val == 5 + + +def test_avl_insert_greater_in_filled_tree1(): + root = Node(10) + root.left, root.right = Node(5, root.left), Node(15, root.right) + root.right.right = Node(20, root.right) + root.insert(17) + assert root.right.val == 17 + assert root.right.left.val == 15 and root.right.right.val == 20 + + +def test_avl_insert_greater_in_filled_tree2(): + root = Node(10) + root.left, root.right = Node(5, root.left), Node(15, root.right) + root.right.right = Node(20, root.right) + root.insert(25) + assert root.right.val == 20 + assert root.right.left.val == 15 and root.right.right.val == 25 From 91e0b2ba6909f35a3896a2e9583e62f54ea41073 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 01:13:04 -0700 Subject: [PATCH 274/330] Complete AVL tests for insert and delete --- test_bst.py | 77 ++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 61 insertions(+), 16 deletions(-) diff --git a/test_bst.py b/test_bst.py index b7f2c1c..bef4ee6 100644 --- a/test_bst.py +++ b/test_bst.py @@ -13,7 +13,6 @@ def rand_setup(): for idx in range(20): val = randint(1, 100) root.insert(val, balanced=False) - return root @@ -23,17 +22,6 @@ def fixed_setup(): setup = [15, 30, 9, 17, 21, 39, 12, 24, 40, 14] for item in setup: root.insert(item, balanced=False) - # root.insert(15) - # root.insert(30) - # root.insert(9) - # root.insert(17) - # root.insert(21) - # root.insert(39) - # root.insert(12) - # root.insert(24) - # root.insert(40) - # root.insert(14) - return root @@ -382,7 +370,7 @@ def test_avl_insert_greater_in_filled_root(): def test_avl_insert_lesser_in_filled_tree1(): root = Node(10) - root.left, root.right = Node(5, root.left), Node(15, root.right) + root.left, root.right = Node(5), Node(15) root.left.left = Node(2, root.left) root.insert(1) assert root.left.val == 2 @@ -391,7 +379,7 @@ def test_avl_insert_lesser_in_filled_tree1(): def test_avl_insert_lesser_in_filled_tree2(): root = Node(10) - root.left, root.right = Node(5, root.left), Node(15, root.right) + root.left, root.right = Node(5, root.left), Node(15) root.left.left = Node(2, root.left) root.insert(3) assert root.left.val == 3 @@ -400,7 +388,7 @@ def test_avl_insert_lesser_in_filled_tree2(): def test_avl_insert_greater_in_filled_tree1(): root = Node(10) - root.left, root.right = Node(5, root.left), Node(15, root.right) + root.left, root.right = Node(5), Node(15) root.right.right = Node(20, root.right) root.insert(17) assert root.right.val == 17 @@ -409,8 +397,65 @@ def test_avl_insert_greater_in_filled_tree1(): def test_avl_insert_greater_in_filled_tree2(): root = Node(10) - root.left, root.right = Node(5, root.left), Node(15, root.right) + root.left, root.right = Node(5), Node(15, root.right) root.right.right = Node(20, root.right) root.insert(25) assert root.right.val == 20 assert root.right.left.val == 15 and root.right.right.val == 25 + + +def test_avl_delete_root_only(): + root = Node(0) + root.delete(0) + assert root.val is None + assert root.left is None and root.right is None + + +def test_avl_delete_root_with_one_child(): + root = Node(10) + root.left = Node(5, root) + root.delete(10) + assert root.val == 5 + assert root.left is None and root.right is None + + +def test_avl_delete_root_with_two_children(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.delete(10) + assert root.val == 15 + assert root.left.val == 5 and root.right is None + + +def test_avl_delete_node_without_children(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.right.right = Node(20, root.right) + root.delete(5) + assert root.val == 15 + assert root.left.val == 10 and root.right.val == 20 + + +def test_avl_delete_node_with_one_child(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.right.left = Node(12, root.right) + root.right.right = Node(20, root.right) + root.right.left.right = Node(13, root.right.left) + root.left.right = Node(8, root.left) + root.delete(5) + assert root.val == 12 + assert root.left.val == 10 and root.right.val == 15 + + +def test_avl_delete_node_with_two_children(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.right.left = Node(12, root.right) + root.right.right = Node(20, root.right) + root.right.left.right = Node(13, root.right.left) + root.left.right = Node(8, root.left) + root.delete(15) + assert root.right.val == 13 + assert root.right.left.val == 12 and root.right.right.val == 20 + From e20b1716173d448ecc566afec704c4b88e86758d Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 01:23:53 -0700 Subject: [PATCH 275/330] Add tests for create worst case and _is_left --- test_bst.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/test_bst.py b/test_bst.py index bef4ee6..17ec237 100644 --- a/test_bst.py +++ b/test_bst.py @@ -288,6 +288,12 @@ def test_create_best_case(): assert root.size() == 100 and root.balance() == 0 +def test_create_worst_case(): + root = Node.create_worst_case(100) + assert isinstance(root, Node) + assert root.size() == 100 and root.balance() == -99 + + def test_lookup(traversal_setup): root = traversal_setup expected = root.left @@ -295,6 +301,27 @@ def test_lookup(traversal_setup): assert expected is actual +def test_is_left_no_parent(): + root = Node(10) + root.left, root.right = Node(5, root.left), Node(15) + assert root._is_left() is None + + +def test_is_left_left_node(): + root = Node(10) + root.left, root.right = Node(5, root.left), Node(15) + node = root.left + result = node._is_left() + assert result is None + + +def test_is_left_right_node(): + root = Node(10) + root.left, root.right = Node(5, root.left), Node(15) + node = root.right + assert not node._is_left() + + def test_delete_root_only(): root = Node('A') root.delete('A', balanced=False) From 039a5f521cd1f4788677d9e5365d5b92637aee98 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 01:43:53 -0700 Subject: [PATCH 276/330] Add rotate tests --- test_bst.py | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/test_bst.py b/test_bst.py index 17ec237..96c74e5 100644 --- a/test_bst.py +++ b/test_bst.py @@ -34,6 +34,16 @@ def traversal_setup(): return root +@pytest.fixture() +def pivot_setup(): + root = Node(10) + root.left, root.right = Node(5, root), Node(15, root) + root.left.left = Node(1, root.left) + root.left.right = Node(7, root.left) + pivot = root.left + return root, pivot + + def test_init_empty(): root = Node() assert root.val is None @@ -369,6 +379,34 @@ def test_delete_root_with_two_children(traversal_setup): # Tests for AVL Balanced Behavior Follow +def test_rotate_right(pivot_setup): + root, pivot = pivot_setup + root_val, pivot_val = root.val, pivot.val + root_right = root.right + pivot_right, pivot_left = pivot.right, pivot.left + root.rotate_right() + assert root.val == pivot_val and pivot.val == root_val + assert root_right is pivot.right and pivot_right is pivot.left + assert pivot_left is root.left + + +def test_rotate_left(pivot_setup): + root, pivot = pivot_setup + root_val, pivot_val = root.val, pivot.val + root_right = root.right + pivot_right, pivot_left = pivot.right, pivot.left + root.rotate_right() + root.rotate_left() + root.rotate_right() + assert root.val == pivot_val and pivot.val == root_val + assert root_right is pivot.right and pivot_right is pivot.left + assert pivot_left is root.left + + +def test_self_balance(): + pass + + def test_avl_insert_in_empty_root(): root = Node() expected = 10 @@ -485,4 +523,3 @@ def test_avl_delete_node_with_two_children(): root.delete(15) assert root.right.val == 13 assert root.right.left.val == 12 and root.right.right.val == 20 - From 4ad6cb04818bdd7c2090a91957a94825b6274c37 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 02:06:33 -0700 Subject: [PATCH 277/330] Complete self_balance tests --- bst.py | 8 ++++---- test_bst.py | 40 ++++++++++++++++++++++++++++++++++++++-- 2 files changed, 42 insertions(+), 6 deletions(-) diff --git a/bst.py b/bst.py index 11382f2..a6b368b 100644 --- a/bst.py +++ b/bst.py @@ -419,9 +419,9 @@ def self_balance(self): # Tree is left heavy if balance == 2: if self.left.balance() <= -1: - # left-left case + # Double Right self.left.rotate_left() - # left-right case + # Single Right self.rotate_right() if self.parent is not None: self.parent.self_balance() @@ -429,9 +429,9 @@ def self_balance(self): # Tree is right heavy elif balance == -2: if self.right.balance() >= 1: - # right-right case + # Double Left self.right.rotate_right() - # right-left case + # Single Left self.rotate_left() if self.parent is not None: self.parent.self_balance() diff --git a/test_bst.py b/test_bst.py index 96c74e5..6430189 100644 --- a/test_bst.py +++ b/test_bst.py @@ -403,8 +403,44 @@ def test_rotate_left(pivot_setup): assert pivot_left is root.left -def test_self_balance(): - pass +def test_self_balance_single_right(): + root = Node(10) + root.left = Node(5, root) + root.left.left = Node(1, root.left) + leaf = root.left.left + leaf.self_balance() + assert root.val == 5 + assert root.left.val == 1 and root.right.val == 10 + + +def test_self_balance_double_right(): + root = Node(10) + root.left = Node(5, root) + root.left.right = Node(7, root.left) + leaf = root.left.right + leaf.self_balance() + assert root.val == 7 + assert root.left.val == 5 and root.right.val == 10 + + +def test_self_balance_single_left(): + root = Node(10) + root.right = Node(15, root) + root.right.right = Node(20, root.right) + leaf = root.right.right + leaf.self_balance() + assert root.val == 15 + assert root.left.val == 10 and root.right.val == 20 + + +def test_self_balance_double_left(): + root = Node(10) + root.right = Node(15, root) + root.right.left = Node(13, root.right) + leaf = root.right.left + leaf.self_balance() + assert root.val == 13 + assert root.left.val == 10 and root.right.val == 15 def test_avl_insert_in_empty_root(): From 3bd3542a670042aa29c6d90146a575988d29877c Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 02:23:52 -0700 Subject: [PATCH 278/330] Improve docstrings --- bst.py | 45 ++++++++++++++++++++++++++++++++++++++------- test_bst.py | 9 +++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/bst.py b/bst.py index a6b368b..0e11831 100644 --- a/bst.py +++ b/bst.py @@ -1,24 +1,41 @@ -"""Contains a Node class which implements a binary search tree. +"""Contains a Node class which implements an AVL binary search tree. Each node can be considered a binary search tree and has the usual -methods to insert, delete, and check membership of nodes. The class also -supports four traversal methods which return generators: +methods to insert, delete, and check membership of nodes. By default, +the insert and delete methods will perform self-balancing consistent +with an AVL tree. This behavior can be suppressed by passing the optional +'balanced=False' keyword argument to the insert or delete methods. -in_order, pre_order, post_order, and breadth_first. +The class also supports four traversal methods which return generators: + +- in_order +- pre_order +- post_order +- breadth_first. Additionally, methods are included to help visualize the tree structure. get_dot returns DOT source code, suitable for use with programs such as Graphviz (http://graphviz.readthedocs.org/en/stable/index.html), and save_render saves a rendering of the tree structure to the file system. +Passing the optional 'render=True' keyword argument to the insert and +delete methods will automatically save a render to disk upon execution. + +Finally, the helper methods 'create_best_case' and 'create_worst_case' +facilitates creation of tree composeds of _n_ integers. -Finally, the helper method `create_best_case' facilitates creation of a -balanced tree composed of _n_ integers. +This module was completed with reference to the following: -This module was completed with reference to the very helpful post 'Binary Search Tree libary in Python' (http://www.laurentluce.com/posts/binary-search-tree-library-in-python/) by Laurent Luce. +'How to Balance your Binary Search Trees - AVL Trees' +(https://triangleinequality.wordpress.com/2014/07/15/how-to-balance-your-binary-search-trees-avl-trees/) + +'The AVL Tree Rotations Tutorial' +(http://pages.cs.wisc.edu/~paton/readings/liblitVersion/AVL-Tree-Rotations.pdf) +by John Hargrove + """ from __future__ import print_function from __future__ import unicode_literals @@ -54,6 +71,8 @@ def insert(self, val, balanced=True, render=False): args: val: the value to insert + balanced: performs AVL self-balancing if set to True + render: automatically saves a render to disk if set to True """ if self.val is not None: if val == self.val: @@ -225,6 +244,8 @@ def delete(self, val, balanced=True, render=False): args: val: the value of the node to delete + balanced: performs AVL self-balancing if set to True + render: automatically saves a render to disk if set to True """ node = self._lookup(val) parent = node.parent @@ -377,12 +398,20 @@ def create_worst_case(cls, n): return node def _is_left(self): + """Check nodes relationship to parent. + + returns: + - True if node is left child of parent + - False if node is right childe of parent + - None if node has no parent + """ if self.parent is None: return None else: return self is self.parent.left def rotate_right(self): + """Perform a single right tree rotation.""" pivot = self.left if pivot is None: return @@ -399,6 +428,7 @@ def rotate_right(self): self.right, pivot.parent = pivot, self def rotate_left(self): + """Perform a single left tree rotation.""" pivot = self.right if pivot is None: return @@ -415,6 +445,7 @@ def rotate_left(self): self.left, pivot.parent = pivot, self def self_balance(self): + """Balance the subtree from given node.""" balance = self.balance() # Tree is left heavy if balance == 2: diff --git a/test_bst.py b/test_bst.py index 6430189..e156881 100644 --- a/test_bst.py +++ b/test_bst.py @@ -471,7 +471,7 @@ def test_avl_insert_greater_in_filled_root(): def test_avl_insert_lesser_in_filled_tree1(): root = Node(10) - root.left, root.right = Node(5), Node(15) + root.left, root.right = Node(5, root), Node(15, root) root.left.left = Node(2, root.left) root.insert(1) assert root.left.val == 2 @@ -480,7 +480,7 @@ def test_avl_insert_lesser_in_filled_tree1(): def test_avl_insert_lesser_in_filled_tree2(): root = Node(10) - root.left, root.right = Node(5, root.left), Node(15) + root.left, root.right = Node(5, root), Node(15, root) root.left.left = Node(2, root.left) root.insert(3) assert root.left.val == 3 @@ -489,7 +489,7 @@ def test_avl_insert_lesser_in_filled_tree2(): def test_avl_insert_greater_in_filled_tree1(): root = Node(10) - root.left, root.right = Node(5), Node(15) + root.left, root.right = Node(5, root), Node(15, root) root.right.right = Node(20, root.right) root.insert(17) assert root.right.val == 17 @@ -498,7 +498,7 @@ def test_avl_insert_greater_in_filled_tree1(): def test_avl_insert_greater_in_filled_tree2(): root = Node(10) - root.left, root.right = Node(5), Node(15, root.right) + root.left, root.right = Node(5, root), Node(15, root) root.right.right = Node(20, root.right) root.insert(25) assert root.right.val == 20 @@ -559,3 +559,4 @@ def test_avl_delete_node_with_two_children(): root.delete(15) assert root.right.val == 13 assert root.right.left.val == 12 and root.right.right.val == 20 + From 109755fcf6b221cb91d2f55bcd92189865ef5ce2 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 02:29:01 -0700 Subject: [PATCH 279/330] Update readme --- README.md | 39 +++++++++++++++++++++++++++++++++------ 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 4e2ef0b..35f3b08 100644 --- a/README.md +++ b/README.md @@ -91,20 +91,46 @@ O(nodes * edges). See the doc strings for additional implementation details. ##Binary Search Tree -The included node class implements a [binary search tree](https://en.wikipedia.org/wiki/Binary_search_tree). Binary search trees allow lookup operations using binary search, which allows operations such as search and insert to be completed with an average case time complexity of O(log n) and worst case O(n). +Contains a Node class which implements an AVL binary search tree. -The node class supports four traversal methods which return generators: `in_order`, `pre_order`, `post_order`, and `breadth_first`. Further details are available at the Wikipedia entry for [Tree Traversal](https://en.wikipedia.org/wiki/Tree_traversal). +Each node can be considered a binary search tree and has the usual +methods to insert, delete, and check membership of nodes. By default, +the insert and delete methods will perform self-balancing consistent +with an AVL tree. This behavior can be suppressed by passing the optional +'balanced=False' keyword argument to the insert or delete methods. -Additionally, methods are included to help visualize the tree structure. `get_dot` returns DOT source code, suitable for use with programs such as [Graphviz](http://graphviz.readthedocs.org/en/stable/index.html), and `save_render` saves a rendering of the tree structure to the file system. +The class also supports four traversal methods which return generators: -Finally, the helper method `create_best_case' facilitates creation of a balance tree composed of _n_ integers. +- in_order +- pre_order +- post_order +- breadth_first. -This module was completed with reference to the very helpful post [Binary Search Tree libary in Python](http://www.laurentluce.com/posts/binary-search-tree-library-in-python/) by Laurent Luce. +Additionally, methods are included to help visualize the tree structure. +get_dot returns DOT source code, suitable for use with programs such as +Graphviz (http://graphviz.readthedocs.org/en/stable/index.html), and +save_render saves a rendering of the tree structure to the file system. +Passing the optional 'render=True' keyword argument to the insert and +delete methods will automatically save a render to disk upon execution. +Finally, the helper methods 'create_best_case' and 'create_worst_case' +facilitates creation of tree composeds of _n_ integers. + +This module was completed with reference to the following: + +[Binary Search Tree libary in Python](http://www.laurentluce.com/posts/binary-search-tree-library-in-python/) +by Laurent Luce. + +[How to Balance your Binary Search Trees - AVL Trees](https://triangleinequality.wordpress.com/2014/07/15/how-to-balance-your-binary-search-trees-avl-trees/) + +[The AVL Tree Rotations Tutorial](http://pages.cs.wisc.edu/~paton/readings/liblitVersion/AVL-Tree-Rotations.pdf) +by John Hargrove Available methods include: -* insert(val) +* insert(val, balanced=True, render=False) +* delete(val, balanced=True, render=False) * contains(val) +* lookup(val) * size() * depth() * balance() @@ -115,6 +141,7 @@ Available methods include: * get_dot() * save_render() * create_best_case() +* create_worst_case() See the doc strings for additional implementation details. From f7b8955812c52517dce993132303b484fb047dfb Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 02:35:26 -0700 Subject: [PATCH 280/330] Mark some methods private; reorganize --- bst.py | 340 ++++++++++++++++++++++++++-------------------------- test_bst.py | 18 +-- 2 files changed, 179 insertions(+), 179 deletions(-) diff --git a/bst.py b/bst.py index 0e11831..17f2937 100644 --- a/bst.py +++ b/bst.py @@ -81,14 +81,14 @@ def insert(self, val, balanced=True, render=False): if self.left is None: self.left = Node(val, self) if balanced: - self.left.self_balance() + self.left._self_balance() else: self.left.insert(val, balanced, render) elif val > self.val: if self.right is None: self.right = Node(val, self) if balanced: - self.right.self_balance() + self.right._self_balance() else: self.right.insert(val, balanced, render) else: @@ -96,6 +96,80 @@ def insert(self, val, balanced=True, render=False): if render and self.parent is None: self.save_render() + def delete(self, val, balanced=True, render=False): + """Delete a node matching value and reorganize tree as needed. + + If the matched node is the only node in the tree, only its value + will be deleted. + + args: + val: the value of the node to delete + balanced: performs AVL self-balancing if set to True + render: automatically saves a render to disk if set to True + """ + node = self.lookup(val) + parent = node.parent + if node is not None: + children_count = node._children_count() + if children_count == 0: + if parent: + if parent.left is node: + parent.left = None + else: + parent.right = None + if balanced: + parent._self_balance() + else: + self.val = None + elif children_count == 1: + if node.left: + child = node.left + else: + child = node.right + if parent: + if parent.left is node: + parent.left = child + else: + parent.right = child + child.parent = parent + if balanced: + child._self_balance() + else: + self.left = child.left + self.right = child.right + try: + self.right.parent = self + self.left.parent = self + except AttributeError: + pass + self.val = child.val + if balanced: + self._self_balance() + else: + parent = node + successor = node.right + while successor.left: + parent = successor + successor = successor.left + node.val = successor.val + if parent.left == successor: + parent.left = successor.right + try: + parent.left.parent = parent + except AttributeError: + pass + parent._self_balance() + else: + parent.right = successor.right + try: + parent.right.parent = parent + except AttributeError: + pass + if balanced: + parent._self_balance() + if render and self.parent is None: + self.save_render() + def contains(self, val): """Check tree for node with given value. @@ -115,6 +189,26 @@ def contains(self, val): return False return self.right.contains(val) + def lookup(self, val): + """Find a node by value and return that node and its parent. + + args: + val: the value to search by + parent: the parent of the node (for recursion) + + returns: a tuple with node and its parent + """ + if val < self.val: + if self.left is None: + return None, None + return self.left.lookup(val) + elif val > self.val: + if self.right is None: + return None, None + return self.right.lookup(val) + else: + return self + def size(self): """Return the total number of nodes in the tree. @@ -151,6 +245,79 @@ def balance(self): right_depth = self.right.depth() if self.right is not None else 0 return left_depth - right_depth + def _is_left(self): + """Check nodes relationship to parent. + + returns: + - True if node is left child of parent + - False if node is right childe of parent + - None if node has no parent + """ + if self.parent is None: + return None + else: + return self is self.parent.left + + def _rotate_right(self): + """Perform a single right tree rotation.""" + pivot = self.left + if pivot is None: + return + self.val, pivot.val = pivot.val, self.val + self.left = pivot.left + if self.left is not None: + self.left.parent = self + pivot.left = pivot.right + if pivot.left is not None: + pivot.left.parent = pivot + pivot.right = self.right + if pivot.right is not None: + pivot.right.parent = pivot + self.right, pivot.parent = pivot, self + + def _rotate_left(self): + """Perform a single left tree rotation.""" + pivot = self.right + if pivot is None: + return + self.val, pivot.val = pivot.val, self.val + self.right = pivot.right + if self.right is not None: + self.right.parent = self + pivot.right = pivot.left + if pivot.right is not None: + pivot.right.parent = pivot + pivot.left = self.left + if pivot.left is not None: + pivot.left.parent = pivot + self.left, pivot.parent = pivot, self + + def _self_balance(self): + """Balance the subtree from given node.""" + balance = self.balance() + # Tree is left heavy + if balance == 2: + if self.left.balance() <= -1: + # Double Right + self.left._rotate_left() + # Single Right + self._rotate_right() + if self.parent is not None: + self.parent._self_balance() + + # Tree is right heavy + elif balance == -2: + if self.right.balance() >= 1: + # Double Left + self.right._rotate_right() + # Single Left + self._rotate_left() + if self.parent is not None: + self.parent._self_balance() + else: + if self.parent is not None: + self.parent._self_balance() + def in_order(self): """Return a generator with tree values from in-order traversal""" stack = [] @@ -207,27 +374,7 @@ def breadth_first(self): if node.right: q.enqueue(node.right) - def _lookup(self, val): - """Find a node by value and return that node and its parent. - - args: - val: the value to search by - parent: the parent of the node (for recursion) - - returns: a tuple with node and its parent - """ - if val < self.val: - if self.left is None: - return None, None - return self.left._lookup(val) - elif val > self.val: - if self.right is None: - return None, None - return self.right._lookup(val) - else: - return self - - def children_count(self): + def _children_count(self): """Return a node's number of children.""" cnt = 0 if self.left: @@ -236,80 +383,6 @@ def children_count(self): cnt += 1 return cnt - def delete(self, val, balanced=True, render=False): - """Delete a node matching value and reorganize tree as needed. - - If the matched node is the only node in the tree, only its value - will be deleted. - - args: - val: the value of the node to delete - balanced: performs AVL self-balancing if set to True - render: automatically saves a render to disk if set to True - """ - node = self._lookup(val) - parent = node.parent - if node is not None: - children_count = node.children_count() - if children_count == 0: - if parent: - if parent.left is node: - parent.left = None - else: - parent.right = None - if balanced: - parent.self_balance() - else: - self.val = None - elif children_count == 1: - if node.left: - child = node.left - else: - child = node.right - if parent: - if parent.left is node: - parent.left = child - else: - parent.right = child - child.parent = parent - if balanced: - child.self_balance() - else: - self.left = child.left - self.right = child.right - try: - self.right.parent = self - self.left.parent = self - except AttributeError: - pass - self.val = child.val - if balanced: - self.self_balance() - else: - parent = node - successor = node.right - while successor.left: - parent = successor - successor = successor.left - node.val = successor.val - if parent.left == successor: - parent.left = successor.right - try: - parent.left.parent = parent - except AttributeError: - pass - parent.self_balance() - else: - parent.right = successor.right - try: - parent.right.parent = parent - except AttributeError: - pass - if balanced: - parent.self_balance() - if render and self.parent is None: - self.save_render() - def get_dot(self): """Return the tree with root as a dot graph for visualization.""" return "digraph G{\n%s}" % ("" if self.val is None else ( @@ -397,79 +470,6 @@ def create_worst_case(cls, n): parent = parent.right return node - def _is_left(self): - """Check nodes relationship to parent. - - returns: - - True if node is left child of parent - - False if node is right childe of parent - - None if node has no parent - """ - if self.parent is None: - return None - else: - return self is self.parent.left - - def rotate_right(self): - """Perform a single right tree rotation.""" - pivot = self.left - if pivot is None: - return - self.val, pivot.val = pivot.val, self.val - self.left = pivot.left - if self.left is not None: - self.left.parent = self - pivot.left = pivot.right - if pivot.left is not None: - pivot.left.parent = pivot - pivot.right = self.right - if pivot.right is not None: - pivot.right.parent = pivot - self.right, pivot.parent = pivot, self - - def rotate_left(self): - """Perform a single left tree rotation.""" - pivot = self.right - if pivot is None: - return - self.val, pivot.val = pivot.val, self.val - self.right = pivot.right - if self.right is not None: - self.right.parent = self - pivot.right = pivot.left - if pivot.right is not None: - pivot.right.parent = pivot - pivot.left = self.left - if pivot.left is not None: - pivot.left.parent = pivot - self.left, pivot.parent = pivot, self - - def self_balance(self): - """Balance the subtree from given node.""" - balance = self.balance() - # Tree is left heavy - if balance == 2: - if self.left.balance() <= -1: - # Double Right - self.left.rotate_left() - # Single Right - self.rotate_right() - if self.parent is not None: - self.parent.self_balance() - - # Tree is right heavy - elif balance == -2: - if self.right.balance() >= 1: - # Double Left - self.right.rotate_right() - # Single Left - self.rotate_left() - if self.parent is not None: - self.parent.self_balance() - else: - if self.parent is not None: - self.parent.self_balance() - if __name__ == '__main__': from timeit import Timer diff --git a/test_bst.py b/test_bst.py index e156881..386814d 100644 --- a/test_bst.py +++ b/test_bst.py @@ -307,7 +307,7 @@ def test_create_worst_case(): def test_lookup(traversal_setup): root = traversal_setup expected = root.left - actual = root._lookup('B') + actual = root.lookup('B') assert expected is actual @@ -384,7 +384,7 @@ def test_rotate_right(pivot_setup): root_val, pivot_val = root.val, pivot.val root_right = root.right pivot_right, pivot_left = pivot.right, pivot.left - root.rotate_right() + root._rotate_right() assert root.val == pivot_val and pivot.val == root_val assert root_right is pivot.right and pivot_right is pivot.left assert pivot_left is root.left @@ -395,9 +395,9 @@ def test_rotate_left(pivot_setup): root_val, pivot_val = root.val, pivot.val root_right = root.right pivot_right, pivot_left = pivot.right, pivot.left - root.rotate_right() - root.rotate_left() - root.rotate_right() + root._rotate_right() + root._rotate_left() + root._rotate_right() assert root.val == pivot_val and pivot.val == root_val assert root_right is pivot.right and pivot_right is pivot.left assert pivot_left is root.left @@ -408,7 +408,7 @@ def test_self_balance_single_right(): root.left = Node(5, root) root.left.left = Node(1, root.left) leaf = root.left.left - leaf.self_balance() + leaf._self_balance() assert root.val == 5 assert root.left.val == 1 and root.right.val == 10 @@ -418,7 +418,7 @@ def test_self_balance_double_right(): root.left = Node(5, root) root.left.right = Node(7, root.left) leaf = root.left.right - leaf.self_balance() + leaf._self_balance() assert root.val == 7 assert root.left.val == 5 and root.right.val == 10 @@ -428,7 +428,7 @@ def test_self_balance_single_left(): root.right = Node(15, root) root.right.right = Node(20, root.right) leaf = root.right.right - leaf.self_balance() + leaf._self_balance() assert root.val == 15 assert root.left.val == 10 and root.right.val == 20 @@ -438,7 +438,7 @@ def test_self_balance_double_left(): root.right = Node(15, root) root.right.left = Node(13, root.right) leaf = root.right.left - leaf.self_balance() + leaf._self_balance() assert root.val == 13 assert root.left.val == 10 and root.right.val == 15 From 9912eff7062224500d6f4c3c7a7ac4c8d05e3880 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 02:50:06 -0700 Subject: [PATCH 281/330] Add addition and subtraction from iterables for fun --- bst.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/bst.py b/bst.py index 17f2937..877cd6a 100644 --- a/bst.py +++ b/bst.py @@ -21,7 +21,7 @@ delete methods will automatically save a render to disk upon execution. Finally, the helper methods 'create_best_case' and 'create_worst_case' -facilitates creation of tree composeds of _n_ integers. +facilitate creation of tree composeds of _n_ integers. This module was completed with reference to the following: @@ -64,6 +64,14 @@ def __len__(self): def __iter__(self): return self.in_order() + def __add__(self, other): + for item in other: + self.insert(item) + + def __sub__(self, other): + for item in other: + self.delete(item) + def insert(self, val, balanced=True, render=False): """Insert a node with a value into the tree. From 8c6048074894d5a8120a0ce7d9d06daa64eccae5 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 14:02:21 -0700 Subject: [PATCH 282/330] Add hash.py --- hash.py | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 hash.py diff --git a/hash.py b/hash.py new file mode 100644 index 0000000..73d342b --- /dev/null +++ b/hash.py @@ -0,0 +1,3 @@ +from __future__ import unicode_literals + + From ce692cfcc26d14b49fb57a40525c6d507f64fd7b Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 14:06:31 -0700 Subject: [PATCH 283/330] Add test_hash.py --- test_hash.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test_hash.py diff --git a/test_hash.py b/test_hash.py new file mode 100644 index 0000000..e69de29 From f8f1479d8d46211fc2c3882a2e1af491e1817f12 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 14:23:07 -0700 Subject: [PATCH 284/330] Sketch out first methods --- hash.py | 41 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/hash.py b/hash.py index 73d342b..2a4d053 100644 --- a/hash.py +++ b/hash.py @@ -1,3 +1,44 @@ from __future__ import unicode_literals +class Item(object): + """docstring for Item""" + def __init__(self, key, value): + self.key = key + self.value = value + + def __repr__(self): + return "".format(self.key, self.value) + + +class HashTable(object): + """docstring for HashTable""" + table_size = 0 + entries_count = 0 + alphabet_size = 52 + # hash_table = [] + + def __init__(self, size): + self.size = size + self.hashtable = [[] for i in range(size)] + + def char2int(self, char): + """Convert a alpha character to an int.""" + # offset for ASCII table + if char >= 'A' and char <= 'Z': + return ord(char) - 65 + elif char >= 'a' and char <= 'z': + return ord(char) - 65 - 7 + else: + raise NameError('Invalid character in key! Use alpha character.') + + def hasing(self, key): + """pass""" + hash = 0 + for i, c in enumerate(key): + hash += pow( + self.alphabet_size, len(key) - i - 1) * self.char2int(c) + return hash % self.table_size + + + From fb5d56b26f752eb05ca707f1adfce031d83febf9 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 14:23:33 -0700 Subject: [PATCH 285/330] Fix typo --- hash.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hash.py b/hash.py index 2a4d053..1bda832 100644 --- a/hash.py +++ b/hash.py @@ -32,7 +32,7 @@ def char2int(self, char): else: raise NameError('Invalid character in key! Use alpha character.') - def hasing(self, key): + def hashing(self, key): """pass""" hash = 0 for i, c in enumerate(key): @@ -40,5 +40,5 @@ def hasing(self, key): self.alphabet_size, len(key) - i - 1) * self.char2int(c) return hash % self.table_size - + From 07f5c321c4b04b4a4baab0759838e1b2717578fa Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 14:29:32 -0700 Subject: [PATCH 286/330] Add insert method --- hash.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/hash.py b/hash.py index 1bda832..d83e015 100644 --- a/hash.py +++ b/hash.py @@ -34,11 +34,19 @@ def char2int(self, char): def hashing(self, key): """pass""" - hash = 0 + hash_ = 0 for i, c in enumerate(key): - hash += pow( + hash_ += pow( self.alphabet_size, len(key) - i - 1) * self.char2int(c) - return hash % self.table_size + return hash_ % self.table_size + + def insert(self, item): + hash_ = self.hashing(item.key) + for i, it in enumerate(self.hashtable[hash_]): + if it.key == item.key: + del self.hashtable[hash_][i] + self.entries_count -= 1 + self.hashtable[hash_].append(item) + self.entries_count += 1 - From 5f4d91c6795b5146591c3069233e58efd0890bf1 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 14:38:51 -0700 Subject: [PATCH 287/330] First pass spec methods --- hash.py | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/hash.py b/hash.py index d83e015..1a48021 100644 --- a/hash.py +++ b/hash.py @@ -19,7 +19,7 @@ class HashTable(object): # hash_table = [] def __init__(self, size): - self.size = size + self.table_size = size self.hashtable = [[] for i in range(size)] def char2int(self, char): @@ -49,4 +49,10 @@ def insert(self, item): self.hashtable[hash_].append(item) self.entries_count += 1 - + def get(self, key): + hash_ = self.hashing(key) + for i, it in enumerate(self.hashtable[hash_]): + if it.key == key: + return self.hashtable[hash_] + return None + From 0bd8dd32474446ae5c497e8ac3a1305be6b17d8a Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 14:51:22 -0700 Subject: [PATCH 288/330] Refactor to remove item class --- hash.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/hash.py b/hash.py index 1a48021..78593f0 100644 --- a/hash.py +++ b/hash.py @@ -1,16 +1,6 @@ from __future__ import unicode_literals -class Item(object): - """docstring for Item""" - def __init__(self, key, value): - self.key = key - self.value = value - - def __repr__(self): - return "".format(self.key, self.value) - - class HashTable(object): """docstring for HashTable""" table_size = 0 @@ -40,13 +30,13 @@ def hashing(self, key): self.alphabet_size, len(key) - i - 1) * self.char2int(c) return hash_ % self.table_size - def insert(self, item): - hash_ = self.hashing(item.key) + def set(self, key, value): + hash_ = self.hashing(key) for i, it in enumerate(self.hashtable[hash_]): - if it.key == item.key: + if it.key == key: del self.hashtable[hash_][i] self.entries_count -= 1 - self.hashtable[hash_].append(item) + self.hashtable[hash_].append((key, value)) self.entries_count += 1 def get(self, key): From 67f9e0f5b48a6ce3ae00d1af88d5c4142734c303 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 14:59:55 -0700 Subject: [PATCH 289/330] Fix change to tuple storage in hashtable --- hash.py | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/hash.py b/hash.py index 78593f0..c4ea40d 100644 --- a/hash.py +++ b/hash.py @@ -6,12 +6,14 @@ class HashTable(object): table_size = 0 entries_count = 0 alphabet_size = 52 - # hash_table = [] - def __init__(self, size): + def __init__(self, size=16): self.table_size = size self.hashtable = [[] for i in range(size)] + def __repr__(self): + return "".format(self.hashtable) + def char2int(self, char): """Convert a alpha character to an int.""" # offset for ASCII table @@ -30,10 +32,10 @@ def hashing(self, key): self.alphabet_size, len(key) - i - 1) * self.char2int(c) return hash_ % self.table_size - def set(self, key, value): + def set(self, key, value): # Validate for string only hash_ = self.hashing(key) - for i, it in enumerate(self.hashtable[hash_]): - if it.key == key: + for i, item in enumerate(self.hashtable[hash_]): + if item[0] == key: del self.hashtable[hash_][i] self.entries_count -= 1 self.hashtable[hash_].append((key, value)) @@ -41,8 +43,8 @@ def set(self, key, value): def get(self, key): hash_ = self.hashing(key) - for i, it in enumerate(self.hashtable[hash_]): - if it.key == key: + for i, item in enumerate(self.hashtable[hash_]): + if item[0] == key: return self.hashtable[hash_] return None From 8168189224c816e56ec0bad0b6e25b24585f2db7 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 15:04:54 -0700 Subject: [PATCH 290/330] Add string validation to insert --- hash.py | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/hash.py b/hash.py index c4ea40d..6c6072b 100644 --- a/hash.py +++ b/hash.py @@ -32,7 +32,9 @@ def hashing(self, key): self.alphabet_size, len(key) - i - 1) * self.char2int(c) return hash_ % self.table_size - def set(self, key, value): # Validate for string only + def set(self, key, value): + if not isinstance(key, str): + raise TypeError('Only strings may be used as keys.') hash_ = self.hashing(key) for i, item in enumerate(self.hashtable[hash_]): if item[0] == key: @@ -46,5 +48,5 @@ def get(self, key): for i, item in enumerate(self.hashtable[hash_]): if item[0] == key: return self.hashtable[hash_] - return None + raise KeyError('Key not in hash table.') From 43c0c58424fb9d7c7ba4517d96666a600f04d357 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 15:06:28 -0700 Subject: [PATCH 291/330] Add if name is main block --- hash.py | 2 ++ 1 file changed, 2 insertions(+) diff --git a/hash.py b/hash.py index 6c6072b..7e69541 100644 --- a/hash.py +++ b/hash.py @@ -50,3 +50,5 @@ def get(self, key): return self.hashtable[hash_] raise KeyError('Key not in hash table.') +if __name__ == '__main__': + pass From 7e53a3a465ac6d81c1afe8831ebba3bade227c14 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 15:30:40 -0700 Subject: [PATCH 292/330] Add len method; set default table size --- hash.py | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/hash.py b/hash.py index 7e69541..4e56855 100644 --- a/hash.py +++ b/hash.py @@ -1,4 +1,3 @@ -from __future__ import unicode_literals class HashTable(object): @@ -7,22 +6,28 @@ class HashTable(object): entries_count = 0 alphabet_size = 52 - def __init__(self, size=16): + def __init__(self, size=1024): self.table_size = size self.hashtable = [[] for i in range(size)] def __repr__(self): return "".format(self.hashtable) + def __len__(self): + count = 0 + for item in self.hashtable: + if len(item) != 0: + count += 1 + return count + def char2int(self, char): """Convert a alpha character to an int.""" # offset for ASCII table - if char >= 'A' and char <= 'Z': - return ord(char) - 65 - elif char >= 'a' and char <= 'z': - return ord(char) - 65 - 7 - else: - raise NameError('Invalid character in key! Use alpha character.') + # if char >= 'A' and char <= 'Z': + # return ord(char) - 65 + # elif char >= 'a' and char <= 'z': + # return ord(char) - 65 - 7 + return ord(char) def hashing(self, key): """pass""" From 2bf756404700f4c38e2f3895dfa8aba2d8dc13be Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Thu, 30 Jul 2015 15:31:40 -0700 Subject: [PATCH 293/330] Refactor and remove char2int --- hash.py | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/hash.py b/hash.py index 4e56855..ad8a01f 100644 --- a/hash.py +++ b/hash.py @@ -20,21 +20,12 @@ def __len__(self): count += 1 return count - def char2int(self, char): - """Convert a alpha character to an int.""" - # offset for ASCII table - # if char >= 'A' and char <= 'Z': - # return ord(char) - 65 - # elif char >= 'a' and char <= 'z': - # return ord(char) - 65 - 7 - return ord(char) - def hashing(self, key): """pass""" hash_ = 0 for i, c in enumerate(key): hash_ += pow( - self.alphabet_size, len(key) - i - 1) * self.char2int(c) + self.alphabet_size, len(key) - i - 1) * ord(c) return hash_ % self.table_size def set(self, key, value): From d023f309724d673127880d58096dc3acaa884c22 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 31 Jul 2015 13:38:24 -0700 Subject: [PATCH 294/330] Rename hash.py due to reserved name --- hash.py => hashtable.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename hash.py => hashtable.py (100%) diff --git a/hash.py b/hashtable.py similarity index 100% rename from hash.py rename to hashtable.py From c85af6bea5356d737f366b1af03ff394ce4991dc Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 31 Jul 2015 15:27:52 -0700 Subject: [PATCH 295/330] Save work --- test_hash.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test_hash.py b/test_hash.py index e69de29..54d806a 100644 --- a/test_hash.py +++ b/test_hash.py @@ -0,0 +1,4 @@ +import pytest + +from hashtable import HashTable + From 7241ae6632d078882ee92333d57621845ab627a0 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 31 Jul 2015 15:42:51 -0700 Subject: [PATCH 296/330] Fix error in get method --- hashtable.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/hashtable.py b/hashtable.py index ad8a01f..fd212c4 100644 --- a/hashtable.py +++ b/hashtable.py @@ -43,7 +43,7 @@ def get(self, key): hash_ = self.hashing(key) for i, item in enumerate(self.hashtable[hash_]): if item[0] == key: - return self.hashtable[hash_] + return item[1] raise KeyError('Key not in hash table.') if __name__ == '__main__': From cbf584f105e9181027e3a29104b827ccdf966fb0 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 31 Jul 2015 16:09:14 -0700 Subject: [PATCH 297/330] fix len method --- hashtable.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/hashtable.py b/hashtable.py index fd212c4..bb6a7c8 100644 --- a/hashtable.py +++ b/hashtable.py @@ -2,7 +2,6 @@ class HashTable(object): """docstring for HashTable""" - table_size = 0 entries_count = 0 alphabet_size = 52 @@ -16,8 +15,7 @@ def __repr__(self): def __len__(self): count = 0 for item in self.hashtable: - if len(item) != 0: - count += 1 + count += len(item) return count def hashing(self, key): From cd608599b353946489509784bacff92f7a52e717 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 31 Jul 2015 16:26:00 -0700 Subject: [PATCH 298/330] Add some tests --- test_hash.py | 53 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/test_hash.py b/test_hash.py index 54d806a..656b3dd 100644 --- a/test_hash.py +++ b/test_hash.py @@ -2,3 +2,56 @@ from hashtable import HashTable + +@pytest.fixture() +def word_list(): + with open('/usr/share/dict/words') as fh: + words = fh.read() + words = words.split('\n') + return words + + +def test_init_default(): + foo = HashTable() + assert foo.table_size == 1024 + assert len(foo.hashtable) == 1024 + + +def test_init_set_size(): + foo = HashTable(size=4096) + assert foo.table_size == 4096 + assert len(foo.hashtable) == 4096 + + +def test_len(): + foo = HashTable() + empty_len = len(foo) + stuff = [('a', 'a'), ('b', 'b'), ('c', 'c')] + more = [('d', 'd'), ('e', 'e')] + foo.hashtable[0].extend(stuff) + foo.hashtable[500].extend(more) + filled_len = len(foo) + assert empty_len == 0 + assert filled_len == 5 + + +def test_set_wrong_type(): + foo = HashTable() + with pytest.raises(TypeError): + foo.set(898, [838]) + + +def test_get_missing_key(): + foo = HashTable() + with pytest.raises(KeyError): + foo.get('bar') + + +def test_set_and_get_word_list(word_list): + foo = HashTable() + words = word_list + for word in words: + foo.set(word, word) + for word in words: + value = foo.get(word) + assert word == value From 1353e9cda28c577d83091a6fc3460ffd6c975671 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 31 Jul 2015 16:38:39 -0700 Subject: [PATCH 299/330] Add further tests --- test_hash.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/test_hash.py b/test_hash.py index 656b3dd..0c62a9b 100644 --- a/test_hash.py +++ b/test_hash.py @@ -35,12 +35,32 @@ def test_len(): assert filled_len == 5 +def test_set(): + foo = HashTable(size=1024) + foo.set('foo', 'foo') + foo.set('spoofs', 'spoofs') + foo.set('utopia', 'utopia') + assert foo.hashtable[91][0] == ('foo', 'foo') + assert foo.hashtable[91][1] == ('spoofs', 'spoofs') + assert foo.hashtable[885][0] == ('utopia', 'utopia') + + def test_set_wrong_type(): foo = HashTable() with pytest.raises(TypeError): foo.set(898, [838]) +def test_get(): + foo = HashTable(size=1024) + foo.hashtable[91].append(('foo', 'foo')) + foo.hashtable[91].append(('spoofs', 'spoofs')) + foo.hashtable[885].append(('utopia', 'utopia')) + assert foo.get('foo') == 'foo' + assert foo.get('spoofs') == 'spoofs' + assert foo.get('utopia') == 'utopia' + + def test_get_missing_key(): foo = HashTable() with pytest.raises(KeyError): From 7c1f5300277696d52e4b7e8ea4949ab46558ba4f Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 31 Jul 2015 16:55:47 -0700 Subject: [PATCH 300/330] Change default hash table size to 8192; good lookup perfomance --- hashtable.py | 2 +- test_hash.py | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/hashtable.py b/hashtable.py index bb6a7c8..3e62fcb 100644 --- a/hashtable.py +++ b/hashtable.py @@ -5,7 +5,7 @@ class HashTable(object): entries_count = 0 alphabet_size = 52 - def __init__(self, size=1024): + def __init__(self, size=8192): self.table_size = size self.hashtable = [[] for i in range(size)] diff --git a/test_hash.py b/test_hash.py index 0c62a9b..5b000f8 100644 --- a/test_hash.py +++ b/test_hash.py @@ -13,8 +13,8 @@ def word_list(): def test_init_default(): foo = HashTable() - assert foo.table_size == 1024 - assert len(foo.hashtable) == 1024 + assert foo.table_size == 8192 + assert len(foo.hashtable) == 8192 def test_init_set_size(): @@ -24,7 +24,7 @@ def test_init_set_size(): def test_len(): - foo = HashTable() + foo = HashTable(size=1024) empty_len = len(foo) stuff = [('a', 'a'), ('b', 'b'), ('c', 'c')] more = [('d', 'd'), ('e', 'e')] From 48c374b423ca14d942f495166782aa17c98bcaec Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 31 Jul 2015 17:02:40 -0700 Subject: [PATCH 301/330] Improve docstrings --- hashtable.py | 29 ++++++++++++++++++++++++----- 1 file changed, 24 insertions(+), 5 deletions(-) diff --git a/hashtable.py b/hashtable.py index 3e62fcb..a37451f 100644 --- a/hashtable.py +++ b/hashtable.py @@ -1,7 +1,7 @@ class HashTable(object): - """docstring for HashTable""" + """A class for a hash table.""" entries_count = 0 alphabet_size = 52 @@ -18,8 +18,14 @@ def __len__(self): count += len(item) return count - def hashing(self, key): - """pass""" + def _hashing(self, key): + """Create a hash from a given key. + + args: + key: a string to hash + + returns: an integer corresponding to hashtable index + """ hash_ = 0 for i, c in enumerate(key): hash_ += pow( @@ -27,9 +33,15 @@ def hashing(self, key): return hash_ % self.table_size def set(self, key, value): + """Add a key and value into the hash table. + + args: + key: the key to store + value: the value to store + """ if not isinstance(key, str): raise TypeError('Only strings may be used as keys.') - hash_ = self.hashing(key) + hash_ = self._hashing(key) for i, item in enumerate(self.hashtable[hash_]): if item[0] == key: del self.hashtable[hash_][i] @@ -38,7 +50,14 @@ def set(self, key, value): self.entries_count += 1 def get(self, key): - hash_ = self.hashing(key) + """Retrieve a value from the hash table by key. + + args: + key: a string to find the value in the hash table + + returns: the value stored with the key + """ + hash_ = self._hashing(key) for i, item in enumerate(self.hashtable[hash_]): if item[0] == key: return item[1] From a7f0aaa463b6da94bf29cfaeb2b51d4dc1e3c8ad Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Fri, 31 Jul 2015 18:18:08 -0700 Subject: [PATCH 302/330] Update readme; add module docstring --- README.md | 8 ++++++++ hashtable.py | 8 +++++--- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 35f3b08..3cd0116 100644 --- a/README.md +++ b/README.md @@ -146,5 +146,13 @@ Available methods include: See the doc strings for additional implementation details. +##Hash Table +Contains a HashTable class. The [hash table](https://en.wikipedia.org/wiki/Hash_table) +is implemented on a list of lists, with a default table size of 8192. This table +size can be overridden on initialization by passing a size keyword argument. Insertion +and lookup time complexity ranges from O(1) at best to O(n) at worst. +Available methods include: +* set(key, value) +* get(key) diff --git a/hashtable.py b/hashtable.py index a37451f..c5b9f10 100644 --- a/hashtable.py +++ b/hashtable.py @@ -1,3 +1,8 @@ +"""Contains a HashTable class. The hash table is implemented on a list of +lists, with a default table size of 8192. This table size can be overridden on +initialization by passing a size keyword argument. Insertion and lookup time +complexity ranges from O(1) at best to O(n) at worst. +""" class HashTable(object): @@ -62,6 +67,3 @@ def get(self, key): if item[0] == key: return item[1] raise KeyError('Key not in hash table.') - -if __name__ == '__main__': - pass From 06fc94beb1f0e557be11010eaaf9635350940865 Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Mon, 3 Aug 2015 13:13:47 -0700 Subject: [PATCH 303/330] Add insertion sort method to module. --- insertion_sort.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 insertion_sort.py diff --git a/insertion_sort.py b/insertion_sort.py new file mode 100644 index 0000000..0267092 --- /dev/null +++ b/insertion_sort.py @@ -0,0 +1,13 @@ +def insertion_sort(un_list): + for idx in range(1, len(un_list)): + current = un_list[idx] + position = idx + + while position > 0 and un_list[position-1] > current: + un_list[position] = un_list[position-1] + position = position - 1 + + un_list[position] = current + +if __name__ == '__main__': + pass From 9cfc5c5acf568b56f4f150e3040827e5856b52c2 Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Mon, 3 Aug 2015 13:26:37 -0700 Subject: [PATCH 304/330] Update module with timeit testing for best and worst case scenarios. --- insertion_sort.py | 18 +++++++++++++++++- 1 file changed, 17 insertions(+), 1 deletion(-) diff --git a/insertion_sort.py b/insertion_sort.py index 0267092..510fa7b 100644 --- a/insertion_sort.py +++ b/insertion_sort.py @@ -10,4 +10,20 @@ def insertion_sort(un_list): un_list[position] = current if __name__ == '__main__': - pass + BEST_CASE = range(1000) + WORST_CASE = BEST_CASE[::-1] + + from timeit import Timer + + best = Timer( + 'insertion_sort({})'.format(BEST_CASE), + 'from __main__ import BEST_CASE, insertion_sort').timeit(1000) + + worst = Timer( + 'insertion_sort({})'.format(WORST_CASE), + 'from __main__ import WORST_CASE, insertion_sort').timeit(1000) + + print("""Best case represented as a list that is already sorted\n + Worst case represented as a list that is absolute reverse of sorted""") + print('Best Case: {}'.format(best)) + print('Worst Case: {}'.format(worst)) From e0ebd4cb41d3ed9168e819f7017dd98c2fbb599a Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Mon, 3 Aug 2015 13:38:15 -0700 Subject: [PATCH 305/330] Update insertion sort with list validation --- insertion_sort.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/insertion_sort.py b/insertion_sort.py index 510fa7b..117916d 100644 --- a/insertion_sort.py +++ b/insertion_sort.py @@ -1,4 +1,7 @@ def insertion_sort(un_list): + if type(un_list) is not list: + return "You must pass a valid list as argument. Do it." + for idx in range(1, len(un_list)): current = un_list[idx] position = idx From 1f9ba14e1dea8802ca762b1a931e022fc9ee254a Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 3 Aug 2015 13:57:51 -0700 Subject: [PATCH 306/330] Add test_insertion_sort --- test_insertion_sort.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 test_insertion_sort.py diff --git a/test_insertion_sort.py b/test_insertion_sort.py new file mode 100644 index 0000000..b9fe1ec --- /dev/null +++ b/test_insertion_sort.py @@ -0,0 +1,13 @@ +from random import shuffle + +from insertion_sort import insertion_sort as in_sort + + +def test_insertion_sort(): + expected = range(20) + unsorted = expected[:] + shuffle(unsorted) + in_sort(unsorted) + actual = unsorted + assert expected is actual + From 9f4d0e6539c6793540be97fcb35d4ac5f92fe028 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 3 Aug 2015 14:04:28 -0700 Subject: [PATCH 307/330] Add another test --- test_insertion_sort.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/test_insertion_sort.py b/test_insertion_sort.py index b9fe1ec..e9f0efa 100644 --- a/test_insertion_sort.py +++ b/test_insertion_sort.py @@ -1,4 +1,5 @@ from random import shuffle +import pytest from insertion_sort import insertion_sort as in_sort @@ -9,5 +10,10 @@ def test_insertion_sort(): shuffle(unsorted) in_sort(unsorted) actual = unsorted - assert expected is actual + assert expected == actual + + +def test_insertion_sort_wrong_type(): + with pytest.raises(TypeError): + in_sort('some string') From f3cb538c2b908a41ddf8c562401103f6f3496f83 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 3 Aug 2015 14:12:06 -0700 Subject: [PATCH 308/330] Improve docstrings --- insertion_sort.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/insertion_sort.py b/insertion_sort.py index 117916d..2ed34ce 100644 --- a/insertion_sort.py +++ b/insertion_sort.py @@ -1,4 +1,20 @@ +"""This module contains the insertion sort method, which performs an +in-place sort on a passed in list. Insertion sort has a best case timeit +complexity of O(n) when list is nearly sorted, and a worst case of O(n2) +when the incoming list is already reverse sorted. While this not always +the most efficient algorithm, it is still popular when the data is nearly +sorted (because it is adaptive) or when the problem size is small. +See the excellent 'sortingalgorithms.com' for more information. + +""" + + def insertion_sort(un_list): + """Sort a list in place using insertion sort. + + args: + un_list: the list to sort + """ if type(un_list) is not list: return "You must pass a valid list as argument. Do it." From 1a237fd38a910fbd271b35e934be2674aa5302af Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 3 Aug 2015 14:17:33 -0700 Subject: [PATCH 309/330] Resolve name clobbering; add TypeError raise when approriate. --- insertion_sort.py | 4 ++-- test_insertion_sort.py | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/insertion_sort.py b/insertion_sort.py index 2ed34ce..e7cc26b 100644 --- a/insertion_sort.py +++ b/insertion_sort.py @@ -9,14 +9,14 @@ """ -def insertion_sort(un_list): +def in_sort(un_list): """Sort a list in place using insertion sort. args: un_list: the list to sort """ if type(un_list) is not list: - return "You must pass a valid list as argument. Do it." + raise TypeError("You must pass a valid list as argument. Do it.") for idx in range(1, len(un_list)): current = un_list[idx] diff --git a/test_insertion_sort.py b/test_insertion_sort.py index e9f0efa..9e340ec 100644 --- a/test_insertion_sort.py +++ b/test_insertion_sort.py @@ -1,7 +1,7 @@ from random import shuffle import pytest -from insertion_sort import insertion_sort as in_sort +from insertion_sort import in_sort def test_insertion_sort(): From 57a02c9e3bb0ed82ee84a08dbadba0dac4e7f2f4 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Mon, 3 Aug 2015 15:06:51 -0700 Subject: [PATCH 310/330] Add insertion sort with duplicate values test --- test_insertion_sort.py | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/test_insertion_sort.py b/test_insertion_sort.py index 9e340ec..077a684 100644 --- a/test_insertion_sort.py +++ b/test_insertion_sort.py @@ -13,6 +13,15 @@ def test_insertion_sort(): assert expected == actual +def test_insertion_sort_with_duplicates(): + expected = [1, 3, 3, 6, 7, 8, 8, 8] + unsorted = expected[:] + shuffle(unsorted) + in_sort(unsorted) + actual = unsorted + assert expected == actual + + def test_insertion_sort_wrong_type(): with pytest.raises(TypeError): in_sort('some string') From 2ea66456b58af225938cb3cda447d0798ec00227 Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Tue, 4 Aug 2015 13:25:22 -0700 Subject: [PATCH 311/330] Add merge_sort to repo. --- merge_sort.py | 60 +++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 merge_sort.py diff --git a/merge_sort.py b/merge_sort.py new file mode 100644 index 0000000..dbc88db --- /dev/null +++ b/merge_sort.py @@ -0,0 +1,60 @@ +""" +Placeholder for Jonathan's ridiculously long docstring +""" + + +def merge_srt(un_list): + if len(un_list) > 1: + mid = len(un_list) // 2 + left_half = un_list[:mid] + right_half = un_list[mid:] + + merge_srt(left_half) + merge_srt(right_half) + + x = y = z = 0 + + while x < len(left_half) and y < len(right_half): + if left_half[x] < right_half[y]: + un_list[z] = left_half[x] + x += 1 + else: + un_list[z] = right_half[y] + y += 1 + z += 1 + + while x < len(left_half): + un_list[z] = left_half[x] + x += 1 + z += 1 + + while y < len(right_half): + un_list[z] = right_half[y] + y += 1 + z += 1 + + +if __name__ == '__main__': + even_half = range(0, 1001, 2) + odd_half = range(1, 1000, 2) + BEST_CASE = range(0, 1001) + WORST_CASE = even_half + odd_half + + from timeit import Timer + + setup = """ + 'merge_srt({})'.format(BEST_CASE), + 'from __main__ import BEST_CASE' + """ + + best = Timer( + ).timeit(1000) + + worst = Timer( + 'insertion_sort({})'.format(WORST_CASE), + 'from __main__ import WORST_CASE, insertion_sort').timeit(1000) + + print("""Best case represented as a list that is already sorted\n + Worst case represented as a list that is absolute reverse of sorted""") + print('Best Case: {}'.format(best)) + print('Worst Case: {}'.format(worst)) From 4732cfb10dc4a7126166b340c3bc2a6023de924e Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 4 Aug 2015 13:32:49 -0700 Subject: [PATCH 312/330] Save work on performance demo --- merge_sort.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/merge_sort.py b/merge_sort.py index dbc88db..887379c 100644 --- a/merge_sort.py +++ b/merge_sort.py @@ -42,17 +42,11 @@ def merge_srt(un_list): from timeit import Timer - setup = """ - 'merge_srt({})'.format(BEST_CASE), - 'from __main__ import BEST_CASE' - """ + SETUP = """from __main__ import BEST_CASE, WORST_CASE, merge_srt""" - best = Timer( - ).timeit(1000) + best = Timer('merge_srt({})'.format(BEST_CASE), SETUP).timeit(1000) - worst = Timer( - 'insertion_sort({})'.format(WORST_CASE), - 'from __main__ import WORST_CASE, insertion_sort').timeit(1000) + worst = Timer('merge_srt({})'.format(WORST_CASE), SETUP).timeit(1000) print("""Best case represented as a list that is already sorted\n Worst case represented as a list that is absolute reverse of sorted""") From 1c69cf60b0169981f0e68c07904d3817f8ee255c Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 4 Aug 2015 14:18:10 -0700 Subject: [PATCH 313/330] Add test_merge_sort --- test_merge_sort.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 test_merge_sort.py diff --git a/test_merge_sort.py b/test_merge_sort.py new file mode 100644 index 0000000..e69de29 From 0f606c8b1e5236f1790e1d46d0985c2951bc559f Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 4 Aug 2015 14:24:42 -0700 Subject: [PATCH 314/330] Raise TypeError on merge_sort if not passed an iterable --- merge_sort.py | 1 + 1 file changed, 1 insertion(+) diff --git a/merge_sort.py b/merge_sort.py index 887379c..bf4340c 100644 --- a/merge_sort.py +++ b/merge_sort.py @@ -4,6 +4,7 @@ def merge_srt(un_list): + iter(un_list) # Check if un_list is iterable; allow error to raise if len(un_list) > 1: mid = len(un_list) // 2 left_half = un_list[:mid] From 7451a0dba263e58b7cb88ca40eb7e291bb1a84db Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 4 Aug 2015 14:24:57 -0700 Subject: [PATCH 315/330] Complete simple tests --- test_merge_sort.py | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/test_merge_sort.py b/test_merge_sort.py index e69de29..0d40f80 100644 --- a/test_merge_sort.py +++ b/test_merge_sort.py @@ -0,0 +1,28 @@ +from random import shuffle +import pytest + +from merge_sort import merge_srt + + +def test_merge_sort(): + expected = range(20) + unsorted = expected[:] + shuffle(unsorted) + merge_srt(unsorted) + actual = unsorted + assert expected == actual + + +def test_insertion_sort_with_duplicates(): + expected = [1, 3, 3, 6, 7, 8, 8, 8] + unsorted = expected[:] + shuffle(unsorted) + merge_srt(unsorted) + actual = unsorted + assert expected == actual + + +def test_merge_sort_wrong_type(): + with pytest.raises(TypeError): + merge_srt(15) + From 8219fe04478756308b2e90d27f283d56dbbe0093 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 4 Aug 2015 14:37:37 -0700 Subject: [PATCH 316/330] Improve docstrings. --- merge_sort.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/merge_sort.py b/merge_sort.py index bf4340c..3fd02a8 100644 --- a/merge_sort.py +++ b/merge_sort.py @@ -1,9 +1,18 @@ -""" -Placeholder for Jonathan's ridiculously long docstring +"""This module contains the merge_srt method, which performs an +in-place merge sort on a passed in list. Merge sort has a best case time +complexity of O(n log n) when list is nearly sorted, and also a worst case of +O(n log n). Merge sort is a very predictable and stable sort, but it is not +adaptive. See the excellent 'sortingalgorithms.com' for more information. + """ def merge_srt(un_list): + """Perform an in-place merge sort on iterable. + + args: + un_list: the iterable to sort + """ iter(un_list) # Check if un_list is iterable; allow error to raise if len(un_list) > 1: mid = len(un_list) // 2 From 49317a6a25dfa8fe7d5e212319c1b44cc7443a7f Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 4 Aug 2015 14:39:37 -0700 Subject: [PATCH 317/330] Update readme --- README.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/README.md b/README.md index 3cd0116..d6a2c6c 100644 --- a/README.md +++ b/README.md @@ -156,3 +156,19 @@ Available methods include: * set(key, value) * get(key) + +##Insertion Sort +This module contains the in_sort method, which performs an +in-place insertion sort on a passed in list. Insertion sort has a best case time +complexity of O(n) when list is nearly sorted, and a worst case of O(n2) +when the incoming list is already reverse sorted. While this not always +the most efficient algorithm, it is still popular when the data is nearly +sorted (because it is adaptive) or when the problem size is small. +See the excellent 'sortingalgorithms.com' for more information. + +## Merge Sort +This module contains the merge_srt method, which performs an +in-place merge sort on a passed in list. Merge sort has a best case time +complexity of O(n log n) when list is nearly sorted, and also a worst case of +O(n log n). Merge sort is a very predictable and stable sort, but it is not +adaptive. See the excellent 'sortingalgorithms.com' for more information. From 3ab8d10794c7416fda0a5d4e91b52be7b2350a95 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 4 Aug 2015 14:55:17 -0700 Subject: [PATCH 318/330] Require merge_srt to take a list --- merge_sort.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/merge_sort.py b/merge_sort.py index 3fd02a8..88a0eed 100644 --- a/merge_sort.py +++ b/merge_sort.py @@ -8,12 +8,13 @@ def merge_srt(un_list): - """Perform an in-place merge sort on iterable. + """Perform an in-place merge sort on list. args: - un_list: the iterable to sort + un_list: the list to sort """ - iter(un_list) # Check if un_list is iterable; allow error to raise + if type(un_list) is not list: + raise TypeError("You must pass a valid list as argument. Do it.") if len(un_list) > 1: mid = len(un_list) // 2 left_half = un_list[:mid] From 62b3b3121834a7edb50251bdf49b6ce5867bf821 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Tue, 4 Aug 2015 16:03:32 -0700 Subject: [PATCH 319/330] Remove list-type validation from merge sort; allow natural errors to raise --- merge_sort.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/merge_sort.py b/merge_sort.py index 88a0eed..7d9f977 100644 --- a/merge_sort.py +++ b/merge_sort.py @@ -13,8 +13,6 @@ def merge_srt(un_list): args: un_list: the list to sort """ - if type(un_list) is not list: - raise TypeError("You must pass a valid list as argument. Do it.") if len(un_list) > 1: mid = len(un_list) // 2 left_half = un_list[:mid] From 80c25844d18c2b9cc06b073900cff310eadd3d72 Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Wed, 5 Aug 2015 13:08:21 -0700 Subject: [PATCH 320/330] Add quicksort to repo. --- quick_sort.py | 41 +++++++++++++++++++++++++++++++++++++++++ test_quick_sort.py | 0 2 files changed, 41 insertions(+) create mode 100644 quick_sort.py create mode 100644 test_quick_sort.py diff --git a/quick_sort.py b/quick_sort.py new file mode 100644 index 0000000..60e2ec6 --- /dev/null +++ b/quick_sort.py @@ -0,0 +1,41 @@ +"""Doc string to end all doc strings""" + + +def quick_srt(un_list): + _helper(un_list, 0, len(un_list)-1) + + +def _helper(un_list, first, last): + if first < last: + split = _split(un_list, first, last) + _helper(un_list, first, split-1) + _helper(un_list, split+1, last) + + +def _split(un_list, first, last): + pivot = un_list[first] + left = first + 1 + right = last + + while True: + while left <= right and un_list[left] <= pivot: + left += 1 + while right >= left and un_list[right] >= pivot: + right += 1 + + if right < left: + break + else: + temp = un_list[left] + un_list[left] = un_list[right] + un_list[right] = temp + + temp = un_list[first] + un_list[first] = un_list[right] + un_list[right] = temp + + return right + + +if __name__ == '__main__': + pass diff --git a/test_quick_sort.py b/test_quick_sort.py new file mode 100644 index 0000000..e69de29 From d34b0b142d9d144f92107a449edf67a71b58dc5b Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 5 Aug 2015 13:20:54 -0700 Subject: [PATCH 321/330] Refactor tests --- test_merge_sort.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/test_merge_sort.py b/test_merge_sort.py index 0d40f80..bbd364c 100644 --- a/test_merge_sort.py +++ b/test_merge_sort.py @@ -6,19 +6,17 @@ def test_merge_sort(): expected = range(20) - unsorted = expected[:] - shuffle(unsorted) - merge_srt(unsorted) - actual = unsorted + actual = expected[:] + shuffle(actual) + merge_srt(actual) assert expected == actual -def test_insertion_sort_with_duplicates(): +def test_merge_sort_with_duplicates(): expected = [1, 3, 3, 6, 7, 8, 8, 8] - unsorted = expected[:] - shuffle(unsorted) - merge_srt(unsorted) - actual = unsorted + actual = expected[:] + shuffle(actual) + merge_srt(actual) assert expected == actual From adc3fa70c32bce764a6b6a7efd7a39c349d3a685 Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Wed, 5 Aug 2015 13:34:16 -0700 Subject: [PATCH 322/330] Update index error in _sort method --- quick_sort.py | 25 ++++++++++++++++++++++--- 1 file changed, 22 insertions(+), 3 deletions(-) diff --git a/quick_sort.py b/quick_sort.py index 60e2ec6..cda6da7 100644 --- a/quick_sort.py +++ b/quick_sort.py @@ -20,8 +20,8 @@ def _split(un_list, first, last): while True: while left <= right and un_list[left] <= pivot: left += 1 - while right >= left and un_list[right] >= pivot: - right += 1 + while un_list[right] >= pivot and right >= left: + right -= 1 if right < left: break @@ -38,4 +38,23 @@ def _split(un_list, first, last): if __name__ == '__main__': - pass + from random import shuffle + rands = [2 for num in range(0, 1001)] + nums = range(0, 1001) + BEST_CASE = shuffle(nums) + WORST_CASE = nums + + from timeit import Timer + + SETUP = """from __main__ import BEST_CASE, WORST_CASE, quick_srt""" + + best = Timer('quick_srt({})'.format(BEST_CASE), SETUP).timeit(100) + + worst = Timer('quick_srt({})'.format(WORST_CASE), SETUP).timeit(100) + + print(""" + Best case represented as a list that is + Worst case represented as a list that is + """) + print('Best Case: {}'.format(best)) + print('Worst Case: {}'.format(worst)) From 0af1b0bc4448a289c0e21b32face5545ce5d13c7 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 5 Aug 2015 13:34:33 -0700 Subject: [PATCH 323/330] Add basic tests for quick sort --- test_quick_sort.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/test_quick_sort.py b/test_quick_sort.py index e69de29..c690566 100644 --- a/test_quick_sort.py +++ b/test_quick_sort.py @@ -0,0 +1,26 @@ +from random import shuffle +import pytest + +from quick_sort import quick_srt + + +def test_quick_srt(): + expected = range(20) + actual = expected[:] + shuffle(actual) + quick_srt(actual) + assert expected == actual + + +def test_quick_srt_with_duplicates(): + expected = [1, 3, 3, 6, 7, 8, 8, 8] + actual = expected[:] + shuffle(actual) + quick_srt(actual) + assert expected == actual + + +def test_quick_sort_wrong_type(): + with pytest.raises(TypeError): + quick_srt(15) + From 648b8cac6d457590649f8ad7fb35645bed8723cd Mon Sep 17 00:00:00 2001 From: Scott Schmidt Date: Wed, 5 Aug 2015 14:17:12 -0700 Subject: [PATCH 324/330] Refactor to account for larger data sets. --- quick_sort.py | 94 +++++++++++++++++++++++++++++++++------------------ 1 file changed, 61 insertions(+), 33 deletions(-) diff --git a/quick_sort.py b/quick_sort.py index cda6da7..2673451 100644 --- a/quick_sort.py +++ b/quick_sort.py @@ -1,48 +1,76 @@ """Doc string to end all doc strings""" -def quick_srt(un_list): - _helper(un_list, 0, len(un_list)-1) +def quick_srt(un_list, low=0, high=-1): + if high == -1: + high = len(un_list) - 1 + if high > low: + pivot = partition(un_list, low, high) + if pivot > 0: + quick_srt(un_list, low, pivot-1) + if pivot > -1: + quick_srt(un_list, pivot+1, high) -def _helper(un_list, first, last): - if first < last: - split = _split(un_list, first, last) - _helper(un_list, first, split-1) - _helper(un_list, split+1, last) +def partition(un_list, low=0, high=-1): + if high == -1: + high = len(un_list) - 1 + pivot = _choose_pivot(un_list, low, high) + _swap(un_list, pivot, high) + j = low + for i in range(low, high+1): + if un_list[i] < un_list[high]: + _swap(un_list, i, j) + j += 1 -def _split(un_list, first, last): - pivot = un_list[first] - left = first + 1 - right = last + _swap(un_list, high, j) + for i in range(low, high): + if un_list[i] != un_list[i+1]: + return j - while True: - while left <= right and un_list[left] <= pivot: - left += 1 - while un_list[right] >= pivot and right >= left: - right -= 1 + return -1 - if right < left: - break - else: - temp = un_list[left] - un_list[left] = un_list[right] - un_list[right] = temp - temp = un_list[first] - un_list[first] = un_list[right] - un_list[right] = temp +def _swap(un_list, x, y): + temp = un_list[x] + un_list[x] = un_list[y] + un_list[y] = temp - return right + +def _choose_pivot(un_list, low=0, high=-1): + if high == -1: + high = len(un_list) - 1 + mid = low + int(high - low) // 2 + + if un_list[low] == un_list[mid] and un_list[mid] == un_list[high]: + return mid + + if ( + un_list[low] < un_list[mid] and + un_list[low] < un_list[high] and + un_list[mid] < un_list[high] + ): + return mid + + elif ( + un_list[mid] < un_list[low] and + un_list[mid] < un_list[high] and + un_list[low] < un_list[high] + ): + return low + + else: + return high if __name__ == '__main__': - from random import shuffle - rands = [2 for num in range(0, 1001)] - nums = range(0, 1001) - BEST_CASE = shuffle(nums) - WORST_CASE = nums + from random import randint + rands = [randint(1, 10001) for x in range(1, 10001)] + dupes = [1 for x in range(1, 10001)] + nums = range(0, 10001) + BEST_CASE = dupes + WORST_CASE = rands from timeit import Timer @@ -53,8 +81,8 @@ def _split(un_list, first, last): worst = Timer('quick_srt({})'.format(WORST_CASE), SETUP).timeit(100) print(""" - Best case represented as a list that is - Worst case represented as a list that is + Best case represented as a list that being a list of the same values. + Worst case represented as a list that is random numbers. """) print('Best Case: {}'.format(best)) print('Worst Case: {}'.format(worst)) From 009488b197930fc38c8efb9d749789fdb4830f60 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 5 Aug 2015 15:28:53 -0700 Subject: [PATCH 325/330] Improve docstrings --- quick_sort.py | 82 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 65 insertions(+), 17 deletions(-) diff --git a/quick_sort.py b/quick_sort.py index 2673451..123763f 100644 --- a/quick_sort.py +++ b/quick_sort.py @@ -1,19 +1,55 @@ -"""Doc string to end all doc strings""" +"""This module contains the quick sort method (quick_srt), which performs an +in-place sort on a passed in list. Quick sort has a best case time +complexity of O(n log n) when all elements are equal, and a worst case of +O(n2). Quick sort is not stable or adaptive, but it is robust and has low +overhead. +See the excellent 'sortingalgorithms.com' for more information. +""" -def quick_srt(un_list, low=0, high=-1): + +def quick_srt(un_list): + """Sort a list in place with quick sort. + + args: + un_list: the list to be sorted + """ + _quick_srt(un_list, low=0, high=-1) + + +def _quick_srt(un_list, low=0, high=-1): + """Sort a list in place with quick sort. + + args: + un_list: the list to be sorted + low: index lower bound + high: the index upper bound + """ if high == -1: high = len(un_list) - 1 if high > low: - pivot = partition(un_list, low, high) + pivot = _partition(un_list, low, high) if pivot > 0: - quick_srt(un_list, low, pivot-1) + _quick_srt(un_list, low, pivot-1) if pivot > -1: - quick_srt(un_list, pivot+1, high) + _quick_srt(un_list, pivot+1, high) + +def _partition(un_list, low=0, high=-1): + """Partition the list into values less than and greater than pivot. -def partition(un_list, low=0, high=-1): + If the the partitioned sublist contains at least two uniqe values, + the pivot index is returned. Else, a value of -1 is returned, which + will end any further recursive calls to _quick_srt. + + args: + un_list: the list to be sorted + low: index lower bound + high: the index upper bound + + returns: the pivot index or -1 + """ if high == -1: high = len(un_list) - 1 pivot = _choose_pivot(un_list, low, high) @@ -33,31 +69,43 @@ def partition(un_list, low=0, high=-1): def _swap(un_list, x, y): + """Swap the values at two index positions in list. + + args: + un_list: the list to act upon + x: index to first element + y: index to second element + """ temp = un_list[x] un_list[x] = un_list[y] un_list[y] = temp def _choose_pivot(un_list, low=0, high=-1): + """Choose a pivot for quick sort. + + args: + un_list: the list to be sorted + low: index lower bound + high: the index upper bound + + returns: the pivot index + """ if high == -1: high = len(un_list) - 1 - mid = low + int(high - low) // 2 + mid = low + (high - low) // 2 if un_list[low] == un_list[mid] and un_list[mid] == un_list[high]: return mid - if ( - un_list[low] < un_list[mid] and - un_list[low] < un_list[high] and - un_list[mid] < un_list[high] - ): + if (un_list[low] < un_list[mid] + and un_list[low] < un_list[high] + and un_list[mid] < un_list[high]): return mid - elif ( - un_list[mid] < un_list[low] and - un_list[mid] < un_list[high] and - un_list[low] < un_list[high] - ): + elif (un_list[mid] < un_list[low] + and un_list[mid] < un_list[high] + and un_list[low] < un_list[high]): return low else: From e44a589d264038ad4b436ec634352cdda28556f6 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 5 Aug 2015 15:28:58 -0700 Subject: [PATCH 326/330] Update readme --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index d6a2c6c..9faa9ec 100644 --- a/README.md +++ b/README.md @@ -172,3 +172,16 @@ in-place merge sort on a passed in list. Merge sort has a best case time complexity of O(n log n) when list is nearly sorted, and also a worst case of O(n log n). Merge sort is a very predictable and stable sort, but it is not adaptive. See the excellent 'sortingalgorithms.com' for more information. + +##Quick Sort +"""This module contains the quick sort method (quick_srt), which performs an +in-place sort on a passed in list. Quick sort has a best case time +complexity of O(n log n) when all elements are equal, and a worst case of +O(n2). Quick sort is not stable or adaptive, but it is robust and has low +overhead. + +This module was completed with reference to: +[Quicksort: A Python Implementation](http://pfsensesetup.com/pythonscript.net/quicksort-a-python-implementation/) +by maximumdx + +See the excellent 'sortingalgorithms.com' for more information. From d8f45506ae531b4408d922e2efabba39982228c9 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 5 Aug 2015 15:34:06 -0700 Subject: [PATCH 327/330] Add reference attribution to module docstring --- quick_sort.py | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/quick_sort.py b/quick_sort.py index 123763f..fc45373 100644 --- a/quick_sort.py +++ b/quick_sort.py @@ -4,6 +4,12 @@ O(n2). Quick sort is not stable or adaptive, but it is robust and has low overhead. +This module was completed with reference to: + +Quicksort: A Python Implementation +http://pfsensesetup.com/pythonscript.net/quicksort-a-python-implementation/ +by maximumdx + See the excellent 'sortingalgorithms.com' for more information. """ @@ -39,7 +45,7 @@ def _quick_srt(un_list, low=0, high=-1): def _partition(un_list, low=0, high=-1): """Partition the list into values less than and greater than pivot. - If the the partitioned sublist contains at least two uniqe values, + If the the partitioned sublist contains at least two unique values, the pivot index is returned. Else, a value of -1 is returned, which will end any further recursive calls to _quick_srt. From af9c686750d55ff786807f9175faadb8bb9087e7 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 5 Aug 2015 17:18:31 -0700 Subject: [PATCH 328/330] Add a few more tests for edge cases --- test_quick_sort.py | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/test_quick_sort.py b/test_quick_sort.py index c690566..fe70a40 100644 --- a/test_quick_sort.py +++ b/test_quick_sort.py @@ -20,6 +20,20 @@ def test_quick_srt_with_duplicates(): assert expected == actual +def test_quick_srt_with_zero_items(): + expected = [] + actual = [] + quick_srt(actual) + assert expected == actual + + +def test_quick_srt_with_one_item(): + expected = [1] + actual = [1] + quick_srt(actual) + assert expected == actual + + def test_quick_sort_wrong_type(): with pytest.raises(TypeError): quick_srt(15) From 34f8ba65a8e52d52c30b78efd936b066f7d2cec4 Mon Sep 17 00:00:00 2001 From: Jonathan Stallings Date: Wed, 12 Aug 2015 22:53:26 -0700 Subject: [PATCH 329/330] Fix docstring --- bst.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/bst.py b/bst.py index 877cd6a..cf33233 100644 --- a/bst.py +++ b/bst.py @@ -198,13 +198,12 @@ def contains(self, val): return self.right.contains(val) def lookup(self, val): - """Find a node by value and return that node and its parent. + """Find a node by value and return that node. args: val: the value to search by - parent: the parent of the node (for recursion) - returns: a tuple with node and its parent + returns: a node """ if val < self.val: if self.left is None: From 2ee41a5cb812bc23685a90e257ac473a774af69a Mon Sep 17 00:00:00 2001 From: Caiden Pyle <40413115+Caidentp@users.noreply.github.com> Date: Wed, 17 Oct 2018 08:50:47 -0500 Subject: [PATCH 330/330] Simplify bst.py rotation algorithms Simplify the bst left and right rotation methods Remove the if block in the _rotate_right and _rotate_left methods from bst.py that validate the parent node of the pivot, as well as the code that updates the pivot's parent at the end of _rotate_right and _rotate_left. The pivot's parent does not change throughout the rotation of the tree. Get rid of the lines that update the parent of the pivot node because the parent of the pivot stays constant in _rotate_left and _rotate_right. Lines 278, 279, 295, 296 can be deleted without altering the functionality of the rotation algorithms and gets rid of the unneeded code that updates the parent of the pivot node. In addition, lines 283 and 300 also unnecessarily update the parent of the pivot node. This can be removed without changing the functionality of _rotate_left and _rotate_right. The parent of the pivot node does not change in _rotate_right or _rotate_left. Delete code that updates the parent of pivot. --- bst.py | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/bst.py b/bst.py index cf33233..d9837de 100644 --- a/bst.py +++ b/bst.py @@ -275,12 +275,10 @@ def _rotate_right(self): if self.left is not None: self.left.parent = self pivot.left = pivot.right - if pivot.left is not None: - pivot.left.parent = pivot pivot.right = self.right if pivot.right is not None: pivot.right.parent = pivot - self.right, pivot.parent = pivot, self + self.right = pivot def _rotate_left(self): """Perform a single left tree rotation.""" @@ -292,12 +290,10 @@ def _rotate_left(self): if self.right is not None: self.right.parent = self pivot.right = pivot.left - if pivot.right is not None: - pivot.right.parent = pivot pivot.left = self.left if pivot.left is not None: pivot.left.parent = pivot - self.left, pivot.parent = pivot, self + self.left = pivot def _self_balance(self): """Balance the subtree from given node."""