From c6cbff82c90d89cda3113b6a0df462df7ecea51b Mon Sep 17 00:00:00 2001 From: imgemp Date: Thu, 11 Dec 2014 14:38:34 -0500 Subject: [PATCH 01/31] 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 02/31] 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 03/31] 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 04/31] 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 05/31] 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 06/31] 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 07/31] 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 08/31] 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 09/31] 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 10/31] 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 11/31] 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 12/31] 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 13/31] 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 14/31] 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 15/31] 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 16/31] 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 17/31] 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 18/31] 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 19/31] 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 20/31] 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 21/31] 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 22/31] .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 23/31] 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 24/31] 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 25/31] 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 26/31] 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 27/31] 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 28/31] 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 29/31] 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 30/31] 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 31/31] 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