From c4d8526d27421b77d7e2a8e6a414e34bf5c62ddd Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Mon, 27 Dec 2010 16:52:51 -0800 Subject: [PATCH 01/43] use is to compare objects and not == --- algorithms/binary_tree.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/algorithms/binary_tree.py b/algorithms/binary_tree.py index e821ecc..1edab6c 100644 --- a/algorithms/binary_tree.py +++ b/algorithms/binary_tree.py @@ -19,12 +19,12 @@ def insert(self, data): @param data node data object to insert """ if data < self.data: - if self.left == None: + if self.left is None: self.left = Node(data) else: self.left.insert(data) else: - if self.right == None: + if self.right is None: self.right = Node(data) else: self.right.insert(data) @@ -38,11 +38,11 @@ def lookup(self, data, parent=None): @returns node and node's parent if found or None, None """ if data < self.data: - if self.left == None: + if self.left is None: return None, None return self.left.lookup(data, self) elif data > self.data: - if self.right == None: + if self.right is None: return None, None return self.right.lookup(data, self) else: @@ -56,7 +56,7 @@ def delete(self, data): """ # get node containing data node, parent = self.lookup(data) - if node != None: + if node is not None: children_count = node.children_count() if children_count == 0: # if node has no children, just remove it @@ -96,17 +96,17 @@ def compare_trees(self, node): @param node tree to compare @returns True if the tree passed is identical to this tree """ - if node == None: + if node is None: return False if self.data != node.data: return False res = True - if self.left == None: + if self.left is None: if node.left: return False else: res = self.left.compare_trees(node.left) - if self.right == None: + if self.right is None: if node.right: return False else: From 944ef531a71b9874a1e42a3a9f759495f1489020 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Mon, 27 Dec 2010 16:56:40 -0800 Subject: [PATCH 02/43] use is to compare objects and not == --- algorithms/tests/test_binary_tree.py | 2 +- binary_tree_tutorial.txt | 20 ++++++++++---------- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/algorithms/tests/test_binary_tree.py b/algorithms/tests/test_binary_tree.py index c57f4bd..229ea7e 100644 --- a/algorithms/tests/test_binary_tree.py +++ b/algorithms/tests/test_binary_tree.py @@ -26,7 +26,7 @@ def test_binary_tree(self): # test lookup node, parent = root.lookup(9) - self.assertTrue(node == None) + self.assertTrue(node is None) # check if returned node and parent are correct node, parent = root.lookup(11) self.assertTrue(node.data == 11) diff --git a/binary_tree_tutorial.txt b/binary_tree_tutorial.txt index 561daee..5cb7162 100644 --- a/binary_tree_tutorial.txt +++ b/binary_tree_tutorial.txt @@ -77,12 +77,12 @@ class Node: @param data node data object to insert """ if data < self.data: - if self.left == None: + if self.left is None: self.left = Node(data) else: self.left.insert(data) else: - if self.right == None: + if self.right is None: self.right = Node(data) else: self.right.insert(data) @@ -151,11 +151,11 @@ class Node: @returns node and node's parent if found or None, None """ if data < self.data: - if self.left == None: + if self.left is None: return None, None return self.left.lookup(data, self) elif data > self.data: - if self.right == None: + if self.right is None: return None, None return self.right.lookup(data, self) else: @@ -196,7 +196,7 @@ class Node: """ # get node containing data node, parent = self.lookup(data) - if node != None: + if node is not None: children_count = node.children_count() ... [/code] @@ -235,7 +235,7 @@ class Node: @returns number of children: 0, 1, 2 """ - if node == None: + if node is None: return None cnt = 0 if self.left: @@ -347,17 +347,17 @@ class Node: @param node tree's root node to compare to @returns True if the tree passed is identical to this tree """ - if node == None: + if node is None: return False if self.data != node.data: return False res = True - if self.left == None: + if self.left is None: if node.left: return False else: res = self.left.compare_trees(node.left) - if self.right == None: + if self.right is None: if node.right: return False else: @@ -485,7 +485,7 @@ Does lookup() works? [code lang="python"] node, parent = root.lookup(9) # Node 9 doesn't exist - self.assertTrue(node == None) + self.assertTrue(node is None) # check if returned node and parent are correct node, parent = root.lookup(11) self.assertTrue(node.data == 11) From 050302b444a4eda23bea8cd1f594626336be2bac Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Fri, 11 Mar 2011 15:19:35 -0800 Subject: [PATCH 03/43] maze --- algorithms/a_star_path_finding.py | 142 ++++++++++++++++++++++++++++++ algorithms/maze.py | 33 +++++++ 2 files changed, 175 insertions(+) create mode 100644 algorithms/a_star_path_finding.py create mode 100644 algorithms/maze.py diff --git a/algorithms/a_star_path_finding.py b/algorithms/a_star_path_finding.py new file mode 100644 index 0000000..1e93349 --- /dev/null +++ b/algorithms/a_star_path_finding.py @@ -0,0 +1,142 @@ +import heapq + +class Cell(object): + def __init__(self, x, y, reachable): + """ + Initialize new cell + + @param x cell x coordinate + @param y cell y coordinate + @param reachable is cell reachable? not a wall? + """ + self.reachable = reachable + self.x = x + self.y = y + self.parent = None + self.g = 0 + self.h = 0 + self.f = 0 + +class AStar(object): + def __init__(self): + self.op = [] + heapq.heapify(self.op) + self.cl = set() + self.cells = [] + self.gridHeight = 6 + self.gridWidth = 6 + + def init_grid(self): + walls = ((0, 5), (1, 0), (1, 1), (1, 5), (2, 3), + (3, 1), (3, 2), (3, 5), (4, 1), (4, 4), (5, 1)) + for x in range(self.gridWidth): + for y in range(self.gridHeight): + if (x, y) in walls: + reachable = False + else: + reachable = True + self.cells.append(Cell(x, y, reachable)) + self.start = self.get_cell(0, 0) + self.end = self.get_cell(5, 5) + + def get_heuristic(self, cell): + """ + Compute the heuristic value H for a cell: distance between + this cell and the ending cell multiply by 10. + + @param cell + @returns heuristic value H + """ + return 10 * (abs(cell.x - self.end.x) + abs(cell.y - self.end.y)) + + def get_cell(self, x, y): + """ + Returns a cell from the cells list + + @param x cell x coordinate + @param y cell y coordinate + @returns cell + """ + return self.cells[x * self.gridHeight + y] + + def get_adjacent_cells(self, cell): + """ + Returns adjacent cells to a cell. Clockwise starting + from the one on the right. + + @param cell get adjacent cells for this cell + @returns adjacent cells list + """ + cells = [] + if cell.x < self.gridWidth-1: + cells.append(self.get_cell(cell.x+1, cell.y)) + if cell.y > 0: + cells.append(self.get_cell(cell.x, cell.y-1)) + if cell.x > 0: + cells.append(self.get_cell(cell.x-1, cell.y)) + if cell.y < self.gridHeight-1: + cells.append(self.get_cell(cell.x, cell.y+1)) + return cells + + def display_path(self): + cell = self.end + while cell.parent is not self.start: + cell = cell.parent + print 'path: cell: %d,%d' % (cell.x, cell.y) + + def compare(self, cell1, cell2): + """ + Compare 2 cells F values + + @param cell1 1st cell + @param cell2 2nd cell + @returns -1, 0 or 1 if lower, equal or greater + """ + if cell1.f < cell2.f: + return -1 + elif cell1.f > cell2.f: + return 1 + return 0 + + def update_cell(self, adj, cell): + """ + Update adjacent cell + + @param adj adjacent cell to current cell + @param cell current cell being processed + """ + adj.g = cell.g + 10 + adj.h = self.get_heuristic(adj) + adj.parent = cell + adj.f = adj.h + adj.g + + def process(self): + # add starting cell to open heap queue + heapq.heappush(self.op, (self.start.f, self.start)) + while len(self.op): + # pop cell from heap queue + h, cell = heapq.heappop(self.op) + # add cell to closed list so we don't process it twice + self.cl.add(cell) + # if ending cell, display found path + if cell is self.end: + self.display_path() + break + # get adjacent cells for cell + adj_cells = self.get_adjacent_cells(cell) + for c in adj_cells: + if c.reachable and c not in self.cl: + if c in self.op: + # if adj cell in open list, check if current path is + # better than the one previously found for this adj cell. + if c.g > cell.g + 10: + self.update_cell(c, cell) + else: + self.update_cell(c, cell) + # add adj cell to open list + heapq.heappush(self.op, (c.f, c)) + +a = AStar() +a.init_grid() +a.process() + diff --git a/algorithms/maze.py b/algorithms/maze.py new file mode 100644 index 0000000..a24997c --- /dev/null +++ b/algorithms/maze.py @@ -0,0 +1,33 @@ +grid = [[0, 0, 0, 0, 0, 1], + [1, 1, 0, 0, 0, 1], + [0, 0, 0, 1, 0, 0], + [0, 1, 1, 0, 0, 1], + [0, 1, 0, 0, 1, 0], + [0, 1, 0, 0, 0, 2] + ] + +def search(x, y): + if grid[x][y] == 2: + print 'found at %d,%d' % (x, y) + return True + elif grid[x][y] == 1: + print 'wall at %d,%d' % (x, y) + return False + elif grid[x][y] == 3: + print 'visited at %d,%d' % (x, y) + return False + + print 'visiting %d,%d' % (x, y) + + # mark as visited + grid[x][y] = 3 + if ((x < len(grid)-1 and search(x+1, y)) + or (y > 0 and search(x, y-1)) + or (x > 0 and search(x-1, y)) + or (y < len(grid)-1 and search(x, y+1))): + return True + + return False + +search(0, 0) + From 92bb9962617937dc7933530a65ed68f7035c2255 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Fri, 11 Mar 2011 15:20:01 -0800 Subject: [PATCH 04/43] update description --- algorithms/permutations.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/algorithms/permutations.py b/algorithms/permutations.py index 6ac855c..3afd03e 100644 --- a/algorithms/permutations.py +++ b/algorithms/permutations.py @@ -2,12 +2,22 @@ def permutations(l): """ Generator for list permutations - Example: [1,2,3] = [1,2,3], [1,3,2], [2,1,3] ... - @param l list to generate permutations for @result yield each permutation + + Example: + l = [1,2,3] + a = [1] + permutations([2,3]) = [[2,3], [3,2]] + [2,3] + yield [1,2,3] + yield [2,1,3] + yield [2,3,1] + [3,2] + yield [1,3,2] + yield [3,1,2] + yield [3,2,1] """ - print 'permutations: ',l if len(l) <= 1: yield l else: @@ -16,3 +26,6 @@ def permutations(l): for i in range(len(p)+1): yield p[:i] + a + p[i:] +for p in permutations([1,2,3]): + print p + From 67e9cc0c7f42bd9279d2e9c6927a5a4b502c10c1 Mon Sep 17 00:00:00 2001 From: David Watson Date: Mon, 9 Apr 2012 13:58:58 -0300 Subject: [PATCH 05/43] Fixed missing parentheses on tree_data method call. --- binary_tree_tutorial.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/binary_tree_tutorial.txt b/binary_tree_tutorial.txt index 5cb7162..33c6d13 100644 --- a/binary_tree_tutorial.txt +++ b/binary_tree_tutorial.txt @@ -417,7 +417,7 @@ class Node: For example, we want to access the tree nodes using a for loop: [code lang="python"] -for data in root.tree_data: +for data in root.tree_data(): print data [/code] From 1b83a77ebdcdcac5e7db0e9df8e2525eb75a0408 Mon Sep 17 00:00:00 2001 From: laurentluce Date: Sat, 7 Jul 2012 13:22:09 -0400 Subject: [PATCH 06/43] more details on permutation example. --- algorithms/permutations.py | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/algorithms/permutations.py b/algorithms/permutations.py index 6ac855c..3afd03e 100644 --- a/algorithms/permutations.py +++ b/algorithms/permutations.py @@ -2,12 +2,22 @@ def permutations(l): """ Generator for list permutations - Example: [1,2,3] = [1,2,3], [1,3,2], [2,1,3] ... - @param l list to generate permutations for @result yield each permutation + + Example: + l = [1,2,3] + a = [1] + permutations([2,3]) = [[2,3], [3,2]] + [2,3] + yield [1,2,3] + yield [2,1,3] + yield [2,3,1] + [3,2] + yield [1,3,2] + yield [3,1,2] + yield [3,2,1] """ - print 'permutations: ',l if len(l) <= 1: yield l else: @@ -16,3 +26,6 @@ def permutations(l): for i in range(len(p)+1): yield p[:i] + a + p[i:] +for p in permutations([1,2,3]): + print p + From f08f9796cfdab1bcab5c4dba56d62059c9c1edb7 Mon Sep 17 00:00:00 2001 From: laurentluce Date: Tue, 10 Jul 2012 23:57:07 -0400 Subject: [PATCH 07/43] sync post with code --- algorithms/binary_tree.py | 283 +++++++++++++++++++------------------- binary_tree_tutorial.txt | 102 ++------------ 2 files changed, 152 insertions(+), 233 deletions(-) diff --git a/algorithms/binary_tree.py b/algorithms/binary_tree.py index 1edab6c..59bfd44 100644 --- a/algorithms/binary_tree.py +++ b/algorithms/binary_tree.py @@ -1,156 +1,159 @@ class Node: - """ - Tree node: left and right child + data which can be any object - """ - def __init__(self, data): """ - Node constructor - - @param data node data object - """ - self.left = None - self.right = None - self.data = data - - def insert(self, data): + Tree node: left and right child + data which can be any object """ - Insert new node with data + def __init__(self, data): + """ + Node constructor - @param data node data object to insert - """ - if data < self.data: - if self.left is None: - self.left = Node(data) - else: - self.left.insert(data) - else: - if self.right is None: - self.right = Node(data) - else: - self.right.insert(data) + @param data node data object + """ + self.left = None + self.right = None + self.data = data - def lookup(self, data, parent=None): - """ - Lookup node containing data + def insert(self, data): + """ + Insert new node with data - @param data node data object to look up - @param parent node's parent - @returns node and node's parent if found or None, None - """ - if data < self.data: - if self.left is None: - return None, None - return self.left.lookup(data, self) - elif data > self.data: - if self.right is None: - return None, None - return self.right.lookup(data, self) - else: - return self, parent + @param data node data object to insert + """ + if data < self.data: + if self.left is None: + self.left = Node(data) + else: + self.left.insert(data) + else: + if self.right is None: + self.right = Node(data) + else: + self.right.insert(data) - def delete(self, data): - """ - Delete node containing data + def lookup(self, data, parent=None): + """ + Lookup node containing data - @param data node's content to delete - """ - # get node containing data - node, parent = self.lookup(data) - if node is not None: - children_count = node.children_count() - if children_count == 0: - # if node has no children, just remove it - if parent.left is node: - parent.left = None - else: - parent.right = None - elif children_count == 1: - # if node has 1 child - # replace node by its child - if node.left: - node.data = node.left.data - node.left = None - else: - node.data = node.right.data - node.right = None - else: - # if node has 2 children - # find its successor - parent = node - successor = node.right - while successor.left: - parent = successor - successor = successor.left - # replace node data by its successor data - node.data = successor.data - # fix successor's parent node child - if parent.left == successor: - parent.left = successor.right + @param data node data object to look up + @param parent node's parent + @returns node and node's parent if found or None, None + """ + if data < self.data: + if self.left is None: + return None, None + return self.left.lookup(data, self) + elif data > self.data: + if self.right is None: + return None, None + return self.right.lookup(data, self) else: - parent.right = successor.right + return self, parent - def compare_trees(self, node): - """ - Compare 2 trees + def delete(self, data): + """ + Delete node containing data - @param node tree to compare - @returns True if the tree passed is identical to this tree - """ - if node is None: - return False - if self.data != node.data: - return False - res = True - if self.left is None: - if node.left: - return False - else: - res = self.left.compare_trees(node.left) - if self.right is None: - if node.right: - return False - else: - res = self.right.compare_trees(node.right) - return res - - def print_tree(self): - """ - Print tree content inorder - """ - if self.left: - self.left.print_tree() - print self.data, - if self.right: - self.right.print_tree() + @param data node's content to delete + """ + # get node containing data + node, parent = self.lookup(data) + if node is not None: + children_count = node.children_count() + if children_count == 0: + # if node has no children, just remove it + # check if it is not the root node + if parent: + if parent.left is node: + parent.left = None + else: + parent.right = None + del node + elif children_count == 1: + # if node has 1 child + # replace node by its child + if node.left: + n = node.left + else: + n = node.right + if parent.left is node: + parent.left = n + else: + parent.right = n + del node + else: + # if node has 2 children + # find its successor + parent = node + successor = node.right + while successor.left: + parent = successor + successor = successor.left + # replace node data by its successor data + node.data = successor.data + # fix successor's parent node child + if parent.left == successor: + parent.left = successor.right + else: + parent.right = successor.right - def tree_data(self): - """ - Generator to get the tree nodes data - """ - # we use a stack to traverse the tree in a non-recursive way - stack = [] - node = self - while stack or node: - if node: - stack.append(node) - node = node.left - else: # we are returning so we pop the node and we yield it - node = stack.pop() - yield node.data - node = node.right + def compare_trees(self, node): + """ + Compare 2 trees - def children_count(self): - """ - Return the number of children - - @returns number of children: 0, 1, 2 - """ - cnt = 0 - if self.left: - cnt += 1 - if self.right: - cnt += 1 - return cnt + @param node tree to compare + @returns True if the tree passed is identical to this tree + """ + if node is None: + return False + if self.data != node.data: + return False + res = True + if self.left is None: + if node.left: + return False + else: + res = self.left.compare_trees(node.left) + if self.right is None: + if node.right: + return False + else: + res = self.right.compare_trees(node.right) + return res + + def print_tree(self): + """ + Print tree content inorder + """ + if self.left: + self.left.print_tree() + print self.data, + if self.right: + self.right.print_tree() + def tree_data(self): + """ + Generator to get the tree nodes data + """ + # we use a stack to traverse the tree in a non-recursive way + stack = [] + node = self + while stack or node: + if node: + stack.append(node) + node = node.left + else: # we are returning so we pop the node and we yield it + node = stack.pop() + yield node.data + node = node.right + def children_count(self): + """ + Return the number of children + @returns number of children: 0, 1, 2 + """ + cnt = 0 + if self.left: + cnt += 1 + if self.right: + cnt += 1 + return cnt diff --git a/binary_tree_tutorial.txt b/binary_tree_tutorial.txt index 33c6d13..f62e09f 100644 --- a/binary_tree_tutorial.txt +++ b/binary_tree_tutorial.txt @@ -8,7 +8,6 @@ This article is about a Python library I created to manage binary search trees.
  • Print method
  • Comparing 2 trees
  • Generator returning the tree elements one by one
  • -
  • Unit tests suite
  • You can checkout the library code on GitHub: git clone https://laurentluce@github.com/laurentluce/python-algorithms.git. This folder contains more libraries but we are just going to focus on the Binary Tree one. @@ -219,6 +218,7 @@ Let's tackle the first possibility which is the easiest. We look for the node to parent.left = None else: parent.right = None + del node ... [/code] @@ -262,11 +262,15 @@ Let's look at the second possibility which is the node to be removed has 1 child # if node has 1 child # replace node by its child if node.left: - node.data = node.left.data - node.left = None + n = node.left else: - node.data = node.right.data - node.right = None + n = node.right + if parent: + if parent.left is node: + parent.left = n + else: + parent.right = n + del node ... [/code] @@ -437,92 +441,4 @@ Let's look at what happens in the code with the same example we have been using:
  • ...
  • -

    Unit tests suite

    - -As a good practice, you should always create a test suite for your library or application. I did a lot of unit testing at Tomnica and on Gourmious and it has been a life saver. - -We are going to use Python unittest module. We place our test modules in a folder called 'tests' so Python unit testing knows where to look. - -Each test module filename needs to start with 'test' and the methods in our test class also. - -We create a file named test_binary_tree.py in the folder 'tests' and we add a new class to it derived from unittest.TestCase. - -[code lang="python"] -class NodeTest(unittest.TestCase): - def test_binary_tree(self): - ... -[/code] - -We are going to add our test cases in the method test_binary_tree. - -Let's create 2 identical trees: -[code lang="python"] - data = [10, 5, 15, 4, 7, 13, 17, 11, 14] - # create 2 trees with the same content - root = binary_tree.Node(data[0]) - for i in data[1:]: - root.insert(i) - - root2 = binary_tree.Node(data[0]) - for i in data[1:]: - root2.insert(i) -[/code] - -Does compare_trees() returns True? -[code lang="python"] - self.assertTrue(root.compare_trees(root2)) -[/code] - -Does the generator tree_data() returns the nodes inorder? -[code lang="python"] - t = [] - for d in root.tree_data(): - t.append(d) - self.assertEquals(t, [4, 5, 7, 10, 11, 13, 14, 15, 17]) -[/code] - -Does lookup() works? -[code lang="python"] - node, parent = root.lookup(9) - # Node 9 doesn't exist - self.assertTrue(node is None) - # check if returned node and parent are correct - node, parent = root.lookup(11) - self.assertTrue(node.data == 11) - self.assertTrue(parent.data == 13) -[/code] - -Does deleting a node with no child works? -[code lang="python"] - # delete a leaf node - root.delete(4) - # check the content of the tree inorder - t = [] - for d in root.tree_data(): - t.append(d) - self.assertEquals(t, [5, 7, 10, 11, 13, 14, 15, 17]) -[/code] - -Does deleting a node with 1 child works? -[code lang="python"] - # delete a node with 1 child - root.delete(5) - # check the content of the tree inorder - t = [] - for d in root.tree_data(): - t.append(d) - self.assertEquals(t, [7, 10, 11, 13, 14, 15, 17]) -[/code] - -Does deleting a node with 2 children works? -[code lang="python"] - # delete a node with 2 children - root.delete(13) - # check the content of the tree inorder - t = [] - for d in root.tree_data(): - t.append(d) - self.assertEquals(t, [7, 10, 11, 14, 15, 17]) -[/code] - Here you go, I hope you enjoyed this tutorial. Don't hesitate to add comments if you have any feedback. From 6d2bcde9b1cea72744fb06eb3e7d6712351a9dbb Mon Sep 17 00:00:00 2001 From: laurentluce Date: Wed, 11 Jul 2012 00:31:09 -0400 Subject: [PATCH 08/43] sync post with code --- algorithms/binary_tree.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/algorithms/binary_tree.py b/algorithms/binary_tree.py index 59bfd44..792767c 100644 --- a/algorithms/binary_tree.py +++ b/algorithms/binary_tree.py @@ -61,11 +61,10 @@ def delete(self, data): if children_count == 0: # if node has no children, just remove it # check if it is not the root node - if parent: - if parent.left is node: - parent.left = None - else: - parent.right = None + if parent.left is node: + parent.left = None + else: + parent.right = None del node elif children_count == 1: # if node has 1 child From 2939c6a1c9a4e31787210784cb8eab9be998da6a Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Tue, 24 Jul 2012 19:14:33 -0700 Subject: [PATCH 09/43] fix indentation --- algorithms/a_star_path_finding.py | 265 ++++++++++++------------ algorithms/generators.py | 22 +- algorithms/list.py | 46 ++--- algorithms/maze.py | 38 ++-- algorithms/permutations.py | 52 ++--- algorithms/string_matching.py | 296 +++++++++++++------------- binary_tree_tutorial.txt | 332 +++++++++++++++--------------- 7 files changed, 526 insertions(+), 525 deletions(-) diff --git a/algorithms/a_star_path_finding.py b/algorithms/a_star_path_finding.py index 1e93349..66dd8d8 100644 --- a/algorithms/a_star_path_finding.py +++ b/algorithms/a_star_path_finding.py @@ -1,140 +1,141 @@ import heapq class Cell(object): - def __init__(self, x, y, reachable): - """ - Initialize new cell - - @param x cell x coordinate - @param y cell y coordinate - @param reachable is cell reachable? not a wall? - """ - self.reachable = reachable - self.x = x - self.y = y - self.parent = None - self.g = 0 - self.h = 0 - self.f = 0 + def __init__(self, x, y, reachable): + """ + Initialize new cell + + @param x cell x coordinate + @param y cell y coordinate + @param reachable is cell reachable? not a wall? + """ + self.reachable = reachable + self.x = x + self.y = y + self.parent = None + self.g = 0 + self.h = 0 + self.f = 0 class AStar(object): - def __init__(self): - self.op = [] - heapq.heapify(self.op) - self.cl = set() - self.cells = [] - self.gridHeight = 6 - self.gridWidth = 6 - - def init_grid(self): - walls = ((0, 5), (1, 0), (1, 1), (1, 5), (2, 3), - (3, 1), (3, 2), (3, 5), (4, 1), (4, 4), (5, 1)) - for x in range(self.gridWidth): - for y in range(self.gridHeight): - if (x, y) in walls: - reachable = False - else: - reachable = True - self.cells.append(Cell(x, y, reachable)) - self.start = self.get_cell(0, 0) - self.end = self.get_cell(5, 5) - - def get_heuristic(self, cell): - """ - Compute the heuristic value H for a cell: distance between - this cell and the ending cell multiply by 10. - - @param cell - @returns heuristic value H - """ - return 10 * (abs(cell.x - self.end.x) + abs(cell.y - self.end.y)) - - def get_cell(self, x, y): - """ - Returns a cell from the cells list - - @param x cell x coordinate - @param y cell y coordinate - @returns cell - """ - return self.cells[x * self.gridHeight + y] - - def get_adjacent_cells(self, cell): - """ - Returns adjacent cells to a cell. Clockwise starting - from the one on the right. - - @param cell get adjacent cells for this cell - @returns adjacent cells list - """ - cells = [] - if cell.x < self.gridWidth-1: - cells.append(self.get_cell(cell.x+1, cell.y)) - if cell.y > 0: - cells.append(self.get_cell(cell.x, cell.y-1)) - if cell.x > 0: - cells.append(self.get_cell(cell.x-1, cell.y)) - if cell.y < self.gridHeight-1: - cells.append(self.get_cell(cell.x, cell.y+1)) - return cells - - def display_path(self): - cell = self.end - while cell.parent is not self.start: - cell = cell.parent - print 'path: cell: %d,%d' % (cell.x, cell.y) - - def compare(self, cell1, cell2): - """ - Compare 2 cells F values - - @param cell1 1st cell - @param cell2 2nd cell - @returns -1, 0 or 1 if lower, equal or greater - """ - if cell1.f < cell2.f: - return -1 - elif cell1.f > cell2.f: - return 1 - return 0 - - def update_cell(self, adj, cell): - """ - Update adjacent cell - - @param adj adjacent cell to current cell - @param cell current cell being processed - """ - adj.g = cell.g + 10 - adj.h = self.get_heuristic(adj) - adj.parent = cell - adj.f = adj.h + adj.g - - def process(self): - # add starting cell to open heap queue - heapq.heappush(self.op, (self.start.f, self.start)) - while len(self.op): - # pop cell from heap queue - h, cell = heapq.heappop(self.op) - # add cell to closed list so we don't process it twice - self.cl.add(cell) - # if ending cell, display found path - if cell is self.end: - self.display_path() - break - # get adjacent cells for cell - adj_cells = self.get_adjacent_cells(cell) - for c in adj_cells: - if c.reachable and c not in self.cl: - if c in self.op: - # if adj cell in open list, check if current path is - # better than the one previously found for this adj cell. - if c.g > cell.g + 10: - self.update_cell(c, cell) - else: - self.update_cell(c, cell) - # add adj cell to open list - heapq.heappush(self.op, (c.f, c)) + def __init__(self): + self.op = [] + heapq.heapify(self.op) + self.cl = set() + self.cells = [] + self.gridHeight = 6 + self.gridWidth = 6 + + def init_grid(self): + walls = ((0, 5), (1, 0), (1, 1), (1, 5), (2, 3), + (3, 1), (3, 2), (3, 5), (4, 1), (4, 4), (5, 1)) + for x in range(self.gridWidth): + for y in range(self.gridHeight): + if (x, y) in walls: + reachable = False + else: + reachable = True + self.cells.append(Cell(x, y, reachable)) + self.start = self.get_cell(0, 0) + self.end = self.get_cell(5, 5) + + def get_heuristic(self, cell): + """ + Compute the heuristic value H for a cell: distance between + this cell and the ending cell multiply by 10. + + @param cell + @returns heuristic value H + """ + return 10 * (abs(cell.x - self.end.x) + abs(cell.y - self.end.y)) + + def get_cell(self, x, y): + """ + Returns a cell from the cells list + + @param x cell x coordinate + @param y cell y coordinate + @returns cell + """ + return self.cells[x * self.gridHeight + y] + + def get_adjacent_cells(self, cell): + """ + Returns adjacent cells to a cell. Clockwise starting + from the one on the right. + + @param cell get adjacent cells for this cell + @returns adjacent cells list + """ + cells = [] + if cell.x < self.gridWidth-1: + cells.append(self.get_cell(cell.x+1, cell.y)) + if cell.y > 0: + cells.append(self.get_cell(cell.x, cell.y-1)) + if cell.x > 0: + cells.append(self.get_cell(cell.x-1, cell.y)) + if cell.y < self.gridHeight-1: + cells.append(self.get_cell(cell.x, cell.y+1)) + return cells + + def display_path(self): + cell = self.end + while cell.parent is not self.start: + cell = cell.parent + print 'path: cell: %d,%d' % (cell.x, cell.y) + + def compare(self, cell1, cell2): + """ + Compare 2 cells F values + + @param cell1 1st cell + @param cell2 2nd cell + @returns -1, 0 or 1 if lower, equal or greater + """ + if cell1.f < cell2.f: + return -1 + elif cell1.f > cell2.f: + return 1 + return 0 + + def update_cell(self, adj, cell): + """ + Update adjacent cell + + @param adj adjacent cell to current cell + @param cell current cell being processed + """ + adj.g = cell.g + 10 + adj.h = self.get_heuristic(adj) + adj.parent = cell + adj.f = adj.h + adj.g + + def process(self): + # add starting cell to open heap queue + heapq.heappush(self.op, (self.start.f, self.start)) + while len(self.op): + # pop cell from heap queue + h, cell = heapq.heappop(self.op) + # add cell to closed list so we don't process it twice + self.cl.add(cell) + # if ending cell, display found path + if cell is self.end: + self.display_path() + break + # get adjacent cells for cell + adj_cells = self.get_adjacent_cells(cell) + for c in adj_cells: + if c.reachable and c not in self.cl: + if c in self.op: + # if adj cell in open list, check if current path is + # better than the one previously found + # for this adj cell. + if c.g > cell.g + 10: + self.update_cell(c, cell) + else: + self.update_cell(c, cell) + # add adj cell to open list + heapq.heappush(self.op, (c.f, c)) a = AStar() a.init_grid() diff --git a/algorithms/generators.py b/algorithms/generators.py index b037434..c8de4cf 100644 --- a/algorithms/generators.py +++ b/algorithms/generators.py @@ -1,14 +1,14 @@ def fib(n): - """ - Generator for Fibonacci serie + """ + Generator for Fibonacci serie - Example: for i in fib(5): print i - @param n fib range upper bound - """ - a, b = 0, 1 - i = 0 - while i < n: - yield b - a, b = b, a+b - i += 1 + Example: for i in fib(5): print i + @param n fib range upper bound + """ + a, b = 0, 1 + i = 0 + while i < n: + yield b + a, b = b, a+b + i += 1 diff --git a/algorithms/list.py b/algorithms/list.py index f82c423..5a8a657 100644 --- a/algorithms/list.py +++ b/algorithms/list.py @@ -1,25 +1,25 @@ def find_max_sub(l): - """ - Find subset with higest sum + """ + Find subset with higest sum - Example: [-2, 3, -4, 5, 1, -5] -> (3,4), 6 - @param l list - @returns subset bounds and highest sum - """ - # max sum - max = l[0] - # current sum - m = 0 - # max sum subset bounds - bounds = (0, 0) - # current subset start - s = 0 - for i in range(len(l)): - m += l[i] - if m > max: - max = m - bounds = (s, i) - elif m < 0: - m = 0 - s = i+1 - return bounds, max + Example: [-2, 3, -4, 5, 1, -5] -> (3,4), 6 + @param l list + @returns subset bounds and highest sum + """ + # max sum + max = l[0] + # current sum + m = 0 + # max sum subset bounds + bounds = (0, 0) + # current subset start + s = 0 + for i in range(len(l)): + m += l[i] + if m > max: + max = m + bounds = (s, i) + elif m < 0: + m = 0 + s = i+1 + return bounds, max diff --git a/algorithms/maze.py b/algorithms/maze.py index a24997c..57a7128 100644 --- a/algorithms/maze.py +++ b/algorithms/maze.py @@ -7,27 +7,27 @@ ] def search(x, y): - if grid[x][y] == 2: - print 'found at %d,%d' % (x, y) - return True - elif grid[x][y] == 1: - print 'wall at %d,%d' % (x, y) - return False - elif grid[x][y] == 3: - print 'visited at %d,%d' % (x, y) - return False - - print 'visiting %d,%d' % (x, y) + if grid[x][y] == 2: + print 'found at %d,%d' % (x, y) + return True + elif grid[x][y] == 1: + print 'wall at %d,%d' % (x, y) + return False + elif grid[x][y] == 3: + print 'visited at %d,%d' % (x, y) + return False + + print 'visiting %d,%d' % (x, y) - # mark as visited - grid[x][y] = 3 - if ((x < len(grid)-1 and search(x+1, y)) - or (y > 0 and search(x, y-1)) - or (x > 0 and search(x-1, y)) - or (y < len(grid)-1 and search(x, y+1))): - return True + # mark as visited + grid[x][y] = 3 + if ((x < len(grid)-1 and search(x+1, y)) + or (y > 0 and search(x, y-1)) + or (x > 0 and search(x-1, y)) + or (y < len(grid)-1 and search(x, y+1))): + return True - return False + return False search(0, 0) diff --git a/algorithms/permutations.py b/algorithms/permutations.py index 3afd03e..49d27ad 100644 --- a/algorithms/permutations.py +++ b/algorithms/permutations.py @@ -1,31 +1,31 @@ def permutations(l): - """ - Generator for list permutations - - @param l list to generate permutations for - @result yield each permutation + """ + Generator for list permutations + + @param l list to generate permutations for + @result yield each permutation - Example: - l = [1,2,3] - a = [1] - permutations([2,3]) = [[2,3], [3,2]] - [2,3] - yield [1,2,3] - yield [2,1,3] - yield [2,3,1] - [3,2] - yield [1,3,2] - yield [3,1,2] - yield [3,2,1] - """ - if len(l) <= 1: - yield l - else: - a = [l.pop(0)] - for p in permutations(l): - for i in range(len(p)+1): - yield p[:i] + a + p[i:] + Example: + l = [1,2,3] + a = [1] + permutations([2,3]) = [[2,3], [3,2]] + [2,3] + yield [1,2,3] + yield [2,1,3] + yield [2,3,1] + [3,2] + yield [1,3,2] + yield [3,1,2] + yield [3,2,1] + """ + if len(l) <= 1: + yield l + else: + a = [l.pop(0)] + for p in permutations(l): + for i in range(len(p)+1): + yield p[:i] + a + p[i:] for p in permutations([1,2,3]): - print p + print p diff --git a/algorithms/string_matching.py b/algorithms/string_matching.py index 8373250..d2387d0 100644 --- a/algorithms/string_matching.py +++ b/algorithms/string_matching.py @@ -3,165 +3,165 @@ """ def string_matching_naive(text='', pattern=''): - """ - Returns positions where pattern is found in text + """ + Returns positions where pattern is found in text + + We slide the string to match 'pattern' over the text + + O((n-m)m) + Example: text = 'ababbababa', pattern = 'aba' + string_matching_naive(t, s) returns [0, 5, 7] + @param text text to search inside + @param pattern string to search for + @return list containing offsets (shifts) where pattern is found inside text + """ + + n = len(text) + m = len(pattern) + offsets = [] + for i in range(n-m+1): + if pattern == text[i:i+m]: + offsets.append(i) - We slide the string to match 'pattern' over the text - - O((n-m)m) - Example: text = 'ababbababa', pattern = 'aba' - string_matching_naive(t, s) returns [0, 5, 7] - @param text text to search inside - @param pattern string to search for - @return list containing offsets (shifts) where pattern is found inside text - """ - - n = len(text) - m = len(pattern) - offsets = [] - for i in range(n-m+1): - if pattern == text[i:i+m]: - offsets.append(i) - - return offsets + return offsets def string_matching_rabin_karp(text='', pattern='', hash_base=256): - """ - Returns positions where pattern is found in text - - We calculate the hash value of the pattern and we compare it to the hash - value of text[i:i+m] for i = 0..n-m - The nice thing is that we don't need to calculate the hash value of - text[i:i+m] each time from scratch, we know that: - h(text[i+1:i+m+1]) = (base * (h(text[i:i+m]) - (text[i] * (base ^ (m-1))))) + text[i+m] - We can get h('bcd') from h('abc'). - h('bcd') = (base * (h('abc') - ('a' * (base ^ 2)))) + 'd' - - worst case: O(nm) - we can expect O(n+m) if the number of valid matches is small and the pattern - large - - Performance: ord() is slow so we shouldn't use it here - - Example: text = 'ababbababa', pattern = 'aba' - string_matching_rabin_karp(text, pattern) returns [0, 5, 7] - @param text text to search inside - @param pattern string to search for - @param hash_base base to calculate the hash value - @return list containing offsets (shifts) where pattern is found inside text - """ - - n = len(text) - m = len(pattern) - offsets = [] - htext = hash_value(text[:m], hash_base) - hpattern = hash_value(pattern, hash_base) - for i in range(n-m+1): - if htext == hpattern: - if text[i:i+m] == pattern: - offsets.append(i) - if i < n-m: - htext = (hash_base * (htext - (ord(text[i]) * (hash_base ** (m-1))))) + ord(text[i+m]) - - return offsets + """ + Returns positions where pattern is found in text + + We calculate the hash value of the pattern and we compare it to the hash + value of text[i:i+m] for i = 0..n-m + The nice thing is that we don't need to calculate the hash value of + text[i:i+m] each time from scratch, we know that: + h(text[i+1:i+m+1]) = (base * (h(text[i:i+m]) - (text[i] * (base ^ (m-1))))) + text[i+m] + We can get h('bcd') from h('abc'). + h('bcd') = (base * (h('abc') - ('a' * (base ^ 2)))) + 'd' + + worst case: O(nm) + we can expect O(n+m) if the number of valid matches is small and the pattern + large + + Performance: ord() is slow so we shouldn't use it here + + Example: text = 'ababbababa', pattern = 'aba' + string_matching_rabin_karp(text, pattern) returns [0, 5, 7] + @param text text to search inside + @param pattern string to search for + @param hash_base base to calculate the hash value + @return list containing offsets (shifts) where pattern is found inside text + """ + + n = len(text) + m = len(pattern) + offsets = [] + htext = hash_value(text[:m], hash_base) + hpattern = hash_value(pattern, hash_base) + for i in range(n-m+1): + if htext == hpattern: + if text[i:i+m] == pattern: + offsets.append(i) + if i < n-m: + htext = (hash_base * (htext - (ord(text[i]) * (hash_base ** (m-1))))) + ord(text[i+m]) + + return offsets def hash_value(s, base): - """ - Calculate the hash value of a string using base - - Example: 'abc' = 97 x base^2 + 98 x base^1 + 99 x base^0 - @param s string to compute hash value for - @param base base to use to compute hash value - @return hash value - """ - v = 0 - p = len(s)-1 - for i in range(p+1): - v += ord(s[i]) * (base ** p) - p -= 1 - - return v + """ + Calculate the hash value of a string using base + + Example: 'abc' = 97 x base^2 + 98 x base^1 + 99 x base^0 + @param s string to compute hash value for + @param base base to use to compute hash value + @return hash value + """ + v = 0 + p = len(s)-1 + for i in range(p+1): + v += ord(s[i]) * (base ** p) + p -= 1 + + return v def string_matching_knuth_morris_pratt(text='', pattern=''): - """ - Returns positions where pattern is found in text - - See http://jboxer.com/2009/12/the-knuth-morris-pratt-algorithm-in-my-own-words/ for a great explanation on how this algorithm works. - - O(m+n) - Example: text = 'ababbababa', pattern = 'aba' - string_matching_knuth_morris_pratt(text, pattern) returns [0, 5, 7] - @param text text to search inside - @param pattern string to search for - @return list containing offsets (shifts) where pattern is found inside text - """ - - n = len(text) - m = len(pattern) - offsets = [] - pi = compute_prefix_function(pattern) - q = 0 - for i in range(n): - while q > 0 and pattern[q] != text[i]: - q = pi[q - 1] - if pattern[q] == text[i]: - q = q + 1 - if q == m: - offsets.append(i - m + 1) - q = pi[q-1] - - return offsets + """ + Returns positions where pattern is found in text + + See http://jboxer.com/2009/12/the-knuth-morris-pratt-algorithm-in-my-own-words/ for a great explanation on how this algorithm works. + + O(m+n) + Example: text = 'ababbababa', pattern = 'aba' + string_matching_knuth_morris_pratt(text, pattern) returns [0, 5, 7] + @param text text to search inside + @param pattern string to search for + @return list containing offsets (shifts) where pattern is found inside text + """ + + n = len(text) + m = len(pattern) + offsets = [] + pi = compute_prefix_function(pattern) + q = 0 + for i in range(n): + while q > 0 and pattern[q] != text[i]: + q = pi[q - 1] + if pattern[q] == text[i]: + q = q + 1 + if q == m: + offsets.append(i - m + 1) + q = pi[q-1] + + return offsets def compute_prefix_function(p): - m = len(p) - pi = [0] * m - k = 0 - for q in range(1, m): - while k > 0 and p[k] != p[q]: - k = pi[k - 1] - if p[k] == p[q]: - k = k + 1 - pi[q] = k - return pi + m = len(p) + pi = [0] * m + k = 0 + for q in range(1, m): + while k > 0 and p[k] != p[q]: + k = pi[k - 1] + if p[k] == p[q]: + k = k + 1 + pi[q] = k + return pi def string_matching_boyer_moore_horspool(text='', pattern=''): - """ - Returns positions where pattern is found in text - - See http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm for an explanation on how - this algorithm works. - - O(n) - Performance: ord() is slow so we shouldn't use it here - - Example: text = 'ababbababa', pattern = 'aba' - string_matching_boyer_moore_horspool(text, pattern) returns [0, 5, 7] - @param text text to search inside - @param pattern string to search for - @return list containing offsets (shifts) where pattern is found inside text - """ - - m = len(pattern) - n = len(text) - offsets = [] - if m > n: + """ + Returns positions where pattern is found in text + + See http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm for an explanation on how + this algorithm works. + + O(n) + Performance: ord() is slow so we shouldn't use it here + + Example: text = 'ababbababa', pattern = 'aba' + string_matching_boyer_moore_horspool(text, pattern) returns [0, 5, 7] + @param text text to search inside + @param pattern string to search for + @return list containing offsets (shifts) where pattern is found inside text + """ + + m = len(pattern) + n = len(text) + offsets = [] + if m > n: + return offsets + skip = [] + for k in range(256): + skip.append(m) + for k in range(m-1): + skip[ord(pattern[k])] = m - k - 1 + skip = tuple(skip) + k = m - 1 + while k < n: + j = m - 1; i = k + while j >= 0 and text[i] == pattern[j]: + j -= 1 + i -= 1 + if j == -1: + offsets.append(i + 1) + k += skip[ord(text[k])] + return offsets - skip = [] - for k in range(256): - skip.append(m) - for k in range(m-1): - skip[ord(pattern[k])] = m - k - 1 - skip = tuple(skip) - k = m - 1 - while k < n: - j = m - 1; i = k - while j >= 0 and text[i] == pattern[j]: - j -= 1 - i -= 1 - if j == -1: - offsets.append(i + 1) - k += skip[ord(text[k])] - - return offsets diff --git a/binary_tree_tutorial.txt b/binary_tree_tutorial.txt index f62e09f..2e76ec5 100644 --- a/binary_tree_tutorial.txt +++ b/binary_tree_tutorial.txt @@ -38,18 +38,18 @@ We need to represent a tree node. To do that, we create a new class named Node w [code lang="python"] class Node: - """ - Tree node: left and right child + data which can be any object - """ - def __init__(self, data): """ - Node constructor - - @param data node data object + Tree node: left and right child + data which can be any object """ - self.left = None - self.right = None - self.data = data + def __init__(self, data): + """ + Node constructor + + @param data node data object + """ + self.left = None + self.right = None + self.data = data [/code] Let's create a tree node containing the integer 8. You can pass any object for the data so it is flexible. When you create a node, both left and right node equal to None. @@ -68,23 +68,23 @@ We need a method to help us populate our tree. This method takes the node's data [code lang="python"] class Node: - ... - def insert(self, data): - """ - Insert new node with data - - @param data node data object to insert - """ - if data < self.data: - if self.left is None: - self.left = Node(data) - else: - self.left.insert(data) - else: - if self.right is None: - self.right = Node(data) - else: - self.right.insert(data) + ... + def insert(self, data): + """ + Insert new node with data + + @param data node data object to insert + """ + if data < self.data: + if self.left is None: + self.left = Node(data) + else: + self.left.insert(data) + else: + if self.right is None: + self.right = Node(data) + else: + self.right.insert(data) [/code] insert() is called recursively as we are locating the place where to add the new node. @@ -140,25 +140,25 @@ We need a way to look for a specific node in the tree. We add a new method named [code lang="python"] class Node: - ... - def lookup(self, data, parent=None): - """ - Lookup node containing data - - @param data node data object to look up - @param parent node's parent - @returns node and node's parent if found or None, None - """ - if data < self.data: - if self.left is None: - return None, None - return self.left.lookup(data, self) - elif data > self.data: - if self.right is None: - return None, None - return self.right.lookup(data, self) - else: - return self, parent + ... + def lookup(self, data, parent=None): + """ + Lookup node containing data + + @param data node data object to look up + @param parent node's parent + @returns node and node's parent if found or None, None + """ + if data < self.data: + if self.left is None: + return None, None + return self.left.lookup(data, self) + elif data > self.data: + if self.right is None: + return None, None + return self.right.lookup(data, self) + else: + return self, parent [/code] Let's look up the node containing 6. @@ -186,18 +186,18 @@ The method delete() takes the data of the node to remove as an argument. [code lang="python"] class Node: - ... - def delete(self, data): - """ - Delete node containing data - - @param data node's content to delete - """ - # get node containing data - node, parent = self.lookup(data) - if node is not None: - children_count = node.children_count() ... + def delete(self, data): + """ + Delete node containing data + + @param data node's content to delete + """ + # get node containing data + node, parent = self.lookup(data) + if node is not None: + children_count = node.children_count() + ... [/code] There are 3 possibilities to handle: @@ -210,16 +210,16 @@ There are 3 possibilities to handle: Let's tackle the first possibility which is the easiest. We look for the node to remove and we set its parent's left or right child to None. [code lang="python"] - def delete(self, data): - ... - if children_count == 0: - # if node has no children, just remove it - if parent.left is node: - parent.left = None - else: - parent.right = None - del node - ... + def delete(self, data): + ... + if children_count == 0: + # if node has no children, just remove it + if parent.left is node: + parent.left = None + else: + parent.right = None + del node + ... [/code] Note: children_count() returns the number of children of a node. @@ -228,21 +228,21 @@ Here is the function children_count: [code lang="python"] class Node: - ... - def children_count(self): - """ - Returns the number of children - - @returns number of children: 0, 1, 2 - """ - if node is None: - return None - cnt = 0 - if self.left: - cnt += 1 - if self.right: - cnt += 1 - return cnt + ... + def children_count(self): + """ + Returns the number of children + + @returns number of children: 0, 1, 2 + """ + if node is None: + return None + cnt = 0 + if self.left: + cnt += 1 + if self.right: + cnt += 1 + return cnt [/code] For example, we want to remove node 1. Node 3 left child will be set to None. @@ -256,22 +256,22 @@ root.delete(1) Let's look at the second possibility which is the node to be removed has 1 child. We replace the node's data by its left or right child's data and we set its left or right child to None. [code lang="python"] - def delete(self, data): - ... - elif children_count == 1: - # if node has 1 child - # replace node by its child - if node.left: - n = node.left - else: - n = node.right - if parent: - if parent.left is node: - parent.left = n - else: - parent.right = n - del node - ... + def delete(self, data): + ... + elif children_count == 1: + # if node has 1 child + # replace node by its child + if node.left: + n = node.left + else: + n = node.right + if parent: + if parent.left is node: + parent.left = n + else: + parent.right = n + del node + ... [/code] For example, we want to remove node 14. Node 14 data will be set to 13 (its left child's data) and its left child will be set to None. @@ -285,23 +285,23 @@ root.delete(14) Let's look at the last possibility which is the node to be removed has 2 children. We replace its data with its successor's data and we fix the successor's parent's child. [code lang="python"] - def delete(self, data): - ... - else: - # if node has 2 children - # find its successor - parent = node - successor = node.right - while successor.left: - parent = successor - successor = successor.left - # replace node data by its successor data - node.data = successor.data - # fix successor's parent's child - if parent.left == successor: - parent.left = successor.right - else: - parent.right = successor.right + def delete(self, data): + ... + else: + # if node has 2 children + # find its successor + parent = node + successor = node.right + while successor.left: + parent = successor + successor = successor.left + # replace node data by its successor data + node.data = successor.data + # fix successor's parent's child + if parent.left == successor: + parent.left = successor.right + else: + parent.right = successor.right [/code] For example, we want to remove node 3. We look for its successor by going right then left until we reach a leaf. Its successor is node 4. We replace 3 with 4. Node 4 doesn't have a child so we set node 6 left child to None. @@ -318,16 +318,16 @@ We add a method to print the tree inorder. This method has no argument. We use r [code lang="python"] class Node: - ... - def print_tree(self): - """ - Print tree content inorder - """ - if self.left: - self.left.print_tree() - print self.data, - if self.right: - self.right.print_tree() + ... + def print_tree(self): + """ + Print tree content inorder + """ + if self.left: + self.left.print_tree() + print self.data, + if self.right: + self.right.print_tree() [/code] Let's print our tree: @@ -343,30 +343,30 @@ To compare 2 trees, we add a method which compares each subtree recursively. It [code lang="python"] class Node: - ... - def compare_trees(self, node): - """ - Compare 2 trees - - @param node tree's root node to compare to - @returns True if the tree passed is identical to this tree - """ - if node is None: - return False - if self.data != node.data: - return False - res = True - if self.left is None: - if node.left: - return False - else: - res = self.left.compare_trees(node.left) - if self.right is None: - if node.right: - return False - else: - res = self.right.compare_trees(node.right) - return res + ... + def compare_trees(self, node): + """ + Compare 2 trees + + @param node tree's root node to compare to + @returns True if the tree passed is identical to this tree + """ + if node is None: + return False + if self.data != node.data: + return False + res = True + if self.left is None: + if node.left: + return False + else: + res = self.left.compare_trees(node.left) + if self.right is None: + if node.right: + return False + else: + res = self.right.compare_trees(node.right) + return res [/code] For example, we want to compare tree (3, 8, 10) with tree (3, 8, 11) @@ -401,28 +401,28 @@ Here is the code: [code lang="python"] class Node: - ... - def tree_data(self): - """ - Generator to get the tree nodes data - """ - # we use a stack to traverse the tree in a non-recursive way - stack = [] - node = self - while stack or node: - if node: - stack.append(node) - node = node.left - else: # we are returning so we pop the node and we yield it - node = stack.pop() - yield node.data - node = node.right + ... + def tree_data(self): + """ + Generator to get the tree nodes data + """ + # we use a stack to traverse the tree in a non-recursive way + stack = [] + node = self + while stack or node: + if node: + stack.append(node) + node = node.left + else: # we are returning so we pop the node and we yield it + node = stack.pop() + yield node.data + node = node.right [/code] For example, we want to access the tree nodes using a for loop: [code lang="python"] for data in root.tree_data(): - print data + print data [/code] Let's look at what happens in the code with the same example we have been using: From 0fbcb38b26d8690028cd5a676743950fdf3a060f Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Sun, 12 Aug 2012 22:59:54 +0200 Subject: [PATCH 10/43] Fix heap lookup --- algorithms/a_star_path_finding.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/algorithms/a_star_path_finding.py b/algorithms/a_star_path_finding.py index 66dd8d8..7c6973e 100644 --- a/algorithms/a_star_path_finding.py +++ b/algorithms/a_star_path_finding.py @@ -115,7 +115,7 @@ def process(self): heapq.heappush(self.op, (self.start.f, self.start)) while len(self.op): # pop cell from heap queue - h, cell = heapq.heappop(self.op) + f, cell = heapq.heappop(self.op) # add cell to closed list so we don't process it twice self.cl.add(cell) # if ending cell, display found path @@ -126,7 +126,7 @@ def process(self): adj_cells = self.get_adjacent_cells(cell) for c in adj_cells: if c.reachable and c not in self.cl: - if c in self.op: + if (c.f, c) in self.op: # if adj cell in open list, check if current path is # better than the one previously found # for this adj cell. From 656c1c93bd697134aa4c1e8ecbc2ecb07494aa09 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Tue, 3 Jun 2014 09:38:03 -0400 Subject: [PATCH 11/43] Remove tutorial. Sync binary tree code with blog. --- algorithms/binary_tree.py | 14 +- binary_tree_tutorial.txt | 444 -------------------------------------- 2 files changed, 8 insertions(+), 450 deletions(-) delete mode 100644 binary_tree_tutorial.txt diff --git a/algorithms/binary_tree.py b/algorithms/binary_tree.py index 792767c..28bac22 100644 --- a/algorithms/binary_tree.py +++ b/algorithms/binary_tree.py @@ -23,7 +23,7 @@ def insert(self, data): self.left = Node(data) else: self.left.insert(data) - else: + elif data > self.data: if self.right is None: self.right = Node(data) else: @@ -60,7 +60,6 @@ def delete(self, data): children_count = node.children_count() if children_count == 0: # if node has no children, just remove it - # check if it is not the root node if parent.left is node: parent.left = None else: @@ -73,10 +72,11 @@ def delete(self, data): n = node.left else: n = node.right - if parent.left is node: - parent.left = n - else: - parent.right = n + if parent: + if parent.left is node: + parent.left = n + else: + parent.right = n del node else: # if node has 2 children @@ -111,6 +111,8 @@ def compare_trees(self, node): return False else: res = self.left.compare_trees(node.left) + if res is False: + return False if self.right is None: if node.right: return False diff --git a/binary_tree_tutorial.txt b/binary_tree_tutorial.txt deleted file mode 100644 index f62e09f..0000000 --- a/binary_tree_tutorial.txt +++ /dev/null @@ -1,444 +0,0 @@ -This article is about a Python library I created to manage binary search trees. I will go over the following: - -
      -
    • Node class
    • -
    • Insert method
    • -
    • Lookup method
    • -
    • Delete method
    • -
    • Print method
    • -
    • Comparing 2 trees
    • -
    • Generator returning the tree elements one by one
    • -
    - -You can checkout the library code on GitHub: git clone https://laurentluce@github.com/laurentluce/python-algorithms.git. This folder contains more libraries but we are just going to focus on the Binary Tree one. - -As a reminder, here is a binary search tree definition (Wikipedia). - -A binary search tree (BST) or ordered binary tree is a node-based binary tree data structure which has the following properties: - -
      -
    • The left subtree of a node contains only nodes with keys less than the node's key.
    • -
    • The right subtree of a node contains only nodes with keys greater than the node's key.
    • -
    • Both the left and right subtrees must also be binary search trees.
    • -
    - -Here is an example of a binary search tree: - - - -

    Node class

    - -We need to represent a tree node. To do that, we create a new class named Node with 3 attributes: - -
      -
    • Left node
    • -
    • Right node
    • -
    • Node's data (same as key in the definition above.)
    • -
    - -[code lang="python"] -class Node: - """ - Tree node: left and right child + data which can be any object - """ - def __init__(self, data): - """ - Node constructor - - @param data node data object - """ - self.left = None - self.right = None - self.data = data -[/code] - -Let's create a tree node containing the integer 8. You can pass any object for the data so it is flexible. When you create a node, both left and right node equal to None. - -[code lang="python"] -root = Node(8) -[/code] - -Note that we just created a tree with a single node. - - - -

    Insert method

    - -We need a method to help us populate our tree. This method takes the node's data as an argument and inserts a new node in the tree. - -[code lang="python"] -class Node: - ... - def insert(self, data): - """ - Insert new node with data - - @param data node data object to insert - """ - if data < self.data: - if self.left is None: - self.left = Node(data) - else: - self.left.insert(data) - else: - if self.right is None: - self.right = Node(data) - else: - self.right.insert(data) -[/code] - -insert() is called recursively as we are locating the place where to add the new node. - -Let's add 3 nodes to our root node which we created above and let's look at what the code does. - -[code lang="python"] -root.insert(3) -root.insert(10) -root.insert(1) -[/code] - -This is what happens when we add the second node (3): -
      -
    • 1- root node's method insert() is called with data = 3.
    • -
    • 2- 3 is less than 8 and left child is None so we attach the new node to it. -
    - -This is what happens when we add the third node (10): -
      -
    • 1- root node's method insert() is called with data = 10.
    • -
    • 2- 10 is greater than 8 and right child is None so we attach the new node to it. -
    - -This is what happens when we add the fourth node (1): -
      -
    • 1- root node's method insert() is called with data = 1.
    • -
    • 2- 1 is less than 8 so the root's left child (3) insert() method is called with data = 1. Note how we call the method on a subtree. -
    • 3- 1 is less than 3 and left child is None so we attach the new node to it. -
    - -This is how the tree looks like now: - - - -Let's continue and complete our tree so we can move on to the next section which is about looking up nodes in the tree. - -[code lang="python"] -root.insert(6) -root.insert(4) -root.insert(7) -root.insert(14) -root.insert(13) -[/code] - -The complete tree looks like this: - - - -

    Lookup method

    - -We need a way to look for a specific node in the tree. We add a new method named lookup which takes a node's data as an argument and returns the node if found or None if not. We also return the node's parent for convenience. - -[code lang="python"] -class Node: - ... - def lookup(self, data, parent=None): - """ - Lookup node containing data - - @param data node data object to look up - @param parent node's parent - @returns node and node's parent if found or None, None - """ - if data < self.data: - if self.left is None: - return None, None - return self.left.lookup(data, self) - elif data > self.data: - if self.right is None: - return None, None - return self.right.lookup(data, self) - else: - return self, parent -[/code] - -Let's look up the node containing 6. - -[code lang="python"] -node, parent = root.lookup(6) -[/code] - -This is what happens when lookup() is called: - -
      -
    • 1- lookup() is called with data = 6, and default value parent = None.
    • -
    • 2- data = 6 is less than root's data which is 8.
    • -
    • 3- root's left child lookup() method is called with data = 6, parent = current node. Notice how we call lookup() on a subtree.
    • -
    • 4- data = 6 is greater than node's data which is now 3.
    • -
    • 5- node's right child lookup() method is called with data = 6 and parent = current node
    • -
    • 6- node's data is equal to 6 so we return it and its parent which is node 3. -
    - - - -

    Delete method

    - -The method delete() takes the data of the node to remove as an argument. - -[code lang="python"] -class Node: - ... - def delete(self, data): - """ - Delete node containing data - - @param data node's content to delete - """ - # get node containing data - node, parent = self.lookup(data) - if node is not None: - children_count = node.children_count() - ... -[/code] - -There are 3 possibilities to handle: -
      -
    • 1- The node to remove has no child.
    • -
    • 2- The node to remove has 1 child.
    • -
    • 3- The node to remove has 2 children.
    • -
    - -Let's tackle the first possibility which is the easiest. We look for the node to remove and we set its parent's left or right child to None. - -[code lang="python"] - def delete(self, data): - ... - if children_count == 0: - # if node has no children, just remove it - if parent.left is node: - parent.left = None - else: - parent.right = None - del node - ... -[/code] - -Note: children_count() returns the number of children of a node. - -Here is the function children_count: - -[code lang="python"] -class Node: - ... - def children_count(self): - """ - Returns the number of children - - @returns number of children: 0, 1, 2 - """ - if node is None: - return None - cnt = 0 - if self.left: - cnt += 1 - if self.right: - cnt += 1 - return cnt -[/code] - -For example, we want to remove node 1. Node 3 left child will be set to None. - -[code lang="python"] -root.delete(1) -[/code] - - - -Let's look at the second possibility which is the node to be removed has 1 child. We replace the node's data by its left or right child's data and we set its left or right child to None. - -[code lang="python"] - def delete(self, data): - ... - elif children_count == 1: - # if node has 1 child - # replace node by its child - if node.left: - n = node.left - else: - n = node.right - if parent: - if parent.left is node: - parent.left = n - else: - parent.right = n - del node - ... -[/code] - -For example, we want to remove node 14. Node 14 data will be set to 13 (its left child's data) and its left child will be set to None. - -[code lang="python"] -root.delete(14) -[/code] - - - -Let's look at the last possibility which is the node to be removed has 2 children. We replace its data with its successor's data and we fix the successor's parent's child. - -[code lang="python"] - def delete(self, data): - ... - else: - # if node has 2 children - # find its successor - parent = node - successor = node.right - while successor.left: - parent = successor - successor = successor.left - # replace node data by its successor data - node.data = successor.data - # fix successor's parent's child - if parent.left == successor: - parent.left = successor.right - else: - parent.right = successor.right -[/code] - -For example, we want to remove node 3. We look for its successor by going right then left until we reach a leaf. Its successor is node 4. We replace 3 with 4. Node 4 doesn't have a child so we set node 6 left child to None. - -[code lang="python"] -root.delete(3) -[/code] - - - -

    Print method

    - -We add a method to print the tree inorder. This method has no argument. We use recursion inside print_tree() to walk the tree breath-first. We first traverse the left subtree, then we print the root node then we traverse the right subtree. - -[code lang="python"] -class Node: - ... - def print_tree(self): - """ - Print tree content inorder - """ - if self.left: - self.left.print_tree() - print self.data, - if self.right: - self.right.print_tree() -[/code] - -Let's print our tree: -[code lang="python"] -root.print_tree() -[/code] - -The output will be: 1, 3, 4, 6, 7, 8, 10, 13, 14 - -

    Comparing 2 trees

    - -To compare 2 trees, we add a method which compares each subtree recursively. It returns False when one leaf is not the same in both trees. This includes 1 leaf missing in the other tree or the data is different. We need to pass the root of the tree to compare to as an argument. - -[code lang="python"] -class Node: - ... - def compare_trees(self, node): - """ - Compare 2 trees - - @param node tree's root node to compare to - @returns True if the tree passed is identical to this tree - """ - if node is None: - return False - if self.data != node.data: - return False - res = True - if self.left is None: - if node.left: - return False - else: - res = self.left.compare_trees(node.left) - if self.right is None: - if node.right: - return False - else: - res = self.right.compare_trees(node.right) - return res -[/code] - -For example, we want to compare tree (3, 8, 10) with tree (3, 8, 11) - - - -[code lang="python"] -# root2 is the root of tree 2 -root.compare_trees(root2) -[/code] - -This is what happens in the code when we call compare_trees(). - -
      -
    • 1- The root node compare_trees() method is called with the tree to compare root node.
    • -
    • 2- The root node has a left child so we call the left child compare_trees() method.
    • -
    • 3- The left subtree comparison will return True.
    • -
    • 2- The root node has a right child so we call the right child compare_trees() method.
    • -
    • 3- The right subtree comparison will return False because the data is different.
    • -
    • 4- compare_trees() will return False.
    • -
    - -

    Generator returning the tree elements one by one

    - -It is sometimes useful to create a generator which returns the tree nodes values one by one. It is memory efficient as it doesn't have to build the full list of nodes right away. Each time we call this method, it returns the next node value. - -To do that, we use the yield keyword which returns an object and stops right there so the function will continue from there next time the method is called. - -We cannot use recursion in this case so we use a stack. - -Here is the code: - -[code lang="python"] -class Node: - ... - def tree_data(self): - """ - Generator to get the tree nodes data - """ - # we use a stack to traverse the tree in a non-recursive way - stack = [] - node = self - while stack or node: - if node: - stack.append(node) - node = node.left - else: # we are returning so we pop the node and we yield it - node = stack.pop() - yield node.data - node = node.right -[/code] - -For example, we want to access the tree nodes using a for loop: -[code lang="python"] -for data in root.tree_data(): - print data -[/code] - -Let's look at what happens in the code with the same example we have been using: - - - -
      -
    • 1- The root node tree_data() method is called.
    • -
    • 2- Node 8 is added to the stack. We go to the left child of 8.
    • -
    • 3- Node 3 is added to the stack. We go to the left child of 3.
    • -
    • 4- Node 1 is added to the stack. Node is set to None because there is no left child.
    • -
    • 5- We pop a node which is Node 1. We yield it (returns 1 and stops here until tree_data() is called again.
    • -
    • 6- tree_data() is called again because we are using it in a for loop.
    • -
    • 7- Node is set to None because Node 1 doesn't have a right child.
    • -
    • 8- We pop a node which is Node 3. We yield it (returns 3 and stops here until tree_data() is called again.
    • -
    • ...
    • -
    - -Here you go, I hope you enjoyed this tutorial. Don't hesitate to add comments if you have any feedback. From a84487bf5dc40d20e272a997bdbdb2431421560b Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Wed, 23 Jul 2014 21:58:18 -0400 Subject: [PATCH 12/43] PEP8 changes. --- algorithms/a_star_path_finding.py | 42 +++++++++++++++---------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/algorithms/a_star_path_finding.py b/algorithms/a_star_path_finding.py index 7c6973e..8e5b81d 100644 --- a/algorithms/a_star_path_finding.py +++ b/algorithms/a_star_path_finding.py @@ -19,18 +19,18 @@ def __init__(self, x, y, reachable): class AStar(object): def __init__(self): - self.op = [] - heapq.heapify(self.op) - self.cl = set() + self.opened = [] + heapq.heapify(self.opened) + self.closed = set() self.cells = [] - self.gridHeight = 6 - self.gridWidth = 6 + self.grid_height = 6 + self.grid_width = 6 def init_grid(self): walls = ((0, 5), (1, 0), (1, 1), (1, 5), (2, 3), (3, 1), (3, 2), (3, 5), (4, 1), (4, 4), (5, 1)) - for x in range(self.gridWidth): - for y in range(self.gridHeight): + for x in range(self.grid_width): + for y in range(self.grid_height): if (x, y) in walls: reachable = False else: @@ -57,7 +57,7 @@ def get_cell(self, x, y): @param y cell y coordinate @returns cell """ - return self.cells[x * self.gridHeight + y] + return self.cells[x * self.grid_height + y] def get_adjacent_cells(self, cell): """ @@ -68,13 +68,13 @@ def get_adjacent_cells(self, cell): @returns adjacent cells list """ cells = [] - if cell.x < self.gridWidth-1: + if cell.x < self.grid_width-1: cells.append(self.get_cell(cell.x+1, cell.y)) if cell.y > 0: cells.append(self.get_cell(cell.x, cell.y-1)) if cell.x > 0: cells.append(self.get_cell(cell.x-1, cell.y)) - if cell.y < self.gridHeight-1: + if cell.y < self.grid_height-1: cells.append(self.get_cell(cell.x, cell.y+1)) return cells @@ -112,30 +112,30 @@ def update_cell(self, adj, cell): def process(self): # add starting cell to open heap queue - heapq.heappush(self.op, (self.start.f, self.start)) - while len(self.op): + heapq.heappush(self.opened, (self.start.f, self.start)) + while len(self.opened): # pop cell from heap queue - f, cell = heapq.heappop(self.op) + f, cell = heapq.heappop(self.opened) # add cell to closed list so we don't process it twice - self.cl.add(cell) + self.closed.add(cell) # if ending cell, display found path if cell is self.end: self.display_path() break # get adjacent cells for cell adj_cells = self.get_adjacent_cells(cell) - for c in adj_cells: - if c.reachable and c not in self.cl: - if (c.f, c) in self.op: + for adj_cell in adj_cells: + if adj_cell.reachable and adj_cell not in self.closed: + if (adj_cell.f, adj_cell) in self.opened: # if adj cell in open list, check if current path is # better than the one previously found # for this adj cell. - if c.g > cell.g + 10: - self.update_cell(c, cell) + if adj_cell.g > cell.g + 10: + self.update_cell(adj_cell, cell) else: - self.update_cell(c, cell) + self.update_cell(adj_cell, cell) # add adj cell to open list - heapq.heappush(self.op, (c.f, c)) + heapq.heappush(self.opened, (adj_cell.f, adj_cell)) a = AStar() a.init_grid() From c6cbff82c90d89cda3113b6a0df462df7ecea51b Mon Sep 17 00:00:00 2001 From: imgemp Date: Thu, 11 Dec 2014 14:38:34 -0500 Subject: [PATCH 13/43] Enabled root deletion for zero and one child roots --- algorithms/__init__.pyc | Bin 0 -> 109 bytes algorithms/binary_tree.py | 42 +++++++++++++++++---------- algorithms/binary_tree.pyc | Bin 0 -> 3766 bytes algorithms/tests/test_binary_tree.py | 16 ++++++++++ 4 files changed, 42 insertions(+), 16 deletions(-) create mode 100644 algorithms/__init__.pyc create mode 100644 algorithms/binary_tree.pyc diff --git a/algorithms/__init__.pyc b/algorithms/__init__.pyc new file mode 100644 index 0000000000000000000000000000000000000000..532c96eaf2863df96ec56811d4dd736c22b579d1 GIT binary patch literal 109 zcmZSn%*(a^YG+6?0~9a self.data: - if self.right is None: - self.right = Node(data) - else: - self.right.insert(data) + if self.data: + if data < self.data: + if self.left is None: + self.left = Node(data) + else: + self.left.insert(data) + elif data > self.data: + if self.right is None: + self.right = Node(data) + else: + self.right.insert(data) + else: + self.data = data def lookup(self, data, parent=None): """ @@ -60,11 +63,14 @@ def delete(self, data): children_count = node.children_count() if children_count == 0: # if node has no children, just remove it - if parent.left is node: - parent.left = None + if parent: + if parent.left is node: + parent.left = None + else: + parent.right = None + del node else: - parent.right = None - del node + self.data = None elif children_count == 1: # if node has 1 child # replace node by its child @@ -77,7 +83,11 @@ def delete(self, data): parent.left = n else: parent.right = n - del node + del node + else: + self.left = n.left + self.right = n.right + self.data = n.data else: # if node has 2 children # find its successor @@ -157,4 +167,4 @@ def children_count(self): cnt += 1 if self.right: cnt += 1 - return cnt + return cnt \ No newline at end of file diff --git a/algorithms/binary_tree.pyc b/algorithms/binary_tree.pyc new file mode 100644 index 0000000000000000000000000000000000000000..8b7e9f12fb001212ddadf0c336f80183fcf026a7 GIT binary patch literal 3766 zcmb7H&2Ah;5bl{>&w8CT4*3BEkZ2JJR>I;ZQb36yiU=Ygq7@oPL|KR?W55&e2D}6kXTGm`c6RMZXtQ?rR{vCWb=6l@y{&&%SMLA) z^md}^PYd7I(afvpQvA16>ZvD69VvBph_SCW`faRPc7rm_8de_@pP`w%=rW~-9t8~j zTEGyJd`cR&RobQCC!QJxDs9VTN2NQvI$@V;k!SJ9)CBleo~E8TLHV<>qddSP_v>@oBq3 z>2IRLtq+xoa0f^D>d+Blkc~bWW@=Cs8bc>#VQe)?Y+0>{bgo<<$5p)VG@-7Jno~j8 z=gnGVVqad#F5x7(l1%YBa=464Hrfj5i9VX^%mq;KXuGU(yR&cJ-^`1+dK6h0>b>zJ zyNuN+%8T4a5ha@doeH{M*L&U&zhVhi*o6W=)%ORzhv6-m7whaTtLlofEp9lPTJ7Lj zyLPm} z$j}d92r>C$9?`bcxrk^a4*Il`7&@fmLysMrvRrVxL&lD{CZu6s$Sq+99BXLi96GvV z4S#3+v-S5YenRQfCMbP~35UtrCzp@=;rMxp!sTiH}kYOnW|03&dKz{ z%*nFslv*?lLx$l+G;;=>TBf(#p7S=oeF;Tkf@l+O$ABE&B0fi$eebKs_=jJSU|VXh ztEzh*=v|+w`Z1!;`P5g{L>;z}LD&-qzJ%M5T$qVk3L#a~ZS{k%4u&3wNE3)b$b=lQ zK+eD9g@o$ons~fKUQV_ZXrA)d@l*a#hcfR@sp;t{7*Zb`gx-ZYXlM=^T#W+LYx0yp zJ$yqQ2BOAA4+JeBLo6LEdv`bhvrp~o3kRb8fV-JWVG*3*-A&+xJ zd)Vdt3OY-{h2_{xk|Z;xtQJM30NH8#5&5s7Q=Ri(kAHpujQ0Gl7x);px{jWo^VZq{ z%1F-(yj8#F1Je%2)V{&-?-(Ef)#>Im^_6rZ^%daV1Bl+1SyU7toS1L2)7NChu(~fh ziu3g{JuV9f4D$F~W>Fb8^6}7cQz%T>8mQ{Gc^M8hve3w=Ys*t;+&J$>xz9+~moy8i zd9I>JdFK)xpSaBp^Yv+_x?N2&<|Vs>ecU{bjmc7-8=a>>O`gOf3a~qv;f6-Upf3oZ zljULXMLaT@lp1*tP_RJ8u3|@hzSMqmGIO1x2)d@q%%T7ZU&elve`Pc})#)K=ok1fs zx?aa8RN4)lP|In%0or_ujt<447it2<=?J?JfL!p^O@09bS7eNg)vQb1k!ROB#55N9 zrpg&+wBj`8@}jKLtZKFlFwVEK zj%uJb7zv-&} zU|oN>t}it*XPMnS1F89mSD{2)QrJb!B{`kXaMMGl0w&m1X@fpz>i2vR?`KhD3B)Mc zFVo40L#B!-O3NgQLV3V8xkKI`A hJKMe7eGSy Date: Thu, 11 Dec 2014 14:52:05 -0500 Subject: [PATCH 14/43] Oops. Getting rid of pycs --- algorithms/__init__.pyc | Bin 109 -> 0 bytes algorithms/binary_tree.pyc | Bin 3766 -> 0 bytes 2 files changed, 0 insertions(+), 0 deletions(-) delete mode 100644 algorithms/__init__.pyc delete mode 100644 algorithms/binary_tree.pyc diff --git a/algorithms/__init__.pyc b/algorithms/__init__.pyc deleted file mode 100644 index 532c96eaf2863df96ec56811d4dd736c22b579d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 109 zcmZSn%*(a^YG+6?0~9a&w8CT4*3BEkZ2JJR>I;ZQb36yiU=Ygq7@oPL|KR?W55&e2D}6kXTGm`c6RMZXtQ?rR{vCWb=6l@y{&&%SMLA) z^md}^PYd7I(afvpQvA16>ZvD69VvBph_SCW`faRPc7rm_8de_@pP`w%=rW~-9t8~j zTEGyJd`cR&RobQCC!QJxDs9VTN2NQvI$@V;k!SJ9)CBleo~E8TLHV<>qddSP_v>@oBq3 z>2IRLtq+xoa0f^D>d+Blkc~bWW@=Cs8bc>#VQe)?Y+0>{bgo<<$5p)VG@-7Jno~j8 z=gnGVVqad#F5x7(l1%YBa=464Hrfj5i9VX^%mq;KXuGU(yR&cJ-^`1+dK6h0>b>zJ zyNuN+%8T4a5ha@doeH{M*L&U&zhVhi*o6W=)%ORzhv6-m7whaTtLlofEp9lPTJ7Lj zyLPm} z$j}d92r>C$9?`bcxrk^a4*Il`7&@fmLysMrvRrVxL&lD{CZu6s$Sq+99BXLi96GvV z4S#3+v-S5YenRQfCMbP~35UtrCzp@=;rMxp!sTiH}kYOnW|03&dKz{ z%*nFslv*?lLx$l+G;;=>TBf(#p7S=oeF;Tkf@l+O$ABE&B0fi$eebKs_=jJSU|VXh ztEzh*=v|+w`Z1!;`P5g{L>;z}LD&-qzJ%M5T$qVk3L#a~ZS{k%4u&3wNE3)b$b=lQ zK+eD9g@o$ons~fKUQV_ZXrA)d@l*a#hcfR@sp;t{7*Zb`gx-ZYXlM=^T#W+LYx0yp zJ$yqQ2BOAA4+JeBLo6LEdv`bhvrp~o3kRb8fV-JWVG*3*-A&+xJ zd)Vdt3OY-{h2_{xk|Z;xtQJM30NH8#5&5s7Q=Ri(kAHpujQ0Gl7x);px{jWo^VZq{ z%1F-(yj8#F1Je%2)V{&-?-(Ef)#>Im^_6rZ^%daV1Bl+1SyU7toS1L2)7NChu(~fh ziu3g{JuV9f4D$F~W>Fb8^6}7cQz%T>8mQ{Gc^M8hve3w=Ys*t;+&J$>xz9+~moy8i zd9I>JdFK)xpSaBp^Yv+_x?N2&<|Vs>ecU{bjmc7-8=a>>O`gOf3a~qv;f6-Upf3oZ zljULXMLaT@lp1*tP_RJ8u3|@hzSMqmGIO1x2)d@q%%T7ZU&elve`Pc})#)K=ok1fs zx?aa8RN4)lP|In%0or_ujt<447it2<=?J?JfL!p^O@09bS7eNg)vQb1k!ROB#55N9 zrpg&+wBj`8@}jKLtZKFlFwVEK zj%uJb7zv-&} zU|oN>t}it*XPMnS1F89mSD{2)QrJb!B{`kXaMMGl0w&m1X@fpz>i2vR?`KhD3B)Mc zFVo40L#B!-O3NgQLV3V8xkKI`A hJKMe7eGSy Date: Fri, 26 Dec 2014 17:05:43 -0500 Subject: [PATCH 15/43] Fix formatting. --- algorithms/binary_tree.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/algorithms/binary_tree.py b/algorithms/binary_tree.py index 2a8d71d..3fcb17d 100644 --- a/algorithms/binary_tree.py +++ b/algorithms/binary_tree.py @@ -129,7 +129,7 @@ def compare_trees(self, node): else: res = self.right.compare_trees(node.right) return res - + def print_tree(self): """ Print tree content inorder @@ -147,7 +147,7 @@ def tree_data(self): # we use a stack to traverse the tree in a non-recursive way stack = [] node = self - while stack or node: + while stack or node: if node: stack.append(node) node = node.left @@ -167,4 +167,5 @@ def children_count(self): cnt += 1 if self.right: cnt += 1 - return cnt \ No newline at end of file + return cnt + From 89ecbd716b69d7e3504cb0d9c709df9c8167eeaf Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Tue, 16 Jun 2015 21:10:06 -0400 Subject: [PATCH 16/43] Improve unit tests. --- algorithms/tests/test_binary_tree.py | 231 ++++++++++++++++++--------- 1 file changed, 155 insertions(+), 76 deletions(-) diff --git a/algorithms/tests/test_binary_tree.py b/algorithms/tests/test_binary_tree.py index 080ef5f..1deff34 100644 --- a/algorithms/tests/test_binary_tree.py +++ b/algorithms/tests/test_binary_tree.py @@ -1,84 +1,163 @@ +import copy import unittest import algorithms.binary_tree as binary_tree class BinaryTreeTest(unittest.TestCase): - - def test_binary_tree(self): - - data = [10, 5, 15, 4, 7, 13, 17, 11, 14] - # create 2 trees with the same content - root = binary_tree.Node(data[0]) - for i in data[1:]: - root.insert(i) - - root2 = binary_tree.Node(data[0]) - for i in data[1:]: - root2.insert(i) - - # check if both trees are identical - self.assertTrue(root.compare_trees(root2)) - - # check the content of the tree inorder - t = [] - for d in root.tree_data(): - t.append(d) - self.assertEquals(t, [4, 5, 7, 10, 11, 13, 14, 15, 17]) - - # test lookup - node, parent = root.lookup(9) - self.assertTrue(node is None) - # check if returned node and parent are correct - node, parent = root.lookup(11) - self.assertTrue(node.data == 11) - self.assertTrue(parent.data == 13) - - # delete a leaf node - root.delete(4) - # check the content of the tree inorder - t = [] - for d in root.tree_data(): - t.append(d) - self.assertEquals(t, [5, 7, 10, 11, 13, 14, 15, 17]) - - # delete a node with 1 child - root.delete(5) - # check the content of the tree inorder - t = [] - for d in root.tree_data(): - t.append(d) - self.assertEquals(t, [7, 10, 11, 13, 14, 15, 17]) - - # delete a node with 2 children - root.delete(13) - # check the content of the tree inorder - t = [] - for d in root.tree_data(): - t.append(d) - self.assertEquals(t, [7, 10, 11, 14, 15, 17]) - - # delete a node with 2 children - root.delete(15) - # check the content of the tree inorder - t = [] - for d in root.tree_data(): - t.append(d) - self.assertEquals(t, [7, 10, 11, 14, 17]) - - # check for root deletion - root = binary_tree.Node(1) - root.insert(2) - root.insert(0) - root.delete(1) - self.assertEquals(root.data, 2) - root.delete(2) - self.assertEquals(root.data, 0) - root.delete(0) - self.assertEquals(root.data, None) - root.insert(1) - self.assertEquals(root.data, 1) - self.assertEquals(root.left, None) - self.assertEquals(root.right, None) + def setUp(self): + self.root_single_node = binary_tree.Node(None) + self.root = binary_tree.Node(10) + self.root.left = binary_tree.Node(5) + self.root.left.left = binary_tree.Node(3) + self.root.left.right = binary_tree.Node(7) + self.root.right = binary_tree.Node(15) + self.root.right.left = binary_tree.Node(12) + self.root.right.left.left = binary_tree.Node(11) + self.root.right.right = binary_tree.Node(20) + self.root_copy = copy.deepcopy(self.root) + + def test_insert(self): + root = self.root_single_node + + root.insert(10) + self.assertEqual(root.data, 10) + + root.insert(5) + self.assertEqual(root.left.data, 5) + + root.insert(15) + self.assertEqual(root.right.data, 15) + + root.insert(8) + self.assertEqual(root.left.right.data, 8) + + root.insert(2) + self.assertEqual(root.left.left.data, 2) + + root.insert(12) + self.assertEqual(root.right.left.data, 12) + + root.insert(17) + self.assertEqual(root.right.right.data, 17) + + def test_lookup(self): + node, parent = self.root.lookup(0) + self.assertIsNone(parent) + self.assertIsNone(node) + + node, parent = self.root.lookup(13) + self.assertIsNone(parent) + self.assertIsNone(node) + + node, parent = self.root.lookup(7) + self.assertIs(node, self.root.left.right) + self.assertIs(parent, self.root.left) + + def test_delete_root_no_child(self): + self.root_single_node.data = 7 + self.root_single_node.delete(7) + self.assertIsNone(self.root_single_node.data) + + def test_delete_root_one_child(self): + self.root_single_node.data = 7 + self.root_single_node.insert(3) + self.root_single_node.delete(7) + self.assertEqual(self.root_single_node.data, 3) + + def test_delete_one_child_left(self): + self.root.delete(12) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.left.right.data, 7) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 11) + self.assertEqual(self.root.right.right.data, 20) + + def test_delete_one_child_right(self): + self.root.insert(25) + self.root.delete(20) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.left.right.data, 7) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.left.left.data, 11) + self.assertEqual(self.root.right.right.data, 25) + + def test_delete_right_leaf(self): + self.root.delete(7) + self.assertIsNone(self.root.left.right) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.left.left.data, 11) + self.assertEqual(self.root.right.right.data, 20) + + def test_delete_left_leaf(self): + self.root.delete(3) + self.assertIsNone(self.root.left.left) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.right.data, 7) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.left.left.data, 11) + self.assertEqual(self.root.right.right.data, 20) + + def test_delete_right_node_two_childs(self): + self.root.delete(15) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.left.right.data, 7) + self.assertEqual(self.root.right.data, 20) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.left.left.data, 11) + + def test_delete_left_node_two_childs(self): + self.root.delete(5) + self.assertEqual(self.root.left.data, 7) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.left.left.data, 11) + self.assertEqual(self.root.right.right.data, 20) + + def test_delete_root_two_childs(self): + self.root.delete(10) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.left.right.data, 7) + self.assertEqual(self.root.data, 11) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.right.data, 20) + + def test_compare_trees_left_leaf_missing(self): + self.root_copy.delete(11) + self.assertFalse(self.root.compare_trees(self.root_copy)) + + def test_compare_trees_right_leaf_missing(self): + self.root_copy.delete(20) + self.assertFalse(self.root.compare_trees(self.root_copy)) + + def test_compare_trees_diff_value(self): + self.root_copy.left.data = 16 + self.assertFalse(self.root.compare_trees(self.root_copy)) + + def test_compare_trees_extra_right_leaf(self): + self.root_copy.insert(25) + self.assertFalse(self.root.compare_trees(self.root_copy)) + + def test_compare_trees_extra_left_leaf(self): + self.root_copy.insert(18) + self.assertFalse(self.root.compare_trees(self.root_copy)) + + def test_print_tree(self): + self.root.print_tree() + + def test_tree_data(self): + self.assertEqual([e for e in self.root.tree_data()], + [3, 5, 7, 10, 11, 12, 15, 20]) if __name__ == '__main__': unittest.main() From a09db6fc1e76edfc911289e0c0371861ef6feaf0 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Tue, 16 Jun 2015 21:10:20 -0400 Subject: [PATCH 17/43] Coverage script for all modules. --- coverage.sh | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 coverage.sh diff --git a/coverage.sh b/coverage.sh new file mode 100644 index 0000000..9cb047a --- /dev/null +++ b/coverage.sh @@ -0,0 +1,2 @@ +coverage run algorithms/tests/test_binary_tree.py +coverage report -m From 71a354d020bc7ecba285c9abb92016cb00673e89 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Tue, 10 Nov 2015 11:37:49 -0500 Subject: [PATCH 18/43] Doc strings and unit tests. --- algorithms/a_star_path_finding.py | 63 +++++++++++++------- algorithms/tests/test_a_star_path_finding.py | 35 +++++++++++ 2 files changed, 77 insertions(+), 21 deletions(-) create mode 100644 algorithms/tests/test_a_star_path_finding.py diff --git a/algorithms/a_star_path_finding.py b/algorithms/a_star_path_finding.py index 8e5b81d..22e6567 100644 --- a/algorithms/a_star_path_finding.py +++ b/algorithms/a_star_path_finding.py @@ -5,9 +5,13 @@ def __init__(self, x, y, reachable): """ Initialize new cell + @param reachable is cell reachable? not a wall? @param x cell x coordinate @param y cell y coordinate - @param reachable is cell reachable? not a wall? + @param g cost to move from the starting cell to this cell. + @param h estimation of the cost to move from this cell + to the ending cell. + @param f f = g + h """ self.reachable = reachable self.x = x @@ -19,16 +23,28 @@ def __init__(self, x, y, reachable): class AStar(object): def __init__(self): + # open list self.opened = [] heapq.heapify(self.opened) + # visited cells list self.closed = set() + # grid cells self.cells = [] - self.grid_height = 6 - self.grid_width = 6 + self.grid_height = None + self.grid_width = None - def init_grid(self): - walls = ((0, 5), (1, 0), (1, 1), (1, 5), (2, 3), - (3, 1), (3, 2), (3, 5), (4, 1), (4, 4), (5, 1)) + def init_grid(self, width, height, walls, start, end): + """ + Prepare grid cells, walls. + + @param width grid's width. + @param height grid's height. + @param walls list of wall x,y tuples. + @param start grid starting point x,y tuple. + @param end grid ending point x,y tuple. + """ + self.grid_height = height + self.grid_width = width for x in range(self.grid_width): for y in range(self.grid_height): if (x, y) in walls: @@ -36,15 +52,14 @@ def init_grid(self): else: reachable = True self.cells.append(Cell(x, y, reachable)) - self.start = self.get_cell(0, 0) - self.end = self.get_cell(5, 5) + self.start = self.get_cell(*start) + self.end = self.get_cell(*end) def get_heuristic(self, cell): """ Compute the heuristic value H for a cell: distance between this cell and the ending cell multiply by 10. - @param cell @returns heuristic value H """ return 10 * (abs(cell.x - self.end.x) + abs(cell.y - self.end.y)) @@ -65,7 +80,7 @@ def get_adjacent_cells(self, cell): from the one on the right. @param cell get adjacent cells for this cell - @returns adjacent cells list + @returns adjacent cells list. """ cells = [] if cell.x < self.grid_width-1: @@ -78,11 +93,16 @@ def get_adjacent_cells(self, cell): cells.append(self.get_cell(cell.x, cell.y+1)) return cells - def display_path(self): + def get_path(self): cell = self.end + path = [(cell.x, cell.y)] while cell.parent is not self.start: cell = cell.parent - print 'path: cell: %d,%d' % (cell.x, cell.y) + path.append((cell.x, cell.y)) + + path.append((self.start.x, self.start.y)) + path.reverse() + return path def compare(self, cell1, cell2): """ @@ -97,7 +117,7 @@ def compare(self, cell1, cell2): elif cell1.f > cell2.f: return 1 return 0 - + def update_cell(self, adj, cell): """ Update adjacent cell @@ -110,18 +130,22 @@ def update_cell(self, adj, cell): adj.parent = cell adj.f = adj.h + adj.g - def process(self): + def solve(self): + """ + Solve maze, find path to ending cell. + + @returns path or None if not found. + """ # add starting cell to open heap queue heapq.heappush(self.opened, (self.start.f, self.start)) while len(self.opened): - # pop cell from heap queue + # pop cell from heap queue f, cell = heapq.heappop(self.opened) # add cell to closed list so we don't process it twice self.closed.add(cell) - # if ending cell, display found path + # if ending cell, return found path if cell is self.end: - self.display_path() - break + return self.get_path() # get adjacent cells for cell adj_cells = self.get_adjacent_cells(cell) for adj_cell in adj_cells: @@ -137,7 +161,4 @@ def process(self): # add adj cell to open list heapq.heappush(self.opened, (adj_cell.f, adj_cell)) -a = AStar() -a.init_grid() -a.process() diff --git a/algorithms/tests/test_a_star_path_finding.py b/algorithms/tests/test_a_star_path_finding.py new file mode 100644 index 0000000..1f86183 --- /dev/null +++ b/algorithms/tests/test_a_star_path_finding.py @@ -0,0 +1,35 @@ +import unittest +import algorithms.a_star_path_finding as pf + +class Test(unittest.TestCase): + + def setUp(self): + pass + + def test_maze(self): + a = pf.AStar() + walls = ((0, 5), (1, 0), (1, 1), (1, 5), (2, 3), + (3, 1), (3, 2), (3, 5), (4, 1), (4, 4), (5, 1)) + a.init_grid(6, 6, walls, (0, 0), (5, 5)) + path = a.solve() + self.assertEqual(path, [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 4), + (2, 4), (3, 4), (3, 3), (4, 3), (5, 3), (5, 4), + (5, 5)]) + + def test_maze_no_walls(self): + a = pf.AStar() + walls = () + a.init_grid(6, 6, walls, (0, 0), (5, 5)) + path = a.solve() + self.assertEqual(len(path), 11) + + def test_maze_no_solution(self): + a = pf.AStar() + walls = ((0, 5), (1, 0), (1, 1), (1, 2), (1, 3), (1, 4), (1, 5), + (2, 3), (3, 1), (3, 2), (3, 5), (4, 1), (4, 4), (5, 1)) + a.init_grid(6, 6, walls, (0, 0), (5, 5)) + self.assertIsNone(a.solve()) + +if __name__ == '__main__': + unittest.main() + From f921adf9fa48f9aa06b47730f71686a45bca00dc Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Tue, 10 Nov 2015 11:39:01 -0500 Subject: [PATCH 19/43] Update. --- README | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/README b/README index 5711b77..80a8c77 100644 --- a/README +++ b/README @@ -4,14 +4,10 @@ ### Description The purpose of this library is to help you with common algorithms like: -String matching - - Naive - - Rabin-Karp - - Knuth-Morris-Pratt - - Boyer-Moore-Horspool +A* path finding. Binary tree - - node and tree class + - node class - lookup - insert - delete @@ -19,6 +15,12 @@ Binary tree - print tree - tree inorder generator +String matching + - Naive + - Rabin-Karp + - Knuth-Morris-Pratt + - Boyer-Moore-Horspool + ### Installation Get the source and run From dbd22bcdc51d12c71eb46fae5434c8ea9e631f2b Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Tue, 10 Nov 2015 13:39:19 -0500 Subject: [PATCH 20/43] Unit tests. --- algorithms/a_star_path_finding.py | 14 -------------- algorithms/generators.py | 5 ++++- algorithms/permutations.py | 6 +----- algorithms/tests/all_tests.py | 20 ++++++++++++++++++++ algorithms/tests/coverage.sh | 2 ++ algorithms/tests/test_generators.py | 20 ++++++++++++++++++++ algorithms/tests/test_list.py | 17 +++++++++++++++++ algorithms/tests/test_permutations.py | 21 +++++++++++++++++++++ algorithms/tests/test_string_matching.py | 4 ++-- 9 files changed, 87 insertions(+), 22 deletions(-) create mode 100644 algorithms/tests/all_tests.py create mode 100644 algorithms/tests/coverage.sh create mode 100644 algorithms/tests/test_generators.py create mode 100644 algorithms/tests/test_list.py create mode 100644 algorithms/tests/test_permutations.py diff --git a/algorithms/a_star_path_finding.py b/algorithms/a_star_path_finding.py index 22e6567..eab7ac9 100644 --- a/algorithms/a_star_path_finding.py +++ b/algorithms/a_star_path_finding.py @@ -104,20 +104,6 @@ def get_path(self): path.reverse() return path - def compare(self, cell1, cell2): - """ - Compare 2 cells F values - - @param cell1 1st cell - @param cell2 2nd cell - @returns -1, 0 or 1 if lower, equal or greater - """ - if cell1.f < cell2.f: - return -1 - elif cell1.f > cell2.f: - return 1 - return 0 - def update_cell(self, adj, cell): """ Update adjacent cell diff --git a/algorithms/generators.py b/algorithms/generators.py index c8de4cf..f3bf608 100644 --- a/algorithms/generators.py +++ b/algorithms/generators.py @@ -5,9 +5,12 @@ def fib(n): Example: for i in fib(5): print i @param n fib range upper bound """ + if not n: + return a, b = 0, 1 + yield a i = 0 - while i < n: + while i < n - 1: yield b a, b = b, a+b i += 1 diff --git a/algorithms/permutations.py b/algorithms/permutations.py index 49d27ad..6288471 100644 --- a/algorithms/permutations.py +++ b/algorithms/permutations.py @@ -1,7 +1,7 @@ def permutations(l): """ Generator for list permutations - + @param l list to generate permutations for @result yield each permutation @@ -25,7 +25,3 @@ def permutations(l): for p in permutations(l): for i in range(len(p)+1): yield p[:i] + a + p[i:] - -for p in permutations([1,2,3]): - print p - diff --git a/algorithms/tests/all_tests.py b/algorithms/tests/all_tests.py new file mode 100644 index 0000000..f4228cf --- /dev/null +++ b/algorithms/tests/all_tests.py @@ -0,0 +1,20 @@ +"""Run all of the tests.""" +import os +import sys +import unittest2 as unittest + +def main(args=None): + unittest_dir = '.' + unittest_suite = unittest.defaultTestLoader.discover(unittest_dir) + + kwargs = {} + if args and '-v' in args: + kwargs['verbosity'] = 2 + runner = unittest.TextTestRunner(sys.stdout, "Unittests", + **kwargs) + results = runner.run(unittest_suite) + return results.wasSuccessful() + +if __name__ == '__main__': + status = main(sys.argv[1:]) + sys.exit(int(not status)) diff --git a/algorithms/tests/coverage.sh b/algorithms/tests/coverage.sh new file mode 100644 index 0000000..a69b3be --- /dev/null +++ b/algorithms/tests/coverage.sh @@ -0,0 +1,2 @@ +coverage run --source=$PYTHONPATH/algorithms --omit=$PYTHONPATH/algorithms/tests/* all_tests.py +coverage report --omit=$PYTHONPATH/algorithms/tests/* -m diff --git a/algorithms/tests/test_generators.py b/algorithms/tests/test_generators.py new file mode 100644 index 0000000..bc2e3b8 --- /dev/null +++ b/algorithms/tests/test_generators.py @@ -0,0 +1,20 @@ +import unittest +import algorithms.generators as generators + +class GeneratorsTest(unittest.TestCase): + + def setUp(self): + pass + + def test_fib(self): + fib = [e for e in generators.fib(10)] + self.assertEqual(fib, [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]) + + def test_fib_empty(self): + fib = [e for e in generators.fib(0)] + self.assertEqual(fib, []) + + +if __name__ == '__main__': + unittest.main() + diff --git a/algorithms/tests/test_list.py b/algorithms/tests/test_list.py new file mode 100644 index 0000000..dd869eb --- /dev/null +++ b/algorithms/tests/test_list.py @@ -0,0 +1,17 @@ +import unittest +import algorithms.list as list + +class List(unittest.TestCase): + + def setUp(self): + pass + + def test_list(self): + bounds, m = [e for e in list.find_max_sub([-2, 3, -4, 5, 1, -5])] + self.assertEqual(bounds, (3, 4)) + self.assertEqual(m, 6) + + +if __name__ == '__main__': + unittest.main() + diff --git a/algorithms/tests/test_permutations.py b/algorithms/tests/test_permutations.py new file mode 100644 index 0000000..f757f1d --- /dev/null +++ b/algorithms/tests/test_permutations.py @@ -0,0 +1,21 @@ +import unittest +import algorithms.permutations as permutations + +class GeneratorsTest(unittest.TestCase): + + def setUp(self): + pass + + def test_permutations(self): + p = [e for e in permutations.permutations([1, 2, 3])] + self.assertEqual(p, [[1, 2, 3], [2, 1, 3], [2, 3, 1], [1, 3, 2], + [3, 1, 2], [3, 2, 1]]) + + def test_permutations_single(self): + p = [e for e in permutations.permutations([1])] + self.assertEqual(p, [[1]]) + + +if __name__ == '__main__': + unittest.main() + diff --git a/algorithms/tests/test_string_matching.py b/algorithms/tests/test_string_matching.py index 4b7d11d..6758b50 100644 --- a/algorithms/tests/test_string_matching.py +++ b/algorithms/tests/test_string_matching.py @@ -1,8 +1,8 @@ import unittest -import string_matching +import algorithms.string_matching as string_matching class StringMatchingTest(unittest.TestCase): - + def test_string_matching_naive(self): t = 'ababbababa' s = 'aba' From 7d263dfbc3fb7c71f6b6fc91e2ad90b19a421e62 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Tue, 10 Nov 2015 13:40:42 -0500 Subject: [PATCH 21/43] Unused. --- coverage.sh | 2 -- 1 file changed, 2 deletions(-) delete mode 100644 coverage.sh diff --git a/coverage.sh b/coverage.sh deleted file mode 100644 index 9cb047a..0000000 --- a/coverage.sh +++ /dev/null @@ -1,2 +0,0 @@ -coverage run algorithms/tests/test_binary_tree.py -coverage report -m From 88c4c61593e04e96a565e7e7beef06006ebf536a Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Mon, 16 Nov 2015 09:39:45 -0500 Subject: [PATCH 22/43] Find integer in sorted list. --- README => README.md | 7 +++++++ algorithms/list.py | 25 +++++++++++++++++++++++++ algorithms/tests/test_list.py | 21 ++++++++++++++++++++- 3 files changed, 52 insertions(+), 1 deletion(-) rename README => README.md (83%) diff --git a/README b/README.md similarity index 83% rename from README rename to README.md index 80a8c77..d444a79 100644 --- a/README +++ b/README.md @@ -21,6 +21,13 @@ String matching - Knuth-Morris-Pratt - Boyer-Moore-Horspool +Generators + - Permutations. + +Lists + - Subset with highest sum. + - Find integer in sorted list. + ### Installation Get the source and run diff --git a/algorithms/list.py b/algorithms/list.py index 5a8a657..ac02d9d 100644 --- a/algorithms/list.py +++ b/algorithms/list.py @@ -1,3 +1,28 @@ +def find_int(i, l): + """ + Find integer in a sorted list. + + Example: 4 in [1, 3, 4, 6, 7, 9] -> 2 + @param i integer to find. + @param l sorted list. + @returns index if found, None if not. + """ + if l: + p_idx = len(l) / 2 + p = l[p_idx] + if i == p: + return p_idx + elif len(l) == 1: + return + elif i < p: + res = find_int(i, l[:p_idx]) + if res: + return res + elif i > p: + res = find_int(i, l[p_idx:]) + if res: + return res + p_idx + def find_max_sub(l): """ Find subset with higest sum diff --git a/algorithms/tests/test_list.py b/algorithms/tests/test_list.py index dd869eb..27f249c 100644 --- a/algorithms/tests/test_list.py +++ b/algorithms/tests/test_list.py @@ -6,11 +6,30 @@ class List(unittest.TestCase): def setUp(self): pass - def test_list(self): + def test_find_max_sub(self): bounds, m = [e for e in list.find_max_sub([-2, 3, -4, 5, 1, -5])] self.assertEqual(bounds, (3, 4)) self.assertEqual(m, 6) + def test_find_int_first_half(self): + idx = list.find_int(4, [1, 2, 4, 5, 7, 9]) + self.assertEqual(idx, 2) + + def test_find_int_second_half(self): + idx = list.find_int(7, [1, 2, 4, 5, 7, 9]) + self.assertEqual(idx, 4) + + def test_find_int_not_found(self): + idx = list.find_int(3, [1, 2, 4, 5, 7, 9]) + self.assertIsNone(idx) + + def test_find_int_single_element_list(self): + idx = list.find_int(3, [3,]) + self.assertEqual(idx, 0) + + def test_find_int_empty_list(self): + idx = list.find_int(3, []) + self.assertIsNone(idx) if __name__ == '__main__': unittest.main() From 0a2fa2375be5e4a9f03d1fe4e39614b4f530b1a0 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Fri, 20 Nov 2015 15:55:28 -0500 Subject: [PATCH 23/43] PEP8. string atoi added. --- algorithms/a_star_path_finding.py | 31 +++---- algorithms/binary_tree.py | 43 ++++----- algorithms/generators.py | 4 +- algorithms/list.py | 7 +- algorithms/maze.py | 33 ------- .../performance_string_matching.py | 41 --------- algorithms/permutations.py | 3 +- algorithms/{string_matching.py => string.py} | 88 ++++++++++--------- algorithms/tests/all_tests.py | 2 +- algorithms/tests/test_a_star_path_finding.py | 7 +- algorithms/tests/test_binary_tree.py | 7 +- algorithms/tests/test_generators.py | 5 +- algorithms/tests/test_list.py | 7 +- algorithms/tests/test_permutations.py | 5 +- algorithms/tests/test_string.py | 50 +++++++++++ algorithms/tests/test_string_matching.py | 40 --------- pep8.sh | 2 + 17 files changed, 156 insertions(+), 219 deletions(-) delete mode 100644 algorithms/performance/performance_string_matching.py rename algorithms/{string_matching.py => string.py} (66%) create mode 100644 algorithms/tests/test_string.py delete mode 100644 algorithms/tests/test_string_matching.py create mode 100755 pep8.sh diff --git a/algorithms/a_star_path_finding.py b/algorithms/a_star_path_finding.py index eab7ac9..e239247 100644 --- a/algorithms/a_star_path_finding.py +++ b/algorithms/a_star_path_finding.py @@ -1,9 +1,9 @@ import heapq + class Cell(object): def __init__(self, x, y, reachable): - """ - Initialize new cell + """Initialize new cell. @param reachable is cell reachable? not a wall? @param x cell x coordinate @@ -21,6 +21,7 @@ def __init__(self, x, y, reachable): self.h = 0 self.f = 0 + class AStar(object): def __init__(self): # open list @@ -34,8 +35,7 @@ def __init__(self): self.grid_width = None def init_grid(self, width, height, walls, start, end): - """ - Prepare grid cells, walls. + """Prepare grid cells, walls. @param width grid's width. @param height grid's height. @@ -56,17 +56,16 @@ def init_grid(self, width, height, walls, start, end): self.end = self.get_cell(*end) def get_heuristic(self, cell): - """ - Compute the heuristic value H for a cell: distance between - this cell and the ending cell multiply by 10. + """Compute the heuristic value H for a cell. + + Distance between this cell and the ending cell multiply by 10. @returns heuristic value H """ return 10 * (abs(cell.x - self.end.x) + abs(cell.y - self.end.y)) def get_cell(self, x, y): - """ - Returns a cell from the cells list + """Returns a cell from the cells list. @param x cell x coordinate @param y cell y coordinate @@ -75,9 +74,9 @@ def get_cell(self, x, y): return self.cells[x * self.grid_height + y] def get_adjacent_cells(self, cell): - """ - Returns adjacent cells to a cell. Clockwise starting - from the one on the right. + """Returns adjacent cells to a cell. + + Clockwise starting from the one on the right. @param cell get adjacent cells for this cell @returns adjacent cells list. @@ -105,8 +104,7 @@ def get_path(self): return path def update_cell(self, adj, cell): - """ - Update adjacent cell + """Update adjacent cell. @param adj adjacent cell to current cell @param cell current cell being processed @@ -117,8 +115,7 @@ def update_cell(self, adj, cell): adj.f = adj.h + adj.g def solve(self): - """ - Solve maze, find path to ending cell. + """Solve maze, find path to ending cell. @returns path or None if not found. """ @@ -146,5 +143,3 @@ def solve(self): self.update_cell(adj_cell, cell) # add adj cell to open list heapq.heappush(self.opened, (adj_cell.f, adj_cell)) - - diff --git a/algorithms/binary_tree.py b/algorithms/binary_tree.py index 3fcb17d..3e8c20b 100644 --- a/algorithms/binary_tree.py +++ b/algorithms/binary_tree.py @@ -1,10 +1,12 @@ -class Node: - """ - Tree node: left and right child + data which can be any object +from __future__ import print_function + + +class Node(object): + """Tree node: left and right child + data which can be any object + """ def __init__(self, data): - """ - Node constructor + """Node constructor @param data node data object """ @@ -13,8 +15,7 @@ def __init__(self, data): self.data = data def insert(self, data): - """ - Insert new node with data + """Insert new node with data @param data node data object to insert """ @@ -33,8 +34,7 @@ def insert(self, data): self.data = data def lookup(self, data, parent=None): - """ - Lookup node containing data + """Lookup node containing data @param data node data object to look up @param parent node's parent @@ -52,8 +52,7 @@ def lookup(self, data, parent=None): return self, parent def delete(self, data): - """ - Delete node containing data + """Delete node containing data @param data node's content to delete """ @@ -68,7 +67,6 @@ def delete(self, data): parent.left = None else: parent.right = None - del node else: self.data = None elif children_count == 1: @@ -83,7 +81,6 @@ def delete(self, data): parent.left = n else: parent.right = n - del node else: self.left = n.left self.right = n.right @@ -105,8 +102,7 @@ def delete(self, data): parent.right = successor.right def compare_trees(self, node): - """ - Compare 2 trees + """Compare 2 trees @param node tree to compare @returns True if the tree passed is identical to this tree @@ -131,18 +127,18 @@ def compare_trees(self, node): return res def print_tree(self): - """ - Print tree content inorder + """Print tree content inorder + """ if self.left: self.left.print_tree() - print self.data, + print(self.data, end=" ") if self.right: self.right.print_tree() def tree_data(self): - """ - Generator to get the tree nodes data + """Generator to get the tree nodes data + """ # we use a stack to traverse the tree in a non-recursive way stack = [] @@ -151,14 +147,14 @@ def tree_data(self): if node: stack.append(node) node = node.left - else: # we are returning so we pop the node and we yield it + else: + # we are returning so we pop the node and we yield it node = stack.pop() yield node.data node = node.right def children_count(self): - """ - Return the number of children + """Return the number of children @returns number of children: 0, 1, 2 """ @@ -168,4 +164,3 @@ def children_count(self): if self.right: cnt += 1 return cnt - diff --git a/algorithms/generators.py b/algorithms/generators.py index f3bf608..0a55e7a 100644 --- a/algorithms/generators.py +++ b/algorithms/generators.py @@ -1,6 +1,5 @@ def fib(n): - """ - Generator for Fibonacci serie + """Generator for Fibonacci serie. Example: for i in fib(5): print i @param n fib range upper bound @@ -14,4 +13,3 @@ def fib(n): yield b a, b = b, a+b i += 1 - diff --git a/algorithms/list.py b/algorithms/list.py index ac02d9d..ecdb036 100644 --- a/algorithms/list.py +++ b/algorithms/list.py @@ -1,6 +1,5 @@ def find_int(i, l): - """ - Find integer in a sorted list. + """Find integer in a sorted list. Example: 4 in [1, 3, 4, 6, 7, 9] -> 2 @param i integer to find. @@ -23,9 +22,9 @@ def find_int(i, l): if res: return res + p_idx + def find_max_sub(l): - """ - Find subset with higest sum + """Find subset with higest sum. Example: [-2, 3, -4, 5, 1, -5] -> (3,4), 6 @param l list diff --git a/algorithms/maze.py b/algorithms/maze.py index 57a7128..e69de29 100644 --- a/algorithms/maze.py +++ b/algorithms/maze.py @@ -1,33 +0,0 @@ -grid = [[0, 0, 0, 0, 0, 1], - [1, 1, 0, 0, 0, 1], - [0, 0, 0, 1, 0, 0], - [0, 1, 1, 0, 0, 1], - [0, 1, 0, 0, 1, 0], - [0, 1, 0, 0, 0, 2] - ] - -def search(x, y): - if grid[x][y] == 2: - print 'found at %d,%d' % (x, y) - return True - elif grid[x][y] == 1: - print 'wall at %d,%d' % (x, y) - return False - elif grid[x][y] == 3: - print 'visited at %d,%d' % (x, y) - return False - - print 'visiting %d,%d' % (x, y) - - # mark as visited - grid[x][y] = 3 - if ((x < len(grid)-1 and search(x+1, y)) - or (y > 0 and search(x, y-1)) - or (x > 0 and search(x-1, y)) - or (y < len(grid)-1 and search(x, y+1))): - return True - - return False - -search(0, 0) - diff --git a/algorithms/performance/performance_string_matching.py b/algorithms/performance/performance_string_matching.py deleted file mode 100644 index 6642df8..0000000 --- a/algorithms/performance/performance_string_matching.py +++ /dev/null @@ -1,41 +0,0 @@ -import time -import string_matching - -class StringMatchingPerformance: - - def __init__(self): - pass - - def calculate_performance(self): - t = 'ababbababa' - s = 'aba' - times = 1000 - - ts = time.time() - for i in range(times): - string_matching.string_matching_naive(t, s) - t1 = time.time() - ts - print 'string_matching_naive: %.2f seconds' % t1 - - ts = time.time() - for i in range(times): - string_matching.string_matching_rabin_karp(t, s) - t2 = time.time() - ts - print 'string_matching_rabin_karp: %.2f seconds' % t2 - - ts = time.time() - for i in range(times): - string_matching.string_matching_knuth_morris_pratt(t, s) - t2 = time.time() - ts - print 'string_matching_knuth_morris_pratt: %.2f seconds' % t2 - - ts = time.time() - for i in range(times): - string_matching.string_matching_boyer_moore_horspool(t, s) - t2 = time.time() - ts - print 'string_matching_boyer_moore_horspool: %.2f seconds' % t2 - -if __name__ == '__main__': - p = StringMatchingPerformance() - p.calculate_performance() - diff --git a/algorithms/permutations.py b/algorithms/permutations.py index 6288471..0c789b1 100644 --- a/algorithms/permutations.py +++ b/algorithms/permutations.py @@ -1,6 +1,5 @@ def permutations(l): - """ - Generator for list permutations + """Generator for list permutations. @param l list to generate permutations for @result yield each permutation diff --git a/algorithms/string_matching.py b/algorithms/string.py similarity index 66% rename from algorithms/string_matching.py rename to algorithms/string.py index d2387d0..2ce5682 100644 --- a/algorithms/string_matching.py +++ b/algorithms/string.py @@ -1,10 +1,5 @@ -""" -Filename: string_matching.py -""" - def string_matching_naive(text='', pattern=''): - """ - Returns positions where pattern is found in text + """Returns positions where pattern is found in text. We slide the string to match 'pattern' over the text @@ -27,28 +22,18 @@ def string_matching_naive(text='', pattern=''): def string_matching_rabin_karp(text='', pattern='', hash_base=256): - """ - Returns positions where pattern is found in text - - We calculate the hash value of the pattern and we compare it to the hash - value of text[i:i+m] for i = 0..n-m - The nice thing is that we don't need to calculate the hash value of - text[i:i+m] each time from scratch, we know that: - h(text[i+1:i+m+1]) = (base * (h(text[i:i+m]) - (text[i] * (base ^ (m-1))))) + text[i+m] - We can get h('bcd') from h('abc'). - h('bcd') = (base * (h('abc') - ('a' * (base ^ 2)))) + 'd' - + """Returns positions where pattern is found in text. + worst case: O(nm) - we can expect O(n+m) if the number of valid matches is small and the pattern - large - + O(n+m) if the number of valid matches is small and the pattern is large. + Performance: ord() is slow so we shouldn't use it here Example: text = 'ababbababa', pattern = 'aba' - string_matching_rabin_karp(text, pattern) returns [0, 5, 7] + string_matching_rabin_karp(text, pattern) returns [0, 5, 7] @param text text to search inside @param pattern string to search for - @param hash_base base to calculate the hash value + @param hash_base base to calculate the hash value @return list containing offsets (shifts) where pattern is found inside text """ @@ -59,16 +44,19 @@ def string_matching_rabin_karp(text='', pattern='', hash_base=256): hpattern = hash_value(pattern, hash_base) for i in range(n-m+1): if htext == hpattern: - if text[i:i+m] == pattern: + if text[i:i+m] == pattern: offsets.append(i) if i < n-m: - htext = (hash_base * (htext - (ord(text[i]) * (hash_base ** (m-1))))) + ord(text[i+m]) + htext = (hash_base * + (htext - + (ord(text[i]) * + (hash_base ** (m-1))))) + ord(text[i+m]) return offsets + def hash_value(s, base): - """ - Calculate the hash value of a string using base + """Calculate the hash value of a string using base. Example: 'abc' = 97 x base^2 + 98 x base^1 + 99 x base^0 @param s string to compute hash value for @@ -83,20 +71,17 @@ def hash_value(s, base): return v + def string_matching_knuth_morris_pratt(text='', pattern=''): - """ - Returns positions where pattern is found in text + """Returns positions where pattern is found in text. - See http://jboxer.com/2009/12/the-knuth-morris-pratt-algorithm-in-my-own-words/ for a great explanation on how this algorithm works. - O(m+n) Example: text = 'ababbababa', pattern = 'aba' - string_matching_knuth_morris_pratt(text, pattern) returns [0, 5, 7] + string_matching_knuth_morris_pratt(text, pattern) returns [0, 5, 7] @param text text to search inside @param pattern string to search for @return list containing offsets (shifts) where pattern is found inside text """ - n = len(text) m = len(pattern) offsets = [] @@ -113,6 +98,7 @@ def string_matching_knuth_morris_pratt(text='', pattern=''): return offsets + def compute_prefix_function(p): m = len(p) pi = [0] * m @@ -125,23 +111,19 @@ def compute_prefix_function(p): pi[q] = k return pi + def string_matching_boyer_moore_horspool(text='', pattern=''): - """ - Returns positions where pattern is found in text + """Returns positions where pattern is found in text. - See http://en.wikipedia.org/wiki/Boyer%E2%80%93Moore%E2%80%93Horspool_algorithm for an explanation on how - this algorithm works. - O(n) Performance: ord() is slow so we shouldn't use it here Example: text = 'ababbababa', pattern = 'aba' - string_matching_boyer_moore_horspool(text, pattern) returns [0, 5, 7] + string_matching_boyer_moore_horspool(text, pattern) returns [0, 5, 7] @param text text to search inside @param pattern string to search for @return list containing offsets (shifts) where pattern is found inside text """ - m = len(pattern) n = len(text) offsets = [] @@ -155,7 +137,8 @@ def string_matching_boyer_moore_horspool(text='', pattern=''): skip = tuple(skip) k = m - 1 while k < n: - j = m - 1; i = k + j = m - 1 + i = k while j >= 0 and text[i] == pattern[j]: j -= 1 i -= 1 @@ -165,3 +148,28 @@ def string_matching_boyer_moore_horspool(text='', pattern=''): return offsets + +def atoi(s): + """Convert string to integer without doing int(s). + + '123' -> 123 + @param s string to convert. + @returns integer + """ + if not s: + raise ValueError + i = 0 + idx = 0 + neg = False + if s[0] == '-': + neg = True + idx += 1 + + for c in s[idx:]: + i *= 10 + i += int(c) + + if neg: + i = -i + + return i diff --git a/algorithms/tests/all_tests.py b/algorithms/tests/all_tests.py index f4228cf..ba9bc3b 100644 --- a/algorithms/tests/all_tests.py +++ b/algorithms/tests/all_tests.py @@ -1,8 +1,8 @@ """Run all of the tests.""" -import os import sys import unittest2 as unittest + def main(args=None): unittest_dir = '.' unittest_suite = unittest.defaultTestLoader.discover(unittest_dir) diff --git a/algorithms/tests/test_a_star_path_finding.py b/algorithms/tests/test_a_star_path_finding.py index 1f86183..e95b7eb 100644 --- a/algorithms/tests/test_a_star_path_finding.py +++ b/algorithms/tests/test_a_star_path_finding.py @@ -1,6 +1,8 @@ -import unittest import algorithms.a_star_path_finding as pf +import unittest + + class Test(unittest.TestCase): def setUp(self): @@ -31,5 +33,4 @@ def test_maze_no_solution(self): self.assertIsNone(a.solve()) if __name__ == '__main__': - unittest.main() - + unittest.main() diff --git a/algorithms/tests/test_binary_tree.py b/algorithms/tests/test_binary_tree.py index 1deff34..0ec3397 100644 --- a/algorithms/tests/test_binary_tree.py +++ b/algorithms/tests/test_binary_tree.py @@ -1,7 +1,9 @@ import copy import unittest + import algorithms.binary_tree as binary_tree + class BinaryTreeTest(unittest.TestCase): def setUp(self): @@ -157,8 +159,7 @@ def test_print_tree(self): def test_tree_data(self): self.assertEqual([e for e in self.root.tree_data()], - [3, 5, 7, 10, 11, 12, 15, 20]) + [3, 5, 7, 10, 11, 12, 15, 20]) if __name__ == '__main__': - unittest.main() - + unittest.main() diff --git a/algorithms/tests/test_generators.py b/algorithms/tests/test_generators.py index bc2e3b8..7670340 100644 --- a/algorithms/tests/test_generators.py +++ b/algorithms/tests/test_generators.py @@ -1,6 +1,8 @@ import unittest + import algorithms.generators as generators + class GeneratorsTest(unittest.TestCase): def setUp(self): @@ -16,5 +18,4 @@ def test_fib_empty(self): if __name__ == '__main__': - unittest.main() - + unittest.main() diff --git a/algorithms/tests/test_list.py b/algorithms/tests/test_list.py index 27f249c..6960823 100644 --- a/algorithms/tests/test_list.py +++ b/algorithms/tests/test_list.py @@ -1,6 +1,8 @@ import unittest + import algorithms.list as list + class List(unittest.TestCase): def setUp(self): @@ -24,7 +26,7 @@ def test_find_int_not_found(self): self.assertIsNone(idx) def test_find_int_single_element_list(self): - idx = list.find_int(3, [3,]) + idx = list.find_int(3, [3, ]) self.assertEqual(idx, 0) def test_find_int_empty_list(self): @@ -32,5 +34,4 @@ def test_find_int_empty_list(self): self.assertIsNone(idx) if __name__ == '__main__': - unittest.main() - + unittest.main() diff --git a/algorithms/tests/test_permutations.py b/algorithms/tests/test_permutations.py index f757f1d..167a244 100644 --- a/algorithms/tests/test_permutations.py +++ b/algorithms/tests/test_permutations.py @@ -1,6 +1,8 @@ import unittest + import algorithms.permutations as permutations + class GeneratorsTest(unittest.TestCase): def setUp(self): @@ -17,5 +19,4 @@ def test_permutations_single(self): if __name__ == '__main__': - unittest.main() - + unittest.main() diff --git a/algorithms/tests/test_string.py b/algorithms/tests/test_string.py new file mode 100644 index 0000000..60fab25 --- /dev/null +++ b/algorithms/tests/test_string.py @@ -0,0 +1,50 @@ +import unittest + +import algorithms.string as string + + +class StringTest(unittest.TestCase): + + def test_atoi(self): + self.assertEqual(string.atoi('123'), 123) + + def test_atoi_neg(self): + self.assertEqual(string.atoi('-123'), -123) + + def test_string_matching_naive(self): + t = 'ababbababa' + s = 'aba' + self.assertEqual(string.string_matching_naive(t, s), [0, 5, 7]) + t = 'ababbababa' + s = 'abbb' + self.assertEqual(string.string_matching_naive(t, s), []) + + def test_string_matching_rabin_karp(self): + t = 'ababbababa' + s = 'aba' + self.assertEqual(string.string_matching_rabin_karp(t, s), [0, 5, 7]) + t = 'ababbababa' + s = 'abbb' + self.assertEqual(string.string_matching_rabin_karp(t, s), []) + + def test_string_matching_knuth_morris_pratt(self): + t = 'ababbababa' + s = 'aba' + self.assertEqual(string.string_matching_knuth_morris_pratt(t, s), + [0, 5, 7]) + t = 'ababbababa' + s = 'abbb' + self.assertEqual(string.string_matching_knuth_morris_pratt(t, s), []) + + def test_string_matching_boyer_moore_horspool(self): + t = 'ababbababa' + s = 'aba' + self.assertEqual(string.string_matching_boyer_moore_horspool(t, s), + [0, 5, 7]) + t = 'ababbababa' + s = 'abbb' + self.assertEqual(string.string_matching_boyer_moore_horspool(t, s), []) + + +if __name__ == '__main__': + unittest.main() diff --git a/algorithms/tests/test_string_matching.py b/algorithms/tests/test_string_matching.py deleted file mode 100644 index 6758b50..0000000 --- a/algorithms/tests/test_string_matching.py +++ /dev/null @@ -1,40 +0,0 @@ -import unittest -import algorithms.string_matching as string_matching - -class StringMatchingTest(unittest.TestCase): - - def test_string_matching_naive(self): - t = 'ababbababa' - s = 'aba' - self.assertEquals(string_matching.string_matching_naive(t, s), [0, 5, 7]) - t = 'ababbababa' - s = 'abbb' - self.assertEquals(string_matching.string_matching_naive(t, s), []) - - def test_string_matching_rabin_karp(self): - t = 'ababbababa' - s = 'aba' - self.assertEquals(string_matching.string_matching_rabin_karp(t, s), [0, 5, 7]) - t = 'ababbababa' - s = 'abbb' - self.assertEquals(string_matching.string_matching_rabin_karp(t, s), []) - - def test_string_matching_knuth_morris_pratt(self): - t = 'ababbababa' - s = 'aba' - self.assertEquals(string_matching.string_matching_knuth_morris_pratt(t, s), [0, 5, 7]) - t = 'ababbababa' - s = 'abbb' - self.assertEquals(string_matching.string_matching_knuth_morris_pratt(t, s), []) - - def test_string_matching_boyer_moore_horspool(self): - t = 'ababbababa' - s = 'aba' - self.assertEquals(string_matching.string_matching_boyer_moore_horspool(t, s), [0, 5, 7]) - t = 'ababbababa' - s = 'abbb' - self.assertEquals(string_matching.string_matching_boyer_moore_horspool(t, s), []) - -if __name__ == '__main__': - unittest.main() - diff --git a/pep8.sh b/pep8.sh new file mode 100755 index 0000000..2dc8e1a --- /dev/null +++ b/pep8.sh @@ -0,0 +1,2 @@ +flake8 . +exit From d9f0049c8376819e1804a1eca828669605754ea4 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Fri, 20 Nov 2015 15:57:37 -0500 Subject: [PATCH 24/43] PEP8. --- pep8.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pep8.sh b/pep8.sh index 2dc8e1a..e7fe912 100755 --- a/pep8.sh +++ b/pep8.sh @@ -1,2 +1,2 @@ -flake8 . +flake8 algorithms exit From 7c577c3df5f8918e8629f6cef187c7ecc6a73e15 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Mon, 23 Nov 2015 11:23:37 -0500 Subject: [PATCH 25/43] Reverse words in string. --- README.md | 29 ++++++++++++++------------ algorithms/string.py | 37 +++++++++++++++++++++++++++++++++ algorithms/tests/test_string.py | 17 +++++++++++++++ 3 files changed, 70 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index d444a79..6938839 100644 --- a/README.md +++ b/README.md @@ -7,19 +7,22 @@ The purpose of this library is to help you with common algorithms like: A* path finding. Binary tree - - node class - - lookup - - insert - - delete - - compare 2 trees - - print tree - - tree inorder generator - -String matching - - Naive - - Rabin-Karp - - Knuth-Morris-Pratt - - Boyer-Moore-Horspool + - lookup. + - insert. + - delete. + - compare 2 trees. + - print tree. + - tree inorder generator. + +String Matching + - Naive. + - Rabin-Karp. + - Knuth-Morris-Pratt. + - Boyer-Moore-Horspool. + +String + - Convert string to integer without using int on the full string. + - Reverse string containing words. Generators - Permutations. diff --git a/algorithms/string.py b/algorithms/string.py index 2ce5682..8f6d134 100644 --- a/algorithms/string.py +++ b/algorithms/string.py @@ -173,3 +173,40 @@ def atoi(s): i = -i return i + + +def reverse_string_words(s): + """Reverse words inside a string (in place). + + Since strings are immutable in Python, we copy the string chars to a list + first. + 'word1 word2 word3' -> 'word3 word2 word1' + + Complexity: O(n) + + @param s string words to reverse. + @returns reversed string words. + """ + def reverse(l, i, j): + # 'word1' -> '1drow' + # Complexity: O(n/2) + while i != j: + l[i], l[j] = l[j], l[i] + i += 1 + j -= 1 + + w = [e for e in s] + i = 0 + j = len(w) - 1 + reverse(w, i, j) + + i = 0 + j = 0 + while j < len(w): + while j < len(w) and w[j] != ' ': + j += 1 + reverse(w, i, j-1) + i = j + 1 + j += 1 + + return ''.join(e for e in w) diff --git a/algorithms/tests/test_string.py b/algorithms/tests/test_string.py index 60fab25..1d27a6e 100644 --- a/algorithms/tests/test_string.py +++ b/algorithms/tests/test_string.py @@ -11,6 +11,19 @@ def test_atoi(self): def test_atoi_neg(self): self.assertEqual(string.atoi('-123'), -123) + def test_atoi_empty_string(self): + self.assertRaises(ValueError, string.atoi, '') + + def test_reverse_string_words(self): + s = 'word1 word2 word3' + s = string.reverse_string_words(s) + self.assertEqual(s, 'word3 word2 word1') + + def test_reverse_string_word(self): + s = 'word1' + s = string.reverse_string_words(s) + self.assertEqual(s, 'word1') + def test_string_matching_naive(self): t = 'ababbababa' s = 'aba' @@ -45,6 +58,10 @@ def test_string_matching_boyer_moore_horspool(self): s = 'abbb' self.assertEqual(string.string_matching_boyer_moore_horspool(t, s), []) + s = 'ababbababa' + t = 'abbb' + self.assertEqual(string.string_matching_boyer_moore_horspool(t, s), []) + if __name__ == '__main__': unittest.main() From 84d9757a7179c1babfb25816c88a8ba3227aa27d Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Mon, 30 Nov 2015 09:58:21 -0500 Subject: [PATCH 26/43] Merge sort. --- algorithms/list.py | 45 +++++++++++++++++++++++++++++++++++ algorithms/tests/test_list.py | 12 ++++++++++ 2 files changed, 57 insertions(+) diff --git a/algorithms/list.py b/algorithms/list.py index ecdb036..55928fe 100644 --- a/algorithms/list.py +++ b/algorithms/list.py @@ -47,3 +47,48 @@ def find_max_sub(l): m = 0 s = i+1 return bounds, max + + +def merge_sort(l): + """Sort list using merge sort. + + @param l list to sort. + @returns sorted list. + """ + def merge(l1, l2): + """Merge sorted lists l1 and l2. + + [1, 2, 4], [1, 3, 4, 5] -> [1, 1, 2, 3, 4, 5] + @param l1 sorted list + @param l2 sorted list + @returns merge sorted list + """ + res = [] + i = 0 + j = 0 + while i < len(l1) and j < len(l2): + if l1[i] <= l2[j]: + res.append(l1[i]) + i += 1 + elif l2[j] < l1[i]: + res.append(l2[j]) + j += 1 + + while i < len(l1): + res.append(l1[i]) + i += 1 + + while j < len(l2): + res.append(l2[j]) + j += 1 + + return res + + length = len(l) + if length <= 1: + return l + mid = length / 2 + h1 = merge_sort(l[:mid]) + h2 = merge_sort(l[mid:]) + + return merge(h1, h2) diff --git a/algorithms/tests/test_list.py b/algorithms/tests/test_list.py index 6960823..05d848c 100644 --- a/algorithms/tests/test_list.py +++ b/algorithms/tests/test_list.py @@ -33,5 +33,17 @@ def test_find_int_empty_list(self): idx = list.find_int(3, []) self.assertIsNone(idx) + def test_merge_sort(self): + res = list.merge_sort([3, 4, 1, 5, 0]) + self.assertListEqual(res, [0, 1, 3, 4, 5]) + + def test_merge_sort_duplicates(self): + res = list.merge_sort([3, 4, 1, 5, 0, 4]) + self.assertListEqual(res, [0, 1, 3, 4, 4, 5]) + + def test_merge_sort_single_element(self): + res = list.merge_sort([3]) + self.assertListEqual(res, [3]) + if __name__ == '__main__': unittest.main() From ba47ae454f848a8791e439a4d5dbd587c563a111 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Mon, 30 Nov 2015 11:08:41 -0500 Subject: [PATCH 27/43] Quicksort. --- README.md | 2 ++ algorithms/list.py | 28 ++++++++++++++++++++++++++++ algorithms/tests/test_list.py | 13 +++++++++++++ 3 files changed, 43 insertions(+) diff --git a/README.md b/README.md index 6938839..b081e2d 100644 --- a/README.md +++ b/README.md @@ -30,6 +30,8 @@ Generators Lists - Subset with highest sum. - Find integer in sorted list. + - Merge sort. + - Quicksort. ### Installation Get the source and run diff --git a/algorithms/list.py b/algorithms/list.py index 55928fe..86fb40f 100644 --- a/algorithms/list.py +++ b/algorithms/list.py @@ -52,6 +52,8 @@ def find_max_sub(l): def merge_sort(l): """Sort list using merge sort. + Complexity: O(n log n) + @param l list to sort. @returns sorted list. """ @@ -92,3 +94,29 @@ def merge(l1, l2): h2 = merge_sort(l[mid:]) return merge(h1, h2) + + +def quicksort(l): + """Sort list using quick sort. + + Complexity: O(n log n). Worst: O(n2) + + @param l list to sort. + @returns sorted list. + """ + if len(l) <= 1: + return l + + pivot = l[0] + less = [] + equal = [] + greater = [] + for e in l: + if e < pivot: + less.append(e) + elif e == pivot: + equal.append(e) + else: + greater.append(e) + + return quicksort(less) + equal + quicksort(greater) diff --git a/algorithms/tests/test_list.py b/algorithms/tests/test_list.py index 05d848c..e8c61c4 100644 --- a/algorithms/tests/test_list.py +++ b/algorithms/tests/test_list.py @@ -45,5 +45,18 @@ def test_merge_sort_single_element(self): res = list.merge_sort([3]) self.assertListEqual(res, [3]) + def test_quicksort(self): + res = list.quicksort([3, 4, 1, 5, 0]) + self.assertListEqual(res, [0, 1, 3, 4, 5]) + + def test_quicksort_duplicates(self): + res = list.quicksort([3, 4, 1, 5, 4, 0, 1]) + self.assertListEqual(res, [0, 1, 1, 3, 4, 4, 5]) + + def test_quicksort_single_element(self): + res = list.quicksort([3]) + self.assertListEqual(res, [3]) + + if __name__ == '__main__': unittest.main() From 9b05b2e7ae95cf2ae932d3fe97372ecdde4fedc3 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Fri, 17 Jan 2020 13:53:14 -0500 Subject: [PATCH 28/43] Updated README following removal of some algos. --- README.md | 14 -------------- 1 file changed, 14 deletions(-) diff --git a/README.md b/README.md index b081e2d..99df38e 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,6 @@ The purpose of this library is to help you with common algorithms like: A* path finding. -Binary tree - - lookup. - - insert. - - delete. - - compare 2 trees. - - print tree. - - tree inorder generator. - String Matching - Naive. - Rabin-Karp. @@ -27,12 +19,6 @@ String Generators - Permutations. -Lists - - Subset with highest sum. - - Find integer in sorted list. - - Merge sort. - - Quicksort. - ### Installation Get the source and run From a80c09336b9728eb29108d9e346a5d8547cb8631 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Thu, 24 Dec 2020 17:40:55 -0500 Subject: [PATCH 29/43] Remove some algos. --- algorithms/binary_tree.py | 166 --------------------------- algorithms/list.py | 122 -------------------- algorithms/maze.py | 0 algorithms/tests/test_binary_tree.py | 165 -------------------------- algorithms/tests/test_list.py | 62 ---------- 5 files changed, 515 deletions(-) delete mode 100644 algorithms/binary_tree.py delete mode 100644 algorithms/list.py delete mode 100644 algorithms/maze.py delete mode 100644 algorithms/tests/test_binary_tree.py delete mode 100644 algorithms/tests/test_list.py diff --git a/algorithms/binary_tree.py b/algorithms/binary_tree.py deleted file mode 100644 index 3e8c20b..0000000 --- a/algorithms/binary_tree.py +++ /dev/null @@ -1,166 +0,0 @@ -from __future__ import print_function - - -class Node(object): - """Tree node: left and right child + data which can be any object - - """ - def __init__(self, data): - """Node constructor - - @param data node data object - """ - self.left = None - self.right = None - self.data = data - - def insert(self, data): - """Insert new node with data - - @param data node data object to insert - """ - if self.data: - if data < self.data: - if self.left is None: - self.left = Node(data) - else: - self.left.insert(data) - elif data > self.data: - if self.right is None: - self.right = Node(data) - else: - self.right.insert(data) - else: - self.data = data - - def lookup(self, data, parent=None): - """Lookup node containing data - - @param data node data object to look up - @param parent node's parent - @returns node and node's parent if found or None, None - """ - if data < self.data: - if self.left is None: - return None, None - return self.left.lookup(data, self) - elif data > self.data: - if self.right is None: - return None, None - return self.right.lookup(data, self) - else: - return self, parent - - def delete(self, data): - """Delete node containing data - - @param data node's content to delete - """ - # get node containing data - node, parent = self.lookup(data) - if node is not None: - children_count = node.children_count() - if children_count == 0: - # if node has no children, just remove it - if parent: - if parent.left is node: - parent.left = None - else: - parent.right = None - else: - self.data = None - elif children_count == 1: - # if node has 1 child - # replace node by its child - if node.left: - n = node.left - else: - n = node.right - if parent: - if parent.left is node: - parent.left = n - else: - parent.right = n - else: - self.left = n.left - self.right = n.right - self.data = n.data - else: - # if node has 2 children - # find its successor - parent = node - successor = node.right - while successor.left: - parent = successor - successor = successor.left - # replace node data by its successor data - node.data = successor.data - # fix successor's parent node child - if parent.left == successor: - parent.left = successor.right - else: - parent.right = successor.right - - def compare_trees(self, node): - """Compare 2 trees - - @param node tree to compare - @returns True if the tree passed is identical to this tree - """ - if node is None: - return False - if self.data != node.data: - return False - res = True - if self.left is None: - if node.left: - return False - else: - res = self.left.compare_trees(node.left) - if res is False: - return False - if self.right is None: - if node.right: - return False - else: - res = self.right.compare_trees(node.right) - return res - - def print_tree(self): - """Print tree content inorder - - """ - if self.left: - self.left.print_tree() - print(self.data, end=" ") - if self.right: - self.right.print_tree() - - def tree_data(self): - """Generator to get the tree nodes data - - """ - # we use a stack to traverse the tree in a non-recursive way - stack = [] - node = self - while stack or node: - if node: - stack.append(node) - node = node.left - else: - # we are returning so we pop the node and we yield it - node = stack.pop() - yield node.data - node = node.right - - def children_count(self): - """Return the number of children - - @returns number of children: 0, 1, 2 - """ - cnt = 0 - if self.left: - cnt += 1 - if self.right: - cnt += 1 - return cnt diff --git a/algorithms/list.py b/algorithms/list.py deleted file mode 100644 index 86fb40f..0000000 --- a/algorithms/list.py +++ /dev/null @@ -1,122 +0,0 @@ -def find_int(i, l): - """Find integer in a sorted list. - - Example: 4 in [1, 3, 4, 6, 7, 9] -> 2 - @param i integer to find. - @param l sorted list. - @returns index if found, None if not. - """ - if l: - p_idx = len(l) / 2 - p = l[p_idx] - if i == p: - return p_idx - elif len(l) == 1: - return - elif i < p: - res = find_int(i, l[:p_idx]) - if res: - return res - elif i > p: - res = find_int(i, l[p_idx:]) - if res: - return res + p_idx - - -def find_max_sub(l): - """Find subset with higest sum. - - Example: [-2, 3, -4, 5, 1, -5] -> (3,4), 6 - @param l list - @returns subset bounds and highest sum - """ - # max sum - max = l[0] - # current sum - m = 0 - # max sum subset bounds - bounds = (0, 0) - # current subset start - s = 0 - for i in range(len(l)): - m += l[i] - if m > max: - max = m - bounds = (s, i) - elif m < 0: - m = 0 - s = i+1 - return bounds, max - - -def merge_sort(l): - """Sort list using merge sort. - - Complexity: O(n log n) - - @param l list to sort. - @returns sorted list. - """ - def merge(l1, l2): - """Merge sorted lists l1 and l2. - - [1, 2, 4], [1, 3, 4, 5] -> [1, 1, 2, 3, 4, 5] - @param l1 sorted list - @param l2 sorted list - @returns merge sorted list - """ - res = [] - i = 0 - j = 0 - while i < len(l1) and j < len(l2): - if l1[i] <= l2[j]: - res.append(l1[i]) - i += 1 - elif l2[j] < l1[i]: - res.append(l2[j]) - j += 1 - - while i < len(l1): - res.append(l1[i]) - i += 1 - - while j < len(l2): - res.append(l2[j]) - j += 1 - - return res - - length = len(l) - if length <= 1: - return l - mid = length / 2 - h1 = merge_sort(l[:mid]) - h2 = merge_sort(l[mid:]) - - return merge(h1, h2) - - -def quicksort(l): - """Sort list using quick sort. - - Complexity: O(n log n). Worst: O(n2) - - @param l list to sort. - @returns sorted list. - """ - if len(l) <= 1: - return l - - pivot = l[0] - less = [] - equal = [] - greater = [] - for e in l: - if e < pivot: - less.append(e) - elif e == pivot: - equal.append(e) - else: - greater.append(e) - - return quicksort(less) + equal + quicksort(greater) diff --git a/algorithms/maze.py b/algorithms/maze.py deleted file mode 100644 index e69de29..0000000 diff --git a/algorithms/tests/test_binary_tree.py b/algorithms/tests/test_binary_tree.py deleted file mode 100644 index 0ec3397..0000000 --- a/algorithms/tests/test_binary_tree.py +++ /dev/null @@ -1,165 +0,0 @@ -import copy -import unittest - -import algorithms.binary_tree as binary_tree - - -class BinaryTreeTest(unittest.TestCase): - - def setUp(self): - self.root_single_node = binary_tree.Node(None) - self.root = binary_tree.Node(10) - self.root.left = binary_tree.Node(5) - self.root.left.left = binary_tree.Node(3) - self.root.left.right = binary_tree.Node(7) - self.root.right = binary_tree.Node(15) - self.root.right.left = binary_tree.Node(12) - self.root.right.left.left = binary_tree.Node(11) - self.root.right.right = binary_tree.Node(20) - self.root_copy = copy.deepcopy(self.root) - - def test_insert(self): - root = self.root_single_node - - root.insert(10) - self.assertEqual(root.data, 10) - - root.insert(5) - self.assertEqual(root.left.data, 5) - - root.insert(15) - self.assertEqual(root.right.data, 15) - - root.insert(8) - self.assertEqual(root.left.right.data, 8) - - root.insert(2) - self.assertEqual(root.left.left.data, 2) - - root.insert(12) - self.assertEqual(root.right.left.data, 12) - - root.insert(17) - self.assertEqual(root.right.right.data, 17) - - def test_lookup(self): - node, parent = self.root.lookup(0) - self.assertIsNone(parent) - self.assertIsNone(node) - - node, parent = self.root.lookup(13) - self.assertIsNone(parent) - self.assertIsNone(node) - - node, parent = self.root.lookup(7) - self.assertIs(node, self.root.left.right) - self.assertIs(parent, self.root.left) - - def test_delete_root_no_child(self): - self.root_single_node.data = 7 - self.root_single_node.delete(7) - self.assertIsNone(self.root_single_node.data) - - def test_delete_root_one_child(self): - self.root_single_node.data = 7 - self.root_single_node.insert(3) - self.root_single_node.delete(7) - self.assertEqual(self.root_single_node.data, 3) - - def test_delete_one_child_left(self): - self.root.delete(12) - self.assertEqual(self.root.left.data, 5) - self.assertEqual(self.root.left.left.data, 3) - self.assertEqual(self.root.left.right.data, 7) - self.assertEqual(self.root.right.data, 15) - self.assertEqual(self.root.right.left.data, 11) - self.assertEqual(self.root.right.right.data, 20) - - def test_delete_one_child_right(self): - self.root.insert(25) - self.root.delete(20) - self.assertEqual(self.root.left.data, 5) - self.assertEqual(self.root.left.left.data, 3) - self.assertEqual(self.root.left.right.data, 7) - self.assertEqual(self.root.right.data, 15) - self.assertEqual(self.root.right.left.data, 12) - self.assertEqual(self.root.right.left.left.data, 11) - self.assertEqual(self.root.right.right.data, 25) - - def test_delete_right_leaf(self): - self.root.delete(7) - self.assertIsNone(self.root.left.right) - self.assertEqual(self.root.left.data, 5) - self.assertEqual(self.root.left.left.data, 3) - self.assertEqual(self.root.right.data, 15) - self.assertEqual(self.root.right.left.data, 12) - self.assertEqual(self.root.right.left.left.data, 11) - self.assertEqual(self.root.right.right.data, 20) - - def test_delete_left_leaf(self): - self.root.delete(3) - self.assertIsNone(self.root.left.left) - self.assertEqual(self.root.left.data, 5) - self.assertEqual(self.root.left.right.data, 7) - self.assertEqual(self.root.right.data, 15) - self.assertEqual(self.root.right.left.data, 12) - self.assertEqual(self.root.right.left.left.data, 11) - self.assertEqual(self.root.right.right.data, 20) - - def test_delete_right_node_two_childs(self): - self.root.delete(15) - self.assertEqual(self.root.left.data, 5) - self.assertEqual(self.root.left.left.data, 3) - self.assertEqual(self.root.left.right.data, 7) - self.assertEqual(self.root.right.data, 20) - self.assertEqual(self.root.right.left.data, 12) - self.assertEqual(self.root.right.left.left.data, 11) - - def test_delete_left_node_two_childs(self): - self.root.delete(5) - self.assertEqual(self.root.left.data, 7) - self.assertEqual(self.root.left.left.data, 3) - self.assertEqual(self.root.right.data, 15) - self.assertEqual(self.root.right.left.data, 12) - self.assertEqual(self.root.right.left.left.data, 11) - self.assertEqual(self.root.right.right.data, 20) - - def test_delete_root_two_childs(self): - self.root.delete(10) - self.assertEqual(self.root.left.data, 5) - self.assertEqual(self.root.left.left.data, 3) - self.assertEqual(self.root.left.right.data, 7) - self.assertEqual(self.root.data, 11) - self.assertEqual(self.root.right.data, 15) - self.assertEqual(self.root.right.left.data, 12) - self.assertEqual(self.root.right.right.data, 20) - - def test_compare_trees_left_leaf_missing(self): - self.root_copy.delete(11) - self.assertFalse(self.root.compare_trees(self.root_copy)) - - def test_compare_trees_right_leaf_missing(self): - self.root_copy.delete(20) - self.assertFalse(self.root.compare_trees(self.root_copy)) - - def test_compare_trees_diff_value(self): - self.root_copy.left.data = 16 - self.assertFalse(self.root.compare_trees(self.root_copy)) - - def test_compare_trees_extra_right_leaf(self): - self.root_copy.insert(25) - self.assertFalse(self.root.compare_trees(self.root_copy)) - - def test_compare_trees_extra_left_leaf(self): - self.root_copy.insert(18) - self.assertFalse(self.root.compare_trees(self.root_copy)) - - def test_print_tree(self): - self.root.print_tree() - - def test_tree_data(self): - self.assertEqual([e for e in self.root.tree_data()], - [3, 5, 7, 10, 11, 12, 15, 20]) - -if __name__ == '__main__': - unittest.main() diff --git a/algorithms/tests/test_list.py b/algorithms/tests/test_list.py deleted file mode 100644 index e8c61c4..0000000 --- a/algorithms/tests/test_list.py +++ /dev/null @@ -1,62 +0,0 @@ -import unittest - -import algorithms.list as list - - -class List(unittest.TestCase): - - def setUp(self): - pass - - def test_find_max_sub(self): - bounds, m = [e for e in list.find_max_sub([-2, 3, -4, 5, 1, -5])] - self.assertEqual(bounds, (3, 4)) - self.assertEqual(m, 6) - - def test_find_int_first_half(self): - idx = list.find_int(4, [1, 2, 4, 5, 7, 9]) - self.assertEqual(idx, 2) - - def test_find_int_second_half(self): - idx = list.find_int(7, [1, 2, 4, 5, 7, 9]) - self.assertEqual(idx, 4) - - def test_find_int_not_found(self): - idx = list.find_int(3, [1, 2, 4, 5, 7, 9]) - self.assertIsNone(idx) - - def test_find_int_single_element_list(self): - idx = list.find_int(3, [3, ]) - self.assertEqual(idx, 0) - - def test_find_int_empty_list(self): - idx = list.find_int(3, []) - self.assertIsNone(idx) - - def test_merge_sort(self): - res = list.merge_sort([3, 4, 1, 5, 0]) - self.assertListEqual(res, [0, 1, 3, 4, 5]) - - def test_merge_sort_duplicates(self): - res = list.merge_sort([3, 4, 1, 5, 0, 4]) - self.assertListEqual(res, [0, 1, 3, 4, 4, 5]) - - def test_merge_sort_single_element(self): - res = list.merge_sort([3]) - self.assertListEqual(res, [3]) - - def test_quicksort(self): - res = list.quicksort([3, 4, 1, 5, 0]) - self.assertListEqual(res, [0, 1, 3, 4, 5]) - - def test_quicksort_duplicates(self): - res = list.quicksort([3, 4, 1, 5, 4, 0, 1]) - self.assertListEqual(res, [0, 1, 1, 3, 4, 4, 5]) - - def test_quicksort_single_element(self): - res = list.quicksort([3]) - self.assertListEqual(res, [3]) - - -if __name__ == '__main__': - unittest.main() From f0487717e1abbc8fd821d71a5d6f2e4b4527079a Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Sun, 27 Dec 2020 18:39:41 -0500 Subject: [PATCH 30/43] Pants for CI. --- BUILD | 17 +++++++++++++++++ algorithms/BUILD | 7 +++++++ algorithms/tests/BUILD | 3 +++ build-support/.flake8 | 1 + constraints.txt | 0 pants.toml | 26 ++++++++++++++++++++++++++ requirements.txt | 0 setup.py | 29 ----------------------------- 8 files changed, 54 insertions(+), 29 deletions(-) create mode 100644 BUILD create mode 100644 algorithms/BUILD create mode 100644 algorithms/tests/BUILD create mode 100644 build-support/.flake8 create mode 100644 constraints.txt create mode 100644 pants.toml create mode 100644 requirements.txt delete mode 100644 setup.py diff --git a/BUILD b/BUILD new file mode 100644 index 0000000..feacd05 --- /dev/null +++ b/BUILD @@ -0,0 +1,17 @@ +python_requirements() + +python_distribution( + name="python-algorithms", + dependencies=[ + ], + provides=setup_py( + name="python-algorithms", + version="1.0", + description="Python algorithms.", + author="Laurent Luce", + classifiers=[ + "Programming Language :: Python :: 3.6", + ], + ), + setup_py_commands=["sdist", "bdist_wheel", "--python-tag", "py36.py37"] +) diff --git a/algorithms/BUILD b/algorithms/BUILD new file mode 100644 index 0000000..92cbbb1 --- /dev/null +++ b/algorithms/BUILD @@ -0,0 +1,7 @@ +python_library( + name="algorithms", + sources=["*.py", "!*_test.py"], + interpreter_constraints=[">=3.6"], +) + + diff --git a/algorithms/tests/BUILD b/algorithms/tests/BUILD new file mode 100644 index 0000000..7fb50a5 --- /dev/null +++ b/algorithms/tests/BUILD @@ -0,0 +1,3 @@ +# `sources` defaults to ['*_test.py', 'test_*.py', 'conftest.py']. +# `dependencies` are inferred. +python_tests(name = 'tests') diff --git a/build-support/.flake8 b/build-support/.flake8 new file mode 100644 index 0000000..ef09bcb --- /dev/null +++ b/build-support/.flake8 @@ -0,0 +1 @@ +[flake8] diff --git a/constraints.txt b/constraints.txt new file mode 100644 index 0000000..e69de29 diff --git a/pants.toml b/pants.toml new file mode 100644 index 0000000..5a9c159 --- /dev/null +++ b/pants.toml @@ -0,0 +1,26 @@ +[GLOBAL] +pants_version = "2.1.0" +backend_packages = [ + "pants.backend.python", + "pants.backend.python.lint.flake8" +] + +[source] +# The Python source root is the repo root. See https://www.pantsbuild.org/docs/source-roots. +root_patterns = ["/"] + +[python-setup] +# The default interpreter compatibility for code in this repo. Individual targets can override +# this with the `interpreter_constraints` field. See +# https://www.pantsbuild.org/docs/python-interpreter-compatibility. +interpreter_constraints = [">=3.6"] +# Use a constraints file. See https://www.pantsbuild.org/docs/python-third-party-dependencies. +requirement_constraints = "constraints.txt" +# We search for interpreters on both on the $PATH and in the `$(pyenv root)/versions` folder. +# If you're using macOS, you may want to leave off the entry to avoid using the +# problematic system Pythons. See +# https://www.pantsbuild.org/docs/python-interpreter-compatibility#changing-the-interpreter-search-path. +interpreter_search_paths = ["", ""] + +[flake8] +config = "build-support/.flake8" diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..e69de29 diff --git a/setup.py b/setup.py deleted file mode 100644 index 0137842..0000000 --- a/setup.py +++ /dev/null @@ -1,29 +0,0 @@ -from distutils.core import setup -setup( - name = "algorithms", - packages = ['algorithms'], - version = "0.1", - description = "Algorithms implemented in Python", - author = "Laurent Luce", - author_email = "laurentluce49@yahoo.com", - url = "http://github.com/laurentluce/python-algorithms", - download_url = "http://github.com/laurentluce/python-algorithms", - keywords = ["algorithms"], - classifiers = [ - "Programming Language :: Python", - "Operating System :: OS Independent", - "License :: OSI Approved :: MIT License", - "Intended Audience :: Developers", - "Development Status :: 5 - Production/Stable", - "Topic :: Software Development :: Libraries :: Python Modules" - ], - long_description = """\ - Python Algorithms Library - ---------------------------- - - DESCRIPTION - The purpose of this library is to help you with basic and more advanced - algorithms - - LICENSE The Python Algorithms Library is distributed under the MIT - License """ ) From 38e11a8450ef9781154487e1dd8aedd192286d6f Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Sun, 27 Dec 2020 18:40:32 -0500 Subject: [PATCH 31/43] Pants for CI. --- pants | 294 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 294 insertions(+) create mode 100755 pants diff --git a/pants b/pants new file mode 100755 index 0000000..e9b5768 --- /dev/null +++ b/pants @@ -0,0 +1,294 @@ +#!/usr/bin/env bash +# Copyright 2020 Pants project contributors (see CONTRIBUTORS.md). +# Licensed under the Apache License, Version 2.0 (see LICENSE). + +# =============================== NOTE =============================== +# This ./pants bootstrap script comes from the pantsbuild/setup +# project. It is intended to be checked into your code repository so +# that other developers have the same setup. +# +# Learn more here: https://www.pantsbuild.org/docs/installation +# ==================================================================== + +set -eou pipefail + +# NOTE: To use an unreleased version of Pants from the pantsbuild/pants master branch, +# locate the master branch SHA, set PANTS_SHA= in the environment, and run this script as usual. +# +# E.g., PANTS_SHA=725fdaf504237190f6787dda3d72c39010a4c574 ./pants --version + +PYTHON_BIN_NAME="${PYTHON:-unspecified}" + +# Set this to specify a non-standard location for this script to read the Pants version from. +# NB: This will *not* cause Pants itself to use this location as a config file. +# You can use PANTS_CONFIG_FILES or --pants-config-files to do so. +PANTS_TOML=${PANTS_TOML:-pants.toml} + +PANTS_BIN_NAME="${PANTS_BIN_NAME:-$0}" + +PANTS_SETUP_CACHE="${PANTS_SETUP_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/pants/setup}" +# If given a relative path, we fix it to be absolute. +if [[ "$PANTS_SETUP_CACHE" != /* ]]; then + PANTS_SETUP_CACHE="${PWD}/${PANTS_SETUP_CACHE}" +fi + +PANTS_BOOTSTRAP="${PANTS_SETUP_CACHE}/bootstrap-$(uname -s)-$(uname -m)" + +VENV_VERSION=${VENV_VERSION:-20.2.2} + +VENV_PACKAGE=virtualenv-${VENV_VERSION} +VENV_TARBALL=${VENV_PACKAGE}.tar.gz + +COLOR_RED="\x1b[31m" +COLOR_GREEN="\x1b[32m" +COLOR_RESET="\x1b[0m" + +function log() { + echo -e "$@" 1>&2 +} + +function die() { + (($# > 0)) && log "${COLOR_RED}$*${COLOR_RESET}" + exit 1 +} + +function green() { + (($# > 0)) && log "${COLOR_GREEN}$*${COLOR_RESET}" +} + +function tempdir { + mktemp -d "$1"/pants.XXXXXX +} + +function get_exe_path_or_die { + local exe="$1" + if ! command -v "${exe}"; then + die "Could not find ${exe}. Please ensure ${exe} is on your PATH." + fi +} + +function get_pants_config_value { + local config_key="$1" + local optional_space="[[:space:]]*" + local prefix="^${config_key}${optional_space}=${optional_space}" + local raw_value + raw_value="$(sed -ne "/${prefix}/ s#${prefix}##p" "${PANTS_TOML}")" + echo "${raw_value}" | tr -d \"\' && return 0 + return 0 +} + +function get_python_major_minor_version { + local python_exe="$1" + "$python_exe" <&1 > /dev/null)" == "pyenv: python${version}"* ]]; then + continue + fi + echo "${interpreter_path}" && return 0 + done +} + +function determine_python_exe { + local pants_version="$1" + set_supported_python_versions "${pants_version}" + local requirement_str="For \`pants_version = \"${pants_version}\"\`, Pants requires Python ${supported_message} to run." + + local python_bin_name + if [[ "${PYTHON_BIN_NAME}" != 'unspecified' ]]; then + python_bin_name="${PYTHON_BIN_NAME}" + else + python_bin_name="$(determine_default_python_exe)" + if [[ -z "${python_bin_name}" ]]; then + die "No valid Python interpreter found. ${requirement_str} Please check that a valid interpreter is installed and on your \$PATH." + fi + fi + local python_exe + python_exe="$(get_exe_path_or_die "${python_bin_name}")" + local major_minor_version + major_minor_version="$(get_python_major_minor_version "${python_exe}")" + for valid_version in "${supported_python_versions_int[@]}"; do + if [[ "${major_minor_version}" == "${valid_version}" ]]; then + echo "${python_exe}" && return 0 + fi + done + die "Invalid Python interpreter version for ${python_exe}. ${requirement_str}" +} + +# TODO(John Sirois): GC race loser tmp dirs leftover from bootstrap_XXX +# functions. Any tmp dir w/o a symlink pointing to it can go. + +function bootstrap_venv { + if [[ ! -d "${PANTS_BOOTSTRAP}/${VENV_PACKAGE}" ]]; then + ( + mkdir -p "${PANTS_BOOTSTRAP}" + local staging_dir + staging_dir=$(tempdir "${PANTS_BOOTSTRAP}") + cd "${staging_dir}" + curl -LO "https://pypi.io/packages/source/v/virtualenv/${VENV_TARBALL}" + tar -xzf "${VENV_TARBALL}" + ln -s "${staging_dir}/${VENV_PACKAGE}" "${staging_dir}/latest" + mv "${staging_dir}/latest" "${PANTS_BOOTSTRAP}/${VENV_PACKAGE}" + ) 1>&2 + fi + + local venv_path="${PANTS_BOOTSTRAP}/${VENV_PACKAGE}" + local venv_entry_point + + # shellcheck disable=SC2086 + if [[ -f "${venv_path}/virtualenv.py" ]]; then + venv_entry_point="${venv_path}/virtualenv.py" + elif [[ -f "${venv_path}/src/virtualenv/__main__.py" ]]; then + venv_entry_point="${venv_path}/src/virtualenv/__main__.py" + else + die "Could not find virtualenv entry point for version $VENV_VERSION" + fi + + echo "${venv_entry_point}" +} + +function find_links_url { + local pants_version="$1" + local pants_sha="$2" + echo -n "https://binaries.pantsbuild.org/wheels/pantsbuild.pants/${pants_sha}/${pants_version/+/%2B}/index.html" +} + +function get_version_for_sha { + local sha="$1" + + # Retrieve the Pants version associated with this commit. + local pants_version + pants_version="$(curl --fail -sL "https://raw.githubusercontent.com/pantsbuild/pants/${sha}/src/python/pants/VERSION")" + + # Construct the version as the release version from src/python/pants/VERSION, plus the string `+gitXXXXXXXX`, + # where the XXXXXXXX is the first 8 characters of the SHA. + echo "${pants_version}+git${sha:0:8}" +} + +function bootstrap_pants { + local pants_version="$1" + local python="$2" + local pants_sha="${3:-}" + + local pants_requirement="pantsbuild.pants==${pants_version}" + local maybe_find_links + if [[ -z "${pants_sha}" ]]; then + maybe_find_links="" + else + maybe_find_links="--find-links=$(find_links_url "${pants_version}" "${pants_sha}")" + fi + local python_major_minor_version + python_major_minor_version="$(get_python_major_minor_version "${python}")" + local target_folder_name + target_folder_name="${pants_version}_py${python_major_minor_version}" + + if [[ ! -d "${PANTS_BOOTSTRAP}/${target_folder_name}" ]]; then + ( + local venv_entry_point="$(bootstrap_venv)" + local staging_dir + staging_dir=$(tempdir "${PANTS_BOOTSTRAP}") + "${python}" "${venv_entry_point}" --no-download "${staging_dir}/install" && \ + "${staging_dir}/install/bin/pip" install -U pip && \ + "${staging_dir}/install/bin/pip" install ${maybe_find_links} --progress-bar off "${pants_requirement}" && \ + ln -s "${staging_dir}/install" "${staging_dir}/${target_folder_name}" && \ + mv "${staging_dir}/${target_folder_name}" "${PANTS_BOOTSTRAP}/${target_folder_name}" && \ + green "New virtual environment successfully created at ${PANTS_BOOTSTRAP}/${target_folder_name}." + ) 1>&2 + fi + echo "${PANTS_BOOTSTRAP}/${target_folder_name}" +} + +# Ensure we operate from the context of the ./pants buildroot. +cd "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)" +pants_version="$(determine_pants_version)" +python="$(determine_python_exe "${pants_version}")" +pants_dir="$(bootstrap_pants "${pants_version}" "${python}" "${PANTS_SHA:-}")" +pants_python="${pants_dir}/bin/python" +pants_binary="${pants_dir}/bin/pants" +pants_extra_args="" +if [[ -n "${PANTS_SHA:-}" ]]; then + pants_extra_args="${pants_extra_args} --python-repos-repos=$(find_links_url "$pants_version" "$PANTS_SHA")" +fi + +# We set the env var no_proxy to '*', to work around an issue with urllib using non +# async-signal-safe syscalls after we fork a process that has already spawned threads. +# +# See https://blog.phusion.nl/2017/10/13/why-ruby-app-servers-break-on-macos-high-sierra-and-what-can-be-done-about-it/ +export no_proxy='*' + +# shellcheck disable=SC2086 +exec "${pants_python}" "${pants_binary}" ${pants_extra_args} \ + --pants-bin-name="${PANTS_BIN_NAME}" --pants-version=${pants_version} "$@" From d2f6cb4deecadf14c200dfe0d38719f509e282e0 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Sun, 27 Dec 2020 18:40:54 -0500 Subject: [PATCH 32/43] Pants for CI. --- algorithms/tests/all_tests.py | 20 -------------------- 1 file changed, 20 deletions(-) delete mode 100644 algorithms/tests/all_tests.py diff --git a/algorithms/tests/all_tests.py b/algorithms/tests/all_tests.py deleted file mode 100644 index ba9bc3b..0000000 --- a/algorithms/tests/all_tests.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Run all of the tests.""" -import sys -import unittest2 as unittest - - -def main(args=None): - unittest_dir = '.' - unittest_suite = unittest.defaultTestLoader.discover(unittest_dir) - - kwargs = {} - if args and '-v' in args: - kwargs['verbosity'] = 2 - runner = unittest.TextTestRunner(sys.stdout, "Unittests", - **kwargs) - results = runner.run(unittest_suite) - return results.wasSuccessful() - -if __name__ == '__main__': - status = main(sys.argv[1:]) - sys.exit(int(not status)) From 62e988ecbe3223f06279d69bc0c54265b56e0f97 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Sun, 27 Dec 2020 18:41:15 -0500 Subject: [PATCH 33/43] Linting and Py3 support. --- algorithms/a_star_path_finding.py | 3 +++ algorithms/permutations.py | 14 +++++++------- algorithms/string.py | 4 ++-- algorithms/tests/coverage.sh | 2 -- algorithms/tests/test_a_star_path_finding.py | 2 +- 5 files changed, 13 insertions(+), 12 deletions(-) delete mode 100644 algorithms/tests/coverage.sh diff --git a/algorithms/a_star_path_finding.py b/algorithms/a_star_path_finding.py index e239247..1448cb7 100644 --- a/algorithms/a_star_path_finding.py +++ b/algorithms/a_star_path_finding.py @@ -21,6 +21,9 @@ def __init__(self, x, y, reachable): self.h = 0 self.f = 0 + def __lt__(self, other): + return self.f < other.f + class AStar(object): def __init__(self): diff --git a/algorithms/permutations.py b/algorithms/permutations.py index 0c789b1..f013d2d 100644 --- a/algorithms/permutations.py +++ b/algorithms/permutations.py @@ -1,11 +1,11 @@ -def permutations(l): +def permutations(lst): """Generator for list permutations. - @param l list to generate permutations for + @param lst list to generate permutations for @result yield each permutation Example: - l = [1,2,3] + lst = [1,2,3] a = [1] permutations([2,3]) = [[2,3], [3,2]] [2,3] @@ -17,10 +17,10 @@ def permutations(l): yield [3,1,2] yield [3,2,1] """ - if len(l) <= 1: - yield l + if len(lst) <= 1: + yield lst else: - a = [l.pop(0)] - for p in permutations(l): + a = [lst.pop(0)] + for p in permutations(lst): for i in range(len(p)+1): yield p[:i] + a + p[i:] diff --git a/algorithms/string.py b/algorithms/string.py index 8f6d134..e653263 100644 --- a/algorithms/string.py +++ b/algorithms/string.py @@ -187,11 +187,11 @@ def reverse_string_words(s): @param s string words to reverse. @returns reversed string words. """ - def reverse(l, i, j): + def reverse(lst, i, j): # 'word1' -> '1drow' # Complexity: O(n/2) while i != j: - l[i], l[j] = l[j], l[i] + lst[i], lst[j] = lst[j], lst[i] i += 1 j -= 1 diff --git a/algorithms/tests/coverage.sh b/algorithms/tests/coverage.sh deleted file mode 100644 index a69b3be..0000000 --- a/algorithms/tests/coverage.sh +++ /dev/null @@ -1,2 +0,0 @@ -coverage run --source=$PYTHONPATH/algorithms --omit=$PYTHONPATH/algorithms/tests/* all_tests.py -coverage report --omit=$PYTHONPATH/algorithms/tests/* -m diff --git a/algorithms/tests/test_a_star_path_finding.py b/algorithms/tests/test_a_star_path_finding.py index e95b7eb..c2e991e 100644 --- a/algorithms/tests/test_a_star_path_finding.py +++ b/algorithms/tests/test_a_star_path_finding.py @@ -14,7 +14,7 @@ def test_maze(self): (3, 1), (3, 2), (3, 5), (4, 1), (4, 4), (5, 1)) a.init_grid(6, 6, walls, (0, 0), (5, 5)) path = a.solve() - self.assertEqual(path, [(0, 0), (0, 1), (0, 2), (0, 3), (0, 4), (1, 4), + self.assertEqual(path, [(0, 0), (0, 1), (0, 2), (1, 2), (1, 3), (1, 4), (2, 4), (3, 4), (3, 3), (4, 3), (5, 3), (5, 4), (5, 5)]) From e7719c43aa5069d744b62bf902f856a6fbac0893 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Sun, 27 Dec 2020 18:42:35 -0500 Subject: [PATCH 34/43] .gitignore --- .gitignore | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..86997e9 --- /dev/null +++ b/.gitignore @@ -0,0 +1,59 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] + +# C extensions +*.so + +# Distribution / packaging +.Python +env/ +bin/ +build/ +develop-eggs/ +dist/ +eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +*.egg-info/ +.installed.cfg +*.egg + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.coverage +.cache +nosetests.xml +coverage.xml + +# Translations +*.mo + +# Mr Developer +.mr.developer.cfg +.project +.pydevproject + +# Rope +.ropeproject + +# Django stuff: +*.log +*.pot + +# Sphinx documentation +docs/_build/ + +# Pants workspace files +/.pants.d/ +/dist/ +/.pids +/.pants.workdir.file_lock* From 4ddcba45d2a9e1d43ab3fdcf29e53040f165302a Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Sun, 27 Dec 2020 19:13:08 -0500 Subject: [PATCH 35/43] Py2 and Py3 support. --- algorithms/a_star_path_finding.py | 2 ++ algorithms/permutations.py | 1 + algorithms/string.py | 1 + 3 files changed, 4 insertions(+) diff --git a/algorithms/a_star_path_finding.py b/algorithms/a_star_path_finding.py index 1448cb7..25c21d7 100644 --- a/algorithms/a_star_path_finding.py +++ b/algorithms/a_star_path_finding.py @@ -1,3 +1,5 @@ +from builtins import range +from builtins import object import heapq diff --git a/algorithms/permutations.py b/algorithms/permutations.py index f013d2d..dd88fb2 100644 --- a/algorithms/permutations.py +++ b/algorithms/permutations.py @@ -1,3 +1,4 @@ +from builtins import range def permutations(lst): """Generator for list permutations. diff --git a/algorithms/string.py b/algorithms/string.py index e653263..b0e4a0a 100644 --- a/algorithms/string.py +++ b/algorithms/string.py @@ -1,3 +1,4 @@ +from builtins import range def string_matching_naive(text='', pattern=''): """Returns positions where pattern is found in text. From a86ba64175869e4b31d327ad15fbcd7ad4d993fe Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Sun, 27 Dec 2020 19:13:20 -0500 Subject: [PATCH 36/43] Add .bak files to gitignore. --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 86997e9..991b2c2 100644 --- a/.gitignore +++ b/.gitignore @@ -57,3 +57,6 @@ docs/_build/ /dist/ /.pids /.pants.workdir.file_lock* + +# Others +*.bak From b6bcf7befd282b7d66b79fbe43086de43831bdba Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Sun, 27 Dec 2020 20:02:36 -0500 Subject: [PATCH 37/43] Add two interpreters constraints: 2.7 and >=3.6. --- algorithms/BUILD | 2 +- algorithms/a_star_path_finding.py | 2 -- algorithms/permutations.py | 1 - algorithms/string.py | 1 - algorithms/tests/BUILD | 5 ++++- pants.toml | 6 +++++- 6 files changed, 10 insertions(+), 7 deletions(-) diff --git a/algorithms/BUILD b/algorithms/BUILD index 92cbbb1..d06a175 100644 --- a/algorithms/BUILD +++ b/algorithms/BUILD @@ -1,7 +1,7 @@ python_library( name="algorithms", sources=["*.py", "!*_test.py"], - interpreter_constraints=[">=3.6"], + interpreter_constraints=["==2.7.*", ">=3.6"], ) diff --git a/algorithms/a_star_path_finding.py b/algorithms/a_star_path_finding.py index 25c21d7..1448cb7 100644 --- a/algorithms/a_star_path_finding.py +++ b/algorithms/a_star_path_finding.py @@ -1,5 +1,3 @@ -from builtins import range -from builtins import object import heapq diff --git a/algorithms/permutations.py b/algorithms/permutations.py index dd88fb2..f013d2d 100644 --- a/algorithms/permutations.py +++ b/algorithms/permutations.py @@ -1,4 +1,3 @@ -from builtins import range def permutations(lst): """Generator for list permutations. diff --git a/algorithms/string.py b/algorithms/string.py index b0e4a0a..e653263 100644 --- a/algorithms/string.py +++ b/algorithms/string.py @@ -1,4 +1,3 @@ -from builtins import range def string_matching_naive(text='', pattern=''): """Returns positions where pattern is found in text. diff --git a/algorithms/tests/BUILD b/algorithms/tests/BUILD index 7fb50a5..e9edb5d 100644 --- a/algorithms/tests/BUILD +++ b/algorithms/tests/BUILD @@ -1,3 +1,6 @@ # `sources` defaults to ['*_test.py', 'test_*.py', 'conftest.py']. # `dependencies` are inferred. -python_tests(name = 'tests') +python_tests( + name = 'tests', + interpreter_constraints=["==2.7.*", ">=3.6"], +) diff --git a/pants.toml b/pants.toml index 5a9c159..0c3de5a 100644 --- a/pants.toml +++ b/pants.toml @@ -13,7 +13,7 @@ root_patterns = ["/"] # The default interpreter compatibility for code in this repo. Individual targets can override # this with the `interpreter_constraints` field. See # https://www.pantsbuild.org/docs/python-interpreter-compatibility. -interpreter_constraints = [">=3.6"] +interpreter_constraints = ["==2.7.*", ">=3.6"] # Use a constraints file. See https://www.pantsbuild.org/docs/python-third-party-dependencies. requirement_constraints = "constraints.txt" # We search for interpreters on both on the $PATH and in the `$(pyenv root)/versions` folder. @@ -24,3 +24,7 @@ interpreter_search_paths = ["", ""] [flake8] config = "build-support/.flake8" + +[pytest] +version = "pytest>=4.0,<6.1" +pytest_plugins = ["zipp>=1.2"] From da6018e62488a50775c3b939ca031b8e124f7288 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Sun, 27 Dec 2020 20:26:10 -0500 Subject: [PATCH 38/43] Tests using pants. --- README.md | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 99df38e..77f1a8b 100644 --- a/README.md +++ b/README.md @@ -19,10 +19,9 @@ String Generators - Permutations. -### Installation -Get the source and run +### Tests - $ python setup.py install + $ ./pants test :: ### License The Python Algorithms Library is distributed under the MIT License From 3edbb256e6156071c671e0e632e9d3b102659214 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Fri, 29 Jan 2021 19:16:35 -0500 Subject: [PATCH 39/43] Add comments to string matching algos. --- algorithms/string.py | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/algorithms/string.py b/algorithms/string.py index e653263..a809fab 100644 --- a/algorithms/string.py +++ b/algorithms/string.py @@ -1,7 +1,7 @@ def string_matching_naive(text='', pattern=''): """Returns positions where pattern is found in text. - We slide the string to match 'pattern' over the text + Sliding window. O((n-m)m) Example: text = 'ababbababa', pattern = 'aba' @@ -24,6 +24,11 @@ def string_matching_naive(text='', pattern=''): def string_matching_rabin_karp(text='', pattern='', hash_base=256): """Returns positions where pattern is found in text. + Similar to the naive approach but matches the hash value of the pattern + with the hash value of current substring of text. Needs to match + individual characters once a match is found because of potential + hash collisions. + worst case: O(nm) O(n+m) if the number of valid matches is small and the pattern is large. @@ -75,6 +80,8 @@ def hash_value(s, base): def string_matching_knuth_morris_pratt(text='', pattern=''): """Returns positions where pattern is found in text. + https://en.wikipedia.org/wiki/Knuth%E2%80%93Morris%E2%80%93Pratt_algorithm + O(m+n) Example: text = 'ababbababa', pattern = 'aba' string_matching_knuth_morris_pratt(text, pattern) returns [0, 5, 7] @@ -115,6 +122,8 @@ def compute_prefix_function(p): def string_matching_boyer_moore_horspool(text='', pattern=''): """Returns positions where pattern is found in text. + https://en.wikipedia.org/wiki/Boyer%E2%80%93Moore_string-search_algorithm + O(n) Performance: ord() is slow so we shouldn't use it here From abf8c3abe244893f11d9a46945f455a40839b86f Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Fri, 29 Jan 2021 19:28:25 -0500 Subject: [PATCH 40/43] Revert delete mistake. --- algorithms/binary_tree.py | 166 +++++++++++++++++++++++++++ algorithms/list.py | 122 ++++++++++++++++++++ algorithms/tests/test_binary_tree.py | 165 ++++++++++++++++++++++++++ algorithms/tests/test_list.py | 62 ++++++++++ 4 files changed, 515 insertions(+) create mode 100644 algorithms/binary_tree.py create mode 100644 algorithms/list.py create mode 100644 algorithms/tests/test_binary_tree.py create mode 100644 algorithms/tests/test_list.py diff --git a/algorithms/binary_tree.py b/algorithms/binary_tree.py new file mode 100644 index 0000000..3e8c20b --- /dev/null +++ b/algorithms/binary_tree.py @@ -0,0 +1,166 @@ +from __future__ import print_function + + +class Node(object): + """Tree node: left and right child + data which can be any object + + """ + def __init__(self, data): + """Node constructor + + @param data node data object + """ + self.left = None + self.right = None + self.data = data + + def insert(self, data): + """Insert new node with data + + @param data node data object to insert + """ + if self.data: + if data < self.data: + if self.left is None: + self.left = Node(data) + else: + self.left.insert(data) + elif data > self.data: + if self.right is None: + self.right = Node(data) + else: + self.right.insert(data) + else: + self.data = data + + def lookup(self, data, parent=None): + """Lookup node containing data + + @param data node data object to look up + @param parent node's parent + @returns node and node's parent if found or None, None + """ + if data < self.data: + if self.left is None: + return None, None + return self.left.lookup(data, self) + elif data > self.data: + if self.right is None: + return None, None + return self.right.lookup(data, self) + else: + return self, parent + + def delete(self, data): + """Delete node containing data + + @param data node's content to delete + """ + # get node containing data + node, parent = self.lookup(data) + if node is not None: + children_count = node.children_count() + if children_count == 0: + # if node has no children, just remove it + if parent: + if parent.left is node: + parent.left = None + else: + parent.right = None + else: + self.data = None + elif children_count == 1: + # if node has 1 child + # replace node by its child + if node.left: + n = node.left + else: + n = node.right + if parent: + if parent.left is node: + parent.left = n + else: + parent.right = n + else: + self.left = n.left + self.right = n.right + self.data = n.data + else: + # if node has 2 children + # find its successor + parent = node + successor = node.right + while successor.left: + parent = successor + successor = successor.left + # replace node data by its successor data + node.data = successor.data + # fix successor's parent node child + if parent.left == successor: + parent.left = successor.right + else: + parent.right = successor.right + + def compare_trees(self, node): + """Compare 2 trees + + @param node tree to compare + @returns True if the tree passed is identical to this tree + """ + if node is None: + return False + if self.data != node.data: + return False + res = True + if self.left is None: + if node.left: + return False + else: + res = self.left.compare_trees(node.left) + if res is False: + return False + if self.right is None: + if node.right: + return False + else: + res = self.right.compare_trees(node.right) + return res + + def print_tree(self): + """Print tree content inorder + + """ + if self.left: + self.left.print_tree() + print(self.data, end=" ") + if self.right: + self.right.print_tree() + + def tree_data(self): + """Generator to get the tree nodes data + + """ + # we use a stack to traverse the tree in a non-recursive way + stack = [] + node = self + while stack or node: + if node: + stack.append(node) + node = node.left + else: + # we are returning so we pop the node and we yield it + node = stack.pop() + yield node.data + node = node.right + + def children_count(self): + """Return the number of children + + @returns number of children: 0, 1, 2 + """ + cnt = 0 + if self.left: + cnt += 1 + if self.right: + cnt += 1 + return cnt diff --git a/algorithms/list.py b/algorithms/list.py new file mode 100644 index 0000000..86fb40f --- /dev/null +++ b/algorithms/list.py @@ -0,0 +1,122 @@ +def find_int(i, l): + """Find integer in a sorted list. + + Example: 4 in [1, 3, 4, 6, 7, 9] -> 2 + @param i integer to find. + @param l sorted list. + @returns index if found, None if not. + """ + if l: + p_idx = len(l) / 2 + p = l[p_idx] + if i == p: + return p_idx + elif len(l) == 1: + return + elif i < p: + res = find_int(i, l[:p_idx]) + if res: + return res + elif i > p: + res = find_int(i, l[p_idx:]) + if res: + return res + p_idx + + +def find_max_sub(l): + """Find subset with higest sum. + + Example: [-2, 3, -4, 5, 1, -5] -> (3,4), 6 + @param l list + @returns subset bounds and highest sum + """ + # max sum + max = l[0] + # current sum + m = 0 + # max sum subset bounds + bounds = (0, 0) + # current subset start + s = 0 + for i in range(len(l)): + m += l[i] + if m > max: + max = m + bounds = (s, i) + elif m < 0: + m = 0 + s = i+1 + return bounds, max + + +def merge_sort(l): + """Sort list using merge sort. + + Complexity: O(n log n) + + @param l list to sort. + @returns sorted list. + """ + def merge(l1, l2): + """Merge sorted lists l1 and l2. + + [1, 2, 4], [1, 3, 4, 5] -> [1, 1, 2, 3, 4, 5] + @param l1 sorted list + @param l2 sorted list + @returns merge sorted list + """ + res = [] + i = 0 + j = 0 + while i < len(l1) and j < len(l2): + if l1[i] <= l2[j]: + res.append(l1[i]) + i += 1 + elif l2[j] < l1[i]: + res.append(l2[j]) + j += 1 + + while i < len(l1): + res.append(l1[i]) + i += 1 + + while j < len(l2): + res.append(l2[j]) + j += 1 + + return res + + length = len(l) + if length <= 1: + return l + mid = length / 2 + h1 = merge_sort(l[:mid]) + h2 = merge_sort(l[mid:]) + + return merge(h1, h2) + + +def quicksort(l): + """Sort list using quick sort. + + Complexity: O(n log n). Worst: O(n2) + + @param l list to sort. + @returns sorted list. + """ + if len(l) <= 1: + return l + + pivot = l[0] + less = [] + equal = [] + greater = [] + for e in l: + if e < pivot: + less.append(e) + elif e == pivot: + equal.append(e) + else: + greater.append(e) + + return quicksort(less) + equal + quicksort(greater) diff --git a/algorithms/tests/test_binary_tree.py b/algorithms/tests/test_binary_tree.py new file mode 100644 index 0000000..0ec3397 --- /dev/null +++ b/algorithms/tests/test_binary_tree.py @@ -0,0 +1,165 @@ +import copy +import unittest + +import algorithms.binary_tree as binary_tree + + +class BinaryTreeTest(unittest.TestCase): + + def setUp(self): + self.root_single_node = binary_tree.Node(None) + self.root = binary_tree.Node(10) + self.root.left = binary_tree.Node(5) + self.root.left.left = binary_tree.Node(3) + self.root.left.right = binary_tree.Node(7) + self.root.right = binary_tree.Node(15) + self.root.right.left = binary_tree.Node(12) + self.root.right.left.left = binary_tree.Node(11) + self.root.right.right = binary_tree.Node(20) + self.root_copy = copy.deepcopy(self.root) + + def test_insert(self): + root = self.root_single_node + + root.insert(10) + self.assertEqual(root.data, 10) + + root.insert(5) + self.assertEqual(root.left.data, 5) + + root.insert(15) + self.assertEqual(root.right.data, 15) + + root.insert(8) + self.assertEqual(root.left.right.data, 8) + + root.insert(2) + self.assertEqual(root.left.left.data, 2) + + root.insert(12) + self.assertEqual(root.right.left.data, 12) + + root.insert(17) + self.assertEqual(root.right.right.data, 17) + + def test_lookup(self): + node, parent = self.root.lookup(0) + self.assertIsNone(parent) + self.assertIsNone(node) + + node, parent = self.root.lookup(13) + self.assertIsNone(parent) + self.assertIsNone(node) + + node, parent = self.root.lookup(7) + self.assertIs(node, self.root.left.right) + self.assertIs(parent, self.root.left) + + def test_delete_root_no_child(self): + self.root_single_node.data = 7 + self.root_single_node.delete(7) + self.assertIsNone(self.root_single_node.data) + + def test_delete_root_one_child(self): + self.root_single_node.data = 7 + self.root_single_node.insert(3) + self.root_single_node.delete(7) + self.assertEqual(self.root_single_node.data, 3) + + def test_delete_one_child_left(self): + self.root.delete(12) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.left.right.data, 7) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 11) + self.assertEqual(self.root.right.right.data, 20) + + def test_delete_one_child_right(self): + self.root.insert(25) + self.root.delete(20) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.left.right.data, 7) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.left.left.data, 11) + self.assertEqual(self.root.right.right.data, 25) + + def test_delete_right_leaf(self): + self.root.delete(7) + self.assertIsNone(self.root.left.right) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.left.left.data, 11) + self.assertEqual(self.root.right.right.data, 20) + + def test_delete_left_leaf(self): + self.root.delete(3) + self.assertIsNone(self.root.left.left) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.right.data, 7) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.left.left.data, 11) + self.assertEqual(self.root.right.right.data, 20) + + def test_delete_right_node_two_childs(self): + self.root.delete(15) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.left.right.data, 7) + self.assertEqual(self.root.right.data, 20) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.left.left.data, 11) + + def test_delete_left_node_two_childs(self): + self.root.delete(5) + self.assertEqual(self.root.left.data, 7) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.left.left.data, 11) + self.assertEqual(self.root.right.right.data, 20) + + def test_delete_root_two_childs(self): + self.root.delete(10) + self.assertEqual(self.root.left.data, 5) + self.assertEqual(self.root.left.left.data, 3) + self.assertEqual(self.root.left.right.data, 7) + self.assertEqual(self.root.data, 11) + self.assertEqual(self.root.right.data, 15) + self.assertEqual(self.root.right.left.data, 12) + self.assertEqual(self.root.right.right.data, 20) + + def test_compare_trees_left_leaf_missing(self): + self.root_copy.delete(11) + self.assertFalse(self.root.compare_trees(self.root_copy)) + + def test_compare_trees_right_leaf_missing(self): + self.root_copy.delete(20) + self.assertFalse(self.root.compare_trees(self.root_copy)) + + def test_compare_trees_diff_value(self): + self.root_copy.left.data = 16 + self.assertFalse(self.root.compare_trees(self.root_copy)) + + def test_compare_trees_extra_right_leaf(self): + self.root_copy.insert(25) + self.assertFalse(self.root.compare_trees(self.root_copy)) + + def test_compare_trees_extra_left_leaf(self): + self.root_copy.insert(18) + self.assertFalse(self.root.compare_trees(self.root_copy)) + + def test_print_tree(self): + self.root.print_tree() + + def test_tree_data(self): + self.assertEqual([e for e in self.root.tree_data()], + [3, 5, 7, 10, 11, 12, 15, 20]) + +if __name__ == '__main__': + unittest.main() diff --git a/algorithms/tests/test_list.py b/algorithms/tests/test_list.py new file mode 100644 index 0000000..e8c61c4 --- /dev/null +++ b/algorithms/tests/test_list.py @@ -0,0 +1,62 @@ +import unittest + +import algorithms.list as list + + +class List(unittest.TestCase): + + def setUp(self): + pass + + def test_find_max_sub(self): + bounds, m = [e for e in list.find_max_sub([-2, 3, -4, 5, 1, -5])] + self.assertEqual(bounds, (3, 4)) + self.assertEqual(m, 6) + + def test_find_int_first_half(self): + idx = list.find_int(4, [1, 2, 4, 5, 7, 9]) + self.assertEqual(idx, 2) + + def test_find_int_second_half(self): + idx = list.find_int(7, [1, 2, 4, 5, 7, 9]) + self.assertEqual(idx, 4) + + def test_find_int_not_found(self): + idx = list.find_int(3, [1, 2, 4, 5, 7, 9]) + self.assertIsNone(idx) + + def test_find_int_single_element_list(self): + idx = list.find_int(3, [3, ]) + self.assertEqual(idx, 0) + + def test_find_int_empty_list(self): + idx = list.find_int(3, []) + self.assertIsNone(idx) + + def test_merge_sort(self): + res = list.merge_sort([3, 4, 1, 5, 0]) + self.assertListEqual(res, [0, 1, 3, 4, 5]) + + def test_merge_sort_duplicates(self): + res = list.merge_sort([3, 4, 1, 5, 0, 4]) + self.assertListEqual(res, [0, 1, 3, 4, 4, 5]) + + def test_merge_sort_single_element(self): + res = list.merge_sort([3]) + self.assertListEqual(res, [3]) + + def test_quicksort(self): + res = list.quicksort([3, 4, 1, 5, 0]) + self.assertListEqual(res, [0, 1, 3, 4, 5]) + + def test_quicksort_duplicates(self): + res = list.quicksort([3, 4, 1, 5, 4, 0, 1]) + self.assertListEqual(res, [0, 1, 1, 3, 4, 4, 5]) + + def test_quicksort_single_element(self): + res = list.quicksort([3]) + self.assertListEqual(res, [3]) + + +if __name__ == '__main__': + unittest.main() From f1860fe6d41c7515613e59b78411e082e3d9fb33 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Fri, 29 Jan 2021 20:05:11 -0500 Subject: [PATCH 41/43] Updated README to match the current list of algos. --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 77f1a8b..85c29c2 100644 --- a/README.md +++ b/README.md @@ -19,6 +19,14 @@ String Generators - Permutations. +Lists + - Find integer using binary search. + - Find subset with max sum. + - Merge sort. + - Quicksort. + +Binary tree + ### Tests $ ./pants test :: From e565d7316a766a8f3884b40e635e5248b5c70933 Mon Sep 17 00:00:00 2001 From: Laurent Luce Date: Fri, 29 Jan 2021 20:05:22 -0500 Subject: [PATCH 42/43] Linting. --- algorithms/list.py | 48 ++++++++++---------- algorithms/tests/test_a_star_path_finding.py | 1 + algorithms/tests/test_binary_tree.py | 1 + 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/algorithms/list.py b/algorithms/list.py index 86fb40f..ce7e1c8 100644 --- a/algorithms/list.py +++ b/algorithms/list.py @@ -1,45 +1,45 @@ -def find_int(i, l): +def find_int(i, lst): """Find integer in a sorted list. Example: 4 in [1, 3, 4, 6, 7, 9] -> 2 @param i integer to find. - @param l sorted list. + @param lst sorted list. @returns index if found, None if not. """ - if l: - p_idx = len(l) / 2 - p = l[p_idx] + if lst: + p_idx = len(lst) / 2 + p = lst[p_idx] if i == p: return p_idx - elif len(l) == 1: + elif len(lst) == 1: return elif i < p: - res = find_int(i, l[:p_idx]) + res = find_int(i, lst[:p_idx]) if res: return res elif i > p: - res = find_int(i, l[p_idx:]) + res = find_int(i, lst[p_idx:]) if res: return res + p_idx -def find_max_sub(l): +def find_max_sub(lst): """Find subset with higest sum. Example: [-2, 3, -4, 5, 1, -5] -> (3,4), 6 - @param l list + @param lst list @returns subset bounds and highest sum """ # max sum - max = l[0] + max = lst[0] # current sum m = 0 # max sum subset bounds bounds = (0, 0) # current subset start s = 0 - for i in range(len(l)): - m += l[i] + for i in range(len(lst)): + m += lst[i] if m > max: max = m bounds = (s, i) @@ -49,7 +49,7 @@ def find_max_sub(l): return bounds, max -def merge_sort(l): +def merge_sort(lst): """Sort list using merge sort. Complexity: O(n log n) @@ -86,32 +86,32 @@ def merge(l1, l2): return res - length = len(l) + length = len(lst) if length <= 1: - return l + return lst mid = length / 2 - h1 = merge_sort(l[:mid]) - h2 = merge_sort(l[mid:]) + h1 = merge_sort(lst[:mid]) + h2 = merge_sort(lst[mid:]) return merge(h1, h2) -def quicksort(l): +def quicksort(lst): """Sort list using quick sort. Complexity: O(n log n). Worst: O(n2) - @param l list to sort. + @param lst list to sort. @returns sorted list. """ - if len(l) <= 1: - return l + if len(lst) <= 1: + return lst - pivot = l[0] + pivot = lst[0] less = [] equal = [] greater = [] - for e in l: + for e in lst: if e < pivot: less.append(e) elif e == pivot: diff --git a/algorithms/tests/test_a_star_path_finding.py b/algorithms/tests/test_a_star_path_finding.py index c2e991e..396388d 100644 --- a/algorithms/tests/test_a_star_path_finding.py +++ b/algorithms/tests/test_a_star_path_finding.py @@ -32,5 +32,6 @@ def test_maze_no_solution(self): a.init_grid(6, 6, walls, (0, 0), (5, 5)) self.assertIsNone(a.solve()) + if __name__ == '__main__': unittest.main() diff --git a/algorithms/tests/test_binary_tree.py b/algorithms/tests/test_binary_tree.py index 0ec3397..ca0eede 100644 --- a/algorithms/tests/test_binary_tree.py +++ b/algorithms/tests/test_binary_tree.py @@ -161,5 +161,6 @@ def test_tree_data(self): self.assertEqual([e for e in self.root.tree_data()], [3, 5, 7, 10, 11, 12, 15, 20]) + if __name__ == '__main__': unittest.main() From 6a82a4c6c594508436835c8127d8d2f1b931a300 Mon Sep 17 00:00:00 2001 From: Tim Gates Date: Sat, 4 Sep 2021 08:08:35 +1000 Subject: [PATCH 43/43] docs: fix simple typo, higest -> highest There is a small typo in algorithms/list.py. Should read `highest` rather than `higest`. --- algorithms/list.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/algorithms/list.py b/algorithms/list.py index ce7e1c8..1546904 100644 --- a/algorithms/list.py +++ b/algorithms/list.py @@ -24,7 +24,7 @@ def find_int(i, lst): def find_max_sub(lst): - """Find subset with higest sum. + """Find subset with highest sum. Example: [-2, 3, -4, 5, 1, -5] -> (3,4), 6 @param lst list