From 85e87f6626e5f19e2c8165943315c4b1b9230bab Mon Sep 17 00:00:00 2001 From: nelson-brochado Date: Sun, 5 Feb 2017 03:15:22 +0100 Subject: [PATCH 1/3] Solved issue 32 by renaming BaseHeap to BinaryHeap MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - module Heap.py renamed to heap.py, since it could contain more than one class representing heaps - Removed HeapPrinter class, which had only one method, and made this method a standalone function - Removed class Heap, from which MinHeap and MaxHeap derived, since it only contain one method, “delete”, which was moved to its child classes. - Refactored a few things around --- ands/ds/BST.py | 46 +- ands/ds/DSForests.py | 31 +- ands/ds/HashTable.py | 13 +- ands/ds/Heap.py | 148 +- ands/ds/MaxHeap.py | 38 +- ands/ds/MinHeap.py | 37 +- ands/ds/MinMaxHeap.py | 18 +- ands/ds/Queue.py | 2 + ands/ds/RBT.py | 15 +- ands/ds/Stack.py | 3 + ands/ds/TST.py | 10 +- automate.sh | 2 +- docs/ands/algorithms/crypto/caesar.m.html | 1222 +++++ docs/ands/algorithms/crypto/index.html | 1049 ++++ .../algorithms/crypto/one_time_pad.m.html | 1096 ++++ docs/ands/algorithms/dac/binary_search.m.html | 1225 +++++ .../algorithms/dac/find_max_and_min.m.html | 1146 +++++ docs/ands/algorithms/dac/find_peak.m.html | 1164 +++++ docs/ands/algorithms/dac/index.html | 1074 ++++ docs/ands/algorithms/dac/select.m.html | 1151 +++++ docs/ands/algorithms/dp/change_making.m.html | 1349 +++++ docs/ands/algorithms/dp/edit_distance.m.html | 1485 ++++++ docs/ands/algorithms/dp/fibonacci.m.html | 1238 +++++ docs/ands/algorithms/dp/index.html | 1186 +++++ .../dp/longest_common_subsequence.m.html | 1712 +++++++ .../dp/longest_common_substring.m.html | 1110 ++++ .../dp/longest_increasing_subsequence.m.html | 1254 +++++ .../dp/max_non_adjacent_seq_weight.m.html | 1122 ++++ .../dp/max_sum_contiguous_subsequence.m.html | 1417 ++++++ docs/ands/algorithms/dp/plus_sign_game.m.html | 1227 +++++ docs/ands/algorithms/dp/rod_cut.m.html | 1580 ++++++ docs/ands/algorithms/dp/subset_sum.m.html | 1246 +++++ .../algorithms/dp/zero_one_knapsack.m.html | 1424 ++++++ .../graphs/best_team_of_three.m.html | 1261 +++++ .../graphs/build_shortest_path.m.html | 1134 +++++ docs/ands/algorithms/graphs/dfs.m.html | 1456 ++++++ .../algorithms/graphs/find_triangle.m.html | 1266 +++++ docs/ands/algorithms/graphs/four_cycle.m.html | 1213 +++++ docs/ands/algorithms/graphs/index.html | 1134 +++++ docs/ands/algorithms/graphs/prim.m.html | 1361 +++++ docs/ands/algorithms/graphs/top_sort.m.html | 1638 ++++++ .../top_three_friends_of_friends.m.html | 1235 +++++ .../greedy/activity_selection.m.html | 1261 +++++ .../greedy/fractional_knapsack.m.html | 1250 +++++ docs/ands/algorithms/greedy/huffman.m.html | 1427 ++++++ docs/ands/algorithms/greedy/index.html | 1064 ++++ docs/ands/algorithms/index.html | 1103 ++++ .../algorithms/math/arithmetic/index.html | 1035 ++++ .../algorithms/math/arithmetic/sum.m.html | 1176 +++++ .../algorithms/math/combinatorics/index.html | 1041 ++++ .../math/combinatorics/n_choose_k.m.html | 1238 +++++ docs/ands/algorithms/math/index.html | 1040 ++++ docs/ands/algorithms/parsing/index.html | 1036 ++++ docs/ands/algorithms/parsing/smep.m.html | 1469 ++++++ docs/ands/algorithms/primes/index.html | 1035 ++++ docs/ands/algorithms/primes/is_prime.m.html | 1269 +++++ .../algorithms/recursion/ackermann.m.html | 1119 ++++ docs/ands/algorithms/recursion/count.m.html | 1103 ++++ .../algorithms/recursion/factorial.m.html | 1310 +++++ docs/ands/algorithms/recursion/hanoi.m.html | 1197 +++++ docs/ands/algorithms/recursion/index.html | 1167 +++++ .../algorithms/recursion/is_sorted.m.html | 1211 +++++ .../algorithms/recursion/make_decimal.m.html | 1178 +++++ .../algorithms/recursion/palindrome.m.html | 1115 ++++ docs/ands/algorithms/recursion/power.m.html | 1101 ++++ docs/ands/algorithms/recursion/reverse.m.html | 1100 ++++ .../algorithms/sorting/bubble_sort.m.html | 1098 ++++ docs/ands/algorithms/sorting/heap_sort.m.html | 1168 +++++ docs/ands/algorithms/sorting/index.html | 1137 +++++ .../algorithms/sorting/insertion_sort.m.html | 1102 ++++ .../ands/algorithms/sorting/merge_sort.m.html | 1258 +++++ .../ands/algorithms/sorting/quick_sort.m.html | 1228 +++++ .../algorithms/sorting/selection_sort.m.html | 1102 ++++ docs/ands/algorithms/unclassified/index.html | 1033 ++++ .../unclassified/max_num_dups.m.html | 1110 ++++ docs/ands/ds/BST.m.html | 4518 +++++++++++++++++ docs/ands/ds/DSForests.m.html | 1945 +++++++ docs/ands/ds/Graph.m.html | 2766 ++++++++++ docs/ands/ds/HashTable.m.html | 1805 +++++++ docs/ands/ds/MaxHeap.m.html | 2324 +++++++++ docs/ands/ds/MinHeap.m.html | 2374 +++++++++ docs/ands/ds/MinMaxHeap.m.html | 3013 +++++++++++ docs/ands/ds/MinPriorityQueue.m.html | 2360 +++++++++ docs/ands/ds/Queue.m.html | 1349 +++++ docs/ands/ds/RBT.m.html | 3715 ++++++++++++++ docs/ands/ds/Stack.m.html | 1439 ++++++ docs/ands/ds/TST.m.html | 3002 +++++++++++ docs/ands/ds/heap.m.html | 2786 ++++++++++ docs/ands/ds/index.html | 1235 +++++ docs/ands/index.html | 1045 ++++ tests/README.md | 4 +- tests/algorithms/crypto/test_caesar.py | 6 + tests/algorithms/crypto/test_one_time_pad.py | 10 +- tests/algorithms/ode/test_forward_euler.py | 13 +- tests/algorithms/recursion/test_ackermann.py | 6 +- tests/algorithms/recursion/test_count.py | 5 +- tests/algorithms/recursion/test_factorial.py | 7 +- tests/algorithms/recursion/test_hanoi.py | 7 +- tests/algorithms/recursion/test_is_sorted.py | 7 +- .../algorithms/recursion/test_make_decimal.py | 7 +- tests/algorithms/recursion/test_palindrome.py | 7 +- tests/algorithms/recursion/test_power.py | 7 +- tests/algorithms/recursion/test_reverse.py | 6 +- tests/ds/test_BST.py | 12 +- tests/ds/test_BSTNode.py | 12 +- tests/ds/test_BinaryHeap.py | 26 + tests/ds/test_DSForests.py | 16 +- tests/ds/test_HashTable.py | 13 +- tests/ds/test_Heap.py | 87 - tests/ds/test_HeapNode.py | 121 + tests/ds/test_MaxHeap.py | 12 +- tests/ds/test_MinHeap.py | 12 +- tests/ds/test_MinMaxHeap.py | 8 +- tests/ds/test_Queue.py | 6 + tests/ds/test_RBT.py | 13 +- 115 files changed, 112593 insertions(+), 332 deletions(-) create mode 100644 docs/ands/algorithms/crypto/caesar.m.html create mode 100644 docs/ands/algorithms/crypto/index.html create mode 100644 docs/ands/algorithms/crypto/one_time_pad.m.html create mode 100644 docs/ands/algorithms/dac/binary_search.m.html create mode 100644 docs/ands/algorithms/dac/find_max_and_min.m.html create mode 100644 docs/ands/algorithms/dac/find_peak.m.html create mode 100644 docs/ands/algorithms/dac/index.html create mode 100644 docs/ands/algorithms/dac/select.m.html create mode 100644 docs/ands/algorithms/dp/change_making.m.html create mode 100644 docs/ands/algorithms/dp/edit_distance.m.html create mode 100644 docs/ands/algorithms/dp/fibonacci.m.html create mode 100644 docs/ands/algorithms/dp/index.html create mode 100644 docs/ands/algorithms/dp/longest_common_subsequence.m.html create mode 100644 docs/ands/algorithms/dp/longest_common_substring.m.html create mode 100644 docs/ands/algorithms/dp/longest_increasing_subsequence.m.html create mode 100644 docs/ands/algorithms/dp/max_non_adjacent_seq_weight.m.html create mode 100644 docs/ands/algorithms/dp/max_sum_contiguous_subsequence.m.html create mode 100644 docs/ands/algorithms/dp/plus_sign_game.m.html create mode 100644 docs/ands/algorithms/dp/rod_cut.m.html create mode 100644 docs/ands/algorithms/dp/subset_sum.m.html create mode 100644 docs/ands/algorithms/dp/zero_one_knapsack.m.html create mode 100644 docs/ands/algorithms/graphs/best_team_of_three.m.html create mode 100644 docs/ands/algorithms/graphs/build_shortest_path.m.html create mode 100644 docs/ands/algorithms/graphs/dfs.m.html create mode 100644 docs/ands/algorithms/graphs/find_triangle.m.html create mode 100644 docs/ands/algorithms/graphs/four_cycle.m.html create mode 100644 docs/ands/algorithms/graphs/index.html create mode 100644 docs/ands/algorithms/graphs/prim.m.html create mode 100644 docs/ands/algorithms/graphs/top_sort.m.html create mode 100644 docs/ands/algorithms/graphs/top_three_friends_of_friends.m.html create mode 100644 docs/ands/algorithms/greedy/activity_selection.m.html create mode 100644 docs/ands/algorithms/greedy/fractional_knapsack.m.html create mode 100644 docs/ands/algorithms/greedy/huffman.m.html create mode 100644 docs/ands/algorithms/greedy/index.html create mode 100644 docs/ands/algorithms/index.html create mode 100644 docs/ands/algorithms/math/arithmetic/index.html create mode 100644 docs/ands/algorithms/math/arithmetic/sum.m.html create mode 100644 docs/ands/algorithms/math/combinatorics/index.html create mode 100644 docs/ands/algorithms/math/combinatorics/n_choose_k.m.html create mode 100644 docs/ands/algorithms/math/index.html create mode 100644 docs/ands/algorithms/parsing/index.html create mode 100644 docs/ands/algorithms/parsing/smep.m.html create mode 100644 docs/ands/algorithms/primes/index.html create mode 100644 docs/ands/algorithms/primes/is_prime.m.html create mode 100644 docs/ands/algorithms/recursion/ackermann.m.html create mode 100644 docs/ands/algorithms/recursion/count.m.html create mode 100644 docs/ands/algorithms/recursion/factorial.m.html create mode 100644 docs/ands/algorithms/recursion/hanoi.m.html create mode 100644 docs/ands/algorithms/recursion/index.html create mode 100644 docs/ands/algorithms/recursion/is_sorted.m.html create mode 100644 docs/ands/algorithms/recursion/make_decimal.m.html create mode 100644 docs/ands/algorithms/recursion/palindrome.m.html create mode 100644 docs/ands/algorithms/recursion/power.m.html create mode 100644 docs/ands/algorithms/recursion/reverse.m.html create mode 100644 docs/ands/algorithms/sorting/bubble_sort.m.html create mode 100644 docs/ands/algorithms/sorting/heap_sort.m.html create mode 100644 docs/ands/algorithms/sorting/index.html create mode 100644 docs/ands/algorithms/sorting/insertion_sort.m.html create mode 100644 docs/ands/algorithms/sorting/merge_sort.m.html create mode 100644 docs/ands/algorithms/sorting/quick_sort.m.html create mode 100644 docs/ands/algorithms/sorting/selection_sort.m.html create mode 100644 docs/ands/algorithms/unclassified/index.html create mode 100644 docs/ands/algorithms/unclassified/max_num_dups.m.html create mode 100644 docs/ands/ds/BST.m.html create mode 100644 docs/ands/ds/DSForests.m.html create mode 100644 docs/ands/ds/Graph.m.html create mode 100644 docs/ands/ds/HashTable.m.html create mode 100644 docs/ands/ds/MaxHeap.m.html create mode 100644 docs/ands/ds/MinHeap.m.html create mode 100644 docs/ands/ds/MinMaxHeap.m.html create mode 100644 docs/ands/ds/MinPriorityQueue.m.html create mode 100644 docs/ands/ds/Queue.m.html create mode 100644 docs/ands/ds/RBT.m.html create mode 100644 docs/ands/ds/Stack.m.html create mode 100644 docs/ands/ds/TST.m.html create mode 100644 docs/ands/ds/heap.m.html create mode 100644 docs/ands/ds/index.html create mode 100644 docs/ands/index.html create mode 100755 tests/ds/test_BinaryHeap.py delete mode 100755 tests/ds/test_Heap.py create mode 100644 tests/ds/test_HeapNode.py diff --git a/ands/ds/BST.py b/ands/ds/BST.py index 518bd088..291e1d9d 100755 --- a/ands/ds/BST.py +++ b/ands/ds/BST.py @@ -2,13 +2,18 @@ # -*- coding: utf-8 -*- """ +# Meta info + Author: Nelson Brochado -Creation: July, 2015 +Created: 01/07/2015 + +Updated: 28/08/2016 + +# Description -Last update: 28/08/2016 +### Coding Conventions -## Names' Conventions In general, if a variable name has more than one word, those words are separated by _ (underscores). Functions' names should roughly describe what the function does. @@ -17,11 +22,13 @@ comments are usually provide on the first occurrence of the name, in order to explain the purpose of such a variable. -### Functions +#### Functions + - Methods that start with _ should not be called, because they might either be "helper" or private functions. -### Parameters +#### Parameters + - `u`, `v`, `z` and `w` are used to indicate that a general `BSTNode` object is expected. - `s` is used to indicate that a source node is expected. @@ -31,12 +38,14 @@ - `ls` is usually used to indicate that a list or a tuple is expected. -### Local Variables +#### Local Variables + - `c` usually indicates some "current" changing variable. - `p` is usually `c`'s parent. -### Docstrings +#### Docstrings + Under methods' signatures, h in O(h) is the height of the tree. Note that the height of a BST varies depending on how elements are inserted and removed. @@ -46,25 +55,22 @@ Other names are self-descriptive. For example, "key" and "value" are self-descriptive. -## Resources - -- [https://en.wikipedia.org/wiki/Binary_search_tree](https://en.wikipedia.org/wiki/Binary_search_tree) - -- [Introduction to Algorithms (3rd edition)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS, chapter 12 +# TODO -- [http://algs4.cs.princeton.edu/32bst/](http://algs4.cs.princeton.edu/32bst/) - -- [http://www.cs.princeton.edu/courses/archive/spr04/cos226/lectures/bst.4up.pdf](http://www.cs.princeton.edu/courses/archive/spr04/cos226/lectures/bst.4up.pdf -) - -- [http://algs4.cs.princeton.edu/32bst/BST.java.html](http://algs4.cs.princeton.edu/32bst/BST.java.html) - -## TODO - Improve the "randomness" of insertion into the BSTImproved class. - Add functions "intersection" and "union". - Implement a recursive version of insert (OPTIONAL). - implement "is balanced" function (http://codereview.stackexchange.com/questions/108459/binary-tree-data-structure) - Maybe the methods of the BSTNode need an improvement in terms of implementation... + +# Resources + +- [https://en.wikipedia.org/wiki/Binary_search_tree](https://en.wikipedia.org/wiki/Binary_search_tree) +- [Introduction to Algorithms (3rd edition)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS, chapter 12 +- [http://algs4.cs.princeton.edu/32bst/](http://algs4.cs.princeton.edu/32bst/) +- [http://www.cs.princeton.edu/courses/archive/spr04/cos226/lectures/bst.4up.pdf](http://www.cs.princeton.edu/courses/archive/spr04/cos226/lectures/bst.4up.pdf) +- [http://algs4.cs.princeton.edu/32bst/BST.java.html](http://algs4.cs.princeton.edu/32bst/BST.java.html) + """ from random import randint diff --git a/ands/ds/DSForests.py b/ands/ds/DSForests.py index ab2f2dac..ac18dc41 100644 --- a/ands/ds/DSForests.py +++ b/ands/ds/DSForests.py @@ -2,15 +2,15 @@ # -*- coding: utf-8 -*- """ -## Meta info +# Meta info Author: Nelson Brochado -Creation: 21/02/16 +Created: 21/02/2016 -Last update: 03/01/16 +Updated: 03/01/2016 -## Description +# Description A disjoint-set (forests) or union-find data structure is a data structure which keeps track of a set of elements partitioned into disjoint (non-overlapping, i.e. their intersection is the empty set) sets. @@ -31,34 +31,26 @@ These two techniques complement each other: applied together, the amortized time per operation is only O( α (n)). -## References +# TODO -- Introduction to algorithms, 3rd, by C.L.R.S., chapter 21.3 +- Deletion operation (OPTIONAL, since it's usually not part of the interface of a disjoint-set data structure) +- Pretty-print(x), for some element x in the disjoint-set data structure. +- Implement the version explained [here](http://algs4.cs.princeton.edu/15uf/) -- [https://en.wikipedia.org/wiki/Disjoint-set_data_structure](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) +# References +- Introduction to algorithms, 3rd, by C.L.R.S., chapter 21.3 +- [https://en.wikipedia.org/wiki/Disjoint-set_data_structure](https://en.wikipedia.org/wiki/Disjoint-set_data_structure) - [http://orionsword.no-ip.org/blog/wordpress/?p=246](http://orionsword.no-ip.org/blog/wordpress/?p=246) - - [http://stackoverflow.com/a/22945492/3924118](http://stackoverflow.com/a/22945492/3924118) - - [http://stackoverflow.com/q/23055236/3924118](http://stackoverflow.com/q/23055236/3924118) - - [https://www.cs.usfca.edu/~galles/JavascriptVisual/DisjointSets.html](https://www.cs.usfca.edu/~galles/JavascriptVisual/DisjointSets.html) to visualize how disjoint-sets work. -## TODO - -- Deletion operation (OPTIONAL, since it's usually not part of the interface of a disjoint-set data structure) - -- Pretty-print(x), for some element x in the disjoint-set data structure. - -- Implement the version explained [here](http://algs4.cs.princeton.edu/15uf/) - """ class DSNode: - def __init__(self, x, rank=0): # This attribute can contain any hashable value. self.value = x @@ -97,7 +89,6 @@ def __repr__(self): class DSForests: - def __init__(self): # keeps tracks of the DSNodes in this disjoint-set forests. self.sets = {} diff --git a/ands/ds/HashTable.py b/ands/ds/HashTable.py index 849e7dc7..9cb77295 100644 --- a/ands/ds/HashTable.py +++ b/ands/ds/HashTable.py @@ -2,11 +2,15 @@ # -*- coding: utf-8 -*- """ +# Meta info + Author: Nelson Brochado -Creation: June, 2015 +Created: 01/06/2015 + +Updated: 21/02/2016 -Last update: 21/02/16 +# Description Hash table that re-sizes if no more slot is available. The process of re-sizing doubles the current capacity of the hash table each time (for now). @@ -19,10 +23,11 @@ h[12] = 3 print(h[12]) -## References -- [http://interactivepython.org/runestone/static/pythonds/SortSearch/Hashing.html](http://interactivepython.org/runestone/static/pythonds/SortSearch/Hashing.html) +# References +- [http://interactivepython.org/runestone/static/pythonds/SortSearch/Hashing.html](http://interactivepython.org/runestone/static/pythonds/SortSearch/Hashing.html) - [http://stackoverflow.com/questions/279539/best-way-to-remove-an-entry-from-a-hash-table](http://stackoverflow.com/questions/279539/best-way-to-remove-an-entry-from-a-hash-table) + """ from tabulate import tabulate diff --git a/ands/ds/Heap.py b/ands/ds/Heap.py index 5ad91b61..60d27f77 100755 --- a/ands/ds/Heap.py +++ b/ands/ds/Heap.py @@ -2,38 +2,37 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info -Creation: July, 2015 +Author: Nelson Brochado -Updated: 21/01/2017 +Created: 01/07/2015 -Base abstract class to represent heaps. -See `MinHeap` and `MaxHeap` if you want to instantiate heap objects. +Updated: 05/02/2017 -## [NotImplementedError](https://docs.python.org/3/library/exceptions.html#NotImplementedError) +# Description -This exception is derived from `RuntimeError`. -In user defined base classes, -abstract methods should raise this exception -when they require derived classes to override the method. +This module contains currently the classes `HeapNode`, which is a class to represent nodes of heaps, +the class `BinaryHeap` and a function which returns a pretty string representation of a heap passed as parameter. -## References +# References - [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html) - Slides by prof. A. Carzaniga - Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS +- [NotImplementedError](https://docs.python.org/3/library/exceptions.html#NotImplementedError) + """ import io import math +from collections import Iterable -__all__ = ["BaseHeap", "Heap", "HeapNode"] +__all__ = ["BinaryHeap", "HeapNode", "build_pretty_binary_heap"] class HeapNode: - """All elements of Heap objects are represented - with objects of the class HeapNode.""" + """All elements of heap objects are represented with objects of the class HeapNode.""" def __init__(self, key, value=None): """`key` is the priority used to heapify the heap, @@ -69,15 +68,17 @@ def __repr__(self): return str(self.value) + " -> " + str(self.key) -class BaseHeap: +class BinaryHeap: + """Abstract class to represent binary heaps. + + `MinHeap`, `MaxHeap` and `MinMaxHeap` all derive from this class.""" + def __init__(self, ls=None): if ls is None: ls = [] - self.heap = BaseHeap._create_list_of_heap_nodes(ls) + self.heap = BinaryHeap._create_list_of_heap_nodes(ls) self.build_heap() - # ABSTRACT METHODS - def push_down(self, i: int) -> None: """Classical so-called heapify operation for heaps.""" raise NotImplementedError() @@ -86,6 +87,9 @@ def push_up(self, i: int) -> None: """Classical reverse-heapify operation for heaps.""" raise NotImplementedError() + def delete(self, i: int) -> HeapNode: + raise NotImplementedError() + def replace(self, i: int, x) -> HeapNode: """Replaces the `HeapNode` object at index `i` with `x`. @@ -94,11 +98,6 @@ def replace(self, i: int, x) -> HeapNode: whose key and value are equal to `x`.""" raise NotImplementedError() - def delete(self, i: int) -> HeapNode: - raise NotImplementedError() - - # BASE-IMPLEMENTED METHODS - def build_heap(self) -> list: """Builds the heap data structure from `self.heap`.""" if self.heap: @@ -335,19 +334,11 @@ def is_on_odd_level(self, i: int) -> bool: """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`).""" return not self.is_on_even_level(i) - # PRINT FUNCTIONS - def __str__(self) -> str: return str(self.heap) def __repr__(self) -> str: - return HeapPrinter(self.heap).show() - - def show(self, total_width=36, fill=" ") -> None: - """Pretty-prints this heap.""" - print(HeapPrinter(self.heap).show(total_width, fill)) - - # STATIC FUNCTIONS + return build_pretty_binary_heap(self.heap) @staticmethod def _create_list_of_heap_nodes(ls: list) -> list: @@ -371,75 +362,48 @@ def _create_list_of_heap_nodes(ls: list) -> list: return nodes -class Heap(BaseHeap): - # Abstract class from which MinHeap and MaxHeap derive. - # MinMaxHeap instead derives from the root abstract class BaseHeap. - - def __init__(self, ls=None): - BaseHeap.__init__(self, ls) - - def delete(self, i: int) -> HeapNode: - """Deletes and returns the `HeapNode` object at index `i`. - - `IndexError` is raised if `i` is not a valid index. - - Implementation based on: - [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html) - - **Time Complexity:** O(log2 h), - where `h` is the number of nodes rooted at `i`.""" - if not self.is_good_index(i): - raise IndexError("i is not a valid index.") - if i == self.size() - 1: - return self.heap.pop() - self.swap(i, self.size() - 1) - d = self.heap.pop() - self.push_down(i) - return d +def build_pretty_binary_heap(heap: list, total_width=36, fill=" ") -> str: + """Returns a string (which can be printed) representing `heap` as a tree. + Adapted for Python 3 from: [http://pymotw.com/2/heapq/](http://pymotw.com/2/heapq/). -class HeapPrinter: - def __init__(self, heap): - self.heap = heap + To increase/decrease the horizontal space between nodes, + just increase/decrease the float number h_space. - def show(self, total_width=36, fill=" ") -> str: - """Adapted for Python 3 from: - [http://pymotw.com/2/heapq/](http://pymotw.com/2/heapq/). + To increase/decrease the vertical space between nodes, + just increase/decrease the integer number v_space. + Note that v_space must be an integer. - To increase/decrease the horizontal space between nodes, - just increase/decrease the float number h_space. + To change the length of the line under the heap, + you can simply change the line_length variable.""" + if not isinstance(heap, Iterable): + raise TypeError("heap must be an iterable object") + if len(heap) == 0: + return "Nothing to print: heap is empty." - To increase/decrease the vertical space between nodes, - just increase/decrease the integer number v_space. - Note that v_space must be an integer. + output = io.StringIO() + last_row = -1 - To change the length of the line under the heap, - you can simply change the line_length variable.""" - if not self.heap: - return "Nothing to print: heap is empty." + h_space = 3.0 # float + v_space = 2 # int - output = io.StringIO() - last_row = -1 - - h_space = 3.0 # float - v_space = 2 # int + for i, heap_node in enumerate(heap): + if i: + row = int(math.floor(math.log(i + 1, 2))) + else: + row = 0 - for i, heap_node in enumerate(self.heap): - if i: - row = int(math.floor(math.log(i + 1, 2))) - else: - row = 0 - if row != last_row: - output.write("\n" * v_space) + if row != last_row: + output.write("\n" * v_space) - columns = 2 ** row + columns = 2 ** row - column_width = int(math.floor((total_width * h_space) / columns)) - output.write(str(heap_node).center(column_width, fill)) - last_row = row + column_width = int(math.floor((total_width * h_space) / columns)) + output.write(str(heap_node).center(column_width, fill)) + last_row = row - s = output.getvalue() + "\n" + s = output.getvalue() + "\n" - line_length = total_width + 15 # int - s += ('-' * line_length + "\n") - return s + line_length = total_width + 15 # int + s += ('-' * line_length + "\n") + return s diff --git a/ands/ds/MaxHeap.py b/ands/ds/MaxHeap.py index 0351f9b0..72452717 100644 --- a/ands/ds/MaxHeap.py +++ b/ands/ds/MaxHeap.py @@ -2,32 +2,35 @@ # -*- coding: utf-8 -*- """ +# Meta info + Author: Nelson Brochado -Creation: 15/02/16 +Created: 15/02/2016 + +Updated: 05/02/2017 -Last update: 28/08/16 +# Description Mirror-class to the MinHeap class. For more info, see the introductory doc-strings of the file [`MinHeap.py`](MinHeap.py). -## References +# References - [https://en.wikipedia.org/wiki/Binary_heap](https://en.wikipedia.org/wiki/Binary_heap) - - Slides by prof. A. Carzaniga - - Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS + """ -from ands.ds.Heap import Heap, HeapNode +from ands.ds.heap import BinaryHeap, HeapNode __all__ = ["MaxHeap", "is_max_heap"] -class MaxHeap(Heap): +class MaxHeap(BinaryHeap): def __init__(self, ls=None): - Heap.__init__(self, ls) + BinaryHeap.__init__(self, ls) def push_down(self, i: int) -> None: """'Max-heapify' this max-heap starting from index `i`. @@ -81,6 +84,25 @@ def remove_max(self) -> HeapNode: self.push_down(0) return m + def delete(self, i: int) -> HeapNode: + """Deletes and returns the `HeapNode` object at index `i`. + + `IndexError` is raised if `i` is not a valid index. + + Implementation based on: + [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html) + + **Time Complexity:** O(log2 h), + where `h` is the number of nodes rooted at `i`.""" + if not self.is_good_index(i): + raise IndexError("i is not a valid index.") + if i == self.size() - 1: + return self.heap.pop() + self.swap(i, self.size() - 1) + d = self.heap.pop() + self.push_down(i) + return d + def replace(self, i: int, x) -> HeapNode: """Replaces element at index `i` with `x`. diff --git a/ands/ds/MinHeap.py b/ands/ds/MinHeap.py index 6355463f..6f3fc852 100755 --- a/ands/ds/MinHeap.py +++ b/ands/ds/MinHeap.py @@ -2,11 +2,15 @@ # -*- coding: utf-8 -*- """ +# Meta info + Author: Nelson Brochado -Creation: July, 2015 +Created: 01/07/2015 + +Updated: 05/02/2017 -Last update: 28/08/16 +# Description A binary min-heap is a data structure similar to a binary tree, where the parent nodes are smaller or equal to their children. @@ -29,23 +33,21 @@ Note that these indexes are for 0-index based lists (or arrays). -## References +# References - [https://en.wikipedia.org/wiki/Binary_heap](https://en.wikipedia.org/wiki/Binary_heap) - - Slides by prof. A. Carzaniga - - Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS """ -from ands.ds.Heap import Heap, HeapNode +from ands.ds.heap import BinaryHeap, HeapNode __all__ = ["MinHeap", "is_min_heap"] -class MinHeap(Heap): +class MinHeap(BinaryHeap): def __init__(self, ls=None): - Heap.__init__(self, ls) + BinaryHeap.__init__(self, ls) def push_down(self, i: int) -> None: """'Min-heapify' this min-heap starting from index `i`. @@ -103,6 +105,25 @@ def remove_min(self) -> HeapNode: self.push_down(0) return m + def delete(self, i: int) -> HeapNode: + """Deletes and returns the `HeapNode` object at index `i`. + + `IndexError` is raised if `i` is not a valid index. + + Implementation based on: + [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html) + + **Time Complexity:** O(log2 h), + where `h` is the number of nodes rooted at `i`.""" + if not self.is_good_index(i): + raise IndexError("i is not a valid index.") + if i == self.size() - 1: + return self.heap.pop() + self.swap(i, self.size() - 1) + d = self.heap.pop() + self.push_down(i) + return d + def replace(self, i: int, x) -> HeapNode: """Replaces element at index `i` with `x`. diff --git a/ands/ds/MinMaxHeap.py b/ands/ds/MinMaxHeap.py index ad3ab73f..88a9fbf0 100644 --- a/ands/ds/MinMaxHeap.py +++ b/ands/ds/MinMaxHeap.py @@ -2,11 +2,15 @@ # -*- coding: utf-8 -*- """ +# Meta info + Author: Nelson Brochado -Creation: 18/02/16 +Created: 18/02/2016 + +Updated: 29/12/2016 -Last update: 29/12/16 +# Description Min-Max Heap is a heap that supports find-min and find-max operations in constant time. Moreover, both remove-min and remove-max are supported in logarithmic time. @@ -51,24 +55,24 @@ - `merge` in O(n + m) time - `clear` in O(1) time -## TODO +# TODO - `find-kth`, i.e. find the kth smallest element in the structure, in O(1) time - `delete-kth`, i.e. delete the kth smallest element, in O(log n) time -## References: +# References - [Min-Max Heaps and Generalized Priority Queues](http://www.akira.ruc.dk/~keld/teaching/algoritmedesign_f03/Artikler/02/Atkinson86.pdf), original paper describing and introducing the min-max heap data structure, by M. D. Atkinson, J.R. Sack, N. Santoro and T. Strothotte. - [http://www.diku.dk/forskning/performance-engineering/Jesper/heaplab/heapsurvey_html/node11.html](http://www.diku.dk/forskning/performance-engineering/Jesper/heaplab/heapsurvey_html/node11.html) """ -from ands.ds.Heap import BaseHeap, HeapNode +from ands.ds.heap import BinaryHeap, HeapNode -class MinMaxHeap(BaseHeap): +class MinMaxHeap(BinaryHeap): def __init__(self, ls=None): - BaseHeap.__init__(self, ls) + BinaryHeap.__init__(self, ls) def push_down(self, i: int) -> None: """Also called `bubble-down` or `shift-down`.""" diff --git a/ands/ds/Queue.py b/ands/ds/Queue.py index 9f9520d8..09bd26cd 100755 --- a/ands/ds/Queue.py +++ b/ands/ds/Queue.py @@ -5,7 +5,9 @@ # Meta info Author: Nelson Brochado + Created: 02/07/2015 + Updated: 04/02/2017 # Description diff --git a/ands/ds/RBT.py b/ands/ds/RBT.py index 44f7a35e..fac02985 100755 --- a/ands/ds/RBT.py +++ b/ands/ds/RBT.py @@ -2,12 +2,15 @@ # -*- coding: utf-8 -*- """ +# Meta info + Author: Nelson Brochado -Creation: July, 2015 +Created: 01/08/2015 -Last update: 28/08/16 +Updated: 28/08/2016 +# Description ## Red-black Tree Property @@ -83,16 +86,16 @@ insert, etc, so the complexity of those operations is T(n) = O(h), that is T(n) = O(log2 n), which is also the worst case complexity. -## TODO +# TODO + - Override needed methods inherited from BST. -## References +# References - [https://en.wikipedia.org/wiki/Red%E2%80%93black_tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree) - - Slides by prof. A. Carzaniga - - Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS + """ import math diff --git a/ands/ds/Stack.py b/ands/ds/Stack.py index 54eb7f69..fd186d7f 100755 --- a/ands/ds/Stack.py +++ b/ands/ds/Stack.py @@ -5,7 +5,9 @@ # Meta info Author: Nelson Brochado + Created: 05/07/2015 + Updated: 04/02/2017 # Description @@ -40,6 +42,7 @@ for the time complexity analysis of the size operation. - [http://stackoverflow.com/questions/12342457/what-is-the-big-o-notation-for-the-len-function-in-python](http://stackoverflow.com/questions/12342457/what-is-the-big-o-notation-for-the-len-function-in-python), for other time complexity analysis of the list class. + """ from collections import Iterable diff --git a/ands/ds/TST.py b/ands/ds/TST.py index f36496b0..87e98fa4 100644 --- a/ands/ds/TST.py +++ b/ands/ds/TST.py @@ -5,7 +5,9 @@ # Meta info Author: Nelson Brochado + Created: 05/09/2015 + Updated: 03/02/2017 # Description @@ -541,10 +543,10 @@ def keys_that_match(self, pattern: str) -> list: A key `k` of length `m` matches `pattern` if: - 1. m = length(pattern), and - 2. Either k[i] == pattern[i] or k[i] == '.'. - - Example: if `pattern == ".ood"`, - then `k == "good"` would match, but not `k == "foodie"`. + 1. m = length(pattern), and + 2. Either k[i] == pattern[i] or k[i] == '.'. + - Example: if `pattern == ".ood"`, + then `k == "good"` would match, but not `k == "foodie"`. If `pattern` is not a `str`, `TypeError` is raised. If `pattern` is an empty string, `ValueError` is raised.""" diff --git a/automate.sh b/automate.sh index d318fc7b..69df68b8 100755 --- a/automate.sh +++ b/automate.sh @@ -115,7 +115,7 @@ test_in_virtual_environment() run_tests fi - #new_docs + new_docs deactivate printf "${YELLOW}Exited from virtual environment.${NORMAL}\n\n" diff --git a/docs/ands/algorithms/crypto/caesar.m.html b/docs/ands/algorithms/crypto/caesar.m.html new file mode 100644 index 00000000..88b1d813 --- /dev/null +++ b/docs/ands/algorithms/crypto/caesar.m.html @@ -0,0 +1,1222 @@ + + + + + + ands.algorithms.crypto.caesar API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.crypto.caesar module

+

Author: Nelson Brochado

+

Caesar cipher that accepts messages of characters +whose value returned by the ord function is between 0 and 2**16 - 1. +Caeser cipher is far from being a good cryptographic algorithm, +so, in general, you should prefer other algorithms.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Caesar cipher that accepts messages of characters
+whose value returned by the `ord` function is between 0 and 2**16 - 1.
+Caeser cipher is far from being a good cryptographic algorithm,
+so, in general, you should prefer other algorithms.
+"""
+
+import random
+
+# Using `sys.maxunicode` causes some problems with encondings
+# i.e. an error is thrown because Tcl/Tk do not support certain characters
+MAX = 2 ** 16 - 1
+
+
+def move_char(c, k):
+    return chr(ord(c) + k)
+
+
+def encrypt(m, k):
+    return "".join(move_char(c, k) for c in m)
+
+
+def decrypt(cipher, k):
+    return "".join(move_char(c, -k) for c in cipher)
+
+
+# Example of polyalphabetic encryption
+
+def multi_encrypt(m, keys):
+    """Given a message m and a set of keys,
+    it encrypts each symbol of m with a random key from keys.
+    The random pattern is the second item of the tuple returned."""
+    pattern = []
+    cipher = []
+
+    for c in m:
+        k = random.choice(keys)
+        pattern.append(k)
+        cipher.append(move_char(c, k))
+
+    return "".join(cipher), pattern
+
+
+def multi_decrypt(cipher, pattern):
+    """`len(pattern) == len(keys)`,
+    where `keys` are the keys passed to `multi_encrypt`."""
+    return "".join(move_char(cipher[i], -k) for i, k in enumerate(pattern))
+
+
+ +
+ +
+

Module variables

+
+

var MAX

+ + +
+
+ +
+ +

Functions

+ +
+
+

def decrypt(

cipher, k)

+
+ + + + +
+ +
+
def decrypt(cipher, k):
+    return "".join(move_char(c, -k) for c in cipher)
+
+
+
+ +
+ + +
+
+

def encrypt(

m, k)

+
+ + + + +
+ +
+
def encrypt(m, k):
+    return "".join(move_char(c, k) for c in m)
+
+
+
+ +
+ + +
+
+

def move_char(

c, k)

+
+ + + + +
+ +
+
def move_char(c, k):
+    return chr(ord(c) + k)
+
+
+
+ +
+ + +
+
+

def multi_decrypt(

cipher, pattern)

+
+ + + + +

len(pattern) == len(keys), +where keys are the keys passed to multi_encrypt.

+
+ +
+
def multi_decrypt(cipher, pattern):
+    """`len(pattern) == len(keys)`,
+    where `keys` are the keys passed to `multi_encrypt`."""
+    return "".join(move_char(cipher[i], -k) for i, k in enumerate(pattern))
+
+
+
+ +
+ + +
+
+

def multi_encrypt(

m, keys)

+
+ + + + +

Given a message m and a set of keys, +it encrypts each symbol of m with a random key from keys. +The random pattern is the second item of the tuple returned.

+
+ +
+
def multi_encrypt(m, keys):
+    """Given a message m and a set of keys,
+    it encrypts each symbol of m with a random key from keys.
+    The random pattern is the second item of the tuple returned."""
+    pattern = []
+    cipher = []
+
+    for c in m:
+        k = random.choice(keys)
+        pattern.append(k)
+        cipher.append(move_char(c, k))
+
+    return "".join(cipher), pattern
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/crypto/index.html b/docs/ands/algorithms/crypto/index.html new file mode 100644 index 00000000..26eede58 --- /dev/null +++ b/docs/ands/algorithms/crypto/index.html @@ -0,0 +1,1049 @@ + + + + + + ands.algorithms.crypto API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.crypto module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms.crypto.caesar

+ + +

Author: Nelson Brochado

+

Caesar cipher that accepts messages of characters +whose value returned by the ord function is between 0 and 2**16 - 1. +Caeser cipher is far from being a good cryptographic algorithm, +so, in general, you should prefer other algorithms.

+ +
+
+

ands.algorithms.crypto.one_time_pad

+ + +

Author: Nelson Brochado

+

One Time Pad cipher algorithm, which provides 'perfect secrecy', +but has some drawbacks, for example the key used +must be at least of the same length of the original message.

+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/crypto/one_time_pad.m.html b/docs/ands/algorithms/crypto/one_time_pad.m.html new file mode 100644 index 00000000..b6d70e5a --- /dev/null +++ b/docs/ands/algorithms/crypto/one_time_pad.m.html @@ -0,0 +1,1096 @@ + + + + + + ands.algorithms.crypto.one_time_pad API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.crypto.one_time_pad module

+

Author: Nelson Brochado

+

One Time Pad cipher algorithm, which provides 'perfect secrecy', +but has some drawbacks, for example the key used +must be at least of the same length of the original message.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+One Time Pad cipher algorithm, which provides 'perfect secrecy',
+but has some drawbacks, for example the key used
+must be at least of the same length of the original message.
+"""
+
+
+def encrypt(message, key):
+    """Encrypt message using key according to the one-time-pad algorithm."""
+    return "".join(chr(ord(i) ^ ord(j)) for (i, j) in zip(message, key))
+
+
+def decrypt(ciphertext, key):
+    """Decript ciphertext using key according to the OTP algorithm."""
+    return encrypt(ciphertext, key)
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def decrypt(

ciphertext, key)

+
+ + + + +

Decript ciphertext using key according to the OTP algorithm.

+
+ +
+
def decrypt(ciphertext, key):
+    """Decript ciphertext using key according to the OTP algorithm."""
+    return encrypt(ciphertext, key)
+
+
+
+ +
+ + +
+
+

def encrypt(

message, key)

+
+ + + + +

Encrypt message using key according to the one-time-pad algorithm.

+
+ +
+
def encrypt(message, key):
+    """Encrypt message using key according to the one-time-pad algorithm."""
+    return "".join(chr(ord(i) ^ ord(j)) for (i, j) in zip(message, key))
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dac/binary_search.m.html b/docs/ands/algorithms/dac/binary_search.m.html new file mode 100644 index 00000000..bbc0da09 --- /dev/null +++ b/docs/ands/algorithms/dac/binary_search.m.html @@ -0,0 +1,1225 @@ + + + + + + ands.algorithms.dac.binary_search API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dac.binary_search module

+

Author: Nelson Brochado

+

Binary search is a "divide and conquer" search algorithm +that operates on a sorted list.

+

If you want to know more about binary search:

+
    +
  • http://en.wikipedia.org/wiki/Binary_search_algorithm
  • +
  • http://interactivepython.org/runestone/static/pythonds/SortSearch/TheBinarySearch.html
  • +
+

Note that no serious tests have been made on this algorihm.

+ + + + +
+ +
+ +

Functions

+ +
+
+

def binary_search_i(

ls, value)

+
+ + + + +

Iterative binary search algorithm.

+

Time complexity: O(n*log_2(n))

+
+ +
+
def binary_search_i(ls, value):
+    """Iterative binary search algorithm.
+
+    Time complexity: O(n*log_2(n))
+    """
+    if len(ls) == 0:
+        return False
+    else:
+        start = 0
+        end = len(ls) - 1
+        while start <= end:
+            mid = (start + end) // 2
+
+            if ls[mid] == value:
+                return True
+
+            elif ls[mid] < value:  # search on the right
+                start = mid + 1
+
+            else:  # search on the left
+                end = mid - 1
+        return False
+
+
+
+ +
+ + +
+
+

def binary_search_r(

ls, value)

+
+ + + + +

Recursive binary search.

+

Note that this algorithm uses the slice operator, +which creates a sub-lists. +slides is an operation that runs in O(k) time... +To repair this, we can pass the indices, +instead of creating a new sub-list using the slice operator

+
+ +
+
def binary_search_r(ls, value):
+    """Recursive binary search.
+
+    Note that this algorithm uses the slice operator,
+    which creates a sub-lists.
+    slides is an operation that runs in O(k) time...
+    To repair this, we can pass the indices,
+    instead of creating a new sub-list using the slice operator"""
+    if len(ls) == 0:  # basis
+        return False
+    else:
+        mid = len(ls) // 2
+        if ls[mid] == value:
+            return True
+        elif ls[mid] < value:
+            return binary_search_r(ls[mid + 1:], value)
+        else:
+            return binary_search_r(ls[0:mid], value)
+
+
+
+ +
+ + +
+ + + + + +

Searches for item in ls

+

Time complexity: O(n), +where n is the size of ls.

+
+ + +
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dac/find_max_and_min.m.html b/docs/ands/algorithms/dac/find_max_and_min.m.html new file mode 100644 index 00000000..8aebb777 --- /dev/null +++ b/docs/ands/algorithms/dac/find_max_and_min.m.html @@ -0,0 +1,1146 @@ + + + + + + ands.algorithms.dac.find_max_and_min API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dac.find_max_and_min module

+

Author: Nelson Brochado

+

Finding the minimum and the maximum +of a list of numbers using the "Divide and Conquer" strategy.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Finding the minimum and the maximum
+of a list of numbers using the "Divide and Conquer" strategy.
+"""
+
+
+def find_max(ls):
+    """Divide and Conquer not in-place algorithm
+    to find the maximum element of a list or tuple"""
+
+    if len(ls) == 1:
+        return ls[0]
+    elif len(ls) == 2:
+        return ls[0] if ls[0] > ls[1] else ls[1]
+    else:
+        mid = len(ls) // 2
+        m1 = find_max(ls[0:mid])
+        m2 = find_max(ls[mid:])
+        return m1 if m1 > m2 else m2
+
+
+def find_min(ls):
+    """'Divide and Conquer' not in-place algorithm
+    to find the minimum element of a list or tuple."""
+
+    if len(ls) == 1:
+        return ls[0]
+    elif len(ls) == 2:
+        return ls[0] if ls[0] < ls[1] else ls[1]
+    else:
+        mid = len(ls) // 2
+        m1 = find_min(ls[0:mid])
+        m2 = find_min(ls[mid:])
+        return m1 if m1 < m2 else m2
+
+
+if __name__ == "__main__":
+    from random import randint
+
+    a = [randint(0, 10) for _ in range(10)]
+    print("List:", a)
+
+    print("Min:", find_min(a))
+    print("Max:", find_max(a))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def find_max(

ls)

+
+ + + + +

Divide and Conquer not in-place algorithm +to find the maximum element of a list or tuple

+
+ +
+
def find_max(ls):
+    """Divide and Conquer not in-place algorithm
+    to find the maximum element of a list or tuple"""
+
+    if len(ls) == 1:
+        return ls[0]
+    elif len(ls) == 2:
+        return ls[0] if ls[0] > ls[1] else ls[1]
+    else:
+        mid = len(ls) // 2
+        m1 = find_max(ls[0:mid])
+        m2 = find_max(ls[mid:])
+        return m1 if m1 > m2 else m2
+
+
+
+ +
+ + +
+
+

def find_min(

ls)

+
+ + + + +

'Divide and Conquer' not in-place algorithm +to find the minimum element of a list or tuple.

+
+ +
+
def find_min(ls):
+    """'Divide and Conquer' not in-place algorithm
+    to find the minimum element of a list or tuple."""
+
+    if len(ls) == 1:
+        return ls[0]
+    elif len(ls) == 2:
+        return ls[0] if ls[0] < ls[1] else ls[1]
+    else:
+        mid = len(ls) // 2
+        m1 = find_min(ls[0:mid])
+        m2 = find_min(ls[mid:])
+        return m1 if m1 < m2 else m2
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dac/find_peak.m.html b/docs/ands/algorithms/dac/find_peak.m.html new file mode 100644 index 00000000..5d91b6c2 --- /dev/null +++ b/docs/ands/algorithms/dac/find_peak.m.html @@ -0,0 +1,1164 @@ + + + + + + ands.algorithms.dac.find_peak API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dac.find_peak module

+

Author: Nelson Brochado

+

The two algorithms to find the peak below +can return different correct answers, +because they operate differently.

+

Resources: +- https://www.youtube.com/watch?v=HtSuA80QTyo&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb&spfreload=10

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+The two algorithms to find the peak below
+can return different correct answers,
+because they operate differently.
+
+Resources:
+- https://www.youtube.com/watch?v=HtSuA80QTyo&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb&spfreload=10
+"""
+
+
+def find_peak_brute_force(ls):
+    """Finds the first peak in ls.
+    If there's no peak, None is returned.
+
+    A peak ls[i] satisfies the following condition:
+    ls[i - 1] <= ls[i] >= ls[i + 1]
+    for i=1...len(ls) - 2.
+    In other words, A[i] is a peak
+    if it is not smaller than its neighbors,
+
+    Time complexity: O(n),
+    where n is the size of ls."""
+    for i in range(1, len(ls) - 1):
+        if ls[i - 1] <= ls[i] >= ls[i + 1]:
+            return i
+
+
+def _find_peak_aux(ls, i, j):
+    """Using Divide-and-Conquer paradigm."""
+    m = (i + j) // 2
+
+    if 0 < m < len(ls) - 1:
+
+        if ls[m - 1] <= ls[m] >= ls[m + 1]:
+            return m
+
+        elif ls[m - 1] > ls[m]:
+            return _find_peak_aux(ls, i, m - 1)
+
+        elif ls[m] < ls[m + 1]:
+            return _find_peak_aux(ls, m + 1, j)
+    else:
+        return -1
+
+
+def find_peak(ls):
+    """Returns the index of a peak in ls.
+    If there's no peak, -1 is returned."""
+    return _find_peak_aux(ls, 0, len(ls) - 1)
+
+
+if __name__ == "__main__":
+    from random import randint
+
+    a = [randint(0, 10) for _ in range(10)]
+    print("List:", a)
+
+    print(find_peak_brute_force(a))
+    print(find_peak(a))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def find_peak(

ls)

+
+ + + + +

Returns the index of a peak in ls. +If there's no peak, -1 is returned.

+
+ +
+
def find_peak(ls):
+    """Returns the index of a peak in ls.
+    If there's no peak, -1 is returned."""
+    return _find_peak_aux(ls, 0, len(ls) - 1)
+
+
+
+ +
+ + +
+
+

def find_peak_brute_force(

ls)

+
+ + + + +

Finds the first peak in ls. +If there's no peak, None is returned.

+

A peak ls[i] satisfies the following condition: +ls[i - 1] <= ls[i] >= ls[i + 1] +for i=1...len(ls) - 2. +In other words, A[i] is a peak +if it is not smaller than its neighbors,

+

Time complexity: O(n), +where n is the size of ls.

+
+ +
+
def find_peak_brute_force(ls):
+    """Finds the first peak in ls.
+    If there's no peak, None is returned.
+
+    A peak ls[i] satisfies the following condition:
+    ls[i - 1] <= ls[i] >= ls[i + 1]
+    for i=1...len(ls) - 2.
+    In other words, A[i] is a peak
+    if it is not smaller than its neighbors,
+
+    Time complexity: O(n),
+    where n is the size of ls."""
+    for i in range(1, len(ls) - 1):
+        if ls[i - 1] <= ls[i] >= ls[i + 1]:
+            return i
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dac/index.html b/docs/ands/algorithms/dac/index.html new file mode 100644 index 00000000..a17f76b5 --- /dev/null +++ b/docs/ands/algorithms/dac/index.html @@ -0,0 +1,1074 @@ + + + + + + ands.algorithms.dac API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dac module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms.dac.binary_search

+ + +

Author: Nelson Brochado

+

Binary search is a "divide and conquer" search algorithm +that operates on a sorted list.

+

If you want to know more about binary search:

+
    +
  • http://en.wikipedia.org/wiki/Binary_search_algorithm
  • +
  • http://interactivepython.org/runestone/static/pythonds/SortSearch/TheBinarySearch...
  • +
+ +
+
+

ands.algorithms.dac.find_max_and_min

+ + +

Author: Nelson Brochado

+

Finding the minimum and the maximum +of a list of numbers using the "Divide and Conquer" strategy.

+ +
+
+

ands.algorithms.dac.find_peak

+ + +

Author: Nelson Brochado

+

The two algorithms to find the peak below +can return different correct answers, +because they operate differently.

+

Resources: +- https://www.youtube.com/watch?v=HtSuA80QTyo&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb&spfreload=10

+ +
+
+

ands.algorithms.dac.select

+ + +

Author: Nelson Brochado

+

Find an element x in a list, +such that at most k elements of the list are less than x.

+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dac/select.m.html b/docs/ands/algorithms/dac/select.m.html new file mode 100644 index 00000000..b384311b --- /dev/null +++ b/docs/ands/algorithms/dac/select.m.html @@ -0,0 +1,1151 @@ + + + + + + ands.algorithms.dac.select API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dac.select module

+

Author: Nelson Brochado

+

Find an element x in a list, +such that at most k elements of the list are less than x.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Find an element x in a list,
+such that at most k elements of the list are less than x.
+"""
+
+
+def partition(ls: list, start: int, end: int):
+    """Partition algorithm used also by the quick-sort algorithm.
+    It basically returns the index i of a number in ls,
+    such that all elements on the left of ls[i] are less than ls[i],
+    and all elements on the right of ls[i] are greater than ls[i].
+    """
+    pivot = ls[end]
+    p = start
+
+    for i in range(start, end):
+        if ls[i] <= pivot:
+            ls[p], ls[i] = ls[i], ls[p]
+            p += 1
+
+    ls[p], ls[end] = ls[end], ls[p]
+    return p
+
+
+def select(ls: list, k: int):
+    """Find an element x in ls,
+    such that at most k elements of ls are less than x.
+    """
+    p = partition(ls, 0, len(ls) - 1)  # p := pivot's index
+
+    if p == k:
+        return ls[p]
+    elif p > k:
+        return select(ls[0:p], k)
+    else:  # p < k
+        return select(ls[p + 1:], k - p - 1)
+
+
+if __name__ == "__main__":
+    from random import randint
+
+    a = [randint(0, 10) for _ in range(10)]
+    print("List:", a)
+
+    print("Selected:", select(a, 4))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def partition(

ls, start, end)

+
+ + + + +

Partition algorithm used also by the quick-sort algorithm. +It basically returns the index i of a number in ls, +such that all elements on the left of ls[i] are less than ls[i], +and all elements on the right of ls[i] are greater than ls[i].

+
+ +
+
def partition(ls: list, start: int, end: int):
+    """Partition algorithm used also by the quick-sort algorithm.
+    It basically returns the index i of a number in ls,
+    such that all elements on the left of ls[i] are less than ls[i],
+    and all elements on the right of ls[i] are greater than ls[i].
+    """
+    pivot = ls[end]
+    p = start
+
+    for i in range(start, end):
+        if ls[i] <= pivot:
+            ls[p], ls[i] = ls[i], ls[p]
+            p += 1
+
+    ls[p], ls[end] = ls[end], ls[p]
+    return p
+
+
+
+ +
+ + +
+
+

def select(

ls, k)

+
+ + + + +

Find an element x in ls, +such that at most k elements of ls are less than x.

+
+ +
+
def select(ls: list, k: int):
+    """Find an element x in ls,
+    such that at most k elements of ls are less than x.
+    """
+    p = partition(ls, 0, len(ls) - 1)  # p := pivot's index
+
+    if p == k:
+        return ls[p]
+    elif p > k:
+        return select(ls[0:p], k)
+    else:  # p < k
+        return select(ls[p + 1:], k - p - 1)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/change_making.m.html b/docs/ands/algorithms/dp/change_making.m.html new file mode 100644 index 00000000..81c8858e --- /dev/null +++ b/docs/ands/algorithms/dp/change_making.m.html @@ -0,0 +1,1349 @@ + + + + + + ands.algorithms.dp.change_making API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.change_making module

+

Author: Nelson Brochado

+

Problem (https://en.wikipedia.org/wiki/Change-making_problem): +Given a set of coins, which is the smallest subset of these coins, +such that summed together yields a certain number n.

+

This problem is similar to the integer knapsack problem, +the different is that here values=weights.

+

This problem can be solved using dynamic programming.

+

Proof that it exhibits optimal substructure.

+

Suppose S is the optimal solution for making n cents. +Then S' = S - c, where c is a coin in the optimal solution S, +is an optimal solution for making n - c cents. +Suppose S' is not the optimal solution for making n - c cents, +then there exists an optimal solution X != S'. +Now, if we add c to X, we obtain an optimal solution for making n cents, +but this contradicts that fact that S is the optimal solution.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Problem (https://en.wikipedia.org/wiki/Change-making_problem):
+Given a set of coins, which is the smallest subset of these coins,
+such that summed together yields a certain number n.
+
+This problem is similar to the integer knapsack problem,
+the different is that here values=weights.
+
+
+This problem can be solved using dynamic programming.
+
+Proof that it exhibits optimal substructure.
+
+Suppose S is the optimal solution for making n cents.
+Then S' = S - c, where c is a coin in the optimal solution S,
+is an optimal solution for making n - c cents.
+Suppose S' is not the optimal solution for making n - c cents,
+then there exists an optimal solution X != S'.
+Now, if we add c to X, we obtain an optimal solution for making n cents,
+but this contradicts that fact that S is the optimal solution.
+"""
+
+from pprint import pprint
+
+
+def _get_change_making_matrix(set_of_coins, r):
+    m = [[0 for _ in range(r + 1)] for _ in range(len(set_of_coins) + 1)]
+
+    for i in range(r + 1):
+        m[0][i] = i
+
+    return m
+
+
+def _get_sets_of_coins_matrix(set_of_coins, r):
+    m = [[[] for _ in range(r + 1)] for _ in range(len(set_of_coins) + 1)]
+
+    for i in range(r + 1):
+        for _ in range(i):
+            m[0][i].append(1)
+
+    for j in range(len(set_of_coins) + 1):
+        m[j][0] = []
+
+    return m
+
+
+def change_making(coins, n):
+    """This function assumes that all coins are available infinitely.
+
+    I will assume that all coins and n are integers.
+
+    n is number that we need to obtain
+    with the fewest number of coins in set of coins.
+
+    Running time complexity: O(number of coins * quantity to obtain)
+
+    :type coins : list of int | tuple of int
+    :type n : int
+    """
+    m = _get_change_making_matrix(coins, n)
+
+    for c in range(1, len(coins) + 1):
+
+        for r in range(1, n + 1):
+
+            if coins[c - 1] == r:
+                m[c][r] = 1
+
+            elif coins[c - 1] > r:
+                m[c][r] = m[c - 1][r]
+
+            else:
+                m[c][r] = min(m[c - 1][r], 1 + m[c][r - coins[c - 1]])
+
+    return m[-1][-1]
+
+
+def extended_change_making(coins, rest):
+    """Returns the smallest amount of coins
+    you need to use to obtain the quantity "rest".
+
+    This function is a super version of change_making.
+
+    Running time complexity: O(number of coins * quantity to obtain)
+
+    :type coins : list of int | tuple of int
+    :type rest : int
+    """
+    m = _get_change_making_matrix(coins, rest)
+
+    # Matrix used to keep track of which coins are used
+    p = _get_sets_of_coins_matrix(coins, rest)
+
+    for c in range(1, len(coins) + 1):
+
+        for r in range(1, rest + 1):
+
+            # Just use the coin coins[c - 1].
+            if coins[c - 1] == r:
+                m[c][r] = 1
+                p[c][r].append(coins[c - 1])
+
+            # coins[c - 1] cannot be included.
+            # We use the previous solution for for making r,
+            # excluding coins[c - 1].
+            elif coins[c - 1] > r:
+                m[c][r] = m[c - 1][r]
+                p[c][r] = p[c - 1][r]
+
+            # We can use coins[c - 1].
+            # We need to decide which one of the following solutions is the best:
+            # 1. Using the previous solution for making r (without using coins[c - 1]).
+            # 2. Using coins[c - 1] + the optimal solution for making r -
+            # coins[c - 1].
+            else:
+                if m[c - 1][r] < 1 + m[c][r - coins[c - 1]]:
+                    p[c][r] = p[c - 1][r]
+                    m[c][r] = m[c - 1][r]
+                else:
+                    p[c][r] = [coins[c - 1]] + p[c][r - coins[c - 1]]
+                    m[c][r] = 1 + m[c][r - coins[c - 1]]
+
+    return p[-1][-1]
+
+
+def recursive_change_making(coins, n, index):
+    # http://algorithms.tutorialhorizon.com/dynamic-programming-coin-change-problem/
+    if n < 0:
+        return 0
+
+    if n == 0:
+        return 1
+
+    if index == len(coins) and n > 0:
+        return 0
+
+    return recursive_change_making(
+        coins, n - coins[index], index) + recursive_change_making(coins, n, index + 1)
+
+
+if __name__ == "__main__":
+    pprint(extended_change_making((12, 25, 1, 5), 16))
+    pprint(extended_change_making((1, 5, 10, 21, 25), 63))
+    print(recursive_change_making((4, 2, 1), 5, 0))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def change_making(

coins, n)

+
+ + + + +

This function assumes that all coins are available infinitely.

+

I will assume that all coins and n are integers.

+

n is number that we need to obtain +with the fewest number of coins in set of coins.

+

Running time complexity: O(number of coins * quantity to obtain)

+

:type coins : list of int | tuple of int +:type n : int

+
+ +
+
def change_making(coins, n):
+    """This function assumes that all coins are available infinitely.
+
+    I will assume that all coins and n are integers.
+
+    n is number that we need to obtain
+    with the fewest number of coins in set of coins.
+
+    Running time complexity: O(number of coins * quantity to obtain)
+
+    :type coins : list of int | tuple of int
+    :type n : int
+    """
+    m = _get_change_making_matrix(coins, n)
+
+    for c in range(1, len(coins) + 1):
+
+        for r in range(1, n + 1):
+
+            if coins[c - 1] == r:
+                m[c][r] = 1
+
+            elif coins[c - 1] > r:
+                m[c][r] = m[c - 1][r]
+
+            else:
+                m[c][r] = min(m[c - 1][r], 1 + m[c][r - coins[c - 1]])
+
+    return m[-1][-1]
+
+
+
+ +
+ + +
+
+

def extended_change_making(

coins, rest)

+
+ + + + +

Returns the smallest amount of coins +you need to use to obtain the quantity "rest".

+

This function is a super version of change_making.

+

Running time complexity: O(number of coins * quantity to obtain)

+

:type coins : list of int | tuple of int +:type rest : int

+
+ +
+
def extended_change_making(coins, rest):
+    """Returns the smallest amount of coins
+    you need to use to obtain the quantity "rest".
+
+    This function is a super version of change_making.
+
+    Running time complexity: O(number of coins * quantity to obtain)
+
+    :type coins : list of int | tuple of int
+    :type rest : int
+    """
+    m = _get_change_making_matrix(coins, rest)
+
+    # Matrix used to keep track of which coins are used
+    p = _get_sets_of_coins_matrix(coins, rest)
+
+    for c in range(1, len(coins) + 1):
+
+        for r in range(1, rest + 1):
+
+            # Just use the coin coins[c - 1].
+            if coins[c - 1] == r:
+                m[c][r] = 1
+                p[c][r].append(coins[c - 1])
+
+            # coins[c - 1] cannot be included.
+            # We use the previous solution for for making r,
+            # excluding coins[c - 1].
+            elif coins[c - 1] > r:
+                m[c][r] = m[c - 1][r]
+                p[c][r] = p[c - 1][r]
+
+            # We can use coins[c - 1].
+            # We need to decide which one of the following solutions is the best:
+            # 1. Using the previous solution for making r (without using coins[c - 1]).
+            # 2. Using coins[c - 1] + the optimal solution for making r -
+            # coins[c - 1].
+            else:
+                if m[c - 1][r] < 1 + m[c][r - coins[c - 1]]:
+                    p[c][r] = p[c - 1][r]
+                    m[c][r] = m[c - 1][r]
+                else:
+                    p[c][r] = [coins[c - 1]] + p[c][r - coins[c - 1]]
+                    m[c][r] = 1 + m[c][r - coins[c - 1]]
+
+    return p[-1][-1]
+
+
+
+ +
+ + +
+
+

def recursive_change_making(

coins, n, index)

+
+ + + + +
+ +
+
def recursive_change_making(coins, n, index):
+    # http://algorithms.tutorialhorizon.com/dynamic-programming-coin-change-problem/
+    if n < 0:
+        return 0
+
+    if n == 0:
+        return 1
+
+    if index == len(coins) and n > 0:
+        return 0
+
+    return recursive_change_making(
+        coins, n - coins[index], index) + recursive_change_making(coins, n, index + 1)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/edit_distance.m.html b/docs/ands/algorithms/dp/edit_distance.m.html new file mode 100644 index 00000000..e32addf7 --- /dev/null +++ b/docs/ands/algorithms/dp/edit_distance.m.html @@ -0,0 +1,1485 @@ + + + + + + ands.algorithms.dp.edit_distance API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.edit_distance module

+

Author: Nelson Brochado +Creation: 31/08/15

+

Calculate the edit distance between two strings.

+

Based on: +- https://github.com/dossan/interview/blob/master/src/com/interview/dynamic/EditDistance.java +- https://www.youtube.com/watch?v=We3YDTzNXEk

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 31/08/15
+
+Calculate the edit distance between two strings.
+
+Based on:
+- https://github.com/dossan/interview/blob/master/src/com/interview/dynamic/EditDistance.java
+- https://www.youtube.com/watch?v=We3YDTzNXEk
+"""
+
+from pprint import pprint
+
+
+def _get_edit_distance_matrix(s1, s2):
+    """Returns a len(s2) + 1 by len(s1) + 1 matrix,
+    where the first row and column are filled with 0s,
+    the rest is filled with -1s.
+
+    :type s1 : str
+    :type s2 : str
+    """
+    matrix = [[-1 for _ in range(len(s2) + 1)] for _ in range(len(s1) + 1)]
+
+    for j in range(len(matrix[0])):
+        matrix[0][j] = j
+
+    for i, _ in enumerate(matrix):
+        matrix[i][0] = i
+
+    return matrix
+
+
+def _get_coordinates_matrix(s1, s2):
+    return [[(0, 0) for _ in range(len(s2) + 1)] for _ in range(len(s1) + 1)]
+
+
+def min_edit_distance(s1, s2, return_matrix=False):
+    """Returns the edit distance of s1 to s2.
+
+    The edit distance is the minimum number of operations,
+    among insertion, deletion and substitution,
+    that we need to turn s1 into s2.
+
+    If return_matrix = True,
+    the matrix used to calculate the edit distance is returned,
+    instead of the edit distance.
+
+    This algorithm uses a dynamic programming solution.
+
+    Running time complexity: O(m * n),
+    where m is the length of s1 and n is the length of s2.
+
+    :type s1 : str
+    :type s2 : str
+    """
+    m = _get_edit_distance_matrix(s1, s2)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+            # How do we obtain the m[i][j] value?
+            # We need to look at three positions while iterating:
+            # 1. m[i - 1][j -1]
+            # 2. m[i][j - 1]
+            # 3. m[i - 1][j]
+
+            # s1[i - 1] and s2[j - 1] are the characters
+
+            # (node that i and j start from 1!)
+            # that we are currently comparing.
+            # If the characters are equal,
+            # we don't need to perform any of the operations:
+            # insertion, deletion or substitution,
+            # and the minimum edit distance to convert s1[i - 1] to s2[j - 1]
+            # is the same as the one to convert s1[i] to s[j],
+            # because, as stated above, s1[i - 1] and s2[j - 1] are equal,
+            # so we don't have to perform any other operation.
+            if s1[i - 1] == s2[j - 1]:
+                m[i][j] = m[i - 1][j - 1]
+            else:
+                m[i][j] = min(m[i - 1][j - 1] + 1, m[i - 1]
+                              [j] + 1, m[i][j - 1] + 1)
+
+                # pprint(m)
+                # input()
+                # print()
+
+    return m[len(s1)][len(s2)] if not return_matrix else m
+
+
+def extended_min_edit_distance(s1, s2):
+    """Returns a tuple whose first item is the minimum edit distance,
+    and the second item is a list of lists containing the instructions
+    (in the language of coordinates) to convert a string to another.
+
+    Running time complexity: O(m * n),
+    where m is the length of s1 and n is the length of s2.
+
+    :type s1 : str
+    :type s2 : str
+    """
+    m = _get_edit_distance_matrix(s1, s2)
+
+    o = _get_coordinates_matrix(s1, s2)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+
+            coordinates = (i - 1, j - 1)
+
+            if s1[i - 1] == s2[j - 1]:
+                m[i][j] = m[i - 1][j - 1]
+            else:
+                _min = -1
+                if m[i][j - 1] + 1 < m[i - 1][j] + 1:
+                    _min = m[i][j - 1] + 1
+                    coordinates = (i, j - 1)
+                else:
+                    _min = m[i - 1][j] + 1
+                    coordinates = (i - 1, j)
+
+                if m[i - 1][j - 1] + 1 < _min:
+                    _min = m[i - 1][j - 1] + 1
+                    coordinates = (i - 1, j - 1)
+
+                m[i][j] = _min
+            o[i][j] = coordinates
+
+    return m[len(s1)][len(s2)], o
+
+
+def build_min_edit_instructions(s1, s2, o):
+    """Interprets the coordinates o
+    and creates a comprehensible list of instructions.
+
+    :type s1 : str
+    :type s2 : str
+    :type o : list
+    """
+
+    i = []  # List for the instructions
+
+    c = (len(s1), len(s2))  # Initial coordinates for o
+
+    while c != (0, 0):
+        # Three Cases:
+        # 1. Go diagonally (to the left) => Replace, if characters are different
+        # 2. Go left  => Remove from the first string
+        # 3. Go up  => Remove from the second string
+
+        next_c = o[c[0]][c[1]]
+
+        # Case 1
+        if next_c[0] < c[0] and next_c[1] < c[1]:
+
+            if s1[c[0] - 1] != s2[c[1] - 1]:
+                i.append("Replace char at index " + str(c[0] - 1) + " (" + s1[c[0] - 1] + ") from '" + s1 +
+                         "' with char at index " + str(c[1] - 1) + " (" + s2[c[1] - 1] + ") from '" + s2 + "'.")
+        # Case 3
+        elif next_c[0] == c[0] and next_c[1] < c[1]:
+            i.append("Insert into '" + s1 + "' at index " + str(c[1] - 1) + " char at index "
+                     + str(c[1] - 1) + " (" + s2[c[1] - 1] + ") from '" + s2 + "'.")
+
+        # Case 2
+        else:  # next_c[0] < c[0] and next_c[1] == c[1]
+            i.append("Delete from '" + s1 + "' char at index " +
+                     str(c[0] - 1) + " (" + s1[c[0] - 1] + ").")
+
+        c = next_c
+
+    i.reverse()
+    return i
+
+
+def convert(str1, str2):
+    print("Edit distance:", min_edit_distance(str1, str2))
+    print()
+
+    print("Matrix:")
+    pprint(min_edit_distance(str1, str2, return_matrix=True))
+    print()
+
+    instructions = build_min_edit_instructions(
+        str1, str2, extended_min_edit_distance(str1, str2)[1])
+
+    for i in instructions:
+        print(i)
+
+
+if __name__ == "__main__":
+    str1 = "Jazayeri"
+    str2 = "Carzaniga"
+
+    str3 = "BANA"
+    str4 = "ANA"
+
+    convert(str1, str2)
+    convert("kitten", "sitting")
+    convert(str3, str3)  # nothing will be indicated to do!
+    convert(str3, str4)
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def build_min_edit_instructions(

s1, s2, o)

+
+ + + + +

Interprets the coordinates o +and creates a comprehensible list of instructions.

+

:type s1 : str +:type s2 : str +:type o : list

+
+ +
+
def build_min_edit_instructions(s1, s2, o):
+    """Interprets the coordinates o
+    and creates a comprehensible list of instructions.
+
+    :type s1 : str
+    :type s2 : str
+    :type o : list
+    """
+
+    i = []  # List for the instructions
+
+    c = (len(s1), len(s2))  # Initial coordinates for o
+
+    while c != (0, 0):
+        # Three Cases:
+        # 1. Go diagonally (to the left) => Replace, if characters are different
+        # 2. Go left  => Remove from the first string
+        # 3. Go up  => Remove from the second string
+
+        next_c = o[c[0]][c[1]]
+
+        # Case 1
+        if next_c[0] < c[0] and next_c[1] < c[1]:
+
+            if s1[c[0] - 1] != s2[c[1] - 1]:
+                i.append("Replace char at index " + str(c[0] - 1) + " (" + s1[c[0] - 1] + ") from '" + s1 +
+                         "' with char at index " + str(c[1] - 1) + " (" + s2[c[1] - 1] + ") from '" + s2 + "'.")
+        # Case 3
+        elif next_c[0] == c[0] and next_c[1] < c[1]:
+            i.append("Insert into '" + s1 + "' at index " + str(c[1] - 1) + " char at index "
+                     + str(c[1] - 1) + " (" + s2[c[1] - 1] + ") from '" + s2 + "'.")
+
+        # Case 2
+        else:  # next_c[0] < c[0] and next_c[1] == c[1]
+            i.append("Delete from '" + s1 + "' char at index " +
+                     str(c[0] - 1) + " (" + s1[c[0] - 1] + ").")
+
+        c = next_c
+
+    i.reverse()
+    return i
+
+
+
+ +
+ + +
+
+

def convert(

str1, str2)

+
+ + + + +
+ +
+
def convert(str1, str2):
+    print("Edit distance:", min_edit_distance(str1, str2))
+    print()
+
+    print("Matrix:")
+    pprint(min_edit_distance(str1, str2, return_matrix=True))
+    print()
+
+    instructions = build_min_edit_instructions(
+        str1, str2, extended_min_edit_distance(str1, str2)[1])
+
+    for i in instructions:
+        print(i)
+
+
+
+ +
+ + +
+
+

def extended_min_edit_distance(

s1, s2)

+
+ + + + +

Returns a tuple whose first item is the minimum edit distance, +and the second item is a list of lists containing the instructions +(in the language of coordinates) to convert a string to another.

+

Running time complexity: O(m * n), +where m is the length of s1 and n is the length of s2.

+

:type s1 : str +:type s2 : str

+
+ +
+
def extended_min_edit_distance(s1, s2):
+    """Returns a tuple whose first item is the minimum edit distance,
+    and the second item is a list of lists containing the instructions
+    (in the language of coordinates) to convert a string to another.
+
+    Running time complexity: O(m * n),
+    where m is the length of s1 and n is the length of s2.
+
+    :type s1 : str
+    :type s2 : str
+    """
+    m = _get_edit_distance_matrix(s1, s2)
+
+    o = _get_coordinates_matrix(s1, s2)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+
+            coordinates = (i - 1, j - 1)
+
+            if s1[i - 1] == s2[j - 1]:
+                m[i][j] = m[i - 1][j - 1]
+            else:
+                _min = -1
+                if m[i][j - 1] + 1 < m[i - 1][j] + 1:
+                    _min = m[i][j - 1] + 1
+                    coordinates = (i, j - 1)
+                else:
+                    _min = m[i - 1][j] + 1
+                    coordinates = (i - 1, j)
+
+                if m[i - 1][j - 1] + 1 < _min:
+                    _min = m[i - 1][j - 1] + 1
+                    coordinates = (i - 1, j - 1)
+
+                m[i][j] = _min
+            o[i][j] = coordinates
+
+    return m[len(s1)][len(s2)], o
+
+
+
+ +
+ + +
+
+

def min_edit_distance(

s1, s2, return_matrix=False)

+
+ + + + +

Returns the edit distance of s1 to s2.

+

The edit distance is the minimum number of operations, +among insertion, deletion and substitution, +that we need to turn s1 into s2.

+

If return_matrix = True, +the matrix used to calculate the edit distance is returned, +instead of the edit distance.

+

This algorithm uses a dynamic programming solution.

+

Running time complexity: O(m * n), +where m is the length of s1 and n is the length of s2.

+

:type s1 : str +:type s2 : str

+
+ +
+
def min_edit_distance(s1, s2, return_matrix=False):
+    """Returns the edit distance of s1 to s2.
+
+    The edit distance is the minimum number of operations,
+    among insertion, deletion and substitution,
+    that we need to turn s1 into s2.
+
+    If return_matrix = True,
+    the matrix used to calculate the edit distance is returned,
+    instead of the edit distance.
+
+    This algorithm uses a dynamic programming solution.
+
+    Running time complexity: O(m * n),
+    where m is the length of s1 and n is the length of s2.
+
+    :type s1 : str
+    :type s2 : str
+    """
+    m = _get_edit_distance_matrix(s1, s2)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+            # How do we obtain the m[i][j] value?
+            # We need to look at three positions while iterating:
+            # 1. m[i - 1][j -1]
+            # 2. m[i][j - 1]
+            # 3. m[i - 1][j]
+
+            # s1[i - 1] and s2[j - 1] are the characters
+
+            # (node that i and j start from 1!)
+            # that we are currently comparing.
+            # If the characters are equal,
+            # we don't need to perform any of the operations:
+            # insertion, deletion or substitution,
+            # and the minimum edit distance to convert s1[i - 1] to s2[j - 1]
+            # is the same as the one to convert s1[i] to s[j],
+            # because, as stated above, s1[i - 1] and s2[j - 1] are equal,
+            # so we don't have to perform any other operation.
+            if s1[i - 1] == s2[j - 1]:
+                m[i][j] = m[i - 1][j - 1]
+            else:
+                m[i][j] = min(m[i - 1][j - 1] + 1, m[i - 1]
+                              [j] + 1, m[i][j - 1] + 1)
+
+                # pprint(m)
+                # input()
+                # print()
+
+    return m[len(s1)][len(s2)] if not return_matrix else m
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/fibonacci.m.html b/docs/ands/algorithms/dp/fibonacci.m.html new file mode 100644 index 00000000..4aca3892 --- /dev/null +++ b/docs/ands/algorithms/dp/fibonacci.m.html @@ -0,0 +1,1238 @@ + + + + + + ands.algorithms.dp.fibonacci API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.fibonacci module

+

Author: Nelson Brochado +Creation: June, 2015

+

In this file you can find some functions +that return the nth fibonacci number, +but they do it in different ways, +which has also an impact on the performance (Big-O complexity) +of the same algorithms.

+

The dynamic programming versions run in O(n) time.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: June, 2015
+
+In this file you can find some functions
+that return the nth fibonacci number,
+but they do it in different ways,
+which has also an impact on the performance (Big-O complexity)
+of the same algorithms.
+
+The dynamic programming versions run in O(n) time.
+"""
+
+
+def recursive_fib(n):
+    """Returns the nth fibonacci number.
+
+    Running time complexity: O(2^n).
+
+    :type n : int
+    """
+    if n == 0:
+        return 0
+    elif n == 1:
+        return 1
+    else:
+        return recursive_fib(n - 1) + recursive_fib(n - 2)
+
+
+def _memoized_fib_aux(n, memo):
+    """Returns the nth fibonacci number.
+
+    This function uses recursion and memoisation
+    to calculate the n fibonacci number.
+
+    Running time complexity: O(n).
+
+    :type n : int
+    :type memo : dict
+    """
+    if n == 0 or n == 1:
+        return n
+    if n not in memo.keys():
+        memo[n] = _memoized_fib_aux(n - 1, memo) + \
+            _memoized_fib_aux(n - 2, memo)
+    return memo[n]
+
+
+def memoized_fib(n):
+    """Returns the nth fibonacci number using memoisation."""
+    memo = {}
+    return _memoized_fib_aux(n, memo)
+
+
+def bottom_up_fib(n, ls=False):
+    """Returns the nth fibonacci number if ls=False,
+    else it returns a list containing
+    all the ith fibonacci numbers, for i=0, ... , n
+
+    This function uses a dynamic programing bottom up approach,
+    because we start by finding the optimal solution
+    to smaller sub-problems, and from there,
+    we build the optimal solution to the initial problem.
+
+    :type n : int
+    :type ls : bool
+    """
+    if n == 0:
+        return n if not ls else [n]
+    if n == 1:
+        return n if not ls else [0, n]
+
+    fib = [0] * (n + 1)
+    fib[0] = 0
+    fib[1] = 1
+
+    for i in range(2, n + 1):
+        fib[i] = fib[i - 1] + fib[i - 2]
+
+    return fib[-1] if not ls else fib
+
+
+if __name__ == "__main__":
+    for f in range(10):
+        print(recursive_fib(f))
+        print(memoized_fib(f))
+        print(bottom_up_fib(f, ls=True))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def bottom_up_fib(

n, ls=False)

+
+ + + + +

Returns the nth fibonacci number if ls=False, +else it returns a list containing +all the ith fibonacci numbers, for i=0, ... , n

+

This function uses a dynamic programing bottom up approach, +because we start by finding the optimal solution +to smaller sub-problems, and from there, +we build the optimal solution to the initial problem.

+

:type n : int +:type ls : bool

+
+ +
+
def bottom_up_fib(n, ls=False):
+    """Returns the nth fibonacci number if ls=False,
+    else it returns a list containing
+    all the ith fibonacci numbers, for i=0, ... , n
+
+    This function uses a dynamic programing bottom up approach,
+    because we start by finding the optimal solution
+    to smaller sub-problems, and from there,
+    we build the optimal solution to the initial problem.
+
+    :type n : int
+    :type ls : bool
+    """
+    if n == 0:
+        return n if not ls else [n]
+    if n == 1:
+        return n if not ls else [0, n]
+
+    fib = [0] * (n + 1)
+    fib[0] = 0
+    fib[1] = 1
+
+    for i in range(2, n + 1):
+        fib[i] = fib[i - 1] + fib[i - 2]
+
+    return fib[-1] if not ls else fib
+
+
+
+ +
+ + +
+
+

def memoized_fib(

n)

+
+ + + + +

Returns the nth fibonacci number using memoisation.

+
+ +
+
def memoized_fib(n):
+    """Returns the nth fibonacci number using memoisation."""
+    memo = {}
+    return _memoized_fib_aux(n, memo)
+
+
+
+ +
+ + +
+
+

def recursive_fib(

n)

+
+ + + + +

Returns the nth fibonacci number.

+

Running time complexity: O(2^n).

+

:type n : int

+
+ +
+
def recursive_fib(n):
+    """Returns the nth fibonacci number.
+
+    Running time complexity: O(2^n).
+
+    :type n : int
+    """
+    if n == 0:
+        return 0
+    elif n == 1:
+        return 1
+    else:
+        return recursive_fib(n - 1) + recursive_fib(n - 2)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/index.html b/docs/ands/algorithms/dp/index.html new file mode 100644 index 00000000..ef9eb30a --- /dev/null +++ b/docs/ands/algorithms/dp/index.html @@ -0,0 +1,1186 @@ + + + + + + ands.algorithms.dp API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms.dp.change_making

+ + +

Author: Nelson Brochado

+

Problem (https://en.wikipedia.org/wiki/Change-making_problem): +Given a set of coins, which is the smallest subset of these coins, +such that summed together yields a certain number n.

+

This problem is similar to the integer knapsack problem, +the different is that here values=...

+ +
+
+

ands.algorithms.dp.edit_distance

+ + +

Author: Nelson Brochado +Creation: 31/08/15

+

Calculate the edit distance between two strings.

+

Based on: +- https://github.com/dossan/interview/blob/master/src/com/interview/dynamic/EditDistance.java +- https://www.youtube.com/watch?v=We3YDTzNXEk

+ +
+
+

ands.algorithms.dp.fibonacci

+ + +

Author: Nelson Brochado +Creation: June, 2015

+

In this file you can find some functions +that return the nth fibonacci number, +but they do it in different ways, +which has also an impact on the performance (Big-O complexity) +of the same algorithms.

+

The dynamic programming versions run in O(n) time.

+ +
+
+

ands.algorithms.dp.longest_common_subsequence

+ + +

Meta info

+

Author: Nelson Brochado +Created: 02/09/2015 +Updated: 26/01/2017

+

Description

+

The longest common subsequence or, in short, lcs, of two strings x and y +is a common measure of similarity between the two strings.

+

More specifically the problem is as follows:

+
given two ...
+
+ +
+
+

ands.algorithms.dp.longest_common_substring

+ + +

Author: Nelson Brochado +Creation: 02/09/15

+ +
+
+

ands.algorithms.dp.longest_increasing_subsequence

+ + +

Author: Nelson Brochado +Creation: 29/08/2015

+

Based on the following answer: +http://stackoverflow.com/a/19639755/3924118

+

https://www.youtube.com/watch?v=CE2b_-XfVDk

+ +
+
+

ands.algorithms.dp.max_non_adjacent_seq_weight

+ + +

Author: Nelson Brochado +Creation: 04/09/15

+

See exercise 140.

+

Based on: http://www.geeksforgeeks.org/maximum-sum-such-that-no-two-elements-are-adjacent/

+ +
+
+

ands.algorithms.dp.max_sum_contiguous_subsequence

+ + +

Author: Nelson Brochado +Creation: 04/09/15

+

Based on: https://tkramesh.wordpress.com/2011/03/09/dynamic-programming-maximum-sum-contiguous-subsequence/

+ +
+
+

ands.algorithms.dp.plus_sign_game

+ + +

Author: Nelson Brochado +Creation: 01/09/15

+

Given a string s of numbers (s1 s2 s3...sn), +is it possible to insert some plus signs "+" +in the string so that the remaining expression +is equal to a certain number k?

+

Example: Is it possible to insert some + signs in 214, +so that the resulting expressio...

+ +
+
+

ands.algorithms.dp.rod_cut

+ + +

Author: Nelson Brochado +Creation: 30/08/15 +Last update: 18/11/2016

+

References:

+
    +
  • http://www.radford.edu/~nokie/classes/360/dp-rod-cutting.html
  • +
  • Introduction to Algorithms (3rd edition) by CLSR
  • +
  • Slides by prof. E. Papadopoulou
  • +
+ +
+
+

ands.algorithms.dp.subset_sum

+ + +

Author: Nelson Brochado +Creation: 03/09/15

+ +
+
+

ands.algorithms.dp.zero_one_knapsack

+ + +

Meta info

+

Author: Nelson Brochado +Created: 2015 +Updated: 26/01/2017

+

Description

+

Given n objects and a "knapsack". +Item i weighs w_i > 0 and has a value v_i > 0. +Knapsack has capacity W. +Goal: fill knapsack so as to maximize total value.

+

References

+
    +
  • Slides by prof. Evanthia Papadopoulo...
  • +
+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/longest_common_subsequence.m.html b/docs/ands/algorithms/dp/longest_common_subsequence.m.html new file mode 100644 index 00000000..74ba8f68 --- /dev/null +++ b/docs/ands/algorithms/dp/longest_common_subsequence.m.html @@ -0,0 +1,1712 @@ + + + + + + ands.algorithms.dp.longest_common_subsequence API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.longest_common_subsequence module

+

Meta info

+

Author: Nelson Brochado +Created: 02/09/2015 +Updated: 26/01/2017

+

Description

+

The longest common subsequence or, in short, lcs, of two strings x and y +is a common measure of similarity between the two strings.

+

More specifically the problem is as follows:

+
given two strings x = x_1 x_2 .. x_m and y = y_1 y_2 .. y_n,
+what is (the length of) the longest common subsequence between strings `x` and `y`,
+where characters in the subsequences are not necessarily contiguous?
+
+

The solution is not necessarily unique. +You can find a recursive and a two dynamic programming implementations for the lcs problem. +You can find just one implementation using dynamic programming that actually returns the lcs, +instead of just computing its length, like all other implementations do.

+

References

+
    +
  • Introduction to Algorithms (3rd ed.) by CLRS
  • +
  • Slides by prof. Evanthia Papadopoulou
  • +
+

Resources

+ +

TODO

+
    +
  • Create a version with case insensitive matching.
  • +
+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+Created: 02/09/2015
+Updated: 26/01/2017
+
+## Description
+
+The longest common subsequence or, in short, _lcs_, of _two_ strings `x` and `y`
+is a common **measure of similarity** between the two strings.
+
+More specifically the problem is as follows:
+
+    given two strings x = x_1 x_2 .. x_m and y = y_1 y_2 .. y_n,
+    what is (the length of) the longest common subsequence between strings `x` and `y`,
+    where characters in the subsequences are not necessarily contiguous?
+    
+The solution is not necessarily unique.
+You can find a recursive and a two dynamic programming implementations for the lcs problem.
+You can find just one implementation using dynamic programming that actually returns the lcs,
+instead of just computing its length, like all other implementations do.
+
+## References
+
+- Introduction to Algorithms (3rd ed.) by CLRS
+- Slides by prof. Evanthia Papadopoulou
+
+## Resources
+
+- [https://en.wikipedia.org/wiki/Longest_common_subsequence_problem](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem)
+
+## TODO
+
+- Create a version with case insensitive matching.
+"""
+
+
+def _get_lcs_length_matrix(s1: str, s2: str) -> list:
+    """Let m = len(s1) and n = len(s2),
+    then this function returns a (m + 1)x(n + 1) matrix,
+    specifically it returns a list of length m + 1,
+    whose elements are lists of length (n + 1).
+    The "+ 1" in (m + 1) and (n + 1) is because the first row and column
+    are reserved for the cases where we compare with _empty_ sequences.
+    """
+    return [[0 for _ in range(len(s2) + 1)] for _ in range(len(s1) + 1)]
+
+
+def _get_lcs_matrix(s1: str, s2: str) -> list:
+    m = []
+    for _ in range(len(s1) + 1):
+        m.append([])
+        for _ in range(len(s2) + 1):
+            m[-1].append([])
+    return m
+
+
+def _recursive_lcs_length_aux(s1: str, n: int, s2: str, m: int, result: int) -> int:
+    """Helper function of `recursive_lcs_length`."""
+    if n == 0 or m == 0:
+        return 0
+    elif s1[n - 1] == s2[m - 1]:
+        result = 1 + _recursive_lcs_length_aux(s1, n - 1, s2, m - 1, result)
+    else:
+        result = max(_recursive_lcs_length_aux(s1, n - 1, s2, m, result),
+                     _recursive_lcs_length_aux(s1, n, s2, m - 1, result))
+    return result
+
+
+def recursive_lcs_length(s1: str, s2: str) -> int:
+    """Returns the length of the longest common subsequence between s1 and s2.
+    This algorithm uses a recursive solution, as the name suggests,
+    but this results in an exponential algorithm.
+    
+    ### Idea
+    Given two strings x and y, how do we find the length of the lcs between x and y?
+    
+    For every subsequence of x check weather it's a subsequence of y.
+    There are 2^n subsequences of x to check, and each subsequence takes O(m) time to check:
+    scan y for the first letter, from there scan for the second, and so on.
+    
+    ### Definition
+    
+    lcs(i, j) = length of the longest comment subsequence between
+    x(i) = x(i)_1, x(i)_2, ..., x(i)_i and y(j) = y(j)_1, y(j)_2, ..., y(j)_j.
+    
+    where by x(i) it's meant a subsequence of x up to i,
+    and by x(i)_i it's meant the ith element of that same subsequence x(i).
+    A similar thing can be said for y(j) and y(j)_j.
+        
+    ### Goal 
+    lcs(n, m), where n = length(x) and m = length(y).
+    
+    ### Algorithm  
+  
+    If x(i) or y(j) is empty, lcs(i, j) = 0.
+    
+    If x(i)_i == y(j)_j, then x(i)_i and y(j)_j must be included in the lcs(i, j),
+    and we express it as lcs(i, j) = lcs(i - 1, j - 1) + 1,
+    where the +1 stands for the inclusion of x(i)_i and y(j)_j.
+    
+    If x(i)_i != y(j)_j, then we can either skip x(i)_i or y(j)_j (or both):
+    we need to choose the best!! So lets see these options more closely.
+    
+    Option 1: x(i)_i is not in the lcs, then lcs(i, j) = lcs(i - 1, j).
+    Option 2: y(j)_j is not in the lcs, then lcs(i, j) = lcs(i, j - 1).
+    
+    So, basically, what we do is: lcs(i, j) = max(lcs(i - 1, j), lcs(i, j - 1)).
+    
+    Note that we don't really need to include lcs(i - 1, j - 1),
+    for the case where neither x(i)_i nor y(j)_j are included in the lcs(i, j),
+    because max(lcs(i - 1, j), lcs(i, j - 1), lcs(i - 1, j - 1)) = max(lcs(i - 1, j), lcs(i, j - 1)),
+    i.e. the maximum "profit" can simply be retrieved from lcs(i - 1, j) and lcs(i, j - 1),
+    since for sure lcs(i - 1, j - 1) brings less (or equal) profit than lcs(i - 1, j) or lcs(i, j - 1).
+    
+    #### Summary
+    
+                +--
+                | 0                                   if i == 0 or j == 0.
+    lcs(i, j) = | lcs(i - 1, j - 1) + 1               if i, j > 0 and x(i)_i == y(j)_j.
+                | max(lcs(i - 1, j), lcs(i, j - 1))   if i, j > 0 and x(i)_i != y(j)_j.
+                +--
+                
+    ### Complexity
+    This plain recursive approach is very inefficient, 
+    because we keep on recomputing sub-problems.
+    
+    **Time complexity**: θ(m*2^n).
+    """
+    n = len(s1)
+    m = len(s2)
+    result = 0
+    return _recursive_lcs_length_aux(s1, n, s2, m, result)
+
+
+def _memoized_recursive_lcs_length_aux(s1: str, n: int, s2: str, m: int, result: list, matrix: list) -> int:
+    """Helper function of `recursive_lcs_length`."""
+    if n == 0 or m == 0:
+        return 0
+    elif matrix[n - 1][m - 1] is not None:
+        return matrix[n - 1][m - 1]
+    elif s1[n - 1] == s2[m - 1]:
+        result = 1 + _memoized_recursive_lcs_length_aux(s1, n - 1, s2, m - 1, result, matrix)
+    else:
+        result = max(_memoized_recursive_lcs_length_aux(s1, n - 1, s2, m, result, matrix),
+                     _memoized_recursive_lcs_length_aux(s1, n, s2, m - 1, result, matrix))
+
+    matrix[n - 1][m - 1] = result
+
+    return result
+
+
+def memoized_recursive_lcs_length(s1: str, s2: str) -> int:
+    """Returns the length of the longest common subsequence between strings s1 and s2.
+    This algorithm uses _memoization_ to improve performance with respect to `recursive_lcs_length`.
+    
+    If n = length(s1) and m = length(s2), then time complexity of this algorithm O(n*m),
+    which is very similar to the bottom-up version (below).
+    """
+    n = len(s1)
+    m = len(s2)
+    result = 0
+    matrix = [[None for _ in range(len(s2))] for _ in range(len(s1))]
+    
+    return _memoized_recursive_lcs_length_aux(s1, n, s2, m, result, matrix)
+
+
+def bottom_up_lcs_length(s1: str, s2: str, matrix: bool=False):
+    """Returns the length of the longest common subsequence between strings s1 and s2,
+    if `matrix` is set to `False`, 
+    else it returns the matrix used to calculate the length of the lcs of sub-problems.
+    
+    If n = length(s1) and m = length(s2), 
+    then the following are the asymptotic complexities of this algorithm.
+    
+    **Time complexity:** O(n*m)
+    **Space complexity:** O(n*m)
+    """
+    # m is initialized with zeros everywhere
+    m = _get_lcs_length_matrix(s1, s2)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+
+            # note that i and j start from 1,
+            # thus we index s1 and s2 using i - 1 and respectively j - 1,
+            # instead of simply i and j.
+            if s1[i - 1] == s2[j - 1]: 
+                m[i][j] = m[i - 1][j - 1] + 1
+            else:
+                m[i][j] = max(m[i - 1][j], m[i][j - 1])
+
+    return m[-1][-1] if not matrix else m
+
+
+def bottom_up_lcs_length_partial(s1: str, s2: str, c1: str, c2: str, partial_weight: int = 0.5, matrix: bool=False):
+    """Returns the length of the lcs between strings s1 and s2,
+    but considers c1 and c2 partially equal characters,
+    and thus instead of adding +1 to the length being computed `partial_weight` is added.
+    
+    **Time complexity:** O(n*m)
+    **Space complexity:** O(n*m)   
+    """
+    
+    m = _get_lcs_length_matrix(s1, s2)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+
+            if s1[i - 1] == s2[j - 1]:
+                m[i][j] = m[i - 1][j - 1] + 1
+            
+            # partial match
+            elif (s1[i - 1] == c1 and s2[j - 1] == c2) or (s1[i - 1] == c2 and s2[j - 1] == c1):
+                m[i][j] = max(m[i - 1][j], m[i][j - 1], m[i - 1][j - 1] + partial_weight)
+                
+            else: 
+                m[i][j] = max(m[i - 1][j], m[i][j - 1])
+
+    return m[-1][-1] if not matrix else m
+
+
+def backtrack(m: list, s1: str, s2: str, i: int, j: int):
+    if i == 1 or j == 1:
+        return ""
+    elif s1[i] == s2[j]:
+        print(s1[i])
+        return backtrack(m, s1, s2, i - 1, j - 1) + s1[i]
+    else:
+        if m[i][j - 1] > m[i - 1][j]:
+            return backtrack(m, s1, s2, i, j - 1)
+        else:
+            return backtrack(m, s1, s2, i - 1, j)
+
+
+def get_lcs(s1: str, s2: str) -> None:
+    m = bottom_up_lcs_length(s1, s2, matrix=True)
+    backtrack(m, s1, s2, len(s1) - 1, len(s2) - 1)
+
+
+def bottom_up_lcs(s1: str, s2: str):
+    """Builds all lists with all LCSs to sub-strings of sub-problems,
+    and then returns a list of characters representing
+    the longest common subsequence for the original problem.
+    :type s1 : str
+    :type s2 : str
+    :rtype : list of str"""
+
+    m = _get_lcs_matrix(s1, s2)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+
+            if s1[i - 1] == s2[j - 1]:
+                m[i][j] += m[i - 1][j - 1]
+                m[i][j].append(s2[j - 1])
+            else:
+                if len(m[i - 1][j]) > len(m[i][j - 1]):
+                    m[i][j] += m[i - 1][j]
+                else:
+                    m[i][j] += m[i][j - 1]
+
+    return m[-1][-1]
+
+
+if __name__ == "__main__":
+    examples = [("acbcf", "abcdaf"),
+                ("BANANA", "ATANA"),
+                ("abdeccbbaede", "bbdccedacde"),
+                ("abe", "eb")]
+
+    for a, b in examples:
+        print("a =", a, ", b =", b)
+        print(recursive_lcs_length(a, b))
+        print(bottom_up_lcs_length(a, b))        
+        print(memoized_recursive_lcs_length(a, b))
+        print(bottom_up_lcs_length_partial(a, b, 'a', 'e'))
+        print()
+        
+    #backtrack(m, a, b, len(a) - 1, len(b) - 1)
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def backtrack(

m, s1, s2, i, j)

+
+ + + + +
+ +
+
def backtrack(m: list, s1: str, s2: str, i: int, j: int):
+    if i == 1 or j == 1:
+        return ""
+    elif s1[i] == s2[j]:
+        print(s1[i])
+        return backtrack(m, s1, s2, i - 1, j - 1) + s1[i]
+    else:
+        if m[i][j - 1] > m[i - 1][j]:
+            return backtrack(m, s1, s2, i, j - 1)
+        else:
+            return backtrack(m, s1, s2, i - 1, j)
+
+
+
+ +
+ + +
+
+

def bottom_up_lcs(

s1, s2)

+
+ + + + +

Builds all lists with all LCSs to sub-strings of sub-problems, +and then returns a list of characters representing +the longest common subsequence for the original problem. +:type s1 : str +:type s2 : str +:rtype : list of str

+
+ +
+
def bottom_up_lcs(s1: str, s2: str):
+    """Builds all lists with all LCSs to sub-strings of sub-problems,
+    and then returns a list of characters representing
+    the longest common subsequence for the original problem.
+    :type s1 : str
+    :type s2 : str
+    :rtype : list of str"""
+
+    m = _get_lcs_matrix(s1, s2)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+
+            if s1[i - 1] == s2[j - 1]:
+                m[i][j] += m[i - 1][j - 1]
+                m[i][j].append(s2[j - 1])
+            else:
+                if len(m[i - 1][j]) > len(m[i][j - 1]):
+                    m[i][j] += m[i - 1][j]
+                else:
+                    m[i][j] += m[i][j - 1]
+
+    return m[-1][-1]
+
+
+
+ +
+ + +
+
+

def bottom_up_lcs_length(

s1, s2, matrix=False)

+
+ + + + +

Returns the length of the longest common subsequence between strings s1 and s2, +if matrix is set to False, +else it returns the matrix used to calculate the length of the lcs of sub-problems.

+

If n = length(s1) and m = length(s2), +then the following are the asymptotic complexities of this algorithm.

+

Time complexity: O(nm) +Space complexity: O(nm)

+
+ +
+
def bottom_up_lcs_length(s1: str, s2: str, matrix: bool=False):
+    """Returns the length of the longest common subsequence between strings s1 and s2,
+    if `matrix` is set to `False`, 
+    else it returns the matrix used to calculate the length of the lcs of sub-problems.
+    
+    If n = length(s1) and m = length(s2), 
+    then the following are the asymptotic complexities of this algorithm.
+    
+    **Time complexity:** O(n*m)
+    **Space complexity:** O(n*m)
+    """
+    # m is initialized with zeros everywhere
+    m = _get_lcs_length_matrix(s1, s2)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+
+            # note that i and j start from 1,
+            # thus we index s1 and s2 using i - 1 and respectively j - 1,
+            # instead of simply i and j.
+            if s1[i - 1] == s2[j - 1]: 
+                m[i][j] = m[i - 1][j - 1] + 1
+            else:
+                m[i][j] = max(m[i - 1][j], m[i][j - 1])
+
+    return m[-1][-1] if not matrix else m
+
+
+
+ +
+ + +
+
+

def bottom_up_lcs_length_partial(

s1, s2, c1, c2, partial_weight=0.5, matrix=False)

+
+ + + + +

Returns the length of the lcs between strings s1 and s2, +but considers c1 and c2 partially equal characters, +and thus instead of adding +1 to the length being computed partial_weight is added.

+

Time complexity: O(nm) +Space complexity: O(nm)

+
+ +
+
def bottom_up_lcs_length_partial(s1: str, s2: str, c1: str, c2: str, partial_weight: int = 0.5, matrix: bool=False):
+    """Returns the length of the lcs between strings s1 and s2,
+    but considers c1 and c2 partially equal characters,
+    and thus instead of adding +1 to the length being computed `partial_weight` is added.
+    
+    **Time complexity:** O(n*m)
+    **Space complexity:** O(n*m)   
+    """
+    
+    m = _get_lcs_length_matrix(s1, s2)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+
+            if s1[i - 1] == s2[j - 1]:
+                m[i][j] = m[i - 1][j - 1] + 1
+            
+            # partial match
+            elif (s1[i - 1] == c1 and s2[j - 1] == c2) or (s1[i - 1] == c2 and s2[j - 1] == c1):
+                m[i][j] = max(m[i - 1][j], m[i][j - 1], m[i - 1][j - 1] + partial_weight)
+                
+            else: 
+                m[i][j] = max(m[i - 1][j], m[i][j - 1])
+
+    return m[-1][-1] if not matrix else m
+
+
+
+ +
+ + +
+
+

def get_lcs(

s1, s2)

+
+ + + + +
+ +
+
def get_lcs(s1: str, s2: str) -> None:
+    m = bottom_up_lcs_length(s1, s2, matrix=True)
+    backtrack(m, s1, s2, len(s1) - 1, len(s2) - 1)
+
+
+
+ +
+ + +
+
+

def memoized_recursive_lcs_length(

s1, s2)

+
+ + + + +

Returns the length of the longest common subsequence between strings s1 and s2. +This algorithm uses memoization to improve performance with respect to recursive_lcs_length.

+

If n = length(s1) and m = length(s2), then time complexity of this algorithm O(n*m), +which is very similar to the bottom-up version (below).

+
+ +
+
def memoized_recursive_lcs_length(s1: str, s2: str) -> int:
+    """Returns the length of the longest common subsequence between strings s1 and s2.
+    This algorithm uses _memoization_ to improve performance with respect to `recursive_lcs_length`.
+    
+    If n = length(s1) and m = length(s2), then time complexity of this algorithm O(n*m),
+    which is very similar to the bottom-up version (below).
+    """
+    n = len(s1)
+    m = len(s2)
+    result = 0
+    matrix = [[None for _ in range(len(s2))] for _ in range(len(s1))]
+    
+    return _memoized_recursive_lcs_length_aux(s1, n, s2, m, result, matrix)
+
+
+
+ +
+ + +
+
+

def recursive_lcs_length(

s1, s2)

+
+ + + + +

Returns the length of the longest common subsequence between s1 and s2. +This algorithm uses a recursive solution, as the name suggests, +but this results in an exponential algorithm.

+

Idea

+

Given two strings x and y, how do we find the length of the lcs between x and y?

+

For every subsequence of x check weather it's a subsequence of y. +There are 2^n subsequences of x to check, and each subsequence takes O(m) time to check: +scan y for the first letter, from there scan for the second, and so on.

+

Definition

+

lcs(i, j) = length of the longest comment subsequence between +x(i) = x(i)_1, x(i)_2, ..., x(i)_i and y(j) = y(j)_1, y(j)_2, ..., y(j)_j.

+

where by x(i) it's meant a subsequence of x up to i, +and by x(i)_i it's meant the ith element of that same subsequence x(i). +A similar thing can be said for y(j) and y(j)_j.

+

Goal

+

lcs(n, m), where n = length(x) and m = length(y).

+

Algorithm

+

If x(i) or y(j) is empty, lcs(i, j) = 0.

+

If x(i)_i == y(j)_j, then x(i)_i and y(j)_j must be included in the lcs(i, j), +and we express it as lcs(i, j) = lcs(i - 1, j - 1) + 1, +where the +1 stands for the inclusion of x(i)_i and y(j)_j.

+

If x(i)_i != y(j)_j, then we can either skip x(i)_i or y(j)_j (or both): +we need to choose the best!! So lets see these options more closely.

+

Option 1: x(i)_i is not in the lcs, then lcs(i, j) = lcs(i - 1, j). +Option 2: y(j)_j is not in the lcs, then lcs(i, j) = lcs(i, j - 1).

+

So, basically, what we do is: lcs(i, j) = max(lcs(i - 1, j), lcs(i, j - 1)).

+

Note that we don't really need to include lcs(i - 1, j - 1), +for the case where neither x(i)_i nor y(j)_j are included in the lcs(i, j), +because max(lcs(i - 1, j), lcs(i, j - 1), lcs(i - 1, j - 1)) = max(lcs(i - 1, j), lcs(i, j - 1)), +i.e. the maximum "profit" can simply be retrieved from lcs(i - 1, j) and lcs(i, j - 1), +since for sure lcs(i - 1, j - 1) brings less (or equal) profit than lcs(i - 1, j) or lcs(i, j - 1).

+

Summary

+
        +--
+        | 0                                   if i == 0 or j == 0.
+
+

lcs(i, j) = | lcs(i - 1, j - 1) + 1 if i, j > 0 and x(i)_i == y(j)_j. + | max(lcs(i - 1, j), lcs(i, j - 1)) if i, j > 0 and x(i)_i != y(j)_j. + +--

+

Complexity

+

This plain recursive approach is very inefficient, +because we keep on recomputing sub-problems.

+

Time complexity: θ(m*2^n).

+
+ +
+
def recursive_lcs_length(s1: str, s2: str) -> int:
+    """Returns the length of the longest common subsequence between s1 and s2.
+    This algorithm uses a recursive solution, as the name suggests,
+    but this results in an exponential algorithm.
+    
+    ### Idea
+    Given two strings x and y, how do we find the length of the lcs between x and y?
+    
+    For every subsequence of x check weather it's a subsequence of y.
+    There are 2^n subsequences of x to check, and each subsequence takes O(m) time to check:
+    scan y for the first letter, from there scan for the second, and so on.
+    
+    ### Definition
+    
+    lcs(i, j) = length of the longest comment subsequence between
+    x(i) = x(i)_1, x(i)_2, ..., x(i)_i and y(j) = y(j)_1, y(j)_2, ..., y(j)_j.
+    
+    where by x(i) it's meant a subsequence of x up to i,
+    and by x(i)_i it's meant the ith element of that same subsequence x(i).
+    A similar thing can be said for y(j) and y(j)_j.
+        
+    ### Goal 
+    lcs(n, m), where n = length(x) and m = length(y).
+    
+    ### Algorithm  
+  
+    If x(i) or y(j) is empty, lcs(i, j) = 0.
+    
+    If x(i)_i == y(j)_j, then x(i)_i and y(j)_j must be included in the lcs(i, j),
+    and we express it as lcs(i, j) = lcs(i - 1, j - 1) + 1,
+    where the +1 stands for the inclusion of x(i)_i and y(j)_j.
+    
+    If x(i)_i != y(j)_j, then we can either skip x(i)_i or y(j)_j (or both):
+    we need to choose the best!! So lets see these options more closely.
+    
+    Option 1: x(i)_i is not in the lcs, then lcs(i, j) = lcs(i - 1, j).
+    Option 2: y(j)_j is not in the lcs, then lcs(i, j) = lcs(i, j - 1).
+    
+    So, basically, what we do is: lcs(i, j) = max(lcs(i - 1, j), lcs(i, j - 1)).
+    
+    Note that we don't really need to include lcs(i - 1, j - 1),
+    for the case where neither x(i)_i nor y(j)_j are included in the lcs(i, j),
+    because max(lcs(i - 1, j), lcs(i, j - 1), lcs(i - 1, j - 1)) = max(lcs(i - 1, j), lcs(i, j - 1)),
+    i.e. the maximum "profit" can simply be retrieved from lcs(i - 1, j) and lcs(i, j - 1),
+    since for sure lcs(i - 1, j - 1) brings less (or equal) profit than lcs(i - 1, j) or lcs(i, j - 1).
+    
+    #### Summary
+    
+                +--
+                | 0                                   if i == 0 or j == 0.
+    lcs(i, j) = | lcs(i - 1, j - 1) + 1               if i, j > 0 and x(i)_i == y(j)_j.
+                | max(lcs(i - 1, j), lcs(i, j - 1))   if i, j > 0 and x(i)_i != y(j)_j.
+                +--
+                
+    ### Complexity
+    This plain recursive approach is very inefficient, 
+    because we keep on recomputing sub-problems.
+    
+    **Time complexity**: θ(m*2^n).
+    """
+    n = len(s1)
+    m = len(s2)
+    result = 0
+    return _recursive_lcs_length_aux(s1, n, s2, m, result)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/longest_common_substring.m.html b/docs/ands/algorithms/dp/longest_common_substring.m.html new file mode 100644 index 00000000..75968de3 --- /dev/null +++ b/docs/ands/algorithms/dp/longest_common_substring.m.html @@ -0,0 +1,1110 @@ + + + + + + ands.algorithms.dp.longest_common_substring API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.longest_common_substring module

+

Author: Nelson Brochado +Creation: 02/09/15

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 02/09/15
+"""
+
+
+def _get_longest_common_substring_matrix(s1, s2):
+    return [[0 for _ in range(len(s2) + 1)] for _ in range(len(s1) + 1)]
+
+
+def _build_longest_common_substring(s1, c, m):
+    lcs = []
+
+    while m[c[0]][c[1]] != 0:
+        lcs.append(s1[c[0] - 1])
+        c = (c[0] - 1, c[1] - 1)
+
+    lcs.reverse()
+    return lcs, c
+
+
+def longest_common_substring(s1, s2):
+    m = _get_longest_common_substring_matrix(s1, s2)
+    c = (0, 0)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+
+            if s2[j - 1] == s1[i - 1]:
+                m[i][j] = m[i - 1][j - 1] + 1
+
+                if m[i][j] > m[c[0]][c[1]]:
+                    c = (i, j)
+            else:
+                m[i][j] = 0
+
+    return _build_longest_common_substring(s1, c, m)
+
+
+if __name__ == "__main__":
+    print(longest_common_substring("abcdaf", "zbcdf"))
+    print(longest_common_substring("zbcdf", "abcdaf"))
+    print(longest_common_substring("Nelson Brochado", "John Lennon"))
+    print(longest_common_substring("Hello World!", "Halo Welt!"))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def longest_common_substring(

s1, s2)

+
+ + + + +
+ +
+
def longest_common_substring(s1, s2):
+    m = _get_longest_common_substring_matrix(s1, s2)
+    c = (0, 0)
+
+    for i in range(1, len(s1) + 1):
+
+        for j in range(1, len(s2) + 1):
+
+            if s2[j - 1] == s1[i - 1]:
+                m[i][j] = m[i - 1][j - 1] + 1
+
+                if m[i][j] > m[c[0]][c[1]]:
+                    c = (i, j)
+            else:
+                m[i][j] = 0
+
+    return _build_longest_common_substring(s1, c, m)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/longest_increasing_subsequence.m.html b/docs/ands/algorithms/dp/longest_increasing_subsequence.m.html new file mode 100644 index 00000000..cdfcf1f2 --- /dev/null +++ b/docs/ands/algorithms/dp/longest_increasing_subsequence.m.html @@ -0,0 +1,1254 @@ + + + + + + ands.algorithms.dp.longest_increasing_subsequence API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.longest_increasing_subsequence module

+

Author: Nelson Brochado +Creation: 29/08/2015

+

Based on the following answer: +http://stackoverflow.com/a/19639755/3924118

+

https://www.youtube.com/watch?v=CE2b_-XfVDk

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 29/08/2015
+
+Based on the following answer:
+http://stackoverflow.com/a/19639755/3924118
+
+https://www.youtube.com/watch?v=CE2b_-XfVDk
+"""
+
+
+def build_longest_increasing_subsequence(s, prev, index_of_max_size):
+    a = []
+
+    while index_of_max_size != -1:
+        a.append(s[index_of_max_size])
+        index_of_max_size = prev[index_of_max_size]
+
+    a.reverse()
+    return a
+
+
+def lis(s):
+    """Returns a list with one of the possible
+    longest increasing subsequences of s.
+
+    This algorithm uses a dynamic programming strategy,
+    which runs in O(n^2) time, where n is the size of s.
+
+    :type s : list of int
+    :rtype : list of int
+    """
+
+    # At the end of the algorithm,
+    # each item of this list (indexed, say, by i)
+    # will be the size of the LIS seen so far
+    # starting from the beginning of s (s[0]) up to s[i].
+    # For example, suppose s = [3, 2, 5],
+    # then, at the end of the algorithm,
+    # a = [1, 1, 2]
+    # Why is this true?
+    # The maximum possible longest increasing subsequence
+    # from the beginning of s up to the beginning of s is 1,
+    # because the beginning of s contains only one element (3).
+    # The LIS from s[0] to s[1] is still one.
+    # The LIS from s[0] to s[2] is 2,
+    # because we can either pick 3 or 2 and 5.
+    a = [1] * len(s)
+
+    # This array is useful to retrieve information
+    # about the indexes of the chosen numbers to belong to the LIS.
+    # See the function build_longest_increasing_subsequence,
+    # if you understand how to retrieve the numbers in the LIS.
+    prev = [-1] * len(s)
+
+    # Current maximum size of the increasing subsequence
+    # Note that initially all numbers in s are increasing subsequences of size
+    # 1
+    current_max_size = 1
+
+    # Index of a, which contains the size of the current L.I.S.
+    index_of_max_size = 0
+
+    for i in range(1, len(s)):
+
+        for j in range(0, i):
+
+            if s[j] < s[i] and a[i] < a[j] + 1:
+                a[i] = a[j] + 1
+
+                prev[i] = j
+
+        # Updates the current index of where the size of the current L.I.S is in a,
+        # and also the current maximum size of the so far L.I.S.
+        if a[i] > current_max_size:
+            index_of_max_size = i
+            current_max_size = a[i]
+
+    return build_longest_increasing_subsequence(s, prev, index_of_max_size)
+
+
+def recursive_lis(s):
+    pass
+    # http://stackoverflow.com/questions/2631726/how-to-determine-the-longest-increasing-subsequence-using-dynamic-programming
+
+
+# print(lis([3, 2, 6, 4, 5, 1]))
+print(lis([0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def build_longest_increasing_subsequence(

s, prev, index_of_max_size)

+
+ + + + +
+ +
+
def build_longest_increasing_subsequence(s, prev, index_of_max_size):
+    a = []
+
+    while index_of_max_size != -1:
+        a.append(s[index_of_max_size])
+        index_of_max_size = prev[index_of_max_size]
+
+    a.reverse()
+    return a
+
+
+
+ +
+ + +
+
+

def lis(

s)

+
+ + + + +

Returns a list with one of the possible +longest increasing subsequences of s.

+

This algorithm uses a dynamic programming strategy, +which runs in O(n^2) time, where n is the size of s.

+

:type s : list of int +:rtype : list of int

+
+ +
+
def lis(s):
+    """Returns a list with one of the possible
+    longest increasing subsequences of s.
+
+    This algorithm uses a dynamic programming strategy,
+    which runs in O(n^2) time, where n is the size of s.
+
+    :type s : list of int
+    :rtype : list of int
+    """
+
+    # At the end of the algorithm,
+    # each item of this list (indexed, say, by i)
+    # will be the size of the LIS seen so far
+    # starting from the beginning of s (s[0]) up to s[i].
+    # For example, suppose s = [3, 2, 5],
+    # then, at the end of the algorithm,
+    # a = [1, 1, 2]
+    # Why is this true?
+    # The maximum possible longest increasing subsequence
+    # from the beginning of s up to the beginning of s is 1,
+    # because the beginning of s contains only one element (3).
+    # The LIS from s[0] to s[1] is still one.
+    # The LIS from s[0] to s[2] is 2,
+    # because we can either pick 3 or 2 and 5.
+    a = [1] * len(s)
+
+    # This array is useful to retrieve information
+    # about the indexes of the chosen numbers to belong to the LIS.
+    # See the function build_longest_increasing_subsequence,
+    # if you understand how to retrieve the numbers in the LIS.
+    prev = [-1] * len(s)
+
+    # Current maximum size of the increasing subsequence
+    # Note that initially all numbers in s are increasing subsequences of size
+    # 1
+    current_max_size = 1
+
+    # Index of a, which contains the size of the current L.I.S.
+    index_of_max_size = 0
+
+    for i in range(1, len(s)):
+
+        for j in range(0, i):
+
+            if s[j] < s[i] and a[i] < a[j] + 1:
+                a[i] = a[j] + 1
+
+                prev[i] = j
+
+        # Updates the current index of where the size of the current L.I.S is in a,
+        # and also the current maximum size of the so far L.I.S.
+        if a[i] > current_max_size:
+            index_of_max_size = i
+            current_max_size = a[i]
+
+    return build_longest_increasing_subsequence(s, prev, index_of_max_size)
+
+
+
+ +
+ + +
+
+

def recursive_lis(

s)

+
+ + + + +
+ +
+
def recursive_lis(s):
+    pass
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/max_non_adjacent_seq_weight.m.html b/docs/ands/algorithms/dp/max_non_adjacent_seq_weight.m.html new file mode 100644 index 00000000..f2844ae3 --- /dev/null +++ b/docs/ands/algorithms/dp/max_non_adjacent_seq_weight.m.html @@ -0,0 +1,1122 @@ + + + + + + ands.algorithms.dp.max_non_adjacent_seq_weight API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.max_non_adjacent_seq_weight module

+

Author: Nelson Brochado +Creation: 04/09/15

+

See exercise 140.

+

Based on: http://www.geeksforgeeks.org/maximum-sum-such-that-no-two-elements-are-adjacent/

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 04/09/15
+
+See exercise 140.
+
+Based on: http://www.geeksforgeeks.org/maximum-sum-such-that-no-two-elements-are-adjacent/
+"""
+
+
+def max_non_adj_seq_weight(s):
+    """
+    Time complexity: O(n).
+    """
+
+    include = s[0]  # Maximum sum including the previous element.
+    exclude = 0  # Maximum sum excluding the previous element.
+    # The maximum sum excluding the current element will be max(include, exclude).
+    # The maximum sum including the current element will be exclude + current_element
+    # because we cannot sum two consecutive numbers.
+
+    new_exclude = None
+
+    for i in range(1, len(s)):
+
+        if include > exclude:
+            new_exclude = include
+        else:
+            new_exclude = exclude
+
+        include = exclude + s[i]
+        exclude = new_exclude
+
+    return max(include, exclude)
+
+
+if __name__ == "__main__":
+    a = [5, 5, 9, 100, 11, 5]  # 5 + 5 + 100 = 110
+    b = [3, 2, 5, 10, 7]  # 3 + 5 + 7 = 15
+    c = [3, 2, 7, 10]  # 3 + 10 = 13
+    print(max_non_adj_seq_weight(a))
+    print(max_non_adj_seq_weight(b))
+    print(max_non_adj_seq_weight(c))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def max_non_adj_seq_weight(

s)

+
+ + + + +

Time complexity: O(n).

+
+ +
+
def max_non_adj_seq_weight(s):
+    """
+    Time complexity: O(n).
+    """
+
+    include = s[0]  # Maximum sum including the previous element.
+    exclude = 0  # Maximum sum excluding the previous element.
+    # The maximum sum excluding the current element will be max(include, exclude).
+    # The maximum sum including the current element will be exclude + current_element
+    # because we cannot sum two consecutive numbers.
+
+    new_exclude = None
+
+    for i in range(1, len(s)):
+
+        if include > exclude:
+            new_exclude = include
+        else:
+            new_exclude = exclude
+
+        include = exclude + s[i]
+        exclude = new_exclude
+
+    return max(include, exclude)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/max_sum_contiguous_subsequence.m.html b/docs/ands/algorithms/dp/max_sum_contiguous_subsequence.m.html new file mode 100644 index 00000000..b6bff30a --- /dev/null +++ b/docs/ands/algorithms/dp/max_sum_contiguous_subsequence.m.html @@ -0,0 +1,1417 @@ + + + + + + ands.algorithms.dp.max_sum_contiguous_subsequence API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.max_sum_contiguous_subsequence module

+

Author: Nelson Brochado +Creation: 04/09/15

+

Based on: https://tkramesh.wordpress.com/2011/03/09/dynamic-programming-maximum-sum-contiguous-subsequence/

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 04/09/15
+
+Based on: https://tkramesh.wordpress.com/2011/03/09/dynamic-programming-maximum-sum-contiguous-subsequence/
+"""
+
+
+def brute_force_max_sum_contiguous_subsequence(s):
+    """Brute force approach to compute the sum of all subsequences of s.
+    There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.
+
+    Running time complexity: O(n^3).
+
+    :type s : list or tuple
+    """
+    _sum = _max = s[0]
+    start = end = 0
+
+    for i in range(0, len(s)):
+
+        for j in range(i, len(s)):
+
+            _sum = 0
+
+            for k in range(i, j + 1):
+                _sum += s[k]
+
+            if _sum > _max:
+                _max = _sum
+                start = i
+                end = j
+
+    return _max, start, end
+
+
+def better_brute_force_max_sum_contiguous_subsequence(s):
+    """Brute force approach to compute the sum of all subsequences of s.
+    There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.
+
+    Running time complexity: O(n^2).
+
+    :type s : list or tuple
+    """
+    _sum = _max = s[0]
+    start = end = 0
+
+    for i in range(0, len(s)):
+
+        _sum = 0
+
+        for j in range(i, len(s)):
+            _sum += s[j]
+
+            # We need to update every iteration of the inner loop,
+            # because we need to check if from i to j
+            # we have found a better sum...
+            if _sum > _max:
+                _max = _sum
+                start = i
+                end = j
+
+    return _max, start, end
+
+
+def bottom_up_max_sum_contiguous_subsequence(s):
+    """
+    Let sum[k] denote the max contiguous sequence ending at k.
+    So, sum[k + 1] = max(s[k], sum[k] + s[k]).
+    sum[0] = s[0]
+
+    To keep track where the max contiguous subsequence starts,
+    we use another array.
+
+    Time complexity: O(n).
+    Space complexity: O(n).
+
+    :type s : list
+    """
+    indices = [0] * len(s)
+
+    _sum = [0] * len(s)
+    _sum[0] = s[0]
+    _max = _sum[0]
+
+    start = end = 0
+
+    for i in range(1, len(s)):
+
+        if _sum[i - 1] > 0:
+            _sum[i] = _sum[i - 1] + s[i]
+            indices[i] = indices[i - 1]
+
+        else:
+            _sum[i] = s[i]
+            indices[i] = i
+
+        if _sum[i] > _max:
+            _max = _sum[i]
+            end = i
+            start = indices[i]
+
+    return _max, start, end
+
+
+def better_bottom_up_max_sum_contiguous_subsequence(s):
+    """Returns a tuple or three elements (sum, start index, ending index),
+    where sum is the sum of the maximum contiguous subsequence,
+    start index is the starting index of the subsequence,
+    and ending index is the corresponding ending index.
+
+    Let sum[k] denote the max contiguous sequence ending at k.
+    So, sum[k + 1] = max(s[k], sum[k] + s[k]).
+    sum[0] = s[0]
+
+    To keep track where the max contiguous subsequence starts,
+    we use another array.
+
+    Time complexity: O(n).
+    Space complexity: O(1).
+
+    :type s : list
+    """
+    _max = s[0]
+    _sum = s[0]
+    index = 0
+
+    start = end = 0
+
+    for i in range(1, len(s)):
+
+        if _sum > 0:
+            _sum = _sum + s[i]
+        else:
+            _sum = s[i]
+            index = i
+
+        if _sum > _max:
+            _max = _sum
+            end = i
+            start = index
+
+    return _max, start, end
+
+
+if __name__ == "__main__":
+    seq = [4, 2, -4]
+
+    print(brute_force_max_sum_contiguous_subsequence(seq))
+    print(better_brute_force_max_sum_contiguous_subsequence(seq))
+    print(bottom_up_max_sum_contiguous_subsequence(seq))
+    print(better_bottom_up_max_sum_contiguous_subsequence(seq))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def better_bottom_up_max_sum_contiguous_subsequence(

s)

+
+ + + + +

Returns a tuple or three elements (sum, start index, ending index), +where sum is the sum of the maximum contiguous subsequence, +start index is the starting index of the subsequence, +and ending index is the corresponding ending index.

+

Let sum[k] denote the max contiguous sequence ending at k. +So, sum[k + 1] = max(s[k], sum[k] + s[k]). +sum[0] = s[0]

+

To keep track where the max contiguous subsequence starts, +we use another array.

+

Time complexity: O(n). +Space complexity: O(1).

+

:type s : list

+
+ +
+
def better_bottom_up_max_sum_contiguous_subsequence(s):
+    """Returns a tuple or three elements (sum, start index, ending index),
+    where sum is the sum of the maximum contiguous subsequence,
+    start index is the starting index of the subsequence,
+    and ending index is the corresponding ending index.
+
+    Let sum[k] denote the max contiguous sequence ending at k.
+    So, sum[k + 1] = max(s[k], sum[k] + s[k]).
+    sum[0] = s[0]
+
+    To keep track where the max contiguous subsequence starts,
+    we use another array.
+
+    Time complexity: O(n).
+    Space complexity: O(1).
+
+    :type s : list
+    """
+    _max = s[0]
+    _sum = s[0]
+    index = 0
+
+    start = end = 0
+
+    for i in range(1, len(s)):
+
+        if _sum > 0:
+            _sum = _sum + s[i]
+        else:
+            _sum = s[i]
+            index = i
+
+        if _sum > _max:
+            _max = _sum
+            end = i
+            start = index
+
+    return _max, start, end
+
+
+
+ +
+ + +
+
+

def better_brute_force_max_sum_contiguous_subsequence(

s)

+
+ + + + +

Brute force approach to compute the sum of all subsequences of s. +There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.

+

Running time complexity: O(n^2).

+

:type s : list or tuple

+
+ +
+
def better_brute_force_max_sum_contiguous_subsequence(s):
+    """Brute force approach to compute the sum of all subsequences of s.
+    There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.
+
+    Running time complexity: O(n^2).
+
+    :type s : list or tuple
+    """
+    _sum = _max = s[0]
+    start = end = 0
+
+    for i in range(0, len(s)):
+
+        _sum = 0
+
+        for j in range(i, len(s)):
+            _sum += s[j]
+
+            # We need to update every iteration of the inner loop,
+            # because we need to check if from i to j
+            # we have found a better sum...
+            if _sum > _max:
+                _max = _sum
+                start = i
+                end = j
+
+    return _max, start, end
+
+
+
+ +
+ + +
+
+

def bottom_up_max_sum_contiguous_subsequence(

s)

+
+ + + + +

Let sum[k] denote the max contiguous sequence ending at k. +So, sum[k + 1] = max(s[k], sum[k] + s[k]). +sum[0] = s[0]

+

To keep track where the max contiguous subsequence starts, +we use another array.

+

Time complexity: O(n). +Space complexity: O(n).

+

:type s : list

+
+ +
+
def bottom_up_max_sum_contiguous_subsequence(s):
+    """
+    Let sum[k] denote the max contiguous sequence ending at k.
+    So, sum[k + 1] = max(s[k], sum[k] + s[k]).
+    sum[0] = s[0]
+
+    To keep track where the max contiguous subsequence starts,
+    we use another array.
+
+    Time complexity: O(n).
+    Space complexity: O(n).
+
+    :type s : list
+    """
+    indices = [0] * len(s)
+
+    _sum = [0] * len(s)
+    _sum[0] = s[0]
+    _max = _sum[0]
+
+    start = end = 0
+
+    for i in range(1, len(s)):
+
+        if _sum[i - 1] > 0:
+            _sum[i] = _sum[i - 1] + s[i]
+            indices[i] = indices[i - 1]
+
+        else:
+            _sum[i] = s[i]
+            indices[i] = i
+
+        if _sum[i] > _max:
+            _max = _sum[i]
+            end = i
+            start = indices[i]
+
+    return _max, start, end
+
+
+
+ +
+ + +
+
+

def brute_force_max_sum_contiguous_subsequence(

s)

+
+ + + + +

Brute force approach to compute the sum of all subsequences of s. +There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.

+

Running time complexity: O(n^3).

+

:type s : list or tuple

+
+ +
+
def brute_force_max_sum_contiguous_subsequence(s):
+    """Brute force approach to compute the sum of all subsequences of s.
+    There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.
+
+    Running time complexity: O(n^3).
+
+    :type s : list or tuple
+    """
+    _sum = _max = s[0]
+    start = end = 0
+
+    for i in range(0, len(s)):
+
+        for j in range(i, len(s)):
+
+            _sum = 0
+
+            for k in range(i, j + 1):
+                _sum += s[k]
+
+            if _sum > _max:
+                _max = _sum
+                start = i
+                end = j
+
+    return _max, start, end
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/plus_sign_game.m.html b/docs/ands/algorithms/dp/plus_sign_game.m.html new file mode 100644 index 00000000..a84a58dc --- /dev/null +++ b/docs/ands/algorithms/dp/plus_sign_game.m.html @@ -0,0 +1,1227 @@ + + + + + + ands.algorithms.dp.plus_sign_game API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.plus_sign_game module

+

Author: Nelson Brochado +Creation: 01/09/15

+

Given a string s of numbers (s1 s2 s3...sn), +is it possible to insert some plus signs "+" +in the string so that the remaining expression +is equal to a certain number k?

+

Example: Is it possible to insert some + signs in 214, +so that the resulting expression is 25? +Yes, if we insert a + sign between 1 and 4, +that is 21 + 4, we obtain 25.

+

TODO: PROVE THAT THIS PROBLEM EXHIBITS OR NOT OPTIMAL SUBSTRUCTURE!

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 01/09/15
+
+Given a string s of numbers (s1 s2 s3...sn),
+is it possible to insert some plus signs "+"
+in the string so that the remaining expression
+is equal to a certain number k?
+
+Example: Is it possible to insert some + signs in 214,
+so that the resulting expression is 25?
+Yes, if we insert a + sign between 1 and 4,
+that is 21 + 4, we obtain 25.
+
+TODO: PROVE THAT THIS PROBLEM EXHIBITS OR NOT OPTIMAL SUBSTRUCTURE!
+"""
+
+
+def _generate_combinations_matrix(p, combinations):
+    m = [["" for _ in range(p)] for _ in range(combinations)]
+
+    c = combinations - 1
+    interval = combinations // 2
+
+    j = interval
+    sign = "+"
+
+    for i in range(p):
+
+        while c >= 0:
+
+            m[c][i] = sign
+            c -= 1
+            j -= 1
+
+            if j == 0:
+                j = interval
+                sign = "" if sign == "+" else "+"
+
+        sign = "+"
+        c = combinations - 1
+        interval //= 2
+        j = interval
+
+    return m
+
+
+def _build_expressions(string, m):
+    combinations = [["" for _ in range(len(m[0]))] for _ in range(len(m))]
+
+    for i, c in enumerate(m):
+
+        result = ""
+
+        for j, sign in enumerate(c):
+
+            if result == "":
+                result = string[j] + sign + string[j + 1]
+            else:
+                result += sign + string[j + 1]
+
+            combinations[i] = result
+
+    return combinations
+
+
+def _evaluate_combinations(combinations, k):
+    """Returns the right combination (if it exists),
+    among all the combinations in "combinations",
+    of inserting plus signs in a certain string of numbers s,
+    such that the resulting expression is equal to k.
+
+    If no combination yields k, None is returned.
+
+    :type combinations : list of str
+    :type k : int
+    """
+    for c in combinations:
+        if int(eval(c)) == k:
+            return c
+    return None
+
+
+def plus_sign_game(s, k):
+    """Given a string s of numbers (s1 s2 s3...sn),
+    is it possible to insert_key some plus signs "+"
+    in the string so that the remaining expression
+    is equal to a certain number k?
+
+    Example: Is it possible to insert_key some + signs in 214,
+    so that the resulting expression is 25?
+    Yes, if we insert_key a + sign between 1 and 4,
+    that is 21 + 4, we obtain 25.
+
+    :type s : str | int
+    :type k : int
+    """
+
+    s = str(s)
+
+    # Number of places where to place a + sign
+    p = len(s) - 1
+
+    # Number of possible combinations
+    combinations = 2 ** p
+
+    # Generating a matrix with all possible combinations
+    # of alternating between +  and no +
+    m = _generate_combinations_matrix(p, combinations)
+
+    # Building all possible expressions
+    combinations = _build_expressions(s, m)
+
+    return _evaluate_combinations(combinations, k)
+
+
+if __name__ == "__main__":
+    print(plus_sign_game("21347823", 2000))
+    print(plus_sign_game("214", 25))
+    print(plus_sign_game("1214", 26))
+    print(plus_sign_game("1214", 215))
+    print(plus_sign_game("1214", 125))
+
+    # Do not run this script with the following statement uncommented
+    # if you are in a hurry.
+    # print(plus_sign_game("646805736141599100791159198", 472004))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def plus_sign_game(

s, k)

+
+ + + + +

Given a string s of numbers (s1 s2 s3...sn), +is it possible to insert_key some plus signs "+" +in the string so that the remaining expression +is equal to a certain number k?

+

Example: Is it possible to insert_key some + signs in 214, +so that the resulting expression is 25? +Yes, if we insert_key a + sign between 1 and 4, +that is 21 + 4, we obtain 25.

+

:type s : str | int +:type k : int

+
+ +
+
def plus_sign_game(s, k):
+    """Given a string s of numbers (s1 s2 s3...sn),
+    is it possible to insert_key some plus signs "+"
+    in the string so that the remaining expression
+    is equal to a certain number k?
+
+    Example: Is it possible to insert_key some + signs in 214,
+    so that the resulting expression is 25?
+    Yes, if we insert_key a + sign between 1 and 4,
+    that is 21 + 4, we obtain 25.
+
+    :type s : str | int
+    :type k : int
+    """
+
+    s = str(s)
+
+    # Number of places where to place a + sign
+    p = len(s) - 1
+
+    # Number of possible combinations
+    combinations = 2 ** p
+
+    # Generating a matrix with all possible combinations
+    # of alternating between +  and no +
+    m = _generate_combinations_matrix(p, combinations)
+
+    # Building all possible expressions
+    combinations = _build_expressions(s, m)
+
+    return _evaluate_combinations(combinations, k)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/rod_cut.m.html b/docs/ands/algorithms/dp/rod_cut.m.html new file mode 100644 index 00000000..d5130f15 --- /dev/null +++ b/docs/ands/algorithms/dp/rod_cut.m.html @@ -0,0 +1,1580 @@ + + + + + + ands.algorithms.dp.rod_cut API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.rod_cut module

+

Author: Nelson Brochado +Creation: 30/08/15 +Last update: 18/11/2016

+

References:

+
    +
  • http://www.radford.edu/~nokie/classes/360/dp-rod-cutting.html
  • +
  • Introduction to Algorithms (3rd edition) by CLSR
  • +
  • Slides by prof. E. Papadopoulou
  • +
+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 30/08/15
+Last update: 18/11/2016
+
+## References:
+- http://www.radford.edu/~nokie/classes/360/dp-rod-cutting.html
+- Introduction to Algorithms (3rd edition) by CLSR
+- Slides by prof. E. Papadopoulou
+"""
+
+import sys
+
+
+def recursive_rod_cut(prices, n):
+    """Returns the maximum revenue of cutting a rod of length n.
+
+    It does not return which rod pieces
+    you need to pick to obtain the maximum revenue.
+
+    The rod cutting problem is the following:
+    given a rod of length n,
+    and a table of prices p_i, for i = 0, 1, 2, 3, ...,
+    determine the maximum revenue r_n obtainable
+    by cutting up the rod and selling the pieces.
+
+    Note that if the price p_n
+    for a rod of length n is large enough,
+    an optimal solution may require no cutting at all.
+
+    We can cut up a rod of length n in 2^{n - 1} different ways,
+    since we have an independent option of cutting, or not cutting,
+    at distance i inches from the left end,
+    for i = 1, 2, ... , n - 1.
+
+    prices contains the prices for each rod of different length.
+    prices[0] would be the price of the rod of length 0 (not useful).
+    prices[1] would be the price of the rod of length 1,
+    prices[2] would be the price for a rod of length 2, and so on.
+
+    Running time complexity: O(2^n)
+
+    :type prices : list | tuple
+    :type n : int
+    """
+
+    if n == 0:  # Base case
+        return 0
+
+    max_revenue = -sys.maxsize
+
+    for i in range(1, n + 1):  # Last i is n.
+        max_revenue = max(max_revenue, prices[i] + recursive_rod_cut(prices, n - i))
+
+    return max_revenue
+
+
+def _memoized_rod_cut_aux(prices, n, revenues, s):
+    """Auxiliary function for the memoized_rod_cut function.
+
+    :type prices : list | tuple
+    :type n : int
+    :type revenues : list | tuple
+    """
+    # If the following condition is true,
+    # that would mean that the revenue
+    # for a rod of length n has already been "memoised",
+    # and we don't need to recompute it again,
+    # but we simply return it.
+    if revenues[n] >= 0:
+        return revenues[n]
+
+    max_revenue = -sys.maxsize
+
+    if n == 0:  # If the size of the rod is 0, then max_revenue is 0.
+        max_revenue = 0
+    else:
+        for i in range(1, n + 1):
+            q = _memoized_rod_cut_aux(prices, n - i, revenues, s)
+
+            if prices[i] + q > max_revenue:
+                max_revenue = prices[i] + q
+                s[n] = i
+
+    # Memoising the maximum revenue for sub-problem with a rod of length n.
+    revenues[n] = max_revenue
+
+    return max_revenue
+
+
+def memoized_rod_cut(prices, n):
+    """_Top-down_ dynamic programming version of `recursive_rod_cut`,
+    using _memoisation_ to store sub problems' solutions.
+    _Memoisation_ is basically the name to the technique 
+    of storing what it's been computed previously.
+
+    In this algorithm, as opppose to the plain recursive one,
+    instead of repeatedly solving the same subproblems,
+    we store the solution to a subproblem in a table,
+    the first time we solve the subproblem,
+    so that this solution can simply be looked up, if needed again.
+
+    The disadvantge of this solution is that we need additional memory,
+    i.e., a table, to store intermediary solutions.
+
+    Running time complexity: theta(n^2)
+
+    :type prices : list | tuple
+    :type n : int
+    :rtype : int
+    """
+
+    # Initialing the revenues list f
+    # or the sub-problems length i = 0, 1, 2, ... , n
+    # to a small and negative number,
+    # which simply means that we have not yet computed
+    # the revenue for those sub-problems.
+    # Note that revenue values are always nonnegative,
+    # unless prices contain negative numbers.
+    revenues = [-sys.maxsize] * (n + 1)
+
+    # optimal first cut for rods of length 0..n
+    s = [0] * (n + 1)
+    
+    return _memoized_rod_cut_aux(prices, n, revenues, s), s
+
+
+def bottom_up_rod_cut(prices, n):
+    """_Bottom-up_ dynamic programming solution to the rod cut problem.
+
+    Running time complexity: theta(n^2)
+
+    :type prices : list | tuple
+    :type n : int
+    """
+    revenues = [-sys.maxsize] * (n + 1)
+    revenues[0] = 0  # Revenue for rod of length 0 is 0.
+
+    for i in range(1, n + 1):        
+        max_revenue = -sys.maxsize
+
+        for j in range(1, i + 1): # Find the max cut position for length i
+            max_revenue = max(max_revenue, prices[j] + revenues[i - j])
+
+        revenues[i] = max_revenue
+
+    return revenues[n]
+
+
+def extended_bottom_up_rod_cut(prices, n):
+    """Dynamic programming solution to the rod cut problem.
+
+    This dynamic programming version uses a bottom-up approach.
+
+    It returns a tuple, whose first item is a list of the revenues
+    and second is a list containing the rod pieces that are used in the revenue.
+
+    Running time complexity: O(n^2)
+
+    :type prices : list
+    :type n : int
+    :rtype : tuple
+    """
+    revenues = [-sys.maxsize] * (n + 1)
+    s = [[]] * (n + 1) # Used to store the optimal choices
+
+    revenues[0] = 0
+    s[0] = [0]
+
+    for i in range(1, n + 1):
+
+        max_revenue = -sys.maxsize
+
+        for j in range(1, i + 1):
+            # Note that j + (i - j) = i.
+            # What does this mean, or why should this fact be useful?
+            # Note that at each iteration of the outer loop,
+            # we are trying to find the max_revenue for a rod of length i
+            # (and we want also to find which items we are including to obtain that max_revenue).
+            # To obtain a rod of size i, we need at least 2 other smaller rods,
+            # unless we do not cut the rod.
+            # Now, to obtain a rod of length i,
+            # we need to insert together a rod of length j < i and a rod of length i - j < j,
+            # because j + (i - j) = i, as we stated at the beginning.
+            if max_revenue < prices[j] + revenues[i - j]:
+
+                max_revenue = prices[j] + revenues[i - j]
+
+                if revenues[i - j] != 0:
+                    s[i] = [j] + s[i - j]
+                else:
+                    # revenue[i] (current) uses a rod of length j
+                    # left most cut is at j
+                    s[i] = [j]  
+
+        revenues[i] = max_revenue
+
+    return revenues, s
+
+
+def rod_cut_solution_print(prices, n, s):
+    """prices is the list of initial prices.
+    n is the number of those prices - 1.
+    s is the solution returned by memoized_rod_cut."""
+    while n > 0:
+        print(s[n], end=" ")
+        n = n - s[n]
+    print()
+
+
+if __name__ == "__main__":
+    p1 = [0, 1, 5, 8, 9, 10, 17, 17, 20]
+
+    def test0():
+        r = recursive_rod_cut(p1, len(p1) - 1)
+        print("Revenue:", r)
+        print("--------------------------------------------")
+
+    def test1():
+        r, s = memoized_rod_cut(p1, len(p1) - 1)
+        print("Revenue:", r)
+        print("s:", s) 
+        rod_cut_solution_print(p1, len(p1) - 1, s)
+        print("--------------------------------------------")
+
+    def test2():
+        r = bottom_up_rod_cut(p1, len(p1) - 1)
+        print("Revenue:", r)
+        print("--------------------------------------------")
+
+    def test3():
+        r, s = extended_bottom_up_rod_cut(p1, len(p1) - 1)
+        print("Revenues:", r)
+        print("s:", s)
+        print("--------------------------------------------")
+
+    test0()
+    test1()
+    test2()
+    test3()
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def bottom_up_rod_cut(

prices, n)

+
+ + + + +

Bottom-up dynamic programming solution to the rod cut problem.

+

Running time complexity: theta(n^2)

+

:type prices : list | tuple +:type n : int

+
+ +
+
def bottom_up_rod_cut(prices, n):
+    """_Bottom-up_ dynamic programming solution to the rod cut problem.
+
+    Running time complexity: theta(n^2)
+
+    :type prices : list | tuple
+    :type n : int
+    """
+    revenues = [-sys.maxsize] * (n + 1)
+    revenues[0] = 0  # Revenue for rod of length 0 is 0.
+
+    for i in range(1, n + 1):        
+        max_revenue = -sys.maxsize
+
+        for j in range(1, i + 1): # Find the max cut position for length i
+            max_revenue = max(max_revenue, prices[j] + revenues[i - j])
+
+        revenues[i] = max_revenue
+
+    return revenues[n]
+
+
+
+ +
+ + +
+
+

def extended_bottom_up_rod_cut(

prices, n)

+
+ + + + +

Dynamic programming solution to the rod cut problem.

+

This dynamic programming version uses a bottom-up approach.

+

It returns a tuple, whose first item is a list of the revenues +and second is a list containing the rod pieces that are used in the revenue.

+

Running time complexity: O(n^2)

+

:type prices : list +:type n : int +:rtype : tuple

+
+ +
+
def extended_bottom_up_rod_cut(prices, n):
+    """Dynamic programming solution to the rod cut problem.
+
+    This dynamic programming version uses a bottom-up approach.
+
+    It returns a tuple, whose first item is a list of the revenues
+    and second is a list containing the rod pieces that are used in the revenue.
+
+    Running time complexity: O(n^2)
+
+    :type prices : list
+    :type n : int
+    :rtype : tuple
+    """
+    revenues = [-sys.maxsize] * (n + 1)
+    s = [[]] * (n + 1) # Used to store the optimal choices
+
+    revenues[0] = 0
+    s[0] = [0]
+
+    for i in range(1, n + 1):
+
+        max_revenue = -sys.maxsize
+
+        for j in range(1, i + 1):
+            # Note that j + (i - j) = i.
+            # What does this mean, or why should this fact be useful?
+            # Note that at each iteration of the outer loop,
+            # we are trying to find the max_revenue for a rod of length i
+            # (and we want also to find which items we are including to obtain that max_revenue).
+            # To obtain a rod of size i, we need at least 2 other smaller rods,
+            # unless we do not cut the rod.
+            # Now, to obtain a rod of length i,
+            # we need to insert together a rod of length j < i and a rod of length i - j < j,
+            # because j + (i - j) = i, as we stated at the beginning.
+            if max_revenue < prices[j] + revenues[i - j]:
+
+                max_revenue = prices[j] + revenues[i - j]
+
+                if revenues[i - j] != 0:
+                    s[i] = [j] + s[i - j]
+                else:
+                    # revenue[i] (current) uses a rod of length j
+                    # left most cut is at j
+                    s[i] = [j]  
+
+        revenues[i] = max_revenue
+
+    return revenues, s
+
+
+
+ +
+ + +
+
+

def memoized_rod_cut(

prices, n)

+
+ + + + +

Top-down dynamic programming version of recursive_rod_cut, +using memoisation to store sub problems' solutions. +Memoisation is basically the name to the technique +of storing what it's been computed previously.

+

In this algorithm, as opppose to the plain recursive one, +instead of repeatedly solving the same subproblems, +we store the solution to a subproblem in a table, +the first time we solve the subproblem, +so that this solution can simply be looked up, if needed again.

+

The disadvantge of this solution is that we need additional memory, +i.e., a table, to store intermediary solutions.

+

Running time complexity: theta(n^2)

+

:type prices : list | tuple +:type n : int +:rtype : int

+
+ +
+
def memoized_rod_cut(prices, n):
+    """_Top-down_ dynamic programming version of `recursive_rod_cut`,
+    using _memoisation_ to store sub problems' solutions.
+    _Memoisation_ is basically the name to the technique 
+    of storing what it's been computed previously.
+
+    In this algorithm, as opppose to the plain recursive one,
+    instead of repeatedly solving the same subproblems,
+    we store the solution to a subproblem in a table,
+    the first time we solve the subproblem,
+    so that this solution can simply be looked up, if needed again.
+
+    The disadvantge of this solution is that we need additional memory,
+    i.e., a table, to store intermediary solutions.
+
+    Running time complexity: theta(n^2)
+
+    :type prices : list | tuple
+    :type n : int
+    :rtype : int
+    """
+
+    # Initialing the revenues list f
+    # or the sub-problems length i = 0, 1, 2, ... , n
+    # to a small and negative number,
+    # which simply means that we have not yet computed
+    # the revenue for those sub-problems.
+    # Note that revenue values are always nonnegative,
+    # unless prices contain negative numbers.
+    revenues = [-sys.maxsize] * (n + 1)
+
+    # optimal first cut for rods of length 0..n
+    s = [0] * (n + 1)
+    
+    return _memoized_rod_cut_aux(prices, n, revenues, s), s
+
+
+
+ +
+ + +
+
+

def recursive_rod_cut(

prices, n)

+
+ + + + +

Returns the maximum revenue of cutting a rod of length n.

+

It does not return which rod pieces +you need to pick to obtain the maximum revenue.

+

The rod cutting problem is the following: +given a rod of length n, +and a table of prices p_i, for i = 0, 1, 2, 3, ..., +determine the maximum revenue r_n obtainable +by cutting up the rod and selling the pieces.

+

Note that if the price p_n +for a rod of length n is large enough, +an optimal solution may require no cutting at all.

+

We can cut up a rod of length n in 2^{n - 1} different ways, +since we have an independent option of cutting, or not cutting, +at distance i inches from the left end, +for i = 1, 2, ... , n - 1.

+

prices contains the prices for each rod of different length. +prices[0] would be the price of the rod of length 0 (not useful). +prices[1] would be the price of the rod of length 1, +prices[2] would be the price for a rod of length 2, and so on.

+

Running time complexity: O(2^n)

+

:type prices : list | tuple +:type n : int

+
+ +
+
def recursive_rod_cut(prices, n):
+    """Returns the maximum revenue of cutting a rod of length n.
+
+    It does not return which rod pieces
+    you need to pick to obtain the maximum revenue.
+
+    The rod cutting problem is the following:
+    given a rod of length n,
+    and a table of prices p_i, for i = 0, 1, 2, 3, ...,
+    determine the maximum revenue r_n obtainable
+    by cutting up the rod and selling the pieces.
+
+    Note that if the price p_n
+    for a rod of length n is large enough,
+    an optimal solution may require no cutting at all.
+
+    We can cut up a rod of length n in 2^{n - 1} different ways,
+    since we have an independent option of cutting, or not cutting,
+    at distance i inches from the left end,
+    for i = 1, 2, ... , n - 1.
+
+    prices contains the prices for each rod of different length.
+    prices[0] would be the price of the rod of length 0 (not useful).
+    prices[1] would be the price of the rod of length 1,
+    prices[2] would be the price for a rod of length 2, and so on.
+
+    Running time complexity: O(2^n)
+
+    :type prices : list | tuple
+    :type n : int
+    """
+
+    if n == 0:  # Base case
+        return 0
+
+    max_revenue = -sys.maxsize
+
+    for i in range(1, n + 1):  # Last i is n.
+        max_revenue = max(max_revenue, prices[i] + recursive_rod_cut(prices, n - i))
+
+    return max_revenue
+
+
+
+ +
+ + +
+
+

def rod_cut_solution_print(

prices, n, s)

+
+ + + + +

prices is the list of initial prices. +n is the number of those prices - 1. +s is the solution returned by memoized_rod_cut.

+
+ +
+
def rod_cut_solution_print(prices, n, s):
+    """prices is the list of initial prices.
+    n is the number of those prices - 1.
+    s is the solution returned by memoized_rod_cut."""
+    while n > 0:
+        print(s[n], end=" ")
+        n = n - s[n]
+    print()
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/subset_sum.m.html b/docs/ands/algorithms/dp/subset_sum.m.html new file mode 100644 index 00000000..575c8dd0 --- /dev/null +++ b/docs/ands/algorithms/dp/subset_sum.m.html @@ -0,0 +1,1246 @@ + + + + + + ands.algorithms.dp.subset_sum API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.subset_sum module

+

Author: Nelson Brochado +Creation: 03/09/15

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 03/09/15
+"""
+
+from pprint import pprint
+
+
+def m_print(m):
+    pprint(m)
+    input()
+
+
+def _check_negativeness(subset):
+    """Returns the largest negative number in the subset.
+    If no negative number exists, it returns 0."""
+    s = 0
+    for n in subset:
+        if n < s:  # Initially s is 0
+            s = n
+    return s
+
+
+def _shift_numbers(subset, smallest):
+    m = -smallest
+    for i, _ in enumerate(subset):
+        subset[i] += m
+
+
+def _recursive_subset_sum_aux(subset, current_sum, index, n, solution):
+    if current_sum == n:  # Solution was found...
+        print("Subset found.")
+
+        for i, s in enumerate(solution):
+            if s == 1:
+                print(subset[i])
+
+    elif index == len(subset):
+        return
+    else:
+        # Include the current ith element
+        solution[index] = 1
+        current_sum += subset[index]
+        _recursive_subset_sum_aux(subset, current_sum, index + 1, n, solution)
+
+        # do not to include the ith element
+        solution[index] = 0
+        current_sum -= subset[index]
+        _recursive_subset_sum_aux(subset, current_sum, index + 1, n, solution)
+
+
+def recursive_subset_sum(subset, s):
+    # Allows negative numbers too...
+    c_sum = 0
+    i = 0
+    solution = [0] * len(subset)
+
+    return _recursive_subset_sum_aux(subset, c_sum, i, s, solution)
+
+
+def _get_subset_sum_matrix(subset, s):
+    m = [[0 for _ in range(s + 1)] for _ in range(len(subset) + 1)]
+
+    for i in range(1, s + 1):
+        m[0][i] = 0
+
+    for j in range(0, len(subset) + 1):
+        m[j][0] = 1
+
+    return m
+
+
+def bottom_up_subset_sum(subset, s, return_matrix=False):
+    """Returns 1 if there's a subset
+    whose sum of the numbers is equal to s,
+    if return_matrix == True,
+    else it returns the matrix used during the computation.
+
+    NOTE: the subset can only contain positive integers!
+
+    :type subset : list or tuple
+    :type s : int
+    """
+
+    m = _get_subset_sum_matrix(subset, s)
+
+    for i in range(1, len(subset) + 1):
+
+        for j in range(1, s + 1):
+
+            if subset[i - 1] == j:
+                m[i][j] = 1
+            else:
+                # We can include the current element,
+                # because it is less than the current number j.
+                if subset[i - 1] <= j:
+                    m[i][j] = max(m[i - 1][j], m[i - 1][j - subset[i - 1]])
+                else:
+                    m[i][j] = m[i - 1][j]
+
+    return m[-1][-1] if not return_matrix else m
+
+
+if __name__ == "__main__":
+    # print(bottom_up_subset_sum((1, 3, 5, 5, 2, 1, 1, 6), 12))
+
+    pprint(bottom_up_subset_sum([2, 2, 2, 6], 6, return_matrix=True))
+
+    print(bottom_up_subset_sum((1, 1, 6), 2, return_matrix=True))
+
+    recursive_subset_sum([-2, 8, 6], 6)
+    # recursive_subset_sum((4, 2, 6), 6)
+    # recursive_subset_sum((0, 0, 6), 6)
+    # recursive_subset_sum((1, 3, 5, 5, 2, 1, 1, 6), 12)
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def bottom_up_subset_sum(

subset, s, return_matrix=False)

+
+ + + + +

Returns 1 if there's a subset +whose sum of the numbers is equal to s, +if return_matrix == True, +else it returns the matrix used during the computation.

+

NOTE: the subset can only contain positive integers!

+

:type subset : list or tuple +:type s : int

+
+ +
+
def bottom_up_subset_sum(subset, s, return_matrix=False):
+    """Returns 1 if there's a subset
+    whose sum of the numbers is equal to s,
+    if return_matrix == True,
+    else it returns the matrix used during the computation.
+
+    NOTE: the subset can only contain positive integers!
+
+    :type subset : list or tuple
+    :type s : int
+    """
+
+    m = _get_subset_sum_matrix(subset, s)
+
+    for i in range(1, len(subset) + 1):
+
+        for j in range(1, s + 1):
+
+            if subset[i - 1] == j:
+                m[i][j] = 1
+            else:
+                # We can include the current element,
+                # because it is less than the current number j.
+                if subset[i - 1] <= j:
+                    m[i][j] = max(m[i - 1][j], m[i - 1][j - subset[i - 1]])
+                else:
+                    m[i][j] = m[i - 1][j]
+
+    return m[-1][-1] if not return_matrix else m
+
+
+
+ +
+ + +
+
+

def m_print(

m)

+
+ + + + +
+ +
+
def m_print(m):
+    pprint(m)
+    input()
+
+
+
+ +
+ + +
+
+

def recursive_subset_sum(

subset, s)

+
+ + + + +
+ +
+
def recursive_subset_sum(subset, s):
+    # Allows negative numbers too...
+    c_sum = 0
+    i = 0
+    solution = [0] * len(subset)
+
+    return _recursive_subset_sum_aux(subset, c_sum, i, s, solution)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/dp/zero_one_knapsack.m.html b/docs/ands/algorithms/dp/zero_one_knapsack.m.html new file mode 100644 index 00000000..6eaa4695 --- /dev/null +++ b/docs/ands/algorithms/dp/zero_one_knapsack.m.html @@ -0,0 +1,1424 @@ + + + + + + ands.algorithms.dp.zero_one_knapsack API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.dp.zero_one_knapsack module

+

Meta info

+

Author: Nelson Brochado +Created: 2015 +Updated: 26/01/2017

+

Description

+

Given n objects and a "knapsack". +Item i weighs w_i > 0 and has a value v_i > 0. +Knapsack has capacity W. +Goal: fill knapsack so as to maximize total value.

+

References

+
    +
  • Slides by prof. Evanthia Papadopoulou
  • +
+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+Created: 2015
+Updated: 26/01/2017
+
+## Description
+
+Given n objects and a "knapsack".
+Item i weighs w_i > 0 and has a value v_i > 0.
+Knapsack has capacity W.
+Goal: fill knapsack so as to maximize total value.
+
+## References
+
+- Slides by prof. Evanthia Papadopoulou
+
+"""
+
+from pprint import pprint
+
+
+def _get_zero_one_knapsack_matrix(total_weight: int, n: int) -> list:
+    """Returns a matrix for a dynamic programming solution to the 0/1 knapsack problem.
+
+    The first row of this matrix contains the numbers
+    corresponding to the weights of the (sub)problems.
+    The first column contains an enumeration of the items,
+    starting from the fact that we could not include any item,
+    and this is represented with a 0.
+
+    m[0][0] is 0 just because of the alignment,
+    it does make any logical sense for this purpose,
+    it could be None, or any other value.
+
+    :type total_weight : int
+    :type n : int
+    """
+    m = [[0 for _ in range(total_weight + 2)] for _ in range(n + 2)]
+
+    for x in range(1, total_weight + 2):
+        m[0][x] = x - 1
+
+    for j in range(1, n + 2):
+        m[j][0] = j - 1
+        m[j][1] = 0
+
+    return m
+
+
+def zero_one_knapsack_verbose(total_weight: int, weights, values) -> int:
+    """Verbose version of zero_one_knapsack."""
+    assert len(weights) == len(values)
+    assert total_weight >= 0
+
+    n = len(weights)
+
+    profits = _get_zero_one_knapsack_matrix(total_weight, n)
+
+    print("Initial empty profits matrix:\n")
+    pprint(profits)
+    print()
+
+    for i in range(2, n + 2):
+
+        for w in range(2, total_weight + 2):
+
+            print("-" * 30)
+            print("Weight of item", i - 1, ":", weights[i - 2])
+            print("Value of item", i - 1, ":", values[i - 2])
+            print("Current total weight:", w - 1)
+            print()
+
+            if weights[i - 2] > w - 1:
+                profits[i][w] = profits[i - 1][w]
+            else:
+                profits[i][w] = max(profits[i - 1][w], values[i - 2] + profits[i - 1][w - weights[i - 2]])
+
+            print("Profits matrix after calculation:\n")
+            pprint(profits)
+            input()
+
+    return profits[-1][-1]
+
+
+def zero_one_knapsack(total_weight: int, weights, values) -> int:
+    """Returns the maximum profit that can be obtained by
+    using items with weights and values and a total_weight.
+
+    This version does not tell which items to pick.
+
+    **Time complexity**: O(n * total_weight), where n is the number of items.
+    This is consider a pseudo-polynomial time algorithm, **not** polynomial!
+
+    The decision version of this problem is actually NP-Complete,
+    and the running time complexity above does not contradict it:
+    total_weight is not polynomial in the length of the input!
+
+    :type weights : list | tuple
+    :type values : list | tuple
+    """
+    assert len(weights) == len(values)
+    assert total_weight >= 0
+
+    n = len(weights)
+
+    profits = _get_zero_one_knapsack_matrix(total_weight, n)
+
+    # Iterating through the items
+    for i in range(2, n + 2):
+
+        # Iterating through the weights
+        for w in range(2, total_weight + 2):
+
+            # If the weight of the (i - 2)th item is greater than w - 1,
+            # which is the current weight being analysed.
+            # Note that the weights in the matrix `profits` are shifted to the right by 1.
+            if weights[i - 2] > w - 1:
+                profits[i][w] = profits[i - 1][w]
+            else:
+                # Note: indices in the `profits` matrix are also shifted 2 positions to the bottom.
+
+                # The weight of the current item is less (or equal) than the total weight,
+                # but we need to decide if it is convenient to include this item or not.
+                # To do this, we compare if we gain more by including it or not.
+
+                # `profits[i - 1][w]` refers to the profit of not including current item.
+                # `values[i - 2]` refers to the value of the current item.
+                # `w - weights[i - 2]` is the remaining weight, if we include the current item.
+                # Note: `weights[i - 2]` is the weight of the current item.
+                profits[i][w] = max(profits[i - 1][w], values[i - 2] + profits[i - 1][w - weights[i - 2]])
+
+    return profits[-1][-1]
+
+
+def _recursive_01_knapsack_aux(capacity: int, w, v, value: int) -> int:
+    """Either takes the last element or it doesn't.
+
+    This algorithm takes exponential time.
+    """
+    if capacity == 0:
+        return 0
+    if len(w) > 0 and len(v) > 0:
+        if w[-1] > capacity:  # We cannot include the nth item
+            value = _recursive_01_knapsack_aux(capacity, w[:-1], v[:-1], value)
+        else:
+            value = max(v[-1] + _recursive_01_knapsack_aux(capacity - w[-1], w[:-1], v[:-1], value),
+                        _recursive_01_knapsack_aux(capacity, w[:-1], v[:-1], value))
+    return value
+
+
+def recursive_01_knapsack(total_weight: int, weights, values):
+    assert len(weights) == len(values)
+    assert total_weight >= 0
+    
+    value = 0
+    return _recursive_01_knapsack_aux(total_weight, weights, values, value)
+
+
+def _memoized_01_knapsack_aux(capacity: int, w, v, value: int, m: list) -> int:
+    """Either takes the last element or it doesn't.
+
+    Memoization version of _recursive_01_knapsack_aux
+    """
+    if capacity == 0:
+        return 0
+
+    if m[len(w) - 1][capacity - 1] is not None:
+        return m[len(w) - 1][capacity - 1]
+
+    if len(w) > 0 and len(v) > 0:
+
+        if w[-1] > capacity:  # We cannot include the nth item
+            value = _memoized_01_knapsack_aux(capacity, w[:-1], v[:-1], value, m)
+        else:
+            value = max(v[-1] + _memoized_01_knapsack_aux(capacity - w[-1], w[:-1], v[:-1], value, m),
+                        _memoized_01_knapsack_aux(capacity, w[:-1], v[:-1], value, m))
+
+    m[len(w) - 1][capacity - 1] = value
+
+    return value
+
+
+def memoized_01_knapsack(capacity: int, weights, values) -> int:
+    result = 0
+    m = [[None for _ in range(capacity)] for _ in range(len(weights))]
+    return _memoized_01_knapsack_aux(capacity, weights, values, result, m)
+
+
+if __name__ == "__main__":
+    tw = 7  # total weight that you can carry
+
+    ws = [1, 3, 4, 5]  # weights
+    vs = [1, 4, 5, 7]  # values
+
+    print(recursive_01_knapsack(tw, ws, vs))
+    print(memoized_01_knapsack(tw, ws, vs))
+    # print(zero_one_knapsack_verbose(tw, ws, vs))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def memoized_01_knapsack(

capacity, weights, values)

+
+ + + + +
+ +
+
def memoized_01_knapsack(capacity: int, weights, values) -> int:
+    result = 0
+    m = [[None for _ in range(capacity)] for _ in range(len(weights))]
+    return _memoized_01_knapsack_aux(capacity, weights, values, result, m)
+
+
+
+ +
+ + +
+
+

def recursive_01_knapsack(

total_weight, weights, values)

+
+ + + + +
+ +
+
def recursive_01_knapsack(total_weight: int, weights, values):
+    assert len(weights) == len(values)
+    assert total_weight >= 0
+    
+    value = 0
+    return _recursive_01_knapsack_aux(total_weight, weights, values, value)
+
+
+
+ +
+ + +
+
+

def zero_one_knapsack(

total_weight, weights, values)

+
+ + + + +

Returns the maximum profit that can be obtained by +using items with weights and values and a total_weight.

+

This version does not tell which items to pick.

+

Time complexity: O(n * total_weight), where n is the number of items. +This is consider a pseudo-polynomial time algorithm, not polynomial!

+

The decision version of this problem is actually NP-Complete, +and the running time complexity above does not contradict it: +total_weight is not polynomial in the length of the input!

+

:type weights : list | tuple +:type values : list | tuple

+
+ +
+
def zero_one_knapsack(total_weight: int, weights, values) -> int:
+    """Returns the maximum profit that can be obtained by
+    using items with weights and values and a total_weight.
+
+    This version does not tell which items to pick.
+
+    **Time complexity**: O(n * total_weight), where n is the number of items.
+    This is consider a pseudo-polynomial time algorithm, **not** polynomial!
+
+    The decision version of this problem is actually NP-Complete,
+    and the running time complexity above does not contradict it:
+    total_weight is not polynomial in the length of the input!
+
+    :type weights : list | tuple
+    :type values : list | tuple
+    """
+    assert len(weights) == len(values)
+    assert total_weight >= 0
+
+    n = len(weights)
+
+    profits = _get_zero_one_knapsack_matrix(total_weight, n)
+
+    # Iterating through the items
+    for i in range(2, n + 2):
+
+        # Iterating through the weights
+        for w in range(2, total_weight + 2):
+
+            # If the weight of the (i - 2)th item is greater than w - 1,
+            # which is the current weight being analysed.
+            # Note that the weights in the matrix `profits` are shifted to the right by 1.
+            if weights[i - 2] > w - 1:
+                profits[i][w] = profits[i - 1][w]
+            else:
+                # Note: indices in the `profits` matrix are also shifted 2 positions to the bottom.
+
+                # The weight of the current item is less (or equal) than the total weight,
+                # but we need to decide if it is convenient to include this item or not.
+                # To do this, we compare if we gain more by including it or not.
+
+                # `profits[i - 1][w]` refers to the profit of not including current item.
+                # `values[i - 2]` refers to the value of the current item.
+                # `w - weights[i - 2]` is the remaining weight, if we include the current item.
+                # Note: `weights[i - 2]` is the weight of the current item.
+                profits[i][w] = max(profits[i - 1][w], values[i - 2] + profits[i - 1][w - weights[i - 2]])
+
+    return profits[-1][-1]
+
+
+
+ +
+ + +
+
+

def zero_one_knapsack_verbose(

total_weight, weights, values)

+
+ + + + +

Verbose version of zero_one_knapsack.

+
+ +
+
def zero_one_knapsack_verbose(total_weight: int, weights, values) -> int:
+    """Verbose version of zero_one_knapsack."""
+    assert len(weights) == len(values)
+    assert total_weight >= 0
+
+    n = len(weights)
+
+    profits = _get_zero_one_knapsack_matrix(total_weight, n)
+
+    print("Initial empty profits matrix:\n")
+    pprint(profits)
+    print()
+
+    for i in range(2, n + 2):
+
+        for w in range(2, total_weight + 2):
+
+            print("-" * 30)
+            print("Weight of item", i - 1, ":", weights[i - 2])
+            print("Value of item", i - 1, ":", values[i - 2])
+            print("Current total weight:", w - 1)
+            print()
+
+            if weights[i - 2] > w - 1:
+                profits[i][w] = profits[i - 1][w]
+            else:
+                profits[i][w] = max(profits[i - 1][w], values[i - 2] + profits[i - 1][w - weights[i - 2]])
+
+            print("Profits matrix after calculation:\n")
+            pprint(profits)
+            input()
+
+    return profits[-1][-1]
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/graphs/best_team_of_three.m.html b/docs/ands/algorithms/graphs/best_team_of_three.m.html new file mode 100644 index 00000000..c6f7e686 --- /dev/null +++ b/docs/ands/algorithms/graphs/best_team_of_three.m.html @@ -0,0 +1,1261 @@ + + + + + + ands.algorithms.graphs.best_team_of_three API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.graphs.best_team_of_three module

+

Author: Nelson Brochado +Creation: 06/09/15, 12:37

+

My solution to exercise 139 from the series of exercises by prof. A. Carzaniga

+

Consider a weighted undirected graph G = (V, E) +representing a group of programmers and their affinity for team work, +such that the weight w(e) of an edge e = (u, v) is a number +representing the ability of programmers u and v +to work together on the same project.

+

Write an algorithm Best-Team-Of-Three +that outputs the best team of three programmers.

+

The value of a team is considered to be the lowest affinity level +between any two members of the team.

+

So, the best team is the group of programmers +for which the lowest affinity level between members of the group is maximal.

+

Ideas: +Basically we are going to have graph with weighted edges. +The weight of these edges represents the affinity +between the nodes (or programmers) of the edge.

+

As far as I have understood, two programmers work best together, +if their affinity or the weight of the edge between them is high. +But the overall affinity of the group is the smallest of the affinities.

+

We need to find some kind of triangle in an undirected graph. +A triangle can be found using dfs.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 06/09/15, 12:37
+
+My solution to exercise 139 from the series of exercises by prof. A. Carzaniga
+
+Consider a weighted undirected graph G = (V, E)
+representing a group of programmers and their affinity for team work,
+such that the weight w(e) of an edge e = (u, v) is a number
+representing the ability of programmers u and v
+to work together on the same project.
+
+Write an algorithm Best-Team-Of-Three
+that outputs the best team of three programmers.
+
+The value of a team is considered to be the lowest affinity level
+between any two members of the team.
+
+So, the best team is the group of programmers
+for which the lowest affinity level between members of the group is maximal.
+
+Ideas:
+Basically we are going to have graph with weighted edges.
+The weight of these edges represents the affinity
+between the nodes (or programmers) of the edge.
+
+As far as I have understood, two programmers work best together,
+if their affinity or the weight of the edge between them is high.
+But the overall affinity of the group is the smallest of the affinities.
+
+We need to find some kind of triangle in an undirected graph.
+A triangle can be found using dfs.
+"""
+
+import sys
+
+from ands.ds.Graph import *
+
+
+def is_an_edge(u, v):
+    return v in u.get_adjacent_nodes()
+
+
+def best_team_of_three(g):
+    """Returns a set of programmers (nodes) with the best affinity.
+
+    :type g : Graph
+    """
+    best_3 = set()
+    max_lowest_affinity = -sys.maxsize
+
+    for u in g.nodes:
+
+        for v in u.get_adjacent_nodes():
+
+            for z in v.get_adjacent_nodes():
+
+                if is_an_edge(z, u):
+
+                    triple = (u, v, z)
+
+                    x = min(g.weight(u, v), g.weight(v, z), g.weight(z, u))
+
+                    if x > max_lowest_affinity:
+                        max_lowest_affinity = x
+                        best_3.clear()
+                        best_3 = best_3.union(triple)
+    return best_3
+
+
+if __name__ == "__main__":
+    g = Graph()
+
+    a = GraphNode("a")
+    b = GraphNode("b")
+    c = GraphNode("c")
+    d = GraphNode("d")
+    e = GraphNode("e")
+
+    g.add_undirected_edge(a, d, 1)
+    g.add_undirected_edge(a, b, 2)
+    g.add_undirected_edge(a, c, 2)
+
+    g.add_undirected_edge(d, b, 3)
+    g.add_undirected_edge(b, c, 3)
+
+    g.add_undirected_edge(d, e, 4)
+    g.add_undirected_edge(e, b, 5)
+
+    print(best_team_of_three(g))
+
+
+ +
+ +
+

Module variables

+
+

var BLACK

+ + +
+
+ +
+
+

var GREY

+ + +
+
+ +
+
+

var INFINITY

+ + +
+
+ +
+
+

var NIL

+ + +
+
+ +
+
+

var WHITE

+ + +
+
+ +
+ +

Functions

+ +
+
+

def best_team_of_three(

g)

+
+ + + + +

Returns a set of programmers (nodes) with the best affinity.

+

:type g : Graph

+
+ +
+
def best_team_of_three(g):
+    """Returns a set of programmers (nodes) with the best affinity.
+
+    :type g : Graph
+    """
+    best_3 = set()
+    max_lowest_affinity = -sys.maxsize
+
+    for u in g.nodes:
+
+        for v in u.get_adjacent_nodes():
+
+            for z in v.get_adjacent_nodes():
+
+                if is_an_edge(z, u):
+
+                    triple = (u, v, z)
+
+                    x = min(g.weight(u, v), g.weight(v, z), g.weight(z, u))
+
+                    if x > max_lowest_affinity:
+                        max_lowest_affinity = x
+                        best_3.clear()
+                        best_3 = best_3.union(triple)
+    return best_3
+
+
+
+ +
+ + +
+
+

def is_an_edge(

u, v)

+
+ + + + +
+ +
+
def is_an_edge(u, v):
+    return v in u.get_adjacent_nodes()
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/graphs/build_shortest_path.m.html b/docs/ands/algorithms/graphs/build_shortest_path.m.html new file mode 100644 index 00000000..42bccaf6 --- /dev/null +++ b/docs/ands/algorithms/graphs/build_shortest_path.m.html @@ -0,0 +1,1134 @@ + + + + + + ands.algorithms.graphs.build_shortest_path API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.graphs.build_shortest_path module

+

Author: Nelson Brochado +Creation: 29/08/15, 17:57

+

build_shortest_path can be used +to construct a list of nodes +that represent the shortest path +from a source node to a destination node.

+

Note that you should call this function +only after an algorithm, such as BFS, +has run on the graph, +i.e. precedecessors are set correctly.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 29/08/15, 17:57
+
+build_shortest_path can be used
+to construct a list of nodes
+that represent the shortest path
+from a source node to a destination node.
+
+Note that you should call this function
+only after an algorithm, such as BFS,
+has run on the graph,
+i.e. precedecessors are set correctly.
+"""
+
+
+def build_shortest_path(destination):
+    """Returns a list with the nodes of the shortest path
+    from source to destination (both included).
+
+    source is the node from which the search started.
+
+    This function should be called only after
+    an algorithm for calculating the shortest path,
+    for example bfs or bellman_ford,
+    has calculated and updated the shortest distances
+    of every vertex from a source node.
+
+    :type destination : GraphNode
+    """
+
+    shortest_path = []
+    tmp = destination
+
+    while tmp is not None:
+        shortest_path.append(tmp)
+        tmp = tmp.predecessor
+
+    shortest_path.reverse()
+
+    return shortest_path
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def build_shortest_path(

destination)

+
+ + + + +

Returns a list with the nodes of the shortest path +from source to destination (both included).

+

source is the node from which the search started.

+

This function should be called only after +an algorithm for calculating the shortest path, +for example bfs or bellman_ford, +has calculated and updated the shortest distances +of every vertex from a source node.

+

:type destination : GraphNode

+
+ +
+
def build_shortest_path(destination):
+    """Returns a list with the nodes of the shortest path
+    from source to destination (both included).
+
+    source is the node from which the search started.
+
+    This function should be called only after
+    an algorithm for calculating the shortest path,
+    for example bfs or bellman_ford,
+    has calculated and updated the shortest distances
+    of every vertex from a source node.
+
+    :type destination : GraphNode
+    """
+
+    shortest_path = []
+    tmp = destination
+
+    while tmp is not None:
+        shortest_path.append(tmp)
+        tmp = tmp.predecessor
+
+    shortest_path.reverse()
+
+    return shortest_path
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/graphs/dfs.m.html b/docs/ands/algorithms/graphs/dfs.m.html new file mode 100644 index 00000000..41b5329e --- /dev/null +++ b/docs/ands/algorithms/graphs/dfs.m.html @@ -0,0 +1,1456 @@ + + + + + + ands.algorithms.graphs.dfs API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.graphs.dfs module

+

Author: Nelson Brochado +Creation: July, 2015

+

Implementation of depth-first search, +which uses starting and ending times of visiting nodes, +and keeps also track of the predecessor of each node, +in order to be able to backtrack from a node to the source, if necessary.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: July, 2015
+
+Implementation of depth-first search,
+which uses starting and ending times of visiting nodes,
+and keeps also track of the predecessor of each node,
+in order to be able to backtrack from a node to the source, if necessary.
+"""
+
+from ands.ds.Graph import *
+
+dfs_global_time = INFINITY
+
+
+def dfs_iterative(graph):
+    # TODO (eventually because it's complex...)
+    pass
+
+
+def dfs_aux(graph: Graph, n: GraphNode):
+    """This function is called by dfs to further explore child nodes."""
+    global dfs_global_time
+
+    n.color = GREY
+    dfs_global_time += 1
+    n.start = dfs_global_time
+
+    for v in n.get_adjacent_nodes():
+
+        if v.color == WHITE:
+            v.predecessor = n
+            dfs_aux(graph, v)
+
+    n.color = BLACK
+    dfs_global_time += 1
+    n.end = dfs_global_time
+
+
+def dfs(graph: Graph):
+    """Typical dfs algorithm that traverses all nodes
+    that have not been explored yet,
+    and keeps track of the visited and finished times."""
+    global dfs_global_time
+
+    for node in graph.nodes:
+        node.predecessor = NIL
+        node.starting_time = INFINITY
+        node.ending_time = INFINITY
+        node.color = WHITE
+
+    dfs_global_time = 0  # global time for dfs
+
+    for n in graph.nodes:
+        if n.color == WHITE:  # if it is not visited
+            dfs_aux(graph, n)
+
+
+# TESTS
+
+def test_dfs():
+    g = Graph()
+
+    # total_nodes for the graph
+    A = GraphNode("A")
+    B = GraphNode("B")
+    C = GraphNode("C")
+    D = GraphNode("D")
+    E = GraphNode("E")
+    F = GraphNode("F")
+
+    # add the just created total_nodes to the graph
+    g.add_node(A)
+    g.add_node(B)
+    g.add_node(C)
+    g.add_node(D)
+    g.add_node(E)
+    g.add_node(F)
+
+    # establish the connections between total_nodes
+    A.add_adjacent_node(B)
+    A.add_adjacent_node(D)
+    A.add_adjacent_node(C)
+
+    B.add_adjacent_node(A)
+    B.add_adjacent_node(E)
+
+    C.add_adjacent_node(D)
+    C.add_adjacent_node(F)
+    C.add_adjacent_node(A)
+
+    D.add_adjacent_node(A)
+    D.add_adjacent_node(C)
+
+    E.add_adjacent_node(B)
+    E.add_adjacent_node(F)
+
+    F.add_adjacent_node(E)
+    F.add_adjacent_node(C)
+
+    # g.show_nodes()
+
+    dfs(g)
+
+    g.show_nodes()
+
+
+def test_dfs2():
+    g = Graph()
+
+    a = GraphNode("a")
+    b = GraphNode("b")
+    c = GraphNode("c")
+    d = GraphNode("d")
+    e = GraphNode("e")
+    f = GraphNode("f")
+
+    g.add_node(a)
+    g.add_node(b)
+    g.add_node(c)
+    g.add_node(d)
+    g.add_node(e)
+    g.add_node(f)
+
+    a.add_adjacent_node(b)
+    b.add_adjacent_node(e)
+    b.add_adjacent_node(c)
+
+    c.add_adjacent_node(a)
+
+    d.add_adjacent_node(c)
+
+    e.add_adjacent_node(d)
+
+    f.add_adjacent_node(a)
+    f.add_adjacent_node(e)
+
+    dfs(g)
+
+    g.show_nodes()
+
+
+if __name__ == "__main__":
+    test_dfs()
+    # test_dfs2()
+
+
+ +
+ +
+

Module variables

+
+

var BLACK

+ + +
+
+ +
+
+

var GREY

+ + +
+
+ +
+
+

var INFINITY

+ + +
+
+ +
+
+

var NIL

+ + +
+
+ +
+
+

var WHITE

+ + +
+
+ +
+
+

var dfs_global_time

+ + +
+
+ +
+ +

Functions

+ +
+
+

def dfs(

graph)

+
+ + + + +

Typical dfs algorithm that traverses all nodes +that have not been explored yet, +and keeps track of the visited and finished times.

+
+ +
+
def dfs(graph: Graph):
+    """Typical dfs algorithm that traverses all nodes
+    that have not been explored yet,
+    and keeps track of the visited and finished times."""
+    global dfs_global_time
+
+    for node in graph.nodes:
+        node.predecessor = NIL
+        node.starting_time = INFINITY
+        node.ending_time = INFINITY
+        node.color = WHITE
+
+    dfs_global_time = 0  # global time for dfs
+
+    for n in graph.nodes:
+        if n.color == WHITE:  # if it is not visited
+            dfs_aux(graph, n)
+
+
+
+ +
+ + +
+
+

def dfs_aux(

graph, n)

+
+ + + + +

This function is called by dfs to further explore child nodes.

+
+ +
+
def dfs_aux(graph: Graph, n: GraphNode):
+    """This function is called by dfs to further explore child nodes."""
+    global dfs_global_time
+
+    n.color = GREY
+    dfs_global_time += 1
+    n.start = dfs_global_time
+
+    for v in n.get_adjacent_nodes():
+
+        if v.color == WHITE:
+            v.predecessor = n
+            dfs_aux(graph, v)
+
+    n.color = BLACK
+    dfs_global_time += 1
+    n.end = dfs_global_time
+
+
+
+ +
+ + +
+
+

def dfs_iterative(

graph)

+
+ + + + +
+ +
+
def dfs_iterative(graph):
+    # TODO (eventually because it's complex...)
+    pass
+
+
+
+ +
+ + +
+
+

def test_dfs(

)

+
+ + + + +
+ +
+
def test_dfs():
+    g = Graph()
+
+    # total_nodes for the graph
+    A = GraphNode("A")
+    B = GraphNode("B")
+    C = GraphNode("C")
+    D = GraphNode("D")
+    E = GraphNode("E")
+    F = GraphNode("F")
+
+    # add the just created total_nodes to the graph
+    g.add_node(A)
+    g.add_node(B)
+    g.add_node(C)
+    g.add_node(D)
+    g.add_node(E)
+    g.add_node(F)
+
+    # establish the connections between total_nodes
+    A.add_adjacent_node(B)
+    A.add_adjacent_node(D)
+    A.add_adjacent_node(C)
+
+    B.add_adjacent_node(A)
+    B.add_adjacent_node(E)
+
+    C.add_adjacent_node(D)
+    C.add_adjacent_node(F)
+    C.add_adjacent_node(A)
+
+    D.add_adjacent_node(A)
+    D.add_adjacent_node(C)
+
+    E.add_adjacent_node(B)
+    E.add_adjacent_node(F)
+
+    F.add_adjacent_node(E)
+    F.add_adjacent_node(C)
+
+    # g.show_nodes()
+
+    dfs(g)
+
+    g.show_nodes()
+
+
+
+ +
+ + +
+
+

def test_dfs2(

)

+
+ + + + +
+ +
+
def test_dfs2():
+    g = Graph()
+
+    a = GraphNode("a")
+    b = GraphNode("b")
+    c = GraphNode("c")
+    d = GraphNode("d")
+    e = GraphNode("e")
+    f = GraphNode("f")
+
+    g.add_node(a)
+    g.add_node(b)
+    g.add_node(c)
+    g.add_node(d)
+    g.add_node(e)
+    g.add_node(f)
+
+    a.add_adjacent_node(b)
+    b.add_adjacent_node(e)
+    b.add_adjacent_node(c)
+
+    c.add_adjacent_node(a)
+
+    d.add_adjacent_node(c)
+
+    e.add_adjacent_node(d)
+
+    f.add_adjacent_node(a)
+    f.add_adjacent_node(e)
+
+    dfs(g)
+
+    g.show_nodes()
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/graphs/find_triangle.m.html b/docs/ands/algorithms/graphs/find_triangle.m.html new file mode 100644 index 00000000..68aec4b9 --- /dev/null +++ b/docs/ands/algorithms/graphs/find_triangle.m.html @@ -0,0 +1,1266 @@ + + + + + + ands.algorithms.graphs.find_triangle API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.graphs.find_triangle module

+

Author: Nelson Brochado +Creation: 05/09/15

+

Exercise 112.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 05/09/15
+
+Exercise 112.
+"""
+
+from ands.ds.Graph import *
+
+edges = {}
+
+
+def is_an_edge(u, v):
+    """Returns true if v is an adjacent node to u.
+
+    Running time complexity: O(number of adjacent nodes to u).
+
+    :type u : GraphNode
+    :type v : GraphNode
+    """
+    if (u, v) not in edges:
+        edges[(u, v)] = w in u.get_adjacent_nodes()
+    return edges[(u, v)]
+
+
+def find_triangle(g):
+    """Returns true if there's a triangle in g.
+
+    A triangle in a graph is a triple of vertices u, v and w,
+    such that all three edges  (u, v), (v, w) and (u, w) are in the graph.
+
+    Note that a triangle is not composed of the edges (u, v), (v, w) and (w, u)!
+
+    :type g : Graph
+    """
+    for u in g.nodes:
+        for v in u.get_adjacent_nodes():
+            for w in g.nodes:
+                if is_an_edge(v, w) and is_an_edge(u, w):
+                    return True
+    return False
+
+
+def improved_find_triangle(g):
+    for e in g.edges:  # e = (u, v)
+        for w in g.nodes:
+            if is_an_edge(e[1], w) and is_an_edge(e[0], w):
+                return True
+    return False
+
+
+if __name__ == "__main__":
+    g = Graph()
+
+    u = GraphNode("u")
+    w = GraphNode("w")
+    v = GraphNode("v")
+    z = GraphNode("z")
+
+    u.add_adjacent_node(v)
+    u.add_adjacent_node(w)  # comment this line to remove the triangle
+
+    v.add_adjacent_node(w)
+    v.add_adjacent_node(z)
+
+    # w.add_adjacent_node(u)
+    w.add_adjacent_node(z)
+
+    g.add_nodes((u, v, w, z))
+
+    print(g)
+
+    print(find_triangle(g))
+    print(improved_find_triangle(g))
+
+
+ +
+ +
+

Module variables

+
+

var BLACK

+ + +
+
+ +
+
+

var GREY

+ + +
+
+ +
+
+

var INFINITY

+ + +
+
+ +
+
+

var NIL

+ + +
+
+ +
+
+

var WHITE

+ + +
+
+ +
+
+

var edges

+ + +
+
+ +
+ +

Functions

+ +
+
+

def find_triangle(

g)

+
+ + + + +

Returns true if there's a triangle in g.

+

A triangle in a graph is a triple of vertices u, v and w, +such that all three edges (u, v), (v, w) and (u, w) are in the graph.

+

Note that a triangle is not composed of the edges (u, v), (v, w) and (w, u)!

+

:type g : Graph

+
+ +
+
def find_triangle(g):
+    """Returns true if there's a triangle in g.
+
+    A triangle in a graph is a triple of vertices u, v and w,
+    such that all three edges  (u, v), (v, w) and (u, w) are in the graph.
+
+    Note that a triangle is not composed of the edges (u, v), (v, w) and (w, u)!
+
+    :type g : Graph
+    """
+    for u in g.nodes:
+        for v in u.get_adjacent_nodes():
+            for w in g.nodes:
+                if is_an_edge(v, w) and is_an_edge(u, w):
+                    return True
+    return False
+
+
+
+ +
+ + +
+
+

def improved_find_triangle(

g)

+
+ + + + +
+ +
+
def improved_find_triangle(g):
+    for e in g.edges:  # e = (u, v)
+        for w in g.nodes:
+            if is_an_edge(e[1], w) and is_an_edge(e[0], w):
+                return True
+    return False
+
+
+
+ +
+ + +
+
+

def is_an_edge(

u, v)

+
+ + + + +

Returns true if v is an adjacent node to u.

+

Running time complexity: O(number of adjacent nodes to u).

+

:type u : GraphNode +:type v : GraphNode

+
+ +
+
def is_an_edge(u, v):
+    """Returns true if v is an adjacent node to u.
+
+    Running time complexity: O(number of adjacent nodes to u).
+
+    :type u : GraphNode
+    :type v : GraphNode
+    """
+    if (u, v) not in edges:
+        edges[(u, v)] = w in u.get_adjacent_nodes()
+    return edges[(u, v)]
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/graphs/four_cycle.m.html b/docs/ands/algorithms/graphs/four_cycle.m.html new file mode 100644 index 00000000..9acca93c --- /dev/null +++ b/docs/ands/algorithms/graphs/four_cycle.m.html @@ -0,0 +1,1213 @@ + + + + + + ands.algorithms.graphs.four_cycle API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.graphs.four_cycle module

+

Author: Nelson Brochado +Creation: 06/09/15

+

My solution to exercise 146 from the series of exercise by prof. A. Carzaniga

+

Write an algorithm called Four-Cycle(G) +that takes a directed graph represented with its adjacency matrix G, +and that returns true if and only if G contains a 4-cycle.

+

A 4-cycle is a sequence of four distinct vertexes a, b, c, d +such that there is an arc from a to b, from b to c, from c to d, +and from d to a.

+

Also, analyze the complexity of Four-Cycle(G).

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 06/09/15
+
+My solution to exercise 146 from the series of exercise by prof. A. Carzaniga
+
+Write an algorithm called Four-Cycle(G)
+that takes a directed graph represented with its adjacency matrix G,
+and that returns true if and only if G contains a 4-cycle.
+
+A 4-cycle is a sequence of four distinct vertexes a, b, c, d
+such that there is an arc from a to b, from b to c, from c to d,
+and from d to a.
+
+Also, analyze the complexity of Four-Cycle(G).
+"""
+
+from ands.ds.Graph import *
+
+
+def is_an_edge(u, v):
+    return v in u.get_adjacent_nodes()
+
+
+def four_cycle(g):
+    """Detects a four cycle in a graph represented as an adjacency list.
+
+    Running time complexity(O^5),
+    but using a adjacency matrix representation,
+    it would only be O(n^4),
+    because checking if there's an edge is a constant operation.
+
+    :type g : Graph
+    """
+    for a in g.nodes:
+        for b in a.get_adjacent_nodes():
+            for c in b.get_adjacent_nodes():
+                for d in c.get_adjacent_nodes():
+                    if is_an_edge(d, a):
+                        return True
+
+    return False
+
+
+if __name__ == "__main__":
+    g = Graph()
+
+    a = GraphNode("a")
+    b = GraphNode("b")
+    c = GraphNode("c")
+    d = GraphNode("d")
+
+    g.add_directed_edge(a, b)
+    g.add_directed_edge(b, c)
+    g.add_directed_edge(c, a)  # change a to d, i.e. (c, d) to form a 4-cycle
+    g.add_directed_edge(d, a)
+
+    print(four_cycle(g))
+
+
+ +
+ +
+

Module variables

+
+

var BLACK

+ + +
+
+ +
+
+

var GREY

+ + +
+
+ +
+
+

var INFINITY

+ + +
+
+ +
+
+

var NIL

+ + +
+
+ +
+
+

var WHITE

+ + +
+
+ +
+ +

Functions

+ +
+
+

def four_cycle(

g)

+
+ + + + +

Detects a four cycle in a graph represented as an adjacency list.

+

Running time complexity(O^5), +but using a adjacency matrix representation, +it would only be O(n^4), +because checking if there's an edge is a constant operation.

+

:type g : Graph

+
+ +
+
def four_cycle(g):
+    """Detects a four cycle in a graph represented as an adjacency list.
+
+    Running time complexity(O^5),
+    but using a adjacency matrix representation,
+    it would only be O(n^4),
+    because checking if there's an edge is a constant operation.
+
+    :type g : Graph
+    """
+    for a in g.nodes:
+        for b in a.get_adjacent_nodes():
+            for c in b.get_adjacent_nodes():
+                for d in c.get_adjacent_nodes():
+                    if is_an_edge(d, a):
+                        return True
+
+    return False
+
+
+
+ +
+ + +
+
+

def is_an_edge(

u, v)

+
+ + + + +
+ +
+
def is_an_edge(u, v):
+    return v in u.get_adjacent_nodes()
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/graphs/index.html b/docs/ands/algorithms/graphs/index.html new file mode 100644 index 00000000..ff5ba132 --- /dev/null +++ b/docs/ands/algorithms/graphs/index.html @@ -0,0 +1,1134 @@ + + + + + + ands.algorithms.graphs API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.graphs module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms.graphs.best_team_of_three

+ + +

Author: Nelson Brochado +Creation: 06/09/15, 12:37

+

My solution to exercise 139 from the series of exercises by prof. A. Carzaniga

+

Consider a weighted undirected graph G = (V, E) +representing a group of programmers and their affinity for team work, +such that the weight w(e) of an edge e = (u, v) is ...

+ +
+
+

ands.algorithms.graphs.build_shortest_path

+ + +

Author: Nelson Brochado +Creation: 29/08/15, 17:57

+

build_shortest_path can be used +to construct a list of nodes +that represent the shortest path +from a source node to a destination node.

+

Note that you should call this function +only after an algorithm, such as BFS, +has run on the graph, +i.e. precede...

+ +
+
+

ands.algorithms.graphs.dfs

+ + +

Author: Nelson Brochado +Creation: July, 2015

+

Implementation of depth-first search, +which uses starting and ending times of visiting nodes, +and keeps also track of the predecessor of each node, +in order to be able to backtrack from a node to the source, if necessary.

+ +
+
+

ands.algorithms.graphs.find_triangle

+ + +

Author: Nelson Brochado +Creation: 05/09/15

+

Exercise 112.

+ +
+
+

ands.algorithms.graphs.four_cycle

+ + +

Author: Nelson Brochado +Creation: 06/09/15

+

My solution to exercise 146 from the series of exercise by prof. A. Carzaniga

+

Write an algorithm called Four-Cycle(G) +that takes a directed graph represented with its adjacency matrix G, +and that returns true if and only if G contains a 4-cycle.

+

A 4-cycl...

+ +
+
+

ands.algorithms.graphs.prim

+ + +

Author: Nelson Brochado +Creation: August, 2015

+

How to find MST of a weighted graph using Prim's algorithm: +https://www.youtube.com/watch?v=YyLaRffCdk4

+ +
+
+

ands.algorithms.graphs.top_sort

+ + +

Author: Nelson Brochado +Creation: 25/08/15

+

Notes: To reduce dependencies between modules, +I decided to included a clean version of dfs here, +specifically for the top_sort algorithm.

+

Explanation: +A topological ordering (or sorting, sort, order) of a directed graph with no cycles (DAG), +is an order ...

+ +
+
+

ands.algorithms.graphs.top_three_friends_of_friends

+ + +

Author: Nelson Brochado +Creation: 05/09/15

+

Exercise 127.

+

Consider a social network system that, for each user u, +stores u's friends in a list friends(u). +Implement an algorithm Top-Three-Friends-Of-Friends(u) that, +given a user u, recommends the three other users that are not +already among u's fri...

+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/graphs/prim.m.html b/docs/ands/algorithms/graphs/prim.m.html new file mode 100644 index 00000000..7b1c1998 --- /dev/null +++ b/docs/ands/algorithms/graphs/prim.m.html @@ -0,0 +1,1361 @@ + + + + + + ands.algorithms.graphs.prim API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.graphs.prim module

+

Author: Nelson Brochado +Creation: August, 2015

+

How to find MST of a weighted graph using Prim's algorithm: +https://www.youtube.com/watch?v=YyLaRffCdk4

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: August, 2015
+
+How to find MST of a weighted graph using Prim's algorithm:
+https://www.youtube.com/watch?v=YyLaRffCdk4
+"""
+
+from ands.ds.MinPriorityQueue import *
+
+from ands.ds.Graph import *
+
+
+def _nodes_and_values(g):
+    """Returns a list of tuples,
+    each of them having 2 items:
+    t_i[0] = reference to the node i
+    t_i[1] = value (in this case is the priority) of node i.
+
+    This method was created as a utility
+    for the Prim's algorithm to find a MST.
+    """
+    ls = []
+    for node in g.nodes:
+        ls.append((node, node.value))
+    return ls
+
+
+def _initialise_prim_mst(g: Graph, s: GraphNode):
+    for u in g.nodes:
+        u.predecessor = NIL
+        u.value = INFINITY
+    s.value = 0
+
+
+def prim_mst(g: Graph, s: GraphNode):
+    """Creates a MST.
+
+    During execution of this algorithm,
+    nodes that are NOT yet in the tree,
+    reside in the min priority queue,
+    which is based on the "value" attribute.
+
+    For each vertex v in the g,
+    the attribute v.value is the minimum weight of any edge
+    connecting v to a vertex in the tree.
+    The attribute v.predecessor names the parent of v in the tree.
+
+    The algorithm implicitly maintains a set A:
+    A = {(v, v.predecessor) : v in V - {r} - Q}
+
+    When the algorithm terminates, the mpq is empty,
+    and A = {(v, v.predecessor) : v in V - {r}}."""
+
+    _initialise_prim_mst(g, s)
+
+    q = MinPriorityQueue(_nodes_and_values(g))
+
+    last_node_added = s
+
+    while not q.is_empty():
+
+        u = q.extract_min()  # Returns the minimum element
+
+        for v in u.get_adjacent_nodes():
+            if q.contains_element(v) and g.weight(u, v) < v.value:
+                v.value = g.weight(u, v)
+                v.predecessor = u
+                q.change_priority(v, new_priority=v.value)
+
+        last_node_added = u
+
+    return last_node_added
+
+
+def print_prim_mst(last):
+    while last is not None:
+        print("Node:", last)
+        print("Edge's weight:", last.value)
+        print("Predecessor:", last.predecessor, end="\n\n")
+        last = last.predecessor
+
+
+def build_prim_mst(last):
+    mst = Graph()
+
+    while last is not None:
+        n = GraphNode(last, last.value)
+        if last.predecessor is not None:
+            n.add_adjacent_node(last.predecessor, last.value)
+            n.predecessor = last.predecessor
+        mst.add_node(n)
+        last = last.predecessor
+
+    return mst
+
+
+if __name__ == "__main__":
+    graph = Graph()
+
+    a = GraphNode("a")
+    b = GraphNode("b")
+    c = GraphNode("c")
+    d = GraphNode("d")
+    e = GraphNode("e")
+    f = GraphNode("f")
+    g = GraphNode("g")
+    h = GraphNode("h")
+    i = GraphNode("i")
+
+    a.add_adjacent_node(b, 4)
+    a.add_adjacent_node(h, 8)
+
+    b.add_adjacent_node(a, 4)
+    b.add_adjacent_node(h, 11)
+    b.add_adjacent_node(c, 8)
+
+    c.add_adjacent_node(b, 8)
+    c.add_adjacent_node(i, 2)
+    c.add_adjacent_node(d, 7)
+    c.add_adjacent_node(f, 4)
+
+    d.add_adjacent_node(c, 7)
+    d.add_adjacent_node(f, 14)
+    d.add_adjacent_node(e, 9)
+
+    e.add_adjacent_node(d, 9)
+    e.add_adjacent_node(f, 10)
+
+    f.add_adjacent_node(e, 10)
+    f.add_adjacent_node(d, 14)
+    f.add_adjacent_node(c, 4)
+    f.add_adjacent_node(g, 2)
+
+    g.add_adjacent_node(f, 2)
+    g.add_adjacent_node(h, 1)
+    g.add_adjacent_node(i, 6)
+
+    i.add_adjacent_node(g, 6)
+    i.add_adjacent_node(c, 2)
+    i.add_adjacent_node(h, 7)
+
+    h.add_adjacent_node(a, 8)
+    h.add_adjacent_node(g, 1)
+    h.add_adjacent_node(i, 7)
+    h.add_adjacent_node(b, 11)
+
+    graph.add_nodes((a, b, c, d, e, f, g, h, i))
+
+    build_prim_mst(prim_mst(graph, a)).show_nodes()
+
+
+ +
+ +
+

Module variables

+
+

var BLACK

+ + +
+
+ +
+
+

var GREY

+ + +
+
+ +
+
+

var INFINITY

+ + +
+
+ +
+
+

var NIL

+ + +
+
+ +
+
+

var WHITE

+ + +
+
+ +
+ +

Functions

+ +
+
+

def build_prim_mst(

last)

+
+ + + + +
+ +
+
def build_prim_mst(last):
+    mst = Graph()
+
+    while last is not None:
+        n = GraphNode(last, last.value)
+        if last.predecessor is not None:
+            n.add_adjacent_node(last.predecessor, last.value)
+            n.predecessor = last.predecessor
+        mst.add_node(n)
+        last = last.predecessor
+
+    return mst
+
+
+
+ +
+ + +
+
+

def prim_mst(

g, s)

+
+ + + + +

Creates a MST.

+

During execution of this algorithm, +nodes that are NOT yet in the tree, +reside in the min priority queue, +which is based on the "value" attribute.

+

For each vertex v in the g, +the attribute v.value is the minimum weight of any edge +connecting v to a vertex in the tree. +The attribute v.predecessor names the parent of v in the tree.

+

The algorithm implicitly maintains a set A: +A = {(v, v.predecessor) : v in V - {r} - Q}

+

When the algorithm terminates, the mpq is empty, +and A = {(v, v.predecessor) : v in V - {r}}.

+
+ +
+
def prim_mst(g: Graph, s: GraphNode):
+    """Creates a MST.
+
+    During execution of this algorithm,
+    nodes that are NOT yet in the tree,
+    reside in the min priority queue,
+    which is based on the "value" attribute.
+
+    For each vertex v in the g,
+    the attribute v.value is the minimum weight of any edge
+    connecting v to a vertex in the tree.
+    The attribute v.predecessor names the parent of v in the tree.
+
+    The algorithm implicitly maintains a set A:
+    A = {(v, v.predecessor) : v in V - {r} - Q}
+
+    When the algorithm terminates, the mpq is empty,
+    and A = {(v, v.predecessor) : v in V - {r}}."""
+
+    _initialise_prim_mst(g, s)
+
+    q = MinPriorityQueue(_nodes_and_values(g))
+
+    last_node_added = s
+
+    while not q.is_empty():
+
+        u = q.extract_min()  # Returns the minimum element
+
+        for v in u.get_adjacent_nodes():
+            if q.contains_element(v) and g.weight(u, v) < v.value:
+                v.value = g.weight(u, v)
+                v.predecessor = u
+                q.change_priority(v, new_priority=v.value)
+
+        last_node_added = u
+
+    return last_node_added
+
+
+
+ +
+ + +
+
+

def print_prim_mst(

last)

+
+ + + + +
+ +
+
def print_prim_mst(last):
+    while last is not None:
+        print("Node:", last)
+        print("Edge's weight:", last.value)
+        print("Predecessor:", last.predecessor, end="\n\n")
+        last = last.predecessor
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/graphs/top_sort.m.html b/docs/ands/algorithms/graphs/top_sort.m.html new file mode 100644 index 00000000..fe51d72e --- /dev/null +++ b/docs/ands/algorithms/graphs/top_sort.m.html @@ -0,0 +1,1638 @@ + + + + + + ands.algorithms.graphs.top_sort API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.graphs.top_sort module

+

Author: Nelson Brochado +Creation: 25/08/15

+

Notes: To reduce dependencies between modules, +I decided to included a clean version of dfs here, +specifically for the top_sort algorithm.

+

Explanation: +A topological ordering (or sorting, sort, order) of a directed graph with no cycles (DAG), +is an order of the vertices, such that all the directed edges only go forward.

+

Knowing the topological ordering of a DAG can be useful +for example when we want to schedule certain tasks, +where some tasks have higher precedence respect to others: +in this example, vertices of the graph represent the tasks, +and a directed edge from a certain node A to node B +represents the fact that A must be "computed" or "executed" before B, +A has higher precedence respect to B.

+

When does a graph have a topological sorting?

+

A graph has a topological sorting if and only if it has no cycles.

+

The fact that a graph with a topological order +has no cycles is quite easy to understand: +Suppose a graph G has a cycle, +then when searching for a topological ordering in the cycle, +we are going at some point to return to a node that we have already visited, +and this would mean that there's an edge that goes backward, +which contradicts the fact that G has a topological ordering, +by the definition of topological ordering.

+

What if we don't have a cycle, do we always have a topological ordering? +Yes [missing proof].

+

We can find a topological ordering: +1. without dfs (the straightforward solution) +2. with depth-first search +3. another solution

+
    +
  1. Topological ordering without dfs (the straightforward solution)
  2. +
+

First, we need to have the notion of a "sink vertex", +which simply is a vertex with no outgoing edges.

+

Lemma: Every DAG must have a sink vertex. +Proof: Suppose DAG g has no sink vertex and it has a finite number of vertices, +then, when you are searching on the graph, +you will always find at least one outgoing edge from every vertex. +Since g has a finite number of vertices, +we will find at some point a vertex that we have already visited, +which would contradict the assumptions that g is a directed graph with no cycles (DAG).

+

PSEUDO-CODE:

+

TOP-SORT(DAG): + top_sort_list = []

+
create a copy of DAG called g
+
+while g is not empty:
+    // the next sink vertex is also removed from g,
+    // so its size is decreased at each iteration.
+    top_sort_list.add(g.get_next_sink_vertex())
+
+return top_sort_list
+
+

Note that by removing a node from a DAG, we will never create a cycle, +and thus we will always be able to find a sink vertex.

+
    +
  1. Topological ordering with dfs +Check the function implemented below. You should also check the function dfs.
  2. +
+

But using dfs should be as easy as the following pseudo-code shows:

+

TOPOLOGICAL-SORT(G): + DFS(G) + output vertices V in reversed order of finish times.

+
    +
  1. TOPOLOGICAL-SORT-SOL-3(G):
      +
    1. Find an vertex v with no incoming edges
    2. +
    3. Remove outgoing edge from v
    4. +
    5. Go back to step 1
    6. +
    +
  2. +
+

To improve the performance of this algorithm, +- we can pre-compute the number of incoming edges of each vertex. +- insert_key all nodes with degree of 0 into a queue +- repeat the following until the queue is not empty + * Take u from the queue + * for each v adj[u]: + decrement deg[v] (essentially removing edge u - v + if deg[v] == 0: + insert_key v into the queue

+

PROOF OF CORRECTNESS: +We basically need to show_nodes that, if (u, v) is a directed edge from u to v, +then u comes before v in the topological sort.

+

We have 2 cases: +1. u is visited by dfs before v +2. v is visited by dfs before u

+

Assume that u is visited before v (1). +Then there's nothing to prove, +because v will be set explored (and therefore at the right of u) before u.

+

So assume that v is visited before u (2). +This is only true if v was visited from another vertex +(and note that u has not yet been visited). +v will be therefore set as explored before even visiting u, +and thus v will be to the right of u in the topological order, +since u will be added to the topological order only when it is completely explored.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 25/08/15
+
+Notes: To reduce dependencies between modules,
+I decided to included a clean version of dfs here,
+specifically for the top_sort algorithm.
+
+Explanation:
+A topological ordering (or sorting, sort, order) of a directed graph with no cycles (DAG),
+is an order of the vertices, such that all the directed edges only go forward.
+
+Knowing the topological ordering of a DAG can be useful
+for example when we want to schedule certain tasks,
+where some tasks have higher precedence respect to others:
+in this example, vertices of the graph represent the tasks,
+and a directed edge from a certain node A to node B
+represents the fact that A must be "computed" or "executed" before B,
+A has higher precedence respect to B.
+
+When does a graph have a topological sorting?
+
+A graph has a topological sorting if and only if it has no cycles.
+
+The fact that a graph with a topological order
+has no cycles is quite easy to understand:
+Suppose a graph G has a cycle,
+then when searching for a topological ordering in the cycle,
+we are going at some point to return to a node that we have already visited,
+and this would mean that there's an edge that goes backward,
+which contradicts the fact that G has a topological ordering,
+by the definition of topological ordering.
+
+What if we don't have a cycle, do we always have a topological ordering?
+Yes [missing proof].
+
+We can find a topological ordering:
+1. without dfs (the straightforward solution)
+2. with depth-first search
+3. another solution
+
+1. Topological ordering without dfs (the straightforward solution)
+
+First, we need to have the notion of a "sink vertex",
+which simply is a vertex with no outgoing edges.
+
+Lemma: Every DAG must have a sink vertex.
+Proof: Suppose DAG g has no sink vertex and it has a finite number of vertices,
+then, when you are searching on the graph,
+you will always find at least one outgoing edge from every vertex.
+Since g has a finite number of vertices,
+we will find at some point a vertex that we have already visited,
+which would contradict the assumptions that g is a directed graph with no cycles (DAG).
+
+PSEUDO-CODE:
+
+TOP-SORT(DAG):
+    top_sort_list = []
+
+    create a copy of DAG called g
+
+    while g is not empty:
+        // the next sink vertex is also removed from g,
+        // so its size is decreased at each iteration.
+        top_sort_list.add(g.get_next_sink_vertex())
+
+    return top_sort_list
+
+Note that by removing a node from a DAG, we will never create a cycle,
+and thus we will always be able to find a sink vertex.
+
+
+2. Topological ordering with dfs
+Check the function implemented below. You should also check the function dfs.
+
+But using dfs should be as easy as the following pseudo-code shows:
+
+TOPOLOGICAL-SORT(G):
+    DFS(G)
+    output vertices V in reversed order of finish times.
+
+
+3. TOPOLOGICAL-SORT-SOL-3(G):
+    1. Find an vertex v with no incoming edges
+    2. Remove outgoing edge from v
+    3. Go back to step 1
+
+To improve the performance of this algorithm,
+- we can pre-compute the number of incoming edges of each vertex.
+- insert_key all nodes with degree of 0 into a queue
+- repeat the following until the queue is not empty
+    * Take u from the queue
+    * for each v adj[u]:
+        decrement deg[v] (essentially removing edge u - v
+        if deg[v] == 0:
+            insert_key v into the queue
+
+
+PROOF OF CORRECTNESS:
+We basically need to show_nodes that, if (u, v) is a directed edge from u to v,
+then u comes before v in the topological sort.
+
+We have 2 cases:
+1. u is visited by dfs before v
+2. v is visited by dfs before u
+
+Assume that u is visited before v (1).
+Then there's nothing to prove,
+because v will be set explored (and therefore at the right of u) before u.
+
+So assume that v is visited before u (2).
+This is only true if v was visited from another vertex
+(and note that u has not yet been visited).
+v will be therefore set as explored before even visiting u,
+and thus v will be to the right of u in the topological order,
+since u will be added to the topological order only when it is completely explored.
+
+"""
+
+from ands.ds.Graph import *
+
+
+def dfs_visit(graph: Graph, n: GraphNode):
+    """This function is called by dfs to further explore child nodes."""
+
+    n.color = GREY
+
+    for v in n.get_adjacent_nodes():
+        if v.color == WHITE:
+            dfs_visit(graph, v)
+
+    n.color = BLACK
+
+    # adding the just explored node to the list
+    # which represents the topological sort
+    graph.topological_sort.append(n)
+
+
+def dfs(graph: Graph):
+    """Typical dfs algorithm that traverses
+    all nodes that have not been explored yet
+    and keeps track of the visited and finished times."""
+
+    for n in graph.nodes:
+        n.color = WHITE
+
+    for n in graph.nodes:
+        if n.color == WHITE:
+            dfs_visit(graph, n)
+
+
+# TODO: DETECT CYCLE IN A "SUPPOSED" DAG
+
+def top_sort(dag: Graph):
+    """dag = DAG = Direct A-cycle Graph
+
+    If dag is not really a directed graph with no cycles (DAG),
+    the behaviour of this function is undefined.
+
+    This algorithm creates a topological sort
+    by using the ending visited times of each node:
+    once a node has been completely explored,
+    it is added to a list representing the topological sort.
+
+    Time complexity: O(|V| + |E|)"""
+    dfs(dag)
+
+    # dag.top_sort_with_dfs is an object of type TopologicalSortStack,
+    # so the first element of this object
+    # is the element at the bottom of the _stack
+    # and the last element is the element at the top
+    dag.topological_sort.reverse()
+
+    return dag.topological_sort
+
+
+def test_topological_sort():
+    g = Graph()
+
+    a = GraphNode("A")
+    b = GraphNode("B")
+    c = GraphNode("C")
+    d = GraphNode("D")
+
+    a.add_adjacent_node(b)
+    a.add_adjacent_node(c)
+
+    b.add_adjacent_node(d)
+
+    c.add_adjacent_node(d)
+
+    g.add_node(a)
+    g.add_node(b)
+    g.add_node(c)
+    g.add_node(d)
+
+    ts = top_sort(g)
+    ts = [x.key for x in ts]
+    print(ts)
+
+
+# https://en.wikipedia.org/wiki/Topological_sorting
+def test_topological_sort2():
+    g = Graph()
+
+    n7 = GraphNode(7)
+    n5 = GraphNode(5)
+    n3 = GraphNode(3)
+    n11 = GraphNode(11)
+    n8 = GraphNode(8)
+    n2 = GraphNode(2)
+    n9 = GraphNode(9)
+    n10 = GraphNode(10)
+
+    g.add_node(n2)
+    g.add_node(n7)
+    g.add_node(n5)
+    g.add_node(n3)
+    g.add_node(n11)
+    g.add_node(n8)
+    g.add_node(n9)
+    g.add_node(n10)
+
+    n7.add_adjacent_node(n11)
+    n7.add_adjacent_node(n8)
+
+    n5.add_adjacent_node(n11)
+
+    n3.add_adjacent_node(n8)
+    n3.add_adjacent_node(n10)
+
+    n11.add_adjacent_node(n2)
+    n11.add_adjacent_node(n9)
+    n11.add_adjacent_node(n10)
+
+    n8.add_adjacent_node(n9)
+
+    ts = top_sort(g)
+    ts = [x.key for x in ts]
+    print(ts)
+
+
+if __name__ == "__main__":
+    test_topological_sort()
+    test_topological_sort2()
+
+
+ +
+ +
+

Module variables

+
+

var BLACK

+ + +
+
+ +
+
+

var GREY

+ + +
+
+ +
+
+

var INFINITY

+ + +
+
+ +
+
+

var NIL

+ + +
+
+ +
+
+

var WHITE

+ + +
+
+ +
+ +

Functions

+ +
+
+

def dfs(

graph)

+
+ + + + +

Typical dfs algorithm that traverses +all nodes that have not been explored yet +and keeps track of the visited and finished times.

+
+ +
+
def dfs(graph: Graph):
+    """Typical dfs algorithm that traverses
+    all nodes that have not been explored yet
+    and keeps track of the visited and finished times."""
+
+    for n in graph.nodes:
+        n.color = WHITE
+
+    for n in graph.nodes:
+        if n.color == WHITE:
+            dfs_visit(graph, n)
+
+
+
+ +
+ + +
+
+

def dfs_visit(

graph, n)

+
+ + + + +

This function is called by dfs to further explore child nodes.

+
+ +
+
def dfs_visit(graph: Graph, n: GraphNode):
+    """This function is called by dfs to further explore child nodes."""
+
+    n.color = GREY
+
+    for v in n.get_adjacent_nodes():
+        if v.color == WHITE:
+            dfs_visit(graph, v)
+
+    n.color = BLACK
+
+    # adding the just explored node to the list
+    # which represents the topological sort
+    graph.topological_sort.append(n)
+
+
+
+ +
+ + +
+
+

def test_topological_sort(

)

+
+ + + + +
+ +
+
def test_topological_sort():
+    g = Graph()
+
+    a = GraphNode("A")
+    b = GraphNode("B")
+    c = GraphNode("C")
+    d = GraphNode("D")
+
+    a.add_adjacent_node(b)
+    a.add_adjacent_node(c)
+
+    b.add_adjacent_node(d)
+
+    c.add_adjacent_node(d)
+
+    g.add_node(a)
+    g.add_node(b)
+    g.add_node(c)
+    g.add_node(d)
+
+    ts = top_sort(g)
+    ts = [x.key for x in ts]
+    print(ts)
+
+
+
+ +
+ + +
+
+

def test_topological_sort2(

)

+
+ + + + +
+ +
+
def test_topological_sort2():
+    g = Graph()
+
+    n7 = GraphNode(7)
+    n5 = GraphNode(5)
+    n3 = GraphNode(3)
+    n11 = GraphNode(11)
+    n8 = GraphNode(8)
+    n2 = GraphNode(2)
+    n9 = GraphNode(9)
+    n10 = GraphNode(10)
+
+    g.add_node(n2)
+    g.add_node(n7)
+    g.add_node(n5)
+    g.add_node(n3)
+    g.add_node(n11)
+    g.add_node(n8)
+    g.add_node(n9)
+    g.add_node(n10)
+
+    n7.add_adjacent_node(n11)
+    n7.add_adjacent_node(n8)
+
+    n5.add_adjacent_node(n11)
+
+    n3.add_adjacent_node(n8)
+    n3.add_adjacent_node(n10)
+
+    n11.add_adjacent_node(n2)
+    n11.add_adjacent_node(n9)
+    n11.add_adjacent_node(n10)
+
+    n8.add_adjacent_node(n9)
+
+    ts = top_sort(g)
+    ts = [x.key for x in ts]
+    print(ts)
+
+
+
+ +
+ + +
+
+

def top_sort(

dag)

+
+ + + + +

dag = DAG = Direct A-cycle Graph

+

If dag is not really a directed graph with no cycles (DAG), +the behaviour of this function is undefined.

+

This algorithm creates a topological sort +by using the ending visited times of each node: +once a node has been completely explored, +it is added to a list representing the topological sort.

+

Time complexity: O(|V| + |E|)

+
+ +
+
def top_sort(dag: Graph):
+    """dag = DAG = Direct A-cycle Graph
+
+    If dag is not really a directed graph with no cycles (DAG),
+    the behaviour of this function is undefined.
+
+    This algorithm creates a topological sort
+    by using the ending visited times of each node:
+    once a node has been completely explored,
+    it is added to a list representing the topological sort.
+
+    Time complexity: O(|V| + |E|)"""
+    dfs(dag)
+
+    # dag.top_sort_with_dfs is an object of type TopologicalSortStack,
+    # so the first element of this object
+    # is the element at the bottom of the _stack
+    # and the last element is the element at the top
+    dag.topological_sort.reverse()
+
+    return dag.topological_sort
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/graphs/top_three_friends_of_friends.m.html b/docs/ands/algorithms/graphs/top_three_friends_of_friends.m.html new file mode 100644 index 00000000..3e07e4ca --- /dev/null +++ b/docs/ands/algorithms/graphs/top_three_friends_of_friends.m.html @@ -0,0 +1,1235 @@ + + + + + + ands.algorithms.graphs.top_three_friends_of_friends API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.graphs.top_three_friends_of_friends module

+

Author: Nelson Brochado +Creation: 05/09/15

+

Exercise 127.

+

Consider a social network system that, for each user u, +stores u's friends in a list friends(u). +Implement an algorithm Top-Three-Friends-Of-Friends(u) that, +given a user u, recommends the three other users that are not +already among u's friends but are among +the friends of most of u's friends. +Also, analyze the complexity of the Top-Three-Friends-Of-Friends algorithm.

+

My idea: +Build the network system as a graph.

+

But how do we find the 3 friends of friends to suggest to the user u?

+

Given a user u, we can iterate through its friends.

+

First, we should create an empty list "suggestions" to store the suggestions.

+

Then we iterate through each adjacent node w +of each adjacent node "v" of the node "user", +and we check two things: + 1. that w is not adjacent to "user" + 2. w is not already in the list "suggestions".

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+Creation: 05/09/15
+
+Exercise 127.
+
+Consider a social network system that, for each user u,
+stores u's friends in a list friends(u).
+Implement an algorithm Top-Three-Friends-Of-Friends(u) that,
+given a user u, recommends the three other users that are not
+already among u's friends but are among
+the friends of most of u's friends.
+Also, analyze the complexity of the Top-Three-Friends-Of-Friends algorithm.
+
+
+My idea:
+Build the network system as a graph.
+
+But how do we find the 3 friends of friends to suggest to the user u?
+
+Given a user u, we can iterate through its friends.
+
+First, we should create an empty list "suggestions" to store the suggestions.
+
+Then we iterate through each adjacent node w
+of each adjacent node "v" of the node "user",
+and we check two things:
+    1. that w is not adjacent to "user"
+    2. w is not already in the list "suggestions".
+"""
+
+from pprint import pprint
+
+from ands.ds.Graph import *
+
+
+def top_three_friends_of_friends(user, k=3):
+    """Return a list of suggestions of friends for user.
+
+    Running time complexity:
+     - O(n^3) in the worst case
+     - O(n^2) in the average case.
+
+    Node that searching for element in a set
+    is in average a constant operation,
+    but in the worst case it can be linear.
+
+    :type user : GraphNode
+    """
+    friends = set(user.get_adjacent_nodes())
+    suggestions = set()
+
+    for friend in friends:
+        for ff in friend.get_adjacent_nodes():
+            if ff != user and ff not in friends and \
+                    ff not in suggestions and len(suggestions) < k:
+                suggestions.add(ff)
+
+    return suggestions
+
+
+if __name__ == "__main__":
+    sn = Graph("Social Network")
+
+    a = GraphNode("a")
+    b = GraphNode("b")
+    c = GraphNode("c")
+    d = GraphNode("d")
+    e = GraphNode("e")
+    f = GraphNode("f")
+    g = GraphNode("g")
+
+    sn.add_nodes((a, b, c, d, e, f, g))
+
+    sn.add_undirected_edge(a, d)
+    sn.add_undirected_edge(a, c)
+    sn.add_undirected_edge(a, b)
+    sn.add_undirected_edge(b, g)
+    sn.add_undirected_edge(d, f)
+    sn.add_undirected_edge(d, e)
+
+    pprint(top_three_friends_of_friends(a))
+
+
+ +
+ +
+

Module variables

+
+

var BLACK

+ + +
+
+ +
+
+

var GREY

+ + +
+
+ +
+
+

var INFINITY

+ + +
+
+ +
+
+

var NIL

+ + +
+
+ +
+
+

var WHITE

+ + +
+
+ +
+ +

Functions

+ +
+
+

def top_three_friends_of_friends(

user, k=3)

+
+ + + + +

Return a list of suggestions of friends for user.

+

Running time complexity: + - O(n^3) in the worst case + - O(n^2) in the average case.

+

Node that searching for element in a set +is in average a constant operation, +but in the worst case it can be linear.

+

:type user : GraphNode

+
+ +
+
def top_three_friends_of_friends(user, k=3):
+    """Return a list of suggestions of friends for user.
+
+    Running time complexity:
+     - O(n^3) in the worst case
+     - O(n^2) in the average case.
+
+    Node that searching for element in a set
+    is in average a constant operation,
+    but in the worst case it can be linear.
+
+    :type user : GraphNode
+    """
+    friends = set(user.get_adjacent_nodes())
+    suggestions = set()
+
+    for friend in friends:
+        for ff in friend.get_adjacent_nodes():
+            if ff != user and ff not in friends and \
+                    ff not in suggestions and len(suggestions) < k:
+                suggestions.add(ff)
+
+    return suggestions
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/greedy/activity_selection.m.html b/docs/ands/algorithms/greedy/activity_selection.m.html new file mode 100644 index 00000000..4bfef6a5 --- /dev/null +++ b/docs/ands/algorithms/greedy/activity_selection.m.html @@ -0,0 +1,1261 @@ + + + + + + ands.algorithms.greedy.activity_selection API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.greedy.activity_selection module

+

Author: Nelson Brochado

+

Consider a set of requests for a room.

+

Only one person can reserve the room at a time, +and you want to allow the maximum number of requests.

+

The requests for periods (si=start time for i, fi=finish time for i) are:

+

(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11), (8, 12), (2, 13), (12, 14)

+

Where for example in (1, 4), s1 = 1 and f1 = 4.

+

Which ones should we schedule?

+

We can solve this problem using dynamic programming, +but it we can also solve it using a simple greedy algorithm.

+

The algorithm consists of basically choosing the next activity +with the smallest finish time. +So, to do this, we need first to sort the activities by finish time. +This algorithm is a top-down algorithm, +in the sense that we can start by choosing an activity, +the one with the earliest finish time, +and then we can do the same for the remaining sub-problems.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Consider a set of requests for a room.
+
+Only one person can reserve the room at a time,
+and you want to allow the maximum number of requests.
+
+The requests for periods (si=start time for i, fi=finish time for i) are:
+
+(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11), (8, 12), (2, 13), (12, 14)
+
+Where for example in (1, 4), s1 = 1 and f1 = 4.
+
+Which ones should we schedule?
+
+We can solve this problem using dynamic programming,
+but it we can also solve it using a simple greedy algorithm.
+
+The algorithm consists of basically choosing the next activity
+with the smallest finish time.
+So, to do this, we need first to sort the activities by finish time.
+This algorithm is a top-down algorithm,
+in the sense that we can start by choosing an activity,
+the one with the earliest finish time,
+and then we can do the same for the remaining sub-problems.
+"""
+
+import operator
+
+from tabulate import tabulate
+
+activities = [["a", 12, 14], ["b", 0, 6], ["c", 2, 13], ["d", 3, 5],
+              ["e", 5, 7], ["f", 1, 4], ["g", 5, 9], ["h", 3, 8],
+              ["i", 12, 14], ["j", 6, 10], ["k", 8, 11]]
+
+
+def ask_activities():
+    print("Welcome to the Activity Selection problem!\n\n" +
+          "You tell me your activities and their starting\n" +
+          "and ending times, and I will tell you which\n" +
+          "activities are compatible, and should take place.")
+
+    print("=" * 48, end="\n\n")
+    activities.clear()
+
+    while True:
+        name = input("Enter name of the activity: ")
+        starting_time = int(input("Enter the starting time of " + name + ": "))
+        ending_time = int(input("Enter the ending time of " + name + ": "))
+        activities.append([name, starting_time, ending_time])
+
+        a = input("\nType q if you don't have more activities: ")
+
+        if a.lower() == "q":
+            break
+        else:
+            print("-" * 48, end="\n\n")
+
+
+def activity_selector(activities, verbose=True):
+    # sorting activities by finish time
+    activities.sort(key=operator.itemgetter(2))
+
+    if verbose:
+        print("\nAll activities ordered by finish time:")
+        print(tabulate(activities,
+                       headers=(
+                           "Activity's Name",
+                           "Starting Time",
+                           "Ending Time"),
+                       tablefmt="grid"))
+
+    last_selected_activity = activities[0]
+
+    selected_activities = [activities[0]]
+
+    for i in range(1, len(activities)):
+        # if the starting time of the ith activity
+        # is greater or equal to the ending time
+        # of the last selected activity
+        # then add the ith activity to the selected activities.
+        if activities[i][1] >= last_selected_activity[2]:
+            selected_activities.append(activities[i])
+            last_selected_activity = activities[i]
+
+    if verbose:
+        print("\n\nYou should schedule your activities in the following way:")
+        print(tabulate(selected_activities,
+                       headers=(
+                           "Activity's Name",
+                           "Starting Time",
+                           "Ending Time"),
+                       tablefmt="grid"))
+
+    return selected_activities
+
+
+if __name__ == "__main__":
+    # ask_activities()  # uncomment this line if you want to choose your
+    # activities manually
+    sa = activity_selector(activities)
+    print(sa)
+
+
+ +
+ +
+

Module variables

+
+

var activities

+ + +
+
+ +
+ +

Functions

+ +
+
+

def activity_selector(

activities, verbose=True)

+
+ + + + +
+ +
+
def activity_selector(activities, verbose=True):
+    # sorting activities by finish time
+    activities.sort(key=operator.itemgetter(2))
+
+    if verbose:
+        print("\nAll activities ordered by finish time:")
+        print(tabulate(activities,
+                       headers=(
+                           "Activity's Name",
+                           "Starting Time",
+                           "Ending Time"),
+                       tablefmt="grid"))
+
+    last_selected_activity = activities[0]
+
+    selected_activities = [activities[0]]
+
+    for i in range(1, len(activities)):
+        # if the starting time of the ith activity
+        # is greater or equal to the ending time
+        # of the last selected activity
+        # then add the ith activity to the selected activities.
+        if activities[i][1] >= last_selected_activity[2]:
+            selected_activities.append(activities[i])
+            last_selected_activity = activities[i]
+
+    if verbose:
+        print("\n\nYou should schedule your activities in the following way:")
+        print(tabulate(selected_activities,
+                       headers=(
+                           "Activity's Name",
+                           "Starting Time",
+                           "Ending Time"),
+                       tablefmt="grid"))
+
+    return selected_activities
+
+
+
+ +
+ + +
+
+

def ask_activities(

)

+
+ + + + +
+ +
+
def ask_activities():
+    print("Welcome to the Activity Selection problem!\n\n" +
+          "You tell me your activities and their starting\n" +
+          "and ending times, and I will tell you which\n" +
+          "activities are compatible, and should take place.")
+
+    print("=" * 48, end="\n\n")
+    activities.clear()
+
+    while True:
+        name = input("Enter name of the activity: ")
+        starting_time = int(input("Enter the starting time of " + name + ": "))
+        ending_time = int(input("Enter the ending time of " + name + ": "))
+        activities.append([name, starting_time, ending_time])
+
+        a = input("\nType q if you don't have more activities: ")
+
+        if a.lower() == "q":
+            break
+        else:
+            print("-" * 48, end="\n\n")
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/greedy/fractional_knapsack.m.html b/docs/ands/algorithms/greedy/fractional_knapsack.m.html new file mode 100644 index 00000000..65519fb6 --- /dev/null +++ b/docs/ands/algorithms/greedy/fractional_knapsack.m.html @@ -0,0 +1,1250 @@ + + + + + + ands.algorithms.greedy.fractional_knapsack API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.greedy.fractional_knapsack module

+

Author: Nelson Brochado

+

The time complexity of the fractional knapsack is O(n*log(n)), +because of the call to sort the items by value/weight ratio.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+The time complexity of the fractional knapsack is O(n*log(n)),
+because of the call to sort the items by value/weight ratio.
+"""
+
+import operator
+
+from tabulate import tabulate
+
+
+def ask_objects():
+    objects = []
+    print("Welcome to the Fractional Knapsack problem!\n\n" +
+          "You will tell me the objects that you have,\n" +
+          "their path_cost and their weight.\n\n" +
+          "You should also tell me after that\n"
+          "how much weight you can carry with you.\n\n" +
+          "I will tell you then which items or\n" +
+          "fraction of items you should take.\n")
+
+    input("When you are ready, press ENTER.\n" + "=" * 40 + "\n\n")
+
+    while True:
+        name = input("Enter the name of the object: ")
+        cost = int(input("Enter the value of " + name + ": "))
+        weight = int(input("Enter the weight (in grams) of " + name + ": "))
+        objects.append([name, cost, weight])
+        yn = input("\nDo you have other items (y/n)? ")
+
+        if yn.lower() in ("n", "no"):
+            break
+        else:
+            print("-" * 40, end="\n\n")
+
+    for obj in objects:
+        # adding as forth property of each object its path_cost/weight ratio
+        obj.append(obj[1] / obj[2])
+
+    objects.sort(key=operator.itemgetter(3), reverse=True)
+
+    print("\n\nThe following are the items that you have:\n")
+    print(
+        tabulate(
+            objects,
+            tablefmt="grid",
+            headers=(
+                "Name",
+                "Value",
+                "Weight",
+                "Value/Weight Ratio")))
+    capacity = int(
+        input("\nEnter the maximum weight you can bring (in grams): "))
+
+    return objects, capacity
+
+
+def interactive_fractional_knapsack():
+    objects, capacity = ask_objects()
+    current_weight = 0
+    knapsack_objects = []
+
+    for i, obj in enumerate(objects):
+        if obj[2] + current_weight <= capacity:
+            current_weight += obj[2]
+            knapsack_objects.append(i)
+        else:
+            remaining_weight = capacity - current_weight
+            knapsack_objects.append((i, remaining_weight))
+            break
+    output_fractional_knapsack(knapsack_objects, objects)
+
+
+def output_fractional_knapsack(knapsack_objects, objects):
+    s = "You should take "
+
+    for i, item in enumerate(knapsack_objects):
+        if not isinstance(item, tuple):
+            s += str(objects[item][2]) + " gram(s) of " + objects[item][0]
+            if i < len(knapsack_objects) - 1:
+                s += ", "
+        else:
+            s += " and " + str(item[1]) + " gram(s) of " + \
+                 objects[item[0]][0] + "."
+
+    print("\n\n" + s)
+
+# if __name__ == "__main__":
+# interactive_fractional_knapsack()
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def ask_objects(

)

+
+ + + + +
+ +
+
def ask_objects():
+    objects = []
+    print("Welcome to the Fractional Knapsack problem!\n\n" +
+          "You will tell me the objects that you have,\n" +
+          "their path_cost and their weight.\n\n" +
+          "You should also tell me after that\n"
+          "how much weight you can carry with you.\n\n" +
+          "I will tell you then which items or\n" +
+          "fraction of items you should take.\n")
+
+    input("When you are ready, press ENTER.\n" + "=" * 40 + "\n\n")
+
+    while True:
+        name = input("Enter the name of the object: ")
+        cost = int(input("Enter the value of " + name + ": "))
+        weight = int(input("Enter the weight (in grams) of " + name + ": "))
+        objects.append([name, cost, weight])
+        yn = input("\nDo you have other items (y/n)? ")
+
+        if yn.lower() in ("n", "no"):
+            break
+        else:
+            print("-" * 40, end="\n\n")
+
+    for obj in objects:
+        # adding as forth property of each object its path_cost/weight ratio
+        obj.append(obj[1] / obj[2])
+
+    objects.sort(key=operator.itemgetter(3), reverse=True)
+
+    print("\n\nThe following are the items that you have:\n")
+    print(
+        tabulate(
+            objects,
+            tablefmt="grid",
+            headers=(
+                "Name",
+                "Value",
+                "Weight",
+                "Value/Weight Ratio")))
+    capacity = int(
+        input("\nEnter the maximum weight you can bring (in grams): "))
+
+    return objects, capacity
+
+
+
+ +
+ + +
+
+

def interactive_fractional_knapsack(

)

+
+ + + + +
+ +
+
def interactive_fractional_knapsack():
+    objects, capacity = ask_objects()
+    current_weight = 0
+    knapsack_objects = []
+
+    for i, obj in enumerate(objects):
+        if obj[2] + current_weight <= capacity:
+            current_weight += obj[2]
+            knapsack_objects.append(i)
+        else:
+            remaining_weight = capacity - current_weight
+            knapsack_objects.append((i, remaining_weight))
+            break
+    output_fractional_knapsack(knapsack_objects, objects)
+
+
+
+ +
+ + +
+
+

def output_fractional_knapsack(

knapsack_objects, objects)

+
+ + + + +
+ +
+
def output_fractional_knapsack(knapsack_objects, objects):
+    s = "You should take "
+
+    for i, item in enumerate(knapsack_objects):
+        if not isinstance(item, tuple):
+            s += str(objects[item][2]) + " gram(s) of " + objects[item][0]
+            if i < len(knapsack_objects) - 1:
+                s += ", "
+        else:
+            s += " and " + str(item[1]) + " gram(s) of " + \
+                 objects[item[0]][0] + "."
+
+    print("\n\n" + s)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/greedy/huffman.m.html b/docs/ands/algorithms/greedy/huffman.m.html new file mode 100644 index 00000000..1b6c39fd --- /dev/null +++ b/docs/ands/algorithms/greedy/huffman.m.html @@ -0,0 +1,1427 @@ + + + + + + ands.algorithms.greedy.huffman API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.greedy.huffman module

+

Author: Nelson Brochado

+

Warning

+

There are still some functions that I need to implement, +but the basic idea of the huffman algorithm is here, +so I decided to include it already in this repository.

+

Description

+

Huffman coding to encode messages with a variable length of bits.

+

The tree created by the huffman algorithm is not a binary search tree, +but simply a binary tree, that is the left child is not necessarily smaller +than the node i, or the right child is not necessarily greater than the node i, +for all nodes i in the tree.

+

The tree produce by the huffman algorithm is a full tree: +if C is the alphabet from which the characters are drawn and +all characters frequencies are positive, +then the tree for an optimal prefix code as exactly |C| leaves, +one for each character in C, and exactly |C| - 1 internal nodes, +and by the way we need |C| - 1 merges to construct the tree.

+

Note that the codewords produced by the build_huffman_codes +contain distinct prefixes among each other, +and this is very useful when decoding the huffman codes, +because there's no ambiguity.

+

Given a tree T corresponding to a prefix code, +we can easily compute the number of bits necessary to encode a message, +given a certain alphabet C: +For each character c in the alphabet C, +let c.freq be the frequency of c in the message to encode, +and let d(c) denote the depth of the leaf c in the tree +(note that d(c) is also the length of the codeword to encode c), +the number of bits to encode the message is therefore:

+

bits(T):= sum {for all c in C} of c.freq * d(c).

+

TODO

+
    +
  • Correctness of Huffman algorithm
  • +
+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+### Warning
+
+There are still some functions that I need to implement,
+but the basic idea of the huffman algorithm is here,
+so I decided to include it already in this repository.
+
+### Description
+
+Huffman coding to encode messages with a variable length of bits.
+
+The tree created by the huffman algorithm is not a binary search tree,
+but simply a binary tree, that is the left child is not necessarily smaller
+than the node i, or the right child is not necessarily greater than the node i,
+for all nodes i in the tree.
+
+The tree produce by the huffman algorithm is a full tree:
+if C is the alphabet from which the characters are drawn and
+all characters frequencies are positive,
+then the tree for an optimal prefix code as exactly |C| leaves,
+one for each character in C, and exactly |C| - 1 internal nodes,
+and by the way we need |C| - 1 merges to construct the tree.
+
+Note that the codewords produced by the build_huffman_codes
+contain distinct prefixes among each other,
+and this is very useful when decoding the huffman codes,
+because there's no ambiguity.
+
+Given a tree T corresponding to a prefix code,
+we can easily compute the number of bits necessary to encode a message,
+given a certain alphabet C:
+For each character c in the alphabet C,
+let c.freq be the frequency of c in the message to encode,
+and let d(c) denote the depth of the leaf c in the tree
+(note that d(c) is also the length of the codeword to encode c),
+the number of bits to encode the message is therefore:
+
+bits(T):= sum {for all c in C} of c.freq * d(c).
+
+
+### TODO
+- Correctness of Huffman algorithm
+"""
+
+from ands.ds.MinPriorityQueue import MinPriorityQueue
+
+from ands.ds.heap import HeapNode
+
+# export only functions and classes of this module that are implemented
+__all__ = ["calculate_frequencies",
+           "print_frequencies",
+           "huffman",
+           "build_huffman_codes",
+           "huffman_fibonacci_encoder"]
+
+
+def calculate_frequencies(message):
+    frequencies = {}
+    for char in message:
+        if char not in frequencies.keys():
+            frequencies[char] = 1
+        else:
+            frequencies[char] += 1
+    return frequencies
+
+
+def print_frequencies(frequencies):
+    from tabulate import tabulate
+    import operator
+    print(tabulate(sorted(frequencies.items(), key=operator.itemgetter(1)), headers=("Letter", "Frequency"),
+                   tablefmt="grid"))
+
+
+def huffman(message: str, verbose=False):
+    """Creates a Huffman tree representing all the codewords for message.
+
+    **Time Complexity**: O(n*log2(n))."""
+
+    # Counting the frequencies of each character or symbol
+    frequencies = calculate_frequencies(message)
+
+    if verbose:
+        print_frequencies(frequencies)
+
+    # Creates a queue in O(n) time using the build-min-heap algorithm.
+    mpq = MinPriorityQueue(frequencies.items())
+
+    while not mpq.is_empty():
+
+        left = mpq.extract_min(heap_node=True)
+        right = mpq.extract_min(heap_node=True)
+
+        if right is None:
+            return left
+
+        node = HeapNode(key=left.key + right.key, value=left.value + right.value)
+
+        node.left = left
+        node.right = right
+
+        left.parent = node
+        right.parent = node
+
+        mpq.insert_with_priority(node)
+
+
+def build_huffman_codes(root: HeapNode):
+    """Starting from the `root` node obtained by the `huffman` algorithm,
+    this function builds a dictionary where keys are the original characters,
+    and their values are the corresponding huffman codes."""
+    huffman_codes = {}
+
+    for char in root.value:
+        current_node = root
+        huffman_code = ""  # huffman code of char
+
+        while True:
+            if current_node.left is not None and \
+                    char in current_node.left.value:
+                huffman_code += "0"
+                current_node = current_node.left
+            elif current_node.right is not None:
+                huffman_code += "1"
+                current_node = current_node.right
+            else:
+                break
+
+        huffman_codes[char] = huffman_code
+
+    return huffman_codes
+
+
+# TODO: CREATE AN ENCODE AND DECODE FUNCTIONS
+
+def huffman_encoder(huffman_codes):
+    pass
+
+
+def huffman_decoder(encoded_message):
+    pass
+
+
+def huffman_fibonacci_encoder(fn: "list of list"):
+    """`fn` is supposed to be a `list` of `tuple`s,
+    whose first element is the character or symbol,
+    whereas the second element is its corresponding frequency,
+    that happen to be the Fibonacci numbers."""
+    import operator
+
+    fn.sort(key=operator.itemgetter(1), reverse=True)  # sorts by frequency
+    huffman_codes = {fn[-1][0]: "0" * (len(fn) - 1)}
+
+    for i, char in enumerate(fn[:-1]):
+        prefix = "0" * i
+        codeword = prefix + "1"
+        huffman_codes[char[0]] = codeword
+    return huffman_codes
+
+
+hfc = huffman_fibonacci_encoder([("a", 1), ("b", 1), ("c", 2), ("d", 3),
+                                 ("e", 5), ("f", 8), ("g", 13), ("h", 21)])
+
+
+def huffman_fibonacci_decoder(encoded_message):
+    # TODO: Huffman decoder for Fibonacci numbers
+    pass
+
+
+if __name__ == "__main__":
+    root1 = huffman("Cyka Blyat")
+    root2 = huffman("Shook Ones")
+    print(build_huffman_codes(root1))
+    print(build_huffman_codes(root2))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def build_huffman_codes(

root)

+
+ + + + +

Starting from the root node obtained by the huffman algorithm, +this function builds a dictionary where keys are the original characters, +and their values are the corresponding huffman codes.

+
+ +
+
def build_huffman_codes(root: HeapNode):
+    """Starting from the `root` node obtained by the `huffman` algorithm,
+    this function builds a dictionary where keys are the original characters,
+    and their values are the corresponding huffman codes."""
+    huffman_codes = {}
+
+    for char in root.value:
+        current_node = root
+        huffman_code = ""  # huffman code of char
+
+        while True:
+            if current_node.left is not None and \
+                    char in current_node.left.value:
+                huffman_code += "0"
+                current_node = current_node.left
+            elif current_node.right is not None:
+                huffman_code += "1"
+                current_node = current_node.right
+            else:
+                break
+
+        huffman_codes[char] = huffman_code
+
+    return huffman_codes
+
+
+
+ +
+ + +
+
+

def calculate_frequencies(

message)

+
+ + + + +
+ +
+
def calculate_frequencies(message):
+    frequencies = {}
+    for char in message:
+        if char not in frequencies.keys():
+            frequencies[char] = 1
+        else:
+            frequencies[char] += 1
+    return frequencies
+
+
+
+ +
+ + +
+
+

def huffman(

message, verbose=False)

+
+ + + + +

Creates a Huffman tree representing all the codewords for message.

+

Time Complexity: O(n*log2(n)).

+
+ +
+
def huffman(message: str, verbose=False):
+    """Creates a Huffman tree representing all the codewords for message.
+
+    **Time Complexity**: O(n*log2(n))."""
+
+    # Counting the frequencies of each character or symbol
+    frequencies = calculate_frequencies(message)
+
+    if verbose:
+        print_frequencies(frequencies)
+
+    # Creates a queue in O(n) time using the build-min-heap algorithm.
+    mpq = MinPriorityQueue(frequencies.items())
+
+    while not mpq.is_empty():
+
+        left = mpq.extract_min(heap_node=True)
+        right = mpq.extract_min(heap_node=True)
+
+        if right is None:
+            return left
+
+        node = HeapNode(key=left.key + right.key, value=left.value + right.value)
+
+        node.left = left
+        node.right = right
+
+        left.parent = node
+        right.parent = node
+
+        mpq.insert_with_priority(node)
+
+
+
+ +
+ + +
+
+

def huffman_fibonacci_encoder(

fn)

+
+ + + + +

fn is supposed to be a list of tuples, +whose first element is the character or symbol, +whereas the second element is its corresponding frequency, +that happen to be the Fibonacci numbers.

+
+ +
+
def huffman_fibonacci_encoder(fn: "list of list"):
+    """`fn` is supposed to be a `list` of `tuple`s,
+    whose first element is the character or symbol,
+    whereas the second element is its corresponding frequency,
+    that happen to be the Fibonacci numbers."""
+    import operator
+
+    fn.sort(key=operator.itemgetter(1), reverse=True)  # sorts by frequency
+    huffman_codes = {fn[-1][0]: "0" * (len(fn) - 1)}
+
+    for i, char in enumerate(fn[:-1]):
+        prefix = "0" * i
+        codeword = prefix + "1"
+        huffman_codes[char[0]] = codeword
+    return huffman_codes
+
+
+
+ +
+ + +
+
+

def print_frequencies(

frequencies)

+
+ + + + +
+ +
+
def print_frequencies(frequencies):
+    from tabulate import tabulate
+    import operator
+    print(tabulate(sorted(frequencies.items(), key=operator.itemgetter(1)), headers=("Letter", "Frequency"),
+                   tablefmt="grid"))
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/greedy/index.html b/docs/ands/algorithms/greedy/index.html new file mode 100644 index 00000000..3c65cc2b --- /dev/null +++ b/docs/ands/algorithms/greedy/index.html @@ -0,0 +1,1064 @@ + + + + + + ands.algorithms.greedy API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.greedy module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms.greedy.activity_selection

+ + +

Author: Nelson Brochado

+

Consider a set of requests for a room.

+

Only one person can reserve the room at a time, +and you want to allow the maximum number of requests.

+

The requests for periods (si=start time for i, fi=finish time for i) are:

+

(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), ...

+ +
+
+

ands.algorithms.greedy.fractional_knapsack

+ + +

Author: Nelson Brochado

+

The time complexity of the fractional knapsack is O(n*log(n)), +because of the call to sort the items by value/weight ratio.

+ +
+
+

ands.algorithms.greedy.huffman

+ + +

Author: Nelson Brochado

+

Warning

+

There are still some functions that I need to implement, +but the basic idea of the huffman algorithm is here, +so I decided to include it already in this repository.

+

Description

+

Huffman coding to encode messages with a variable length of bits.

+

The tree cre...

+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/index.html b/docs/ands/algorithms/index.html new file mode 100644 index 00000000..776f5afb --- /dev/null +++ b/docs/ands/algorithms/index.html @@ -0,0 +1,1103 @@ + + + + + + ands.algorithms API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+ + +
+

ands.algorithms.dp

+ + + +
+ + + + + + + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/math/arithmetic/index.html b/docs/ands/algorithms/math/arithmetic/index.html new file mode 100644 index 00000000..b08faebd --- /dev/null +++ b/docs/ands/algorithms/math/arithmetic/index.html @@ -0,0 +1,1035 @@ + + + + + + ands.algorithms.math.arithmetic API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.math.arithmetic module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms.math.arithmetic.sum

+ + +

Author: Nelson Brochado

+

Created: 21/01/2017

+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/math/arithmetic/sum.m.html b/docs/ands/algorithms/math/arithmetic/sum.m.html new file mode 100644 index 00000000..28d87a29 --- /dev/null +++ b/docs/ands/algorithms/math/arithmetic/sum.m.html @@ -0,0 +1,1176 @@ + + + + + + ands.algorithms.math.arithmetic.sum API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.math.arithmetic.sum module

+

Author: Nelson Brochado

+

Created: 21/01/2017

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Created: 21/01/2017
+"""
+
+from ands.algorithms.math.combinatorics.n_choose_k import *
+
+
+def a_plus_b_all_to_n(a, b, n):
+    """Solve polynomials of the form (a + b)n."""
+    s = 0
+    for k in range(0, n + 1):
+        s += n_choose_k(n, k) * (a ** k) * (b ** (n - k))
+    return s
+
+
+def sum_first_m_pos_nat_nums(m):
+    """Sum first `m` natural numbers (starting from 1).
+
+    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
+    return n_choose_k_2(m + 1, 2)
+
+
+def sum_first_m_pos_nat_nums_2(m):
+    """Sum first m natural numbers (starting from 1).
+
+    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
+    return ((m + 1) * m) / (2)
+
+
+def sum_first_m_pos_nat_nums_3(m):
+    """Sum first m natural numbers (starting from 1).
+
+    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
+    s = 0
+    for i in range(m):
+        s += (i + 1)
+    return s
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def a_plus_b_all_to_n(

a, b, n)

+
+ + + + +

Solve polynomials of the form (a + b)n.

+
+ +
+
def a_plus_b_all_to_n(a, b, n):
+    """Solve polynomials of the form (a + b)n."""
+    s = 0
+    for k in range(0, n + 1):
+        s += n_choose_k(n, k) * (a ** k) * (b ** (n - k))
+    return s
+
+
+
+ +
+ + +
+
+

def sum_first_m_pos_nat_nums(

m)

+
+ + + + +

Sum first m natural numbers (starting from 1).

+

sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)

+
+ +
+
def sum_first_m_pos_nat_nums(m):
+    """Sum first `m` natural numbers (starting from 1).
+
+    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
+    return n_choose_k_2(m + 1, 2)
+
+
+
+ +
+ + +
+
+

def sum_first_m_pos_nat_nums_2(

m)

+
+ + + + +

Sum first m natural numbers (starting from 1).

+

sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)

+
+ +
+
def sum_first_m_pos_nat_nums_2(m):
+    """Sum first m natural numbers (starting from 1).
+
+    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
+    return ((m + 1) * m) / (2)
+
+
+
+ +
+ + +
+
+

def sum_first_m_pos_nat_nums_3(

m)

+
+ + + + +

Sum first m natural numbers (starting from 1).

+

sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)

+
+ +
+
def sum_first_m_pos_nat_nums_3(m):
+    """Sum first m natural numbers (starting from 1).
+
+    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
+    s = 0
+    for i in range(m):
+        s += (i + 1)
+    return s
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/math/combinatorics/index.html b/docs/ands/algorithms/math/combinatorics/index.html new file mode 100644 index 00000000..1e40d113 --- /dev/null +++ b/docs/ands/algorithms/math/combinatorics/index.html @@ -0,0 +1,1041 @@ + + + + + + ands.algorithms.math.combinatorics API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.math.combinatorics module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms.math.combinatorics.n_choose_k

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 20/01/2017

+

Description

+

"n choose k" is an operation that appears often in combinatorics. +"n choose k" is defined as (n!)/(k! * (n - k)!). +It basically represents the number of ways to choose a subset of size k elements, +disregarding their order, f...

+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/math/combinatorics/n_choose_k.m.html b/docs/ands/algorithms/math/combinatorics/n_choose_k.m.html new file mode 100644 index 00000000..5d8a4f68 --- /dev/null +++ b/docs/ands/algorithms/math/combinatorics/n_choose_k.m.html @@ -0,0 +1,1238 @@ + + + + + + ands.algorithms.math.combinatorics.n_choose_k API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.math.combinatorics.n_choose_k module

+

Meta info

+

Author: Nelson Brochado

+

Created: 20/01/2017

+

Description

+

"n choose k" is an operation that appears often in combinatorics. +"n choose k" is defined as (n!)/(k! * (n - k)!). +It basically represents the number of ways to choose a subset of size k elements, +disregarding their order, from a set of n elements. +"Disregarding the order" means that, e.g., if you have a set A = {x, y, z}, +then the collections of two elements B = [x, y] and C = [y, x] would be considered the same, +because the order of the elements does not count, i.e. x, y ~= y, x,

+

Resources

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+
+Created: 20/01/2017
+
+## Description
+
+"n choose k" is an operation that appears often in combinatorics.
+"n choose k" is defined as (n!)/(k! * (n - k)!).
+It basically represents the number of ways to choose a subset of size k elements,
+disregarding their order, from a set of n elements.
+"Disregarding the order" means that, e.g., if you have a set A = {x, y, z},
+then the collections of two elements B = [x, y] and C = [y, x] would be considered the same,
+because the order of the elements does not count, i.e. x, y ~= y, x,
+
+## Resources
+
+- [https://en.wikipedia.org/wiki/Binomial_coefficient](https://en.wikipedia.org/wiki/Binomial_coefficient)
+
+"""
+
+from ands.algorithms.recursion.factorial import *
+
+
+def n_choose_k(n: int, k: int) -> int:
+    """Returns the number of ways of choosing `k` elements,
+    disregarding their order, from a set of `n` elements.
+
+    Assumes n >= 0 and k >= 0.
+    This is because not everyone defines "n choose k" for negative `n`s  or `k`s.
+    See here: http://math.stackexchange.com/questions/527831/is-n-choose-k-defined-when-k-0-what-about-n-k"""
+    assert n >= 0 and k >= 0
+
+    if n == k or k == 0:
+        return 1
+    if k > n:
+        return 0
+
+    return factorial(n) / (factorial(k) * factorial(n - k))
+
+
+def n_choose_k_2(n: int, k: int) -> int:
+    """'n choose k' operation can be recursively defined as:
+    'n - 1 choose k - 1' + 'n - 1 choose k',
+    for all integers `n` and `k`, such that `1 <= k <= n - 1`.
+
+    **Proof:**
+
+    Suppose that we have n persons, one named Fred,
+    and that we want to select a committee of size k.
+    There are 'n choose k' different committees.
+    On the other hand, there are 'n - 1 choose k - 1' committees
+    with Fred as a member, and 'n - 1 choose k' committees
+    without Fred as a member.
+    The sum of these two numbers is also the number of committees.
+    """
+    return n_choose_k(n - 1, k - 1) + n_choose_k(n - 1, k)
+
+
+def n_choose_k_3(n: int, k: int) -> int:
+    """Proof this formula is equivalent to the one in `n_choose_k`:
+
+    Note that if we select a subset of size `k`
+    from a set of size `n`, then we leave a subset
+    of size `n − k` behind (the complement).
+    Thus A -> Ac is a one-to-one correspondence
+    between subsets of size `k` and subsets of size `n − k`."""
+    return n_choose_k(n, n - k)
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def n_choose_k(

n, k)

+
+ + + + +

Returns the number of ways of choosing k elements, +disregarding their order, from a set of n elements.

+

Assumes n >= 0 and k >= 0. +This is because not everyone defines "n choose k" for negative ns or ks. +See here: http://math.stackexchange.com/questions/527831/is-n-choose-k-defined-when-k-0-what-about-n-k

+
+ +
+
def n_choose_k(n: int, k: int) -> int:
+    """Returns the number of ways of choosing `k` elements,
+    disregarding their order, from a set of `n` elements.
+
+    Assumes n >= 0 and k >= 0.
+    This is because not everyone defines "n choose k" for negative `n`s  or `k`s.
+    See here: http://math.stackexchange.com/questions/527831/is-n-choose-k-defined-when-k-0-what-about-n-k"""
+    assert n >= 0 and k >= 0
+
+    if n == k or k == 0:
+        return 1
+    if k > n:
+        return 0
+
+    return factorial(n) / (factorial(k) * factorial(n - k))
+
+
+
+ +
+ + +
+
+

def n_choose_k_2(

n, k)

+
+ + + + +

'n choose k' operation can be recursively defined as: +'n - 1 choose k - 1' + 'n - 1 choose k', +for all integers n and k, such that 1 <= k <= n - 1.

+

Proof:

+

Suppose that we have n persons, one named Fred, +and that we want to select a committee of size k. +There are 'n choose k' different committees. +On the other hand, there are 'n - 1 choose k - 1' committees +with Fred as a member, and 'n - 1 choose k' committees +without Fred as a member. +The sum of these two numbers is also the number of committees.

+
+ +
+
def n_choose_k_2(n: int, k: int) -> int:
+    """'n choose k' operation can be recursively defined as:
+    'n - 1 choose k - 1' + 'n - 1 choose k',
+    for all integers `n` and `k`, such that `1 <= k <= n - 1`.
+
+    **Proof:**
+
+    Suppose that we have n persons, one named Fred,
+    and that we want to select a committee of size k.
+    There are 'n choose k' different committees.
+    On the other hand, there are 'n - 1 choose k - 1' committees
+    with Fred as a member, and 'n - 1 choose k' committees
+    without Fred as a member.
+    The sum of these two numbers is also the number of committees.
+    """
+    return n_choose_k(n - 1, k - 1) + n_choose_k(n - 1, k)
+
+
+
+ +
+ + +
+
+

def n_choose_k_3(

n, k)

+
+ + + + +

Proof this formula is equivalent to the one in n_choose_k:

+

Note that if we select a subset of size k +from a set of size n, then we leave a subset +of size n − k behind (the complement). +Thus A -> Ac is a one-to-one correspondence +between subsets of size k and subsets of size n − k.

+
+ +
+
def n_choose_k_3(n: int, k: int) -> int:
+    """Proof this formula is equivalent to the one in `n_choose_k`:
+
+    Note that if we select a subset of size `k`
+    from a set of size `n`, then we leave a subset
+    of size `n − k` behind (the complement).
+    Thus A -> Ac is a one-to-one correspondence
+    between subsets of size `k` and subsets of size `n − k`."""
+    return n_choose_k(n, n - k)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/math/index.html b/docs/ands/algorithms/math/index.html new file mode 100644 index 00000000..ebdd2f1d --- /dev/null +++ b/docs/ands/algorithms/math/index.html @@ -0,0 +1,1040 @@ + + + + + + ands.algorithms.math API documentation + + + + + + + + + + + + + + +Top + +
+ + + + + +
+ +
+ + diff --git a/docs/ands/algorithms/parsing/index.html b/docs/ands/algorithms/parsing/index.html new file mode 100644 index 00000000..843c0ef1 --- /dev/null +++ b/docs/ands/algorithms/parsing/index.html @@ -0,0 +1,1036 @@ + + + + + + ands.algorithms.parsing API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.parsing module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms.parsing.smep

+ + +

Author: Nelson Brochado

+

An mathematical infix-to-postfix expression parser/converter. +Includes also a calculator that receives a postfix expression.

+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/parsing/smep.m.html b/docs/ands/algorithms/parsing/smep.m.html new file mode 100644 index 00000000..3c8c5d65 --- /dev/null +++ b/docs/ands/algorithms/parsing/smep.m.html @@ -0,0 +1,1469 @@ + + + + + + ands.algorithms.parsing.smep API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.parsing.smep module

+

Author: Nelson Brochado

+

An mathematical infix-to-postfix expression parser/converter. +Includes also a calculator that receives a postfix expression.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+An mathematical infix-to-postfix expression parser/converter.
+Includes also a calculator that receives a postfix expression.
+"""
+
+import operator
+import re
+
+# higher number => higher precedence
+OPERATORS = {
+    "+": 1,
+    "-": 1,
+    "*": 2,
+    "/": 2,
+    "%": 2,
+    "^": 3
+}
+
+# Associates symbols with functions
+OPS = {
+    "+": operator.add,
+    "-": operator.sub,
+    "*": operator.mul,
+    "/": operator.truediv,
+    "%": operator.mod,
+    "^": operator.pow
+}
+
+PARENTHESIS = {"(", ")"}
+
+REGEX = re.compile(r"(\d+|\w+|[-+*/^%()])")
+
+
+def parse(e, regex=REGEX):
+    """Parses a string expression e to a infix represation list."""
+    return regex.findall(e)
+
+
+def infix_to_postfix(infix):
+    """Return a list representing the postfix representation of the list `infix`.
+
+    Algorithm based on:
+
+    - https://www.youtube.com/watch?v=vXPL6UavUeA
+
+    - http://interactivepython.org/runestone/static/pythonds/BasicDS/InfixPrefixandPostfixExpressions.html"""
+    stack = []
+    postfix = []
+
+    # Used for counting the number of opening and closing parenthesis,
+    # which should be the same
+    opening_paren = 0
+    closing_paren = 0
+
+    for i, c in enumerate(infix):
+        if c in OPERATORS:
+            if i > 0 and infix[i - 1] in OPERATORS:
+                raise SyntaxError("no two operators can be in a row")
+
+            if len(stack) > 0:
+                top = stack[-1]
+
+                if top in OPERATORS:
+                    if OPERATORS[c] > OPERATORS[top]:
+                        stack.append(c)
+                    else:
+                        while top in OPERATORS and OPERATORS[
+                                top] >= OPERATORS[c]:
+                            op = stack.pop()
+                            postfix.append(op)
+                            if len(stack) > 0:
+                                top = stack[-1]
+                            else:
+                                break
+                        stack.append(c)
+                else:
+                    stack.append(c)
+            else:
+                stack.append(c)
+
+        elif c in PARENTHESIS:
+            if c == ")":
+                if len(stack) > 0:
+                    top = stack[-1]
+                    while top != "(":
+                        try:
+                            # pop throws an IndexError if the list is empty
+                            r = stack.pop()
+                            # Adding what's in between ( ) to the postfix list
+                            postfix.append(r)
+                            top = stack[-1]
+                        except IndexError:
+                            raise SyntaxError("'(' not found when popping")
+
+                    stack.pop()  # Removes ( from the top of the stack
+                else:
+                    raise SyntaxError(
+                        "')' cannot be added to the stack if it is empty")
+                closing_paren += 1
+            else:
+                stack.append(c)  # c == '('
+                opening_paren += 1
+        else:  # All the rest is considered an operand
+            if i > 0 and infix[
+                    i - 1] not in OPERATORS and infix[i - 1] not in PARENTHESIS:
+                raise SyntaxError("no two operands can be in a row")
+
+            postfix.append(c)
+
+    if opening_paren != closing_paren:
+        raise SyntaxError(
+            "number of opening and closing parenthesis do not match")
+
+    while len(stack) > 0:
+        top = stack.pop()
+        if top in OPERATORS:
+            postfix.append(top)
+
+    return postfix
+
+
+def calc(postfix):
+    """Simple mathematical postfix expression calculator."""
+    stack = []
+    for c in postfix:
+        if c not in OPERATORS:
+            stack.append(c)
+        else:
+            top = int(stack.pop())
+            top2 = int(stack.pop())
+            stack.append(OPS[c](top2, top))
+    return stack
+
+
+def tostr(ls):
+    return " ".join(ls)
+
+
+# TESTS
+
+def test1():
+    # ifx = "a+b/c*(d+e)-f"
+    # ifx = "(12)^3 * x^3 +  (4  * 3)^3 + ()"
+    # ifx = "a+b*c"
+    # ifx = "3 + 2 * 2 ^ 3 % 3"
+    # ifx = "(((3 + 2) * 4))"
+
+    ifx = "((12*2^3+44*3)*3)"
+    print("Infix:", ifx)
+
+    ls = parse(ifx)
+    print("Infix list:", ls)
+
+    # ls = ['(', '(', '(', '3', '+', '2', '(', ')', ')', '*', '4', ')', ')']
+    # print("Infix:", tostr(ls))
+
+    pfx = infix_to_postfix(ls)
+    print("Postfix list:", pfx)
+    print("Calculated:", calc(pfx))
+
+    pfx_str = tostr(pfx)
+    print("Postfix:", pfx_str)
+
+
+if __name__ == "__main__":
+    test1()
+
+
+ +
+ +
+

Module variables

+
+

var OPERATORS

+ + +
+
+ +
+
+

var OPS

+ + +
+
+ +
+
+

var PARENTHESIS

+ + +
+
+ +
+
+

var REGEX

+ + +
+
+ +
+ +

Functions

+ +
+
+

def calc(

postfix)

+
+ + + + +

Simple mathematical postfix expression calculator.

+
+ +
+
def calc(postfix):
+    """Simple mathematical postfix expression calculator."""
+    stack = []
+    for c in postfix:
+        if c not in OPERATORS:
+            stack.append(c)
+        else:
+            top = int(stack.pop())
+            top2 = int(stack.pop())
+            stack.append(OPS[c](top2, top))
+    return stack
+
+
+
+ +
+ + +
+
+

def infix_to_postfix(

infix)

+
+ + + + +

Return a list representing the postfix representation of the list infix.

+

Algorithm based on:

+
    +
  • +

    https://www.youtube.com/watch?v=vXPL6UavUeA

    +
  • +
  • +

    http://interactivepython.org/runestone/static/pythonds/BasicDS/InfixPrefixandPostfixExpressions.html

    +
  • +
+
+ +
+
def infix_to_postfix(infix):
+    """Return a list representing the postfix representation of the list `infix`.
+
+    Algorithm based on:
+
+    - https://www.youtube.com/watch?v=vXPL6UavUeA
+
+    - http://interactivepython.org/runestone/static/pythonds/BasicDS/InfixPrefixandPostfixExpressions.html"""
+    stack = []
+    postfix = []
+
+    # Used for counting the number of opening and closing parenthesis,
+    # which should be the same
+    opening_paren = 0
+    closing_paren = 0
+
+    for i, c in enumerate(infix):
+        if c in OPERATORS:
+            if i > 0 and infix[i - 1] in OPERATORS:
+                raise SyntaxError("no two operators can be in a row")
+
+            if len(stack) > 0:
+                top = stack[-1]
+
+                if top in OPERATORS:
+                    if OPERATORS[c] > OPERATORS[top]:
+                        stack.append(c)
+                    else:
+                        while top in OPERATORS and OPERATORS[
+                                top] >= OPERATORS[c]:
+                            op = stack.pop()
+                            postfix.append(op)
+                            if len(stack) > 0:
+                                top = stack[-1]
+                            else:
+                                break
+                        stack.append(c)
+                else:
+                    stack.append(c)
+            else:
+                stack.append(c)
+
+        elif c in PARENTHESIS:
+            if c == ")":
+                if len(stack) > 0:
+                    top = stack[-1]
+                    while top != "(":
+                        try:
+                            # pop throws an IndexError if the list is empty
+                            r = stack.pop()
+                            # Adding what's in between ( ) to the postfix list
+                            postfix.append(r)
+                            top = stack[-1]
+                        except IndexError:
+                            raise SyntaxError("'(' not found when popping")
+
+                    stack.pop()  # Removes ( from the top of the stack
+                else:
+                    raise SyntaxError(
+                        "')' cannot be added to the stack if it is empty")
+                closing_paren += 1
+            else:
+                stack.append(c)  # c == '('
+                opening_paren += 1
+        else:  # All the rest is considered an operand
+            if i > 0 and infix[
+                    i - 1] not in OPERATORS and infix[i - 1] not in PARENTHESIS:
+                raise SyntaxError("no two operands can be in a row")
+
+            postfix.append(c)
+
+    if opening_paren != closing_paren:
+        raise SyntaxError(
+            "number of opening and closing parenthesis do not match")
+
+    while len(stack) > 0:
+        top = stack.pop()
+        if top in OPERATORS:
+            postfix.append(top)
+
+    return postfix
+
+
+
+ +
+ + +
+
+

def parse(

e, regex=re.compile('(\\d+|\\w+|[-+*/^%()])'))

+
+ + + + +

Parses a string expression e to a infix represation list.

+
+ +
+
def parse(e, regex=REGEX):
+    """Parses a string expression e to a infix represation list."""
+    return regex.findall(e)
+
+
+
+ +
+ + +
+
+

def test1(

)

+
+ + + + +
+ +
+
def test1():
+    # ifx = "a+b/c*(d+e)-f"
+    # ifx = "(12)^3 * x^3 +  (4  * 3)^3 + ()"
+    # ifx = "a+b*c"
+    # ifx = "3 + 2 * 2 ^ 3 % 3"
+    # ifx = "(((3 + 2) * 4))"
+
+    ifx = "((12*2^3+44*3)*3)"
+    print("Infix:", ifx)
+
+    ls = parse(ifx)
+    print("Infix list:", ls)
+
+    # ls = ['(', '(', '(', '3', '+', '2', '(', ')', ')', '*', '4', ')', ')']
+    # print("Infix:", tostr(ls))
+
+    pfx = infix_to_postfix(ls)
+    print("Postfix list:", pfx)
+    print("Calculated:", calc(pfx))
+
+    pfx_str = tostr(pfx)
+    print("Postfix:", pfx_str)
+
+
+
+ +
+ + +
+
+

def tostr(

ls)

+
+ + + + +
+ +
+
def tostr(ls):
+    return " ".join(ls)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/primes/index.html b/docs/ands/algorithms/primes/index.html new file mode 100644 index 00000000..31a0121a --- /dev/null +++ b/docs/ands/algorithms/primes/index.html @@ -0,0 +1,1035 @@ + + + + + + ands.algorithms.primes API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.primes module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms.primes.is_prime

+ + +

Author: Nelson Brochado

+

Primality Tests.

+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/primes/is_prime.m.html b/docs/ands/algorithms/primes/is_prime.m.html new file mode 100644 index 00000000..72847e50 --- /dev/null +++ b/docs/ands/algorithms/primes/is_prime.m.html @@ -0,0 +1,1269 @@ + + + + + + ands.algorithms.primes.is_prime API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.primes.is_prime module

+

Author: Nelson Brochado

+

Primality Tests.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Primality Tests.
+"""
+
+
+def is_prime(n):
+    """Return `True` if `n` is prime, `False` otherwise."""
+    if n < 2:  # primes are greater than 1
+        return False
+    if n % 2 == 0:
+        return n == 2  # returns True if n == 2 because 2 is a prime
+    i = 3
+    while i * i <= n:
+        if n % i == 0:
+            return False
+        i += 2
+    return True
+
+
+def _is_prime_r(n, i):
+    if i * i <= n:
+        if n % i == 0:
+            return False
+        else:
+            return _is_prime_r(n, i + 2)
+    return True
+
+
+def is_prime_r(n):
+    """Return `True` if `n` is prime, `False` otherwise.
+
+    This function uses recursion.
+    In general, you should prefer an iterative approach,
+    because the stack has a limit,
+    and if exceeded an exception is thrown."""
+    if n <= 1:  # primes are greater than 1
+        return False
+    if n % 2 == 0:
+        return n == 2
+    return _is_prime_r(n, 3)
+
+
+def is_prime_2(n):
+    """Return `True` if `n` is prime, `False` otherwise.
+
+    This algorithm seems to perform better than `is_prime`.
+
+    **Time Complexity:** O(√n/2 * O(n % i == 0))."""
+    if n == 2 or n == 3:
+        return True
+    if n % 2 == 0 or n < 2:
+        return False
+    for i in range(3, int(n ** 0.5) + 1, 2):
+        if n % i == 0:
+            return False
+    return True
+
+
+# TESTS
+
+def test1():
+    from time import time
+    a = time()
+    for i in range(10000000):
+        is_prime_2(i)
+    # assert is_prime(i) == is_prime_2(i)
+    b = time()
+    print(b - a)
+
+
+def test2():
+    from time import time
+    a = time()
+    is_prime_2(10093100991010310111)
+    b = time()
+    print(b - a)
+
+
+if __name__ == "__main__":
+    test2()
+    # test1()
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def is_prime(

n)

+
+ + + + +

Return True if n is prime, False otherwise.

+
+ +
+
def is_prime(n):
+    """Return `True` if `n` is prime, `False` otherwise."""
+    if n < 2:  # primes are greater than 1
+        return False
+    if n % 2 == 0:
+        return n == 2  # returns True if n == 2 because 2 is a prime
+    i = 3
+    while i * i <= n:
+        if n % i == 0:
+            return False
+        i += 2
+    return True
+
+
+
+ +
+ + +
+
+

def is_prime_2(

n)

+
+ + + + +

Return True if n is prime, False otherwise.

+

This algorithm seems to perform better than is_prime.

+

Time Complexity: O(√n/2 * O(n % i == 0)).

+
+ +
+
def is_prime_2(n):
+    """Return `True` if `n` is prime, `False` otherwise.
+
+    This algorithm seems to perform better than `is_prime`.
+
+    **Time Complexity:** O(√n/2 * O(n % i == 0))."""
+    if n == 2 or n == 3:
+        return True
+    if n % 2 == 0 or n < 2:
+        return False
+    for i in range(3, int(n ** 0.5) + 1, 2):
+        if n % i == 0:
+            return False
+    return True
+
+
+
+ +
+ + +
+
+

def is_prime_r(

n)

+
+ + + + +

Return True if n is prime, False otherwise.

+

This function uses recursion. +In general, you should prefer an iterative approach, +because the stack has a limit, +and if exceeded an exception is thrown.

+
+ +
+
def is_prime_r(n):
+    """Return `True` if `n` is prime, `False` otherwise.
+
+    This function uses recursion.
+    In general, you should prefer an iterative approach,
+    because the stack has a limit,
+    and if exceeded an exception is thrown."""
+    if n <= 1:  # primes are greater than 1
+        return False
+    if n % 2 == 0:
+        return n == 2
+    return _is_prime_r(n, 3)
+
+
+
+ +
+ + +
+
+

def test1(

)

+
+ + + + +
+ +
+
def test1():
+    from time import time
+    a = time()
+    for i in range(10000000):
+        is_prime_2(i)
+    # assert is_prime(i) == is_prime_2(i)
+    b = time()
+    print(b - a)
+
+
+
+ +
+ + +
+
+

def test2(

)

+
+ + + + +
+ +
+
def test2():
+    from time import time
+    a = time()
+    is_prime_2(10093100991010310111)
+    b = time()
+    print(b - a)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/recursion/ackermann.m.html b/docs/ands/algorithms/recursion/ackermann.m.html new file mode 100644 index 00000000..6492494d --- /dev/null +++ b/docs/ands/algorithms/recursion/ackermann.m.html @@ -0,0 +1,1119 @@ + + + + + + ands.algorithms.recursion.ackermann API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.recursion.ackermann module

+

Meta info

+

Author: Nelson Brochado

+

Creation: 22/02/16

+

Updated: 18/01/2017

+

Description

+

The Ackermann function is the simplest example of a well defined total function. +Total function means that it's defined for all possible inputs. +The function is computable, but not primitive recursive. +A primitive recursive function is a function that can be implemented using only "for" loops, +i.e. loops that have a fixed number of iterations. +A computable function is a function that can be implemented using "while" loops. +Note that "do" loops are a particular case of while loops.

+

It grows faster than an exponential function, or even a multiple exponential function.

+

References

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+
+Creation: 22/02/16
+
+Updated: 18/01/2017
+
+## Description
+
+The Ackermann function is the simplest example of a well defined total function.
+Total function means that it's defined for all possible inputs.
+The function is computable, but **not** primitive recursive.
+A primitive recursive function is a function that can be implemented using only "for" loops,
+i.e. loops that have a fixed number of iterations.
+A computable function is a function that can be implemented using "while" loops.
+Note that "do" loops are a particular case of while loops.
+
+It grows faster than an exponential function, or even a multiple exponential function.
+
+## References
+
+- [http://mathworld.wolfram.com/AckermannFunction.html](http://mathworld.wolfram.com/AckermannFunction.html)
+- [http://math.stackexchange.com/questions/75296/what-is-the-difference-between-total-recursive-and-primitive-recursive-functions](http://math.stackexchange.com/questions/75296/what-is-the-difference-between-total-recursive-and-primitive-recursive-functions)
+- [https://en.wikipedia.org/wiki/Ackermann_function](https://en.wikipedia.org/wiki/Ackermann_function)
+"""
+
+
+def ackermann(m: int, n: int) -> int:
+    assert m >= 0 and n >= 0
+    if m == 0:
+        return n + 1
+    elif n == 0:
+        return ackermann(m - 1, 1)
+    else:
+        return ackermann(m - 1, ackermann(m, n - 1))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def ackermann(

m, n)

+
+ + + + +
+ +
+
def ackermann(m: int, n: int) -> int:
+    assert m >= 0 and n >= 0
+    if m == 0:
+        return n + 1
+    elif n == 0:
+        return ackermann(m - 1, 1)
+    else:
+        return ackermann(m - 1, ackermann(m, n - 1))
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/recursion/count.m.html b/docs/ands/algorithms/recursion/count.m.html new file mode 100644 index 00000000..607a88a7 --- /dev/null +++ b/docs/ands/algorithms/recursion/count.m.html @@ -0,0 +1,1103 @@ + + + + + + ands.algorithms.recursion.count API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.recursion.count module

+

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 21/01/2017

+

Description

+

A very simple example of how to count the number occurrences +of a certain object o in a list ls.

+

You should not use recursion in general for doing this task: +for example, in Python the stack limit is quite small: 1000 +This is just an example of recursive algorithm!

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+
+Created: 2015
+
+Updated: 21/01/2017
+
+## Description
+
+A very simple example of how to count the number occurrences
+of a certain object `o` in a list `ls`.
+
+You should not use recursion in general for doing this task:
+for example, in Python the stack limit is quite small: 1000
+This is just an example of recursive algorithm!
+"""
+
+__all__ = ["count"]
+
+
+def _count(elem: object, ls, index: int) -> int:
+    if index < len(ls):
+        if ls[index] == elem:
+            return 1 + _count(elem, ls, index + 1)
+        else:
+            return _count(elem, ls, index + 1)
+    return 0
+
+
+def count(elem: object, ls) -> int:
+    """Counts how many times `elem` appears in the list or tuple `ls`."""
+    return _count(elem, ls, 0)
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def count(

elem, ls)

+
+ + + + +

Counts how many times elem appears in the list or tuple ls.

+
+ +
+
def count(elem: object, ls) -> int:
+    """Counts how many times `elem` appears in the list or tuple `ls`."""
+    return _count(elem, ls, 0)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/recursion/factorial.m.html b/docs/ands/algorithms/recursion/factorial.m.html new file mode 100644 index 00000000..c1aefd64 --- /dev/null +++ b/docs/ands/algorithms/recursion/factorial.m.html @@ -0,0 +1,1310 @@ + + + + + + ands.algorithms.recursion.factorial API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.recursion.factorial module

+

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 20/02/2017

+

Description

+

The factorial of a number n is defined recursively as follows:

+
fact(n):
+    # Assume n is int and n >= 0
+    if n == 0 or n == 1:
+        return 1
+    else:
+        return n * fact(n - 1)  # n * (n - 1)!
+
+

Resources

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+
+Created: 2015
+
+Updated: 20/02/2017
+
+## Description
+
+The factorial of a number n is defined recursively as follows:
+
+    fact(n):
+        # Assume n is int and n >= 0
+        if n == 0 or n == 1:
+            return 1
+        else:
+            return n * fact(n - 1)  # n * (n - 1)!
+
+### Resources
+
+- [http://www.math.uah.edu/stat/foundations/Structures.html#com2](http://www.math.uah.edu/stat/foundations/Structures.html#com2)
+
+"""
+
+__all__ = ["factorial", "iterative_factorial", "smallest_geq", "multiple_factorial"]
+
+
+def factorial(n: int) -> int:
+    """Returns the factorial of `n`, which is calculated recursively,
+    as it's usually defined mathematically.
+
+    Assumes that `n >= 0`."""
+    assert n >= 0
+
+    if n == 0:
+        return 1
+    elif n == 1 or n == 2:
+        return n
+    else:
+        return n * factorial(n - 1)
+
+
+def iterative_factorial(n: int) -> int:
+    """Returns the factorial of `n`, which is calculated iteratively.
+    This is just for comparison with the recursive implementation.
+
+    Since the "factorial" is a primitive recursive function,
+    it can be implemented iteratively.
+
+    Proof that factorial is a primitive recursive function:
+    https://proofwiki.org/wiki/Factorial_is_Primitive_Recursive.
+
+    A primitive recursive function is a recursive function which can be implemented with "for" loops.
+    See here: http://mathworld.wolfram.com/PrimitiveRecursiveFunction.html"""
+    assert n >= 0
+
+    if n == 0 or n == 1:
+        return 1
+
+    f = 1
+    for i in range(2, n + 1):
+        f *= i
+
+    return f
+
+
+def smallest_geq(x: int) -> int:
+    """Returns the smallest number `n` such that `n! >= x`.
+
+    Assumes a non-negative integer `x` as input.
+
+    "geq" stands for greater or equal."""
+    assert x >= 0
+
+    n = 0
+    while iterative_factorial(n) < x:
+        n += 1
+
+    return n
+
+
+def _multiple_factorial(n: int, i: int, a: list) -> list:
+    if i <= n:
+        a.append(factorial(i))
+        _multiple_factorial(n, i + 1, a)
+    return a
+
+
+def multiple_factorial(n: int) -> list:
+    """Returns a list L of factorials from 0 to n, that is:
+        L[0] := 0!
+        L[1] := 1!
+        ...
+        L[n] := n!
+    If n is a negative number, returns an empty list.
+
+    Assumes n >= 0."""
+    assert n >= 0
+    return _multiple_factorial(n, 0, [])
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def factorial(

n)

+
+ + + + +

Returns the factorial of n, which is calculated recursively, +as it's usually defined mathematically.

+

Assumes that n >= 0.

+
+ +
+
def factorial(n: int) -> int:
+    """Returns the factorial of `n`, which is calculated recursively,
+    as it's usually defined mathematically.
+
+    Assumes that `n >= 0`."""
+    assert n >= 0
+
+    if n == 0:
+        return 1
+    elif n == 1 or n == 2:
+        return n
+    else:
+        return n * factorial(n - 1)
+
+
+
+ +
+ + +
+
+

def iterative_factorial(

n)

+
+ + + + +

Returns the factorial of n, which is calculated iteratively. +This is just for comparison with the recursive implementation.

+

Since the "factorial" is a primitive recursive function, +it can be implemented iteratively.

+

Proof that factorial is a primitive recursive function: +https://proofwiki.org/wiki/Factorial_is_Primitive_Recursive.

+

A primitive recursive function is a recursive function which can be implemented with "for" loops. +See here: http://mathworld.wolfram.com/PrimitiveRecursiveFunction.html

+
+ +
+
def iterative_factorial(n: int) -> int:
+    """Returns the factorial of `n`, which is calculated iteratively.
+    This is just for comparison with the recursive implementation.
+
+    Since the "factorial" is a primitive recursive function,
+    it can be implemented iteratively.
+
+    Proof that factorial is a primitive recursive function:
+    https://proofwiki.org/wiki/Factorial_is_Primitive_Recursive.
+
+    A primitive recursive function is a recursive function which can be implemented with "for" loops.
+    See here: http://mathworld.wolfram.com/PrimitiveRecursiveFunction.html"""
+    assert n >= 0
+
+    if n == 0 or n == 1:
+        return 1
+
+    f = 1
+    for i in range(2, n + 1):
+        f *= i
+
+    return f
+
+
+
+ +
+ + +
+
+

def multiple_factorial(

n)

+
+ + + + +

Returns a list L of factorials from 0 to n, that is: + L[0] := 0! + L[1] := 1! + ... + L[n] := n! +If n is a negative number, returns an empty list.

+

Assumes n >= 0.

+
+ +
+
def multiple_factorial(n: int) -> list:
+    """Returns a list L of factorials from 0 to n, that is:
+        L[0] := 0!
+        L[1] := 1!
+        ...
+        L[n] := n!
+    If n is a negative number, returns an empty list.
+
+    Assumes n >= 0."""
+    assert n >= 0
+    return _multiple_factorial(n, 0, [])
+
+
+
+ +
+ + +
+
+

def smallest_geq(

x)

+
+ + + + +

Returns the smallest number n such that n! >= x.

+

Assumes a non-negative integer x as input.

+

"geq" stands for greater or equal.

+
+ +
+
def smallest_geq(x: int) -> int:
+    """Returns the smallest number `n` such that `n! >= x`.
+
+    Assumes a non-negative integer `x` as input.
+
+    "geq" stands for greater or equal."""
+    assert x >= 0
+
+    n = 0
+    while iterative_factorial(n) < x:
+        n += 1
+
+    return n
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/recursion/hanoi.m.html b/docs/ands/algorithms/recursion/hanoi.m.html new file mode 100644 index 00000000..d6fd99a7 --- /dev/null +++ b/docs/ands/algorithms/recursion/hanoi.m.html @@ -0,0 +1,1197 @@ + + + + + + ands.algorithms.recursion.hanoi API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.recursion.hanoi module

+

Meta info

+

Author: Nelson Brochado

+

Creation: 27/02/16

+

Updated: 18/01/2017

+

Description

+

Towers of Hanoi is a mathematical game. +It consists of 3 rods, and a number of disks of different sizes, which can slide onto any rod. +The game starts with the disks in a neat stack in ascending order of size on one rod, +the smallest at the top, thus making a conical shape.

+

The objective of the game is to move the entire stack to another rod, +obeying the following rules:

+
    +
  1. +

    Only 1 disk can be moved at a time.

    +
  2. +
  3. +

    Each move consists of taking the upper disk +from one of the stacks and placing it on top of another stack, +i.e. a disk can only be moved if it is the uppermost disk on its stack.

    +
  4. +
  5. +

    No disk may be placed on top of a smaller disk.

    +
  6. +
+

With 3 disks, the game can be solved with at least 7 moves (best case). +The minimum number of moves required to solve a tower of hanoi game +is 2^n - 1, where n is the number of disks.

+

For simplicity, in the following algorithm +the source (='A'), auxiliary (='B') and destination (='C') rodes are fixed, +and therefore the algorithm always shows the steps to go from 'A' to 'C'.

+

References

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+
+Creation: 27/02/16
+
+Updated: 18/01/2017
+
+## Description
+
+Towers of Hanoi is a mathematical game.
+It consists of 3 rods, and a number of disks of different sizes, which can slide onto any rod.
+The game starts with the disks in a neat stack in ascending order of size on one rod,
+the smallest at the top, thus making a conical shape.
+
+The objective of the game is to move the entire stack to another rod,
+obeying the following rules:
+
+1. Only 1 disk can be moved at a time.
+
+2. Each move consists of taking the upper disk
+from one of the stacks and placing it on top of another stack,
+i.e. a disk can only be moved if it is the uppermost disk on its stack.
+
+3. No disk may be placed on top of a smaller disk.
+
+With 3 disks, the game can be solved with at least 7 moves (best case).
+The minimum number of moves required to solve a tower of hanoi game
+is 2^n - 1, where n is the number of disks.
+
+For simplicity, in the following algorithm
+the source (='A'), auxiliary (='B') and destination (='C') rodes are fixed,
+and therefore the algorithm always shows the steps to go from 'A' to 'C'.
+
+## References
+
+- [https://en.wikipedia.org/wiki/Tower_of_Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)
+- [http://www.cut-the-knot.org/recurrence/hanoi.shtml](http://www.cut-the-knot.org/recurrence/hanoi.shtml)
+- [http://stackoverflow.com/questions/105838/real-world-examples-of-recursion](http://stackoverflow.com/questions/105838/real-world-examples-of-recursion)
+"""
+
+__all__ = ["hanoi"]
+
+
+def _hanoi(n: int, ls: list, src='A', aux='B', dst='C') -> list:
+    """Recursively solve the Towers of Hanoi game for `n` disks.
+
+    The smallest disk, which is the topmost one at the beginning,
+    is called 1, and the largest one is called `n`.
+
+    `src` is the start rod where all disks are set in a neat stack in ascending order.
+    `aux` is the third rod.
+    `dst` is similarly the destination rod."""
+    if n > 0:
+        _hanoi(n - 1, ls, src, dst, aux)
+        ls.append((n, src, dst))
+        _hanoi(n - 1, ls, aux, src, dst)
+    return ls
+
+
+def hanoi(n: int) -> list:
+    """Returns a list L of tuples each of them representing a move to be done.
+
+    `n` is the number of disks.
+    The number of rods is clearly always 3.
+
+    L[i] must be done before L[i + 1], for all i.
+    L[i][0] := the disk number (or id).
+    Numbers start from 1 and go up to n.
+    L[i][1] := the source rod from which to move L[i][0].
+    L[i][2] := the destination rod to which to move L[i][0].
+
+    The disk with the smallest radius (at the top) is the disk number 1,
+    its successor in terms or radius' size is disk number 2, and so on.
+    So the largest disk is disk number n."""
+    assert n >= 0
+    return _hanoi(n, [])
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def hanoi(

n)

+
+ + + + +

Returns a list L of tuples each of them representing a move to be done.

+

n is the number of disks. +The number of rods is clearly always 3.

+

L[i] must be done before L[i + 1], for all i. +L[i][0] := the disk number (or id). +Numbers start from 1 and go up to n. +L[i][1] := the source rod from which to move L[i][0]. +L[i][2] := the destination rod to which to move L[i][0].

+

The disk with the smallest radius (at the top) is the disk number 1, +its successor in terms or radius' size is disk number 2, and so on. +So the largest disk is disk number n.

+
+ +
+
def hanoi(n: int) -> list:
+    """Returns a list L of tuples each of them representing a move to be done.
+
+    `n` is the number of disks.
+    The number of rods is clearly always 3.
+
+    L[i] must be done before L[i + 1], for all i.
+    L[i][0] := the disk number (or id).
+    Numbers start from 1 and go up to n.
+    L[i][1] := the source rod from which to move L[i][0].
+    L[i][2] := the destination rod to which to move L[i][0].
+
+    The disk with the smallest radius (at the top) is the disk number 1,
+    its successor in terms or radius' size is disk number 2, and so on.
+    So the largest disk is disk number n."""
+    assert n >= 0
+    return _hanoi(n, [])
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/recursion/index.html b/docs/ands/algorithms/recursion/index.html new file mode 100644 index 00000000..df9c0d78 --- /dev/null +++ b/docs/ands/algorithms/recursion/index.html @@ -0,0 +1,1167 @@ + + + + + + ands.algorithms.recursion API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.recursion module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms.recursion.ackermann

+ + +

Meta info

+

Author: Nelson Brochado

+

Creation: 22/02/16

+

Updated: 18/01/2017

+

Description

+

The Ackermann function is the simplest example of a well defined total function. +Total function means that it's defined for all possible inputs. +The function is computable, but not primitive recursive...

+ +
+
+

ands.algorithms.recursion.count

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 21/01/2017

+

Description

+

A very simple example of how to count the number occurrences +of a certain object o in a list ls.

+

You should not use recursion in general for doing this task: +for example, in Python the stack limit is quit...

+ +
+
+

ands.algorithms.recursion.factorial

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 20/02/2017

+

Description

+

The factorial of a number n is defined recursively as follows:

+
fact(n):
+    # Assume n is int and n >= 0
+    if n == 0 or n == 1:
+        return 1
+    else:
+        return n * fact(n -...
+
+ +
+
+

ands.algorithms.recursion.hanoi

+ + +

Meta info

+

Author: Nelson Brochado

+

Creation: 27/02/16

+

Updated: 18/01/2017

+

Description

+

Towers of Hanoi is a mathematical game. +It consists of 3 rods, and a number of disks of different sizes, which can slide onto any rod. +The game starts with the disks in a neat stack in ascending order of ...

+ +
+
+

ands.algorithms.recursion.is_sorted

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 21/01/2017

+

Description

+

is_sorted check if a list or tuple contains elements in sorted order by using recursion. +This algorithm can potentially be modified to work with other collections. +The other versions are here just for comparison.

+ +
+
+

ands.algorithms.recursion.make_decimal

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 20/01/2017

+

Description

+

Converts a number in a certain base in the range [2, 36] to a decimal number (number in base 10). +Bases greater than 10 take in order as digits the letters of the English alphabet. +For example, a number system...

+ +
+
+

ands.algorithms.recursion.palindrome

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 20/01/2017

+

Description

+

Checking recursively if a string is a palindrome. +A palindrome is a string that reads the same way forward and backward. +For example, "anna" is a palindrome, whereas "prime" is not.

+

Resources

+
    +
  • [https://e...
  • +
+ +
+
+

ands.algorithms.recursion.power

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 18/01/2017

+

Description

+

Raising an integer a to the k >= 0 using recursion, i.e., ak = b.

+ +
+
+

ands.algorithms.recursion.reverse

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 16/01/2017

+

Description

+

Reverses in-place the elements of a list using recursion. +This method could also be adapted to work with other mutable collections.

+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/recursion/is_sorted.m.html b/docs/ands/algorithms/recursion/is_sorted.m.html new file mode 100644 index 00000000..75447027 --- /dev/null +++ b/docs/ands/algorithms/recursion/is_sorted.m.html @@ -0,0 +1,1211 @@ + + + + + + ands.algorithms.recursion.is_sorted API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.recursion.is_sorted module

+

Meta info

+

Author: Nelson Brochado

+

Created: 21/01/2017

+

Description

+

is_sorted check if a list or tuple contains elements in sorted order by using recursion. +This algorithm can potentially be modified to work with other collections. +The other versions are here just for comparison.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+
+Created: 21/01/2017
+
+## Description
+
+`is_sorted` check if a list or tuple contains elements in sorted order by using recursion.
+This algorithm can potentially be modified to work with other collections.
+The other versions are here just for comparison.
+
+"""
+
+import operator
+
+__all__ = ["is_sorted", "iterative_is_sorted", "pythonic_is_sorted"]
+
+
+def _is_sorted(a, i: int, op) -> bool:
+    """`i` is used to index the two adjacent elements of `a`.
+
+    op can either be >, if `a` should be in ascending order,
+    or <, if `a` should be in descending order."""
+    if i == len(a) - 1:  # If i is the last index, there's nothing more to check, thus the list is sorted.
+        return True
+    if op(a[i], a[i + 1]):
+        return False
+    else:
+        return _is_sorted(a, i + 1, op)
+
+
+def is_sorted(a, rev=False) -> bool:
+    """Checks recursively if `a` is sorted.
+
+    If `rev` is `True`, this function checks if `a` is sorted in descending order,
+    else if it's sorted in ascending order."""
+    if len(a) < 2:
+        return True
+
+    op = operator.gt
+    if rev:
+        op = operator.lt
+
+    return _is_sorted(a, 0, op)
+
+
+def iterative_is_sorted(a, rev=False) -> bool:
+    """Iterative alternative to `is_sorted`.
+
+    **Time Complexity**: O(n)."""
+    if len(a) < 2:
+        return True
+
+    op = operator.gt
+    if rev:
+        op = operator.lt
+
+    for i in range(len(a) - 1):
+        if op(a[i], a[i + 1]):
+            return False
+
+    return True
+
+
+def pythonic_is_sorted(a, rev=False) -> bool:
+    """Checking if a is sorted in a shorter way by using the `all` function."""
+    op = operator.le
+    if rev:
+        op = operator.ge
+    return all(op(a[i], a[i + 1]) for i in range(len(a) - 1))
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def is_sorted(

a, rev=False)

+
+ + + + +

Checks recursively if a is sorted.

+

If rev is True, this function checks if a is sorted in descending order, +else if it's sorted in ascending order.

+
+ +
+
def is_sorted(a, rev=False) -> bool:
+    """Checks recursively if `a` is sorted.
+
+    If `rev` is `True`, this function checks if `a` is sorted in descending order,
+    else if it's sorted in ascending order."""
+    if len(a) < 2:
+        return True
+
+    op = operator.gt
+    if rev:
+        op = operator.lt
+
+    return _is_sorted(a, 0, op)
+
+
+
+ +
+ + +
+
+

def iterative_is_sorted(

a, rev=False)

+
+ + + + +

Iterative alternative to is_sorted.

+

Time Complexity: O(n).

+
+ +
+
def iterative_is_sorted(a, rev=False) -> bool:
+    """Iterative alternative to `is_sorted`.
+
+    **Time Complexity**: O(n)."""
+    if len(a) < 2:
+        return True
+
+    op = operator.gt
+    if rev:
+        op = operator.lt
+
+    for i in range(len(a) - 1):
+        if op(a[i], a[i + 1]):
+            return False
+
+    return True
+
+
+
+ +
+ + +
+
+

def pythonic_is_sorted(

a, rev=False)

+
+ + + + +

Checking if a is sorted in a shorter way by using the all function.

+
+ +
+
def pythonic_is_sorted(a, rev=False) -> bool:
+    """Checking if a is sorted in a shorter way by using the `all` function."""
+    op = operator.le
+    if rev:
+        op = operator.ge
+    return all(op(a[i], a[i + 1]) for i in range(len(a) - 1))
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/recursion/make_decimal.m.html b/docs/ands/algorithms/recursion/make_decimal.m.html new file mode 100644 index 00000000..64962d33 --- /dev/null +++ b/docs/ands/algorithms/recursion/make_decimal.m.html @@ -0,0 +1,1178 @@ + + + + + + ands.algorithms.recursion.make_decimal API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.recursion.make_decimal module

+

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 20/01/2017

+

Description

+

Converts a number in a certain base in the range [2, 36] to a decimal number (number in base 10). +Bases greater than 10 take in order as digits the letters of the English alphabet. +For example, a number system with base 11, would take 'a' as the 11th digit.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+
+Created: 2015
+
+Updated: 20/01/2017
+
+## Description
+
+Converts a number in a certain base in the range [2, 36] to a decimal number (number in base 10).
+Bases greater than 10 take in order as digits the letters of the English alphabet.
+For example, a number system with base 11, would take 'a' as the 11th digit.
+"""
+
+from string import ascii_lowercase, digits
+
+__all__ = ["make_decimal", "ALPHA_NUMERIC_ALPHABET"]
+
+
+def _build_alpha_numeric_alphabet() -> dict:
+    """Returns a dictionary whose keys are all nine digits from 0 to 9
+    and the 26 letters of the English alphabet.
+    The values of the numbers are the numbers themselves;
+    the values of the letters are 10 for 'a', 11 for 'b', and so on until 36 for 'z'."""
+    alphabet = {}
+    for i, char in enumerate(ascii_lowercase):
+        # Letters of the alphabet start after digit 9.
+        alphabet[char] = i + 10
+    for i, char in enumerate(digits):
+        alphabet[char] = i
+    return alphabet
+
+
+ALPHA_NUMERIC_ALPHABET = _build_alpha_numeric_alphabet()
+
+
+def _make_decimal(n: str, b: int, pos: int) -> int:
+    """Suppose we have a number `n` (represented as string) in base `b`: xyz
+    The algorithm of converting it to a decimal representation is as follows.
+
+        b^{0} * decimal_value(z) + b^{1} * decimal_value(y) + b^{2} * decimal_value(x),
+
+    where decimal_value(z) is the decimal value of `z`.
+
+    For example, suppose "ef2" is an hexadecimal number
+    that we want to convert to decimal, which should yield 3826.
+
+        16^{0} * decimal_value(2) + 16^{1} * decimal_value(f) + 16^{2} * decimal_value(e) =
+        1 * 2 + 16 * 15 + 256 * 14 =
+        2 + 240 + 3584 =
+        3826
+
+    Note: in any number `xyz` in any base `b`,
+    z is in the "ones" position (has the smaller "value"),
+    y is in the "tens" position and
+    x is in the "hundreds" position (has the greatest "value").
+    See here: http://www.math.com/school/subject1/lessons/S1U1L1GL.html."""
+    if len(n) == 0:
+        return 0
+    else:
+        last = b ** pos * ALPHA_NUMERIC_ALPHABET[n[-1]]
+        return _make_decimal(n[:-1], b, pos + 1) + last
+
+
+def make_decimal(n: str, base: int) -> int:
+    """`n` is a number in any base in the range [2, 36].
+    `base` is the base in which `n` is currently represented.
+
+    Assumes `n` only contains digits in the range 0..9
+    and letters of the English alphabet.
+
+    Returns the decimal representation of `n` (as a int)."""
+    if not n:
+        raise ValueError("n cannot be an empty string or None")
+    if base > 36 or base < 2:
+        raise ValueError("not base >= 2 and base <= 36")
+    else:
+        return _make_decimal(n, base, 0)
+
+
+ +
+ +
+

Module variables

+
+

var ALPHA_NUMERIC_ALPHABET

+ + +
+
+ +
+ +

Functions

+ +
+
+

def make_decimal(

n, base)

+
+ + + + +

n is a number in any base in the range [2, 36]. +base is the base in which n is currently represented.

+

Assumes n only contains digits in the range 0..9 +and letters of the English alphabet.

+

Returns the decimal representation of n (as a int).

+
+ +
+
def make_decimal(n: str, base: int) -> int:
+    """`n` is a number in any base in the range [2, 36].
+    `base` is the base in which `n` is currently represented.
+
+    Assumes `n` only contains digits in the range 0..9
+    and letters of the English alphabet.
+
+    Returns the decimal representation of `n` (as a int)."""
+    if not n:
+        raise ValueError("n cannot be an empty string or None")
+    if base > 36 or base < 2:
+        raise ValueError("not base >= 2 and base <= 36")
+    else:
+        return _make_decimal(n, base, 0)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/recursion/palindrome.m.html b/docs/ands/algorithms/recursion/palindrome.m.html new file mode 100644 index 00000000..b4551bfb --- /dev/null +++ b/docs/ands/algorithms/recursion/palindrome.m.html @@ -0,0 +1,1115 @@ + + + + + + ands.algorithms.recursion.palindrome API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.recursion.palindrome module

+

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 20/01/2017

+

Description

+

Checking recursively if a string is a palindrome. +A palindrome is a string that reads the same way forward and backward. +For example, "anna" is a palindrome, whereas "prime" is not.

+

Resources

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+
+Created: 2015
+
+Updated: 20/01/2017
+
+## Description
+
+Checking recursively if a string is a palindrome.
+A palindrome is a string that reads the same way forward and backward.
+For example, "anna" is a palindrome, whereas "prime" is not.
+
+## Resources
+
+- [https://en.wikipedia.org/wiki/Palindrome](https://en.wikipedia.org/wiki/Palindrome)
+
+"""
+
+__all__ = ["is_palindrome"]
+
+
+def _is_palindrome(s: str, l: int, r: int) -> bool:
+    """`l` is the index that indexes `s` from the left
+    and, similarly, `r` indexes it from the right."""
+    if l >= r:
+        return True
+    if s[l] == s[r]:
+        return _is_palindrome(s, l + 1, r - 1)
+    else:
+        return False
+
+
+def is_palindrome(s: str) -> bool:
+    """Returns `True` if the string `s` is a palindrome, `False` otherwise."""
+    if len(s) <= 1:
+        return True
+    else:
+        return _is_palindrome(s, 0, len(s) - 1)
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def is_palindrome(

s)

+
+ + + + +

Returns True if the string s is a palindrome, False otherwise.

+
+ +
+
def is_palindrome(s: str) -> bool:
+    """Returns `True` if the string `s` is a palindrome, `False` otherwise."""
+    if len(s) <= 1:
+        return True
+    else:
+        return _is_palindrome(s, 0, len(s) - 1)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/recursion/power.m.html b/docs/ands/algorithms/recursion/power.m.html new file mode 100644 index 00000000..caf1ea67 --- /dev/null +++ b/docs/ands/algorithms/recursion/power.m.html @@ -0,0 +1,1101 @@ + + + + + + ands.algorithms.recursion.power API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.recursion.power module

+

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 18/01/2017

+

Description

+

Raising an integer a to the k >= 0 using recursion, i.e., ak = b.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+
+Created: 2015
+
+Updated: 18/01/2017
+
+## Description
+
+Raising an integer `a` to the `k >= 0` using recursion, i.e., ak = b.
+"""
+
+
+def power(base: int, p: int) -> int:
+    """Assumes inputs are integers and that the power `p >= 0`.
+
+    Base case: a0 = 1.
+    Recursive step: an + 1 = an * a."""
+    assert p >= 0
+
+    if p == 0:
+        return 1
+    else:
+        return base * power(base, p - 1)
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def power(

base, p)

+
+ + + + +

Assumes inputs are integers and that the power p >= 0.

+

Base case: a0 = 1. +Recursive step: an + 1 = an * a.

+
+ +
+
def power(base: int, p: int) -> int:
+    """Assumes inputs are integers and that the power `p >= 0`.
+
+    Base case: a0 = 1.
+    Recursive step: an + 1 = an * a."""
+    assert p >= 0
+
+    if p == 0:
+        return 1
+    else:
+        return base * power(base, p - 1)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/recursion/reverse.m.html b/docs/ands/algorithms/recursion/reverse.m.html new file mode 100644 index 00000000..0eed2562 --- /dev/null +++ b/docs/ands/algorithms/recursion/reverse.m.html @@ -0,0 +1,1100 @@ + + + + + + ands.algorithms.recursion.reverse API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.recursion.reverse module

+

Meta info

+

Author: Nelson Brochado

+

Created: 2015

+

Updated: 16/01/2017

+

Description

+

Reverses in-place the elements of a list using recursion. +This method could also be adapted to work with other mutable collections.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+## Meta info
+
+Author: Nelson Brochado
+
+Created: 2015
+
+Updated: 16/01/2017
+
+## Description
+
+Reverses in-place the elements of a list using recursion.
+This method could also be adapted to work with other mutable collections.
+"""
+
+__all__ = ["reverse"]
+
+
+def _reverse(ls: list, i: int, j: int) -> list:
+    if (j - i) >= 1:
+        ls[j], ls[i] = ls[i], ls[j]
+        _reverse(ls, i + 1, j - 1)
+    return ls
+
+
+def reverse(ls: list) -> list:
+    """Returns the reverse of the list `ls` using recursion."""
+    if len(ls) < 2:
+        return ls
+    else:
+        return _reverse(ls, 0, len(ls) - 1)
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def reverse(

ls)

+
+ + + + +

Returns the reverse of the list ls using recursion.

+
+ +
+
def reverse(ls: list) -> list:
+    """Returns the reverse of the list `ls` using recursion."""
+    if len(ls) < 2:
+        return ls
+    else:
+        return _reverse(ls, 0, len(ls) - 1)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/sorting/bubble_sort.m.html b/docs/ands/algorithms/sorting/bubble_sort.m.html new file mode 100644 index 00000000..f2b66e8d --- /dev/null +++ b/docs/ands/algorithms/sorting/bubble_sort.m.html @@ -0,0 +1,1098 @@ + + + + + + ands.algorithms.sorting.bubble_sort API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.sorting.bubble_sort module

+

Author: Nelson Brochado

+

Modified: 01/07/16

+

Resources

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Modified: 01/07/16
+
+### Resources
+- [Bubble Sort](http://en.wikipedia.org/wiki/Bubble_sort), Wiki's article
+
+- [The Bubble Sort](http://interactivepython.org/runestone/static/pythonds/SortSearch/TheBubbleSort.html),
+article by [http://interactivepython.org](http://interactivepython.org)
+"""
+
+
+def bubble_sort(ls: list):
+    """In-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity:** O(n2)."""
+    for i in range(len(ls) - 1):
+        for j in range(len(ls) - 1 - i):
+            if ls[j] > ls[j + 1]:
+                ls[j], ls[j + 1] = ls[j + 1], ls[j]
+    return ls
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def bubble_sort(

ls)

+
+ + + + +

In-place sorting algorithm. +Returns a reference to ls.

+

Time Complexity: O(n2).

+
+ +
+
def bubble_sort(ls: list):
+    """In-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity:** O(n2)."""
+    for i in range(len(ls) - 1):
+        for j in range(len(ls) - 1 - i):
+            if ls[j] > ls[j + 1]:
+                ls[j], ls[j + 1] = ls[j + 1], ls[j]
+    return ls
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/sorting/heap_sort.m.html b/docs/ands/algorithms/sorting/heap_sort.m.html new file mode 100644 index 00000000..cef080bb --- /dev/null +++ b/docs/ands/algorithms/sorting/heap_sort.m.html @@ -0,0 +1,1168 @@ + + + + + + ands.algorithms.sorting.heap_sort API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.sorting.heap_sort module

+

Author: Nelson Brochado

+

Creation: 09/09/15

+

Modified: 01/07/16

+

Resources

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Creation: 09/09/15
+
+Modified: 01/07/16
+
+### Resources
+- [Heap Sort](https://en.wikipedia.org/wiki/Heapsort), Wiki's article
+- [MIT's video lecture on Heaps and Heapsort](http://video.mit.edu/watch/introduction-to-algorithms-lecture-4-heaps-and-heap-sort-14154/)
+"""
+
+
+def max_heapify(ls: list, heap_size: int, i: int):
+    m = i
+    left = 2 * i + 1
+    right = 2 * i + 2
+    if left < heap_size and ls[left] > ls[m]:
+        m = left
+    if right < heap_size and ls[right] > ls[m]:
+        m = right
+    if i != m:
+        ls[i], ls[m] = ls[m], ls[i]
+        max_heapify(ls, heap_size, m)
+
+
+def build_max_heap(ls: list):
+    for i in range(len(ls) // 2, -1, -1):
+        max_heapify(ls, len(ls), i)
+    return ls
+
+
+def heap_sort(ls: list):
+    """In-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity:** O(n*log2(n))."""
+    build_max_heap(ls)
+    for i in range(len(ls) - 1, 0, -1):
+        ls[i], ls[0] = ls[0], ls[i]
+        max_heapify(ls, i, 0)
+    return ls
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def build_max_heap(

ls)

+
+ + + + +
+ +
+
def build_max_heap(ls: list):
+    for i in range(len(ls) // 2, -1, -1):
+        max_heapify(ls, len(ls), i)
+    return ls
+
+
+
+ +
+ + +
+
+

def heap_sort(

ls)

+
+ + + + +

In-place sorting algorithm. +Returns a reference to ls.

+

Time Complexity: O(n*log2(n)).

+
+ +
+
def heap_sort(ls: list):
+    """In-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity:** O(n*log2(n))."""
+    build_max_heap(ls)
+    for i in range(len(ls) - 1, 0, -1):
+        ls[i], ls[0] = ls[0], ls[i]
+        max_heapify(ls, i, 0)
+    return ls
+
+
+
+ +
+ + +
+
+

def max_heapify(

ls, heap_size, i)

+
+ + + + +
+ +
+
def max_heapify(ls: list, heap_size: int, i: int):
+    m = i
+    left = 2 * i + 1
+    right = 2 * i + 2
+    if left < heap_size and ls[left] > ls[m]:
+        m = left
+    if right < heap_size and ls[right] > ls[m]:
+        m = right
+    if i != m:
+        ls[i], ls[m] = ls[m], ls[i]
+        max_heapify(ls, heap_size, m)
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/sorting/index.html b/docs/ands/algorithms/sorting/index.html new file mode 100644 index 00000000..3f9d8411 --- /dev/null +++ b/docs/ands/algorithms/sorting/index.html @@ -0,0 +1,1137 @@ + + + + + + ands.algorithms.sorting API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.sorting module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms.sorting.bubble_sort

+ + +

Author: Nelson Brochado

+

Modified: 01/07/16

+

Resources

+
+ +
+
+

ands.algorithms.sorting.heap_sort

+ + +

Author: Nelson Brochado

+

Creation: 09/09/15

+

Modified: 01/07/16

+

Resources

+
+ +
+
+

ands.algorithms.sorting.insertion_sort

+ + +

Author: Nelson Brochado

+

Modified: 01/07/16

+

Resources

+
+ +
+
+

ands.algorithms.sorting.merge_sort

+ + +

Author: Nelson Brochado

+

Modified: 01/07/16

+

Resources

+
    +
  • +

    Merge Sort, Wiki's article

    +
  • +
  • +

    The Merge Sort, +section of online book on searching and sorting by +[http://inte...

    +
  • +
+ +
+
+

ands.algorithms.sorting.quick_sort

+ + +

Author: Nelson Brochado

+

Modified: 01/07/16

+

Parts of the quicksort algorithm

+
    +
  1. +

    Partition

    +

    All elements smaller than a number usually called "pivot" +are put to the left of the pivot.

    +

    In this quick sort algorithm, the pivot is chosen to be +the last element of the range [`...

    +
  2. +
+ +
+
+

ands.algorithms.sorting.selection_sort

+ + +

Author: Nelson Brochado

+

Modified: 01/07/16

+

Resources

+
+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/sorting/insertion_sort.m.html b/docs/ands/algorithms/sorting/insertion_sort.m.html new file mode 100644 index 00000000..324c7e98 --- /dev/null +++ b/docs/ands/algorithms/sorting/insertion_sort.m.html @@ -0,0 +1,1102 @@ + + + + + + ands.algorithms.sorting.insertion_sort API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.sorting.insertion_sort module

+

Author: Nelson Brochado

+

Modified: 01/07/16

+

Resources

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Modified: 01/07/16
+
+### Resources
+
+- [Insertion Sort](http://en.wikipedia.org/wiki/Insertion_sort), Wiki's article
+
+- [The Insertion Sort](http://interactivepython.org/runestone/static/pythonds/SortSearch/TheInsertionSort.html),
+article by [http://interactivepython.org](http://interactivepython.org)
+"""
+
+
+def insertion_sort(ls: list):
+    """In-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity**: O(n2)."""
+    for i in range(1, len(ls)):
+        n = i
+        while n > 0 and ls[n] < ls[n - 1]:
+            ls[n], ls[n - 1] = ls[n - 1], ls[n]
+            n -= 1
+    return ls
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def insertion_sort(

ls)

+
+ + + + +

In-place sorting algorithm. +Returns a reference to ls.

+

Time Complexity: O(n2).

+
+ +
+
def insertion_sort(ls: list):
+    """In-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity**: O(n2)."""
+    for i in range(1, len(ls)):
+        n = i
+        while n > 0 and ls[n] < ls[n - 1]:
+            ls[n], ls[n - 1] = ls[n - 1], ls[n]
+            n -= 1
+    return ls
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/sorting/merge_sort.m.html b/docs/ands/algorithms/sorting/merge_sort.m.html new file mode 100644 index 00000000..d36d944e --- /dev/null +++ b/docs/ands/algorithms/sorting/merge_sort.m.html @@ -0,0 +1,1258 @@ + + + + + + ands.algorithms.sorting.merge_sort API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.sorting.merge_sort module

+

Author: Nelson Brochado

+

Modified: 01/07/16

+

Resources

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Modified: 01/07/16
+
+### Resources
+- [Merge Sort](http://en.wikipedia.org/wiki/Merge_sort), Wiki's article
+
+- [The Merge Sort](http://interactivepython.org/runestone/static/pythonds/SortSearch/TheMergeSort.html),
+section of online book on searching and sorting by
+[http://interactivepython.org](http://interactivepython.org)
+"""
+
+
+def merge(left: list, right: list):
+    """Merges 2 sorted lists (`left` and `right`) in 1 single list,
+    which is returned at the end.
+
+    **Time Complexity**: O(m), where `m = len(left) + len(right)`."""
+    mid = []
+    i = 0  # Used to index the left list.
+    j = 0  # Used to index the right list.
+
+    while i < len(left) and j < len(right):
+        if left[i] < right[j]:
+            mid.append(left[i])
+            i += 1
+        else:
+            mid.append(right[j])
+            j += 1
+
+    while i < len(left):
+        mid.append(left[i])
+        i += 1
+
+    while j < len(right):
+        mid.append(right[j])
+        j += 1
+
+    return mid
+
+
+def merge_r(left: list, right: list):
+    """Equivalent to `merge`, but using recursion
+    and creating new sub-lists at each recursion call.
+
+    You should use `merge` instead of this function,
+    because the space complexity of this algorithm is higher."""
+    if len(left) == 0:
+        return right
+    elif len(right) == 0:
+        return left
+    elif left[0] < right[0]:
+        return [left[0]] + merge_r(left[1:], right)
+    else:
+        return [right[0]] + merge_r(left, right[1:])
+
+
+def _merge_sort_aux(ls: list):
+    """Not-in-place sorting algorithm.
+
+    Splits the original list `ls` until we have many sub-lists
+    of one element (which is by the way the base case).
+
+    Note that a list of 1 element is sorted by definition.
+
+    Using the merge algorithm,
+    we can easily merge two sorted lists of size 1,
+    to obtain a merged sorted list of size 2.
+    We keep merging greater sorted lists,
+    until we obtain the final sorted list.
+
+    **Time Complexity**: O(n*log2(n))"""
+
+    # Base case, where "ls" contains either 1 or 0 items,
+    # and it is by definition sorted.
+    if len(ls) < 2:
+        return ls
+
+    # Calls merge_sort on the left half part of ls.
+    left = merge_sort(ls[0:len(ls) // 2])
+
+    # Calls merge_sort on the right half part of ls.
+    right = merge_sort(ls[len(ls) // 2:])
+
+    # Note that in the previous 2 statements,
+    # we are creating new sub-lists using ls[0:len(ls)//2],
+    # for the first case, for example.
+
+    # Returns a new sorted list composed of the items in left and right.
+    return merge(left, right)
+
+
+def merge_sort(ls: list):
+    """Not-in-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity**: O(n*log2(n))."""
+    ls = _merge_sort_aux(ls)
+    return ls
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def merge(

left, right)

+
+ + + + +

Merges 2 sorted lists (left and right) in 1 single list, +which is returned at the end.

+

Time Complexity: O(m), where m = len(left) + len(right).

+
+ +
+
def merge(left: list, right: list):
+    """Merges 2 sorted lists (`left` and `right`) in 1 single list,
+    which is returned at the end.
+
+    **Time Complexity**: O(m), where `m = len(left) + len(right)`."""
+    mid = []
+    i = 0  # Used to index the left list.
+    j = 0  # Used to index the right list.
+
+    while i < len(left) and j < len(right):
+        if left[i] < right[j]:
+            mid.append(left[i])
+            i += 1
+        else:
+            mid.append(right[j])
+            j += 1
+
+    while i < len(left):
+        mid.append(left[i])
+        i += 1
+
+    while j < len(right):
+        mid.append(right[j])
+        j += 1
+
+    return mid
+
+
+
+ +
+ + +
+
+

def merge_r(

left, right)

+
+ + + + +

Equivalent to merge, but using recursion +and creating new sub-lists at each recursion call.

+

You should use merge instead of this function, +because the space complexity of this algorithm is higher.

+
+ +
+
def merge_r(left: list, right: list):
+    """Equivalent to `merge`, but using recursion
+    and creating new sub-lists at each recursion call.
+
+    You should use `merge` instead of this function,
+    because the space complexity of this algorithm is higher."""
+    if len(left) == 0:
+        return right
+    elif len(right) == 0:
+        return left
+    elif left[0] < right[0]:
+        return [left[0]] + merge_r(left[1:], right)
+    else:
+        return [right[0]] + merge_r(left, right[1:])
+
+
+
+ +
+ + +
+
+

def merge_sort(

ls)

+
+ + + + +

Not-in-place sorting algorithm. +Returns a reference to ls.

+

Time Complexity: O(n*log2(n)).

+
+ +
+
def merge_sort(ls: list):
+    """Not-in-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity**: O(n*log2(n))."""
+    ls = _merge_sort_aux(ls)
+    return ls
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/sorting/quick_sort.m.html b/docs/ands/algorithms/sorting/quick_sort.m.html new file mode 100644 index 00000000..f9dab29b --- /dev/null +++ b/docs/ands/algorithms/sorting/quick_sort.m.html @@ -0,0 +1,1228 @@ + + + + + + ands.algorithms.sorting.quick_sort API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.sorting.quick_sort module

+

Author: Nelson Brochado

+

Modified: 01/07/16

+

Parts of the quicksort algorithm

+
    +
  1. +

    Partition

    +

    All elements smaller than a number usually called "pivot" +are put to the left of the pivot.

    +

    In this quick sort algorithm, the pivot is chosen to be +the last element of the range [start, end], +but it could also have been choosen, e.g., to be the middle element.

    +

    We keep searching for elements less than the pivot, +from the left to the right of the range [start, end[, +and we insert them at the position tracked by the variable p_index.

    +

    So, p_index keeps track of the position (or index) +in the range [start, end[, where all elements to the left of p_index are smaller than the pivot. +Before returning this position (p_index), the pivot is inserted in that position. +Note that doing this, the pivot will be already in its final sorted position.

    +
  2. +
  3. +

    Recursive Calls

    +

    quick_sort is called recursively +on the left of the pivot and on the right until start >= end.

    +
  4. +
+

Resources

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Modified: 01/07/16
+
+
+### Parts of the quicksort algorithm
+
+1. **Partition**
+
+    All elements smaller than a number  usually called "pivot"
+    are put to the left of the pivot.
+
+    In this quick sort algorithm, the pivot is chosen to be
+    the last element of the range [`start`, `end`],
+    but it could also have been choosen, e.g., to be the middle element.
+
+    We keep searching for elements less than the pivot,
+    from the left to the right of the range [`start`, `end`[,
+    and we insert them at the position tracked by the variable `p_index`.
+
+    So, `p_index` keeps track of the position (or index)
+    in the range [`start`, `end`[, where all elements to the left of `p_index` are smaller than the pivot.
+    Before returning this position (`p_index`), the pivot is inserted in that position.
+    Note that doing this, the pivot will be already in its final sorted position.
+
+2. **Recursive Calls**
+
+    `quick_sort` is called recursively
+    on the left of the pivot and on the right until `start >= end`.
+
+
+## Resources
+- [Quicksort](http://en.wikipedia.org/wiki/Quicksort), Wiki's article
+- [The Quick Sort](http://interactivepython.org/runestone/static/pythonds/SortSearch/TheQuickSort.html),
+section of online book on searching and sorting by
+[http://interactivepython.org](http://interactivepython.org)
+"""
+
+
+def partition(ls: list, start: int, end: int):
+    """Shifts all elements in `ls` that are less than the pivot
+    to the left of the position `p_index`, which is at the end returned.
+
+    **Time Complexity:** O(k), where k is the size of `ls`."""
+    pivot = ls[end]  # Take last element as pivot.
+    p_index = start
+
+    for i in range(start, end):
+        if ls[i] <= pivot:
+            ls[p_index], ls[i] = ls[i], ls[p_index]
+            p_index += 1
+
+    # Insert the pivot at index p_index (the pivot's index).
+    ls[p_index], ls[end] = ls[end], ls[p_index]
+    return p_index
+
+
+def _quick_sort_aux(ls: list, start: int, end: int):
+    """Keeps calling partition to find the pivot index,
+    and then calls itself recursively on the left and right
+    sides of the pivot index (`p_index`)."""
+    if start < end:
+        # Returns the pivot index after partition.
+        p_index = partition(ls, start, end)
+
+        # Calling _quick_sort_aux on the left side of the pivot.
+        _quick_sort_aux(ls, start, p_index - 1)
+
+        # Calling quick_sort on the right side of the pivot.
+        _quick_sort_aux(ls, p_index + 1, end)
+
+
+def quick_sort(ls: list):
+    """In-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity**
+
+    - Worst Case: O(n2)
+
+    - Average Case: O(n*log2(n))"""
+    _quick_sort_aux(ls, 0, len(ls) - 1)
+    return ls
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def partition(

ls, start, end)

+
+ + + + +

Shifts all elements in ls that are less than the pivot +to the left of the position p_index, which is at the end returned.

+

Time Complexity: O(k), where k is the size of ls.

+
+ +
+
def partition(ls: list, start: int, end: int):
+    """Shifts all elements in `ls` that are less than the pivot
+    to the left of the position `p_index`, which is at the end returned.
+
+    **Time Complexity:** O(k), where k is the size of `ls`."""
+    pivot = ls[end]  # Take last element as pivot.
+    p_index = start
+
+    for i in range(start, end):
+        if ls[i] <= pivot:
+            ls[p_index], ls[i] = ls[i], ls[p_index]
+            p_index += 1
+
+    # Insert the pivot at index p_index (the pivot's index).
+    ls[p_index], ls[end] = ls[end], ls[p_index]
+    return p_index
+
+
+
+ +
+ + +
+
+

def quick_sort(

ls)

+
+ + + + +

In-place sorting algorithm. +Returns a reference to ls.

+

Time Complexity

+
    +
  • +

    Worst Case: O(n2)

    +
  • +
  • +

    Average Case: O(n*log2(n))

    +
  • +
+
+ +
+
def quick_sort(ls: list):
+    """In-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity**
+
+    - Worst Case: O(n2)
+
+    - Average Case: O(n*log2(n))"""
+    _quick_sort_aux(ls, 0, len(ls) - 1)
+    return ls
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/sorting/selection_sort.m.html b/docs/ands/algorithms/sorting/selection_sort.m.html new file mode 100644 index 00000000..37437c08 --- /dev/null +++ b/docs/ands/algorithms/sorting/selection_sort.m.html @@ -0,0 +1,1102 @@ + + + + + + ands.algorithms.sorting.selection_sort API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.sorting.selection_sort module

+

Author: Nelson Brochado

+

Modified: 01/07/16

+

Resources

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Modified: 01/07/16
+
+### Resources
+
+- [Selection Sort](http://en.wikipedia.org/wiki/Selection_sort), Wiki's article
+
+- [The Selection Sort](http://interactivepython.org/runestone/static/pythonds/SortSearch/TheSelectionSort.html),
+article at [http://interactivepython.org](http://interactivepython.org)
+"""
+
+
+def selection_sort(ls: list):
+    """In-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity**: O(n2)."""
+    for i in range(len(ls) - 1):
+        k = i
+        for j in range(i + 1, len(ls)):
+            if ls[j] < ls[k]:
+                ls[k], ls[j] = ls[j], ls[k]
+    return ls
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def selection_sort(

ls)

+
+ + + + +

In-place sorting algorithm. +Returns a reference to ls.

+

Time Complexity: O(n2).

+
+ +
+
def selection_sort(ls: list):
+    """In-place sorting algorithm.
+    Returns a reference to `ls`.
+
+    **Time Complexity**: O(n2)."""
+    for i in range(len(ls) - 1):
+        k = i
+        for j in range(i + 1, len(ls)):
+            if ls[j] < ls[k]:
+                ls[k], ls[j] = ls[j], ls[k]
+    return ls
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/unclassified/index.html b/docs/ands/algorithms/unclassified/index.html new file mode 100644 index 00000000..bd696260 --- /dev/null +++ b/docs/ands/algorithms/unclassified/index.html @@ -0,0 +1,1033 @@ + + + + + + ands.algorithms.unclassified API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.unclassified module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+ +
+ +
+
+ +
+ + diff --git a/docs/ands/algorithms/unclassified/max_num_dups.m.html b/docs/ands/algorithms/unclassified/max_num_dups.m.html new file mode 100644 index 00000000..34604953 --- /dev/null +++ b/docs/ands/algorithms/unclassified/max_num_dups.m.html @@ -0,0 +1,1110 @@ + + + + + + ands.algorithms.unclassified.max_num_dups API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.algorithms.unclassified.max_num_dups module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+
+def max_num_dups(A):
+    """Find the maximum number of duplicated numbers.
+
+    A must be sorted!!
+    """
+    m = 0
+    c = 1
+    for i in range(len(A) - 1):
+        if A[i] == A[i + 1]:
+            c += 1
+        elif c > m:
+            m = c
+            c = 1
+    return m
+
+
+def test():
+    ls = [12, 12, 12, 92, 92]
+    print("List:", ls)
+    print("Max number of duplicates:", max_num_dups(ls))
+
+
+if __name__ == '__main__':
+    test()
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def max_num_dups(

A)

+
+ + + + +

Find the maximum number of duplicated numbers.

+

A must be sorted!!

+
+ +
+
def max_num_dups(A):
+    """Find the maximum number of duplicated numbers.
+
+    A must be sorted!!
+    """
+    m = 0
+    c = 1
+    for i in range(len(A) - 1):
+        if A[i] == A[i + 1]:
+            c += 1
+        elif c > m:
+            m = c
+            c = 1
+    return m
+
+
+
+ +
+ + +
+
+

def test(

)

+
+ + + + +
+ +
+
def test():
+    ls = [12, 12, 12, 92, 92]
+    print("List:", ls)
+    print("Max number of duplicates:", max_num_dups(ls))
+
+
+
+ +
+ + + +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/BST.m.html b/docs/ands/ds/BST.m.html new file mode 100644 index 00000000..c5265174 --- /dev/null +++ b/docs/ands/ds/BST.m.html @@ -0,0 +1,4518 @@ + + + + + + ands.ds.BST API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.BST module

+

Meta info

+

Author: Nelson Brochado

+

Created: 01/07/2015

+

Updated: 28/08/2016

+

Description

+

Coding Conventions

+

In general, if a variable name has more than one word, +those words are separated by _ (underscores). +Functions' names should roughly describe what the function does. +Names of functions' local variables are usually short, +and not so self-descriptive, but, on the other hand, +comments are usually provide on the first occurrence of the name, +in order to explain the purpose of such a variable.

+

Functions

+
    +
  • Methods that start with _ should not be called, +because they might either be "helper" or private functions.
  • +
+

Parameters

+
    +
  • +

    u, v, z and w are used to indicate that a general BSTNode object is expected.

    +
  • +
  • +

    s is used to indicate that a source node is expected.

    +
  • +
  • +

    x is used when the parameter's expected type can either be a BSTNode object +or any other comparable object to represent keys.

    +
  • +
  • +

    ls is usually used to indicate that a list or a tuple is expected.

    +
  • +
+

Local Variables

+
    +
  • +

    c usually indicates some "current" changing variable.

    +
  • +
  • +

    p is usually c's parent.

    +
  • +
+

Docstrings

+

Under methods' signatures, h in O(h) is the height of the tree. +Note that the height of a BST varies depending on how elements +are inserted and removed. +m in O(m) is the height of the subtree rooted at the node passed +as parameter.

+

Other names are self-descriptive. +For example, "key" and "value" are self-descriptive.

+

TODO

+
    +
  • Improve the "randomness" of insertion into the BSTImproved class.
  • +
  • Add functions "intersection" and "union".
  • +
  • Implement a recursive version of insert (OPTIONAL).
  • +
  • implement "is balanced" function (http://codereview.stackexchange.com/questions/108459/binary-tree-data-structure)
  • +
  • Maybe the methods of the BSTNode need an improvement in terms of implementation...
  • +
+

Resources

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+# Meta info
+
+Author: Nelson Brochado
+
+Created: 01/07/2015
+
+Updated: 28/08/2016
+
+# Description
+
+### Coding Conventions
+
+In general, if a variable name has more than one word,
+those words are separated by _ (underscores).
+Functions' names should roughly describe what the function does.
+Names of functions' local variables are usually short,
+and not so self-descriptive, but, on the other hand,
+comments are usually provide on the first occurrence of the name,
+in order to explain the purpose of such a variable.
+
+#### Functions
+
+- Methods that start with _ should not be called,
+because they might either be "helper" or private functions.
+
+#### Parameters
+
+- `u`, `v`, `z` and `w` are used to indicate that a general `BSTNode` object is expected.
+
+- `s` is used to indicate that a source node is expected.
+
+- `x` is used when the parameter's expected type can either be a `BSTNode` object
+or any other comparable object to represent keys.
+
+- `ls` is usually used to indicate that a list or a tuple is expected.
+
+#### Local Variables
+
+- `c` usually indicates some "current" changing variable.
+
+- `p` is usually `c`'s parent.
+
+#### Docstrings
+
+Under methods' signatures, h in O(h) is the height of the tree.
+Note that the height of a BST varies depending on how elements
+are inserted and removed.
+m in O(m) is the height of the subtree rooted at the node passed
+as parameter.
+
+Other names are self-descriptive.
+For example, "key" and "value" are self-descriptive.
+
+# TODO
+
+- Improve the "randomness" of insertion into the BSTImproved class.
+- Add functions "intersection" and "union".
+- Implement a recursive version of insert (OPTIONAL).
+- implement "is balanced" function (http://codereview.stackexchange.com/questions/108459/binary-tree-data-structure)
+- Maybe the methods of the BSTNode need an improvement in terms of implementation...
+
+# Resources
+
+- [https://en.wikipedia.org/wiki/Binary_search_tree](https://en.wikipedia.org/wiki/Binary_search_tree)
+- [Introduction to Algorithms (3rd edition)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS, chapter 12
+- [http://algs4.cs.princeton.edu/32bst/](http://algs4.cs.princeton.edu/32bst/)
+- [http://www.cs.princeton.edu/courses/archive/spr04/cos226/lectures/bst.4up.pdf](http://www.cs.princeton.edu/courses/archive/spr04/cos226/lectures/bst.4up.pdf)
+- [http://algs4.cs.princeton.edu/32bst/BST.java.html](http://algs4.cs.princeton.edu/32bst/BST.java.html)
+
+"""
+
+from random import randint
+
+from tabulate import tabulate
+
+__all__ = ["BST", "BSTNode", "is_bst"]
+
+
+class BSTNode:
+    """Class to represent a BST's node."""
+
+    def __init__(self, key, value=None, parent=None, left=None, right=None):
+        if key is None:
+            raise ValueError("key cannot be None")
+        self.key = key
+        self.value = value
+        self.parent = parent
+        self.left = left
+        self.right = right
+        # Used for printing purposes.
+        self.label = "[" + str(self.key) + "]"
+
+    @property
+    def sibling(self):
+        """Returns the sibling node of this node,
+        which can of course be `None`."""
+        if self.parent is not None:
+            if self.is_left_child():
+                return self.parent.right
+            else:
+                return self.parent.left
+
+    @property
+    def grandparent(self):
+        """Returns the parent of the parent of this node."""
+        if self.parent is not None:
+            return self.parent.parent
+
+    @property
+    def uncle(self):
+        """Returns the uncle node of this node.
+        The uncle is the sibling of the parent of this node,
+        if it exists. `None` is returned if it doesn't exist,
+        or the parent or grandparent of this node is `None`."""
+        if self.grandparent is not None:  # implies that also parent is not None
+            if self.parent == self.grandparent.left:
+                return self.grandparent.right
+            else:  # self.parent == self.grandparent.right:
+                return self.grandparent.left
+
+    def reset(self):
+        self.parent = None
+        self.left = None
+        self.right = None
+
+    def is_left_child(self) -> bool:
+        if self.parent is not None:
+            if self.parent.left is not None:
+                return self.parent.left == self
+        else:
+            raise AttributeError("self does not have a parent.")
+
+    def is_right_child(self) -> bool:
+        if self.parent is not None:
+            if self.parent.right is not None:
+                return self.parent.right == self
+        else:
+            raise AttributeError("self does not have a parent.")
+
+    def has_children(self) -> bool:
+        """Returns `True` if `self` has at least one child. `False` otherwise."""
+        return self.left or self.right
+
+    def has_one_child(self) -> bool:
+        """Returns `True` only if `self` has exactly one child. `False` otherwise."""
+        return (self.left and not self.right) or (not self.left and self.right)
+
+    def has_two_children(self) -> bool:
+        """Returns `True` if self has exactly two children. `False` otherwise."""
+        return self.left and self.right
+
+    def count(self) -> int:
+        """Counts the numbers of nodes under `self` (including `self`)."""
+
+        def _count(u, c: int):
+            if u is None:
+                return c
+            else:
+                c += 1
+            c = _count(u.left, c)
+            c = _count(u.right, c)
+            return c
+
+        if not self.has_children():
+            return 1
+        else:
+            c = 0
+            return _count(self, c)
+
+    def __str__(self):
+        return "{" + str(self.key) + ": " + str(self.value) + "}"
+
+    def __fields(self):
+        return [["Node (Key)", self.key],
+                ["Value", self.value],
+                ["Parent", self.parent],
+                ["Left child", self.left],
+                ["Right child", self.right],
+                ["Sibling", self.sibling],
+                ["Grandparent", self.grandparent],
+                ["Uncle", self.uncle]]
+
+    def __repr__(self):
+        return tabulate(self.__fields(), tablefmt="fancy_grid")
+
+    def show(self):
+        print(self.__repr__())
+
+
+class BST:
+    """`BST` is a class that represents a classical binary search tree."""
+
+    def __init__(self, root=None, name="BST"):
+        self.root = root
+        self.name = name
+        self.n = 0  # number of nodes
+        if root is not None:
+            self._initialise(root)
+
+    # INITIALISE
+
+    def _initialise(self, u: BSTNode):
+        """Sets `u` as the new root and unique node of this tree."""
+        self.root = u
+        self.root.parent = None
+        self.root.left = None
+        self.root.right = None
+        self.n = 1
+
+    def size(self):
+        """Returns the total number of nodes.
+
+        **Time Complexity**: O(1)."""
+        return self.n
+
+    def is_empty(self):
+        """Returns `True` if this tree has 0 nodes.
+
+        **Time Complexity**: O(1)."""
+        return self.size() == 0
+
+    def is_root(self, u: BSTNode):
+        """Checks if `u` is the root.
+
+        **Time Complexity**: O(1)."""
+        if u == self.root:
+            assert u.parent is None
+        return u == self.root
+
+    def clear(self):
+        """Removes all nodes from this tree.
+
+        **Time Complexity**: O(1)."""
+        self.root = None
+        self.n = 0
+
+    # INSERTIONS
+
+    def insert(self, x, value=None):
+        """Inserts (normally) `x` into this BST object.
+
+        **Time Complexity**: O(h)."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, BSTNode):
+            x = BSTNode(x, value)
+        if x.left or x.right or x.parent:
+            raise ValueError(
+                "x cannot have left or right children, or parent.")
+
+        if self.root is None:
+            self._initialise(x)
+        else:
+            c = self.root  # c is the current node
+            p = self.root.parent  # parent of c
+
+            while c is not None:
+                p = c
+                if x.key < c.key:
+                    c = c.left
+                else:
+                    c = c.right
+
+            if x.key < p.key:
+                p.left = x
+            else:
+                p.right = x
+
+            x.parent = p
+            self.n += 1
+
+    def insert_many(self, ls):
+        """Calls `self.insert` for all elements of `ls`.
+        Therefore the elements of `ls` should either be
+        `BSTNode` objects or they should represent keys.
+
+        **Time Complexity**: O(len(ls)*h)."""
+        for i in ls:
+            self.insert(i)
+
+    # SEARCH
+
+    def search(self, key, s: BSTNode = None) -> BSTNode:
+        """Searches for the key in the tree.
+        If `s` is specified, then this procedure starts searching from `s`.
+
+        `key` must be a comparable object of the same type as the other keys.
+
+        **Time Complexity**: O(h)."""
+        if key is None:
+            raise ValueError("key cannot be None.")
+        if s is None:
+            return self.search_i(key)
+        else:
+            return BST._search_i(key, s)
+
+    def search_r(self, key) -> BSTNode:
+        """Searches recursively for `key` starting from `self.root`.
+
+        **Time Complexity**: O(h)."""
+        return self._search_r(key, self.root)
+
+    def _search_r(self, key, s: BSTNode) -> BSTNode:
+        """Searches recursively for `key` in the subtree rooted at `s`.
+
+        `key` must be a comparable object of the same type as the other keys.
+
+        **Time Complexity**: O(m),
+        where `m` is the height of the subtree rooted at `s`,
+        if `s` is not `None`. Else the time complexity is O(1)."""
+        if s is None or key == s.key:
+            return s
+        elif key < s.key:
+            return self._search_r(key, s.left)
+        else:
+            return self._search_r(key, s.right)
+
+    def search_i(self, key) -> BSTNode:
+        """Searches iteratively for key starting from the root.
+
+        **Time Complexity**: O(h)."""
+        return BST._search_i(key, self.root)
+
+    @staticmethod
+    def _search_i(key, s: BSTNode):
+        """Searches iteratively for key in the subtree rooted at `s`.
+
+        **Time Complexity**: O(m)."""
+        c = s  # c is the current node
+        while c:
+            if key == c.key:
+                return c
+            elif key < c.key:
+                c = c.left
+            else:
+                c = c.right
+
+    # CONTAINS
+
+    def contains(self, key) -> bool:
+        """Returns `True` if a `BSTNode` object with `key` exists in the tree.
+
+        **Time Complexity**: O(h)."""
+        return self.search_r(key) is not None
+
+    # SELECT
+
+    def rank(self, key) -> int:
+        """Returns the number of keys strictly less than `key`.
+
+        **Time Complexity**: O(h)."""
+        if key is None:
+            raise ValueError("key cannot be None.")
+        if not self.search(key):
+            raise LookupError("key was not found.")
+        if self.root is None:
+            return 0
+        else:
+            r = 0
+            return self._rank(self.root, key, r)
+
+    def _rank(self, u: BSTNode, key, r: int) -> int:
+        if u is None:
+            return r
+        if u.key < key:
+            r += 1
+        r = self._rank(u.left, key, r)
+        r = self._rank(u.right, key, r)
+        return r
+
+    def height(self) -> int:
+        """Returns the maximum depth or height of the tree.
+
+        **Time Complexity**: O(h)."""
+        if self.root is None:
+            return 0
+        return self._height(self.root)
+
+    def _height(self, u: BSTNode) -> int:
+        if u is None:
+            return 0
+        return 1 + max(self._height(u.left), self._height(u.right))
+
+    # TRAVERSALS
+
+    def in_order_traversal(self):
+        """Prints the elements of the tree in increasing order.
+
+        **Time Complexity**: O(h)."""
+        self._in_order_traversal(self.root)
+        print("\n")
+
+    def _in_order_traversal(self, u: BSTNode, e=", "):
+        if u:
+            self._in_order_traversal(u.left)
+            print(u, end=e)
+            self._in_order_traversal(u.right)
+
+    def pre_order_traversal(self):
+        """Prints the keys of this tree in pre-order.
+        The pre-order consists of recursively printing first a node `u`,
+        then its left child node and then its right child node.
+
+        **Time Complexity**: O(h)."""
+        self._pre_order_traversal(self.root)
+        print("\n")
+
+    def _pre_order_traversal(self, u: BSTNode, e=", "):
+        if u:
+            print(u, end=e)
+            self._pre_order_traversal(u.left)
+            self._pre_order_traversal(u.right)
+
+    def post_order_traversal(self):
+        """Prints the keys of this tree in post-order.
+        It does the opposite of `pre_order_traversal`.
+
+        **Time Complexity**: O(h)."""
+        self._post_order_traversal(self.root)
+        print("\n")
+
+    def _post_order_traversal(self, u: BSTNode, e=", "):
+        if u:
+            self._post_order_traversal(u.left)
+            self._post_order_traversal(u.right)
+            print(u, end=e)
+
+    def reverse_in_order_traversal(self):
+        """Prints the keys of this tree in decreasing order.
+
+        It does the opposite of `self.in_order_traversal`.
+
+        **Time Complexity**: O(h)."""
+        self._reverse_in_order_traversal(self.root)
+        print("\n")
+
+    def _reverse_in_order_traversal(self, u: BSTNode, e=", "):
+        if u:
+            self._reverse_in_order_traversal(u.right)
+            print(u, end=e)
+            self._reverse_in_order_traversal(u.left)
+
+    # ROTATIONS
+
+    def left_rotate(self, x):
+        """Left rotates the subtree rooted at node `x`.
+
+        `x` can be a `BSTNode` object, and in that case,
+        this function performs in constant time O(1);
+        else, if node is not a `BSTNode` object,
+        it tries to search for a `BSTNode` object with key=x,
+        and, in that case, it performs in O(h) time.
+
+        Returns the node which is at the previous position of `x`,
+        that is it returns the parent of `x`.
+
+        **Time Complexity**: O(1)."""
+        c = None  # It will rotate the subtree rooted at c.
+
+        if not isinstance(x, BSTNode):
+            c = self.search(x)
+            if c is None:
+                raise LookupError("key node was not found in the tree.")
+        else:
+            c = x
+
+        # To left rotate a node, its right child must exist.
+        if c.right is None:
+            raise ValueError("Left rotation cannot be performed on " + str(c) +
+                             " because it does not have a right child.")
+
+        c.right.parent = c.parent
+
+        # Only the root has a None parent.
+        if c.parent is None:
+            self.root = c.right
+
+        # Checking if c is a left or a right child,
+        # in order to set the new left
+        # or right child respectively of its parent.
+        elif c.is_left_child():
+            c.parent.left = c.right
+        else:
+            c.parent.right = c.right
+
+        c.parent = c.right
+
+        # The new right child of c becomes what is
+        # the left child of its previous right child.
+        c.right = c.parent.left
+
+        # Set c to be the parent of its new right child.
+        if c.right is not None:
+            c.right.parent = c
+
+        # Set c to be the new left child of its new parent.
+        c.parent.left = c
+
+        return c.parent
+
+    def right_rotate(self, x):
+        """Right rotates the subtree rooted at node `x`.
+        See doc-strings of `self.left_rotate`.
+
+        **Time Complexity**: O(1)."""
+        c = None
+
+        if not isinstance(x, BSTNode):
+            c = self.search(x)
+            if c is None:
+                raise LookupError("key node was not found in the tree.")
+        else:
+            c = x
+
+        if c.left is None:
+            raise ValueError("Right rotation cannot be performed on " + str(c) +
+                             " because it does not have a left child.")
+
+        c.left.parent = c.parent
+
+        if c.parent is None:
+            self.root = c.left
+        elif c.is_left_child():
+            c.parent.left = c.left
+        else:
+            c.parent.right = c.left
+
+        c.parent = c.left
+        c.left = c.parent.right
+
+        if c.left is not None:
+            c.left.parent = c
+
+        c.parent.right = c
+        return c.parent
+
+    # MINIMUM AND MAXIMUM
+
+    def minimum(self):
+        """Calls `BST._minimum_r(self.root)` if `self.root` is evaluated to `True`.
+
+        **Time Complexity**: O(h)."""
+        if self.root:
+            return BST._minimum_r(self.root)
+
+    @staticmethod
+    def _minimum_r(u: BSTNode):
+        """Recursive version of the `BST._minimum(u)` function."""
+        if u.left:
+            u = BST._minimum_r(u.left)
+        return u
+
+    @staticmethod
+    def _minimum(u: BSTNode):
+        """Returns the node (rooted at u) with the minimum key."""
+        while u.left:
+            u = u.left
+        return u
+
+    def maximum(self):
+        """Calls `BST._maximum_r(self.root)` if `self.root` is evaluated to `True`.
+
+        **Time Complexity**: O(h)."""
+        if self.root:
+            return BST._maximum_r(self.root)
+
+    @staticmethod
+    def _maximum_r(u: BSTNode):
+        """Recursive version of `BST._maximum`."""
+        if u.right:
+            u = BST._maximum_r(u.right)
+        return u
+
+    @staticmethod
+    def _maximum(u: BSTNode):
+        """Returns the node (rooted at u) with the maximum key."""
+        while u.right:
+            u = u.right
+        return u
+
+    # SUCCESSOR AND PREDECESSOR
+
+    def successor(self, x):
+        """Finds the successor of `x`,
+        i.e. the smallest element greater than `x`.
+
+        If `x` has a right subtree,
+        then the successor of `x` is the minimum of that right subtree.
+
+        Otherwise it is the first ancestor of `x`, lets call it `A`,
+        such that `x` falls in the left subtree of `A`.
+
+        `x` can either be a reference to an actual `BSTNode` object,
+        or it can be a key of a supposed node in self.
+
+        **Time Complexity**: O(h)."""
+        if not isinstance(x, BSTNode):
+            x = self.search(x)
+            if x is None:
+                raise LookupError("No node was found with key=x.")
+
+        if x.right:
+            return BST._minimum_r(x.right)
+
+        p = x.parent
+
+        while p and p.right == x:
+            x = p
+            p = x.parent
+        return p
+
+    def predecessor(self, x):
+        """Finds the predecessor of the node `x`,
+        i.e. the greatest element smaller than `x`.
+
+        `x` can either be a reference to an actual `BSTNode` object,
+        or it can be a key of a supposed node in self.
+
+        **Time Complexity**: O(h)."""
+        if not isinstance(x, BSTNode):
+            x = self.search_r(x)
+            if x is None:
+                raise LookupError("No node was found with key=x.")
+        if x.left:
+            return BST._maximum_r(x.left)
+
+        p = x.parent
+
+        while p and x == p.left:
+            x = p
+            p = x.parent
+        return p
+
+    # REMOVALS
+
+    def remove_max(self):
+        """Removes and returns the maximum element of the tree, if it is not empty.
+
+        **Time Complexity**: O(h)."""
+
+        def _remove_max(u: BSTNode):
+            """Removes the maximum element of the subtree rooted at `u`.
+
+            Note that the maximum element is all the way to the right,
+            and it cannot have a right child,
+            but it can still have a left subtree.
+
+            If `u` is `None`, exceptions will be thrown."""
+            m = BST._maximum_r(u)
+
+            if m.left:  # m has a left subtree.
+                if self.is_root(m):  # m is the root.
+                    self.root = m.left
+                    m.left.parent = None  # self.root.parent = None
+                else:  # m is NOT the root.
+                    m.left.parent = m.parent
+                    m.parent.right = m.left
+            else:  # m has NO children
+                if self.is_root(m):
+                    self.root = None
+                else:
+                    m.parent.right = None
+
+            m.parent = m.left = None
+            self.n -= 1
+            return m
+
+        if self.n > 0:
+            return _remove_max(self.root)
+
+    def remove_min(self):
+        """Removes and returns the minimum element of the tree, if it is not empty.
+
+        **Time Complexity**: O(h)."""
+
+        def _remove_min(u: BSTNode):
+            """Removes and returns the minimum element of the subtree rooted at `u`.
+            If `u` is `None`, exceptions will be thrown."""
+            m = BST._minimum_r(u)
+
+            if m.right:
+                if self.is_root(m):
+                    self.root = m.right
+                    m.right.parent = None
+                else:
+                    m.right.parent = m.parent
+                    m.parent.left = m.right
+            else:  # m has not right subtree.
+                if self.is_root(m):
+                    self.root = None
+                else:  # m is an internal node with no right subtree.
+                    m.parent.left = None
+
+            m.right = m.parent = None
+            self.n -= 1
+            return m
+
+        if self.n > 0:
+            return _remove_min(self.root)
+
+    # DELETION
+
+    def delete(self, x):
+        """Deletes `x` from self (if it exists).
+
+        There are 3 cases of deletion:
+            1. `x` has no children
+            2. `x` has one subtree (or child)
+            3. `x` has the left and right subtrees (or children).
+
+        `x` can either be a reference to an actual `BSTNode` object,
+        or it can be a key of a supposed node in `self`.
+
+        **Time Complexity**: O(h)."""
+
+        def delete_helper(u: BSTNode):
+            """This is a helper method to the delete method,
+            thus it should not be called by clients.
+
+            When deleting a node u from a BST, we have basically to consider 3 cases:
+            1. u has no children
+            2. u has one child
+            3. u has two children
+
+            1. u has no children, then we simply remove it
+            by modifying its parent to replace u with None.
+            If u.parent is None, then u must be the root,
+            and thus we simply set the root to None.
+
+            2. u has just one child,
+            but we first need to decide which one (left or right).
+            Then we elevate this child to u's position in the tree
+            by modifying u's parent to replace u by u's child.
+            But if u's parent is None, that means u was the root,
+            and the new root becomes u's child.
+
+            3. u has two children, then we search for u's successor s,
+            (which must be in the u's right subtree,
+            and it's the smallest of that subtree)
+            which takes u's position in the tree.
+            The rest of the u's subtree becomes the s's right subtree,
+            and the u's left subtree becomes the new s's left subtree.
+            This case is a little bit tricky,
+            because it matters whether s is u's right child.
+
+            Suppose s is the right child of u, then we replace u by s,
+            which might or not have a right subtree, but no left subtree.
+
+            Suppose s is not the right child of u,
+            in this case, we replace s by its own right child,
+            and then we replace u by s.
+
+            Note that "delete_two_children" does NOT exactly do that,
+            but instead it simply replaces the positions of u and s,
+            as if s was u and u was s.
+
+            After that, delete_helper is called again on u,
+            but note that u is now in the previous s's position,
+            and thus u has now no left subtree, but at most a right subtree."""
+
+            def delete_at_most_one_child(v: BSTNode):
+                """Removes v from the tree, when v has at most one child.
+                This means that v could have 0 or 1 child."""
+                child = v.right
+                if v.left:
+                    child = v.left
+                if v.parent is None:  # v is the root.
+                    self.root = child
+                else:  # v has a parent, so it is not the root.
+                    if v.is_left_child():
+                        v.parent.left = child
+                    else:
+                        v.parent.right = child
+                # child is None iff v.right and v.left are None.
+                if child:
+                    child.parent = v.parent
+
+            def delete_two_children(v: BSTNode):
+                """Called by `delete_helper` when a node has two children."""
+                # Replace v with its successor s.
+                self._switch(v, self.successor(v))
+                # v has at most a right child now.
+                delete_helper(v)
+
+            if u.has_two_children():
+                delete_two_children(u)
+            else:  # u has at most one child
+                delete_at_most_one_child(u)
+            u.right = u.left = u.parent = None
+            return u
+
+        if x is None:
+            raise ValueError("x cannot be None.")
+
+        if not isinstance(x, BSTNode):
+            x = self.search_r(x)
+            if x is None:
+                raise LookupError("No node was found with key=x.")
+
+        if x.parent is None and not self.is_root(x):
+            raise ValueError("x is not a valid node.")
+
+        self.n -= 1
+        return delete_helper(x)
+
+    def _switch(self, u: BSTNode, v: BSTNode, search_first=False):
+        """"Switches the roles of `u` and `v` in the tree by moving references.
+        If `search_first` is set to `True`,
+        then `u` and `v` are first searched in the tree
+        to check if they are present, and if not, a `LookupError` is raised.
+        In general, clients should not use this function."""
+        if not u:
+            raise ValueError("u cannot be None.")
+        if not v:
+            raise ValueError("v cannot be None.")
+        if u == v:
+            raise ValueError("u cannot be equal to v.")
+
+        if search_first:
+            if not self.search(u.key) or not self.search(v.key):
+                raise LookupError("u or v not found.")
+
+        def switch_parent_son(p, s):
+            """Switches the roles of p and s,
+            where p (parent) is the direct parent of s (son)."""
+            assert s.parent == p
+
+            if s.is_left_child():
+                p.left = s.left
+                if s.left:
+                    s.left.parent = p
+
+                s.left = p
+
+                s.right, p.right = p.right, s.right
+                if s.right:
+                    s.right.parent = s
+                if p.right:
+                    p.right.parent = p
+            else:
+                p.right = s.right
+                if s.right:
+                    s.right.parent = p
+
+                s.right = p
+
+                s.left, p.left = p.left, s.left
+                if s.left:
+                    s.left.parent = s
+                if p.left:
+                    p.left.parent = p
+
+            if p.parent:
+                if p.is_left_child():
+                    p.parent.left = s
+                else:
+                    p.parent.right = s
+            else:  # p is the root
+                self.root = s
+
+            s.parent = p.parent
+            p.parent = s
+
+        def switch_non_parent_son(z, w):
+            """`z` and `w` are nodes in the tree
+            that are not related by a parent-child.
+
+            **Time Complexity**: O(1)."""
+            assert z.parent != w and w.parent != z
+
+            if not z.parent:
+                self.root = w
+                if w.is_left_child():
+                    w.parent.left = z
+                else:
+                    w.parent.right = z
+            elif not w.parent:
+                self.root = z
+                if z.is_left_child():
+                    z.parent.left = w
+                else:
+                    z.parent.right = w
+            else:  # neither z nor w is the root
+                if z.is_left_child():
+                    if w.is_left_child():
+                        w.parent.left, z.parent.left = z, w
+                    else:
+                        w.parent.right, z.parent.left = z, w
+                else:
+                    if w.is_left_child():
+                        w.parent.left, z.parent.right = z, w
+                    else:
+                        w.parent.right, z.parent.right = z, w
+
+            w.parent, z.parent = z.parent, w.parent
+            z.left, w.left = w.left, z.left
+            z.right, w.right = w.right, z.right
+
+            if z.left:
+                z.left.parent = z
+            if z.right:
+                z.right.parent = z
+            if w.left:
+                w.left.parent = w
+            if w.right:
+                w.right.parent = w
+
+        if u.parent == v:
+            switch_parent_son(v, u)
+        elif v.parent == u:
+            switch_parent_son(u, v)
+        else:
+            switch_non_parent_son(u, v)
+
+    def show(self):
+        """Pretty-prints this tree using `print`."""
+        print(self)
+
+    def __str__(self):
+        if self.root is None:
+            return 'Nothing to print: this BST is empty.'
+        return '\n'.join(BSTPrinter.print_1(self.root)[0]) + "\n"
+
+    def __repr__(self):
+        return self.__str__()
+
+
+class BSTPrinter:
+    """Based on: http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-006-introduction-to-algorithms-fall-2011/readings/binary-search-trees/bst.py"""
+
+    @staticmethod
+    def print_1(node):
+        """Pretty-prints this BST object."""
+        if node is None:
+            return [], 0, 0
+
+        fill = "_"
+
+        left_lines, left_pos, left_width = BSTPrinter.print_1(node.left)
+        right_lines, right_pos, right_width = BSTPrinter.print_1(node.right)
+        middle = max(right_pos + left_width - left_pos + 1, len(node.label), 2)
+        pos = left_pos + middle // 2
+        width = left_pos + middle + right_width - right_pos
+
+        while len(left_lines) < len(right_lines):
+            left_lines.append(' ' * left_width)
+
+        while len(right_lines) < len(left_lines):
+            right_lines.append(' ' * right_width)
+
+        if (middle - len(node.label)) % 2 == 1 and node.parent is not None and \
+                        node is node.parent.left and len(node.label) < middle:
+            node.label += fill
+
+        node.label = node.label.center(middle, fill)
+
+        if node.label[0] == fill:
+            node.label = ' ' + node.label[1:]
+
+        if node.label[-1] == fill:
+            node.label = node.label[:-1] + ' '
+
+        lines = [' ' * left_pos + node.label + ' ' * (right_width - right_pos),
+                 ' ' * left_pos + '/' + ' ' * (middle - 2) +
+                 '\\' + ' ' * (right_width - right_pos)] + \
+                [left_line + ' ' * (width - left_width - right_width) +
+
+                 right_line
+
+                 for left_line, right_line in zip(left_lines, right_lines)]
+        return lines, pos, width
+
+
+def is_bst(bst):
+    """Returns `True` if `bst` is a valid `BST` object. `False` otherwise.
+
+    Invariant: for each node `n` in `bst`,
+    if `n.left` exists, then `n.left <= n`,
+    and if `n.right` exists, then `n.right >= n`."""
+
+    def all_bst_nodes(t):
+        def h(n):
+            if n is not None:
+                if not isinstance(n, BSTNode):
+                    return False
+                return h(n.left) and h(n.right)
+            return True
+
+        return h(t.root)
+
+    def h(n):
+        if n is not None:
+            if n.left and n.key < n.left.key:
+                return False
+            if n.right and n.key > n.right.key:
+                return False
+
+            # Asserting n.left and n.right have n as parent
+            if n.left:
+                assert n.left.parent == n
+            if n.right:
+                assert n.right.parent == n
+
+            return h(n.left) and h(n.right)
+        return True
+
+    if not isinstance(bst, BST):
+        return False
+
+    return all_bst_nodes(bst) and h(bst.root)
+
+
+class BSTImproved(BST):
+    """Binary-search tree that provides somehow randomness at insertion."""
+
+    def __init__(self, root=None, name="BSTImproved"):
+        BST.__init__(self, root, name)
+
+    def insert(self, x, value=None):
+        """Inserts `x` into this tree.
+
+        `x` can either be a `BSTNode` object,
+        or it can be a _key_ of any other type,
+        but it should be comparable with the other keys,
+        and these keys should be comparable objects.
+
+        Note that the height of a `BST` varies
+        depending on how elements are inserted and removed.
+        For example, if we insert a list of numbers in increasing order,
+        the resulting `BST` object will look like a chain with height **n - 1**,
+        where `n` is the number of elements inserted.
+        In general, the optimal height is logarithmic on the number of nodes,
+        and to get closer to the optimal height,
+        randomly insertion of numbers is usually used.
+
+        If we have `n` keys to insert, there are `n!` (n-factorial)
+        ways of inserting those `n` keys into the binary search tree.
+        When we randomly insert them, those permutations are equally likely.
+
+        So, the expected height of a tree created with randomly insertions is O(log2(n)).
+        For a proof, see chapter 12 of Introduction to Algorithms (3rd ed.) by CLRS.
+
+        This function does a pseudo-random insertion of keys."""
+        r = randint(0, self.size() * 3 // 8)  # * 3 // 8 is just a random operation...
+        if r == 0:
+            self.root_insert(x, value)
+        else:
+            self.tail_insert(x, value)
+
+    def tail_insert(self, x, value=None):
+        """Inserts (normally) `x` into this BST object.
+
+        **Time Complexity**: O(h)."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+
+        if not isinstance(x, BSTNode):
+            x = BSTNode(x, value)
+
+        if x.left or x.right or x.parent:
+            raise ValueError("x cannot have left or right children, or parent.")
+
+        if self.root is None:
+            self._initialise(x)
+        else:
+            c = self.root  # c is the current node
+            p = self.root.parent  # parent of c
+
+            while c is not None:
+                p = c
+                if x.key < c.key:
+                    c = c.left
+                else:
+                    c = c.right
+            if x.key < p.key:
+                p.left = x
+            else:
+                p.right = x
+
+            x.parent = p
+            self.n += 1
+
+    def root_insert(self, x, value=None):
+        """Inserts `x` as the root of this tree.
+
+        **Time Complexity**: O(h)."""
+
+        def _root_insert(u: BSTNode, v: BSTNode):
+            """Helper method for `self.root_insert`."""
+            if u is None:
+                return v
+            if v.key < u.key:
+                u.left = _root_insert(u.left, v)
+                u = self.right_rotate(u)
+            else:
+                u.right = _root_insert(u.right, v)
+                u = self.left_rotate(u)
+            return u
+
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, BSTNode):
+            x = BSTNode(x, value)
+        if x.left or x.right or x.parent:
+            raise ValueError("x cannot have left or right children, or parent.")
+        if self.root is None:
+            self._initialise(x)
+        else:
+            _root_insert(self.root, x)
+            self.n += 1
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def is_bst(

bst)

+
+ + + + +

Returns True if bst is a valid BST object. False otherwise.

+

Invariant: for each node n in bst, +if n.left exists, then n.left <= n, +and if n.right exists, then n.right >= n.

+
+ +
+
def is_bst(bst):
+    """Returns `True` if `bst` is a valid `BST` object. `False` otherwise.
+
+    Invariant: for each node `n` in `bst`,
+    if `n.left` exists, then `n.left <= n`,
+    and if `n.right` exists, then `n.right >= n`."""
+
+    def all_bst_nodes(t):
+        def h(n):
+            if n is not None:
+                if not isinstance(n, BSTNode):
+                    return False
+                return h(n.left) and h(n.right)
+            return True
+
+        return h(t.root)
+
+    def h(n):
+        if n is not None:
+            if n.left and n.key < n.left.key:
+                return False
+            if n.right and n.key > n.right.key:
+                return False
+
+            # Asserting n.left and n.right have n as parent
+            if n.left:
+                assert n.left.parent == n
+            if n.right:
+                assert n.right.parent == n
+
+            return h(n.left) and h(n.right)
+        return True
+
+    if not isinstance(bst, BST):
+        return False
+
+    return all_bst_nodes(bst) and h(bst.root)
+
+
+
+ +
+ + +

Classes

+ +
+

class BST

+ + +

BST is a class that represents a classical binary search tree.

+
+ +
+
class BST:
+    """`BST` is a class that represents a classical binary search tree."""
+
+    def __init__(self, root=None, name="BST"):
+        self.root = root
+        self.name = name
+        self.n = 0  # number of nodes
+        if root is not None:
+            self._initialise(root)
+
+    # INITIALISE
+
+    def _initialise(self, u: BSTNode):
+        """Sets `u` as the new root and unique node of this tree."""
+        self.root = u
+        self.root.parent = None
+        self.root.left = None
+        self.root.right = None
+        self.n = 1
+
+    def size(self):
+        """Returns the total number of nodes.
+
+        **Time Complexity**: O(1)."""
+        return self.n
+
+    def is_empty(self):
+        """Returns `True` if this tree has 0 nodes.
+
+        **Time Complexity**: O(1)."""
+        return self.size() == 0
+
+    def is_root(self, u: BSTNode):
+        """Checks if `u` is the root.
+
+        **Time Complexity**: O(1)."""
+        if u == self.root:
+            assert u.parent is None
+        return u == self.root
+
+    def clear(self):
+        """Removes all nodes from this tree.
+
+        **Time Complexity**: O(1)."""
+        self.root = None
+        self.n = 0
+
+    # INSERTIONS
+
+    def insert(self, x, value=None):
+        """Inserts (normally) `x` into this BST object.
+
+        **Time Complexity**: O(h)."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, BSTNode):
+            x = BSTNode(x, value)
+        if x.left or x.right or x.parent:
+            raise ValueError(
+                "x cannot have left or right children, or parent.")
+
+        if self.root is None:
+            self._initialise(x)
+        else:
+            c = self.root  # c is the current node
+            p = self.root.parent  # parent of c
+
+            while c is not None:
+                p = c
+                if x.key < c.key:
+                    c = c.left
+                else:
+                    c = c.right
+
+            if x.key < p.key:
+                p.left = x
+            else:
+                p.right = x
+
+            x.parent = p
+            self.n += 1
+
+    def insert_many(self, ls):
+        """Calls `self.insert` for all elements of `ls`.
+        Therefore the elements of `ls` should either be
+        `BSTNode` objects or they should represent keys.
+
+        **Time Complexity**: O(len(ls)*h)."""
+        for i in ls:
+            self.insert(i)
+
+    # SEARCH
+
+    def search(self, key, s: BSTNode = None) -> BSTNode:
+        """Searches for the key in the tree.
+        If `s` is specified, then this procedure starts searching from `s`.
+
+        `key` must be a comparable object of the same type as the other keys.
+
+        **Time Complexity**: O(h)."""
+        if key is None:
+            raise ValueError("key cannot be None.")
+        if s is None:
+            return self.search_i(key)
+        else:
+            return BST._search_i(key, s)
+
+    def search_r(self, key) -> BSTNode:
+        """Searches recursively for `key` starting from `self.root`.
+
+        **Time Complexity**: O(h)."""
+        return self._search_r(key, self.root)
+
+    def _search_r(self, key, s: BSTNode) -> BSTNode:
+        """Searches recursively for `key` in the subtree rooted at `s`.
+
+        `key` must be a comparable object of the same type as the other keys.
+
+        **Time Complexity**: O(m),
+        where `m` is the height of the subtree rooted at `s`,
+        if `s` is not `None`. Else the time complexity is O(1)."""
+        if s is None or key == s.key:
+            return s
+        elif key < s.key:
+            return self._search_r(key, s.left)
+        else:
+            return self._search_r(key, s.right)
+
+    def search_i(self, key) -> BSTNode:
+        """Searches iteratively for key starting from the root.
+
+        **Time Complexity**: O(h)."""
+        return BST._search_i(key, self.root)
+
+    @staticmethod
+    def _search_i(key, s: BSTNode):
+        """Searches iteratively for key in the subtree rooted at `s`.
+
+        **Time Complexity**: O(m)."""
+        c = s  # c is the current node
+        while c:
+            if key == c.key:
+                return c
+            elif key < c.key:
+                c = c.left
+            else:
+                c = c.right
+
+    # CONTAINS
+
+    def contains(self, key) -> bool:
+        """Returns `True` if a `BSTNode` object with `key` exists in the tree.
+
+        **Time Complexity**: O(h)."""
+        return self.search_r(key) is not None
+
+    # SELECT
+
+    def rank(self, key) -> int:
+        """Returns the number of keys strictly less than `key`.
+
+        **Time Complexity**: O(h)."""
+        if key is None:
+            raise ValueError("key cannot be None.")
+        if not self.search(key):
+            raise LookupError("key was not found.")
+        if self.root is None:
+            return 0
+        else:
+            r = 0
+            return self._rank(self.root, key, r)
+
+    def _rank(self, u: BSTNode, key, r: int) -> int:
+        if u is None:
+            return r
+        if u.key < key:
+            r += 1
+        r = self._rank(u.left, key, r)
+        r = self._rank(u.right, key, r)
+        return r
+
+    def height(self) -> int:
+        """Returns the maximum depth or height of the tree.
+
+        **Time Complexity**: O(h)."""
+        if self.root is None:
+            return 0
+        return self._height(self.root)
+
+    def _height(self, u: BSTNode) -> int:
+        if u is None:
+            return 0
+        return 1 + max(self._height(u.left), self._height(u.right))
+
+    # TRAVERSALS
+
+    def in_order_traversal(self):
+        """Prints the elements of the tree in increasing order.
+
+        **Time Complexity**: O(h)."""
+        self._in_order_traversal(self.root)
+        print("\n")
+
+    def _in_order_traversal(self, u: BSTNode, e=", "):
+        if u:
+            self._in_order_traversal(u.left)
+            print(u, end=e)
+            self._in_order_traversal(u.right)
+
+    def pre_order_traversal(self):
+        """Prints the keys of this tree in pre-order.
+        The pre-order consists of recursively printing first a node `u`,
+        then its left child node and then its right child node.
+
+        **Time Complexity**: O(h)."""
+        self._pre_order_traversal(self.root)
+        print("\n")
+
+    def _pre_order_traversal(self, u: BSTNode, e=", "):
+        if u:
+            print(u, end=e)
+            self._pre_order_traversal(u.left)
+            self._pre_order_traversal(u.right)
+
+    def post_order_traversal(self):
+        """Prints the keys of this tree in post-order.
+        It does the opposite of `pre_order_traversal`.
+
+        **Time Complexity**: O(h)."""
+        self._post_order_traversal(self.root)
+        print("\n")
+
+    def _post_order_traversal(self, u: BSTNode, e=", "):
+        if u:
+            self._post_order_traversal(u.left)
+            self._post_order_traversal(u.right)
+            print(u, end=e)
+
+    def reverse_in_order_traversal(self):
+        """Prints the keys of this tree in decreasing order.
+
+        It does the opposite of `self.in_order_traversal`.
+
+        **Time Complexity**: O(h)."""
+        self._reverse_in_order_traversal(self.root)
+        print("\n")
+
+    def _reverse_in_order_traversal(self, u: BSTNode, e=", "):
+        if u:
+            self._reverse_in_order_traversal(u.right)
+            print(u, end=e)
+            self._reverse_in_order_traversal(u.left)
+
+    # ROTATIONS
+
+    def left_rotate(self, x):
+        """Left rotates the subtree rooted at node `x`.
+
+        `x` can be a `BSTNode` object, and in that case,
+        this function performs in constant time O(1);
+        else, if node is not a `BSTNode` object,
+        it tries to search for a `BSTNode` object with key=x,
+        and, in that case, it performs in O(h) time.
+
+        Returns the node which is at the previous position of `x`,
+        that is it returns the parent of `x`.
+
+        **Time Complexity**: O(1)."""
+        c = None  # It will rotate the subtree rooted at c.
+
+        if not isinstance(x, BSTNode):
+            c = self.search(x)
+            if c is None:
+                raise LookupError("key node was not found in the tree.")
+        else:
+            c = x
+
+        # To left rotate a node, its right child must exist.
+        if c.right is None:
+            raise ValueError("Left rotation cannot be performed on " + str(c) +
+                             " because it does not have a right child.")
+
+        c.right.parent = c.parent
+
+        # Only the root has a None parent.
+        if c.parent is None:
+            self.root = c.right
+
+        # Checking if c is a left or a right child,
+        # in order to set the new left
+        # or right child respectively of its parent.
+        elif c.is_left_child():
+            c.parent.left = c.right
+        else:
+            c.parent.right = c.right
+
+        c.parent = c.right
+
+        # The new right child of c becomes what is
+        # the left child of its previous right child.
+        c.right = c.parent.left
+
+        # Set c to be the parent of its new right child.
+        if c.right is not None:
+            c.right.parent = c
+
+        # Set c to be the new left child of its new parent.
+        c.parent.left = c
+
+        return c.parent
+
+    def right_rotate(self, x):
+        """Right rotates the subtree rooted at node `x`.
+        See doc-strings of `self.left_rotate`.
+
+        **Time Complexity**: O(1)."""
+        c = None
+
+        if not isinstance(x, BSTNode):
+            c = self.search(x)
+            if c is None:
+                raise LookupError("key node was not found in the tree.")
+        else:
+            c = x
+
+        if c.left is None:
+            raise ValueError("Right rotation cannot be performed on " + str(c) +
+                             " because it does not have a left child.")
+
+        c.left.parent = c.parent
+
+        if c.parent is None:
+            self.root = c.left
+        elif c.is_left_child():
+            c.parent.left = c.left
+        else:
+            c.parent.right = c.left
+
+        c.parent = c.left
+        c.left = c.parent.right
+
+        if c.left is not None:
+            c.left.parent = c
+
+        c.parent.right = c
+        return c.parent
+
+    # MINIMUM AND MAXIMUM
+
+    def minimum(self):
+        """Calls `BST._minimum_r(self.root)` if `self.root` is evaluated to `True`.
+
+        **Time Complexity**: O(h)."""
+        if self.root:
+            return BST._minimum_r(self.root)
+
+    @staticmethod
+    def _minimum_r(u: BSTNode):
+        """Recursive version of the `BST._minimum(u)` function."""
+        if u.left:
+            u = BST._minimum_r(u.left)
+        return u
+
+    @staticmethod
+    def _minimum(u: BSTNode):
+        """Returns the node (rooted at u) with the minimum key."""
+        while u.left:
+            u = u.left
+        return u
+
+    def maximum(self):
+        """Calls `BST._maximum_r(self.root)` if `self.root` is evaluated to `True`.
+
+        **Time Complexity**: O(h)."""
+        if self.root:
+            return BST._maximum_r(self.root)
+
+    @staticmethod
+    def _maximum_r(u: BSTNode):
+        """Recursive version of `BST._maximum`."""
+        if u.right:
+            u = BST._maximum_r(u.right)
+        return u
+
+    @staticmethod
+    def _maximum(u: BSTNode):
+        """Returns the node (rooted at u) with the maximum key."""
+        while u.right:
+            u = u.right
+        return u
+
+    # SUCCESSOR AND PREDECESSOR
+
+    def successor(self, x):
+        """Finds the successor of `x`,
+        i.e. the smallest element greater than `x`.
+
+        If `x` has a right subtree,
+        then the successor of `x` is the minimum of that right subtree.
+
+        Otherwise it is the first ancestor of `x`, lets call it `A`,
+        such that `x` falls in the left subtree of `A`.
+
+        `x` can either be a reference to an actual `BSTNode` object,
+        or it can be a key of a supposed node in self.
+
+        **Time Complexity**: O(h)."""
+        if not isinstance(x, BSTNode):
+            x = self.search(x)
+            if x is None:
+                raise LookupError("No node was found with key=x.")
+
+        if x.right:
+            return BST._minimum_r(x.right)
+
+        p = x.parent
+
+        while p and p.right == x:
+            x = p
+            p = x.parent
+        return p
+
+    def predecessor(self, x):
+        """Finds the predecessor of the node `x`,
+        i.e. the greatest element smaller than `x`.
+
+        `x` can either be a reference to an actual `BSTNode` object,
+        or it can be a key of a supposed node in self.
+
+        **Time Complexity**: O(h)."""
+        if not isinstance(x, BSTNode):
+            x = self.search_r(x)
+            if x is None:
+                raise LookupError("No node was found with key=x.")
+        if x.left:
+            return BST._maximum_r(x.left)
+
+        p = x.parent
+
+        while p and x == p.left:
+            x = p
+            p = x.parent
+        return p
+
+    # REMOVALS
+
+    def remove_max(self):
+        """Removes and returns the maximum element of the tree, if it is not empty.
+
+        **Time Complexity**: O(h)."""
+
+        def _remove_max(u: BSTNode):
+            """Removes the maximum element of the subtree rooted at `u`.
+
+            Note that the maximum element is all the way to the right,
+            and it cannot have a right child,
+            but it can still have a left subtree.
+
+            If `u` is `None`, exceptions will be thrown."""
+            m = BST._maximum_r(u)
+
+            if m.left:  # m has a left subtree.
+                if self.is_root(m):  # m is the root.
+                    self.root = m.left
+                    m.left.parent = None  # self.root.parent = None
+                else:  # m is NOT the root.
+                    m.left.parent = m.parent
+                    m.parent.right = m.left
+            else:  # m has NO children
+                if self.is_root(m):
+                    self.root = None
+                else:
+                    m.parent.right = None
+
+            m.parent = m.left = None
+            self.n -= 1
+            return m
+
+        if self.n > 0:
+            return _remove_max(self.root)
+
+    def remove_min(self):
+        """Removes and returns the minimum element of the tree, if it is not empty.
+
+        **Time Complexity**: O(h)."""
+
+        def _remove_min(u: BSTNode):
+            """Removes and returns the minimum element of the subtree rooted at `u`.
+            If `u` is `None`, exceptions will be thrown."""
+            m = BST._minimum_r(u)
+
+            if m.right:
+                if self.is_root(m):
+                    self.root = m.right
+                    m.right.parent = None
+                else:
+                    m.right.parent = m.parent
+                    m.parent.left = m.right
+            else:  # m has not right subtree.
+                if self.is_root(m):
+                    self.root = None
+                else:  # m is an internal node with no right subtree.
+                    m.parent.left = None
+
+            m.right = m.parent = None
+            self.n -= 1
+            return m
+
+        if self.n > 0:
+            return _remove_min(self.root)
+
+    # DELETION
+
+    def delete(self, x):
+        """Deletes `x` from self (if it exists).
+
+        There are 3 cases of deletion:
+            1. `x` has no children
+            2. `x` has one subtree (or child)
+            3. `x` has the left and right subtrees (or children).
+
+        `x` can either be a reference to an actual `BSTNode` object,
+        or it can be a key of a supposed node in `self`.
+
+        **Time Complexity**: O(h)."""
+
+        def delete_helper(u: BSTNode):
+            """This is a helper method to the delete method,
+            thus it should not be called by clients.
+
+            When deleting a node u from a BST, we have basically to consider 3 cases:
+            1. u has no children
+            2. u has one child
+            3. u has two children
+
+            1. u has no children, then we simply remove it
+            by modifying its parent to replace u with None.
+            If u.parent is None, then u must be the root,
+            and thus we simply set the root to None.
+
+            2. u has just one child,
+            but we first need to decide which one (left or right).
+            Then we elevate this child to u's position in the tree
+            by modifying u's parent to replace u by u's child.
+            But if u's parent is None, that means u was the root,
+            and the new root becomes u's child.
+
+            3. u has two children, then we search for u's successor s,
+            (which must be in the u's right subtree,
+            and it's the smallest of that subtree)
+            which takes u's position in the tree.
+            The rest of the u's subtree becomes the s's right subtree,
+            and the u's left subtree becomes the new s's left subtree.
+            This case is a little bit tricky,
+            because it matters whether s is u's right child.
+
+            Suppose s is the right child of u, then we replace u by s,
+            which might or not have a right subtree, but no left subtree.
+
+            Suppose s is not the right child of u,
+            in this case, we replace s by its own right child,
+            and then we replace u by s.
+
+            Note that "delete_two_children" does NOT exactly do that,
+            but instead it simply replaces the positions of u and s,
+            as if s was u and u was s.
+
+            After that, delete_helper is called again on u,
+            but note that u is now in the previous s's position,
+            and thus u has now no left subtree, but at most a right subtree."""
+
+            def delete_at_most_one_child(v: BSTNode):
+                """Removes v from the tree, when v has at most one child.
+                This means that v could have 0 or 1 child."""
+                child = v.right
+                if v.left:
+                    child = v.left
+                if v.parent is None:  # v is the root.
+                    self.root = child
+                else:  # v has a parent, so it is not the root.
+                    if v.is_left_child():
+                        v.parent.left = child
+                    else:
+                        v.parent.right = child
+                # child is None iff v.right and v.left are None.
+                if child:
+                    child.parent = v.parent
+
+            def delete_two_children(v: BSTNode):
+                """Called by `delete_helper` when a node has two children."""
+                # Replace v with its successor s.
+                self._switch(v, self.successor(v))
+                # v has at most a right child now.
+                delete_helper(v)
+
+            if u.has_two_children():
+                delete_two_children(u)
+            else:  # u has at most one child
+                delete_at_most_one_child(u)
+            u.right = u.left = u.parent = None
+            return u
+
+        if x is None:
+            raise ValueError("x cannot be None.")
+
+        if not isinstance(x, BSTNode):
+            x = self.search_r(x)
+            if x is None:
+                raise LookupError("No node was found with key=x.")
+
+        if x.parent is None and not self.is_root(x):
+            raise ValueError("x is not a valid node.")
+
+        self.n -= 1
+        return delete_helper(x)
+
+    def _switch(self, u: BSTNode, v: BSTNode, search_first=False):
+        """"Switches the roles of `u` and `v` in the tree by moving references.
+        If `search_first` is set to `True`,
+        then `u` and `v` are first searched in the tree
+        to check if they are present, and if not, a `LookupError` is raised.
+        In general, clients should not use this function."""
+        if not u:
+            raise ValueError("u cannot be None.")
+        if not v:
+            raise ValueError("v cannot be None.")
+        if u == v:
+            raise ValueError("u cannot be equal to v.")
+
+        if search_first:
+            if not self.search(u.key) or not self.search(v.key):
+                raise LookupError("u or v not found.")
+
+        def switch_parent_son(p, s):
+            """Switches the roles of p and s,
+            where p (parent) is the direct parent of s (son)."""
+            assert s.parent == p
+
+            if s.is_left_child():
+                p.left = s.left
+                if s.left:
+                    s.left.parent = p
+
+                s.left = p
+
+                s.right, p.right = p.right, s.right
+                if s.right:
+                    s.right.parent = s
+                if p.right:
+                    p.right.parent = p
+            else:
+                p.right = s.right
+                if s.right:
+                    s.right.parent = p
+
+                s.right = p
+
+                s.left, p.left = p.left, s.left
+                if s.left:
+                    s.left.parent = s
+                if p.left:
+                    p.left.parent = p
+
+            if p.parent:
+                if p.is_left_child():
+                    p.parent.left = s
+                else:
+                    p.parent.right = s
+            else:  # p is the root
+                self.root = s
+
+            s.parent = p.parent
+            p.parent = s
+
+        def switch_non_parent_son(z, w):
+            """`z` and `w` are nodes in the tree
+            that are not related by a parent-child.
+
+            **Time Complexity**: O(1)."""
+            assert z.parent != w and w.parent != z
+
+            if not z.parent:
+                self.root = w
+                if w.is_left_child():
+                    w.parent.left = z
+                else:
+                    w.parent.right = z
+            elif not w.parent:
+                self.root = z
+                if z.is_left_child():
+                    z.parent.left = w
+                else:
+                    z.parent.right = w
+            else:  # neither z nor w is the root
+                if z.is_left_child():
+                    if w.is_left_child():
+                        w.parent.left, z.parent.left = z, w
+                    else:
+                        w.parent.right, z.parent.left = z, w
+                else:
+                    if w.is_left_child():
+                        w.parent.left, z.parent.right = z, w
+                    else:
+                        w.parent.right, z.parent.right = z, w
+
+            w.parent, z.parent = z.parent, w.parent
+            z.left, w.left = w.left, z.left
+            z.right, w.right = w.right, z.right
+
+            if z.left:
+                z.left.parent = z
+            if z.right:
+                z.right.parent = z
+            if w.left:
+                w.left.parent = w
+            if w.right:
+                w.right.parent = w
+
+        if u.parent == v:
+            switch_parent_son(v, u)
+        elif v.parent == u:
+            switch_parent_son(u, v)
+        else:
+            switch_non_parent_son(u, v)
+
+    def show(self):
+        """Pretty-prints this tree using `print`."""
+        print(self)
+
+    def __str__(self):
+        if self.root is None:
+            return 'Nothing to print: this BST is empty.'
+        return '\n'.join(BSTPrinter.print_1(self.root)[0]) + "\n"
+
+    def __repr__(self):
+        return self.__str__()
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • BST
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, root=None, name='BST')

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, root=None, name="BST"):
+    self.root = root
+    self.name = name
+    self.n = 0  # number of nodes
+    if root is not None:
+        self._initialise(root)
+
+
+
+ +
+ + +
+
+

def clear(

self)

+
+ + + + +

Removes all nodes from this tree.

+

Time Complexity: O(1).

+
+ +
+
def clear(self):
+    """Removes all nodes from this tree.
+    **Time Complexity**: O(1)."""
+    self.root = None
+    self.n = 0
+
+
+
+ +
+ + +
+
+

def contains(

self, key)

+
+ + + + +

Returns True if a BSTNode object with key exists in the tree.

+

Time Complexity: O(h).

+
+ +
+
def contains(self, key) -> bool:
+    """Returns `True` if a `BSTNode` object with `key` exists in the tree.
+    **Time Complexity**: O(h)."""
+    return self.search_r(key) is not None
+
+
+
+ +
+ + +
+
+

def delete(

self, x)

+
+ + + + +

Deletes x from self (if it exists).

+

There are 3 cases of deletion: + 1. x has no children + 2. x has one subtree (or child) + 3. x has the left and right subtrees (or children).

+

x can either be a reference to an actual BSTNode object, +or it can be a key of a supposed node in self.

+

Time Complexity: O(h).

+
+ +
+
def delete(self, x):
+    """Deletes `x` from self (if it exists).
+    There are 3 cases of deletion:
+        1. `x` has no children
+        2. `x` has one subtree (or child)
+        3. `x` has the left and right subtrees (or children).
+    `x` can either be a reference to an actual `BSTNode` object,
+    or it can be a key of a supposed node in `self`.
+    **Time Complexity**: O(h)."""
+    def delete_helper(u: BSTNode):
+        """This is a helper method to the delete method,
+        thus it should not be called by clients.
+        When deleting a node u from a BST, we have basically to consider 3 cases:
+        1. u has no children
+        2. u has one child
+        3. u has two children
+        1. u has no children, then we simply remove it
+        by modifying its parent to replace u with None.
+        If u.parent is None, then u must be the root,
+        and thus we simply set the root to None.
+        2. u has just one child,
+        but we first need to decide which one (left or right).
+        Then we elevate this child to u's position in the tree
+        by modifying u's parent to replace u by u's child.
+        But if u's parent is None, that means u was the root,
+        and the new root becomes u's child.
+        3. u has two children, then we search for u's successor s,
+        (which must be in the u's right subtree,
+        and it's the smallest of that subtree)
+        which takes u's position in the tree.
+        The rest of the u's subtree becomes the s's right subtree,
+        and the u's left subtree becomes the new s's left subtree.
+        This case is a little bit tricky,
+        because it matters whether s is u's right child.
+        Suppose s is the right child of u, then we replace u by s,
+        which might or not have a right subtree, but no left subtree.
+        Suppose s is not the right child of u,
+        in this case, we replace s by its own right child,
+        and then we replace u by s.
+        Note that "delete_two_children" does NOT exactly do that,
+        but instead it simply replaces the positions of u and s,
+        as if s was u and u was s.
+        After that, delete_helper is called again on u,
+        but note that u is now in the previous s's position,
+        and thus u has now no left subtree, but at most a right subtree."""
+        def delete_at_most_one_child(v: BSTNode):
+            """Removes v from the tree, when v has at most one child.
+            This means that v could have 0 or 1 child."""
+            child = v.right
+            if v.left:
+                child = v.left
+            if v.parent is None:  # v is the root.
+                self.root = child
+            else:  # v has a parent, so it is not the root.
+                if v.is_left_child():
+                    v.parent.left = child
+                else:
+                    v.parent.right = child
+            # child is None iff v.right and v.left are None.
+            if child:
+                child.parent = v.parent
+        def delete_two_children(v: BSTNode):
+            """Called by `delete_helper` when a node has two children."""
+            # Replace v with its successor s.
+            self._switch(v, self.successor(v))
+            # v has at most a right child now.
+            delete_helper(v)
+        if u.has_two_children():
+            delete_two_children(u)
+        else:  # u has at most one child
+            delete_at_most_one_child(u)
+        u.right = u.left = u.parent = None
+        return u
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, BSTNode):
+        x = self.search_r(x)
+        if x is None:
+            raise LookupError("No node was found with key=x.")
+    if x.parent is None and not self.is_root(x):
+        raise ValueError("x is not a valid node.")
+    self.n -= 1
+    return delete_helper(x)
+
+
+
+ +
+ + +
+
+

def height(

self)

+
+ + + + +

Returns the maximum depth or height of the tree.

+

Time Complexity: O(h).

+
+ +
+
def height(self) -> int:
+    """Returns the maximum depth or height of the tree.
+    **Time Complexity**: O(h)."""
+    if self.root is None:
+        return 0
+    return self._height(self.root)
+
+
+
+ +
+ + +
+
+

def in_order_traversal(

self)

+
+ + + + +

Prints the elements of the tree in increasing order.

+

Time Complexity: O(h).

+
+ +
+
def in_order_traversal(self):
+    """Prints the elements of the tree in increasing order.
+    **Time Complexity**: O(h)."""
+    self._in_order_traversal(self.root)
+    print("\n")
+
+
+
+ +
+ + +
+
+

def insert(

self, x, value=None)

+
+ + + + +

Inserts (normally) x into this BST object.

+

Time Complexity: O(h).

+
+ +
+
def insert(self, x, value=None):
+    """Inserts (normally) `x` into this BST object.
+    **Time Complexity**: O(h)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, BSTNode):
+        x = BSTNode(x, value)
+    if x.left or x.right or x.parent:
+        raise ValueError(
+            "x cannot have left or right children, or parent.")
+    if self.root is None:
+        self._initialise(x)
+    else:
+        c = self.root  # c is the current node
+        p = self.root.parent  # parent of c
+        while c is not None:
+            p = c
+            if x.key < c.key:
+                c = c.left
+            else:
+                c = c.right
+        if x.key < p.key:
+            p.left = x
+        else:
+            p.right = x
+        x.parent = p
+        self.n += 1
+
+
+
+ +
+ + +
+
+

def insert_many(

self, ls)

+
+ + + + +

Calls self.insert for all elements of ls. +Therefore the elements of ls should either be +BSTNode objects or they should represent keys.

+

Time Complexity: O(len(ls)*h).

+
+ +
+
def insert_many(self, ls):
+    """Calls `self.insert` for all elements of `ls`.
+    Therefore the elements of `ls` should either be
+    `BSTNode` objects or they should represent keys.
+    **Time Complexity**: O(len(ls)*h)."""
+    for i in ls:
+        self.insert(i)
+
+
+
+ +
+ + +
+
+

def is_empty(

self)

+
+ + + + +

Returns True if this tree has 0 nodes.

+

Time Complexity: O(1).

+
+ +
+
def is_empty(self):
+    """Returns `True` if this tree has 0 nodes.
+    **Time Complexity**: O(1)."""
+    return self.size() == 0
+
+
+
+ +
+ + +
+
+

def is_root(

self, u)

+
+ + + + +

Checks if u is the root.

+

Time Complexity: O(1).

+
+ +
+
def is_root(self, u: BSTNode):
+    """Checks if `u` is the root.
+    **Time Complexity**: O(1)."""
+    if u == self.root:
+        assert u.parent is None
+    return u == self.root
+
+
+
+ +
+ + +
+
+

def left_rotate(

self, x)

+
+ + + + +

Left rotates the subtree rooted at node x.

+

x can be a BSTNode object, and in that case, +this function performs in constant time O(1); +else, if node is not a BSTNode object, +it tries to search for a BSTNode object with key=x, +and, in that case, it performs in O(h) time.

+

Returns the node which is at the previous position of x, +that is it returns the parent of x.

+

Time Complexity: O(1).

+
+ +
+
def left_rotate(self, x):
+    """Left rotates the subtree rooted at node `x`.
+    `x` can be a `BSTNode` object, and in that case,
+    this function performs in constant time O(1);
+    else, if node is not a `BSTNode` object,
+    it tries to search for a `BSTNode` object with key=x,
+    and, in that case, it performs in O(h) time.
+    Returns the node which is at the previous position of `x`,
+    that is it returns the parent of `x`.
+    **Time Complexity**: O(1)."""
+    c = None  # It will rotate the subtree rooted at c.
+    if not isinstance(x, BSTNode):
+        c = self.search(x)
+        if c is None:
+            raise LookupError("key node was not found in the tree.")
+    else:
+        c = x
+    # To left rotate a node, its right child must exist.
+    if c.right is None:
+        raise ValueError("Left rotation cannot be performed on " + str(c) +
+                         " because it does not have a right child.")
+    c.right.parent = c.parent
+    # Only the root has a None parent.
+    if c.parent is None:
+        self.root = c.right
+    # Checking if c is a left or a right child,
+    # in order to set the new left
+    # or right child respectively of its parent.
+    elif c.is_left_child():
+        c.parent.left = c.right
+    else:
+        c.parent.right = c.right
+    c.parent = c.right
+    # The new right child of c becomes what is
+    # the left child of its previous right child.
+    c.right = c.parent.left
+    # Set c to be the parent of its new right child.
+    if c.right is not None:
+        c.right.parent = c
+    # Set c to be the new left child of its new parent.
+    c.parent.left = c
+    return c.parent
+
+
+
+ +
+ + +
+
+

def maximum(

self)

+
+ + + + +

Calls BST._maximum_r(self.root) if self.root is evaluated to True.

+

Time Complexity: O(h).

+
+ +
+
def maximum(self):
+    """Calls `BST._maximum_r(self.root)` if `self.root` is evaluated to `True`.
+    **Time Complexity**: O(h)."""
+    if self.root:
+        return BST._maximum_r(self.root)
+
+
+
+ +
+ + +
+
+

def minimum(

self)

+
+ + + + +

Calls BST._minimum_r(self.root) if self.root is evaluated to True.

+

Time Complexity: O(h).

+
+ +
+
def minimum(self):
+    """Calls `BST._minimum_r(self.root)` if `self.root` is evaluated to `True`.
+    **Time Complexity**: O(h)."""
+    if self.root:
+        return BST._minimum_r(self.root)
+
+
+
+ +
+ + +
+
+

def post_order_traversal(

self)

+
+ + + + +

Prints the keys of this tree in post-order. +It does the opposite of pre_order_traversal.

+

Time Complexity: O(h).

+
+ +
+
def post_order_traversal(self):
+    """Prints the keys of this tree in post-order.
+    It does the opposite of `pre_order_traversal`.
+    **Time Complexity**: O(h)."""
+    self._post_order_traversal(self.root)
+    print("\n")
+
+
+
+ +
+ + +
+
+

def pre_order_traversal(

self)

+
+ + + + +

Prints the keys of this tree in pre-order. +The pre-order consists of recursively printing first a node u, +then its left child node and then its right child node.

+

Time Complexity: O(h).

+
+ +
+
def pre_order_traversal(self):
+    """Prints the keys of this tree in pre-order.
+    The pre-order consists of recursively printing first a node `u`,
+    then its left child node and then its right child node.
+    **Time Complexity**: O(h)."""
+    self._pre_order_traversal(self.root)
+    print("\n")
+
+
+
+ +
+ + +
+
+

def predecessor(

self, x)

+
+ + + + +

Finds the predecessor of the node x, +i.e. the greatest element smaller than x.

+

x can either be a reference to an actual BSTNode object, +or it can be a key of a supposed node in self.

+

Time Complexity: O(h).

+
+ +
+
def predecessor(self, x):
+    """Finds the predecessor of the node `x`,
+    i.e. the greatest element smaller than `x`.
+    `x` can either be a reference to an actual `BSTNode` object,
+    or it can be a key of a supposed node in self.
+    **Time Complexity**: O(h)."""
+    if not isinstance(x, BSTNode):
+        x = self.search_r(x)
+        if x is None:
+            raise LookupError("No node was found with key=x.")
+    if x.left:
+        return BST._maximum_r(x.left)
+    p = x.parent
+    while p and x == p.left:
+        x = p
+        p = x.parent
+    return p
+
+
+
+ +
+ + +
+
+

def rank(

self, key)

+
+ + + + +

Returns the number of keys strictly less than key.

+

Time Complexity: O(h).

+
+ +
+
def rank(self, key) -> int:
+    """Returns the number of keys strictly less than `key`.
+    **Time Complexity**: O(h)."""
+    if key is None:
+        raise ValueError("key cannot be None.")
+    if not self.search(key):
+        raise LookupError("key was not found.")
+    if self.root is None:
+        return 0
+    else:
+        r = 0
+        return self._rank(self.root, key, r)
+
+
+
+ +
+ + +
+
+

def remove_max(

self)

+
+ + + + +

Removes and returns the maximum element of the tree, if it is not empty.

+

Time Complexity: O(h).

+
+ +
+
def remove_max(self):
+    """Removes and returns the maximum element of the tree, if it is not empty.
+    **Time Complexity**: O(h)."""
+    def _remove_max(u: BSTNode):
+        """Removes the maximum element of the subtree rooted at `u`.
+        Note that the maximum element is all the way to the right,
+        and it cannot have a right child,
+        but it can still have a left subtree.
+        If `u` is `None`, exceptions will be thrown."""
+        m = BST._maximum_r(u)
+        if m.left:  # m has a left subtree.
+            if self.is_root(m):  # m is the root.
+                self.root = m.left
+                m.left.parent = None  # self.root.parent = None
+            else:  # m is NOT the root.
+                m.left.parent = m.parent
+                m.parent.right = m.left
+        else:  # m has NO children
+            if self.is_root(m):
+                self.root = None
+            else:
+                m.parent.right = None
+        m.parent = m.left = None
+        self.n -= 1
+        return m
+    if self.n > 0:
+        return _remove_max(self.root)
+
+
+
+ +
+ + +
+
+

def remove_min(

self)

+
+ + + + +

Removes and returns the minimum element of the tree, if it is not empty.

+

Time Complexity: O(h).

+
+ +
+
def remove_min(self):
+    """Removes and returns the minimum element of the tree, if it is not empty.
+    **Time Complexity**: O(h)."""
+    def _remove_min(u: BSTNode):
+        """Removes and returns the minimum element of the subtree rooted at `u`.
+        If `u` is `None`, exceptions will be thrown."""
+        m = BST._minimum_r(u)
+        if m.right:
+            if self.is_root(m):
+                self.root = m.right
+                m.right.parent = None
+            else:
+                m.right.parent = m.parent
+                m.parent.left = m.right
+        else:  # m has not right subtree.
+            if self.is_root(m):
+                self.root = None
+            else:  # m is an internal node with no right subtree.
+                m.parent.left = None
+        m.right = m.parent = None
+        self.n -= 1
+        return m
+    if self.n > 0:
+        return _remove_min(self.root)
+
+
+
+ +
+ + +
+
+

def reverse_in_order_traversal(

self)

+
+ + + + +

Prints the keys of this tree in decreasing order.

+

It does the opposite of self.in_order_traversal.

+

Time Complexity: O(h).

+
+ +
+
def reverse_in_order_traversal(self):
+    """Prints the keys of this tree in decreasing order.
+    It does the opposite of `self.in_order_traversal`.
+    **Time Complexity**: O(h)."""
+    self._reverse_in_order_traversal(self.root)
+    print("\n")
+
+
+
+ +
+ + +
+
+

def right_rotate(

self, x)

+
+ + + + +

Right rotates the subtree rooted at node x. +See doc-strings of self.left_rotate.

+

Time Complexity: O(1).

+
+ +
+
def right_rotate(self, x):
+    """Right rotates the subtree rooted at node `x`.
+    See doc-strings of `self.left_rotate`.
+    **Time Complexity**: O(1)."""
+    c = None
+    if not isinstance(x, BSTNode):
+        c = self.search(x)
+        if c is None:
+            raise LookupError("key node was not found in the tree.")
+    else:
+        c = x
+    if c.left is None:
+        raise ValueError("Right rotation cannot be performed on " + str(c) +
+                         " because it does not have a left child.")
+    c.left.parent = c.parent
+    if c.parent is None:
+        self.root = c.left
+    elif c.is_left_child():
+        c.parent.left = c.left
+    else:
+        c.parent.right = c.left
+    c.parent = c.left
+    c.left = c.parent.right
+    if c.left is not None:
+        c.left.parent = c
+    c.parent.right = c
+    return c.parent
+
+
+
+ +
+ + +
+
+

def search(

self, key, s=None)

+
+ + + + +

Searches for the key in the tree. +If s is specified, then this procedure starts searching from s.

+

key must be a comparable object of the same type as the other keys.

+

Time Complexity: O(h).

+
+ +
+
def search(self, key, s: BSTNode = None) -> BSTNode:
+    """Searches for the key in the tree.
+    If `s` is specified, then this procedure starts searching from `s`.
+    `key` must be a comparable object of the same type as the other keys.
+    **Time Complexity**: O(h)."""
+    if key is None:
+        raise ValueError("key cannot be None.")
+    if s is None:
+        return self.search_i(key)
+    else:
+        return BST._search_i(key, s)
+
+
+
+ +
+ + +
+
+

def search_i(

self, key)

+
+ + + + +

Searches iteratively for key starting from the root.

+

Time Complexity: O(h).

+
+ +
+
def search_i(self, key) -> BSTNode:
+    """Searches iteratively for key starting from the root.
+    **Time Complexity**: O(h)."""
+    return BST._search_i(key, self.root)
+
+
+
+ +
+ + +
+
+

def search_r(

self, key)

+
+ + + + +

Searches recursively for key starting from self.root.

+

Time Complexity: O(h).

+
+ +
+
def search_r(self, key) -> BSTNode:
+    """Searches recursively for `key` starting from `self.root`.
+    **Time Complexity**: O(h)."""
+    return self._search_r(key, self.root)
+
+
+
+ +
+ + +
+
+

def show(

self)

+
+ + + + +

Pretty-prints this tree using print.

+
+ +
+
def show(self):
+    """Pretty-prints this tree using `print`."""
+    print(self)
+
+
+
+ +
+ + +
+
+

def size(

self)

+
+ + + + +

Returns the total number of nodes.

+

Time Complexity: O(1).

+
+ +
+
def size(self):
+    """Returns the total number of nodes.
+    **Time Complexity**: O(1)."""
+    return self.n
+
+
+
+ +
+ + +
+
+

def successor(

self, x)

+
+ + + + +

Finds the successor of x, +i.e. the smallest element greater than x.

+

If x has a right subtree, +then the successor of x is the minimum of that right subtree.

+

Otherwise it is the first ancestor of x, lets call it A, +such that x falls in the left subtree of A.

+

x can either be a reference to an actual BSTNode object, +or it can be a key of a supposed node in self.

+

Time Complexity: O(h).

+
+ +
+
def successor(self, x):
+    """Finds the successor of `x`,
+    i.e. the smallest element greater than `x`.
+    If `x` has a right subtree,
+    then the successor of `x` is the minimum of that right subtree.
+    Otherwise it is the first ancestor of `x`, lets call it `A`,
+    such that `x` falls in the left subtree of `A`.
+    `x` can either be a reference to an actual `BSTNode` object,
+    or it can be a key of a supposed node in self.
+    **Time Complexity**: O(h)."""
+    if not isinstance(x, BSTNode):
+        x = self.search(x)
+        if x is None:
+            raise LookupError("No node was found with key=x.")
+    if x.right:
+        return BST._minimum_r(x.right)
+    p = x.parent
+    while p and p.right == x:
+        x = p
+        p = x.parent
+    return p
+
+
+
+ +
+ +

Instance variables

+
+

var n

+ + + + +
+
+ +
+
+

var name

+ + + + +
+
+ +
+
+

var root

+ + + + +
+
+ +
+
+
+ +
+

class BSTNode

+ + +

Class to represent a BST's node.

+
+ +
+
class BSTNode:
+    """Class to represent a BST's node."""
+
+    def __init__(self, key, value=None, parent=None, left=None, right=None):
+        if key is None:
+            raise ValueError("key cannot be None")
+        self.key = key
+        self.value = value
+        self.parent = parent
+        self.left = left
+        self.right = right
+        # Used for printing purposes.
+        self.label = "[" + str(self.key) + "]"
+
+    @property
+    def sibling(self):
+        """Returns the sibling node of this node,
+        which can of course be `None`."""
+        if self.parent is not None:
+            if self.is_left_child():
+                return self.parent.right
+            else:
+                return self.parent.left
+
+    @property
+    def grandparent(self):
+        """Returns the parent of the parent of this node."""
+        if self.parent is not None:
+            return self.parent.parent
+
+    @property
+    def uncle(self):
+        """Returns the uncle node of this node.
+        The uncle is the sibling of the parent of this node,
+        if it exists. `None` is returned if it doesn't exist,
+        or the parent or grandparent of this node is `None`."""
+        if self.grandparent is not None:  # implies that also parent is not None
+            if self.parent == self.grandparent.left:
+                return self.grandparent.right
+            else:  # self.parent == self.grandparent.right:
+                return self.grandparent.left
+
+    def reset(self):
+        self.parent = None
+        self.left = None
+        self.right = None
+
+    def is_left_child(self) -> bool:
+        if self.parent is not None:
+            if self.parent.left is not None:
+                return self.parent.left == self
+        else:
+            raise AttributeError("self does not have a parent.")
+
+    def is_right_child(self) -> bool:
+        if self.parent is not None:
+            if self.parent.right is not None:
+                return self.parent.right == self
+        else:
+            raise AttributeError("self does not have a parent.")
+
+    def has_children(self) -> bool:
+        """Returns `True` if `self` has at least one child. `False` otherwise."""
+        return self.left or self.right
+
+    def has_one_child(self) -> bool:
+        """Returns `True` only if `self` has exactly one child. `False` otherwise."""
+        return (self.left and not self.right) or (not self.left and self.right)
+
+    def has_two_children(self) -> bool:
+        """Returns `True` if self has exactly two children. `False` otherwise."""
+        return self.left and self.right
+
+    def count(self) -> int:
+        """Counts the numbers of nodes under `self` (including `self`)."""
+
+        def _count(u, c: int):
+            if u is None:
+                return c
+            else:
+                c += 1
+            c = _count(u.left, c)
+            c = _count(u.right, c)
+            return c
+
+        if not self.has_children():
+            return 1
+        else:
+            c = 0
+            return _count(self, c)
+
+    def __str__(self):
+        return "{" + str(self.key) + ": " + str(self.value) + "}"
+
+    def __fields(self):
+        return [["Node (Key)", self.key],
+                ["Value", self.value],
+                ["Parent", self.parent],
+                ["Left child", self.left],
+                ["Right child", self.right],
+                ["Sibling", self.sibling],
+                ["Grandparent", self.grandparent],
+                ["Uncle", self.uncle]]
+
+    def __repr__(self):
+        return tabulate(self.__fields(), tablefmt="fancy_grid")
+
+    def show(self):
+        print(self.__repr__())
+
+
+
+ + +
+

Ancestors (in MRO)

+ +

Static methods

+ +
+
+

def __init__(

self, key, value=None, parent=None, left=None, right=None)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, key, value=None, parent=None, left=None, right=None):
+    if key is None:
+        raise ValueError("key cannot be None")
+    self.key = key
+    self.value = value
+    self.parent = parent
+    self.left = left
+    self.right = right
+    # Used for printing purposes.
+    self.label = "[" + str(self.key) + "]"
+
+
+
+ +
+ + +
+
+

def count(

self)

+
+ + + + +

Counts the numbers of nodes under self (including self).

+
+ +
+
def count(self) -> int:
+    """Counts the numbers of nodes under `self` (including `self`)."""
+    def _count(u, c: int):
+        if u is None:
+            return c
+        else:
+            c += 1
+        c = _count(u.left, c)
+        c = _count(u.right, c)
+        return c
+    if not self.has_children():
+        return 1
+    else:
+        c = 0
+        return _count(self, c)
+
+
+
+ +
+ + +
+
+

def has_children(

self)

+
+ + + + +

Returns True if self has at least one child. False otherwise.

+
+ +
+
def has_children(self) -> bool:
+    """Returns `True` if `self` has at least one child. `False` otherwise."""
+    return self.left or self.right
+
+
+
+ +
+ + +
+
+

def has_one_child(

self)

+
+ + + + +

Returns True only if self has exactly one child. False otherwise.

+
+ +
+
def has_one_child(self) -> bool:
+    """Returns `True` only if `self` has exactly one child. `False` otherwise."""
+    return (self.left and not self.right) or (not self.left and self.right)
+
+
+
+ +
+ + +
+
+

def has_two_children(

self)

+
+ + + + +

Returns True if self has exactly two children. False otherwise.

+
+ +
+
def has_two_children(self) -> bool:
+    """Returns `True` if self has exactly two children. `False` otherwise."""
+    return self.left and self.right
+
+
+
+ +
+ + +
+
+

def is_left_child(

self)

+
+ + + + +
+ +
+
def is_left_child(self) -> bool:
+    if self.parent is not None:
+        if self.parent.left is not None:
+            return self.parent.left == self
+    else:
+        raise AttributeError("self does not have a parent.")
+
+
+
+ +
+ + +
+
+

def is_right_child(

self)

+
+ + + + +
+ +
+
def is_right_child(self) -> bool:
+    if self.parent is not None:
+        if self.parent.right is not None:
+            return self.parent.right == self
+    else:
+        raise AttributeError("self does not have a parent.")
+
+
+
+ +
+ + +
+
+

def reset(

self)

+
+ + + + +
+ +
+
def reset(self):
+    self.parent = None
+    self.left = None
+    self.right = None
+
+
+
+ +
+ + +
+
+

def show(

self)

+
+ + + + +
+ +
+
def show(self):
+    print(self.__repr__())
+
+
+
+ +
+ +

Instance variables

+
+

var grandparent

+ + + + +

Returns the parent of the parent of this node.

+
+
+ +
+
+

var key

+ + + + +
+
+ +
+
+

var label

+ + + + +
+
+ +
+
+

var left

+ + + + +
+
+ +
+
+

var parent

+ + + + +
+
+ +
+
+

var right

+ + + + +
+
+ +
+
+

var sibling

+ + + + +

Returns the sibling node of this node, +which can of course be None.

+
+
+ +
+
+

var uncle

+ + + + +

Returns the uncle node of this node. +The uncle is the sibling of the parent of this node, +if it exists. None is returned if it doesn't exist, +or the parent or grandparent of this node is None.

+
+
+ +
+
+

var value

+ + + + +
+
+ +
+
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/DSForests.m.html b/docs/ands/ds/DSForests.m.html new file mode 100644 index 00000000..7009786b --- /dev/null +++ b/docs/ands/ds/DSForests.m.html @@ -0,0 +1,1945 @@ + + + + + + ands.ds.DSForests API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.DSForests module

+

Meta info

+

Author: Nelson Brochado

+

Created: 21/02/2016

+

Updated: 03/01/2016

+

Description

+

A disjoint-set (forests) or union-find data structure is a data structure which keeps track of a set of elements +partitioned into disjoint (non-overlapping, i.e. their intersection is the empty set) sets. +The usual operations supported by this data structure are:

+
    +
  1. +

    make-set(x): creates a single-element set containing x, and x is the representative of that set.

    +
  2. +
  3. +

    find(x): returns the "representative" of the set where the element x is. + If the data structure is implemented a tree, the representative is the root of the tree.

    +
  4. +
  5. +

    union(x, y): unions the sets where x and y are (if they do not belong already to the same set).

    +
  6. +
+

DSForests uses two heuristics that improve the performance with respect to a naive implementation.

+
    +
  1. +

    Union by rank: attach the smaller tree to the root of the larger tree

    +
  2. +
  3. +

    Path compression: is a way of flattening the structure of the tree whenever find is used on it.

    +
  4. +
+

These two techniques complement each other: applied together, the amortized time per operation is only O( α (n)).

+

TODO

+
    +
  • Deletion operation (OPTIONAL, since it's usually not part of the interface of a disjoint-set data structure)
  • +
  • Pretty-print(x), for some element x in the disjoint-set data structure.
  • +
  • Implement the version explained here
  • +
+

References

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+# Meta info
+
+Author: Nelson Brochado
+
+Created: 21/02/2016
+
+Updated: 03/01/2016
+
+# Description
+
+A disjoint-set (forests) or union-find data structure is a data structure which keeps track of a set of elements
+partitioned into disjoint (non-overlapping, i.e. their intersection is the empty set) sets.
+The usual operations supported by this data structure are:
+
+  1. make-set(x): creates a single-element set containing x, and x is the representative of that set.
+
+  2. find(x): returns the "representative" of the set where the element x is.
+    If the data structure is implemented a tree, the representative is the root of the tree.
+
+  3. union(x, y): unions the sets where x and y are (if they do not belong already to the same set).
+
+`DSForests` uses two heuristics that improve the performance with respect to a naive implementation.
+
+  1. Union by rank: attach the smaller tree to the root of the larger tree
+
+  2. Path compression: is a way of flattening the structure of the tree whenever find is used on it.
+
+These two techniques complement each other: applied together, the amortized time per operation is only O( α (n)).
+
+# TODO
+
+- Deletion operation (OPTIONAL, since it's usually not part of the interface of a disjoint-set data structure)
+- Pretty-print(x), for some element x in the disjoint-set data structure.
+- Implement the version explained [here](http://algs4.cs.princeton.edu/15uf/)
+
+# References
+
+- Introduction to algorithms, 3rd, by C.L.R.S., chapter 21.3
+- [https://en.wikipedia.org/wiki/Disjoint-set_data_structure](https://en.wikipedia.org/wiki/Disjoint-set_data_structure)
+- [http://orionsword.no-ip.org/blog/wordpress/?p=246](http://orionsword.no-ip.org/blog/wordpress/?p=246)
+- [http://stackoverflow.com/a/22945492/3924118](http://stackoverflow.com/a/22945492/3924118)
+- [http://stackoverflow.com/q/23055236/3924118](http://stackoverflow.com/q/23055236/3924118)
+- [https://www.cs.usfca.edu/~galles/JavascriptVisual/DisjointSets.html](https://www.cs.usfca.edu/~galles/JavascriptVisual/DisjointSets.html)
+to visualize how disjoint-sets work.
+
+"""
+
+
+class DSNode:
+    def __init__(self, x, rank=0):
+        # This attribute can contain any hashable value.
+        self.value = x
+
+        # The rank of node x only changes in one specific union(x, y) case:
+        # when x is the representative of its set
+        # and the representative of the set where y resides has the same rank as x.
+        # In the DSForests implementation below, if a situation as just described occurs,
+        # then the x.rank is increased by 1.
+        self.rank = rank
+
+        # Reference to the representative of the set where this node resides
+        # Since DSForests actually implements a tree,
+        # self.parent is also the root of that tree.
+        self.parent = self
+
+        # Reference used to help printing all nodes
+        # belonging to the set to which this node belongs in O(m) time,
+        # where m is the size of the mentioned set.
+        self.next = self
+
+    def is_root(self):
+        """A DSNode x is a root or representative of a set
+        whenever its parent pointer points to himself.
+        Of course this is only true if x is already in a DSForests object."""
+        return self.parent == self
+
+    def __str__(self):
+        return str(self.value)
+
+    def __repr__(self):
+        if self.parent == self:
+            return "(value: {0}, rank: {1}, parent: self)".format(self.value, self.rank)
+        else:
+            return "(value: {0}, rank: {1}, parent: {2})".format(self.value, self.rank, self.parent)
+
+
+class DSForests:
+    def __init__(self):
+        # keeps tracks of the DSNodes in this disjoint-set forests.
+        self.sets = {}
+
+    def make_set(self, x) -> DSNode:
+        """Creates a set object for `x`."""
+        assert x not in self.sets
+        self.sets[x] = DSNode(x)
+        return self.sets[x]
+
+    def find(self, x: DSNode) -> DSNode:
+        """Finds and returns the representative (or root) of `x`.
+        It follows parent nodes until it reaches
+        the root of the tree (set) to which `x` belongs.
+
+        It also uses a technique called "path compression",
+        which is a way of flattening the structure of the tree.
+
+        The idea is that each node visited on the way to a root node
+        may as well be attached directly to the root node;
+        they all share the same representative.
+
+        To effect this, as `self.find` recursively traverses up the tree,
+        it changes each node's parent reference
+        to point to the root that it found.
+
+        The resulting tree is much flatter,
+        speeding up future operations not only on these elements
+        but on those referencing them, directly or indirectly.
+
+        This algorithm does not change any ranks of the `Set` objects.
+
+        **Time Complexity:** O*(α (n)),
+        where α (n) is the inverse of the function n = f(x) = A(x, x),
+        and A is the extremely fast-growing **Ackermann** function.
+        Since α (n) is the inverse of this function,
+        α (n) is less than 5 for all remotely practical values of n.
+        Thus, the amortized running time per operation
+        is effectively a small constant."""
+        assert x
+        if x.parent != x:
+            x.parent = self.find(x.parent)
+        return x.parent
+
+    def find_iteratively(self, x: DSNode) -> DSNode:
+        """This version is just an iterative alternative to the find method."""
+        assert x
+
+        y = x
+
+        # find the representative of the set where x resides
+        while y != y.parent:
+            y = y.parent
+
+        # post-condition
+        assert y == self.find(x)
+
+        # now y is the representative of x,
+        # but we also want to do a path compression,
+        # i.e. connect all nodes in the path from x to y directly to y.
+        while x != x.parent:
+            p = x.parent
+            x.parent = y
+            x = p
+
+        return y
+
+    def union(self, x, y) -> DSNode:
+        """"Union by rank" 2 trees (sets) into one by attaching
+        the root of one to the root of the other.
+        Returns the `DSNode` object representing the representative of
+        the set resulted from the union of the sets containing `x` and `y`.
+
+        "Union by rank" consists of attaching the smaller tree
+        to the root of the larger tree.
+
+        Since it is the depth of the tree that affects the running time,
+        the tree with smaller depth gets added
+        under the root of the deeper tree,
+        which only increases the depth if the depths were equal.
+
+        In the context of this algorithm,
+        the term _rank_ is used instead of depth,
+        since it stops being equal to the depth
+        if path compression is also used.
+
+        The rank is an upper bound on the height of the node.
+
+        One-element trees are defined to have a rank of zero,
+        and whenever two trees of the same rank `r` are united,
+        the rank of the result is `r + 1`.
+
+        **Time Complexity:** O*(α (n)),
+        where α (n) is the inverse of the function n = f(x) = A(x, x),
+        and A is the extremely fast-growing **Ackermann** function.
+        Since α (n) is the inverse of this function,
+        α (n) is less than 5 for all remotely practical values of n.
+        Thus, the amortized running time per operation
+        is effectively a small constant."""
+        assert x in self.sets and y in self.sets
+
+        # Since the original values x and y are not used afterwards,
+        # and what we actually need in two places of this algorithm are the corresponding DSNodes
+        # we set x and y to be respectively their DSNode counter-part.
+        x = self.sets[x]
+        y = self.sets[y]
+
+        x_root = self.find(x)
+        y_root = self.find(y)
+
+        # x and y are already joined.
+        if x_root == y_root:
+            return
+
+        # Exchanging the next pointers of x and y.
+        # This is needed in order to print the elements of a set in O(m) time,
+        # where m is the size of the same set.
+        # Check here: http://stackoverflow.com/a/22945492/3924118.
+        x.next, y.next = y.next, x.next
+
+        # x and y are not in the same set, therefore we merge them.
+        if x_root.rank < y_root.rank:
+            x_root.parent = y_root
+            return y_root
+        else:
+            y_root.parent = x_root
+            if x_root.rank == y_root.rank:
+                x_root.rank += 1
+            return x_root
+
+    def print_set(self, x) -> None:
+        assert x in self.sets
+
+        x = self.sets[x]
+        y = x
+
+        print("{0} -> {{{1}".format(x, x), end="")
+        while y.next != x:
+            print(",", y.next, end="")
+            y = y.next
+        print("}")
+
+    def __str__(self):
+        return str(self.sets)
+
+
+ +
+ +
+ + +

Classes

+ +
+

class DSForests

+ + +
+ +
+
class DSForests:
+    def __init__(self):
+        # keeps tracks of the DSNodes in this disjoint-set forests.
+        self.sets = {}
+
+    def make_set(self, x) -> DSNode:
+        """Creates a set object for `x`."""
+        assert x not in self.sets
+        self.sets[x] = DSNode(x)
+        return self.sets[x]
+
+    def find(self, x: DSNode) -> DSNode:
+        """Finds and returns the representative (or root) of `x`.
+        It follows parent nodes until it reaches
+        the root of the tree (set) to which `x` belongs.
+
+        It also uses a technique called "path compression",
+        which is a way of flattening the structure of the tree.
+
+        The idea is that each node visited on the way to a root node
+        may as well be attached directly to the root node;
+        they all share the same representative.
+
+        To effect this, as `self.find` recursively traverses up the tree,
+        it changes each node's parent reference
+        to point to the root that it found.
+
+        The resulting tree is much flatter,
+        speeding up future operations not only on these elements
+        but on those referencing them, directly or indirectly.
+
+        This algorithm does not change any ranks of the `Set` objects.
+
+        **Time Complexity:** O*(α (n)),
+        where α (n) is the inverse of the function n = f(x) = A(x, x),
+        and A is the extremely fast-growing **Ackermann** function.
+        Since α (n) is the inverse of this function,
+        α (n) is less than 5 for all remotely practical values of n.
+        Thus, the amortized running time per operation
+        is effectively a small constant."""
+        assert x
+        if x.parent != x:
+            x.parent = self.find(x.parent)
+        return x.parent
+
+    def find_iteratively(self, x: DSNode) -> DSNode:
+        """This version is just an iterative alternative to the find method."""
+        assert x
+
+        y = x
+
+        # find the representative of the set where x resides
+        while y != y.parent:
+            y = y.parent
+
+        # post-condition
+        assert y == self.find(x)
+
+        # now y is the representative of x,
+        # but we also want to do a path compression,
+        # i.e. connect all nodes in the path from x to y directly to y.
+        while x != x.parent:
+            p = x.parent
+            x.parent = y
+            x = p
+
+        return y
+
+    def union(self, x, y) -> DSNode:
+        """"Union by rank" 2 trees (sets) into one by attaching
+        the root of one to the root of the other.
+        Returns the `DSNode` object representing the representative of
+        the set resulted from the union of the sets containing `x` and `y`.
+
+        "Union by rank" consists of attaching the smaller tree
+        to the root of the larger tree.
+
+        Since it is the depth of the tree that affects the running time,
+        the tree with smaller depth gets added
+        under the root of the deeper tree,
+        which only increases the depth if the depths were equal.
+
+        In the context of this algorithm,
+        the term _rank_ is used instead of depth,
+        since it stops being equal to the depth
+        if path compression is also used.
+
+        The rank is an upper bound on the height of the node.
+
+        One-element trees are defined to have a rank of zero,
+        and whenever two trees of the same rank `r` are united,
+        the rank of the result is `r + 1`.
+
+        **Time Complexity:** O*(α (n)),
+        where α (n) is the inverse of the function n = f(x) = A(x, x),
+        and A is the extremely fast-growing **Ackermann** function.
+        Since α (n) is the inverse of this function,
+        α (n) is less than 5 for all remotely practical values of n.
+        Thus, the amortized running time per operation
+        is effectively a small constant."""
+        assert x in self.sets and y in self.sets
+
+        # Since the original values x and y are not used afterwards,
+        # and what we actually need in two places of this algorithm are the corresponding DSNodes
+        # we set x and y to be respectively their DSNode counter-part.
+        x = self.sets[x]
+        y = self.sets[y]
+
+        x_root = self.find(x)
+        y_root = self.find(y)
+
+        # x and y are already joined.
+        if x_root == y_root:
+            return
+
+        # Exchanging the next pointers of x and y.
+        # This is needed in order to print the elements of a set in O(m) time,
+        # where m is the size of the same set.
+        # Check here: http://stackoverflow.com/a/22945492/3924118.
+        x.next, y.next = y.next, x.next
+
+        # x and y are not in the same set, therefore we merge them.
+        if x_root.rank < y_root.rank:
+            x_root.parent = y_root
+            return y_root
+        else:
+            y_root.parent = x_root
+            if x_root.rank == y_root.rank:
+                x_root.rank += 1
+            return x_root
+
+    def print_set(self, x) -> None:
+        assert x in self.sets
+
+        x = self.sets[x]
+        y = x
+
+        print("{0} -> {{{1}".format(x, x), end="")
+        while y.next != x:
+            print(",", y.next, end="")
+            y = y.next
+        print("}")
+
+    def __str__(self):
+        return str(self.sets)
+
+
+
+ + +
+

Ancestors (in MRO)

+ +

Static methods

+ +
+
+

def __init__(

self)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self):
+    # keeps tracks of the DSNodes in this disjoint-set forests.
+    self.sets = {}
+
+
+
+ +
+ + +
+
+

def find(

self, x)

+
+ + + + +

Finds and returns the representative (or root) of x. +It follows parent nodes until it reaches +the root of the tree (set) to which x belongs.

+

It also uses a technique called "path compression", +which is a way of flattening the structure of the tree.

+

The idea is that each node visited on the way to a root node +may as well be attached directly to the root node; +they all share the same representative.

+

To effect this, as self.find recursively traverses up the tree, +it changes each node's parent reference +to point to the root that it found.

+

The resulting tree is much flatter, +speeding up future operations not only on these elements +but on those referencing them, directly or indirectly.

+

This algorithm does not change any ranks of the Set objects.

+

Time Complexity: O*(α (n)), +where α (n) is the inverse of the function n = f(x) = A(x, x), +and A is the extremely fast-growing Ackermann function. +Since α (n) is the inverse of this function, +α (n) is less than 5 for all remotely practical values of n. +Thus, the amortized running time per operation +is effectively a small constant.

+
+ +
+
def find(self, x: DSNode) -> DSNode:
+    """Finds and returns the representative (or root) of `x`.
+    It follows parent nodes until it reaches
+    the root of the tree (set) to which `x` belongs.
+    It also uses a technique called "path compression",
+    which is a way of flattening the structure of the tree.
+    The idea is that each node visited on the way to a root node
+    may as well be attached directly to the root node;
+    they all share the same representative.
+    To effect this, as `self.find` recursively traverses up the tree,
+    it changes each node's parent reference
+    to point to the root that it found.
+    The resulting tree is much flatter,
+    speeding up future operations not only on these elements
+    but on those referencing them, directly or indirectly.
+    This algorithm does not change any ranks of the `Set` objects.
+    **Time Complexity:** O*(α (n)),
+    where α (n) is the inverse of the function n = f(x) = A(x, x),
+    and A is the extremely fast-growing **Ackermann** function.
+    Since α (n) is the inverse of this function,
+    α (n) is less than 5 for all remotely practical values of n.
+    Thus, the amortized running time per operation
+    is effectively a small constant."""
+    assert x
+    if x.parent != x:
+        x.parent = self.find(x.parent)
+    return x.parent
+
+
+
+ +
+ + +
+
+

def find_iteratively(

self, x)

+
+ + + + +

This version is just an iterative alternative to the find method.

+
+ +
+
def find_iteratively(self, x: DSNode) -> DSNode:
+    """This version is just an iterative alternative to the find method."""
+    assert x
+    y = x
+    # find the representative of the set where x resides
+    while y != y.parent:
+        y = y.parent
+    # post-condition
+    assert y == self.find(x)
+    # now y is the representative of x,
+    # but we also want to do a path compression,
+    # i.e. connect all nodes in the path from x to y directly to y.
+    while x != x.parent:
+        p = x.parent
+        x.parent = y
+        x = p
+    return y
+
+
+
+ +
+ + +
+
+

def make_set(

self, x)

+
+ + + + +

Creates a set object for x.

+
+ +
+
def make_set(self, x) -> DSNode:
+    """Creates a set object for `x`."""
+    assert x not in self.sets
+    self.sets[x] = DSNode(x)
+    return self.sets[x]
+
+
+
+ +
+ + +
+
+

def print_set(

self, x)

+
+ + + + +
+ +
+
def print_set(self, x) -> None:
+    assert x in self.sets
+    x = self.sets[x]
+    y = x
+    print("{0} -> {{{1}".format(x, x), end="")
+    while y.next != x:
+        print(",", y.next, end="")
+        y = y.next
+    print("}")
+
+
+
+ +
+ + +
+
+

def union(

self, x, y)

+
+ + + + +

"Union by rank" 2 trees (sets) into one by attaching +the root of one to the root of the other. +Returns the DSNode object representing the representative of +the set resulted from the union of the sets containing x and y.

+

"Union by rank" consists of attaching the smaller tree +to the root of the larger tree.

+

Since it is the depth of the tree that affects the running time, +the tree with smaller depth gets added +under the root of the deeper tree, +which only increases the depth if the depths were equal.

+

In the context of this algorithm, +the term rank is used instead of depth, +since it stops being equal to the depth +if path compression is also used.

+

The rank is an upper bound on the height of the node.

+

One-element trees are defined to have a rank of zero, +and whenever two trees of the same rank r are united, +the rank of the result is r + 1.

+

Time Complexity: O*(α (n)), +where α (n) is the inverse of the function n = f(x) = A(x, x), +and A is the extremely fast-growing Ackermann function. +Since α (n) is the inverse of this function, +α (n) is less than 5 for all remotely practical values of n. +Thus, the amortized running time per operation +is effectively a small constant.

+
+ +
+
def union(self, x, y) -> DSNode:
+    """"Union by rank" 2 trees (sets) into one by attaching
+    the root of one to the root of the other.
+    Returns the `DSNode` object representing the representative of
+    the set resulted from the union of the sets containing `x` and `y`.
+    "Union by rank" consists of attaching the smaller tree
+    to the root of the larger tree.
+    Since it is the depth of the tree that affects the running time,
+    the tree with smaller depth gets added
+    under the root of the deeper tree,
+    which only increases the depth if the depths were equal.
+    In the context of this algorithm,
+    the term _rank_ is used instead of depth,
+    since it stops being equal to the depth
+    if path compression is also used.
+    The rank is an upper bound on the height of the node.
+    One-element trees are defined to have a rank of zero,
+    and whenever two trees of the same rank `r` are united,
+    the rank of the result is `r + 1`.
+    **Time Complexity:** O*(α (n)),
+    where α (n) is the inverse of the function n = f(x) = A(x, x),
+    and A is the extremely fast-growing **Ackermann** function.
+    Since α (n) is the inverse of this function,
+    α (n) is less than 5 for all remotely practical values of n.
+    Thus, the amortized running time per operation
+    is effectively a small constant."""
+    assert x in self.sets and y in self.sets
+    # Since the original values x and y are not used afterwards,
+    # and what we actually need in two places of this algorithm are the corresponding DSNodes
+    # we set x and y to be respectively their DSNode counter-part.
+    x = self.sets[x]
+    y = self.sets[y]
+    x_root = self.find(x)
+    y_root = self.find(y)
+    # x and y are already joined.
+    if x_root == y_root:
+        return
+    # Exchanging the next pointers of x and y.
+    # This is needed in order to print the elements of a set in O(m) time,
+    # where m is the size of the same set.
+    # Check here: http://stackoverflow.com/a/22945492/3924118.
+    x.next, y.next = y.next, x.next
+    # x and y are not in the same set, therefore we merge them.
+    if x_root.rank < y_root.rank:
+        x_root.parent = y_root
+        return y_root
+    else:
+        y_root.parent = x_root
+        if x_root.rank == y_root.rank:
+            x_root.rank += 1
+        return x_root
+
+
+
+ +
+ +

Instance variables

+
+

var sets

+ + + + +
+
+ +
+
+
+ +
+

class DSNode

+ + +
+ +
+
class DSNode:
+    def __init__(self, x, rank=0):
+        # This attribute can contain any hashable value.
+        self.value = x
+
+        # The rank of node x only changes in one specific union(x, y) case:
+        # when x is the representative of its set
+        # and the representative of the set where y resides has the same rank as x.
+        # In the DSForests implementation below, if a situation as just described occurs,
+        # then the x.rank is increased by 1.
+        self.rank = rank
+
+        # Reference to the representative of the set where this node resides
+        # Since DSForests actually implements a tree,
+        # self.parent is also the root of that tree.
+        self.parent = self
+
+        # Reference used to help printing all nodes
+        # belonging to the set to which this node belongs in O(m) time,
+        # where m is the size of the mentioned set.
+        self.next = self
+
+    def is_root(self):
+        """A DSNode x is a root or representative of a set
+        whenever its parent pointer points to himself.
+        Of course this is only true if x is already in a DSForests object."""
+        return self.parent == self
+
+    def __str__(self):
+        return str(self.value)
+
+    def __repr__(self):
+        if self.parent == self:
+            return "(value: {0}, rank: {1}, parent: self)".format(self.value, self.rank)
+        else:
+            return "(value: {0}, rank: {1}, parent: {2})".format(self.value, self.rank, self.parent)
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • DSNode
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, x, rank=0)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, x, rank=0):
+    # This attribute can contain any hashable value.
+    self.value = x
+    # The rank of node x only changes in one specific union(x, y) case:
+    # when x is the representative of its set
+    # and the representative of the set where y resides has the same rank as x.
+    # In the DSForests implementation below, if a situation as just described occurs,
+    # then the x.rank is increased by 1.
+    self.rank = rank
+    # Reference to the representative of the set where this node resides
+    # Since DSForests actually implements a tree,
+    # self.parent is also the root of that tree.
+    self.parent = self
+    # Reference used to help printing all nodes
+    # belonging to the set to which this node belongs in O(m) time,
+    # where m is the size of the mentioned set.
+    self.next = self
+
+
+
+ +
+ + +
+
+

def is_root(

self)

+
+ + + + +

A DSNode x is a root or representative of a set +whenever its parent pointer points to himself. +Of course this is only true if x is already in a DSForests object.

+
+ +
+
def is_root(self):
+    """A DSNode x is a root or representative of a set
+    whenever its parent pointer points to himself.
+    Of course this is only true if x is already in a DSForests object."""
+    return self.parent == self
+
+
+
+ +
+ +

Instance variables

+
+

var next

+ + + + +
+
+ +
+
+

var parent

+ + + + +
+
+ +
+
+

var rank

+ + + + +
+
+ +
+
+

var value

+ + + + +
+
+ +
+
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/Graph.m.html b/docs/ands/ds/Graph.m.html new file mode 100644 index 00000000..a26121e6 --- /dev/null +++ b/docs/ands/ds/Graph.m.html @@ -0,0 +1,2766 @@ + + + + + + ands.ds.Graph API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.Graph module

+

Author: Nelson Brochado

+

Creation: July, 2015

+

Last update: 01/09/16

+

Graph data structure using adjacency list representation.

+

You can represent a undirected graph by adding +each endpoint (node) of an edge to the adjacency list of the other endpoint. +For example, suppose we have node A and B. +To create an undirected graph composed of A and B, you need to do:

+
1. `A.add_adjacent_node(B)`
+
+2. `B.add_adjacent_node(A)`
+
+

You can also call add_undirected_edge(A, B) directly, +which does the work described above for you.

+

To create directed graphs, you can simply add the pointed node +to the adjacency list of the starting node. +You could also call add_directed_edge (see its docstrings).

+

This graph also contains some "utility" functions (reset_nodes) +that can be used in those same algorithms.

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+Author: Nelson Brochado
+
+Creation: July, 2015
+
+Last update: 01/09/16
+
+
+Graph data structure using adjacency list representation.
+
+You can represent a undirected graph by adding
+each endpoint (node) of an edge to the adjacency list of the other endpoint.
+For example, suppose we have node A and B.
+To create an undirected graph composed of A and B, you need to do:
+
+    1. `A.add_adjacent_node(B)`
+
+    2. `B.add_adjacent_node(A)`
+
+You can also call `add_undirected_edge(A, B)` directly,
+which does the work described above for you.
+
+To create directed graphs, you can simply add the pointed node
+to the adjacency list of the starting node.
+You could also call `add_directed_edge` (see its docstrings).
+
+This graph also contains some "utility" functions (reset_nodes)
+that can be used in those same algorithms.
+"""
+
+import operator
+import sys
+
+from tabulate import tabulate
+
+__all__ = ["Graph", "GraphNode", "WHITE", "BLACK", "GREY", "INFINITY", "NIL"]
+
+WHITE = "WHITE"
+BLACK = "BLACK"
+GREY = "GREY"
+INFINITY = sys.maxsize
+NIL = None
+
+
+class GraphNode:
+    """GraphNode for the `Graph` data structure defined in `Graph.py`.
+    Nodes contain many fields that are used in the 2 main graph algorithms:
+    - `bfs`
+    - `dfs`
+    These fields are for example `self.predecessor`, `self.distance`, etc.
+
+    Nodes keep also track of the edges of which they are the starting point.
+    These edges are represented as a list of 3 items:
+
+        edge = [self, pointed_node, edge_weight]
+
+    where:
+    - `self` is the reference to the node itself,
+    - `pointed_node` is the ending node of the edge
+    - `edge_weigh` is the eventual weight of the edge."""
+
+    def __init__(self, key, value=None):
+        self.key = key
+        self.value = value
+        # Attributes used mostly in algorithms for graphs, such as in bfs or dfs.
+        self.predecessor = NIL
+        self.distance = INFINITY
+        self.color = WHITE  # To keep track if a node has been or not visited yet.
+        self.adjacent_nodes = []
+        self.outgoing_edges = {}  # [(self, other), w]
+
+        self.cc = NIL
+        """This variable can be used to keep track of which
+        connected component this node in the graph belongs to.
+
+        For example, we could name the possible different
+        connected components using simply numbers:
+        the first connected component is 1, the second is 2,
+        the third is 3, and so on.
+
+        When searching for connected components,
+        we can use this variable to associate this node
+        with a connected component:
+        if we set it to 1, that would mean that this node
+        belongs to the connected component 1."""
+
+        # These variables are useful for dfs
+        # to keep track when a node is first visited
+        # and when we finish visiting it,
+        # and we start backtracking.
+        self.start = INFINITY
+        self.end = INFINITY
+
+        self.in_degree = 0
+        self.out_degree = 0
+
+    def get_adjacent_nodes(self) -> list:
+        """Returns the adjacent nodes to self."""
+        return self.adjacent_nodes
+
+    def get_outgoing_edges(self) -> dict:
+        """Returns a dictionary containing the edges outgoing from this node.
+        Edges are of the form (self, other): weight."""
+        return self.outgoing_edges
+
+    def add_adjacent_node(self, u, weight=0):
+        """Adds u as adjacent node to self.
+
+        Creates a directed edge from self to u."""
+        self.adjacent_nodes.append(u)
+        self.outgoing_edges[(self, u)] = weight
+        self.out_degree += 1
+        u.in_degree += 1
+
+    def remove_adjacent_node(self, u):
+        """Removes u's reference from the adjacent_nodes list of self.
+
+        It also removes the directed edge from self to u."""
+        self.adjacent_nodes.remove(u)
+        w = self.outgoing_edges.pop((self, u))
+        self.out_degree -= 1
+        u.in_degree -= 1
+        return w
+
+    def reset(self, clear_nodes=False):
+        """Make self assume the default attribute values.
+
+        If clear_nodes=True, then all connections with other nodes are removed.
+        """
+        self.color = WHITE
+        self.predecessor = NIL
+        self.distance = INFINITY
+        self.cc = NIL
+        self.start = INFINITY
+        self.end = INFINITY
+        if clear_nodes:
+            self.clear_adjacent_nodes()
+
+    def clear_adjacent_nodes(self):
+        self._fix_degrees_before_clearing()
+        self.adjacent_nodes.clear()
+        self.outgoing_edges.clear()
+
+    def _fix_degrees_before_clearing(self):
+        for n in self.adjacent_nodes:
+            n.in_degree -= 1
+        self.out_degree = 0
+
+    def get_str_repr_of_adj_nodes(self):
+        """Returns a string representing all the adjacent total_nodes."""
+        str_repr = "["
+        for node in self.get_adjacent_nodes():
+            str_repr += str(node.key) + ", "
+        return "[]" if not str_repr[:-2] else str_repr[:-2] + "]"
+
+    def _get_list_of_attributes(self):
+        a = [["Name", self.key],
+             ["Adjacent nodes", self.get_str_repr_of_adj_nodes()],
+             ["Color", self.color],
+             ["Distance", self.distance],
+             ["Connected component", self.cc],
+             ["Starting visit time", self.start],
+             ["Ending visit time", self.end]]
+
+        if self.predecessor is not None:
+            a.append(["Predecessor", self.predecessor.key])
+        else:
+            a.append(["Predecessor", NIL])
+
+        return a
+
+    def show(self):
+        """Prints the current status of this node."""
+        print(
+            tabulate(
+                self._get_list_of_attributes(),
+                tablefmt="fancy_grid",
+                headers=(
+                    "ATTRIBUTES",
+                    "VALUES")))
+
+    def __str__(self):
+        return str(self.key)
+
+    def __repr__(self):
+        return self.__str__() + " => " + self.get_str_repr_of_adj_nodes()
+
+    def __eq__(self, other):
+        return self.value == other.value and self.key == other.key
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __hash__(self):
+        return hash(self.key) + id(self)
+
+
+class Graph:
+    """Graph data structure using adjacency list representation."""
+
+    def __init__(self, name="Adjacency List Representation"):
+        self.nodes = []
+        self.name = name
+
+        # Used with the dfs algorithm
+        # to create a topological sort
+        # of a directed acyclic graph (DAG).
+        self.topological_sort = []
+
+        # List of lists, each of them contains
+        # the nodes in different connected components.
+        self.connected_components = []
+
+    def add_node(self, u: GraphNode):
+        """Adds a new node to this graph."""
+        self.nodes.append(u)
+
+    def add_nodes(self, ls: list):
+        """Adds all GraphNode objects in ls to this Graph object."""
+        for u in ls:
+            self.nodes.append(u)
+
+    def reset_nodes(self):
+        """Make all nodes to assume the default attribute values."""
+        for u in self.nodes:
+            u.reset()
+
+    def add_undirected_edge(self, u: GraphNode, v: GraphNode, weight=0):
+        """Adds v to the adjacency list of u,
+        and adds also u to the adjacency list of v,
+        creating an "virtual" undirected edge.
+
+        If u is still not a node of this graph,
+        it is added first before establish any connection with v.
+        The same can be said for v."""
+        if u not in self.nodes:
+            self.add_node(u)
+
+        if v not in self.nodes:
+            self.add_node(v)
+
+        u.add_adjacent_node(v, weight)
+        v.add_adjacent_node(u, weight)
+
+    def add_directed_edge(self, u: GraphNode, v: GraphNode, weight=0):
+        """Adds v to the adjacency list of u.
+
+        If u is still not a node of this graph,
+        it is added first before establish any connection with v.
+        The same can be said for v."""
+        if u not in self.nodes:
+            self.add_node(u)
+
+        if v not in self.nodes:
+            self.add_node(v)
+
+        u.add_adjacent_node(v, weight)
+
+    @property
+    def edges(self) -> dict:
+        """Returns all the edges of this graph as dict,
+        where keys are tuples,
+        whose first elements are the starting points of the edges,
+        and whose second elements are the ending points of the edges.
+        Values of these dict represent the weight of the edges.
+
+        Note that if the graph is undirected,
+        and suppose there's an edge between node A and B,
+        then this function will return both triples:
+        (A, B, weight_AB) and (B, A, weight_BA).
+        """
+        edges = {}
+        for node in self.nodes:
+            edges.update(node.get_outgoing_edges())
+        return edges
+
+    def get_sorted_edges(self, reversed_order=False, undirected=False) -> "list of tuple":
+        """Sorts edges, by default, in increasing order."""
+        edges = self.edges
+        edges_list = sorted(edges.items(), key=operator.itemgetter(1), reverse=reversed_order)
+        if undirected:
+            return self._remove_double_edges(edges_list)
+        else:
+            return edges_list
+
+    @staticmethod
+    def _remove_double_edges(sorted_edges: list) -> list:
+        """If the graph is undirected,
+        we don't need to have a double representation of each edge.
+        This function serves to this purpose:
+        it removes double representations of the same edge,
+        in an undirected graph.
+
+        Note that sorted_edges should be the edges returned by get_sorted_edges."""
+        i = 0
+        while i < len(sorted_edges) - 1:
+            if sorted_edges[i][0][0] == sorted_edges[i + 1][0][1] and \
+                            sorted_edges[i][0][1] == sorted_edges[i + 1][0][0]:
+                sorted_edges.remove(sorted_edges[i])
+            else:
+                i += 1
+        return sorted_edges
+
+    def num_of_nodes(self):
+        return len(self.nodes)
+
+    def num_of_edges(self):
+        return len(self.edges)
+
+    def has_even_degree(self):
+        """Returns True, if all vertices have an even degree, False otherwise."""
+        for n in self.nodes:
+            if n.in_degree != n.out_degree:
+                return False
+        return True
+
+    @staticmethod
+    def weight(node1: GraphNode, node2: GraphNode):
+        """
+        Returns the weight of the edge connecting node1 to node2.
+
+        It returns None if there's no connection between node1 and node2.
+
+        Note that this method is static,
+        because it does not need any field of self
+        to check if node1 and node2 are connected somehow.
+
+        So, this method could also be called on total_nodes
+        belonging to other graphs, or even belonging to no graph."""
+        return node1.get_outgoing_edges().get((node1, node2))
+
+    # PRINT FUNCTIONS
+
+    def show_edges(self, sorted_edges=False):
+        ls = []
+        if sorted_edges:
+            for edge in self.get_sorted_edges():
+                ls.append([edge[0][0].key, edge[0][1].key, edge[1]])
+        else:
+            for edge in self.edges:
+                ls.append([edge[0].key, edge[1].key, self.edges[edge]])
+        print(tabulate(ls, tablefmt="fancy_grid", headers=("FROM", "TO", "WEIGHT")))
+
+    def show_connected_components(self):
+        """This function is useful after finding
+        the connected components of this graph.
+
+        You can find the connected components both with bfs or dfs."""
+        ls = []
+        for i, connected_component in enumerate(self.connected_components):
+            a = []
+            for j in connected_component:
+                a.append(j.key)
+            ls.append([i + 1, a])
+        print(tabulate(ls, tablefmt="fancy_grid", headers=("CC", "ELEMENTS")))
+
+    def show_nodes(self):
+        """Shows nodes' fields."""
+        for node in self.nodes:
+            node.show()
+
+    def __str__(self):
+        return str(self.nodes)
+
+    def __repr__(self):
+        return self.__str__()
+
+
+if __name__ == "__main__":
+    g = Graph()
+
+    a = GraphNode("A")
+    b = GraphNode("B")
+    c = GraphNode("C")
+
+    a.add_adjacent_node(b, 2)
+    a.add_adjacent_node(c, 3)
+
+    b.add_adjacent_node(b, 4)
+    b.add_adjacent_node(c, 10)
+
+    c.add_adjacent_node(b, 1)
+
+    # Testing add_node
+    g.add_node(a)
+
+    # Testing add_nodes
+    g.add_nodes((b, c))
+
+    # Testing show_edges (not sorted)
+    g.show_edges()
+
+    print("Edge's weight between a and b:", Graph.weight(a, b))
+    a.remove_adjacent_node(b)
+
+    # Testing show_nodes edges (sorted)
+    g.show_edges(sorted_edges=True)
+
+    # Testing the Graph.weight static function
+    print("Edge's weight between a and b:", Graph.weight(a, b))
+
+    # Testing the show_nodes function
+    print(g)
+
+    # Function show_connected_components should be tested with bfs or dfs.
+
+    g.show_nodes()
+    print("Out-degree of a:", a.out_degree)
+    print("In-degree of a:", a.in_degree)
+
+    print("Out-degree of b:", b.out_degree)
+    print("In-degree of b:", b.in_degree)
+
+    print("Out-degree of c:", c.out_degree)
+    print("In-degree of c:", c.in_degree)
+
+    c.remove_adjacent_node(b)
+    a.remove_adjacent_node(c)
+
+    print("Out-degree of c:", c.out_degree)
+    print("In-degree of c:", c.in_degree)
+
+    g.show_connected_components()
+
+
+ +
+ +
+

Module variables

+
+

var BLACK

+ + +
+
+ +
+
+

var GREY

+ + +
+
+ +
+
+

var INFINITY

+ + +
+
+ +
+
+

var NIL

+ + +
+
+ +
+
+

var WHITE

+ + +
+
+ +
+ + +

Classes

+ +
+

class Graph

+ + +

Graph data structure using adjacency list representation.

+
+ +
+
class Graph:
+    """Graph data structure using adjacency list representation."""
+
+    def __init__(self, name="Adjacency List Representation"):
+        self.nodes = []
+        self.name = name
+
+        # Used with the dfs algorithm
+        # to create a topological sort
+        # of a directed acyclic graph (DAG).
+        self.topological_sort = []
+
+        # List of lists, each of them contains
+        # the nodes in different connected components.
+        self.connected_components = []
+
+    def add_node(self, u: GraphNode):
+        """Adds a new node to this graph."""
+        self.nodes.append(u)
+
+    def add_nodes(self, ls: list):
+        """Adds all GraphNode objects in ls to this Graph object."""
+        for u in ls:
+            self.nodes.append(u)
+
+    def reset_nodes(self):
+        """Make all nodes to assume the default attribute values."""
+        for u in self.nodes:
+            u.reset()
+
+    def add_undirected_edge(self, u: GraphNode, v: GraphNode, weight=0):
+        """Adds v to the adjacency list of u,
+        and adds also u to the adjacency list of v,
+        creating an "virtual" undirected edge.
+
+        If u is still not a node of this graph,
+        it is added first before establish any connection with v.
+        The same can be said for v."""
+        if u not in self.nodes:
+            self.add_node(u)
+
+        if v not in self.nodes:
+            self.add_node(v)
+
+        u.add_adjacent_node(v, weight)
+        v.add_adjacent_node(u, weight)
+
+    def add_directed_edge(self, u: GraphNode, v: GraphNode, weight=0):
+        """Adds v to the adjacency list of u.
+
+        If u is still not a node of this graph,
+        it is added first before establish any connection with v.
+        The same can be said for v."""
+        if u not in self.nodes:
+            self.add_node(u)
+
+        if v not in self.nodes:
+            self.add_node(v)
+
+        u.add_adjacent_node(v, weight)
+
+    @property
+    def edges(self) -> dict:
+        """Returns all the edges of this graph as dict,
+        where keys are tuples,
+        whose first elements are the starting points of the edges,
+        and whose second elements are the ending points of the edges.
+        Values of these dict represent the weight of the edges.
+
+        Note that if the graph is undirected,
+        and suppose there's an edge between node A and B,
+        then this function will return both triples:
+        (A, B, weight_AB) and (B, A, weight_BA).
+        """
+        edges = {}
+        for node in self.nodes:
+            edges.update(node.get_outgoing_edges())
+        return edges
+
+    def get_sorted_edges(self, reversed_order=False, undirected=False) -> "list of tuple":
+        """Sorts edges, by default, in increasing order."""
+        edges = self.edges
+        edges_list = sorted(edges.items(), key=operator.itemgetter(1), reverse=reversed_order)
+        if undirected:
+            return self._remove_double_edges(edges_list)
+        else:
+            return edges_list
+
+    @staticmethod
+    def _remove_double_edges(sorted_edges: list) -> list:
+        """If the graph is undirected,
+        we don't need to have a double representation of each edge.
+        This function serves to this purpose:
+        it removes double representations of the same edge,
+        in an undirected graph.
+
+        Note that sorted_edges should be the edges returned by get_sorted_edges."""
+        i = 0
+        while i < len(sorted_edges) - 1:
+            if sorted_edges[i][0][0] == sorted_edges[i + 1][0][1] and \
+                            sorted_edges[i][0][1] == sorted_edges[i + 1][0][0]:
+                sorted_edges.remove(sorted_edges[i])
+            else:
+                i += 1
+        return sorted_edges
+
+    def num_of_nodes(self):
+        return len(self.nodes)
+
+    def num_of_edges(self):
+        return len(self.edges)
+
+    def has_even_degree(self):
+        """Returns True, if all vertices have an even degree, False otherwise."""
+        for n in self.nodes:
+            if n.in_degree != n.out_degree:
+                return False
+        return True
+
+    @staticmethod
+    def weight(node1: GraphNode, node2: GraphNode):
+        """
+        Returns the weight of the edge connecting node1 to node2.
+
+        It returns None if there's no connection between node1 and node2.
+
+        Note that this method is static,
+        because it does not need any field of self
+        to check if node1 and node2 are connected somehow.
+
+        So, this method could also be called on total_nodes
+        belonging to other graphs, or even belonging to no graph."""
+        return node1.get_outgoing_edges().get((node1, node2))
+
+    # PRINT FUNCTIONS
+
+    def show_edges(self, sorted_edges=False):
+        ls = []
+        if sorted_edges:
+            for edge in self.get_sorted_edges():
+                ls.append([edge[0][0].key, edge[0][1].key, edge[1]])
+        else:
+            for edge in self.edges:
+                ls.append([edge[0].key, edge[1].key, self.edges[edge]])
+        print(tabulate(ls, tablefmt="fancy_grid", headers=("FROM", "TO", "WEIGHT")))
+
+    def show_connected_components(self):
+        """This function is useful after finding
+        the connected components of this graph.
+
+        You can find the connected components both with bfs or dfs."""
+        ls = []
+        for i, connected_component in enumerate(self.connected_components):
+            a = []
+            for j in connected_component:
+                a.append(j.key)
+            ls.append([i + 1, a])
+        print(tabulate(ls, tablefmt="fancy_grid", headers=("CC", "ELEMENTS")))
+
+    def show_nodes(self):
+        """Shows nodes' fields."""
+        for node in self.nodes:
+            node.show()
+
+    def __str__(self):
+        return str(self.nodes)
+
+    def __repr__(self):
+        return self.__str__()
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • Graph
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, name='Adjacency List Representation')

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, name="Adjacency List Representation"):
+    self.nodes = []
+    self.name = name
+    # Used with the dfs algorithm
+    # to create a topological sort
+    # of a directed acyclic graph (DAG).
+    self.topological_sort = []
+    # List of lists, each of them contains
+    # the nodes in different connected components.
+    self.connected_components = []
+
+
+
+ +
+ + +
+
+

def add_directed_edge(

self, u, v, weight=0)

+
+ + + + +

Adds v to the adjacency list of u.

+

If u is still not a node of this graph, +it is added first before establish any connection with v. +The same can be said for v.

+
+ +
+
def add_directed_edge(self, u: GraphNode, v: GraphNode, weight=0):
+    """Adds v to the adjacency list of u.
+    If u is still not a node of this graph,
+    it is added first before establish any connection with v.
+    The same can be said for v."""
+    if u not in self.nodes:
+        self.add_node(u)
+    if v not in self.nodes:
+        self.add_node(v)
+    u.add_adjacent_node(v, weight)
+
+
+
+ +
+ + +
+
+

def add_node(

self, u)

+
+ + + + +

Adds a new node to this graph.

+
+ +
+
def add_node(self, u: GraphNode):
+    """Adds a new node to this graph."""
+    self.nodes.append(u)
+
+
+
+ +
+ + +
+
+

def add_nodes(

self, ls)

+
+ + + + +

Adds all GraphNode objects in ls to this Graph object.

+
+ +
+
def add_nodes(self, ls: list):
+    """Adds all GraphNode objects in ls to this Graph object."""
+    for u in ls:
+        self.nodes.append(u)
+
+
+
+ +
+ + +
+
+

def add_undirected_edge(

self, u, v, weight=0)

+
+ + + + +

Adds v to the adjacency list of u, +and adds also u to the adjacency list of v, +creating an "virtual" undirected edge.

+

If u is still not a node of this graph, +it is added first before establish any connection with v. +The same can be said for v.

+
+ +
+
def add_undirected_edge(self, u: GraphNode, v: GraphNode, weight=0):
+    """Adds v to the adjacency list of u,
+    and adds also u to the adjacency list of v,
+    creating an "virtual" undirected edge.
+    If u is still not a node of this graph,
+    it is added first before establish any connection with v.
+    The same can be said for v."""
+    if u not in self.nodes:
+        self.add_node(u)
+    if v not in self.nodes:
+        self.add_node(v)
+    u.add_adjacent_node(v, weight)
+    v.add_adjacent_node(u, weight)
+
+
+
+ +
+ + +
+
+

def get_sorted_edges(

self, reversed_order=False, undirected=False)

+
+ + + + +

Sorts edges, by default, in increasing order.

+
+ +
+
def get_sorted_edges(self, reversed_order=False, undirected=False) -> "list of tuple":
+    """Sorts edges, by default, in increasing order."""
+    edges = self.edges
+    edges_list = sorted(edges.items(), key=operator.itemgetter(1), reverse=reversed_order)
+    if undirected:
+        return self._remove_double_edges(edges_list)
+    else:
+        return edges_list
+
+
+
+ +
+ + +
+
+

def has_even_degree(

self)

+
+ + + + +

Returns True, if all vertices have an even degree, False otherwise.

+
+ +
+
def has_even_degree(self):
+    """Returns True, if all vertices have an even degree, False otherwise."""
+    for n in self.nodes:
+        if n.in_degree != n.out_degree:
+            return False
+    return True
+
+
+
+ +
+ + +
+
+

def num_of_edges(

self)

+
+ + + + +
+ +
+
def num_of_edges(self):
+    return len(self.edges)
+
+
+
+ +
+ + +
+
+

def num_of_nodes(

self)

+
+ + + + +
+ +
+
def num_of_nodes(self):
+    return len(self.nodes)
+
+
+
+ +
+ + +
+
+

def reset_nodes(

self)

+
+ + + + +

Make all nodes to assume the default attribute values.

+
+ +
+
def reset_nodes(self):
+    """Make all nodes to assume the default attribute values."""
+    for u in self.nodes:
+        u.reset()
+
+
+
+ +
+ + +
+
+

def show_connected_components(

self)

+
+ + + + +

This function is useful after finding +the connected components of this graph.

+

You can find the connected components both with bfs or dfs.

+
+ +
+
def show_connected_components(self):
+    """This function is useful after finding
+    the connected components of this graph.
+    You can find the connected components both with bfs or dfs."""
+    ls = []
+    for i, connected_component in enumerate(self.connected_components):
+        a = []
+        for j in connected_component:
+            a.append(j.key)
+        ls.append([i + 1, a])
+    print(tabulate(ls, tablefmt="fancy_grid", headers=("CC", "ELEMENTS")))
+
+
+
+ +
+ + +
+
+

def show_edges(

self, sorted_edges=False)

+
+ + + + +
+ +
+
def show_edges(self, sorted_edges=False):
+    ls = []
+    if sorted_edges:
+        for edge in self.get_sorted_edges():
+            ls.append([edge[0][0].key, edge[0][1].key, edge[1]])
+    else:
+        for edge in self.edges:
+            ls.append([edge[0].key, edge[1].key, self.edges[edge]])
+    print(tabulate(ls, tablefmt="fancy_grid", headers=("FROM", "TO", "WEIGHT")))
+
+
+
+ +
+ + +
+
+

def show_nodes(

self)

+
+ + + + +

Shows nodes' fields.

+
+ +
+
def show_nodes(self):
+    """Shows nodes' fields."""
+    for node in self.nodes:
+        node.show()
+
+
+
+ +
+ + +
+
+

def weight(

node1, node2)

+
+ + + + +

Returns the weight of the edge connecting node1 to node2.

+

It returns None if there's no connection between node1 and node2.

+

Note that this method is static, +because it does not need any field of self +to check if node1 and node2 are connected somehow.

+

So, this method could also be called on total_nodes +belonging to other graphs, or even belonging to no graph.

+
+ +
+
@staticmethod
+def weight(node1: GraphNode, node2: GraphNode):
+    """
+    Returns the weight of the edge connecting node1 to node2.
+    It returns None if there's no connection between node1 and node2.
+    Note that this method is static,
+    because it does not need any field of self
+    to check if node1 and node2 are connected somehow.
+    So, this method could also be called on total_nodes
+    belonging to other graphs, or even belonging to no graph."""
+    return node1.get_outgoing_edges().get((node1, node2))
+
+
+
+ +
+ +

Instance variables

+
+

var connected_components

+ + + + +
+
+ +
+
+

var edges

+ + + + +

Returns all the edges of this graph as dict, +where keys are tuples, +whose first elements are the starting points of the edges, +and whose second elements are the ending points of the edges. +Values of these dict represent the weight of the edges.

+

Note that if the graph is undirected, +and suppose there's an edge between node A and B, +then this function will return both triples: +(A, B, weight_AB) and (B, A, weight_BA).

+
+
+ +
+
+

var name

+ + + + +
+
+ +
+
+

var nodes

+ + + + +
+
+ +
+
+

var topological_sort

+ + + + +
+
+ +
+
+
+ +
+

class GraphNode

+ + +

GraphNode for the Graph data structure defined in Graph.py. +Nodes contain many fields that are used in the 2 main graph algorithms: +- bfs +- dfs +These fields are for example self.predecessor, self.distance, etc.

+

Nodes keep also track of the edges of which they are the starting point. +These edges are represented as a list of 3 items:

+
edge = [self, pointed_node, edge_weight]
+
+

where: +- self is the reference to the node itself, +- pointed_node is the ending node of the edge +- edge_weigh is the eventual weight of the edge.

+
+ +
+
class GraphNode:
+    """GraphNode for the `Graph` data structure defined in `Graph.py`.
+    Nodes contain many fields that are used in the 2 main graph algorithms:
+    - `bfs`
+    - `dfs`
+    These fields are for example `self.predecessor`, `self.distance`, etc.
+
+    Nodes keep also track of the edges of which they are the starting point.
+    These edges are represented as a list of 3 items:
+
+        edge = [self, pointed_node, edge_weight]
+
+    where:
+    - `self` is the reference to the node itself,
+    - `pointed_node` is the ending node of the edge
+    - `edge_weigh` is the eventual weight of the edge."""
+
+    def __init__(self, key, value=None):
+        self.key = key
+        self.value = value
+        # Attributes used mostly in algorithms for graphs, such as in bfs or dfs.
+        self.predecessor = NIL
+        self.distance = INFINITY
+        self.color = WHITE  # To keep track if a node has been or not visited yet.
+        self.adjacent_nodes = []
+        self.outgoing_edges = {}  # [(self, other), w]
+
+        self.cc = NIL
+        """This variable can be used to keep track of which
+        connected component this node in the graph belongs to.
+
+        For example, we could name the possible different
+        connected components using simply numbers:
+        the first connected component is 1, the second is 2,
+        the third is 3, and so on.
+
+        When searching for connected components,
+        we can use this variable to associate this node
+        with a connected component:
+        if we set it to 1, that would mean that this node
+        belongs to the connected component 1."""
+
+        # These variables are useful for dfs
+        # to keep track when a node is first visited
+        # and when we finish visiting it,
+        # and we start backtracking.
+        self.start = INFINITY
+        self.end = INFINITY
+
+        self.in_degree = 0
+        self.out_degree = 0
+
+    def get_adjacent_nodes(self) -> list:
+        """Returns the adjacent nodes to self."""
+        return self.adjacent_nodes
+
+    def get_outgoing_edges(self) -> dict:
+        """Returns a dictionary containing the edges outgoing from this node.
+        Edges are of the form (self, other): weight."""
+        return self.outgoing_edges
+
+    def add_adjacent_node(self, u, weight=0):
+        """Adds u as adjacent node to self.
+
+        Creates a directed edge from self to u."""
+        self.adjacent_nodes.append(u)
+        self.outgoing_edges[(self, u)] = weight
+        self.out_degree += 1
+        u.in_degree += 1
+
+    def remove_adjacent_node(self, u):
+        """Removes u's reference from the adjacent_nodes list of self.
+
+        It also removes the directed edge from self to u."""
+        self.adjacent_nodes.remove(u)
+        w = self.outgoing_edges.pop((self, u))
+        self.out_degree -= 1
+        u.in_degree -= 1
+        return w
+
+    def reset(self, clear_nodes=False):
+        """Make self assume the default attribute values.
+
+        If clear_nodes=True, then all connections with other nodes are removed.
+        """
+        self.color = WHITE
+        self.predecessor = NIL
+        self.distance = INFINITY
+        self.cc = NIL
+        self.start = INFINITY
+        self.end = INFINITY
+        if clear_nodes:
+            self.clear_adjacent_nodes()
+
+    def clear_adjacent_nodes(self):
+        self._fix_degrees_before_clearing()
+        self.adjacent_nodes.clear()
+        self.outgoing_edges.clear()
+
+    def _fix_degrees_before_clearing(self):
+        for n in self.adjacent_nodes:
+            n.in_degree -= 1
+        self.out_degree = 0
+
+    def get_str_repr_of_adj_nodes(self):
+        """Returns a string representing all the adjacent total_nodes."""
+        str_repr = "["
+        for node in self.get_adjacent_nodes():
+            str_repr += str(node.key) + ", "
+        return "[]" if not str_repr[:-2] else str_repr[:-2] + "]"
+
+    def _get_list_of_attributes(self):
+        a = [["Name", self.key],
+             ["Adjacent nodes", self.get_str_repr_of_adj_nodes()],
+             ["Color", self.color],
+             ["Distance", self.distance],
+             ["Connected component", self.cc],
+             ["Starting visit time", self.start],
+             ["Ending visit time", self.end]]
+
+        if self.predecessor is not None:
+            a.append(["Predecessor", self.predecessor.key])
+        else:
+            a.append(["Predecessor", NIL])
+
+        return a
+
+    def show(self):
+        """Prints the current status of this node."""
+        print(
+            tabulate(
+                self._get_list_of_attributes(),
+                tablefmt="fancy_grid",
+                headers=(
+                    "ATTRIBUTES",
+                    "VALUES")))
+
+    def __str__(self):
+        return str(self.key)
+
+    def __repr__(self):
+        return self.__str__() + " => " + self.get_str_repr_of_adj_nodes()
+
+    def __eq__(self, other):
+        return self.value == other.value and self.key == other.key
+
+    def __ne__(self, other):
+        return not self.__eq__(other)
+
+    def __hash__(self):
+        return hash(self.key) + id(self)
+
+
+
+ + +
+

Ancestors (in MRO)

+ +

Static methods

+ +
+
+

def __init__(

self, key, value=None)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, key, value=None):
+    self.key = key
+    self.value = value
+    # Attributes used mostly in algorithms for graphs, such as in bfs or dfs.
+    self.predecessor = NIL
+    self.distance = INFINITY
+    self.color = WHITE  # To keep track if a node has been or not visited yet.
+    self.adjacent_nodes = []
+    self.outgoing_edges = {}  # [(self, other), w]
+    self.cc = NIL
+    """This variable can be used to keep track of which
+    connected component this node in the graph belongs to.
+    For example, we could name the possible different
+    connected components using simply numbers:
+    the first connected component is 1, the second is 2,
+    the third is 3, and so on.
+    When searching for connected components,
+    we can use this variable to associate this node
+    with a connected component:
+    if we set it to 1, that would mean that this node
+    belongs to the connected component 1."""
+    # These variables are useful for dfs
+    # to keep track when a node is first visited
+    # and when we finish visiting it,
+    # and we start backtracking.
+    self.start = INFINITY
+    self.end = INFINITY
+    self.in_degree = 0
+    self.out_degree = 0
+
+
+
+ +
+ + +
+
+

def add_adjacent_node(

self, u, weight=0)

+
+ + + + +

Adds u as adjacent node to self.

+

Creates a directed edge from self to u.

+
+ +
+
def add_adjacent_node(self, u, weight=0):
+    """Adds u as adjacent node to self.
+    Creates a directed edge from self to u."""
+    self.adjacent_nodes.append(u)
+    self.outgoing_edges[(self, u)] = weight
+    self.out_degree += 1
+    u.in_degree += 1
+
+
+
+ +
+ + +
+
+

def clear_adjacent_nodes(

self)

+
+ + + + +
+ +
+
def clear_adjacent_nodes(self):
+    self._fix_degrees_before_clearing()
+    self.adjacent_nodes.clear()
+    self.outgoing_edges.clear()
+
+
+
+ +
+ + +
+
+

def get_adjacent_nodes(

self)

+
+ + + + +

Returns the adjacent nodes to self.

+
+ +
+
def get_adjacent_nodes(self) -> list:
+    """Returns the adjacent nodes to self."""
+    return self.adjacent_nodes
+
+
+
+ +
+ + +
+
+

def get_outgoing_edges(

self)

+
+ + + + +

Returns a dictionary containing the edges outgoing from this node. +Edges are of the form (self, other): weight.

+
+ +
+
def get_outgoing_edges(self) -> dict:
+    """Returns a dictionary containing the edges outgoing from this node.
+    Edges are of the form (self, other): weight."""
+    return self.outgoing_edges
+
+
+
+ +
+ + +
+
+

def get_str_repr_of_adj_nodes(

self)

+
+ + + + +

Returns a string representing all the adjacent total_nodes.

+
+ +
+
def get_str_repr_of_adj_nodes(self):
+    """Returns a string representing all the adjacent total_nodes."""
+    str_repr = "["
+    for node in self.get_adjacent_nodes():
+        str_repr += str(node.key) + ", "
+    return "[]" if not str_repr[:-2] else str_repr[:-2] + "]"
+
+
+
+ +
+ + +
+
+

def remove_adjacent_node(

self, u)

+
+ + + + +

Removes u's reference from the adjacent_nodes list of self.

+

It also removes the directed edge from self to u.

+
+ +
+
def remove_adjacent_node(self, u):
+    """Removes u's reference from the adjacent_nodes list of self.
+    It also removes the directed edge from self to u."""
+    self.adjacent_nodes.remove(u)
+    w = self.outgoing_edges.pop((self, u))
+    self.out_degree -= 1
+    u.in_degree -= 1
+    return w
+
+
+
+ +
+ + +
+
+

def reset(

self, clear_nodes=False)

+
+ + + + +

Make self assume the default attribute values.

+

If clear_nodes=True, then all connections with other nodes are removed.

+
+ +
+
def reset(self, clear_nodes=False):
+    """Make self assume the default attribute values.
+    If clear_nodes=True, then all connections with other nodes are removed.
+    """
+    self.color = WHITE
+    self.predecessor = NIL
+    self.distance = INFINITY
+    self.cc = NIL
+    self.start = INFINITY
+    self.end = INFINITY
+    if clear_nodes:
+        self.clear_adjacent_nodes()
+
+
+
+ +
+ + +
+
+

def show(

self)

+
+ + + + +

Prints the current status of this node.

+
+ +
+
def show(self):
+    """Prints the current status of this node."""
+    print(
+        tabulate(
+            self._get_list_of_attributes(),
+            tablefmt="fancy_grid",
+            headers=(
+                "ATTRIBUTES",
+                "VALUES")))
+
+
+
+ +
+ +

Instance variables

+
+

var adjacent_nodes

+ + + + +
+
+ +
+
+

var cc

+ + + + +

This variable can be used to keep track of which +connected component this node in the graph belongs to.

+

For example, we could name the possible different +connected components using simply numbers: +the first connected component is 1, the second is 2, +the third is 3, and so on.

+

When searching for connected components, +we can use this variable to associate this node +with a connected component: +if we set it to 1, that would mean that this node +belongs to the connected component 1.

+
+
+ +
+
+

var color

+ + + + +
+
+ +
+
+

var distance

+ + + + +
+
+ +
+
+

var end

+ + + + +
+
+ +
+
+

var in_degree

+ + + + +
+
+ +
+
+

var key

+ + + + +
+
+ +
+
+

var out_degree

+ + + + +
+
+ +
+
+

var outgoing_edges

+ + + + +
+
+ +
+
+

var predecessor

+ + + + +
+
+ +
+
+

var start

+ + + + +
+
+ +
+
+

var value

+ + + + +
+
+ +
+
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/HashTable.m.html b/docs/ands/ds/HashTable.m.html new file mode 100644 index 00000000..c0196a69 --- /dev/null +++ b/docs/ands/ds/HashTable.m.html @@ -0,0 +1,1805 @@ + + + + + + ands.ds.HashTable API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.HashTable module

+

Meta info

+

Author: Nelson Brochado

+

Created: 01/06/2015

+

Updated: 21/02/2016

+

Description

+

Hash table that re-sizes if no more slot is available. +The process of re-sizing doubles the current capacity of the hash table each time (for now). +It uses linear probing when there's a collision. +The hash function uses both the Python's built-in hash function and the % operator. +You can access and put an item in the hash table by using the same convinient notation +that is used by the Python's standard dict class, that is:

+
h = HashTable()
+h[12] = 3
+print(h[12])
+
+

References

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+# Meta info
+
+Author: Nelson Brochado
+
+Created: 01/06/2015
+
+Updated: 21/02/2016
+
+# Description
+
+Hash table that re-sizes if no more slot is available.
+The process of re-sizing doubles the current capacity of the hash table each time (for now).
+It uses [linear probing](https://en.wikipedia.org/wiki/Linear_probing) when there's a collision.
+The hash function uses both the Python's built-in `hash` function and the `%` operator.
+You can access and put an item in the hash table by using the same convinient notation
+that is used by the Python's standard `dict` class, that is:
+
+    h = HashTable()
+    h[12] = 3
+    print(h[12])
+
+# References
+
+- [http://interactivepython.org/runestone/static/pythonds/SortSearch/Hashing.html](http://interactivepython.org/runestone/static/pythonds/SortSearch/Hashing.html)
+- [http://stackoverflow.com/questions/279539/best-way-to-remove-an-entry-from-a-hash-table](http://stackoverflow.com/questions/279539/best-way-to-remove-an-entry-from-a-hash-table)
+
+"""
+
+from tabulate import tabulate
+
+__all__ = ["HashTable", "has_duplicates", "find_duplicates"]
+
+
+class HashTable:
+    def __init__(self, capacity: int = 11):
+        self.n = capacity
+        self.keys = [None] * self.n
+        self.values = [None] * self.n
+
+    # HASH FUNCTIONS
+
+    def hash_code(self, key, size: int) -> int:
+        """Returns a hash code (an int) between 0 and `size` (excluded).
+
+        `size` must be the size of the buffer based on which
+        this function should return a hash value."""
+        return hash(key) % size
+
+    def rehash(self, old_hash: int, size: int) -> int:
+        """Returns a new hash value based on the previous one called `old_hash`.
+
+        `size` must be the size of the buffer based on which
+        we want to have a new hash value from the old hash value."""
+        return (old_hash + 1) % size
+
+    # PUT
+
+    def put(self, key: object, value: object):
+        """Inserts the pair `key`-`value` in this map.
+
+        If `key` is `None`, a `TypeError` is raised,
+        because keys cannot be `None`."""
+        if key is None:
+            raise TypeError("key cannot be None.")
+
+        assert not has_duplicates(self.keys)
+        a = self._put(key, value, self.n)
+        assert not has_duplicates(self.keys)
+        return a
+
+    def _put(self, key, value, size):
+        assert not has_duplicates(self.keys), "precondition in _put"
+
+        hash_value = self.hash_code(key, size)
+
+        # No need to allocate new space.
+        if self.keys[hash_value] is None:
+            self.keys[hash_value] = key
+            self.values[hash_value] = value
+
+        # If self already contains key, then its value is overridden.
+        elif self.keys[hash_value] == key:
+            self.values[hash_value] = value
+
+        # Collision: there's already a key-value pair
+        # at the slot dedicated to this key-value pair,
+        # according to the self.hash_code function.
+        # We need to rehash, i.e. find another slot for this key-value pair.
+        else:
+            next_slot = self.rehash(hash_value, size)
+            rehashed = False
+
+            while self.keys[next_slot] is not None and self.keys[
+                next_slot] != key:
+
+                next_slot = self.rehash(next_slot, size)
+
+                # Allocate new buffer of length len(self.keys)*2 + 1
+                if next_slot == hash_value:
+                    rehashed = True
+
+                    keys = self.keys
+                    values = self.values
+
+                    new_size = len(self.keys) * 2 + 1
+                    self.keys = [None] * new_size
+                    self.values = [None] * new_size
+
+                    # Reashing and putting all elements again
+                    # Note that the following call to self._put
+                    # will never reach this statement
+                    # because there will be slots available
+                    for k in keys:
+                        v = self._get(k, keys, values, self.n)
+                        self._put(k, v, new_size)
+
+                    self._put(key, value, new_size)
+                    self.n = new_size
+
+            # We exited the loop either because
+            # we have found a free slot or a slot containing our key.
+            # (and not after having re-sized the table!)
+            if not rehashed:
+                if self.keys[next_slot] is None:
+                    self.keys[next_slot] = key
+                    self.values[next_slot] = value
+                else:
+                    assert self.keys[next_slot] == key
+                    self.values[next_slot] = value
+
+        if has_duplicates(self.keys):
+            find_duplicates(self.keys)
+
+        assert not has_duplicates(self.keys), "postcondition in _put"
+
+    def get(self, key):
+        """Returns the value associated with `key`.
+        It returns `None` if there's no value associated with `key`.
+
+        If `key` is `None`, a `TypeError` is raised,
+        because keys cannot be None."""
+        if key is None:
+            raise TypeError("key cannot be None.")
+        return self._get(key, self.keys, self.values, self.n)
+
+    def _get(self, key, keys, values, size):
+        assert not has_duplicates(keys), "precondition in _get"
+
+        hash_value = self.hash_code(key, size)
+
+        data = None
+        stop = False
+        found = False
+        position = hash_value
+
+        while keys[position] is not None and not found and not stop:
+
+            if keys[position] == key:
+                found = True
+                data = values[position]
+            else:
+                # Find a new possible position by rehashing
+                position = self.rehash(position, size)
+
+                # We are at the initial slot,
+                # and thus nothing was found.
+                if position == hash_value:
+                    stop = True
+
+        assert not has_duplicates(keys), "postcondition _get"
+        return data
+
+    def __getitem__(self, key):
+        return self.get(key)
+
+    def __setitem__(self, key, value):
+        self.put(key, value)
+
+    def delete(self, key):
+        """Deletes the mapping (if any) between `key`
+        and its corresponding associated value.
+        If there's no mapping, `None` is returned."""
+        try:
+            i = self.keys.index(key)
+            v = self.values[i]
+            self.keys[i] = self.values[i] = None
+            return v
+        except ValueError:
+            return None
+
+    @property
+    def size(self):
+        """Returns the number of pairs key-value in this map."""
+        assert len(self.keys) == len(self.values) == self.n
+        return sum(k is not None for k in self.keys)
+
+    @property
+    def capacity(self):
+        """Returns the size of the internal buffers that store the keys and the values."""
+        assert len(self.keys) == len(self.values) == self.n
+        return len(self.keys)
+
+    def show(self):
+        """Pretty-prints (using `tabulate.tabulate()`) this table."""
+        c = 0
+        data = []
+        for i in range(len(self.keys)):
+            if self.keys[i] is not None:
+                c += 1
+                data.append([c, self.keys[i], self.values[i]])
+        print(tabulate(data, headers=["#", "Keys", "Values"], tablefmt="grid"))
+
+    def __str__(self):
+        return str([(k, v)
+                    for k, v in zip(self.keys, self.values) if k is not None])
+
+    def __repr__(self):
+        return self.__str__()
+
+
+def has_duplicates(ls):
+    ls = [item for item in ls if item is not None]
+    return len(ls) != len(set(ls))
+
+
+def find_duplicates(ls):
+    return [item for item, count in collections.Counter(
+        ls).items() if (count > 1 and item is not None)]
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def find_duplicates(

ls)

+
+ + + + +
+ +
+
def find_duplicates(ls):
+    return [item for item, count in collections.Counter(
+        ls).items() if (count > 1 and item is not None)]
+
+
+
+ +
+ + +
+
+

def has_duplicates(

ls)

+
+ + + + +
+ +
+
def has_duplicates(ls):
+    ls = [item for item in ls if item is not None]
+    return len(ls) != len(set(ls))
+
+
+
+ +
+ + +

Classes

+ +
+

class HashTable

+ + +
+ +
+
class HashTable:
+    def __init__(self, capacity: int = 11):
+        self.n = capacity
+        self.keys = [None] * self.n
+        self.values = [None] * self.n
+
+    # HASH FUNCTIONS
+
+    def hash_code(self, key, size: int) -> int:
+        """Returns a hash code (an int) between 0 and `size` (excluded).
+
+        `size` must be the size of the buffer based on which
+        this function should return a hash value."""
+        return hash(key) % size
+
+    def rehash(self, old_hash: int, size: int) -> int:
+        """Returns a new hash value based on the previous one called `old_hash`.
+
+        `size` must be the size of the buffer based on which
+        we want to have a new hash value from the old hash value."""
+        return (old_hash + 1) % size
+
+    # PUT
+
+    def put(self, key: object, value: object):
+        """Inserts the pair `key`-`value` in this map.
+
+        If `key` is `None`, a `TypeError` is raised,
+        because keys cannot be `None`."""
+        if key is None:
+            raise TypeError("key cannot be None.")
+
+        assert not has_duplicates(self.keys)
+        a = self._put(key, value, self.n)
+        assert not has_duplicates(self.keys)
+        return a
+
+    def _put(self, key, value, size):
+        assert not has_duplicates(self.keys), "precondition in _put"
+
+        hash_value = self.hash_code(key, size)
+
+        # No need to allocate new space.
+        if self.keys[hash_value] is None:
+            self.keys[hash_value] = key
+            self.values[hash_value] = value
+
+        # If self already contains key, then its value is overridden.
+        elif self.keys[hash_value] == key:
+            self.values[hash_value] = value
+
+        # Collision: there's already a key-value pair
+        # at the slot dedicated to this key-value pair,
+        # according to the self.hash_code function.
+        # We need to rehash, i.e. find another slot for this key-value pair.
+        else:
+            next_slot = self.rehash(hash_value, size)
+            rehashed = False
+
+            while self.keys[next_slot] is not None and self.keys[
+                next_slot] != key:
+
+                next_slot = self.rehash(next_slot, size)
+
+                # Allocate new buffer of length len(self.keys)*2 + 1
+                if next_slot == hash_value:
+                    rehashed = True
+
+                    keys = self.keys
+                    values = self.values
+
+                    new_size = len(self.keys) * 2 + 1
+                    self.keys = [None] * new_size
+                    self.values = [None] * new_size
+
+                    # Reashing and putting all elements again
+                    # Note that the following call to self._put
+                    # will never reach this statement
+                    # because there will be slots available
+                    for k in keys:
+                        v = self._get(k, keys, values, self.n)
+                        self._put(k, v, new_size)
+
+                    self._put(key, value, new_size)
+                    self.n = new_size
+
+            # We exited the loop either because
+            # we have found a free slot or a slot containing our key.
+            # (and not after having re-sized the table!)
+            if not rehashed:
+                if self.keys[next_slot] is None:
+                    self.keys[next_slot] = key
+                    self.values[next_slot] = value
+                else:
+                    assert self.keys[next_slot] == key
+                    self.values[next_slot] = value
+
+        if has_duplicates(self.keys):
+            find_duplicates(self.keys)
+
+        assert not has_duplicates(self.keys), "postcondition in _put"
+
+    def get(self, key):
+        """Returns the value associated with `key`.
+        It returns `None` if there's no value associated with `key`.
+
+        If `key` is `None`, a `TypeError` is raised,
+        because keys cannot be None."""
+        if key is None:
+            raise TypeError("key cannot be None.")
+        return self._get(key, self.keys, self.values, self.n)
+
+    def _get(self, key, keys, values, size):
+        assert not has_duplicates(keys), "precondition in _get"
+
+        hash_value = self.hash_code(key, size)
+
+        data = None
+        stop = False
+        found = False
+        position = hash_value
+
+        while keys[position] is not None and not found and not stop:
+
+            if keys[position] == key:
+                found = True
+                data = values[position]
+            else:
+                # Find a new possible position by rehashing
+                position = self.rehash(position, size)
+
+                # We are at the initial slot,
+                # and thus nothing was found.
+                if position == hash_value:
+                    stop = True
+
+        assert not has_duplicates(keys), "postcondition _get"
+        return data
+
+    def __getitem__(self, key):
+        return self.get(key)
+
+    def __setitem__(self, key, value):
+        self.put(key, value)
+
+    def delete(self, key):
+        """Deletes the mapping (if any) between `key`
+        and its corresponding associated value.
+        If there's no mapping, `None` is returned."""
+        try:
+            i = self.keys.index(key)
+            v = self.values[i]
+            self.keys[i] = self.values[i] = None
+            return v
+        except ValueError:
+            return None
+
+    @property
+    def size(self):
+        """Returns the number of pairs key-value in this map."""
+        assert len(self.keys) == len(self.values) == self.n
+        return sum(k is not None for k in self.keys)
+
+    @property
+    def capacity(self):
+        """Returns the size of the internal buffers that store the keys and the values."""
+        assert len(self.keys) == len(self.values) == self.n
+        return len(self.keys)
+
+    def show(self):
+        """Pretty-prints (using `tabulate.tabulate()`) this table."""
+        c = 0
+        data = []
+        for i in range(len(self.keys)):
+            if self.keys[i] is not None:
+                c += 1
+                data.append([c, self.keys[i], self.values[i]])
+        print(tabulate(data, headers=["#", "Keys", "Values"], tablefmt="grid"))
+
+    def __str__(self):
+        return str([(k, v)
+                    for k, v in zip(self.keys, self.values) if k is not None])
+
+    def __repr__(self):
+        return self.__str__()
+
+
+
+ + +
+

Ancestors (in MRO)

+ +

Static methods

+ +
+
+

def __init__(

self, capacity=11)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, capacity: int = 11):
+    self.n = capacity
+    self.keys = [None] * self.n
+    self.values = [None] * self.n
+
+
+
+ +
+ + +
+
+

def delete(

self, key)

+
+ + + + +

Deletes the mapping (if any) between key +and its corresponding associated value. +If there's no mapping, None is returned.

+
+ +
+
def delete(self, key):
+    """Deletes the mapping (if any) between `key`
+    and its corresponding associated value.
+    If there's no mapping, `None` is returned."""
+    try:
+        i = self.keys.index(key)
+        v = self.values[i]
+        self.keys[i] = self.values[i] = None
+        return v
+    except ValueError:
+        return None
+
+
+
+ +
+ + +
+
+

def get(

self, key)

+
+ + + + +

Returns the value associated with key. +It returns None if there's no value associated with key.

+

If key is None, a TypeError is raised, +because keys cannot be None.

+
+ +
+
def get(self, key):
+    """Returns the value associated with `key`.
+    It returns `None` if there's no value associated with `key`.
+    If `key` is `None`, a `TypeError` is raised,
+    because keys cannot be None."""
+    if key is None:
+        raise TypeError("key cannot be None.")
+    return self._get(key, self.keys, self.values, self.n)
+
+
+
+ +
+ + +
+
+

def hash_code(

self, key, size)

+
+ + + + +

Returns a hash code (an int) between 0 and size (excluded).

+

size must be the size of the buffer based on which +this function should return a hash value.

+
+ +
+
def hash_code(self, key, size: int) -> int:
+    """Returns a hash code (an int) between 0 and `size` (excluded).
+    `size` must be the size of the buffer based on which
+    this function should return a hash value."""
+    return hash(key) % size
+
+
+
+ +
+ + +
+
+

def put(

self, key, value)

+
+ + + + +

Inserts the pair key-value in this map.

+

If key is None, a TypeError is raised, +because keys cannot be None.

+
+ +
+
def put(self, key: object, value: object):
+    """Inserts the pair `key`-`value` in this map.
+    If `key` is `None`, a `TypeError` is raised,
+    because keys cannot be `None`."""
+    if key is None:
+        raise TypeError("key cannot be None.")
+    assert not has_duplicates(self.keys)
+    a = self._put(key, value, self.n)
+    assert not has_duplicates(self.keys)
+    return a
+
+
+
+ +
+ + +
+
+

def rehash(

self, old_hash, size)

+
+ + + + +

Returns a new hash value based on the previous one called old_hash.

+

size must be the size of the buffer based on which +we want to have a new hash value from the old hash value.

+
+ +
+
def rehash(self, old_hash: int, size: int) -> int:
+    """Returns a new hash value based on the previous one called `old_hash`.
+    `size` must be the size of the buffer based on which
+    we want to have a new hash value from the old hash value."""
+    return (old_hash + 1) % size
+
+
+
+ +
+ + +
+
+

def show(

self)

+
+ + + + +

Pretty-prints (using tabulate.tabulate()) this table.

+
+ +
+
def show(self):
+    """Pretty-prints (using `tabulate.tabulate()`) this table."""
+    c = 0
+    data = []
+    for i in range(len(self.keys)):
+        if self.keys[i] is not None:
+            c += 1
+            data.append([c, self.keys[i], self.values[i]])
+    print(tabulate(data, headers=["#", "Keys", "Values"], tablefmt="grid"))
+
+
+
+ +
+ +

Instance variables

+
+

var capacity

+ + + + +

Returns the size of the internal buffers that store the keys and the values.

+
+
+ +
+
+

var keys

+ + + + +
+
+ +
+
+

var n

+ + + + +
+
+ +
+
+

var size

+ + + + +

Returns the number of pairs key-value in this map.

+
+
+ +
+
+

var values

+ + + + +
+
+ +
+
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/MaxHeap.m.html b/docs/ands/ds/MaxHeap.m.html new file mode 100644 index 00000000..b10a43a4 --- /dev/null +++ b/docs/ands/ds/MaxHeap.m.html @@ -0,0 +1,2324 @@ + + + + + + ands.ds.MaxHeap API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.MaxHeap module

+

Meta info

+

Author: Nelson Brochado

+

Created: 15/02/2016

+

Updated: 05/02/2017

+

Description

+

Mirror-class to the MinHeap class. +For more info, see the introductory doc-strings of the file MinHeap.py.

+

References

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+# Meta info
+
+Author: Nelson Brochado
+
+Created: 15/02/2016
+
+Updated: 05/02/2017
+
+# Description
+
+Mirror-class to the MinHeap class.
+For more info, see the introductory doc-strings of the file [`MinHeap.py`](MinHeap.py).
+
+# References
+
+- [https://en.wikipedia.org/wiki/Binary_heap](https://en.wikipedia.org/wiki/Binary_heap)
+- Slides by prof. A. Carzaniga
+- Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS
+
+"""
+
+from ands.ds.heap import BinaryHeap, HeapNode
+
+__all__ = ["MaxHeap", "is_max_heap"]
+
+
+class MaxHeap(BinaryHeap):
+    def __init__(self, ls=None):
+        BinaryHeap.__init__(self, ls)
+
+    def push_down(self, i: int) -> None:
+        """'Max-heapify' this max-heap starting from index `i`.
+
+        **Time Complexity:** O(log2 n)."""
+        m = i
+        l = self.left_index(i)
+        r = self.right_index(i)
+
+        if l != -1 and self.heap[l] > self.heap[m]:
+            m = l
+        if r != -1 and self.heap[r] > self.heap[m]:
+            m = r
+
+        if m != i:
+            self.swap(m, i)
+            self.push_down(m)
+
+    def push_up(self, i: int) -> None:
+        """Pushes up the node at index `i`.
+
+        Note that this operation only happens
+        if the node at index `i` is greater than its parent.
+
+        **Time Complexity:** O(log2 n)."""
+        c = i  # current index
+        p = self.parent_index(i)
+
+        if p != -1 and self.heap[c] > self.heap[p]:
+            c = p
+
+        if c != i:
+            self.swap(c, i)
+            self.push_up(c)
+
+    def find_max(self) -> HeapNode:
+        """Returns (without removing) the greatest element in this max-heap.
+
+        **Time Complexity:** O(1)."""
+        return self.heap[0] if not self.is_empty() else None
+
+    def remove_max(self) -> HeapNode:
+        """Removes and returns the greatest element in this max-heap.
+
+        **Time Complexity:** O(log2 n),
+        if removing the last element of a list is a constant-time operation."""
+        if not self.is_empty():
+            self.swap(0, self.size() - 1)
+            m = self.heap.pop()
+            if not self.is_empty():
+                self.push_down(0)
+            return m
+
+    def delete(self, i: int) -> HeapNode:
+        """Deletes and returns the `HeapNode` object at index `i`.
+
+        `IndexError` is raised if `i` is not a valid index.
+
+        Implementation based on:
+        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
+
+        **Time Complexity:** O(log2 h),
+        where `h` is the number of nodes rooted at `i`."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        if i == self.size() - 1:
+            return self.heap.pop()
+        self.swap(i, self.size() - 1)
+        d = self.heap.pop()
+        self.push_down(i)
+        return d
+
+    def replace(self, i: int, x) -> HeapNode:
+        """Replaces element at index `i` with `x`.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, then a `HeapNode` object
+        first created to represent `x`.
+
+        1. If `x == self.heap[i]`,
+        then just replace `self.heap[i]` with `x`.
+
+        2. Else if `x > self.heap[i]`,
+        then push_up(index).
+
+        3. Else `x < self.heap[i]`,
+        then call `self.push_down(i)`.
+
+        Returns the previous `HeapNode` object at `i`.
+
+        **Time Complexity:** O(log2 n)."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, HeapNode):
+            x = HeapNode(x)
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+
+        c = self.heap[i]
+        self.heap[i] = x
+
+        if x < c:
+            self.push_down(i)
+        elif x > c:
+            self.push_up(i)
+
+        return c
+
+
+def is_max_heap(h) -> bool:
+    """Returns `True` if `h` is a valid `MaxHeap`. `False` otherwise."""
+    if not isinstance(h, MaxHeap):
+        return False
+    if h.heap:
+        for item in h.heap:
+            if not isinstance(item, HeapNode):
+                return False
+        for i, item in enumerate(h.heap):
+            l = h.left_index(i)
+            r = h.right_index(i)
+            if r != -1 and l == -1:
+                return False
+            if l != -1 and item < h.heap[l]:
+                return False
+            if r != -1 and item < h.heap[r]:
+                return False
+    return True
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def is_max_heap(

h)

+
+ + + + +

Returns True if h is a valid MaxHeap. False otherwise.

+
+ +
+
def is_max_heap(h) -> bool:
+    """Returns `True` if `h` is a valid `MaxHeap`. `False` otherwise."""
+    if not isinstance(h, MaxHeap):
+        return False
+    if h.heap:
+        for item in h.heap:
+            if not isinstance(item, HeapNode):
+                return False
+        for i, item in enumerate(h.heap):
+            l = h.left_index(i)
+            r = h.right_index(i)
+            if r != -1 and l == -1:
+                return False
+            if l != -1 and item < h.heap[l]:
+                return False
+            if r != -1 and item < h.heap[r]:
+                return False
+    return True
+
+
+
+ +
+ + +

Classes

+ +
+

class MaxHeap

+ + +

Abstract class to represent binary heaps.

+

MinHeap, MaxHeap and MinMaxHeap all derive from this class.

+
+ +
+
class MaxHeap(BinaryHeap):
+    def __init__(self, ls=None):
+        BinaryHeap.__init__(self, ls)
+
+    def push_down(self, i: int) -> None:
+        """'Max-heapify' this max-heap starting from index `i`.
+
+        **Time Complexity:** O(log2 n)."""
+        m = i
+        l = self.left_index(i)
+        r = self.right_index(i)
+
+        if l != -1 and self.heap[l] > self.heap[m]:
+            m = l
+        if r != -1 and self.heap[r] > self.heap[m]:
+            m = r
+
+        if m != i:
+            self.swap(m, i)
+            self.push_down(m)
+
+    def push_up(self, i: int) -> None:
+        """Pushes up the node at index `i`.
+
+        Note that this operation only happens
+        if the node at index `i` is greater than its parent.
+
+        **Time Complexity:** O(log2 n)."""
+        c = i  # current index
+        p = self.parent_index(i)
+
+        if p != -1 and self.heap[c] > self.heap[p]:
+            c = p
+
+        if c != i:
+            self.swap(c, i)
+            self.push_up(c)
+
+    def find_max(self) -> HeapNode:
+        """Returns (without removing) the greatest element in this max-heap.
+
+        **Time Complexity:** O(1)."""
+        return self.heap[0] if not self.is_empty() else None
+
+    def remove_max(self) -> HeapNode:
+        """Removes and returns the greatest element in this max-heap.
+
+        **Time Complexity:** O(log2 n),
+        if removing the last element of a list is a constant-time operation."""
+        if not self.is_empty():
+            self.swap(0, self.size() - 1)
+            m = self.heap.pop()
+            if not self.is_empty():
+                self.push_down(0)
+            return m
+
+    def delete(self, i: int) -> HeapNode:
+        """Deletes and returns the `HeapNode` object at index `i`.
+
+        `IndexError` is raised if `i` is not a valid index.
+
+        Implementation based on:
+        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
+
+        **Time Complexity:** O(log2 h),
+        where `h` is the number of nodes rooted at `i`."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        if i == self.size() - 1:
+            return self.heap.pop()
+        self.swap(i, self.size() - 1)
+        d = self.heap.pop()
+        self.push_down(i)
+        return d
+
+    def replace(self, i: int, x) -> HeapNode:
+        """Replaces element at index `i` with `x`.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, then a `HeapNode` object
+        first created to represent `x`.
+
+        1. If `x == self.heap[i]`,
+        then just replace `self.heap[i]` with `x`.
+
+        2. Else if `x > self.heap[i]`,
+        then push_up(index).
+
+        3. Else `x < self.heap[i]`,
+        then call `self.push_down(i)`.
+
+        Returns the previous `HeapNode` object at `i`.
+
+        **Time Complexity:** O(log2 n)."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, HeapNode):
+            x = HeapNode(x)
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+
+        c = self.heap[i]
+        self.heap[i] = x
+
+        if x < c:
+            self.push_down(i)
+        elif x > c:
+            self.push_up(i)
+
+        return c
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • MaxHeap
  • +
  • ands.ds.heap.BinaryHeap
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, ls=None)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, ls=None):
+    BinaryHeap.__init__(self, ls)
+
+
+
+ +
+ + +
+
+

def add(

self, x)

+
+ + + + +

Adds x to this heap.

+

In practice, it places x at an available leaf, +then "bubbles up" from there, +in order to maintain the heap property.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(log2 n).

+
+ +
+
def add(self, x) -> None:
+    """Adds `x` to this heap.
+    In practice, it places `x` at an available leaf,
+    then "bubbles up" from there,
+    in order to maintain the heap property.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(log2 n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    self.heap.append(x)
+    if self.size() > 1:
+        self.push_up(self.size() - 1)
+
+
+
+ +
+ + +
+
+

def build_heap(

self)

+
+ + + + +

Builds the heap data structure from self.heap.

+
+ +
+
def build_heap(self) -> list:
+    """Builds the heap data structure from `self.heap`."""
+    if self.heap:
+        for index in range(len(self.heap) // 2, -1, -1):
+            self.push_down(index)
+    return self.heap
+
+
+
+ +
+ + +
+
+

def clear(

self)

+
+ + + + +

Clears all nodes from this heap. +This mean that if you call is_empty, +it will return True.

+

Time Complexity: O(1).

+
+ +
+
def clear(self) -> None:
+    """Clears all nodes from this heap.
+    This mean that if you call `is_empty`,
+    it will return `True`.
+    **Time Complexity:** O(1)."""
+    self.heap.clear()
+
+
+
+ +
+ + +
+
+

def contains(

self, x)

+
+ + + + +

Returns True, if x is in this heap. False otherwise.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(n).

+
+ +
+
def contains(self, x) -> bool:
+    """Returns `True`, if `x` is in this heap. `False` otherwise.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(n)."""
+    return self.search(x) != -1
+
+
+
+ +
+ + +
+
+

def delete(

self, i)

+
+ + + + +

Deletes and returns the HeapNode object at index i.

+

IndexError is raised if i is not a valid index.

+

Implementation based on: +http://www.math.clemson.edu/~warner/M865/HeapDelete.html

+

Time Complexity: O(log2 h), +where h is the number of nodes rooted at i.

+
+ +
+
def delete(self, i: int) -> HeapNode:
+    """Deletes and returns the `HeapNode` object at index `i`.
+    `IndexError` is raised if `i` is not a valid index.
+    Implementation based on:
+    [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
+    **Time Complexity:** O(log2 h),
+    where `h` is the number of nodes rooted at `i`."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    if i == self.size() - 1:
+        return self.heap.pop()
+    self.swap(i, self.size() - 1)
+    d = self.heap.pop()
+    self.push_down(i)
+    return d
+
+
+
+ +
+ + +
+
+

def find_max(

self)

+
+ + + + +

Returns (without removing) the greatest element in this max-heap.

+

Time Complexity: O(1).

+
+ +
+
def find_max(self) -> HeapNode:
+    """Returns (without removing) the greatest element in this max-heap.
+    **Time Complexity:** O(1)."""
+    return self.heap[0] if not self.is_empty() else None
+
+
+
+ +
+ + +
+
+

def grandparent_index(

self, i)

+
+ + + + +

Returns the grandparent's index of the node at index i.

+

-1 is returned either if i has not a parent or +the parent of i does not have a parent.

+

Time Complexity: O(1).

+
+ +
+
def grandparent_index(self, i: int) -> int:
+    """Returns the grandparent's index of the node at index `i`.
+    -1 is returned either if `i` has not a parent or
+    the parent of `i` does not have a parent.
+    **Time Complexity:** O(1)."""
+    p = self.parent_index(i)
+    return -1 if p == -1 else self.parent_index(p)
+
+
+
+ +
+ + +
+
+

def has_children(

self, i)

+
+ + + + +

Returns True if the node at index i +has at least one child, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def has_children(self, i: int) -> bool:
+    """Returns `True` if the node at index `i`
+    has at least one child, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return self.left_index(i) != -1 or self.right_index(i) != -1
+
+
+
+ +
+ + +
+
+

def is_child(

self, c, i)

+
+ + + + +

Returns True if c is a child of i. False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_child(self, c: int, i: int) -> bool:
+    """Returns `True` if `c` is a child of `i`. `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(c) or not self.is_good_index(i):
+        raise IndexError("i or c are not valid indexes.")
+    return c == self.left_index(i) or c == self.right_index(i)
+
+
+
+ +
+ + +
+
+

def is_empty(

self)

+
+ + + + +

Returns True if this heap is empty.

+

Time Complexity: O(1).

+
+ +
+
def is_empty(self) -> bool:
+    """Returns `True` if this heap is empty.
+    **Time Complexity:** O(1)."""
+    return self.size() == 0
+
+
+
+ +
+ + +
+
+

def is_good_index(

self, i)

+
+ + + + +

Returns True if i is valid index for self.heap, +False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_good_index(self, i: int) -> bool:
+    """Returns `True` if `i` is valid index for `self.heap`,
+    `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not isinstance(i, int):
+        raise TypeError("indexes can only be int.")
+    return False if (i < 0 or i >= self.size()) else True
+
+
+
+ +
+ + +
+
+

def is_grandchild(

self, g, i)

+
+ + + + +

Returns True if g is a grandchild of i. False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_grandchild(self, g: int, i: int) -> bool:
+    """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
+    **Time Complexity:** O(1)."""
+    l = self.left_index(i)
+    if l == -1:
+        assert self.right_index(i) == -1
+        if not self.is_good_index(g):
+            raise IndexError("g is not a valid index.")
+        return False
+    r = self.right_index(i)
+    if r == -1:
+        return self.is_child(g, l)
+    else:
+        return self.is_child(g, l) or self.is_child(g, r)
+
+
+
+ +
+ + +
+
+

def is_grandparent(

self, g, i)

+
+ + + + +

Returns True if g is the index of the grandparent +of the node at i, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_grandparent(self, g: int, i: int) -> bool:
+    """Returns `True` if `g` is the index of the grandparent
+    of the node at `i`, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(g):
+        raise IndexError("g is not a valid index.")
+    p = self.parent_index(i)
+    return False if p == -1 else self.is_parent(g, p)
+
+
+
+ +
+ + +
+
+

def is_on_even_level(

self, i)

+
+ + + + +

Returns True if node at index i is on a even-level, +i.e., if i is on a level multiple of 2 (0, 2, 4, 6,...). +If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(int(math.log2(i + 1) % 2) == 0).

+
+ +
+
def is_on_even_level(self, i: int) -> bool:
+    """Returns `True` if node at index `i` is on a even-level,
+    i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return int(math.log2(i + 1) % 2) == 0
+
+
+
+ +
+ + +
+
+

def is_on_odd_level(

self, i)

+
+ + + + +

Returns True (False) if self.is_on_even_level(i) returns False (True).

+
+ +
+
def is_on_odd_level(self, i: int) -> bool:
+    """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
+    return not self.is_on_even_level(i)
+
+
+
+ +
+ + +
+
+

def is_parent(

self, p, i)

+
+ + + + +

Returns True if p is the index of the parent +of the node at i, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_parent(self, p: int, i: int) -> bool:
+    """Returns `True` if `p` is the index of the parent
+    of the node at `i`, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(p):
+        raise IndexError("p is not a valid index.")
+    return self.parent_index(i) == p
+
+
+
+ +
+ + +
+
+

def left_index(

self, i)

+
+ + + + +

Returns the left child's index of the node at index i, +if it exists, otherwise this function returns -1.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def left_index(self, i: int) -> int:
+    """Returns the left child's index of the node at index `i`,
+    if it exists, otherwise this function returns -1.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    left = i * 2 + 1
+    return left if self.is_good_index(left) else -1
+
+
+
+ +
+ + +
+
+

def merge(

self, o)

+
+ + + + +

Merges this heap with the o heap.

+

Returns the list object representing internally the new merged heap.

+

Time Complexity: O(n + m).

+

Time complexity analysis based on: +http://stackoverflow.com/a/29197855/3924118.

+
+ +
+
def merge(self, o) -> list:
+    """Merges this heap with the `o` heap.
+    Returns the `list` object representing internally the new merged heap.
+    **Time Complexity:** O(n + m).
+    Time complexity analysis based on:
+    [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
+    self.heap += o.heap
+    return self.build_heap()
+
+
+
+ +
+ + +
+
+

def parent_index(

self, i)

+
+ + + + +

Returns the parent's index of the node at index i. +If i = 0, then -1 is returned, because the root has no parent.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def parent_index(self, i: int) -> int:
+    """Returns the parent's index of the node at index `i`.
+    If `i = 0`, then -1 is returned, because the root has no parent.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return -1 if i == 0 else (i - 1) // 2
+
+
+
+ +
+ + +
+
+

def push_down(

self, i)

+
+ + + + +

'Max-heapify' this max-heap starting from index i.

+

Time Complexity: O(log2 n).

+
+ +
+
def push_down(self, i: int) -> None:
+    """'Max-heapify' this max-heap starting from index `i`.
+    **Time Complexity:** O(log2 n)."""
+    m = i
+    l = self.left_index(i)
+    r = self.right_index(i)
+    if l != -1 and self.heap[l] > self.heap[m]:
+        m = l
+    if r != -1 and self.heap[r] > self.heap[m]:
+        m = r
+    if m != i:
+        self.swap(m, i)
+        self.push_down(m)
+
+
+
+ +
+ + +
+
+

def push_up(

self, i)

+
+ + + + +

Pushes up the node at index i.

+

Note that this operation only happens +if the node at index i is greater than its parent.

+

Time Complexity: O(log2 n).

+
+ +
+
def push_up(self, i: int) -> None:
+    """Pushes up the node at index `i`.
+    Note that this operation only happens
+    if the node at index `i` is greater than its parent.
+    **Time Complexity:** O(log2 n)."""
+    c = i  # current index
+    p = self.parent_index(i)
+    if p != -1 and self.heap[c] > self.heap[p]:
+        c = p
+    if c != i:
+        self.swap(c, i)
+        self.push_up(c)
+
+
+
+ +
+ + +
+
+

def remove_max(

self)

+
+ + + + +

Removes and returns the greatest element in this max-heap.

+

Time Complexity: O(log2 n), +if removing the last element of a list is a constant-time operation.

+
+ +
+
def remove_max(self) -> HeapNode:
+    """Removes and returns the greatest element in this max-heap.
+    **Time Complexity:** O(log2 n),
+    if removing the last element of a list is a constant-time operation."""
+    if not self.is_empty():
+        self.swap(0, self.size() - 1)
+        m = self.heap.pop()
+        if not self.is_empty():
+            self.push_down(0)
+        return m
+
+
+
+ +
+ + +
+
+

def replace(

self, i, x)

+
+ + + + +

Replaces element at index i with x.

+

x can either be a key or a HeapNode object. +If it's a key, then a HeapNode object +first created to represent x.

+
    +
  1. +

    If x == self.heap[i], +then just replace self.heap[i] with x.

    +
  2. +
  3. +

    Else if x > self.heap[i], +then push_up(index).

    +
  4. +
  5. +

    Else x < self.heap[i], +then call self.push_down(i).

    +
  6. +
+

Returns the previous HeapNode object at i.

+

Time Complexity: O(log2 n).

+
+ +
+
def replace(self, i: int, x) -> HeapNode:
+    """Replaces element at index `i` with `x`.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, then a `HeapNode` object
+    first created to represent `x`.
+    1. If `x == self.heap[i]`,
+    then just replace `self.heap[i]` with `x`.
+    2. Else if `x > self.heap[i]`,
+    then push_up(index).
+    3. Else `x < self.heap[i]`,
+    then call `self.push_down(i)`.
+    Returns the previous `HeapNode` object at `i`.
+    **Time Complexity:** O(log2 n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    c = self.heap[i]
+    self.heap[i] = x
+    if x < c:
+        self.push_down(i)
+    elif x > c:
+        self.push_up(i)
+    return c
+
+
+
+ +
+ + +
+
+

def right_index(

self, i)

+
+ + + + +

Returns the right child's index of the node at index i, +if it exists, otherwise this function returns -1.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def right_index(self, i: int) -> int:
+    """Returns the right child's index of the node at index `i`,
+    if it exists, otherwise this function returns -1.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    right = i * 2 + 2
+    return right if self.is_good_index(right) else -1
+
+
+
+ +
+ + +
+
+

def search(

self, x)

+
+ + + + +

Searches for x in this heap, +and, if present, returns its index, otherwise returns -1.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(n).

+
+ +
+
def search(self, x) -> int:
+    """Searches for `x` in this heap,
+    and, if present, returns its index, otherwise returns -1.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    for i, node in enumerate(self.heap):
+        if node == x:
+            return i
+    return -1
+
+
+
+ +
+ + +
+
+

def search_by_value(

self, val)

+
+ + + + +

Returns the index of the HeapNode object with value=val. +-1 is returned if no such a HeapNode object exists.

+

If val and the values in this heap are not comparable, +the behaviour of this method is undefined.

+

By construction, HeapNode objects can't be initialized with None values, +but that field could also be set manually after creation.

+

Time Complexity: O(n).

+
+ +
+
def search_by_value(self, val) -> int:
+    """Returns the index of the `HeapNode` object with `value=val`.
+    -1 is returned if no such a `HeapNode` object exists.
+    If `val` and the values in this heap are not comparable,
+    the behaviour of this method is undefined.
+    By construction, HeapNode objects can't be initialized with None values,
+    but that field could also be set manually after creation.
+    **Time Complexity:** O(n)."""
+    for i, node in enumerate(self.heap):
+        if node.value == val:
+            return i
+    return -1
+
+
+
+ +
+ + +
+
+

def size(

self)

+
+ + + + +

Returns the size of this heaps.

+

Time Complexity: O(1).

+
+ +
+
def size(self) -> int:
+    """Returns the size of this heaps.
+    **Time Complexity:** O(1)."""
+    return len(self.heap)
+
+
+
+ +
+ + +
+
+

def swap(

self, i, j)

+
+ + + + +

Swaps elements at indexes i and j, +if they are valid indexes, +otherwise an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def swap(self, i: int, j: int) -> None:
+    """Swaps elements at indexes `i` and `j`,
+    if they are valid indexes,
+    otherwise an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if self.is_good_index(i) and self.is_good_index(j):
+        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
+    else:
+        raise IndexError("i or j are not valid indexes.")
+
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/MinHeap.m.html b/docs/ands/ds/MinHeap.m.html new file mode 100644 index 00000000..53bcb3cf --- /dev/null +++ b/docs/ands/ds/MinHeap.m.html @@ -0,0 +1,2374 @@ + + + + + + ands.ds.MinHeap API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.MinHeap module

+

Meta info

+

Author: Nelson Brochado

+

Created: 01/07/2015

+

Updated: 05/02/2017

+

Description

+

A binary min-heap is a data structure similar to a binary tree, +where the parent nodes are smaller or equal to their children.

+

In addition to the previous constraint, a binary min-heap is a complete binary tree, +that is, all levels of the tree, except possibly the deepest one are fully filled, +and, if the last level of the tree is not complete, +the nodes of that level are filled from left to right.

+

A min-heap can be implemented with a classic array or list in Python.

+

If we have a node at index i, then

+
    +
  • +

    its left child can be found at index i*2 + 1

    +
  • +
  • +

    its right child is found at i*2 + 2,

    +
  • +
  • +

    its parent can be found at index floor((i - 1) / 2), +where floor(x) truncates x to the smallest integer.

    +
  • +
+

Note that these indexes are for 0-index based lists (or arrays).

+

References

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+# Meta info
+
+Author: Nelson Brochado
+
+Created: 01/07/2015
+
+Updated: 05/02/2017
+
+# Description
+
+A binary min-heap is a data structure similar to a binary tree,
+where the parent nodes are smaller or equal to their children.
+
+In addition to the previous constraint, a binary min-heap is a complete binary tree,
+that is, all levels of the tree, except possibly the deepest one are fully filled,
+and, if the last level of the tree is not complete,
+the nodes of that level are filled from left to right.
+
+A min-heap can be implemented with a classic array or list in Python.
+
+If we have a node at index i, then
+
+- its left child can be found at index i*2 + 1
+
+- its right child is found at i*2 + 2,
+
+- its parent can be found at index floor((i - 1) / 2),
+where floor(x) truncates x to the smallest integer.
+
+Note that these indexes are for 0-index based lists (or arrays).
+
+# References
+
+- [https://en.wikipedia.org/wiki/Binary_heap](https://en.wikipedia.org/wiki/Binary_heap)
+- Slides by prof. A. Carzaniga
+- Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS
+"""
+
+from ands.ds.heap import BinaryHeap, HeapNode
+
+__all__ = ["MinHeap", "is_min_heap"]
+
+
+class MinHeap(BinaryHeap):
+    def __init__(self, ls=None):
+        BinaryHeap.__init__(self, ls)
+
+    def push_down(self, i: int) -> None:
+        """'Min-heapify' this min-heap starting from index `i`.
+
+        **Time Complexity:** O(log2 n)."""
+        m = i  # index of node with smallest value among i and its children
+        l = self.left_index(i)
+        r = self.right_index(i)
+
+        if l != -1 and self.heap[l] < self.heap[m]:
+            m = l
+        if r != -1 and self.heap[r] < self.heap[m]:
+            m = r
+
+        if m != i:
+            self.swap(m, i)
+            self.push_down(m)
+
+    def push_up(self, i: int) -> None:
+        """Pushes up the node at index `i`.
+
+        Note that this operation only happens
+        if the node at index `i` is smaller than its parent.
+
+        This function is simpler than `push_down` (or also called min-heapify),
+        because in this case we just need to compare
+        the current node's index with its parent's index.
+
+        **Time Complexity:** O(log2 n)."""
+        c = i  # current index
+        p = self.parent_index(i)
+
+        if p != -1 and self.heap[c] < self.heap[p]:
+            c = p
+
+        if c != i:
+            self.swap(c, i)
+            self.push_up(c)
+
+    def find_min(self) -> HeapNode:
+        """Returns (without removing) the smallest element in this min-heap.
+
+        **Time Complexity:** O(1)."""
+        return self.heap[0] if not self.is_empty() else None
+
+    def remove_min(self) -> HeapNode:
+        """Removes and returns the smallest element in this heap.
+
+        **Time Complexity:** O(log2 n),
+        if removing the last element of a list is a constant-time operation."""
+        if not self.is_empty():
+            self.swap(0, self.size() - 1)
+            m = self.heap.pop()
+            if not self.is_empty():
+                self.push_down(0)
+            return m
+
+    def delete(self, i: int) -> HeapNode:
+        """Deletes and returns the `HeapNode` object at index `i`.
+
+        `IndexError` is raised if `i` is not a valid index.
+
+        Implementation based on:
+        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
+
+        **Time Complexity:** O(log2 h),
+        where `h` is the number of nodes rooted at `i`."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        if i == self.size() - 1:
+            return self.heap.pop()
+        self.swap(i, self.size() - 1)
+        d = self.heap.pop()
+        self.push_down(i)
+        return d
+
+    def replace(self, i: int, x) -> HeapNode:
+        """Replaces element at index `i` with `x`.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, then a `HeapNode` object
+        first created to represent `x`.
+
+        1. If `x == self.heap[i]`,
+        then just replace `self.heap[i]` with `x`.
+
+        2. Else if `x < self.heap[i]`,
+        then push_up(index).
+
+        3. Else `x > self.heap[i]`,
+        then call `self.push_down(i)`.
+
+        Returns the previous `HeapNode` object at `i`.
+
+        **Time Complexity:** O(log2 n)."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, HeapNode):
+            x = HeapNode(x)
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+
+        c = self.heap[i]
+        self.heap[i] = x
+
+        if x > c:
+            self.push_down(i)
+        elif x < c:
+            self.push_up(i)
+
+        return c
+
+
+def is_min_heap(h) -> bool:
+    """Returns `True` if `h` is a valid `MinHeap`. `False` otherwise."""
+    if not isinstance(h, MinHeap):
+        return False
+    if h.heap:
+        for item in h.heap:
+            if not isinstance(item, HeapNode):
+                return False
+        for i, item in enumerate(h.heap):
+            l = h.left_index(i)
+            r = h.right_index(i)
+            if r != -1 and l == -1:
+                return False
+            if l != -1 and item > h.heap[l]:
+                return False
+            if r != -1 and item > h.heap[r]:
+                return False
+    return True  # h is empty
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def is_min_heap(

h)

+
+ + + + +

Returns True if h is a valid MinHeap. False otherwise.

+
+ +
+
def is_min_heap(h) -> bool:
+    """Returns `True` if `h` is a valid `MinHeap`. `False` otherwise."""
+    if not isinstance(h, MinHeap):
+        return False
+    if h.heap:
+        for item in h.heap:
+            if not isinstance(item, HeapNode):
+                return False
+        for i, item in enumerate(h.heap):
+            l = h.left_index(i)
+            r = h.right_index(i)
+            if r != -1 and l == -1:
+                return False
+            if l != -1 and item > h.heap[l]:
+                return False
+            if r != -1 and item > h.heap[r]:
+                return False
+    return True  # h is empty
+
+
+
+ +
+ + +

Classes

+ +
+

class MinHeap

+ + +

Abstract class to represent binary heaps.

+

MinHeap, MaxHeap and MinMaxHeap all derive from this class.

+
+ +
+
class MinHeap(BinaryHeap):
+    def __init__(self, ls=None):
+        BinaryHeap.__init__(self, ls)
+
+    def push_down(self, i: int) -> None:
+        """'Min-heapify' this min-heap starting from index `i`.
+
+        **Time Complexity:** O(log2 n)."""
+        m = i  # index of node with smallest value among i and its children
+        l = self.left_index(i)
+        r = self.right_index(i)
+
+        if l != -1 and self.heap[l] < self.heap[m]:
+            m = l
+        if r != -1 and self.heap[r] < self.heap[m]:
+            m = r
+
+        if m != i:
+            self.swap(m, i)
+            self.push_down(m)
+
+    def push_up(self, i: int) -> None:
+        """Pushes up the node at index `i`.
+
+        Note that this operation only happens
+        if the node at index `i` is smaller than its parent.
+
+        This function is simpler than `push_down` (or also called min-heapify),
+        because in this case we just need to compare
+        the current node's index with its parent's index.
+
+        **Time Complexity:** O(log2 n)."""
+        c = i  # current index
+        p = self.parent_index(i)
+
+        if p != -1 and self.heap[c] < self.heap[p]:
+            c = p
+
+        if c != i:
+            self.swap(c, i)
+            self.push_up(c)
+
+    def find_min(self) -> HeapNode:
+        """Returns (without removing) the smallest element in this min-heap.
+
+        **Time Complexity:** O(1)."""
+        return self.heap[0] if not self.is_empty() else None
+
+    def remove_min(self) -> HeapNode:
+        """Removes and returns the smallest element in this heap.
+
+        **Time Complexity:** O(log2 n),
+        if removing the last element of a list is a constant-time operation."""
+        if not self.is_empty():
+            self.swap(0, self.size() - 1)
+            m = self.heap.pop()
+            if not self.is_empty():
+                self.push_down(0)
+            return m
+
+    def delete(self, i: int) -> HeapNode:
+        """Deletes and returns the `HeapNode` object at index `i`.
+
+        `IndexError` is raised if `i` is not a valid index.
+
+        Implementation based on:
+        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
+
+        **Time Complexity:** O(log2 h),
+        where `h` is the number of nodes rooted at `i`."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        if i == self.size() - 1:
+            return self.heap.pop()
+        self.swap(i, self.size() - 1)
+        d = self.heap.pop()
+        self.push_down(i)
+        return d
+
+    def replace(self, i: int, x) -> HeapNode:
+        """Replaces element at index `i` with `x`.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, then a `HeapNode` object
+        first created to represent `x`.
+
+        1. If `x == self.heap[i]`,
+        then just replace `self.heap[i]` with `x`.
+
+        2. Else if `x < self.heap[i]`,
+        then push_up(index).
+
+        3. Else `x > self.heap[i]`,
+        then call `self.push_down(i)`.
+
+        Returns the previous `HeapNode` object at `i`.
+
+        **Time Complexity:** O(log2 n)."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, HeapNode):
+            x = HeapNode(x)
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+
+        c = self.heap[i]
+        self.heap[i] = x
+
+        if x > c:
+            self.push_down(i)
+        elif x < c:
+            self.push_up(i)
+
+        return c
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • MinHeap
  • +
  • ands.ds.heap.BinaryHeap
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, ls=None)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, ls=None):
+    BinaryHeap.__init__(self, ls)
+
+
+
+ +
+ + +
+
+

def add(

self, x)

+
+ + + + +

Adds x to this heap.

+

In practice, it places x at an available leaf, +then "bubbles up" from there, +in order to maintain the heap property.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(log2 n).

+
+ +
+
def add(self, x) -> None:
+    """Adds `x` to this heap.
+    In practice, it places `x` at an available leaf,
+    then "bubbles up" from there,
+    in order to maintain the heap property.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(log2 n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    self.heap.append(x)
+    if self.size() > 1:
+        self.push_up(self.size() - 1)
+
+
+
+ +
+ + +
+
+

def build_heap(

self)

+
+ + + + +

Builds the heap data structure from self.heap.

+
+ +
+
def build_heap(self) -> list:
+    """Builds the heap data structure from `self.heap`."""
+    if self.heap:
+        for index in range(len(self.heap) // 2, -1, -1):
+            self.push_down(index)
+    return self.heap
+
+
+
+ +
+ + +
+
+

def clear(

self)

+
+ + + + +

Clears all nodes from this heap. +This mean that if you call is_empty, +it will return True.

+

Time Complexity: O(1).

+
+ +
+
def clear(self) -> None:
+    """Clears all nodes from this heap.
+    This mean that if you call `is_empty`,
+    it will return `True`.
+    **Time Complexity:** O(1)."""
+    self.heap.clear()
+
+
+
+ +
+ + +
+
+

def contains(

self, x)

+
+ + + + +

Returns True, if x is in this heap. False otherwise.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(n).

+
+ +
+
def contains(self, x) -> bool:
+    """Returns `True`, if `x` is in this heap. `False` otherwise.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(n)."""
+    return self.search(x) != -1
+
+
+
+ +
+ + +
+
+

def delete(

self, i)

+
+ + + + +

Deletes and returns the HeapNode object at index i.

+

IndexError is raised if i is not a valid index.

+

Implementation based on: +http://www.math.clemson.edu/~warner/M865/HeapDelete.html

+

Time Complexity: O(log2 h), +where h is the number of nodes rooted at i.

+
+ +
+
def delete(self, i: int) -> HeapNode:
+    """Deletes and returns the `HeapNode` object at index `i`.
+    `IndexError` is raised if `i` is not a valid index.
+    Implementation based on:
+    [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
+    **Time Complexity:** O(log2 h),
+    where `h` is the number of nodes rooted at `i`."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    if i == self.size() - 1:
+        return self.heap.pop()
+    self.swap(i, self.size() - 1)
+    d = self.heap.pop()
+    self.push_down(i)
+    return d
+
+
+
+ +
+ + +
+
+

def find_min(

self)

+
+ + + + +

Returns (without removing) the smallest element in this min-heap.

+

Time Complexity: O(1).

+
+ +
+
def find_min(self) -> HeapNode:
+    """Returns (without removing) the smallest element in this min-heap.
+    **Time Complexity:** O(1)."""
+    return self.heap[0] if not self.is_empty() else None
+
+
+
+ +
+ + +
+
+

def grandparent_index(

self, i)

+
+ + + + +

Returns the grandparent's index of the node at index i.

+

-1 is returned either if i has not a parent or +the parent of i does not have a parent.

+

Time Complexity: O(1).

+
+ +
+
def grandparent_index(self, i: int) -> int:
+    """Returns the grandparent's index of the node at index `i`.
+    -1 is returned either if `i` has not a parent or
+    the parent of `i` does not have a parent.
+    **Time Complexity:** O(1)."""
+    p = self.parent_index(i)
+    return -1 if p == -1 else self.parent_index(p)
+
+
+
+ +
+ + +
+
+

def has_children(

self, i)

+
+ + + + +

Returns True if the node at index i +has at least one child, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def has_children(self, i: int) -> bool:
+    """Returns `True` if the node at index `i`
+    has at least one child, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return self.left_index(i) != -1 or self.right_index(i) != -1
+
+
+
+ +
+ + +
+
+

def is_child(

self, c, i)

+
+ + + + +

Returns True if c is a child of i. False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_child(self, c: int, i: int) -> bool:
+    """Returns `True` if `c` is a child of `i`. `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(c) or not self.is_good_index(i):
+        raise IndexError("i or c are not valid indexes.")
+    return c == self.left_index(i) or c == self.right_index(i)
+
+
+
+ +
+ + +
+
+

def is_empty(

self)

+
+ + + + +

Returns True if this heap is empty.

+

Time Complexity: O(1).

+
+ +
+
def is_empty(self) -> bool:
+    """Returns `True` if this heap is empty.
+    **Time Complexity:** O(1)."""
+    return self.size() == 0
+
+
+
+ +
+ + +
+
+

def is_good_index(

self, i)

+
+ + + + +

Returns True if i is valid index for self.heap, +False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_good_index(self, i: int) -> bool:
+    """Returns `True` if `i` is valid index for `self.heap`,
+    `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not isinstance(i, int):
+        raise TypeError("indexes can only be int.")
+    return False if (i < 0 or i >= self.size()) else True
+
+
+
+ +
+ + +
+
+

def is_grandchild(

self, g, i)

+
+ + + + +

Returns True if g is a grandchild of i. False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_grandchild(self, g: int, i: int) -> bool:
+    """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
+    **Time Complexity:** O(1)."""
+    l = self.left_index(i)
+    if l == -1:
+        assert self.right_index(i) == -1
+        if not self.is_good_index(g):
+            raise IndexError("g is not a valid index.")
+        return False
+    r = self.right_index(i)
+    if r == -1:
+        return self.is_child(g, l)
+    else:
+        return self.is_child(g, l) or self.is_child(g, r)
+
+
+
+ +
+ + +
+
+

def is_grandparent(

self, g, i)

+
+ + + + +

Returns True if g is the index of the grandparent +of the node at i, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_grandparent(self, g: int, i: int) -> bool:
+    """Returns `True` if `g` is the index of the grandparent
+    of the node at `i`, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(g):
+        raise IndexError("g is not a valid index.")
+    p = self.parent_index(i)
+    return False if p == -1 else self.is_parent(g, p)
+
+
+
+ +
+ + +
+
+

def is_on_even_level(

self, i)

+
+ + + + +

Returns True if node at index i is on a even-level, +i.e., if i is on a level multiple of 2 (0, 2, 4, 6,...). +If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(int(math.log2(i + 1) % 2) == 0).

+
+ +
+
def is_on_even_level(self, i: int) -> bool:
+    """Returns `True` if node at index `i` is on a even-level,
+    i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return int(math.log2(i + 1) % 2) == 0
+
+
+
+ +
+ + +
+
+

def is_on_odd_level(

self, i)

+
+ + + + +

Returns True (False) if self.is_on_even_level(i) returns False (True).

+
+ +
+
def is_on_odd_level(self, i: int) -> bool:
+    """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
+    return not self.is_on_even_level(i)
+
+
+
+ +
+ + +
+
+

def is_parent(

self, p, i)

+
+ + + + +

Returns True if p is the index of the parent +of the node at i, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_parent(self, p: int, i: int) -> bool:
+    """Returns `True` if `p` is the index of the parent
+    of the node at `i`, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(p):
+        raise IndexError("p is not a valid index.")
+    return self.parent_index(i) == p
+
+
+
+ +
+ + +
+
+

def left_index(

self, i)

+
+ + + + +

Returns the left child's index of the node at index i, +if it exists, otherwise this function returns -1.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def left_index(self, i: int) -> int:
+    """Returns the left child's index of the node at index `i`,
+    if it exists, otherwise this function returns -1.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    left = i * 2 + 1
+    return left if self.is_good_index(left) else -1
+
+
+
+ +
+ + +
+
+

def merge(

self, o)

+
+ + + + +

Merges this heap with the o heap.

+

Returns the list object representing internally the new merged heap.

+

Time Complexity: O(n + m).

+

Time complexity analysis based on: +http://stackoverflow.com/a/29197855/3924118.

+
+ +
+
def merge(self, o) -> list:
+    """Merges this heap with the `o` heap.
+    Returns the `list` object representing internally the new merged heap.
+    **Time Complexity:** O(n + m).
+    Time complexity analysis based on:
+    [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
+    self.heap += o.heap
+    return self.build_heap()
+
+
+
+ +
+ + +
+
+

def parent_index(

self, i)

+
+ + + + +

Returns the parent's index of the node at index i. +If i = 0, then -1 is returned, because the root has no parent.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def parent_index(self, i: int) -> int:
+    """Returns the parent's index of the node at index `i`.
+    If `i = 0`, then -1 is returned, because the root has no parent.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return -1 if i == 0 else (i - 1) // 2
+
+
+
+ +
+ + +
+
+

def push_down(

self, i)

+
+ + + + +

'Min-heapify' this min-heap starting from index i.

+

Time Complexity: O(log2 n).

+
+ +
+
def push_down(self, i: int) -> None:
+    """'Min-heapify' this min-heap starting from index `i`.
+    **Time Complexity:** O(log2 n)."""
+    m = i  # index of node with smallest value among i and its children
+    l = self.left_index(i)
+    r = self.right_index(i)
+    if l != -1 and self.heap[l] < self.heap[m]:
+        m = l
+    if r != -1 and self.heap[r] < self.heap[m]:
+        m = r
+    if m != i:
+        self.swap(m, i)
+        self.push_down(m)
+
+
+
+ +
+ + +
+
+

def push_up(

self, i)

+
+ + + + +

Pushes up the node at index i.

+

Note that this operation only happens +if the node at index i is smaller than its parent.

+

This function is simpler than push_down (or also called min-heapify), +because in this case we just need to compare +the current node's index with its parent's index.

+

Time Complexity: O(log2 n).

+
+ +
+
def push_up(self, i: int) -> None:
+    """Pushes up the node at index `i`.
+    Note that this operation only happens
+    if the node at index `i` is smaller than its parent.
+    This function is simpler than `push_down` (or also called min-heapify),
+    because in this case we just need to compare
+    the current node's index with its parent's index.
+    **Time Complexity:** O(log2 n)."""
+    c = i  # current index
+    p = self.parent_index(i)
+    if p != -1 and self.heap[c] < self.heap[p]:
+        c = p
+    if c != i:
+        self.swap(c, i)
+        self.push_up(c)
+
+
+
+ +
+ + +
+
+

def remove_min(

self)

+
+ + + + +

Removes and returns the smallest element in this heap.

+

Time Complexity: O(log2 n), +if removing the last element of a list is a constant-time operation.

+
+ +
+
def remove_min(self) -> HeapNode:
+    """Removes and returns the smallest element in this heap.
+    **Time Complexity:** O(log2 n),
+    if removing the last element of a list is a constant-time operation."""
+    if not self.is_empty():
+        self.swap(0, self.size() - 1)
+        m = self.heap.pop()
+        if not self.is_empty():
+            self.push_down(0)
+        return m
+
+
+
+ +
+ + +
+
+

def replace(

self, i, x)

+
+ + + + +

Replaces element at index i with x.

+

x can either be a key or a HeapNode object. +If it's a key, then a HeapNode object +first created to represent x.

+
    +
  1. +

    If x == self.heap[i], +then just replace self.heap[i] with x.

    +
  2. +
  3. +

    Else if x < self.heap[i], +then push_up(index).

    +
  4. +
  5. +

    Else x > self.heap[i], +then call self.push_down(i).

    +
  6. +
+

Returns the previous HeapNode object at i.

+

Time Complexity: O(log2 n).

+
+ +
+
def replace(self, i: int, x) -> HeapNode:
+    """Replaces element at index `i` with `x`.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, then a `HeapNode` object
+    first created to represent `x`.
+    1. If `x == self.heap[i]`,
+    then just replace `self.heap[i]` with `x`.
+    2. Else if `x < self.heap[i]`,
+    then push_up(index).
+    3. Else `x > self.heap[i]`,
+    then call `self.push_down(i)`.
+    Returns the previous `HeapNode` object at `i`.
+    **Time Complexity:** O(log2 n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    c = self.heap[i]
+    self.heap[i] = x
+    if x > c:
+        self.push_down(i)
+    elif x < c:
+        self.push_up(i)
+    return c
+
+
+
+ +
+ + +
+
+

def right_index(

self, i)

+
+ + + + +

Returns the right child's index of the node at index i, +if it exists, otherwise this function returns -1.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def right_index(self, i: int) -> int:
+    """Returns the right child's index of the node at index `i`,
+    if it exists, otherwise this function returns -1.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    right = i * 2 + 2
+    return right if self.is_good_index(right) else -1
+
+
+
+ +
+ + +
+
+

def search(

self, x)

+
+ + + + +

Searches for x in this heap, +and, if present, returns its index, otherwise returns -1.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(n).

+
+ +
+
def search(self, x) -> int:
+    """Searches for `x` in this heap,
+    and, if present, returns its index, otherwise returns -1.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    for i, node in enumerate(self.heap):
+        if node == x:
+            return i
+    return -1
+
+
+
+ +
+ + +
+
+

def search_by_value(

self, val)

+
+ + + + +

Returns the index of the HeapNode object with value=val. +-1 is returned if no such a HeapNode object exists.

+

If val and the values in this heap are not comparable, +the behaviour of this method is undefined.

+

By construction, HeapNode objects can't be initialized with None values, +but that field could also be set manually after creation.

+

Time Complexity: O(n).

+
+ +
+
def search_by_value(self, val) -> int:
+    """Returns the index of the `HeapNode` object with `value=val`.
+    -1 is returned if no such a `HeapNode` object exists.
+    If `val` and the values in this heap are not comparable,
+    the behaviour of this method is undefined.
+    By construction, HeapNode objects can't be initialized with None values,
+    but that field could also be set manually after creation.
+    **Time Complexity:** O(n)."""
+    for i, node in enumerate(self.heap):
+        if node.value == val:
+            return i
+    return -1
+
+
+
+ +
+ + +
+
+

def size(

self)

+
+ + + + +

Returns the size of this heaps.

+

Time Complexity: O(1).

+
+ +
+
def size(self) -> int:
+    """Returns the size of this heaps.
+    **Time Complexity:** O(1)."""
+    return len(self.heap)
+
+
+
+ +
+ + +
+
+

def swap(

self, i, j)

+
+ + + + +

Swaps elements at indexes i and j, +if they are valid indexes, +otherwise an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def swap(self, i: int, j: int) -> None:
+    """Swaps elements at indexes `i` and `j`,
+    if they are valid indexes,
+    otherwise an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if self.is_good_index(i) and self.is_good_index(j):
+        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
+    else:
+        raise IndexError("i or j are not valid indexes.")
+
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/MinMaxHeap.m.html b/docs/ands/ds/MinMaxHeap.m.html new file mode 100644 index 00000000..84281a19 --- /dev/null +++ b/docs/ands/ds/MinMaxHeap.m.html @@ -0,0 +1,3013 @@ + + + + + + ands.ds.MinMaxHeap API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.MinMaxHeap module

+

Meta info

+

Author: Nelson Brochado

+

Created: 18/02/2016

+

Updated: 29/12/2016

+

Description

+

Min-Max Heap is a heap that supports find-min and find-max operations in constant time. +Moreover, both remove-min and remove-max are supported in logarithmic time. +It's therefore an useful data structure to implement (or represent) double-ended priority queues.

+

The min-max heap ordering is the following:

+
+

values stored at nodes on even (or min) levels +are smaller than or equal to values stored at their descendants, +whereas values stored at nodes on odd (or max) levels +are greater than or equal to values stored at their descendants.

+
+

Even levels are 0, 2, 4, 6, etc, +whereas odd levels are 1, 3, 5, 7, etc.

+

The most important methods used to build and support the data structure are:

+
    +
  • trickle-down (or, also called, bubble-down or shift-down)
  • +
  • trickle-down-min, which is a helper method of trickle-down
  • +
  • trickle-down-max, which is also a helper method of trickle-down
  • +
  • trickle-up (or, also called, bubble-up or shift-up)
  • +
  • trickle-up-min, which is a helper method of trickle-up
  • +
  • trickle-up-max, which is also a helper method of trickle-up
  • +
  • parent-index
  • +
  • grandparent-index
  • +
  • left-child-index
  • +
  • right-child-index
  • +
  • is-on-min-level (or is-on-even-level)
  • +
  • is-on-max-level (or is-on-odd-level)
  • +
  • find-max-element-index
  • +
  • +

    swap

    +
  • +
  • +

    add in O(log n) time

    +
  • +
  • delete-at in O(log n) time
  • +
  • replace-at in O(log n) time
  • +
  • remove-min in O(log n) time
  • +
  • remove-max in O(log n) time
  • +
  • find-min in O(1) time
  • +
  • find-max in O(1) time
  • +
  • size in O(1) time
  • +
  • is-empty in O(1) time
  • +
  • contains in O(n) time
  • +
  • merge in O(n + m) time
  • +
  • clear in O(1) time
  • +
+

TODO

+
    +
  • find-kth, i.e. find the kth smallest element in the structure, in O(1) time
  • +
  • delete-kth, i.e. delete the kth smallest element, in O(log n) time
  • +
+

References

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+# Meta info
+
+Author: Nelson Brochado
+
+Created: 18/02/2016
+
+Updated: 29/12/2016
+
+# Description
+
+Min-Max Heap is a heap that supports find-min and find-max operations in constant time.
+Moreover, both remove-min and remove-max are supported in logarithmic time.
+It's therefore an useful data structure to implement (or represent) double-ended priority queues.
+
+The min-max heap ordering is the following:
+> values stored at nodes on even (or min) levels
+are smaller than or equal to values stored at their descendants,
+whereas values stored at nodes on odd (or max) levels
+are greater than or equal to values stored at their descendants.
+
+Even levels are 0, 2, 4, 6, etc,
+whereas odd levels are 1, 3, 5, 7, etc.
+
+The most important methods used to build and support the data structure are:
+
+- trickle-down (or, also called, bubble-down or shift-down)
+- trickle-down-min, which is a helper method of trickle-down
+- trickle-down-max, which is also a helper method of trickle-down
+- trickle-up (or, also called, bubble-up or shift-up)
+- trickle-up-min, which is a helper method of trickle-up
+- trickle-up-max, which is also a helper method of trickle-up
+- parent-index
+- grandparent-index
+- left-child-index
+- right-child-index
+- is-on-min-level (or is-on-even-level)
+- is-on-max-level (or is-on-odd-level)
+- find-max-element-index
+- swap
+
+- `add` in O(log n) time
+- `delete-at` in O(log n) time
+- `replace-at` in O(log n) time
+- `remove-min` in O(log n) time
+- `remove-max` in O(log n) time
+- `find-min` in O(1) time
+- `find-max` in O(1) time
+- `size` in O(1) time
+- `is-empty` in O(1) time
+- `contains` in O(n) time
+- `merge` in O(n + m) time
+- `clear` in O(1) time
+
+# TODO
+
+- `find-kth`, i.e. find the kth smallest element in the structure, in O(1) time 
+- `delete-kth`, i.e. delete the kth smallest element, in O(log n) time 
+
+# References
+
+- [Min-Max Heaps and Generalized Priority Queues](http://www.akira.ruc.dk/~keld/teaching/algoritmedesign_f03/Artikler/02/Atkinson86.pdf),
+original paper describing and introducing the min-max heap data structure, by M. D. Atkinson, J.R. Sack, N. Santoro and T. Strothotte.
+- [http://www.diku.dk/forskning/performance-engineering/Jesper/heaplab/heapsurvey_html/node11.html](http://www.diku.dk/forskning/performance-engineering/Jesper/heaplab/heapsurvey_html/node11.html)
+"""
+
+from ands.ds.heap import BinaryHeap, HeapNode
+
+
+class MinMaxHeap(BinaryHeap):
+    def __init__(self, ls=None):
+        BinaryHeap.__init__(self, ls)
+
+    def push_down(self, i: int) -> None:
+        """Also called `bubble-down` or `shift-down`."""
+        if self.is_on_even_level(i):
+            self.push_down_min(i)
+        else:
+            self.push_down_max(i)
+
+    def push_down_min(self, i: int) -> None:
+        """Helper method for `push_down`."""
+        if self.has_children(i):
+            m = self.index_of_min(i)
+
+            if self.is_grandchild(m, i):
+                if self.heap[m] < self.heap[i]:
+                    self.swap(i, m)
+
+                    mp = self.parent_index(m)
+                    if mp != -1 and self.heap[m] > self.heap[mp]:
+                        self.swap(m, mp)
+                    self.push_down_min(m)
+
+            else:  # self.heap[m] is a child of self.heap[i]
+                if self.heap[m] < self.heap[i]:
+                    self.swap(i, m)
+
+    def push_down_max(self, i: int) -> None:
+        """Helper method for `push_down`."""
+        if self.has_children(i):
+            m = self.index_of_max(i)
+
+            if self.is_grandchild(m, i):
+                if self.heap[m] > self.heap[i]:
+                    self.swap(i, m)
+
+                    mp = self.parent_index(m)
+                    if mp != -1 and self.heap[m] < self.heap[mp]:
+                        self.swap(m, mp)
+                    self.push_down_max(m)
+
+            else:  # self.heap[m] is a child of self.heap[i]
+                if self.heap[m] > self.heap[i]:
+                    self.swap(i, m)
+
+    def push_up(self, i: int) -> None:
+        """Also called `bubble-up` or `shift-up`."""
+        p = self.parent_index(i)
+
+        # Let x be the element at index i.
+        # If x has a parent at position p, we call it y.
+        if self.is_on_even_level(i):
+            if p != -1 and self.heap[i] > self.heap[p]:
+                # If x is greater than y, swap x with y.
+                # Now, x is at index p, and y at index i.
+                # push_up_max from the new index of x, i.e. p.
+                self.swap(i, p)
+                self.push_up_max(p)
+            else:
+                # x does not have a parent OR x <= y.
+                self.push_up_min(i)
+        else:
+            # Odd or max level.
+            if p != -1 and self.heap[i] < self.heap[p]:
+                self.swap(i, p)
+                self.push_up_min(p)
+            else:
+                self.push_up_max(i)
+
+    def push_up_min(self, i: int) -> None:
+        """Helper method for `push_up`."""
+        g = self.grandparent_index(i)
+        # Let x be the element at index i.
+        # If x has a grandparent at position g,
+        # we call it z.
+
+        # If the z exists and x is smaller than z,
+        # swap x and z. Now, x is at index g and z at index i.
+        if g != -1 and self.heap[i] < self.heap[g]:
+            self.swap(i, g)
+            self.push_up_min(g)
+
+    def push_up_max(self, i: int) -> None:
+        """Helper method for `push_up`."""
+        g = self.grandparent_index(i)
+        if g != -1 and self.heap[i] > self.heap[g]:
+            self.swap(i, g)
+            self.push_up_max(g)
+
+    def find_max(self) -> HeapNode:
+        """Returns the `HeapNode` object representing the maximum element.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_empty():
+            return self.heap[self.find_max_index()]
+
+    def find_min(self) -> HeapNode:
+        """Returns the `HeapNode` object representing the minimum element.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_empty():
+            return self.heap[0]
+
+    def remove_max(self) -> HeapNode:
+        """Deletes and returns the `HeapNode` object representing the maximum element.
+
+        **Time Complexity:** O(log2 n)."""
+        if not self.is_empty():
+            return self.delete(self.find_max_index())
+
+    def remove_min(self) -> HeapNode:
+        """Deletes and returns the `HeapNode` object representing the minimum element.
+
+        **Time Complexity:** O(log2 n)."""
+        if not self.is_empty():
+            return self.delete(0)
+
+    def delete(self, i: int) -> HeapNode:
+        """Deletes and returns the `HeapNode` object at index `i`.
+
+        `IndexError` is raised if `i` is not a valid index.
+
+        Implementation based on:
+        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
+
+        **Time Complexity:** O(log2 n)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        if i == self.size() - 1:
+            return self.heap.pop()
+        self.swap(i, self.size() - 1)
+        d = self.heap.pop()
+        self.push_up(i)
+        self.push_down(i)
+        return d
+
+    def replace(self, i: int, x):
+        """Replace node at index `i` with `x`.
+
+        `x` can either be a key for a HeapNode` object,
+        which is created automatically by this function,
+        and `x` becomes the key and value of that same `HeapNode` object,
+        or it can be (directly) a `HeapNode` object.
+
+        If `x` is NOT a `HeapNode`, it should be comparable
+        with the other keys in the other `HeapNode` objects.
+        If this is not true, the behaviour of this function is undefined.
+
+        If `x` is a `HeapNode`,
+        it's the responsibility of the client of this function
+        to make sure it's a "valid" `HeapNode` object,
+        i.e. it's comparable to the other `HeapNode` objects in this heap.
+
+        **Time Complexity:** O(log2 n)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, HeapNode):
+            x = HeapNode(x)
+        d = self.heap[i]
+        self.heap[i] = x
+        self.push_up(i)
+        self.push_down(i)
+        return d
+
+    def find_max_index(self) -> int:
+        """Returns the index of the maximum element in this min-max heap.
+
+        **Time Complexity:** O(1)."""
+        if self.is_empty():
+            return -1
+        elif self.size() == 1:
+            return 0
+        elif self.size() == 2:
+            return 1
+        else:
+            return 1 if self.heap[1] > self.heap[2] else 2
+
+    def index_of_min(self, i: int) -> int:
+        """Returns the index of the smallest element
+        among the children and grandchildren of the node at index `i`.
+
+        **Time Complexity:** O(1)."""
+        m = l = self.left_index(i)
+        r = self.right_index(i)
+
+        if r != -1 and self.heap[r] < self.heap[m]:
+            m = r
+
+        if l != -1:
+            gll = self.left_index(l)
+            if gll != -1 and self.heap[gll] < self.heap[m]:
+                m = gll
+            glr = self.right_index(l)
+            if glr != -1 and self.heap[glr] < self.heap[m]:
+                m = glr
+
+        if r != -1:
+            grl = self.left_index(r)
+            if grl != -1 and self.heap[grl] < self.heap[m]:
+                m = grl
+            grr = self.right_index(r)
+            if grr != -1 and self.heap[grr] < self.heap[m]:
+                m = grr
+
+        return m
+
+    def index_of_max(self, i: int) -> int:
+        """Returns the index of the largest element
+        among the children and grandchildren of the node at index `i`.
+
+        **Time Complexity:** O(1)."""
+        m = l = self.left_index(i)
+        r = self.right_index(i)
+
+        if r != -1 and self.heap[r] > self.heap[m]:
+            m = r
+
+        if l != -1:
+            gll = self.left_index(l)
+            if gll != -1 and self.heap[gll] > self.heap[m]:
+                m = gll
+            glr = self.right_index(l)
+            if glr != -1 and self.heap[glr] > self.heap[m]:
+                m = glr
+
+        if r != -1:
+            grl = self.left_index(r)
+            if grl != -1 and self.heap[grl] > self.heap[m]:
+                m = grl
+            grr = self.right_index(r)
+            if grr != -1 and self.heap[grr] > self.heap[m]:
+                m = grr
+
+        return m
+
+
+def is_min_max_heap(h) -> bool:
+    """Returns `True` if `h` is a valid `MinMaxHeap` object. `False` otherwise.
+
+    Min-max heap property:
+    each node at an EVEN level in the tree is LESS THAN all of its descendants
+    while each node at an ODD level in the tree is GREATER THAN all of its descendants."""
+    if not isinstance(h, MinMaxHeap):
+        return False
+
+    if h.heap:
+        for item in h.heap:
+            if not isinstance(item, HeapNode):
+                return False
+
+        if h.size() == 1:
+            return True
+
+        if h.size() == 2:
+            return max(h.heap) == h.heap[1] and min(h.heap) == h.heap[0]
+
+        if h.size() >= 3:
+            if h.heap[0] != min(h.heap) or (h.heap[1] != max(h.heap) and h.heap[2] != max(h.heap)):
+                return False
+
+        # i is the index of the current node
+        for i, item in reversed(list(enumerate(h.heap))):
+
+            p = h.parent_index(i)
+
+            if p != -1:
+                if h.is_on_even_level(i):
+                    if h.heap[p] < item:
+                        return False
+                else:
+                    if h.heap[p] > item:
+                        return False
+
+            g = h.grandparent_index(i)
+            if g != -1:
+                if h.is_on_even_level(i):
+                    if h.heap[g] > item:
+                        return False
+                else:
+                    if h.heap[g] < item:
+                        return False
+    return True
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def is_min_max_heap(

h)

+
+ + + + +

Returns True if h is a valid MinMaxHeap object. False otherwise.

+

Min-max heap property: +each node at an EVEN level in the tree is LESS THAN all of its descendants +while each node at an ODD level in the tree is GREATER THAN all of its descendants.

+
+ +
+
def is_min_max_heap(h) -> bool:
+    """Returns `True` if `h` is a valid `MinMaxHeap` object. `False` otherwise.
+
+    Min-max heap property:
+    each node at an EVEN level in the tree is LESS THAN all of its descendants
+    while each node at an ODD level in the tree is GREATER THAN all of its descendants."""
+    if not isinstance(h, MinMaxHeap):
+        return False
+
+    if h.heap:
+        for item in h.heap:
+            if not isinstance(item, HeapNode):
+                return False
+
+        if h.size() == 1:
+            return True
+
+        if h.size() == 2:
+            return max(h.heap) == h.heap[1] and min(h.heap) == h.heap[0]
+
+        if h.size() >= 3:
+            if h.heap[0] != min(h.heap) or (h.heap[1] != max(h.heap) and h.heap[2] != max(h.heap)):
+                return False
+
+        # i is the index of the current node
+        for i, item in reversed(list(enumerate(h.heap))):
+
+            p = h.parent_index(i)
+
+            if p != -1:
+                if h.is_on_even_level(i):
+                    if h.heap[p] < item:
+                        return False
+                else:
+                    if h.heap[p] > item:
+                        return False
+
+            g = h.grandparent_index(i)
+            if g != -1:
+                if h.is_on_even_level(i):
+                    if h.heap[g] > item:
+                        return False
+                else:
+                    if h.heap[g] < item:
+                        return False
+    return True
+
+
+
+ +
+ + +

Classes

+ +
+

class MinMaxHeap

+ + +

Abstract class to represent binary heaps.

+

MinHeap, MaxHeap and MinMaxHeap all derive from this class.

+
+ +
+
class MinMaxHeap(BinaryHeap):
+    def __init__(self, ls=None):
+        BinaryHeap.__init__(self, ls)
+
+    def push_down(self, i: int) -> None:
+        """Also called `bubble-down` or `shift-down`."""
+        if self.is_on_even_level(i):
+            self.push_down_min(i)
+        else:
+            self.push_down_max(i)
+
+    def push_down_min(self, i: int) -> None:
+        """Helper method for `push_down`."""
+        if self.has_children(i):
+            m = self.index_of_min(i)
+
+            if self.is_grandchild(m, i):
+                if self.heap[m] < self.heap[i]:
+                    self.swap(i, m)
+
+                    mp = self.parent_index(m)
+                    if mp != -1 and self.heap[m] > self.heap[mp]:
+                        self.swap(m, mp)
+                    self.push_down_min(m)
+
+            else:  # self.heap[m] is a child of self.heap[i]
+                if self.heap[m] < self.heap[i]:
+                    self.swap(i, m)
+
+    def push_down_max(self, i: int) -> None:
+        """Helper method for `push_down`."""
+        if self.has_children(i):
+            m = self.index_of_max(i)
+
+            if self.is_grandchild(m, i):
+                if self.heap[m] > self.heap[i]:
+                    self.swap(i, m)
+
+                    mp = self.parent_index(m)
+                    if mp != -1 and self.heap[m] < self.heap[mp]:
+                        self.swap(m, mp)
+                    self.push_down_max(m)
+
+            else:  # self.heap[m] is a child of self.heap[i]
+                if self.heap[m] > self.heap[i]:
+                    self.swap(i, m)
+
+    def push_up(self, i: int) -> None:
+        """Also called `bubble-up` or `shift-up`."""
+        p = self.parent_index(i)
+
+        # Let x be the element at index i.
+        # If x has a parent at position p, we call it y.
+        if self.is_on_even_level(i):
+            if p != -1 and self.heap[i] > self.heap[p]:
+                # If x is greater than y, swap x with y.
+                # Now, x is at index p, and y at index i.
+                # push_up_max from the new index of x, i.e. p.
+                self.swap(i, p)
+                self.push_up_max(p)
+            else:
+                # x does not have a parent OR x <= y.
+                self.push_up_min(i)
+        else:
+            # Odd or max level.
+            if p != -1 and self.heap[i] < self.heap[p]:
+                self.swap(i, p)
+                self.push_up_min(p)
+            else:
+                self.push_up_max(i)
+
+    def push_up_min(self, i: int) -> None:
+        """Helper method for `push_up`."""
+        g = self.grandparent_index(i)
+        # Let x be the element at index i.
+        # If x has a grandparent at position g,
+        # we call it z.
+
+        # If the z exists and x is smaller than z,
+        # swap x and z. Now, x is at index g and z at index i.
+        if g != -1 and self.heap[i] < self.heap[g]:
+            self.swap(i, g)
+            self.push_up_min(g)
+
+    def push_up_max(self, i: int) -> None:
+        """Helper method for `push_up`."""
+        g = self.grandparent_index(i)
+        if g != -1 and self.heap[i] > self.heap[g]:
+            self.swap(i, g)
+            self.push_up_max(g)
+
+    def find_max(self) -> HeapNode:
+        """Returns the `HeapNode` object representing the maximum element.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_empty():
+            return self.heap[self.find_max_index()]
+
+    def find_min(self) -> HeapNode:
+        """Returns the `HeapNode` object representing the minimum element.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_empty():
+            return self.heap[0]
+
+    def remove_max(self) -> HeapNode:
+        """Deletes and returns the `HeapNode` object representing the maximum element.
+
+        **Time Complexity:** O(log2 n)."""
+        if not self.is_empty():
+            return self.delete(self.find_max_index())
+
+    def remove_min(self) -> HeapNode:
+        """Deletes and returns the `HeapNode` object representing the minimum element.
+
+        **Time Complexity:** O(log2 n)."""
+        if not self.is_empty():
+            return self.delete(0)
+
+    def delete(self, i: int) -> HeapNode:
+        """Deletes and returns the `HeapNode` object at index `i`.
+
+        `IndexError` is raised if `i` is not a valid index.
+
+        Implementation based on:
+        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
+
+        **Time Complexity:** O(log2 n)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        if i == self.size() - 1:
+            return self.heap.pop()
+        self.swap(i, self.size() - 1)
+        d = self.heap.pop()
+        self.push_up(i)
+        self.push_down(i)
+        return d
+
+    def replace(self, i: int, x):
+        """Replace node at index `i` with `x`.
+
+        `x` can either be a key for a HeapNode` object,
+        which is created automatically by this function,
+        and `x` becomes the key and value of that same `HeapNode` object,
+        or it can be (directly) a `HeapNode` object.
+
+        If `x` is NOT a `HeapNode`, it should be comparable
+        with the other keys in the other `HeapNode` objects.
+        If this is not true, the behaviour of this function is undefined.
+
+        If `x` is a `HeapNode`,
+        it's the responsibility of the client of this function
+        to make sure it's a "valid" `HeapNode` object,
+        i.e. it's comparable to the other `HeapNode` objects in this heap.
+
+        **Time Complexity:** O(log2 n)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, HeapNode):
+            x = HeapNode(x)
+        d = self.heap[i]
+        self.heap[i] = x
+        self.push_up(i)
+        self.push_down(i)
+        return d
+
+    def find_max_index(self) -> int:
+        """Returns the index of the maximum element in this min-max heap.
+
+        **Time Complexity:** O(1)."""
+        if self.is_empty():
+            return -1
+        elif self.size() == 1:
+            return 0
+        elif self.size() == 2:
+            return 1
+        else:
+            return 1 if self.heap[1] > self.heap[2] else 2
+
+    def index_of_min(self, i: int) -> int:
+        """Returns the index of the smallest element
+        among the children and grandchildren of the node at index `i`.
+
+        **Time Complexity:** O(1)."""
+        m = l = self.left_index(i)
+        r = self.right_index(i)
+
+        if r != -1 and self.heap[r] < self.heap[m]:
+            m = r
+
+        if l != -1:
+            gll = self.left_index(l)
+            if gll != -1 and self.heap[gll] < self.heap[m]:
+                m = gll
+            glr = self.right_index(l)
+            if glr != -1 and self.heap[glr] < self.heap[m]:
+                m = glr
+
+        if r != -1:
+            grl = self.left_index(r)
+            if grl != -1 and self.heap[grl] < self.heap[m]:
+                m = grl
+            grr = self.right_index(r)
+            if grr != -1 and self.heap[grr] < self.heap[m]:
+                m = grr
+
+        return m
+
+    def index_of_max(self, i: int) -> int:
+        """Returns the index of the largest element
+        among the children and grandchildren of the node at index `i`.
+
+        **Time Complexity:** O(1)."""
+        m = l = self.left_index(i)
+        r = self.right_index(i)
+
+        if r != -1 and self.heap[r] > self.heap[m]:
+            m = r
+
+        if l != -1:
+            gll = self.left_index(l)
+            if gll != -1 and self.heap[gll] > self.heap[m]:
+                m = gll
+            glr = self.right_index(l)
+            if glr != -1 and self.heap[glr] > self.heap[m]:
+                m = glr
+
+        if r != -1:
+            grl = self.left_index(r)
+            if grl != -1 and self.heap[grl] > self.heap[m]:
+                m = grl
+            grr = self.right_index(r)
+            if grr != -1 and self.heap[grr] > self.heap[m]:
+                m = grr
+
+        return m
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • MinMaxHeap
  • +
  • ands.ds.heap.BinaryHeap
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, ls=None)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, ls=None):
+    BinaryHeap.__init__(self, ls)
+
+
+
+ +
+ + +
+
+

def add(

self, x)

+
+ + + + +

Adds x to this heap.

+

In practice, it places x at an available leaf, +then "bubbles up" from there, +in order to maintain the heap property.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(log2 n).

+
+ +
+
def add(self, x) -> None:
+    """Adds `x` to this heap.
+    In practice, it places `x` at an available leaf,
+    then "bubbles up" from there,
+    in order to maintain the heap property.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(log2 n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    self.heap.append(x)
+    if self.size() > 1:
+        self.push_up(self.size() - 1)
+
+
+
+ +
+ + +
+
+

def build_heap(

self)

+
+ + + + +

Builds the heap data structure from self.heap.

+
+ +
+
def build_heap(self) -> list:
+    """Builds the heap data structure from `self.heap`."""
+    if self.heap:
+        for index in range(len(self.heap) // 2, -1, -1):
+            self.push_down(index)
+    return self.heap
+
+
+
+ +
+ + +
+
+

def clear(

self)

+
+ + + + +

Clears all nodes from this heap. +This mean that if you call is_empty, +it will return True.

+

Time Complexity: O(1).

+
+ +
+
def clear(self) -> None:
+    """Clears all nodes from this heap.
+    This mean that if you call `is_empty`,
+    it will return `True`.
+    **Time Complexity:** O(1)."""
+    self.heap.clear()
+
+
+
+ +
+ + +
+
+

def contains(

self, x)

+
+ + + + +

Returns True, if x is in this heap. False otherwise.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(n).

+
+ +
+
def contains(self, x) -> bool:
+    """Returns `True`, if `x` is in this heap. `False` otherwise.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(n)."""
+    return self.search(x) != -1
+
+
+
+ +
+ + +
+
+

def delete(

self, i)

+
+ + + + +

Deletes and returns the HeapNode object at index i.

+

IndexError is raised if i is not a valid index.

+

Implementation based on: +http://www.math.clemson.edu/~warner/M865/HeapDelete.html

+

Time Complexity: O(log2 n).

+
+ +
+
def delete(self, i: int) -> HeapNode:
+    """Deletes and returns the `HeapNode` object at index `i`.
+    `IndexError` is raised if `i` is not a valid index.
+    Implementation based on:
+    [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
+    **Time Complexity:** O(log2 n)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    if i == self.size() - 1:
+        return self.heap.pop()
+    self.swap(i, self.size() - 1)
+    d = self.heap.pop()
+    self.push_up(i)
+    self.push_down(i)
+    return d
+
+
+
+ +
+ + +
+
+

def find_max(

self)

+
+ + + + +

Returns the HeapNode object representing the maximum element.

+

Time Complexity: O(1).

+
+ +
+
def find_max(self) -> HeapNode:
+    """Returns the `HeapNode` object representing the maximum element.
+    **Time Complexity:** O(1)."""
+    if not self.is_empty():
+        return self.heap[self.find_max_index()]
+
+
+
+ +
+ + +
+
+

def find_max_index(

self)

+
+ + + + +

Returns the index of the maximum element in this min-max heap.

+

Time Complexity: O(1).

+
+ +
+
def find_max_index(self) -> int:
+    """Returns the index of the maximum element in this min-max heap.
+    **Time Complexity:** O(1)."""
+    if self.is_empty():
+        return -1
+    elif self.size() == 1:
+        return 0
+    elif self.size() == 2:
+        return 1
+    else:
+        return 1 if self.heap[1] > self.heap[2] else 2
+
+
+
+ +
+ + +
+
+

def find_min(

self)

+
+ + + + +

Returns the HeapNode object representing the minimum element.

+

Time Complexity: O(1).

+
+ +
+
def find_min(self) -> HeapNode:
+    """Returns the `HeapNode` object representing the minimum element.
+    **Time Complexity:** O(1)."""
+    if not self.is_empty():
+        return self.heap[0]
+
+
+
+ +
+ + +
+
+

def grandparent_index(

self, i)

+
+ + + + +

Returns the grandparent's index of the node at index i.

+

-1 is returned either if i has not a parent or +the parent of i does not have a parent.

+

Time Complexity: O(1).

+
+ +
+
def grandparent_index(self, i: int) -> int:
+    """Returns the grandparent's index of the node at index `i`.
+    -1 is returned either if `i` has not a parent or
+    the parent of `i` does not have a parent.
+    **Time Complexity:** O(1)."""
+    p = self.parent_index(i)
+    return -1 if p == -1 else self.parent_index(p)
+
+
+
+ +
+ + +
+
+

def has_children(

self, i)

+
+ + + + +

Returns True if the node at index i +has at least one child, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def has_children(self, i: int) -> bool:
+    """Returns `True` if the node at index `i`
+    has at least one child, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return self.left_index(i) != -1 or self.right_index(i) != -1
+
+
+
+ +
+ + +
+
+

def index_of_max(

self, i)

+
+ + + + +

Returns the index of the largest element +among the children and grandchildren of the node at index i.

+

Time Complexity: O(1).

+
+ +
+
def index_of_max(self, i: int) -> int:
+    """Returns the index of the largest element
+    among the children and grandchildren of the node at index `i`.
+    **Time Complexity:** O(1)."""
+    m = l = self.left_index(i)
+    r = self.right_index(i)
+    if r != -1 and self.heap[r] > self.heap[m]:
+        m = r
+    if l != -1:
+        gll = self.left_index(l)
+        if gll != -1 and self.heap[gll] > self.heap[m]:
+            m = gll
+        glr = self.right_index(l)
+        if glr != -1 and self.heap[glr] > self.heap[m]:
+            m = glr
+    if r != -1:
+        grl = self.left_index(r)
+        if grl != -1 and self.heap[grl] > self.heap[m]:
+            m = grl
+        grr = self.right_index(r)
+        if grr != -1 and self.heap[grr] > self.heap[m]:
+            m = grr
+    return m
+
+
+
+ +
+ + +
+
+

def index_of_min(

self, i)

+
+ + + + +

Returns the index of the smallest element +among the children and grandchildren of the node at index i.

+

Time Complexity: O(1).

+
+ +
+
def index_of_min(self, i: int) -> int:
+    """Returns the index of the smallest element
+    among the children and grandchildren of the node at index `i`.
+    **Time Complexity:** O(1)."""
+    m = l = self.left_index(i)
+    r = self.right_index(i)
+    if r != -1 and self.heap[r] < self.heap[m]:
+        m = r
+    if l != -1:
+        gll = self.left_index(l)
+        if gll != -1 and self.heap[gll] < self.heap[m]:
+            m = gll
+        glr = self.right_index(l)
+        if glr != -1 and self.heap[glr] < self.heap[m]:
+            m = glr
+    if r != -1:
+        grl = self.left_index(r)
+        if grl != -1 and self.heap[grl] < self.heap[m]:
+            m = grl
+        grr = self.right_index(r)
+        if grr != -1 and self.heap[grr] < self.heap[m]:
+            m = grr
+    return m
+
+
+
+ +
+ + +
+
+

def is_child(

self, c, i)

+
+ + + + +

Returns True if c is a child of i. False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_child(self, c: int, i: int) -> bool:
+    """Returns `True` if `c` is a child of `i`. `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(c) or not self.is_good_index(i):
+        raise IndexError("i or c are not valid indexes.")
+    return c == self.left_index(i) or c == self.right_index(i)
+
+
+
+ +
+ + +
+
+

def is_empty(

self)

+
+ + + + +

Returns True if this heap is empty.

+

Time Complexity: O(1).

+
+ +
+
def is_empty(self) -> bool:
+    """Returns `True` if this heap is empty.
+    **Time Complexity:** O(1)."""
+    return self.size() == 0
+
+
+
+ +
+ + +
+
+

def is_good_index(

self, i)

+
+ + + + +

Returns True if i is valid index for self.heap, +False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_good_index(self, i: int) -> bool:
+    """Returns `True` if `i` is valid index for `self.heap`,
+    `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not isinstance(i, int):
+        raise TypeError("indexes can only be int.")
+    return False if (i < 0 or i >= self.size()) else True
+
+
+
+ +
+ + +
+
+

def is_grandchild(

self, g, i)

+
+ + + + +

Returns True if g is a grandchild of i. False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_grandchild(self, g: int, i: int) -> bool:
+    """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
+    **Time Complexity:** O(1)."""
+    l = self.left_index(i)
+    if l == -1:
+        assert self.right_index(i) == -1
+        if not self.is_good_index(g):
+            raise IndexError("g is not a valid index.")
+        return False
+    r = self.right_index(i)
+    if r == -1:
+        return self.is_child(g, l)
+    else:
+        return self.is_child(g, l) or self.is_child(g, r)
+
+
+
+ +
+ + +
+
+

def is_grandparent(

self, g, i)

+
+ + + + +

Returns True if g is the index of the grandparent +of the node at i, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_grandparent(self, g: int, i: int) -> bool:
+    """Returns `True` if `g` is the index of the grandparent
+    of the node at `i`, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(g):
+        raise IndexError("g is not a valid index.")
+    p = self.parent_index(i)
+    return False if p == -1 else self.is_parent(g, p)
+
+
+
+ +
+ + +
+
+

def is_on_even_level(

self, i)

+
+ + + + +

Returns True if node at index i is on a even-level, +i.e., if i is on a level multiple of 2 (0, 2, 4, 6,...). +If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(int(math.log2(i + 1) % 2) == 0).

+
+ +
+
def is_on_even_level(self, i: int) -> bool:
+    """Returns `True` if node at index `i` is on a even-level,
+    i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return int(math.log2(i + 1) % 2) == 0
+
+
+
+ +
+ + +
+
+

def is_on_odd_level(

self, i)

+
+ + + + +

Returns True (False) if self.is_on_even_level(i) returns False (True).

+
+ +
+
def is_on_odd_level(self, i: int) -> bool:
+    """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
+    return not self.is_on_even_level(i)
+
+
+
+ +
+ + +
+
+

def is_parent(

self, p, i)

+
+ + + + +

Returns True if p is the index of the parent +of the node at i, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_parent(self, p: int, i: int) -> bool:
+    """Returns `True` if `p` is the index of the parent
+    of the node at `i`, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(p):
+        raise IndexError("p is not a valid index.")
+    return self.parent_index(i) == p
+
+
+
+ +
+ + +
+
+

def left_index(

self, i)

+
+ + + + +

Returns the left child's index of the node at index i, +if it exists, otherwise this function returns -1.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def left_index(self, i: int) -> int:
+    """Returns the left child's index of the node at index `i`,
+    if it exists, otherwise this function returns -1.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    left = i * 2 + 1
+    return left if self.is_good_index(left) else -1
+
+
+
+ +
+ + +
+
+

def merge(

self, o)

+
+ + + + +

Merges this heap with the o heap.

+

Returns the list object representing internally the new merged heap.

+

Time Complexity: O(n + m).

+

Time complexity analysis based on: +http://stackoverflow.com/a/29197855/3924118.

+
+ +
+
def merge(self, o) -> list:
+    """Merges this heap with the `o` heap.
+    Returns the `list` object representing internally the new merged heap.
+    **Time Complexity:** O(n + m).
+    Time complexity analysis based on:
+    [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
+    self.heap += o.heap
+    return self.build_heap()
+
+
+
+ +
+ + +
+
+

def parent_index(

self, i)

+
+ + + + +

Returns the parent's index of the node at index i. +If i = 0, then -1 is returned, because the root has no parent.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def parent_index(self, i: int) -> int:
+    """Returns the parent's index of the node at index `i`.
+    If `i = 0`, then -1 is returned, because the root has no parent.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return -1 if i == 0 else (i - 1) // 2
+
+
+
+ +
+ + +
+
+

def push_down(

self, i)

+
+ + + + +

Also called bubble-down or shift-down.

+
+ +
+
def push_down(self, i: int) -> None:
+    """Also called `bubble-down` or `shift-down`."""
+    if self.is_on_even_level(i):
+        self.push_down_min(i)
+    else:
+        self.push_down_max(i)
+
+
+
+ +
+ + +
+
+

def push_down_max(

self, i)

+
+ + + + +

Helper method for push_down.

+
+ +
+
def push_down_max(self, i: int) -> None:
+    """Helper method for `push_down`."""
+    if self.has_children(i):
+        m = self.index_of_max(i)
+        if self.is_grandchild(m, i):
+            if self.heap[m] > self.heap[i]:
+                self.swap(i, m)
+                mp = self.parent_index(m)
+                if mp != -1 and self.heap[m] < self.heap[mp]:
+                    self.swap(m, mp)
+                self.push_down_max(m)
+        else:  # self.heap[m] is a child of self.heap[i]
+            if self.heap[m] > self.heap[i]:
+                self.swap(i, m)
+
+
+
+ +
+ + +
+
+

def push_down_min(

self, i)

+
+ + + + +

Helper method for push_down.

+
+ +
+
def push_down_min(self, i: int) -> None:
+    """Helper method for `push_down`."""
+    if self.has_children(i):
+        m = self.index_of_min(i)
+        if self.is_grandchild(m, i):
+            if self.heap[m] < self.heap[i]:
+                self.swap(i, m)
+                mp = self.parent_index(m)
+                if mp != -1 and self.heap[m] > self.heap[mp]:
+                    self.swap(m, mp)
+                self.push_down_min(m)
+        else:  # self.heap[m] is a child of self.heap[i]
+            if self.heap[m] < self.heap[i]:
+                self.swap(i, m)
+
+
+
+ +
+ + +
+
+

def push_up(

self, i)

+
+ + + + +

Also called bubble-up or shift-up.

+
+ +
+
def push_up(self, i: int) -> None:
+    """Also called `bubble-up` or `shift-up`."""
+    p = self.parent_index(i)
+    # Let x be the element at index i.
+    # If x has a parent at position p, we call it y.
+    if self.is_on_even_level(i):
+        if p != -1 and self.heap[i] > self.heap[p]:
+            # If x is greater than y, swap x with y.
+            # Now, x is at index p, and y at index i.
+            # push_up_max from the new index of x, i.e. p.
+            self.swap(i, p)
+            self.push_up_max(p)
+        else:
+            # x does not have a parent OR x <= y.
+            self.push_up_min(i)
+    else:
+        # Odd or max level.
+        if p != -1 and self.heap[i] < self.heap[p]:
+            self.swap(i, p)
+            self.push_up_min(p)
+        else:
+            self.push_up_max(i)
+
+
+
+ +
+ + +
+
+

def push_up_max(

self, i)

+
+ + + + +

Helper method for push_up.

+
+ +
+
def push_up_max(self, i: int) -> None:
+    """Helper method for `push_up`."""
+    g = self.grandparent_index(i)
+    if g != -1 and self.heap[i] > self.heap[g]:
+        self.swap(i, g)
+        self.push_up_max(g)
+
+
+
+ +
+ + +
+
+

def push_up_min(

self, i)

+
+ + + + +

Helper method for push_up.

+
+ +
+
def push_up_min(self, i: int) -> None:
+    """Helper method for `push_up`."""
+    g = self.grandparent_index(i)
+    # Let x be the element at index i.
+    # If x has a grandparent at position g,
+    # we call it z.
+    # If the z exists and x is smaller than z,
+    # swap x and z. Now, x is at index g and z at index i.
+    if g != -1 and self.heap[i] < self.heap[g]:
+        self.swap(i, g)
+        self.push_up_min(g)
+
+
+
+ +
+ + +
+
+

def remove_max(

self)

+
+ + + + +

Deletes and returns the HeapNode object representing the maximum element.

+

Time Complexity: O(log2 n).

+
+ +
+
def remove_max(self) -> HeapNode:
+    """Deletes and returns the `HeapNode` object representing the maximum element.
+    **Time Complexity:** O(log2 n)."""
+    if not self.is_empty():
+        return self.delete(self.find_max_index())
+
+
+
+ +
+ + +
+
+

def remove_min(

self)

+
+ + + + +

Deletes and returns the HeapNode object representing the minimum element.

+

Time Complexity: O(log2 n).

+
+ +
+
def remove_min(self) -> HeapNode:
+    """Deletes and returns the `HeapNode` object representing the minimum element.
+    **Time Complexity:** O(log2 n)."""
+    if not self.is_empty():
+        return self.delete(0)
+
+
+
+ +
+ + +
+
+

def replace(

self, i, x)

+
+ + + + +

Replace node at index i with x.

+

x can either be a key for a HeapNodeobject, +which is created automatically by this function, +andxbecomes the key and value of that sameHeapNodeobject, +or it can be (directly) aHeapNode` object.

+

If x is NOT a HeapNode, it should be comparable +with the other keys in the other HeapNode objects. +If this is not true, the behaviour of this function is undefined.

+

If x is a HeapNode, +it's the responsibility of the client of this function +to make sure it's a "valid" HeapNode object, +i.e. it's comparable to the other HeapNode objects in this heap.

+

Time Complexity: O(log2 n).

+
+ +
+
def replace(self, i: int, x):
+    """Replace node at index `i` with `x`.
+    `x` can either be a key for a HeapNode` object,
+    which is created automatically by this function,
+    and `x` becomes the key and value of that same `HeapNode` object,
+    or it can be (directly) a `HeapNode` object.
+    If `x` is NOT a `HeapNode`, it should be comparable
+    with the other keys in the other `HeapNode` objects.
+    If this is not true, the behaviour of this function is undefined.
+    If `x` is a `HeapNode`,
+    it's the responsibility of the client of this function
+    to make sure it's a "valid" `HeapNode` object,
+    i.e. it's comparable to the other `HeapNode` objects in this heap.
+    **Time Complexity:** O(log2 n)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    d = self.heap[i]
+    self.heap[i] = x
+    self.push_up(i)
+    self.push_down(i)
+    return d
+
+
+
+ +
+ + +
+
+

def right_index(

self, i)

+
+ + + + +

Returns the right child's index of the node at index i, +if it exists, otherwise this function returns -1.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def right_index(self, i: int) -> int:
+    """Returns the right child's index of the node at index `i`,
+    if it exists, otherwise this function returns -1.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    right = i * 2 + 2
+    return right if self.is_good_index(right) else -1
+
+
+
+ +
+ + +
+
+

def search(

self, x)

+
+ + + + +

Searches for x in this heap, +and, if present, returns its index, otherwise returns -1.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(n).

+
+ +
+
def search(self, x) -> int:
+    """Searches for `x` in this heap,
+    and, if present, returns its index, otherwise returns -1.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    for i, node in enumerate(self.heap):
+        if node == x:
+            return i
+    return -1
+
+
+
+ +
+ + +
+
+

def search_by_value(

self, val)

+
+ + + + +

Returns the index of the HeapNode object with value=val. +-1 is returned if no such a HeapNode object exists.

+

If val and the values in this heap are not comparable, +the behaviour of this method is undefined.

+

By construction, HeapNode objects can't be initialized with None values, +but that field could also be set manually after creation.

+

Time Complexity: O(n).

+
+ +
+
def search_by_value(self, val) -> int:
+    """Returns the index of the `HeapNode` object with `value=val`.
+    -1 is returned if no such a `HeapNode` object exists.
+    If `val` and the values in this heap are not comparable,
+    the behaviour of this method is undefined.
+    By construction, HeapNode objects can't be initialized with None values,
+    but that field could also be set manually after creation.
+    **Time Complexity:** O(n)."""
+    for i, node in enumerate(self.heap):
+        if node.value == val:
+            return i
+    return -1
+
+
+
+ +
+ + +
+
+

def size(

self)

+
+ + + + +

Returns the size of this heaps.

+

Time Complexity: O(1).

+
+ +
+
def size(self) -> int:
+    """Returns the size of this heaps.
+    **Time Complexity:** O(1)."""
+    return len(self.heap)
+
+
+
+ +
+ + +
+
+

def swap(

self, i, j)

+
+ + + + +

Swaps elements at indexes i and j, +if they are valid indexes, +otherwise an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def swap(self, i: int, j: int) -> None:
+    """Swaps elements at indexes `i` and `j`,
+    if they are valid indexes,
+    otherwise an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if self.is_good_index(i) and self.is_good_index(j):
+        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
+    else:
+        raise IndexError("i or j are not valid indexes.")
+
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/MinPriorityQueue.m.html b/docs/ands/ds/MinPriorityQueue.m.html new file mode 100644 index 00000000..1a52427d --- /dev/null +++ b/docs/ands/ds/MinPriorityQueue.m.html @@ -0,0 +1,2360 @@ + + + + + + ands.ds.MinPriorityQueue API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.MinPriorityQueue module

+

Author: Nelson Brochado

+

Created: 24/01/2017

+ + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+
+Author: Nelson Brochado
+
+Created: 24/01/2017
+"""
+
+import sys
+
+from ands.ds.heap import HeapNode
+from ands.ds.MinHeap import MinHeap
+
+
+class MinPriorityQueue(MinHeap):
+    def __init__(self, ls=None):
+        """If `ls` is provided, it should be a list of tuples,
+        each of which contains 2 items:
+        The first item of the tuple is the priority of the element,
+        the second item of the tuple is the element.
+        A smaller number means a higher priority."""
+        MinHeap.__init__(self, ls)
+
+    def insert(self, element: object, priority=sys.maxsize):
+        """Adds `element` to this min priority queue.
+
+        You can specify the priority of the element
+        by assigning a value to "priority".
+
+        Note that the value assigned to priority
+        should be an object that overrides functions
+        such as `__lt__`, `__gt__`, `__le__`, `__ge__`,
+        `__eq__`, `__ne__`, in other words,
+        it should be a comparable object,
+        and it should be comparable to the other priorities
+        of the other elements.
+
+        If element is an `HeapNode`, the `priority` argument's value is ignored,
+        and the priority of the key field of `HeapNode` is used as priority."""
+        if not isinstance(element, HeapNode):
+            self.add(HeapNode(key=priority, value=element))
+        else:
+            self.add(element)
+
+    def extract_min(self, priority=False):
+        """Removes and returns the element with highest priority to exit from the queue.
+
+        In this `MinPriorityQueue` implementation,
+        if A has an higher priority than B,
+        then `A.priority <= B.priority`.
+
+        If `priority` is set to `True`, a tuple is returned,
+        whose first item is the initial added element
+        and the second item is the priority of the element.
+
+        If self is empty, `None` is returned.
+
+        **Time Complexity**: O(log2(n))."""
+        m = self.remove_min()
+        if m is not None:
+            if priority:
+                return (m.value, m.key)
+            else:
+                return m.value
+
+    def peek(self, priority=False):
+        """Returns (without removing) the element with highest priority.
+
+        In this `MinPriorityQueue` implementation,
+        if A has an higher priority than B,
+        then `A.priority <= B.priority`.
+
+        If priority is set to True,
+        a tuple of the form (element, priority) is returned,
+        otherwise only element is returned,
+        where element is the element in self with highest priority.
+
+        **Time Complexity**: O(1)."""
+        m = self.find_min()
+        if m is not None:
+            if priority:
+                return (m.value, m.key)
+            else:
+                return m.value
+
+    def contains(self, element):
+        """Returns `True` if element is in this min-priority queue, `False` otherwise.
+
+        **Time Complexity**: O(n)."""
+        return self.search_by_value(element) != -1
+
+    def change_priority(self, element, new_priority):
+        """Assigns `new_priority` to be the new priority of `element`.
+
+        If `element` is not in this min-priority queue,
+        a `LookupError` is raised.
+
+        **Time Complexity**: O(n)."""
+        i = self.search_by_value(element)
+        if i == -1:
+            raise LookupError("No element found.")
+        self.replace(i, HeapNode(key=new_priority, value=element))
+
+
+ +
+ +
+ + +

Classes

+ +
+

class MinPriorityQueue

+ + +

Abstract class to represent binary heaps.

+

MinHeap, MaxHeap and MinMaxHeap all derive from this class.

+
+ +
+
class MinPriorityQueue(MinHeap):
+    def __init__(self, ls=None):
+        """If `ls` is provided, it should be a list of tuples,
+        each of which contains 2 items:
+        The first item of the tuple is the priority of the element,
+        the second item of the tuple is the element.
+        A smaller number means a higher priority."""
+        MinHeap.__init__(self, ls)
+
+    def insert(self, element: object, priority=sys.maxsize):
+        """Adds `element` to this min priority queue.
+
+        You can specify the priority of the element
+        by assigning a value to "priority".
+
+        Note that the value assigned to priority
+        should be an object that overrides functions
+        such as `__lt__`, `__gt__`, `__le__`, `__ge__`,
+        `__eq__`, `__ne__`, in other words,
+        it should be a comparable object,
+        and it should be comparable to the other priorities
+        of the other elements.
+
+        If element is an `HeapNode`, the `priority` argument's value is ignored,
+        and the priority of the key field of `HeapNode` is used as priority."""
+        if not isinstance(element, HeapNode):
+            self.add(HeapNode(key=priority, value=element))
+        else:
+            self.add(element)
+
+    def extract_min(self, priority=False):
+        """Removes and returns the element with highest priority to exit from the queue.
+
+        In this `MinPriorityQueue` implementation,
+        if A has an higher priority than B,
+        then `A.priority <= B.priority`.
+
+        If `priority` is set to `True`, a tuple is returned,
+        whose first item is the initial added element
+        and the second item is the priority of the element.
+
+        If self is empty, `None` is returned.
+
+        **Time Complexity**: O(log2(n))."""
+        m = self.remove_min()
+        if m is not None:
+            if priority:
+                return (m.value, m.key)
+            else:
+                return m.value
+
+    def peek(self, priority=False):
+        """Returns (without removing) the element with highest priority.
+
+        In this `MinPriorityQueue` implementation,
+        if A has an higher priority than B,
+        then `A.priority <= B.priority`.
+
+        If priority is set to True,
+        a tuple of the form (element, priority) is returned,
+        otherwise only element is returned,
+        where element is the element in self with highest priority.
+
+        **Time Complexity**: O(1)."""
+        m = self.find_min()
+        if m is not None:
+            if priority:
+                return (m.value, m.key)
+            else:
+                return m.value
+
+    def contains(self, element):
+        """Returns `True` if element is in this min-priority queue, `False` otherwise.
+
+        **Time Complexity**: O(n)."""
+        return self.search_by_value(element) != -1
+
+    def change_priority(self, element, new_priority):
+        """Assigns `new_priority` to be the new priority of `element`.
+
+        If `element` is not in this min-priority queue,
+        a `LookupError` is raised.
+
+        **Time Complexity**: O(n)."""
+        i = self.search_by_value(element)
+        if i == -1:
+            raise LookupError("No element found.")
+        self.replace(i, HeapNode(key=new_priority, value=element))
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • MinPriorityQueue
  • +
  • ands.ds.MinHeap.MinHeap
  • +
  • ands.ds.heap.BinaryHeap
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, ls=None)

+
+ + + + +

If ls is provided, it should be a list of tuples, +each of which contains 2 items: +The first item of the tuple is the priority of the element, +the second item of the tuple is the element. +A smaller number means a higher priority.

+
+ +
+
def __init__(self, ls=None):
+    """If `ls` is provided, it should be a list of tuples,
+    each of which contains 2 items:
+    The first item of the tuple is the priority of the element,
+    the second item of the tuple is the element.
+    A smaller number means a higher priority."""
+    MinHeap.__init__(self, ls)
+
+
+
+ +
+ + +
+
+

def add(

self, x)

+
+ + + + +

Adds x to this heap.

+

In practice, it places x at an available leaf, +then "bubbles up" from there, +in order to maintain the heap property.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(log2 n).

+
+ +
+
def add(self, x) -> None:
+    """Adds `x` to this heap.
+    In practice, it places `x` at an available leaf,
+    then "bubbles up" from there,
+    in order to maintain the heap property.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(log2 n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    self.heap.append(x)
+    if self.size() > 1:
+        self.push_up(self.size() - 1)
+
+
+
+ +
+ + +
+
+

def build_heap(

self)

+
+ + + + +

Builds the heap data structure from self.heap.

+
+ +
+
def build_heap(self) -> list:
+    """Builds the heap data structure from `self.heap`."""
+    if self.heap:
+        for index in range(len(self.heap) // 2, -1, -1):
+            self.push_down(index)
+    return self.heap
+
+
+
+ +
+ + +
+
+

def change_priority(

self, element, new_priority)

+
+ + + + +

Assigns new_priority to be the new priority of element.

+

If element is not in this min-priority queue, +a LookupError is raised.

+

Time Complexity: O(n).

+
+ +
+
def change_priority(self, element, new_priority):
+    """Assigns `new_priority` to be the new priority of `element`.
+    If `element` is not in this min-priority queue,
+    a `LookupError` is raised.
+    **Time Complexity**: O(n)."""
+    i = self.search_by_value(element)
+    if i == -1:
+        raise LookupError("No element found.")
+    self.replace(i, HeapNode(key=new_priority, value=element))
+
+
+
+ +
+ + +
+
+

def clear(

self)

+
+ + + + +

Clears all nodes from this heap. +This mean that if you call is_empty, +it will return True.

+

Time Complexity: O(1).

+
+ +
+
def clear(self) -> None:
+    """Clears all nodes from this heap.
+    This mean that if you call `is_empty`,
+    it will return `True`.
+    **Time Complexity:** O(1)."""
+    self.heap.clear()
+
+
+
+ +
+ + +
+
+

def contains(

self, element)

+
+ + + + +

Returns True if element is in this min-priority queue, False otherwise.

+

Time Complexity: O(n).

+
+ +
+
def contains(self, element):
+    """Returns `True` if element is in this min-priority queue, `False` otherwise.
+    **Time Complexity**: O(n)."""
+    return self.search_by_value(element) != -1
+
+
+
+ +
+ + +
+
+

def delete(

self, i)

+
+ + + + +

Deletes and returns the HeapNode object at index i.

+

IndexError is raised if i is not a valid index.

+

Implementation based on: +http://www.math.clemson.edu/~warner/M865/HeapDelete.html

+

Time Complexity: O(log2 h), +where h is the number of nodes rooted at i.

+
+ +
+
def delete(self, i: int) -> HeapNode:
+    """Deletes and returns the `HeapNode` object at index `i`.
+    `IndexError` is raised if `i` is not a valid index.
+    Implementation based on:
+    [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
+    **Time Complexity:** O(log2 h),
+    where `h` is the number of nodes rooted at `i`."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    if i == self.size() - 1:
+        return self.heap.pop()
+    self.swap(i, self.size() - 1)
+    d = self.heap.pop()
+    self.push_down(i)
+    return d
+
+
+
+ +
+ + +
+
+

def extract_min(

self, priority=False)

+
+ + + + +

Removes and returns the element with highest priority to exit from the queue.

+

In this MinPriorityQueue implementation, +if A has an higher priority than B, +then A.priority <= B.priority.

+

If priority is set to True, a tuple is returned, +whose first item is the initial added element +and the second item is the priority of the element.

+

If self is empty, None is returned.

+

Time Complexity: O(log2(n)).

+
+ +
+
def extract_min(self, priority=False):
+    """Removes and returns the element with highest priority to exit from the queue.
+    In this `MinPriorityQueue` implementation,
+    if A has an higher priority than B,
+    then `A.priority <= B.priority`.
+    If `priority` is set to `True`, a tuple is returned,
+    whose first item is the initial added element
+    and the second item is the priority of the element.
+    If self is empty, `None` is returned.
+    **Time Complexity**: O(log2(n))."""
+    m = self.remove_min()
+    if m is not None:
+        if priority:
+            return (m.value, m.key)
+        else:
+            return m.value
+
+
+
+ +
+ + +
+
+

def find_min(

self)

+
+ + + + +

Returns (without removing) the smallest element in this min-heap.

+

Time Complexity: O(1).

+
+ +
+
def find_min(self) -> HeapNode:
+    """Returns (without removing) the smallest element in this min-heap.
+    **Time Complexity:** O(1)."""
+    return self.heap[0] if not self.is_empty() else None
+
+
+
+ +
+ + +
+
+

def grandparent_index(

self, i)

+
+ + + + +

Returns the grandparent's index of the node at index i.

+

-1 is returned either if i has not a parent or +the parent of i does not have a parent.

+

Time Complexity: O(1).

+
+ +
+
def grandparent_index(self, i: int) -> int:
+    """Returns the grandparent's index of the node at index `i`.
+    -1 is returned either if `i` has not a parent or
+    the parent of `i` does not have a parent.
+    **Time Complexity:** O(1)."""
+    p = self.parent_index(i)
+    return -1 if p == -1 else self.parent_index(p)
+
+
+
+ +
+ + +
+
+

def has_children(

self, i)

+
+ + + + +

Returns True if the node at index i +has at least one child, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def has_children(self, i: int) -> bool:
+    """Returns `True` if the node at index `i`
+    has at least one child, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return self.left_index(i) != -1 or self.right_index(i) != -1
+
+
+
+ +
+ + +
+
+

def insert(

self, element, priority=9223372036854775807)

+
+ + + + +

Adds element to this min priority queue.

+

You can specify the priority of the element +by assigning a value to "priority".

+

Note that the value assigned to priority +should be an object that overrides functions +such as __lt__, __gt__, __le__, __ge__, +__eq__, __ne__, in other words, +it should be a comparable object, +and it should be comparable to the other priorities +of the other elements.

+

If element is an HeapNode, the priority argument's value is ignored, +and the priority of the key field of HeapNode is used as priority.

+
+ +
+
def insert(self, element: object, priority=sys.maxsize):
+    """Adds `element` to this min priority queue.
+    You can specify the priority of the element
+    by assigning a value to "priority".
+    Note that the value assigned to priority
+    should be an object that overrides functions
+    such as `__lt__`, `__gt__`, `__le__`, `__ge__`,
+    `__eq__`, `__ne__`, in other words,
+    it should be a comparable object,
+    and it should be comparable to the other priorities
+    of the other elements.
+    If element is an `HeapNode`, the `priority` argument's value is ignored,
+    and the priority of the key field of `HeapNode` is used as priority."""
+    if not isinstance(element, HeapNode):
+        self.add(HeapNode(key=priority, value=element))
+    else:
+        self.add(element)
+
+
+
+ +
+ + +
+
+

def is_child(

self, c, i)

+
+ + + + +

Returns True if c is a child of i. False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_child(self, c: int, i: int) -> bool:
+    """Returns `True` if `c` is a child of `i`. `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(c) or not self.is_good_index(i):
+        raise IndexError("i or c are not valid indexes.")
+    return c == self.left_index(i) or c == self.right_index(i)
+
+
+
+ +
+ + +
+
+

def is_empty(

self)

+
+ + + + +

Returns True if this heap is empty.

+

Time Complexity: O(1).

+
+ +
+
def is_empty(self) -> bool:
+    """Returns `True` if this heap is empty.
+    **Time Complexity:** O(1)."""
+    return self.size() == 0
+
+
+
+ +
+ + +
+
+

def is_good_index(

self, i)

+
+ + + + +

Returns True if i is valid index for self.heap, +False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_good_index(self, i: int) -> bool:
+    """Returns `True` if `i` is valid index for `self.heap`,
+    `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not isinstance(i, int):
+        raise TypeError("indexes can only be int.")
+    return False if (i < 0 or i >= self.size()) else True
+
+
+
+ +
+ + +
+
+

def is_grandchild(

self, g, i)

+
+ + + + +

Returns True if g is a grandchild of i. False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_grandchild(self, g: int, i: int) -> bool:
+    """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
+    **Time Complexity:** O(1)."""
+    l = self.left_index(i)
+    if l == -1:
+        assert self.right_index(i) == -1
+        if not self.is_good_index(g):
+            raise IndexError("g is not a valid index.")
+        return False
+    r = self.right_index(i)
+    if r == -1:
+        return self.is_child(g, l)
+    else:
+        return self.is_child(g, l) or self.is_child(g, r)
+
+
+
+ +
+ + +
+
+

def is_grandparent(

self, g, i)

+
+ + + + +

Returns True if g is the index of the grandparent +of the node at i, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_grandparent(self, g: int, i: int) -> bool:
+    """Returns `True` if `g` is the index of the grandparent
+    of the node at `i`, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(g):
+        raise IndexError("g is not a valid index.")
+    p = self.parent_index(i)
+    return False if p == -1 else self.is_parent(g, p)
+
+
+
+ +
+ + +
+
+

def is_on_even_level(

self, i)

+
+ + + + +

Returns True if node at index i is on a even-level, +i.e., if i is on a level multiple of 2 (0, 2, 4, 6,...). +If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(int(math.log2(i + 1) % 2) == 0).

+
+ +
+
def is_on_even_level(self, i: int) -> bool:
+    """Returns `True` if node at index `i` is on a even-level,
+    i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return int(math.log2(i + 1) % 2) == 0
+
+
+
+ +
+ + +
+
+

def is_on_odd_level(

self, i)

+
+ + + + +

Returns True (False) if self.is_on_even_level(i) returns False (True).

+
+ +
+
def is_on_odd_level(self, i: int) -> bool:
+    """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
+    return not self.is_on_even_level(i)
+
+
+
+ +
+ + +
+
+

def is_parent(

self, p, i)

+
+ + + + +

Returns True if p is the index of the parent +of the node at i, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_parent(self, p: int, i: int) -> bool:
+    """Returns `True` if `p` is the index of the parent
+    of the node at `i`, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(p):
+        raise IndexError("p is not a valid index.")
+    return self.parent_index(i) == p
+
+
+
+ +
+ + +
+
+

def left_index(

self, i)

+
+ + + + +

Returns the left child's index of the node at index i, +if it exists, otherwise this function returns -1.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def left_index(self, i: int) -> int:
+    """Returns the left child's index of the node at index `i`,
+    if it exists, otherwise this function returns -1.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    left = i * 2 + 1
+    return left if self.is_good_index(left) else -1
+
+
+
+ +
+ + +
+
+

def merge(

self, o)

+
+ + + + +

Merges this heap with the o heap.

+

Returns the list object representing internally the new merged heap.

+

Time Complexity: O(n + m).

+

Time complexity analysis based on: +http://stackoverflow.com/a/29197855/3924118.

+
+ +
+
def merge(self, o) -> list:
+    """Merges this heap with the `o` heap.
+    Returns the `list` object representing internally the new merged heap.
+    **Time Complexity:** O(n + m).
+    Time complexity analysis based on:
+    [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
+    self.heap += o.heap
+    return self.build_heap()
+
+
+
+ +
+ + +
+
+

def parent_index(

self, i)

+
+ + + + +

Returns the parent's index of the node at index i. +If i = 0, then -1 is returned, because the root has no parent.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def parent_index(self, i: int) -> int:
+    """Returns the parent's index of the node at index `i`.
+    If `i = 0`, then -1 is returned, because the root has no parent.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return -1 if i == 0 else (i - 1) // 2
+
+
+
+ +
+ + +
+
+

def peek(

self, priority=False)

+
+ + + + +

Returns (without removing) the element with highest priority.

+

In this MinPriorityQueue implementation, +if A has an higher priority than B, +then A.priority <= B.priority.

+

If priority is set to True, +a tuple of the form (element, priority) is returned, +otherwise only element is returned, +where element is the element in self with highest priority.

+

Time Complexity: O(1).

+
+ +
+
def peek(self, priority=False):
+    """Returns (without removing) the element with highest priority.
+    In this `MinPriorityQueue` implementation,
+    if A has an higher priority than B,
+    then `A.priority <= B.priority`.
+    If priority is set to True,
+    a tuple of the form (element, priority) is returned,
+    otherwise only element is returned,
+    where element is the element in self with highest priority.
+    **Time Complexity**: O(1)."""
+    m = self.find_min()
+    if m is not None:
+        if priority:
+            return (m.value, m.key)
+        else:
+            return m.value
+
+
+
+ +
+ + +
+
+

def push_down(

self, i)

+
+ + + + +

'Min-heapify' this min-heap starting from index i.

+

Time Complexity: O(log2 n).

+
+ +
+
def push_down(self, i: int) -> None:
+    """'Min-heapify' this min-heap starting from index `i`.
+    **Time Complexity:** O(log2 n)."""
+    m = i  # index of node with smallest value among i and its children
+    l = self.left_index(i)
+    r = self.right_index(i)
+    if l != -1 and self.heap[l] < self.heap[m]:
+        m = l
+    if r != -1 and self.heap[r] < self.heap[m]:
+        m = r
+    if m != i:
+        self.swap(m, i)
+        self.push_down(m)
+
+
+
+ +
+ + +
+
+

def push_up(

self, i)

+
+ + + + +

Pushes up the node at index i.

+

Note that this operation only happens +if the node at index i is smaller than its parent.

+

This function is simpler than push_down (or also called min-heapify), +because in this case we just need to compare +the current node's index with its parent's index.

+

Time Complexity: O(log2 n).

+
+ +
+
def push_up(self, i: int) -> None:
+    """Pushes up the node at index `i`.
+    Note that this operation only happens
+    if the node at index `i` is smaller than its parent.
+    This function is simpler than `push_down` (or also called min-heapify),
+    because in this case we just need to compare
+    the current node's index with its parent's index.
+    **Time Complexity:** O(log2 n)."""
+    c = i  # current index
+    p = self.parent_index(i)
+    if p != -1 and self.heap[c] < self.heap[p]:
+        c = p
+    if c != i:
+        self.swap(c, i)
+        self.push_up(c)
+
+
+
+ +
+ + +
+
+

def remove_min(

self)

+
+ + + + +

Removes and returns the smallest element in this heap.

+

Time Complexity: O(log2 n), +if removing the last element of a list is a constant-time operation.

+
+ +
+
def remove_min(self) -> HeapNode:
+    """Removes and returns the smallest element in this heap.
+    **Time Complexity:** O(log2 n),
+    if removing the last element of a list is a constant-time operation."""
+    if not self.is_empty():
+        self.swap(0, self.size() - 1)
+        m = self.heap.pop()
+        if not self.is_empty():
+            self.push_down(0)
+        return m
+
+
+
+ +
+ + +
+
+

def replace(

self, i, x)

+
+ + + + +

Replaces element at index i with x.

+

x can either be a key or a HeapNode object. +If it's a key, then a HeapNode object +first created to represent x.

+
    +
  1. +

    If x == self.heap[i], +then just replace self.heap[i] with x.

    +
  2. +
  3. +

    Else if x < self.heap[i], +then push_up(index).

    +
  4. +
  5. +

    Else x > self.heap[i], +then call self.push_down(i).

    +
  6. +
+

Returns the previous HeapNode object at i.

+

Time Complexity: O(log2 n).

+
+ +
+
def replace(self, i: int, x) -> HeapNode:
+    """Replaces element at index `i` with `x`.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, then a `HeapNode` object
+    first created to represent `x`.
+    1. If `x == self.heap[i]`,
+    then just replace `self.heap[i]` with `x`.
+    2. Else if `x < self.heap[i]`,
+    then push_up(index).
+    3. Else `x > self.heap[i]`,
+    then call `self.push_down(i)`.
+    Returns the previous `HeapNode` object at `i`.
+    **Time Complexity:** O(log2 n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    c = self.heap[i]
+    self.heap[i] = x
+    if x > c:
+        self.push_down(i)
+    elif x < c:
+        self.push_up(i)
+    return c
+
+
+
+ +
+ + +
+
+

def right_index(

self, i)

+
+ + + + +

Returns the right child's index of the node at index i, +if it exists, otherwise this function returns -1.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def right_index(self, i: int) -> int:
+    """Returns the right child's index of the node at index `i`,
+    if it exists, otherwise this function returns -1.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    right = i * 2 + 2
+    return right if self.is_good_index(right) else -1
+
+
+
+ +
+ + +
+
+

def search(

self, x)

+
+ + + + +

Searches for x in this heap, +and, if present, returns its index, otherwise returns -1.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(n).

+
+ +
+
def search(self, x) -> int:
+    """Searches for `x` in this heap,
+    and, if present, returns its index, otherwise returns -1.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    for i, node in enumerate(self.heap):
+        if node == x:
+            return i
+    return -1
+
+
+
+ +
+ + +
+
+

def search_by_value(

self, val)

+
+ + + + +

Returns the index of the HeapNode object with value=val. +-1 is returned if no such a HeapNode object exists.

+

If val and the values in this heap are not comparable, +the behaviour of this method is undefined.

+

By construction, HeapNode objects can't be initialized with None values, +but that field could also be set manually after creation.

+

Time Complexity: O(n).

+
+ +
+
def search_by_value(self, val) -> int:
+    """Returns the index of the `HeapNode` object with `value=val`.
+    -1 is returned if no such a `HeapNode` object exists.
+    If `val` and the values in this heap are not comparable,
+    the behaviour of this method is undefined.
+    By construction, HeapNode objects can't be initialized with None values,
+    but that field could also be set manually after creation.
+    **Time Complexity:** O(n)."""
+    for i, node in enumerate(self.heap):
+        if node.value == val:
+            return i
+    return -1
+
+
+
+ +
+ + +
+
+

def size(

self)

+
+ + + + +

Returns the size of this heaps.

+

Time Complexity: O(1).

+
+ +
+
def size(self) -> int:
+    """Returns the size of this heaps.
+    **Time Complexity:** O(1)."""
+    return len(self.heap)
+
+
+
+ +
+ + +
+
+

def swap(

self, i, j)

+
+ + + + +

Swaps elements at indexes i and j, +if they are valid indexes, +otherwise an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def swap(self, i: int, j: int) -> None:
+    """Swaps elements at indexes `i` and `j`,
+    if they are valid indexes,
+    otherwise an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if self.is_good_index(i) and self.is_good_index(j):
+        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
+    else:
+        raise IndexError("i or j are not valid indexes.")
+
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/Queue.m.html b/docs/ands/ds/Queue.m.html new file mode 100644 index 00000000..a096212c --- /dev/null +++ b/docs/ands/ds/Queue.m.html @@ -0,0 +1,1349 @@ + + + + + + ands.ds.Queue API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.Queue module

+

Meta info

+

Author: Nelson Brochado

+

Created: 02/07/2015

+

Updated: 04/02/2017

+

Description

+

Basic queue, which is FIFO (first-in-first-out) data structure. +It's implemented using a deque, because a deque supports better the dequeue operation than lists.

+

References

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+# Meta info
+
+Author: Nelson Brochado
+
+Created: 02/07/2015
+
+Updated: 04/02/2017
+
+# Description
+
+Basic queue, which is FIFO (first-in-first-out) data structure.
+It's implemented using a deque, because a deque supports better the _dequeue_ operation than lists.
+
+# References
+
+- [https://docs.python.org/3.1/tutorial/datastructures.html#using-lists-as-queues](https://docs.python.org/3.1/tutorial/datastructures.html#using-lists-as-queues)
+
+"""
+
+from collections import deque, Iterable
+
+__all__ = ["Queue"]
+
+
+class Queue:
+    """This is a wrapper class around the Python deque data structure
+    to represent logically a queue (FIFO) data structure.
+
+    You can initialize the class using an iterable (list, tuple, etc) of values,
+    which will be assumed to be already in the FIFO order.
+
+    If `ls` is not an instance of `Iterable`, `TypeError` is raised.
+    If one of the values in `ls` is None, `ValueError` is raised.
+    A copy of `ls` is made, so that changes to the original self
+    do not reflect in the original iterable.
+
+    This class does not allow None to be inserted as value
+    to the data structure through the methods of the same.
+
+    It also returns None, instead of raising exceptions,
+    when trying to dequeue, when the data structure is empty.
+
+    Clients of this class should **never** access fields whose names start with _,
+    which are considered private fields."""
+
+    def __init__(self, ls=None):
+        if ls is not None:
+            if not isinstance(ls, Iterable):
+                raise TypeError("ls must be an iterable object")
+            if any(elem is None for elem in ls):
+                raise ValueError("all elements of ls must be not None")
+        else:
+            ls = []
+        self._q = deque(ls)
+
+    def enqueue(self, elem) -> None:
+        """Adds `elem` to the end of this queue.
+        If `elem` is None, ValueError is raised."""
+        if elem is None:
+            raise ValueError("elem cannot be None")
+        self._q.append(elem)
+
+    def dequeue(self):
+        """Returns the first element of this queue, or None if the queue is empty."""
+        return None if self.is_empty() else self._q.popleft()
+
+    def is_empty(self) -> bool:
+        """Returns `True` if this queue is empty, `False` otherwise."""
+        return self.size() == 0
+
+    def size(self) -> int:
+        """Returns the size of this queue."""
+        return len(self._q)
+
+    def __str__(self):
+        return str(list(self._q))
+
+    def __repr__(self):
+        return self.__str__()
+
+
+ +
+ +
+ + +

Classes

+ +
+

class Queue

+ + +

This is a wrapper class around the Python deque data structure +to represent logically a queue (FIFO) data structure.

+

You can initialize the class using an iterable (list, tuple, etc) of values, +which will be assumed to be already in the FIFO order.

+

If ls is not an instance of Iterable, TypeError is raised. +If one of the values in ls is None, ValueError is raised. +A copy of ls is made, so that changes to the original self +do not reflect in the original iterable.

+

This class does not allow None to be inserted as value +to the data structure through the methods of the same.

+

It also returns None, instead of raising exceptions, +when trying to dequeue, when the data structure is empty.

+

Clients of this class should never access fields whose names start with _, +which are considered private fields.

+
+ +
+
class Queue:
+    """This is a wrapper class around the Python deque data structure
+    to represent logically a queue (FIFO) data structure.
+
+    You can initialize the class using an iterable (list, tuple, etc) of values,
+    which will be assumed to be already in the FIFO order.
+
+    If `ls` is not an instance of `Iterable`, `TypeError` is raised.
+    If one of the values in `ls` is None, `ValueError` is raised.
+    A copy of `ls` is made, so that changes to the original self
+    do not reflect in the original iterable.
+
+    This class does not allow None to be inserted as value
+    to the data structure through the methods of the same.
+
+    It also returns None, instead of raising exceptions,
+    when trying to dequeue, when the data structure is empty.
+
+    Clients of this class should **never** access fields whose names start with _,
+    which are considered private fields."""
+
+    def __init__(self, ls=None):
+        if ls is not None:
+            if not isinstance(ls, Iterable):
+                raise TypeError("ls must be an iterable object")
+            if any(elem is None for elem in ls):
+                raise ValueError("all elements of ls must be not None")
+        else:
+            ls = []
+        self._q = deque(ls)
+
+    def enqueue(self, elem) -> None:
+        """Adds `elem` to the end of this queue.
+        If `elem` is None, ValueError is raised."""
+        if elem is None:
+            raise ValueError("elem cannot be None")
+        self._q.append(elem)
+
+    def dequeue(self):
+        """Returns the first element of this queue, or None if the queue is empty."""
+        return None if self.is_empty() else self._q.popleft()
+
+    def is_empty(self) -> bool:
+        """Returns `True` if this queue is empty, `False` otherwise."""
+        return self.size() == 0
+
+    def size(self) -> int:
+        """Returns the size of this queue."""
+        return len(self._q)
+
+    def __str__(self):
+        return str(list(self._q))
+
+    def __repr__(self):
+        return self.__str__()
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • Queue
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, ls=None)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, ls=None):
+    if ls is not None:
+        if not isinstance(ls, Iterable):
+            raise TypeError("ls must be an iterable object")
+        if any(elem is None for elem in ls):
+            raise ValueError("all elements of ls must be not None")
+    else:
+        ls = []
+    self._q = deque(ls)
+
+
+
+ +
+ + +
+
+

def dequeue(

self)

+
+ + + + +

Returns the first element of this queue, or None if the queue is empty.

+
+ +
+
def dequeue(self):
+    """Returns the first element of this queue, or None if the queue is empty."""
+    return None if self.is_empty() else self._q.popleft()
+
+
+
+ +
+ + +
+
+

def enqueue(

self, elem)

+
+ + + + +

Adds elem to the end of this queue. +If elem is None, ValueError is raised.

+
+ +
+
def enqueue(self, elem) -> None:
+    """Adds `elem` to the end of this queue.
+    If `elem` is None, ValueError is raised."""
+    if elem is None:
+        raise ValueError("elem cannot be None")
+    self._q.append(elem)
+
+
+
+ +
+ + +
+
+

def is_empty(

self)

+
+ + + + +

Returns True if this queue is empty, False otherwise.

+
+ +
+
def is_empty(self) -> bool:
+    """Returns `True` if this queue is empty, `False` otherwise."""
+    return self.size() == 0
+
+
+
+ +
+ + +
+
+

def size(

self)

+
+ + + + +

Returns the size of this queue.

+
+ +
+
def size(self) -> int:
+    """Returns the size of this queue."""
+    return len(self._q)
+
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/RBT.m.html b/docs/ands/ds/RBT.m.html new file mode 100644 index 00000000..5a59d299 --- /dev/null +++ b/docs/ands/ds/RBT.m.html @@ -0,0 +1,3715 @@ + + + + + + ands.ds.RBT API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.RBT module

+

Meta info

+

Author: Nelson Brochado

+

Created: 01/08/2015

+

Updated: 28/08/2016

+

Description

+

Red-black Tree Property

+
    +
  1. +

    Every node is either red or black.

    +
  2. +
  3. +

    The root is black.

    +
  4. +
  5. +

    Every NIL or leaf node is black.

    +
  6. +
  7. +

    If a node is red, then both its children are black, +in other words, there cannot be two red nodes in a row.

    +
  8. +
  9. +

    For every node x, each path from x to its descendent leaves +has the same number of black nodes, i.e. bh(x).

    +
  10. +
+

Lemma

+

The height h(x) of a red-black tree with n = size(x) internal nodes +is at most 2 * log2(n + 1), that is, h(x) <= 2 * log2(n + 1), +which is equivalent to h(x)/2 <= log2(n + 1), which is equivalent to +n >= 2h(x)/2 - 1. If you don't understand exactly why this last statements +are equivalent, then do the reversed reasoning:

+
    +
  • +

    n >= 2h(x)/2 - 1

    +
  • +
  • +

    n + 1 >= 2h(x)/2

    +
  • +
+

Now we log both parts

+
    +
  • +

    log2(n + 1) >= log2(2h(x)/2)

    +
  • +
  • +

    log2(n + 1) >= h(x)/2 * log2(2)

    +
  • +
  • +

    log2(n + 1) >= h(x)/2 * 1

    +
  • +
  • +

    2 * log2(n + 1) >= h(x)

    +
  • +
+

Proof

+
    +
  1. +

    Prove that for all x, size(x) >= 2bh(x) - 1 by induction.

    +

    1.1. Base case: x is a leaf, so size(x) = 0 and bh(x) = 0.

    +

    1.2. Induction step: consider y1, y2, +and x such that y1.parent = y2.parent = x, +and assume (induction) that size(y1) >= 2bh(y1) - 1 +and size(y2) >= 2bh(y2) - 1. +Prove that size(x) >= 2bh(x) - 1.

    +

    Proof:

    +

    size(x) = size(y1) + size(y2) + 1 >= (2bh(y1) - 1) ++ (2bh(y2) - 1) + 1

    +

    Since bh(x) = {

    +
    bh(y), if color(y) = red
    +
    +bh(y) + 1, if color(y) = black
    +
    +

    }

    +

    size(x) >= (2bh(x) - 1 - 1) + (2bh(x) - 1 - 1) + 1 += (2bh(x) - 1).

    +

    Since every red node has black children, +in every path from x to a leaf node, +at least half the nodes are black, thus bh(x) >= h(x)/2. +So, n = size(x) >= 2h(x)/2 - 1. Therefore +h(x) <= 2 * log2(n + 1).

    +

    A red-black tree works as a binary-search tree for search, +insert, etc, so the complexity of those operations is T(n) = O(h), +that is T(n) = O(log2 n), which is also the worst case complexity.

    +
  2. +
+

TODO

+
    +
  • Override needed methods inherited from BST.
  • +
+

References

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+# Meta info
+
+Author: Nelson Brochado
+
+Created: 01/08/2015
+
+Updated: 28/08/2016
+
+# Description
+
+## Red-black Tree Property
+
+1. Every node is either red or black.
+
+2. The root is black.
+
+3. Every NIL or leaf node is black.
+
+4. If a node is red, then both its children are black,
+in other words, there cannot be two red nodes in a row.
+
+5. For every node x, each path from x to its descendent leaves
+has the same number of black nodes, i.e. bh(x).
+
+## Lemma
+
+The height `h(x)` of a red-black tree with `n = size(x)` internal nodes
+is at most 2 * log2(n + 1), that is, h(x) <= 2 * log2(n + 1),
+which is equivalent to h(x)/2 <= log2(n + 1), which is equivalent to
+n >= 2h(x)/2 - 1. If you don't understand exactly why this last statements
+are equivalent, then do the reversed reasoning:
+
+* n >= 2h(x)/2 - 1
+
+* n + 1 >= 2h(x)/2
+
+Now we log both parts
+
+* log2(n + 1) >= log2(2h(x)/2)
+
+* log2(n + 1) >= h(x)/2 * log2(2)
+
+* log2(n + 1) >= h(x)/2 * 1
+
+* 2 * log2(n + 1) >= h(x)
+
+
+### Proof
+
+1. Prove that for all `x`, size(x) >= 2bh(x) - 1 by induction.
+
+    1.1. **Base case**: `x` is a leaf, so `size(x) = 0` and `bh(x) = 0`.
+
+    1.2. **Induction step**: consider y1, y2,
+    and `x` such that y1.parent = y2.parent = x,
+    and assume (induction) that size(y1) >= 2bh(y1) - 1
+    and size(y2) >= 2bh(y2) - 1.
+    Prove that size(x) >= 2bh(x) - 1.
+
+    **Proof**:
+
+    size(x) = size(y1) + size(y2) + 1 >= (2bh(y1) - 1)
+    + (2bh(y2) - 1) + 1
+
+    Since bh(x) = {
+
+        bh(y), if color(y) = red
+
+        bh(y) + 1, if color(y) = black
+    }
+
+    size(x) >= (2bh(x) - 1 - 1) + (2bh(x) - 1 - 1) + 1
+    = (2bh(x) - 1).
+
+    Since every red node has black children,
+    in every path from `x` to a leaf node,
+    at least half the nodes are black, thus bh(x) >= h(x)/2.
+    So, n = size(x) >= 2h(x)/2 - 1. Therefore
+    h(x) <= 2 * log2(n + 1).
+
+    A red-black tree works as a binary-search tree for search,
+    insert, etc, so the complexity of those operations is T(n) = O(h),
+    that is T(n) = O(log2 n), which is also the worst case complexity.
+
+# TODO
+
+- Override needed methods inherited from BST.
+
+# References
+
+- [https://en.wikipedia.org/wiki/Red%E2%80%93black_tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree)
+- Slides by prof. A. Carzaniga
+- Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS
+
+"""
+
+import math
+
+from ands.ds.BST import BST, BSTNode, is_bst
+
+__all__ = ["RBT", "RBTNode", "is_rbt", "bh", "upper_bound_height"]
+
+RED = "RED"
+BLACK = "BLACK"
+
+
+class RBTNode(BSTNode):
+    """Class to represent a `RBT`'s node."""
+
+    def __init__(self, key, value=None, color=BLACK,
+                 parent=None, left=None, right=None):
+        BSTNode.__init__(self, key, value, parent, left, right)
+        self._color = color
+        self.label = "[" + str(self.key) + ", " + str(self._color) + "]"
+
+    @property
+    def color(self):
+        return self._color
+
+    @color.setter
+    def color(self, value):
+        self._color = value
+        self.label = "[" + str(self.key) + ", " + str(self._color) + "]"
+
+    def reset(self):
+        super().reset()
+        self.color = BLACK
+
+    def __fields(self):
+        """Used by __repr__."""
+        return [["Node (Key)", self.key],
+                ["Value", self.value],
+                ["Color", self.color],
+                ["Parent", self.parent],
+                ["Left child", self.left],
+                ["Right child", self.right],
+                ["Sibling", self.sibling],
+                ["Grandparent", self.grandparent],
+                ["Uncle", self.uncle]]
+
+
+class RBT(BST):
+    """Red-black tree, which is a self-balancing binary-search tree."""
+
+    def __init__(self, root=None, name="RBT"):
+        BST.__init__(self, root, name)
+
+    def insert(self, x, value=None) -> None:
+        """Inserts `x` into this `RBT`.
+
+        This operation is similar to the `insert` operation of a classical `BST`,
+        but, in this case, the red-black tree property must be maintained,
+        so addional work is needed.
+
+        There are several cases of inserting into a RBT to handle:
+
+        1. `x`  is the root node (first node).
+
+        2. `x.parent` is `BLACK`.
+
+        3. `x.parent` and the uncle of `x` are `RED`.
+
+            The uncle of `x` will be the left child of `x.parent.parent`,
+        if `x.parent` is the right child of `x.parent.parent`,
+        otherwise (`x.parent` is the left child of `x.parent.parent`)
+        the uncle will be the right child of `x.parent.parent`.
+
+        4. x.parent is RED, but x.uncle is BLACK (or None). x.grandparent exists because x.parent is RED.
+
+            4.1. `x` is added to the right of a left child of `x.parent.parent` (grandparent)
+
+            4.2. or `x` is added to the left of a right child of `x.parent.parent`.
+
+            4.3. `x` is added to the left of a left child of `x.parent.parent`.
+
+            4.4. or `x` is added to the right of a right child of `x.parent.parent`.
+
+        `_fix_insertion` handles these cases in the same order as just presented above.
+
+        **Time Complexity:** O(log2(n))."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+
+        if not isinstance(x, RBTNode):
+            x = RBTNode(x, value)
+
+        if x.left or x.right or x.parent:
+            raise ValueError("x cannot have left or right children, or parent.")
+
+        c = self.root  # Current node
+        p = None  # Current node's parent
+
+        while c is not None:
+            p = c
+            if x.key < c.key:
+                c = c.left
+            else:  # x.key >= c.key
+                c = c.right
+
+        x.parent = p
+
+        # The while loop was not executed even once.
+        # Case 1: node is inserted as root.
+        if p is None:
+            self.root = x
+        elif p.key > x.key:
+            p.left = x
+        else:  # p.key < x.key:
+            p.right = x
+
+        x.color = RED
+        self.n += 1
+        self._fix_insertion(x)
+
+    def _fix_insertion(self, u: RBTNode) -> None:
+        # u is the root and we color it BLACK.
+        if u.parent is None:
+            u.color = BLACK
+
+        elif u.parent.color == BLACK:
+            return
+
+        elif u.parent.color == RED and (u.uncle is not None and u.uncle.color == RED):
+            u.parent.color = BLACK
+            u.uncle.color = BLACK
+            u.grandparent.color = RED
+            self._fix_insertion(u.grandparent)
+
+        elif u.parent.color == RED and (u.uncle is None or u.uncle.color == BLACK):
+
+            # u is added as a right child to a node that is the left child.
+            if u.parent.is_left_child() and u.is_right_child():
+
+                # left_rotation does not violate the property:
+                # all paths from any given node to its leaf nodes
+                # contain the same number of black nodes.
+                self.left_rotate(u.parent)
+
+                # With the previous left_rotate call,
+                # u.parent has become the left child of u,
+                # or, u bas become the parent of what before was u.parent
+                # We can pass to case 5, where we have 2 red nodes in a row,
+                # specifically, u.parent and u,
+                # which are both left children of their parents.
+
+                self._fix_insertion(u.left)
+
+            # u is added as a left child to a node that is the right child.
+            elif u.parent.is_right_child() and u.is_left_child():
+                self.right_rotate(u.parent)
+                self._fix_insertion(u.right)
+
+            # u is added as a left child to a node that is the left child.
+            elif u.parent.is_left_child() and u.is_left_child():
+                # Note that grandparent is known to be black,
+                # since its former child could not have been RED
+                # without violating property 4.
+                self.right_rotate(u.grandparent)
+                u.parent.color = BLACK
+                u.parent.right.color = RED
+
+            # u is added as a right child to a node that is the right child.
+            elif u.parent.is_right_child() and u.is_right_child():
+                self.left_rotate(u.grandparent)
+                u.parent.color = BLACK
+                u.parent.left.color = RED
+
+            else:
+                assert False
+
+    def delete(self, x) -> RBTNode:
+        """Delete `x` from this `RBT` object.
+
+        `x` can either be a `RBTNode` object or a key.
+
+        If a key, then a search is performed first
+        to find the corresponding `RBTNode` object.
+        An exception is raised if a `RBTNode` object
+        with a key=x is not found.
+
+        If `x` is a `RBTNode` object, the only check
+        that is performed is that if it hasn't a parent,
+        then it must be the root. Similarly,
+        a node that isn't the root must have a parent.
+        If `x` has a parent, therefore it cannot be the root,
+        but there's no way of knowing if this node
+        really belongs to this `RBT` object,
+        because no search is performed (for now).
+
+        If it does NOT belong to this `RBT` object,
+        then the behaviour of this method is UNDEFINED!
+
+        **Time Complexity:** O(log2(n))."""
+
+        def delete_case1(v):
+            # this check is necessary because this function
+            # is also called from the delete_case3 function.
+            if v.parent is not None:
+                delete_case2(v)
+
+        def delete_case2(v):
+            if v.sibling.color == RED:
+
+                assert v.parent.color == BLACK
+
+                v.sibling.color = BLACK
+                v.parent.color = RED
+
+                if v.is_left_child():
+                    self.left_rotate(v.parent)
+                else:
+                    self.right_rotate(v.parent)
+
+                assert v.sibling.color == BLACK
+
+            delete_case3(v)
+
+        def delete_case3(v):
+            # not sure if the children of v.sibling can be None
+            if (v.parent.color == BLACK and v.sibling.color == BLACK and
+                    ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
+                    ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
+
+                v.sibling.color = RED
+                delete_case1(v.parent)
+            else:
+                delete_case4(v)
+
+        def delete_case4(v):
+            # not sure if the children of v.sibling can be None
+            if (v.parent.color == RED and v.sibling.color == BLACK and
+                    ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
+                    ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
+
+                v.sibling.color = RED
+                v.parent.color = BLACK
+            else:
+                delete_case5(v)
+
+        def delete_case5(v):
+            assert v.sibling is not None
+
+            if v.sibling.color == BLACK:
+                if (v.is_left_child() and
+                        (not v.sibling.right or v.sibling.right.color == BLACK) and
+                            v.sibling.left.color == RED):
+
+                    v.sibling.color = RED
+                    v.sibling.left.color = BLACK
+                    self.right_rotate(v.sibling)
+
+                elif (v.is_right_child() and
+                          (not v.sibling.left or v.sibling.left.color == BLACK) and
+                              v.sibling.right.color == RED):
+
+                    v.sibling.color = RED
+                    v.sibling.right.color = BLACK
+                    self.left_rotate(v.sibling)
+
+            delete_case6(v)
+
+        def delete_case6(v):
+            assert v.sibling is not None
+
+            v.sibling.color, v.parent.color = v.parent.color, v.sibling.color
+
+            if v.is_left_child():
+                assert v.sibling.right
+                v.sibling.right.color = BLACK
+                self.left_rotate(v.parent)
+            else:
+                assert v.sibling.left
+                v.sibling.left.color = BLACK
+                self.right_rotate(v.parent)
+
+        # a few checks of the inputs given
+        if x is None:
+            raise ValueError("x cannot be None.")
+
+        if not isinstance(x, RBTNode):
+            x = self.search(x)
+            if x is None:
+                raise LookupError("No node was found with key=x.")
+
+        if x.parent is None and not self.is_root(x):
+            raise ValueError("x is not a valid node.")
+
+        # If x has 2 non-leaf children, then replace x with its successor.
+        # Note that we exchange also the colors of x and its successor.
+        if x.left is not None and x.right is not None:
+            s = self.successor(x)
+            self._switch(x, s)
+            x.color, s.color = s.color, x.color
+
+        # At least one of the children must be None.
+        # Particularly, if `x` was exchanged with its successor,
+        # `x` now should NOT have a left child.
+        assert x.left is None or x.right is None
+
+        # At this point `x` has at most 1 child.
+        # Keep in mind this when reading the next cases.
+
+        # If `x` is a red node and it has a child,
+        # we simply replace it with its child `c`,
+        # which must be black by property 4.
+
+        # This can only occur when `x` has 2 leaf children,
+        # because if `x` had a black NON-leaf child on one side,
+        # but just a leaf child on the other side,
+        # then the count of black nodes on both sides would be different,
+        # thus the tree would violate property 5.
+        if x.color == RED:
+
+            # a few checks while in alpha stage
+            assert x.left is None and x.right is None
+            assert not self.is_root(x)
+
+            if x.is_left_child():
+                x.parent.left = None
+            else:
+                x.parent.right = None
+
+        else:  # x.color == BLACK
+
+            # One of the children of `x` is red.
+
+            # Simply removing `x` could break properties 4,
+            # i.e., both children of every red node are black,
+            # because x.parent could be red, and 5,
+            # i.e. all paths from any given node to its leaf nodes
+            # contain the same number of black nodes),
+            # but if we repaint `c` (the child) BLACK,
+            # both of these properties are preserved.
+
+            if x.left is not None and x.left.color == RED:
+                if not self.is_root(x):
+                    if x.is_left_child():
+                        x.parent.left = x.left
+                    else:
+                        x.parent.right = x.left
+
+                x.left.parent = x.parent
+                x.left.color = BLACK
+
+                if self.is_root(x):
+                    self.root = x.left
+
+            elif x.right is not None and x.right.color == RED:
+                if not self.is_root(x):
+                    if x.is_left_child():
+                        x.parent.left = x.right
+                    else:
+                        x.parent.right = x.right
+
+                x.right.parent = x.parent
+                x.right.color = BLACK
+
+                if self.is_root(x):
+                    self.root = x.right
+            else:
+                # This the complex case: both `x` and `c` (the child) are BLACK.
+
+                # This can only occur when deleting a black node
+                # which has 2 LEAF children, because if the black node `x`
+                # had a black NON-leaf child on one side
+                # but just a leaf child on the other side,
+                # then the count of black nodes on both sides would be different,
+                # thus the tree would have been an invalid red–black tree
+                # by violation of property 5.
+                assert x.left is None and x.right is None
+
+                # 6 cases
+                if not self.is_root(x):
+
+                    assert x.sibling is not None
+
+                    # Note that x.sibling cannot be None,
+                    # because otherwise the subtree containing it
+                    # would have fewer black nodes
+                    # than the subtree containing x.
+                    # Specifically, the subtree containing x
+                    # would have a black height of 2,
+                    # whereas the one containing the sibling
+                    # would have a black height of 1.
+
+                    delete_case1(x)
+
+                    # We begin by replacing x with its child c.
+                    # Note that both children of x are leaf children.
+                    if x.is_left_child():
+                        x.parent.left = None
+                    else:
+                        x.parent.right = None
+
+                else:
+                    self.root = None
+
+        self.n -= 1
+        # Ensures that x has no reference to any node of this RBT.
+        x.parent = x.left = x.right = None
+        return x
+
+    def remove_max(self) -> RBTNode:
+        """Removes and returns the element with the greatest value from `self`.
+
+        **Time Complexity:** O(log2(n))."""
+        if self.root:
+            m = self.maximum()
+            assert m
+            return self.delete(m)
+
+    def remove_min(self) -> RBTNode:
+        """Removes and returns the element with the smallest value from `self`.
+
+        **Time Complexity:** O(log2(n))."""
+        if self.root:
+            m = self.minimum()
+            assert m
+            return self.delete(m)
+
+
+def bh(n):
+    """Returns the black-height of the node `n`."""
+    if n is None:
+        return 1
+
+    left_bh = bh(n.left)
+    right_bh = bh(n.right)
+
+    if left_bh != right_bh:
+        return -1
+    else:
+        return left_bh + (1 if n.color == BLACK else 0)
+
+
+def upper_bound_height(t):
+    return t.height() <= 2 * math.log2(t.n + 1)
+
+
+def is_rbt(rbt):
+    """Returns `True` if `rbt` is a valid `RBT` object. `False` otherwise."""
+
+    def prop_1(t):
+        """Returns `True` if all colors are either `RED` or `BLACK`."""
+
+        def h(n):
+            if n:
+                if n.color != BLACK and n.color != RED:
+                    return False
+                return h(n.right) and h(n.left)
+            return True
+
+        return h(t.root)
+
+    def prop_2(t):
+        """Returns `True` if the root is `BLACK` (or it is `None`),
+        `False` otherwise."""
+        if t.root:
+            return t.root.color == BLACK
+        return True
+
+        # def prop_3(t):
+        # leaves are represented with Nones,
+        # so there's not need to check this property.
+
+    #    return True
+
+    def prop_4(t):
+        def h(n):
+            if n:
+                if n.parent and n.color == RED and n.parent.color == RED:
+                    return False
+                if not n.parent and n.color == RED:
+                    return False
+                return h(n.left) and h(n.right)
+            return True
+
+        return h(t.root)
+
+    def prop_5(t):
+        return bh(t.root) != -1
+
+    def all_rbt_nodes(t):
+        def h(n):
+            if n is not None:
+                if not isinstance(n, RBTNode):
+                    return False
+                return h(n.left) and h(n.right)
+            return True
+
+        return h(t.root)
+
+    if not is_bst(rbt):
+        return False
+
+    if not isinstance(rbt, RBT):
+        return False
+
+    if not all_rbt_nodes(rbt):
+        return False
+
+    return prop_1(rbt) and prop_2(rbt) and prop_4(rbt) and prop_5(rbt)
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def bh(

n)

+
+ + + + +

Returns the black-height of the node n.

+
+ +
+
def bh(n):
+    """Returns the black-height of the node `n`."""
+    if n is None:
+        return 1
+
+    left_bh = bh(n.left)
+    right_bh = bh(n.right)
+
+    if left_bh != right_bh:
+        return -1
+    else:
+        return left_bh + (1 if n.color == BLACK else 0)
+
+
+
+ +
+ + +
+
+

def is_rbt(

rbt)

+
+ + + + +

Returns True if rbt is a valid RBT object. False otherwise.

+
+ +
+
def is_rbt(rbt):
+    """Returns `True` if `rbt` is a valid `RBT` object. `False` otherwise."""
+
+    def prop_1(t):
+        """Returns `True` if all colors are either `RED` or `BLACK`."""
+
+        def h(n):
+            if n:
+                if n.color != BLACK and n.color != RED:
+                    return False
+                return h(n.right) and h(n.left)
+            return True
+
+        return h(t.root)
+
+    def prop_2(t):
+        """Returns `True` if the root is `BLACK` (or it is `None`),
+        `False` otherwise."""
+        if t.root:
+            return t.root.color == BLACK
+        return True
+
+        # def prop_3(t):
+        # leaves are represented with Nones,
+        # so there's not need to check this property.
+
+    #    return True
+
+    def prop_4(t):
+        def h(n):
+            if n:
+                if n.parent and n.color == RED and n.parent.color == RED:
+                    return False
+                if not n.parent and n.color == RED:
+                    return False
+                return h(n.left) and h(n.right)
+            return True
+
+        return h(t.root)
+
+    def prop_5(t):
+        return bh(t.root) != -1
+
+    def all_rbt_nodes(t):
+        def h(n):
+            if n is not None:
+                if not isinstance(n, RBTNode):
+                    return False
+                return h(n.left) and h(n.right)
+            return True
+
+        return h(t.root)
+
+    if not is_bst(rbt):
+        return False
+
+    if not isinstance(rbt, RBT):
+        return False
+
+    if not all_rbt_nodes(rbt):
+        return False
+
+    return prop_1(rbt) and prop_2(rbt) and prop_4(rbt) and prop_5(rbt)
+
+
+
+ +
+ + +
+
+

def upper_bound_height(

t)

+
+ + + + +
+ +
+
def upper_bound_height(t):
+    return t.height() <= 2 * math.log2(t.n + 1)
+
+
+
+ +
+ + +

Classes

+ +
+

class RBT

+ + +

Red-black tree, which is a self-balancing binary-search tree.

+
+ +
+
class RBT(BST):
+    """Red-black tree, which is a self-balancing binary-search tree."""
+
+    def __init__(self, root=None, name="RBT"):
+        BST.__init__(self, root, name)
+
+    def insert(self, x, value=None) -> None:
+        """Inserts `x` into this `RBT`.
+
+        This operation is similar to the `insert` operation of a classical `BST`,
+        but, in this case, the red-black tree property must be maintained,
+        so addional work is needed.
+
+        There are several cases of inserting into a RBT to handle:
+
+        1. `x`  is the root node (first node).
+
+        2. `x.parent` is `BLACK`.
+
+        3. `x.parent` and the uncle of `x` are `RED`.
+
+            The uncle of `x` will be the left child of `x.parent.parent`,
+        if `x.parent` is the right child of `x.parent.parent`,
+        otherwise (`x.parent` is the left child of `x.parent.parent`)
+        the uncle will be the right child of `x.parent.parent`.
+
+        4. x.parent is RED, but x.uncle is BLACK (or None). x.grandparent exists because x.parent is RED.
+
+            4.1. `x` is added to the right of a left child of `x.parent.parent` (grandparent)
+
+            4.2. or `x` is added to the left of a right child of `x.parent.parent`.
+
+            4.3. `x` is added to the left of a left child of `x.parent.parent`.
+
+            4.4. or `x` is added to the right of a right child of `x.parent.parent`.
+
+        `_fix_insertion` handles these cases in the same order as just presented above.
+
+        **Time Complexity:** O(log2(n))."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+
+        if not isinstance(x, RBTNode):
+            x = RBTNode(x, value)
+
+        if x.left or x.right or x.parent:
+            raise ValueError("x cannot have left or right children, or parent.")
+
+        c = self.root  # Current node
+        p = None  # Current node's parent
+
+        while c is not None:
+            p = c
+            if x.key < c.key:
+                c = c.left
+            else:  # x.key >= c.key
+                c = c.right
+
+        x.parent = p
+
+        # The while loop was not executed even once.
+        # Case 1: node is inserted as root.
+        if p is None:
+            self.root = x
+        elif p.key > x.key:
+            p.left = x
+        else:  # p.key < x.key:
+            p.right = x
+
+        x.color = RED
+        self.n += 1
+        self._fix_insertion(x)
+
+    def _fix_insertion(self, u: RBTNode) -> None:
+        # u is the root and we color it BLACK.
+        if u.parent is None:
+            u.color = BLACK
+
+        elif u.parent.color == BLACK:
+            return
+
+        elif u.parent.color == RED and (u.uncle is not None and u.uncle.color == RED):
+            u.parent.color = BLACK
+            u.uncle.color = BLACK
+            u.grandparent.color = RED
+            self._fix_insertion(u.grandparent)
+
+        elif u.parent.color == RED and (u.uncle is None or u.uncle.color == BLACK):
+
+            # u is added as a right child to a node that is the left child.
+            if u.parent.is_left_child() and u.is_right_child():
+
+                # left_rotation does not violate the property:
+                # all paths from any given node to its leaf nodes
+                # contain the same number of black nodes.
+                self.left_rotate(u.parent)
+
+                # With the previous left_rotate call,
+                # u.parent has become the left child of u,
+                # or, u bas become the parent of what before was u.parent
+                # We can pass to case 5, where we have 2 red nodes in a row,
+                # specifically, u.parent and u,
+                # which are both left children of their parents.
+
+                self._fix_insertion(u.left)
+
+            # u is added as a left child to a node that is the right child.
+            elif u.parent.is_right_child() and u.is_left_child():
+                self.right_rotate(u.parent)
+                self._fix_insertion(u.right)
+
+            # u is added as a left child to a node that is the left child.
+            elif u.parent.is_left_child() and u.is_left_child():
+                # Note that grandparent is known to be black,
+                # since its former child could not have been RED
+                # without violating property 4.
+                self.right_rotate(u.grandparent)
+                u.parent.color = BLACK
+                u.parent.right.color = RED
+
+            # u is added as a right child to a node that is the right child.
+            elif u.parent.is_right_child() and u.is_right_child():
+                self.left_rotate(u.grandparent)
+                u.parent.color = BLACK
+                u.parent.left.color = RED
+
+            else:
+                assert False
+
+    def delete(self, x) -> RBTNode:
+        """Delete `x` from this `RBT` object.
+
+        `x` can either be a `RBTNode` object or a key.
+
+        If a key, then a search is performed first
+        to find the corresponding `RBTNode` object.
+        An exception is raised if a `RBTNode` object
+        with a key=x is not found.
+
+        If `x` is a `RBTNode` object, the only check
+        that is performed is that if it hasn't a parent,
+        then it must be the root. Similarly,
+        a node that isn't the root must have a parent.
+        If `x` has a parent, therefore it cannot be the root,
+        but there's no way of knowing if this node
+        really belongs to this `RBT` object,
+        because no search is performed (for now).
+
+        If it does NOT belong to this `RBT` object,
+        then the behaviour of this method is UNDEFINED!
+
+        **Time Complexity:** O(log2(n))."""
+
+        def delete_case1(v):
+            # this check is necessary because this function
+            # is also called from the delete_case3 function.
+            if v.parent is not None:
+                delete_case2(v)
+
+        def delete_case2(v):
+            if v.sibling.color == RED:
+
+                assert v.parent.color == BLACK
+
+                v.sibling.color = BLACK
+                v.parent.color = RED
+
+                if v.is_left_child():
+                    self.left_rotate(v.parent)
+                else:
+                    self.right_rotate(v.parent)
+
+                assert v.sibling.color == BLACK
+
+            delete_case3(v)
+
+        def delete_case3(v):
+            # not sure if the children of v.sibling can be None
+            if (v.parent.color == BLACK and v.sibling.color == BLACK and
+                    ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
+                    ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
+
+                v.sibling.color = RED
+                delete_case1(v.parent)
+            else:
+                delete_case4(v)
+
+        def delete_case4(v):
+            # not sure if the children of v.sibling can be None
+            if (v.parent.color == RED and v.sibling.color == BLACK and
+                    ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
+                    ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
+
+                v.sibling.color = RED
+                v.parent.color = BLACK
+            else:
+                delete_case5(v)
+
+        def delete_case5(v):
+            assert v.sibling is not None
+
+            if v.sibling.color == BLACK:
+                if (v.is_left_child() and
+                        (not v.sibling.right or v.sibling.right.color == BLACK) and
+                            v.sibling.left.color == RED):
+
+                    v.sibling.color = RED
+                    v.sibling.left.color = BLACK
+                    self.right_rotate(v.sibling)
+
+                elif (v.is_right_child() and
+                          (not v.sibling.left or v.sibling.left.color == BLACK) and
+                              v.sibling.right.color == RED):
+
+                    v.sibling.color = RED
+                    v.sibling.right.color = BLACK
+                    self.left_rotate(v.sibling)
+
+            delete_case6(v)
+
+        def delete_case6(v):
+            assert v.sibling is not None
+
+            v.sibling.color, v.parent.color = v.parent.color, v.sibling.color
+
+            if v.is_left_child():
+                assert v.sibling.right
+                v.sibling.right.color = BLACK
+                self.left_rotate(v.parent)
+            else:
+                assert v.sibling.left
+                v.sibling.left.color = BLACK
+                self.right_rotate(v.parent)
+
+        # a few checks of the inputs given
+        if x is None:
+            raise ValueError("x cannot be None.")
+
+        if not isinstance(x, RBTNode):
+            x = self.search(x)
+            if x is None:
+                raise LookupError("No node was found with key=x.")
+
+        if x.parent is None and not self.is_root(x):
+            raise ValueError("x is not a valid node.")
+
+        # If x has 2 non-leaf children, then replace x with its successor.
+        # Note that we exchange also the colors of x and its successor.
+        if x.left is not None and x.right is not None:
+            s = self.successor(x)
+            self._switch(x, s)
+            x.color, s.color = s.color, x.color
+
+        # At least one of the children must be None.
+        # Particularly, if `x` was exchanged with its successor,
+        # `x` now should NOT have a left child.
+        assert x.left is None or x.right is None
+
+        # At this point `x` has at most 1 child.
+        # Keep in mind this when reading the next cases.
+
+        # If `x` is a red node and it has a child,
+        # we simply replace it with its child `c`,
+        # which must be black by property 4.
+
+        # This can only occur when `x` has 2 leaf children,
+        # because if `x` had a black NON-leaf child on one side,
+        # but just a leaf child on the other side,
+        # then the count of black nodes on both sides would be different,
+        # thus the tree would violate property 5.
+        if x.color == RED:
+
+            # a few checks while in alpha stage
+            assert x.left is None and x.right is None
+            assert not self.is_root(x)
+
+            if x.is_left_child():
+                x.parent.left = None
+            else:
+                x.parent.right = None
+
+        else:  # x.color == BLACK
+
+            # One of the children of `x` is red.
+
+            # Simply removing `x` could break properties 4,
+            # i.e., both children of every red node are black,
+            # because x.parent could be red, and 5,
+            # i.e. all paths from any given node to its leaf nodes
+            # contain the same number of black nodes),
+            # but if we repaint `c` (the child) BLACK,
+            # both of these properties are preserved.
+
+            if x.left is not None and x.left.color == RED:
+                if not self.is_root(x):
+                    if x.is_left_child():
+                        x.parent.left = x.left
+                    else:
+                        x.parent.right = x.left
+
+                x.left.parent = x.parent
+                x.left.color = BLACK
+
+                if self.is_root(x):
+                    self.root = x.left
+
+            elif x.right is not None and x.right.color == RED:
+                if not self.is_root(x):
+                    if x.is_left_child():
+                        x.parent.left = x.right
+                    else:
+                        x.parent.right = x.right
+
+                x.right.parent = x.parent
+                x.right.color = BLACK
+
+                if self.is_root(x):
+                    self.root = x.right
+            else:
+                # This the complex case: both `x` and `c` (the child) are BLACK.
+
+                # This can only occur when deleting a black node
+                # which has 2 LEAF children, because if the black node `x`
+                # had a black NON-leaf child on one side
+                # but just a leaf child on the other side,
+                # then the count of black nodes on both sides would be different,
+                # thus the tree would have been an invalid red–black tree
+                # by violation of property 5.
+                assert x.left is None and x.right is None
+
+                # 6 cases
+                if not self.is_root(x):
+
+                    assert x.sibling is not None
+
+                    # Note that x.sibling cannot be None,
+                    # because otherwise the subtree containing it
+                    # would have fewer black nodes
+                    # than the subtree containing x.
+                    # Specifically, the subtree containing x
+                    # would have a black height of 2,
+                    # whereas the one containing the sibling
+                    # would have a black height of 1.
+
+                    delete_case1(x)
+
+                    # We begin by replacing x with its child c.
+                    # Note that both children of x are leaf children.
+                    if x.is_left_child():
+                        x.parent.left = None
+                    else:
+                        x.parent.right = None
+
+                else:
+                    self.root = None
+
+        self.n -= 1
+        # Ensures that x has no reference to any node of this RBT.
+        x.parent = x.left = x.right = None
+        return x
+
+    def remove_max(self) -> RBTNode:
+        """Removes and returns the element with the greatest value from `self`.
+
+        **Time Complexity:** O(log2(n))."""
+        if self.root:
+            m = self.maximum()
+            assert m
+            return self.delete(m)
+
+    def remove_min(self) -> RBTNode:
+        """Removes and returns the element with the smallest value from `self`.
+
+        **Time Complexity:** O(log2(n))."""
+        if self.root:
+            m = self.minimum()
+            assert m
+            return self.delete(m)
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • RBT
  • +
  • ands.ds.BST.BST
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, root=None, name='RBT')

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, root=None, name="RBT"):
+    BST.__init__(self, root, name)
+
+
+
+ +
+ + +
+
+

def clear(

self)

+
+ + + + +

Removes all nodes from this tree.

+

Time Complexity: O(1).

+
+ +
+
def clear(self):
+    """Removes all nodes from this tree.
+    **Time Complexity**: O(1)."""
+    self.root = None
+    self.n = 0
+
+
+
+ +
+ + +
+
+

def contains(

self, key)

+
+ + + + +

Returns True if a BSTNode object with key exists in the tree.

+

Time Complexity: O(h).

+
+ +
+
def contains(self, key) -> bool:
+    """Returns `True` if a `BSTNode` object with `key` exists in the tree.
+    **Time Complexity**: O(h)."""
+    return self.search_r(key) is not None
+
+
+
+ +
+ + +
+
+

def delete(

self, x)

+
+ + + + +

Delete x from this RBT object.

+

x can either be a RBTNode object or a key.

+

If a key, then a search is performed first +to find the corresponding RBTNode object. +An exception is raised if a RBTNode object +with a key=x is not found.

+

If x is a RBTNode object, the only check +that is performed is that if it hasn't a parent, +then it must be the root. Similarly, +a node that isn't the root must have a parent. +If x has a parent, therefore it cannot be the root, +but there's no way of knowing if this node +really belongs to this RBT object, +because no search is performed (for now).

+

If it does NOT belong to this RBT object, +then the behaviour of this method is UNDEFINED!

+

Time Complexity: O(log2(n)).

+
+ +
+
def delete(self, x) -> RBTNode:
+    """Delete `x` from this `RBT` object.
+    `x` can either be a `RBTNode` object or a key.
+    If a key, then a search is performed first
+    to find the corresponding `RBTNode` object.
+    An exception is raised if a `RBTNode` object
+    with a key=x is not found.
+    If `x` is a `RBTNode` object, the only check
+    that is performed is that if it hasn't a parent,
+    then it must be the root. Similarly,
+    a node that isn't the root must have a parent.
+    If `x` has a parent, therefore it cannot be the root,
+    but there's no way of knowing if this node
+    really belongs to this `RBT` object,
+    because no search is performed (for now).
+    If it does NOT belong to this `RBT` object,
+    then the behaviour of this method is UNDEFINED!
+    **Time Complexity:** O(log2(n))."""
+    def delete_case1(v):
+        # this check is necessary because this function
+        # is also called from the delete_case3 function.
+        if v.parent is not None:
+            delete_case2(v)
+    def delete_case2(v):
+        if v.sibling.color == RED:
+            assert v.parent.color == BLACK
+            v.sibling.color = BLACK
+            v.parent.color = RED
+            if v.is_left_child():
+                self.left_rotate(v.parent)
+            else:
+                self.right_rotate(v.parent)
+            assert v.sibling.color == BLACK
+        delete_case3(v)
+    def delete_case3(v):
+        # not sure if the children of v.sibling can be None
+        if (v.parent.color == BLACK and v.sibling.color == BLACK and
+                ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
+                ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
+            v.sibling.color = RED
+            delete_case1(v.parent)
+        else:
+            delete_case4(v)
+    def delete_case4(v):
+        # not sure if the children of v.sibling can be None
+        if (v.parent.color == RED and v.sibling.color == BLACK and
+                ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
+                ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
+            v.sibling.color = RED
+            v.parent.color = BLACK
+        else:
+            delete_case5(v)
+    def delete_case5(v):
+        assert v.sibling is not None
+        if v.sibling.color == BLACK:
+            if (v.is_left_child() and
+                    (not v.sibling.right or v.sibling.right.color == BLACK) and
+                        v.sibling.left.color == RED):
+                v.sibling.color = RED
+                v.sibling.left.color = BLACK
+                self.right_rotate(v.sibling)
+            elif (v.is_right_child() and
+                      (not v.sibling.left or v.sibling.left.color == BLACK) and
+                          v.sibling.right.color == RED):
+                v.sibling.color = RED
+                v.sibling.right.color = BLACK
+                self.left_rotate(v.sibling)
+        delete_case6(v)
+    def delete_case6(v):
+        assert v.sibling is not None
+        v.sibling.color, v.parent.color = v.parent.color, v.sibling.color
+        if v.is_left_child():
+            assert v.sibling.right
+            v.sibling.right.color = BLACK
+            self.left_rotate(v.parent)
+        else:
+            assert v.sibling.left
+            v.sibling.left.color = BLACK
+            self.right_rotate(v.parent)
+    # a few checks of the inputs given
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, RBTNode):
+        x = self.search(x)
+        if x is None:
+            raise LookupError("No node was found with key=x.")
+    if x.parent is None and not self.is_root(x):
+        raise ValueError("x is not a valid node.")
+    # If x has 2 non-leaf children, then replace x with its successor.
+    # Note that we exchange also the colors of x and its successor.
+    if x.left is not None and x.right is not None:
+        s = self.successor(x)
+        self._switch(x, s)
+        x.color, s.color = s.color, x.color
+    # At least one of the children must be None.
+    # Particularly, if `x` was exchanged with its successor,
+    # `x` now should NOT have a left child.
+    assert x.left is None or x.right is None
+    # At this point `x` has at most 1 child.
+    # Keep in mind this when reading the next cases.
+    # If `x` is a red node and it has a child,
+    # we simply replace it with its child `c`,
+    # which must be black by property 4.
+    # This can only occur when `x` has 2 leaf children,
+    # because if `x` had a black NON-leaf child on one side,
+    # but just a leaf child on the other side,
+    # then the count of black nodes on both sides would be different,
+    # thus the tree would violate property 5.
+    if x.color == RED:
+        # a few checks while in alpha stage
+        assert x.left is None and x.right is None
+        assert not self.is_root(x)
+        if x.is_left_child():
+            x.parent.left = None
+        else:
+            x.parent.right = None
+    else:  # x.color == BLACK
+        # One of the children of `x` is red.
+        # Simply removing `x` could break properties 4,
+        # i.e., both children of every red node are black,
+        # because x.parent could be red, and 5,
+        # i.e. all paths from any given node to its leaf nodes
+        # contain the same number of black nodes),
+        # but if we repaint `c` (the child) BLACK,
+        # both of these properties are preserved.
+        if x.left is not None and x.left.color == RED:
+            if not self.is_root(x):
+                if x.is_left_child():
+                    x.parent.left = x.left
+                else:
+                    x.parent.right = x.left
+            x.left.parent = x.parent
+            x.left.color = BLACK
+            if self.is_root(x):
+                self.root = x.left
+        elif x.right is not None and x.right.color == RED:
+            if not self.is_root(x):
+                if x.is_left_child():
+                    x.parent.left = x.right
+                else:
+                    x.parent.right = x.right
+            x.right.parent = x.parent
+            x.right.color = BLACK
+            if self.is_root(x):
+                self.root = x.right
+        else:
+            # This the complex case: both `x` and `c` (the child) are BLACK.
+            # This can only occur when deleting a black node
+            # which has 2 LEAF children, because if the black node `x`
+            # had a black NON-leaf child on one side
+            # but just a leaf child on the other side,
+            # then the count of black nodes on both sides would be different,
+            # thus the tree would have been an invalid red–black tree
+            # by violation of property 5.
+            assert x.left is None and x.right is None
+            # 6 cases
+            if not self.is_root(x):
+                assert x.sibling is not None
+                # Note that x.sibling cannot be None,
+                # because otherwise the subtree containing it
+                # would have fewer black nodes
+                # than the subtree containing x.
+                # Specifically, the subtree containing x
+                # would have a black height of 2,
+                # whereas the one containing the sibling
+                # would have a black height of 1.
+                delete_case1(x)
+                # We begin by replacing x with its child c.
+                # Note that both children of x are leaf children.
+                if x.is_left_child():
+                    x.parent.left = None
+                else:
+                    x.parent.right = None
+            else:
+                self.root = None
+    self.n -= 1
+    # Ensures that x has no reference to any node of this RBT.
+    x.parent = x.left = x.right = None
+    return x
+
+
+
+ +
+ + +
+
+

def height(

self)

+
+ + + + +

Returns the maximum depth or height of the tree.

+

Time Complexity: O(h).

+
+ +
+
def height(self) -> int:
+    """Returns the maximum depth or height of the tree.
+    **Time Complexity**: O(h)."""
+    if self.root is None:
+        return 0
+    return self._height(self.root)
+
+
+
+ +
+ + +
+
+

def in_order_traversal(

self)

+
+ + + + +

Prints the elements of the tree in increasing order.

+

Time Complexity: O(h).

+
+ +
+
def in_order_traversal(self):
+    """Prints the elements of the tree in increasing order.
+    **Time Complexity**: O(h)."""
+    self._in_order_traversal(self.root)
+    print("\n")
+
+
+
+ +
+ + +
+
+

def insert(

self, x, value=None)

+
+ + + + +

Inserts x into this RBT.

+

This operation is similar to the insert operation of a classical BST, +but, in this case, the red-black tree property must be maintained, +so addional work is needed.

+

There are several cases of inserting into a RBT to handle:

+
    +
  1. +

    x is the root node (first node).

    +
  2. +
  3. +

    x.parent is BLACK.

    +
  4. +
  5. +

    x.parent and the uncle of x are RED.

    +

    The uncle of x will be the left child of x.parent.parent, +if x.parent is the right child of x.parent.parent, +otherwise (x.parent is the left child of x.parent.parent) +the uncle will be the right child of x.parent.parent.

    +
  6. +
  7. +

    x.parent is RED, but x.uncle is BLACK (or None). x.grandparent exists because x.parent is RED.

    +

    4.1. x is added to the right of a left child of x.parent.parent (grandparent)

    +

    4.2. or x is added to the left of a right child of x.parent.parent.

    +

    4.3. x is added to the left of a left child of x.parent.parent.

    +

    4.4. or x is added to the right of a right child of x.parent.parent.

    +
  8. +
+

_fix_insertion handles these cases in the same order as just presented above.

+

Time Complexity: O(log2(n)).

+
+ +
+
def insert(self, x, value=None) -> None:
+    """Inserts `x` into this `RBT`.
+    This operation is similar to the `insert` operation of a classical `BST`,
+    but, in this case, the red-black tree property must be maintained,
+    so addional work is needed.
+    There are several cases of inserting into a RBT to handle:
+    1. `x`  is the root node (first node).
+    2. `x.parent` is `BLACK`.
+    3. `x.parent` and the uncle of `x` are `RED`.
+        The uncle of `x` will be the left child of `x.parent.parent`,
+    if `x.parent` is the right child of `x.parent.parent`,
+    otherwise (`x.parent` is the left child of `x.parent.parent`)
+    the uncle will be the right child of `x.parent.parent`.
+    4. x.parent is RED, but x.uncle is BLACK (or None). x.grandparent exists because x.parent is RED.
+        4.1. `x` is added to the right of a left child of `x.parent.parent` (grandparent)
+        4.2. or `x` is added to the left of a right child of `x.parent.parent`.
+        4.3. `x` is added to the left of a left child of `x.parent.parent`.
+        4.4. or `x` is added to the right of a right child of `x.parent.parent`.
+    `_fix_insertion` handles these cases in the same order as just presented above.
+    **Time Complexity:** O(log2(n))."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, RBTNode):
+        x = RBTNode(x, value)
+    if x.left or x.right or x.parent:
+        raise ValueError("x cannot have left or right children, or parent.")
+    c = self.root  # Current node
+    p = None  # Current node's parent
+    while c is not None:
+        p = c
+        if x.key < c.key:
+            c = c.left
+        else:  # x.key >= c.key
+            c = c.right
+    x.parent = p
+    # The while loop was not executed even once.
+    # Case 1: node is inserted as root.
+    if p is None:
+        self.root = x
+    elif p.key > x.key:
+        p.left = x
+    else:  # p.key < x.key:
+        p.right = x
+    x.color = RED
+    self.n += 1
+    self._fix_insertion(x)
+
+
+
+ +
+ + +
+
+

def insert_many(

self, ls)

+
+ + + + +

Calls self.insert for all elements of ls. +Therefore the elements of ls should either be +BSTNode objects or they should represent keys.

+

Time Complexity: O(len(ls)*h).

+
+ +
+
def insert_many(self, ls):
+    """Calls `self.insert` for all elements of `ls`.
+    Therefore the elements of `ls` should either be
+    `BSTNode` objects or they should represent keys.
+    **Time Complexity**: O(len(ls)*h)."""
+    for i in ls:
+        self.insert(i)
+
+
+
+ +
+ + +
+
+

def is_empty(

self)

+
+ + + + +

Returns True if this tree has 0 nodes.

+

Time Complexity: O(1).

+
+ +
+
def is_empty(self):
+    """Returns `True` if this tree has 0 nodes.
+    **Time Complexity**: O(1)."""
+    return self.size() == 0
+
+
+
+ +
+ + +
+
+

def is_root(

self, u)

+
+ + + + +

Checks if u is the root.

+

Time Complexity: O(1).

+
+ +
+
def is_root(self, u: BSTNode):
+    """Checks if `u` is the root.
+    **Time Complexity**: O(1)."""
+    if u == self.root:
+        assert u.parent is None
+    return u == self.root
+
+
+
+ +
+ + +
+
+

def left_rotate(

self, x)

+
+ + + + +

Left rotates the subtree rooted at node x.

+

x can be a BSTNode object, and in that case, +this function performs in constant time O(1); +else, if node is not a BSTNode object, +it tries to search for a BSTNode object with key=x, +and, in that case, it performs in O(h) time.

+

Returns the node which is at the previous position of x, +that is it returns the parent of x.

+

Time Complexity: O(1).

+
+ +
+
def left_rotate(self, x):
+    """Left rotates the subtree rooted at node `x`.
+    `x` can be a `BSTNode` object, and in that case,
+    this function performs in constant time O(1);
+    else, if node is not a `BSTNode` object,
+    it tries to search for a `BSTNode` object with key=x,
+    and, in that case, it performs in O(h) time.
+    Returns the node which is at the previous position of `x`,
+    that is it returns the parent of `x`.
+    **Time Complexity**: O(1)."""
+    c = None  # It will rotate the subtree rooted at c.
+    if not isinstance(x, BSTNode):
+        c = self.search(x)
+        if c is None:
+            raise LookupError("key node was not found in the tree.")
+    else:
+        c = x
+    # To left rotate a node, its right child must exist.
+    if c.right is None:
+        raise ValueError("Left rotation cannot be performed on " + str(c) +
+                         " because it does not have a right child.")
+    c.right.parent = c.parent
+    # Only the root has a None parent.
+    if c.parent is None:
+        self.root = c.right
+    # Checking if c is a left or a right child,
+    # in order to set the new left
+    # or right child respectively of its parent.
+    elif c.is_left_child():
+        c.parent.left = c.right
+    else:
+        c.parent.right = c.right
+    c.parent = c.right
+    # The new right child of c becomes what is
+    # the left child of its previous right child.
+    c.right = c.parent.left
+    # Set c to be the parent of its new right child.
+    if c.right is not None:
+        c.right.parent = c
+    # Set c to be the new left child of its new parent.
+    c.parent.left = c
+    return c.parent
+
+
+
+ +
+ + +
+
+

def maximum(

self)

+
+ + + + +

Calls BST._maximum_r(self.root) if self.root is evaluated to True.

+

Time Complexity: O(h).

+
+ +
+
def maximum(self):
+    """Calls `BST._maximum_r(self.root)` if `self.root` is evaluated to `True`.
+    **Time Complexity**: O(h)."""
+    if self.root:
+        return BST._maximum_r(self.root)
+
+
+
+ +
+ + +
+
+

def minimum(

self)

+
+ + + + +

Calls BST._minimum_r(self.root) if self.root is evaluated to True.

+

Time Complexity: O(h).

+
+ +
+
def minimum(self):
+    """Calls `BST._minimum_r(self.root)` if `self.root` is evaluated to `True`.
+    **Time Complexity**: O(h)."""
+    if self.root:
+        return BST._minimum_r(self.root)
+
+
+
+ +
+ + +
+
+

def post_order_traversal(

self)

+
+ + + + +

Prints the keys of this tree in post-order. +It does the opposite of pre_order_traversal.

+

Time Complexity: O(h).

+
+ +
+
def post_order_traversal(self):
+    """Prints the keys of this tree in post-order.
+    It does the opposite of `pre_order_traversal`.
+    **Time Complexity**: O(h)."""
+    self._post_order_traversal(self.root)
+    print("\n")
+
+
+
+ +
+ + +
+
+

def pre_order_traversal(

self)

+
+ + + + +

Prints the keys of this tree in pre-order. +The pre-order consists of recursively printing first a node u, +then its left child node and then its right child node.

+

Time Complexity: O(h).

+
+ +
+
def pre_order_traversal(self):
+    """Prints the keys of this tree in pre-order.
+    The pre-order consists of recursively printing first a node `u`,
+    then its left child node and then its right child node.
+    **Time Complexity**: O(h)."""
+    self._pre_order_traversal(self.root)
+    print("\n")
+
+
+
+ +
+ + +
+
+

def predecessor(

self, x)

+
+ + + + +

Finds the predecessor of the node x, +i.e. the greatest element smaller than x.

+

x can either be a reference to an actual BSTNode object, +or it can be a key of a supposed node in self.

+

Time Complexity: O(h).

+
+ +
+
def predecessor(self, x):
+    """Finds the predecessor of the node `x`,
+    i.e. the greatest element smaller than `x`.
+    `x` can either be a reference to an actual `BSTNode` object,
+    or it can be a key of a supposed node in self.
+    **Time Complexity**: O(h)."""
+    if not isinstance(x, BSTNode):
+        x = self.search_r(x)
+        if x is None:
+            raise LookupError("No node was found with key=x.")
+    if x.left:
+        return BST._maximum_r(x.left)
+    p = x.parent
+    while p and x == p.left:
+        x = p
+        p = x.parent
+    return p
+
+
+
+ +
+ + +
+
+

def rank(

self, key)

+
+ + + + +

Returns the number of keys strictly less than key.

+

Time Complexity: O(h).

+
+ +
+
def rank(self, key) -> int:
+    """Returns the number of keys strictly less than `key`.
+    **Time Complexity**: O(h)."""
+    if key is None:
+        raise ValueError("key cannot be None.")
+    if not self.search(key):
+        raise LookupError("key was not found.")
+    if self.root is None:
+        return 0
+    else:
+        r = 0
+        return self._rank(self.root, key, r)
+
+
+
+ +
+ + +
+
+

def remove_max(

self)

+
+ + + + +

Removes and returns the element with the greatest value from self.

+

Time Complexity: O(log2(n)).

+
+ +
+
def remove_max(self) -> RBTNode:
+    """Removes and returns the element with the greatest value from `self`.
+    **Time Complexity:** O(log2(n))."""
+    if self.root:
+        m = self.maximum()
+        assert m
+        return self.delete(m)
+
+
+
+ +
+ + +
+
+

def remove_min(

self)

+
+ + + + +

Removes and returns the element with the smallest value from self.

+

Time Complexity: O(log2(n)).

+
+ +
+
def remove_min(self) -> RBTNode:
+    """Removes and returns the element with the smallest value from `self`.
+    **Time Complexity:** O(log2(n))."""
+    if self.root:
+        m = self.minimum()
+        assert m
+        return self.delete(m)
+
+
+
+ +
+ + +
+
+

def reverse_in_order_traversal(

self)

+
+ + + + +

Prints the keys of this tree in decreasing order.

+

It does the opposite of self.in_order_traversal.

+

Time Complexity: O(h).

+
+ +
+
def reverse_in_order_traversal(self):
+    """Prints the keys of this tree in decreasing order.
+    It does the opposite of `self.in_order_traversal`.
+    **Time Complexity**: O(h)."""
+    self._reverse_in_order_traversal(self.root)
+    print("\n")
+
+
+
+ +
+ + +
+
+

def right_rotate(

self, x)

+
+ + + + +

Right rotates the subtree rooted at node x. +See doc-strings of self.left_rotate.

+

Time Complexity: O(1).

+
+ +
+
def right_rotate(self, x):
+    """Right rotates the subtree rooted at node `x`.
+    See doc-strings of `self.left_rotate`.
+    **Time Complexity**: O(1)."""
+    c = None
+    if not isinstance(x, BSTNode):
+        c = self.search(x)
+        if c is None:
+            raise LookupError("key node was not found in the tree.")
+    else:
+        c = x
+    if c.left is None:
+        raise ValueError("Right rotation cannot be performed on " + str(c) +
+                         " because it does not have a left child.")
+    c.left.parent = c.parent
+    if c.parent is None:
+        self.root = c.left
+    elif c.is_left_child():
+        c.parent.left = c.left
+    else:
+        c.parent.right = c.left
+    c.parent = c.left
+    c.left = c.parent.right
+    if c.left is not None:
+        c.left.parent = c
+    c.parent.right = c
+    return c.parent
+
+
+
+ +
+ + +
+
+

def search(

self, key, s=None)

+
+ + + + +

Searches for the key in the tree. +If s is specified, then this procedure starts searching from s.

+

key must be a comparable object of the same type as the other keys.

+

Time Complexity: O(h).

+
+ +
+
def search(self, key, s: BSTNode = None) -> BSTNode:
+    """Searches for the key in the tree.
+    If `s` is specified, then this procedure starts searching from `s`.
+    `key` must be a comparable object of the same type as the other keys.
+    **Time Complexity**: O(h)."""
+    if key is None:
+        raise ValueError("key cannot be None.")
+    if s is None:
+        return self.search_i(key)
+    else:
+        return BST._search_i(key, s)
+
+
+
+ +
+ + +
+
+

def search_i(

self, key)

+
+ + + + +

Searches iteratively for key starting from the root.

+

Time Complexity: O(h).

+
+ +
+
def search_i(self, key) -> BSTNode:
+    """Searches iteratively for key starting from the root.
+    **Time Complexity**: O(h)."""
+    return BST._search_i(key, self.root)
+
+
+
+ +
+ + +
+
+

def search_r(

self, key)

+
+ + + + +

Searches recursively for key starting from self.root.

+

Time Complexity: O(h).

+
+ +
+
def search_r(self, key) -> BSTNode:
+    """Searches recursively for `key` starting from `self.root`.
+    **Time Complexity**: O(h)."""
+    return self._search_r(key, self.root)
+
+
+
+ +
+ + +
+
+

def show(

self)

+
+ + + + +

Pretty-prints this tree using print.

+
+ +
+
def show(self):
+    """Pretty-prints this tree using `print`."""
+    print(self)
+
+
+
+ +
+ + +
+
+

def size(

self)

+
+ + + + +

Returns the total number of nodes.

+

Time Complexity: O(1).

+
+ +
+
def size(self):
+    """Returns the total number of nodes.
+    **Time Complexity**: O(1)."""
+    return self.n
+
+
+
+ +
+ + +
+
+

def successor(

self, x)

+
+ + + + +

Finds the successor of x, +i.e. the smallest element greater than x.

+

If x has a right subtree, +then the successor of x is the minimum of that right subtree.

+

Otherwise it is the first ancestor of x, lets call it A, +such that x falls in the left subtree of A.

+

x can either be a reference to an actual BSTNode object, +or it can be a key of a supposed node in self.

+

Time Complexity: O(h).

+
+ +
+
def successor(self, x):
+    """Finds the successor of `x`,
+    i.e. the smallest element greater than `x`.
+    If `x` has a right subtree,
+    then the successor of `x` is the minimum of that right subtree.
+    Otherwise it is the first ancestor of `x`, lets call it `A`,
+    such that `x` falls in the left subtree of `A`.
+    `x` can either be a reference to an actual `BSTNode` object,
+    or it can be a key of a supposed node in self.
+    **Time Complexity**: O(h)."""
+    if not isinstance(x, BSTNode):
+        x = self.search(x)
+        if x is None:
+            raise LookupError("No node was found with key=x.")
+    if x.right:
+        return BST._minimum_r(x.right)
+    p = x.parent
+    while p and p.right == x:
+        x = p
+        p = x.parent
+    return p
+
+
+
+ +
+ +
+
+ +
+

class RBTNode

+ + +

Class to represent a RBT's node.

+
+ +
+
class RBTNode(BSTNode):
+    """Class to represent a `RBT`'s node."""
+
+    def __init__(self, key, value=None, color=BLACK,
+                 parent=None, left=None, right=None):
+        BSTNode.__init__(self, key, value, parent, left, right)
+        self._color = color
+        self.label = "[" + str(self.key) + ", " + str(self._color) + "]"
+
+    @property
+    def color(self):
+        return self._color
+
+    @color.setter
+    def color(self, value):
+        self._color = value
+        self.label = "[" + str(self.key) + ", " + str(self._color) + "]"
+
+    def reset(self):
+        super().reset()
+        self.color = BLACK
+
+    def __fields(self):
+        """Used by __repr__."""
+        return [["Node (Key)", self.key],
+                ["Value", self.value],
+                ["Color", self.color],
+                ["Parent", self.parent],
+                ["Left child", self.left],
+                ["Right child", self.right],
+                ["Sibling", self.sibling],
+                ["Grandparent", self.grandparent],
+                ["Uncle", self.uncle]]
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • RBTNode
  • +
  • ands.ds.BST.BSTNode
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, key, value=None, color='BLACK', parent=None, left=None, right=None)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, key, value=None, color=BLACK,
+             parent=None, left=None, right=None):
+    BSTNode.__init__(self, key, value, parent, left, right)
+    self._color = color
+    self.label = "[" + str(self.key) + ", " + str(self._color) + "]"
+
+
+
+ +
+ + +
+
+

def count(

self)

+
+ + + + +

Counts the numbers of nodes under self (including self).

+
+ +
+
def count(self) -> int:
+    """Counts the numbers of nodes under `self` (including `self`)."""
+    def _count(u, c: int):
+        if u is None:
+            return c
+        else:
+            c += 1
+        c = _count(u.left, c)
+        c = _count(u.right, c)
+        return c
+    if not self.has_children():
+        return 1
+    else:
+        c = 0
+        return _count(self, c)
+
+
+
+ +
+ + +
+
+

def has_children(

self)

+
+ + + + +

Returns True if self has at least one child. False otherwise.

+
+ +
+
def has_children(self) -> bool:
+    """Returns `True` if `self` has at least one child. `False` otherwise."""
+    return self.left or self.right
+
+
+
+ +
+ + +
+
+

def has_one_child(

self)

+
+ + + + +

Returns True only if self has exactly one child. False otherwise.

+
+ +
+
def has_one_child(self) -> bool:
+    """Returns `True` only if `self` has exactly one child. `False` otherwise."""
+    return (self.left and not self.right) or (not self.left and self.right)
+
+
+
+ +
+ + +
+
+

def has_two_children(

self)

+
+ + + + +

Returns True if self has exactly two children. False otherwise.

+
+ +
+
def has_two_children(self) -> bool:
+    """Returns `True` if self has exactly two children. `False` otherwise."""
+    return self.left and self.right
+
+
+
+ +
+ + +
+
+

def is_left_child(

self)

+
+ + + + +
+ +
+
def is_left_child(self) -> bool:
+    if self.parent is not None:
+        if self.parent.left is not None:
+            return self.parent.left == self
+    else:
+        raise AttributeError("self does not have a parent.")
+
+
+
+ +
+ + +
+
+

def is_right_child(

self)

+
+ + + + +
+ +
+
def is_right_child(self) -> bool:
+    if self.parent is not None:
+        if self.parent.right is not None:
+            return self.parent.right == self
+    else:
+        raise AttributeError("self does not have a parent.")
+
+
+
+ +
+ + +
+
+

def reset(

self)

+
+ + + + +
+ +
+
def reset(self):
+    super().reset()
+    self.color = BLACK
+
+
+
+ +
+ + +
+
+

def show(

self)

+
+ + + + +
+ +
+
def show(self):
+    print(self.__repr__())
+
+
+
+ +
+ +

Instance variables

+
+

var color

+ + + + +
+
+ +
+
+

var grandparent

+ + + + +

Returns the parent of the parent of this node.

+
+
+ +
+
+

var label

+ + + + +
+
+ +
+
+

var sibling

+ + + + +

Returns the sibling node of this node, +which can of course be None.

+
+
+ +
+
+

var uncle

+ + + + +

Returns the uncle node of this node. +The uncle is the sibling of the parent of this node, +if it exists. None is returned if it doesn't exist, +or the parent or grandparent of this node is None.

+
+
+ +
+
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/Stack.m.html b/docs/ands/ds/Stack.m.html new file mode 100644 index 00000000..95e0c18d --- /dev/null +++ b/docs/ands/ds/Stack.m.html @@ -0,0 +1,1439 @@ + + + + + + ands.ds.Stack API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.Stack module

+

Meta info

+

Author: Nelson Brochado

+

Created: 05/07/2015

+

Updated: 04/02/2017

+

Description

+

A stack is one of the most simple and, at the same time, useful abstract data types in computer science.

+

An abstract data type (or, in short, ADT) is a logical description or specification +of a certain way of viewing and/or organizing data, and which values and operations are allowed on this data. +An ADT is, as the same suggests, an abstract concept or mathematical model; +thus an ADT can be implemented as a data structure in many ways. +Essentially, ADTs is all about ideas or concepts of representing and manipulating data, +whereas a data structure is an implementation of a specific ADTs; +hence there can be more than one data structure for the same ADT.

+

What defines a stack is the order of insertion and removal of elements from it: +a stack is a "last in, first out" (or, as an acronym, LIFO) abstract data type, +that is, the last element inserted into the stack is the first to be removed. +Since this is an ADT, we don't care how the elements are stored in memory, +or how we manipulate them so that the last element inserted is the first to be removed. +The insertion of an element is usually called "push", whereas the removal is usually called "pop". +There's also another operation (i.e. "peek" or "top") +which consists in looking at the last element inserted into the stack. +Of course other operations, such as "size of the stack" (i.e. how many elements in the stack) +or "is empty" (i.e. checking if the stack contains elements or not) may also useful.

+

References

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+# Meta info
+
+Author: Nelson Brochado
+
+Created: 05/07/2015
+
+Updated: 04/02/2017
+
+# Description
+
+A stack is one of the most simple and, at the same time, useful abstract data types in computer science.
+
+An abstract data type (or, in short, ADT) is a logical description or specification
+of a certain way of viewing and/or organizing data, and which values and operations are allowed on this data.
+An ADT is, as the same suggests, an abstract concept or mathematical model;
+thus an ADT can be implemented as a data structure in many ways.
+Essentially, ADTs is all about ideas or concepts of representing and manipulating data,
+whereas a data structure is an implementation of a specific ADTs;
+hence there can be more than one data structure for the same ADT.
+
+What defines a stack is the order of insertion and removal of elements from it:
+a stack is a "last in, first out" (or, as an acronym, LIFO) abstract data type,
+that is, the last element inserted into the stack is the first to be removed.
+Since this is an ADT, we don't care how the elements are stored in memory,
+or how we manipulate them so that the last element inserted is the first to be removed.
+The insertion of an element is usually called "push", whereas the removal is usually called "pop".
+There's also another operation (i.e. "peek" or "top")
+which consists in looking at the last element inserted into the stack.
+Of course other operations, such as "size of the stack" (i.e. how many elements in the stack)
+or "is empty" (i.e. checking if the stack contains elements or not) may also useful.
+
+# References
+
+- [http://interactivepython.org/runestone/static/pythonds/Introduction/WhyStudyDataStructuresandAbstractDataTypes.html](http://interactivepython.org/runestone/static/pythonds/Introduction/WhyStudyDataStructuresandAbstractDataTypes.html)
+- [http://stackoverflow.com/questions/195625/what-is-the-time-complexity-of-popping-elements-from-list-in-python](http://stackoverflow.com/questions/195625/what-is-the-time-complexity-of-popping-elements-from-list-in-python),
+for the time complexity analysis of the pop operation of the last element on lists.
+- [http://stackoverflow.com/questions/1115313/cost-of-len-function](http://stackoverflow.com/questions/1115313/cost-of-len-function),
+for the time complexity analysis of the size operation.
+- [http://stackoverflow.com/questions/12342457/what-is-the-big-o-notation-for-the-len-function-in-python](http://stackoverflow.com/questions/12342457/what-is-the-big-o-notation-for-the-len-function-in-python),
+for other time complexity analysis of the list class.
+
+"""
+
+from collections import Iterable
+
+from tabulate import tabulate
+
+__all__ = ["Stack"]
+
+
+class Stack:
+    """This is a wrapper class around the Python's list to represent a stack data structure.
+
+    It doesn't allow you to insert None elements through the public methods.
+    It returns None whenever you try to pop from or peek at the stack, but it's empty.
+
+    The data structure can be initialized with an iterable object with not None values.
+    A copy of the given iterable is made, so the original iterable is not affected when performing operations."""
+
+    def __init__(self, s=None):
+        if s is not None:
+            if not isinstance(s, Iterable):
+                raise TypeError("s must be an instance of Iterable")
+            if any(elem is None for elem in s):
+                raise ValueError("all elements of s must be not None")
+        else:
+            s = []
+        self._stack = list(s)
+
+    def push(self, elem: object) -> None:
+        """Pushes `elem` on top of this stack.
+
+        If `elem` is None`, `ValueError` is raised.
+
+        **Time complexity:** O(1)."""
+        if elem is None:
+            raise ValueError("elem cannot be None")
+        self._stack.append(elem)
+
+    def pop(self) -> object:
+        """Returns the top of this stack, or `None` if the stack is empty.
+
+        **Time complexity:** O(1)."""
+        return None if self.is_empty() else self._stack.pop()
+
+    def size(self) -> int:
+        """Returns the size of this stack.
+
+        **Time complexity:** O(1)."""
+        return len(self._stack)
+
+    def is_empty(self) -> bool:
+        """Returns `True` if this stack is empty, `False` otherwise.
+
+        **Time complexity:** O(1)."""
+        return self.size() == 0
+
+    def top(self) -> object:
+        """Returns but does **not** pop the top of the stack.
+
+        If the stack is empty, `None` is returned.
+
+        This operation is also called "peek".
+
+        **Time complexity:** O(1)."""
+        return None if self.is_empty() else self._stack[-1]
+
+    def __str__(self):
+        return tabulate([[e] for e in reversed(self._stack)], tablefmt="grid")
+
+    def __repr__(self):
+        return self.__str__()
+
+
+ +
+ +
+ + +

Classes

+ +
+

class Stack

+ + +

This is a wrapper class around the Python's list to represent a stack data structure.

+

It doesn't allow you to insert None elements through the public methods. +It returns None whenever you try to pop from or peek at the stack, but it's empty.

+

The data structure can be initialized with an iterable object with not None values. +A copy of the given iterable is made, so the original iterable is not affected when performing operations.

+
+ +
+
class Stack:
+    """This is a wrapper class around the Python's list to represent a stack data structure.
+
+    It doesn't allow you to insert None elements through the public methods.
+    It returns None whenever you try to pop from or peek at the stack, but it's empty.
+
+    The data structure can be initialized with an iterable object with not None values.
+    A copy of the given iterable is made, so the original iterable is not affected when performing operations."""
+
+    def __init__(self, s=None):
+        if s is not None:
+            if not isinstance(s, Iterable):
+                raise TypeError("s must be an instance of Iterable")
+            if any(elem is None for elem in s):
+                raise ValueError("all elements of s must be not None")
+        else:
+            s = []
+        self._stack = list(s)
+
+    def push(self, elem: object) -> None:
+        """Pushes `elem` on top of this stack.
+
+        If `elem` is None`, `ValueError` is raised.
+
+        **Time complexity:** O(1)."""
+        if elem is None:
+            raise ValueError("elem cannot be None")
+        self._stack.append(elem)
+
+    def pop(self) -> object:
+        """Returns the top of this stack, or `None` if the stack is empty.
+
+        **Time complexity:** O(1)."""
+        return None if self.is_empty() else self._stack.pop()
+
+    def size(self) -> int:
+        """Returns the size of this stack.
+
+        **Time complexity:** O(1)."""
+        return len(self._stack)
+
+    def is_empty(self) -> bool:
+        """Returns `True` if this stack is empty, `False` otherwise.
+
+        **Time complexity:** O(1)."""
+        return self.size() == 0
+
+    def top(self) -> object:
+        """Returns but does **not** pop the top of the stack.
+
+        If the stack is empty, `None` is returned.
+
+        This operation is also called "peek".
+
+        **Time complexity:** O(1)."""
+        return None if self.is_empty() else self._stack[-1]
+
+    def __str__(self):
+        return tabulate([[e] for e in reversed(self._stack)], tablefmt="grid")
+
+    def __repr__(self):
+        return self.__str__()
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • Stack
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, s=None)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, s=None):
+    if s is not None:
+        if not isinstance(s, Iterable):
+            raise TypeError("s must be an instance of Iterable")
+        if any(elem is None for elem in s):
+            raise ValueError("all elements of s must be not None")
+    else:
+        s = []
+    self._stack = list(s)
+
+
+
+ +
+ + +
+
+

def is_empty(

self)

+
+ + + + +

Returns True if this stack is empty, False otherwise.

+

Time complexity: O(1).

+
+ +
+
def is_empty(self) -> bool:
+    """Returns `True` if this stack is empty, `False` otherwise.
+    **Time complexity:** O(1)."""
+    return self.size() == 0
+
+
+
+ +
+ + +
+
+

def pop(

self)

+
+ + + + +

Returns the top of this stack, or None if the stack is empty.

+

Time complexity: O(1).

+
+ +
+
def pop(self) -> object:
+    """Returns the top of this stack, or `None` if the stack is empty.
+    **Time complexity:** O(1)."""
+    return None if self.is_empty() else self._stack.pop()
+
+
+
+ +
+ + +
+
+

def push(

self, elem)

+
+ + + + +

Pushes elem on top of this stack.

+

If elem is None,ValueError` is raised.

+

Time complexity: O(1).

+
+ +
+
def push(self, elem: object) -> None:
+    """Pushes `elem` on top of this stack.
+    If `elem` is None`, `ValueError` is raised.
+    **Time complexity:** O(1)."""
+    if elem is None:
+        raise ValueError("elem cannot be None")
+    self._stack.append(elem)
+
+
+
+ +
+ + +
+
+

def size(

self)

+
+ + + + +

Returns the size of this stack.

+

Time complexity: O(1).

+
+ +
+
def size(self) -> int:
+    """Returns the size of this stack.
+    **Time complexity:** O(1)."""
+    return len(self._stack)
+
+
+
+ +
+ + +
+
+

def top(

self)

+
+ + + + +

Returns but does not pop the top of the stack.

+

If the stack is empty, None is returned.

+

This operation is also called "peek".

+

Time complexity: O(1).

+
+ +
+
def top(self) -> object:
+    """Returns but does **not** pop the top of the stack.
+    If the stack is empty, `None` is returned.
+    This operation is also called "peek".
+    **Time complexity:** O(1)."""
+    return None if self.is_empty() else self._stack[-1]
+
+
+
+ +
+ +
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/TST.m.html b/docs/ands/ds/TST.m.html new file mode 100644 index 00000000..54005909 --- /dev/null +++ b/docs/ands/ds/TST.m.html @@ -0,0 +1,3002 @@ + + + + + + ands.ds.TST API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.TST module

+

Meta info

+

Author: Nelson Brochado

+

Created: 05/09/2015

+

Updated: 03/02/2017

+

Description

+

Ternary-search tries (or trees) combine the time efficiency of other tries +with the space efficiency of binary-search trees.

+

An advantage compared to hash maps is that ternary search tries support sorting, +but the keys of a ternary-search trie can only be strings, +whereas a hash map supports any kind of hashable keys.

+

TSTs vs Hashing

+

Hashing

+
    +
  • Need to examine entire key
  • +
  • Search miss and hits cost about the same
  • +
  • Performance relies on hash function
  • +
  • Does NOT support ordered symbol table operations
  • +
+

TSTs

+
    +
  • Works only for strings (or digital keys)
  • +
  • Only examines just enough key characters
  • +
  • Search miss may involve only a few characters
  • +
  • Supports ordered symbol table operations:
      +
    • keys-that-match
    • +
    • keys-with-prefix
    • +
    • longest-prefix-of
    • +
    +
  • +
+

Bottom line

+

TSTs are:

+
    +
  • faster than hashing (especially for search misses)
  • +
  • more flexible than red-black trees
  • +
+

References

+ +

Resources

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+# Meta info
+
+Author: Nelson Brochado
+
+Created: 05/09/2015
+
+Updated: 03/02/2017
+
+# Description
+
+Ternary-search tries (or trees) combine the time efficiency of other tries
+with the space efficiency of binary-search trees.
+
+An advantage compared to hash maps is that ternary search tries support sorting,
+but the _keys_ of a ternary-search trie can only be _strings_,
+whereas a hash map supports any kind of hashable keys.
+
+## TSTs vs Hashing
+
+### Hashing
+
+- Need to examine entire key
+- Search miss and hits cost about the same
+- Performance relies on hash function
+- Does NOT support ordered symbol table operations
+
+### TSTs
+
+- Works only for strings (or digital keys)
+- Only examines just enough key characters
+- Search miss may involve only a few characters
+- Supports ordered symbol table operations:
+    - keys-that-match
+    - keys-with-prefix
+    - longest-prefix-of
+
+### Bottom line
+
+TSTs are:
+
+- faster than hashing (especially for search misses)
+- more flexible than red-black trees
+
+# References
+
+- [Ternary Search Trees](https://www.cs.upc.edu/~ps/downloads/tst/tst.html) by By Jon Bentley and Bob Sedgewick
+- [Fast Algorithms for Sorting and Searching Strings](https://www.cs.princeton.edu/~rs/strings/), by Jon Bentley and Robert Sedgewick
+- [TST.java](http://algs4.cs.princeton.edu/52trie/TST.java.html), Java implementation by Robert Sedgewick and Kevin Wayne
+- [Ternary Search Tries](https://www.youtube.com/watch?v=CIGyewO7868), video lecture by Robert Sedgewick
+- [Ternary search tree](https://en.wikipedia.org/wiki/Ternary_search_tree) at Wikipedia
+- [How to list in an alphabetical order the words of a ternary search tree?](http://stackoverflow.com/a/27178771/3924118)
+
+# Resources
+
+- [Ternary search tree introduction](https://www.youtube.com/watch?v=xv4oRyqSKiw),
+by [Balazs Holczer](https://www.udemy.com/user/holczerbalazs/)
+- [TernarySearchTree.hh](http://www.keithschwarz.com/interesting/code/?dir=ternary-search-tree),
+C++ implementation of a TST by Keith Schwarz, which provides a good analysis of the complexity of the operations of a TST.
+- [Remove method for Ternary Search Tree](http://p2p.wrox.com/book-beginning-algorithms/60350-remove-method-ternary-search-tree.html),
+at [http://p2p.wrox.com/book-beginning-algorithms](http://p2p.wrox.com/book-beginning-algorithms)
+- [Plant your data in a ternary search tree](http://www.javaworld.com/article/2075027/java-app-dev/plant-your-data-in-a-ternary-search-tree.html?page=1)
+
+"""
+
+
+class TSTNode:
+    """A TSTNode has 6 fields:
+
+        - key, which is a character;
+        - value, which is None if self is not a terminal node (of an inserted string in the TST);
+        - parent, which is a pointer to a TSTNode representing the parent of self;
+        - left, which is a pointer to a TSTNode whose key is smaller lexicographically than key;
+        - right, which is similarly a pointer to a TSTNode whose key is greater lexicographically than key;
+        - mid, which is a pointer to a TSTNode whose key is the following character of key in an inserted string."""
+
+    def __init__(self, key, value=None, parent=None, left=None, mid=None, right=None):
+        if not isinstance(key, str):
+            raise TypeError("key must be an instance of str.")
+        if not key:
+            raise ValueError("key must be a string of length >= 1.")
+
+        self.key = key
+        self.value = value
+        self.parent = parent
+        self.left = left
+        self.mid = mid
+        self.right = right
+
+    def is_left_child(self) -> bool:
+        if not self.parent:
+            raise AttributeError("self does not have a parent.")
+        if self.parent.left:
+            return self.parent.left == self
+        else:
+            return False
+
+    def is_right_child(self) -> bool:
+        if not self.parent:
+            raise AttributeError("self does not have a parent.")
+        if self.parent.right:
+            return self.parent.right == self
+        else:
+            return False
+
+    def is_mid_child(self) -> bool:
+        if not self.parent:
+            raise AttributeError("self does not have a parent.")
+        if self.parent.mid:
+            return self.parent.mid == self
+        else:
+            return False
+
+    def has_children(self) -> bool:
+        return self.left or self.right or self.mid
+
+    def __str__(self):
+        return "{0}: {1}".format(self.key, self.value)
+
+    def __repr__(self):
+        return self.__str__()
+
+
+class TST:
+    """Methods or fields that start with an underscore _ are considered private,
+    so they should not be access and never modified from a client of this class.
+
+    This TST does not allow (through public methods) empty strings to be inserted.
+
+    In general the way the ternary search tree looks like
+    depends highly on the order of insertion of the keys,
+    that is, inserting the same keys but in different orders
+    produces internally a different structure or shape of the TST."""
+
+    def __init__(self):
+        self._n = 0
+        self._root = None
+
+    def __invariants__(self) -> None:
+        """These propositions should always be true at the BEGINNING
+        and END of every PUBLIC method of this TST.
+
+        Call this method if you want to ensure the invariants are holding."""
+        assert self._n >= 0
+        if self._n == 0:
+            assert self._root is None
+        elif self._n > 0:
+            assert isinstance(self._root, TSTNode)
+            assert self._root.parent is None
+
+    def _is_root(self, u: TSTNode) -> bool:
+        result = (self._root == u)
+        if result:
+            assert u.parent is None
+        else:
+            assert u.parent is not None
+        return result
+
+    def size(self) -> int:
+        return self._n
+
+    def count(self) -> int:
+        """Counts the number of strings in self.
+
+        This method recursively passes through all the nodes
+        and counts the ones which have a non None value.
+
+        You should clearly use size instead: 
+        this method is here only for the fun of writing code!
+
+        **Time complexity:** O(n), where n is the number of nodes in this TST."""
+        c = self._count(self._root, 0)
+        assert c == self.size()
+        return c
+
+    def _count(self, node: TSTNode, counter: int) -> int:
+        """Helper method to `self.count`.
+
+        **Time complexity:** O(m), where m is the number of nodes under `node`."""
+        if node is None:  # base case
+            return counter
+
+        counter = self._count(node.left, counter)
+        if node.value is not None:
+            counter += 1
+
+        counter = self._count(node.mid, counter)
+        counter = self._count(node.right, counter)
+
+        return counter
+
+    def is_empty(self) -> bool:
+        """**Time complexity:** O(1)."""
+        return self._n == 0
+
+    def insert(self, key: str, value: object) -> None:
+        """Inserts the `key` into the symbol table and associates with it `value`,
+        overwriting an eventual associated old value, if the `key` is already in self.
+
+        If `key` is not an instance of `str`, `TypeError` is raised.
+        If `key` is an empty string, `ValueError` is raised.
+        If `value` is None, `ValueError` is raised.
+
+        Nodes whose value is not None represent the last character of an inserted word.
+
+        **Time complexity:** O(m + h), where m = length(key),
+        which also represents how many times we follow the middle link,
+        and h is the number of left and right turns.
+        So a lower bound of the complexity would be &Omega(m);."""
+        self.__invariants__()
+        if not isinstance(key, str):
+            raise TypeError("key must be an instance of type str.")
+        if not key:
+            raise ValueError("key must be a string of length >= 1.")
+        if value is None:
+            raise ValueError("value cannot be None.")
+        self._root = self._insert(self._root, key, value, 0)
+        self.__invariants__()
+
+    def _insert(self, node: TSTNode, key: str, value: object, index: int):
+        """Inserts `key` with `value` into self starting from `node`."""
+        if node is None:
+            node = TSTNode(key[index])
+
+        if key[index] < node.key:
+            node.left = self._insert(node.left, key, value, index)
+            node.left.parent = node
+        elif key[index] > node.key:
+            node.right = self._insert(node.right, key, value, index)
+            node.right.parent = node
+        else:  # key[index] == node.key
+            if index < len(key) - 1:
+                # If we're NOT at the end of the key, this is a match,
+                # so we recursively call self._insert from index + 1,
+                # and we move to the mid node (char) of node.
+                # Note that the last index of the key is len(key) - 1.
+                node.mid = self._insert(node.mid, key, value, index + 1)
+                node.mid.parent = node
+            else:
+                if node.value is None:
+                    self._n += 1
+                node.value = value
+
+        return node
+
+    def search(self, key: str) -> object:
+        """Returns the value associated with `key`, if `key` is in self, else None.
+
+        If `key` is not an instance of `str`, `TypeError` is raised.
+        If `key` is an empty string, `ValueError` is raised.
+
+        The search in a TST works as follows.
+
+        We start at the root and we compare its character with the first character of key.
+            - If they are the same, we follow the middle link of the root node.
+            - If the first character of key is smaller lexicographically
+            than the key at the root, then we take the left link or pointer.
+            We do this because we know that all strings that start with characters
+            that are smaller lexicographically than key[0] are on its left subtree.
+            - If the first character of key is greater lexicographically
+            than the key at the root, we take similarly the right link or pointer.
+
+        We keep applying this idea at every node.
+        Moreover, WHEN THERE'S A MATCH, next time we compare the key
+        of the next node with the next character of key.
+
+        For example, if there's a match between the first node (the root) and key[0],
+        we follow the middle link, and the next comparison is between
+        the key of the specific next node and key[1], not key[0]!
+
+        **Time complexity:** O(m + h).
+        Check self.insert to see what m and h are."""
+        if not isinstance(key, str):
+            raise TypeError("key must be an instance of type str.")
+        if not key:
+            raise ValueError("key must be a string of length >= 1.")
+
+        node = self._search(self._root, key, 0)
+
+        if node is not None:
+            assert self.search_iteratively(key) == node.value
+            return node.value
+        else:
+            assert self.search_iteratively(key) is None
+            return None
+
+    def _search(self, node: TSTNode, key: str, index: int) -> TSTNode:
+        """Searches for the node containing the value associated with `key` starting from `node`.
+        If returns None OR a node with value None if there's no such node."""
+        if node is None:
+            return None
+
+        if key[index] < node.key:
+            return self._search(node.left, key, index)
+        elif key[index] > node.key:
+            return self._search(node.right, key, index)
+        elif index < len(key) - 1:  # This is a match, but we're not at the last character of key.
+            return self._search(node.mid, key, index + 1)
+        else:  # This is a match and we're at the last character of key.
+            return node  # node could be None!!
+
+    def search_iteratively(self, key: str) -> object:
+        """Iterative alternative to self.search."""
+        if not isinstance(key, str):
+            raise TypeError("key must be an instance of type str.")
+        if not key:
+            raise ValueError("key must be a string of length >= 1.")
+
+        node = self._root
+
+        if node is None:
+            return None
+
+        # Up to the penultimate index (i.e. len(key) - 1)
+        # because if we reach the penultimate character and it's a match,
+        # then we follow the mid node (i.e. we end up in what's possibly the last node).
+        index = 0
+
+        while index < len(key) - 1:
+            while node and key[index] != node.key:
+                if key[index] < node.key:
+                    node = node.left
+                else:
+                    node = node.right
+
+            if node is None:  # Unsuccessful search.
+                return None
+            else:
+                # Arriving here only if exited from the while loop
+                # because the condition key[i] != node.key was false,
+                # that is key[index] == node.key, thus we follow the middle link.
+                node = node.mid
+                index += 1
+
+        assert index == len(key) - 1
+
+        # If node is not None, then we may still need to go left or right,
+        # and we stop when either we find a node which has the same key as the last character of key,
+        # or when `node` ends up being set to None, i.e. the key does not exist in this TST.
+        while node and key[index] != node.key:
+            if key[index] < node.key:
+                node = node.left
+            else:
+                node = node.right
+
+        if node is None:  # Unsuccessful search.
+            return None
+        else:  # We exit the previous while loop because key[index] == node.key.
+            return node.value  # could also be None!!
+
+    def contains(self, key: str) -> bool:
+        """Returns True if `key` is in self, False otherwise.
+
+        **Time complexity:** O(m + h).
+        See the complexity analysis of self.insert for more info about m and h."""
+        return self.search(key) is not None
+
+    def delete(self, key: str) -> TSTNode:
+        """Deletes and returns the value associated with `key` in this TST,
+        if `key` is in this TST, otherwise it returns None.
+
+        If `key` is not an instance of `str`, `TypeError` is raised.
+        If `key` is an empty string, `ValueError` is raised.
+
+        **Time complexity:** O(m + h + k).
+        Check self.search to see what m and h are.
+        k is the number of "no more necessary" cleaned up
+        after deletion of the node associated with `key`.
+        Unnecessary nodes are nodes with no children and value equal to None."""
+        self.__invariants__()
+
+        if not isinstance(key, str):
+            raise TypeError("key must be an instance of type str.")
+        if not key:
+            raise ValueError("key must be a string of length >= 1.")
+
+        # Note: calling self._search, since self.search does not return a Node,
+        # but the value associated with the key passed as parameter.
+        node = self._search(self._root, key, 0)
+
+        if node is not None and node.value is not None:
+            # If node.value is None, it means
+            result = node.value  # forgetting the string tracked by node.
+            node.value = None
+            self._n -= 1
+            self._delete_fix(node)
+        else:
+            result = None
+
+        self.__invariants__()
+        return result
+
+    def _delete_fix(self, u: TSTNode) -> None:
+        """Does the clean up of this TST after deletion of node `u`."""
+        assert u.value is None
+
+        # While u has no children and his value is None,
+        # forget about u and start from his parent.
+        # So, this while loop terminates when either u is None,
+        # u has at least one child, or u's value is not None.
+        while u and not u.has_children() and u.value is None:
+            if self._is_root(u):
+                assert self._n == 0
+                self._root = None
+                break
+
+            if u.is_left_child():
+                u.parent.left = None
+            elif u.is_right_child():
+                u.parent.right = None
+            else:
+                u.parent.mid = None
+
+            p = u.parent
+            u.parent = None
+            u = p
+
+        if u.has_children() and u.value is None:
+            assert self._count(u, 0) > 0
+
+    def traverse(self) -> None:
+        """Traverses all nodes in this TST and prints the key: value associations.
+
+        **Time complexity:** O(n), where n is the number of nodes in self."""
+        self._traverse(self._root, "")
+
+    def _traverse(self, node: TSTNode, prefix: str) -> None:
+        """Helper method to self.traverse.
+
+        **Time complexity:** O(m), where m is the number of nodes under `node`."""
+        if node is None:  # base case
+            return
+
+        self._traverse(node.left, prefix)
+        if node.value is not None:
+            print(prefix + node.key, ": ", node.value)
+
+        self._traverse(node.mid, prefix + node.key)
+        self._traverse(node.right, prefix)
+
+    def keys_with_prefix(self, prefix: str) -> list:
+        """Returns all keys in this TST that start with `prefix`.
+
+        If `prefix` is not an instance of `str`, `TypeError` is raised.
+
+        If `prefix` is an empty string, then all keys in this TST
+        that start with an empty string, thus all keys are returned."""
+        if not isinstance(prefix, str):
+            raise TypeError("prefix must be an instance of str!")
+
+        kwp = []
+
+        if not prefix:
+            self._keys_with_prefix(self._root, [], kwp)
+        else:
+            node = self._search(self._root, prefix, 0)
+
+            if node is not None:
+                if node.value is not None:
+                    # A `key` equals to prefix was found in the TST with an associated value.
+                    kwp.append(prefix)
+
+                self._keys_with_prefix(node.mid, list(prefix), kwp)
+
+        return kwp
+
+    def _keys_with_prefix(self, node: TSTNode, prefix_list: list, kwp: list) -> None:
+        """Returns all keys rooted at `node` given the prefix given as a list of characters `prefix_list`."""
+        if node is None:
+            return
+
+        self._keys_with_prefix(node.left, prefix_list, kwp)
+
+        if node.value is not None:
+            kwp.append("".join(prefix_list + [node.key]))
+
+        prefix_list.append(node.key)
+        self._keys_with_prefix(node.mid, prefix_list, kwp)
+
+        prefix_list.pop()
+        self._keys_with_prefix(node.right, prefix_list, kwp)
+
+    def all_pairs(self) -> dict:
+        """Returns all pairs of key:value from this TST as a Python `dict`."""
+        pairs = {}
+        self._all_pairs(self._root, [], pairs)
+        return pairs
+
+    def _all_pairs(self, node: TSTNode, key_list: list, all_dict: list) -> None:
+        if node is None:
+            return
+
+        self._all_pairs(node.left, key_list, all_dict)
+
+        if node.value is not None:
+            key = "".join(key_list + [node.key])
+            assert key not in all_dict
+            all_dict[key] = node.value
+
+        key_list.append(node.key)
+        self._all_pairs(node.mid, key_list, all_dict)
+
+        key_list.pop()
+        self._all_pairs(node.right, key_list, all_dict)
+
+    def longest_prefix_of(self, query: str) -> str:
+        """Returns the key in this TST which is the longest prefix of `query`,
+        if such a key exists, else it returns None.
+
+        If `query` is not a string `TypeError` is raised.
+        If `query` is a string but empty, `ValueError` is raised.
+
+        If this TST is empty, it returns an empty string."""
+        if not isinstance(query, str):
+            raise TypeError("query is not an instance of str!")
+        if not query:
+            raise ValueError("empty strings not allowed in this TST!")
+
+        length = 0  # It keeps track of the length of the longest prefix of query.
+        x = self._root
+        i = 0
+
+        while x is not None and i < len(query):
+            c = query[i]
+
+            if c < x.key:
+                x = x.left
+            elif c > x.key:
+                x = x.right
+            else:
+                i += 1
+                if x.value is not None:
+                    length = i
+                x = x.mid
+
+        return query[:length]
+
+    def keys_that_match(self, pattern: str) -> list:
+        """Returns a list of keys of this TST that match `pattern`.
+
+        A key `k` of length `m` matches `pattern` if:
+
+        1. m = length(pattern), and
+        2. Either k[i] == pattern[i] or k[i] == '.'.
+            - Example: if `pattern == ".ood"`,
+            then `k == "good"` would match, but not `k == "foodie"`.
+
+        If `pattern` is not a `str`, `TypeError` is raised.
+        If `pattern` is an empty string, `ValueError` is raised."""
+        if not isinstance(pattern, str):
+            raise TypeError("pattern is not an instance of str!")
+        if not pattern:
+            raise ValueError("pattern cannot be an empty string")
+
+        keys = []
+        self._keys_that_match(self._root, [], 0, pattern, keys)
+        return keys
+
+    def _keys_that_match(self, node: TSTNode, prefix_list: list, i: int, pattern: str, keys: list) -> None:
+        """Stores in the list `keys` the of keys that match `pattern` starting from `node`."""
+        if node is None:
+            return
+
+        c = pattern[i]
+
+        if c == "." or c < node.key:
+            self._keys_that_match(node.left, prefix_list, i, pattern, keys)
+
+        if c == "." or c == node.key:
+
+            if i == len(pattern) - 1 and node.value is not None:
+                # If i is the last index and its value is not None
+                keys.append("".join(prefix_list + [node.key]))
+
+            if i < len(pattern) - 1:
+                prefix_list.append(node.key)
+                self._keys_that_match(node.mid, prefix_list, i + 1, pattern, keys)
+                prefix_list.pop()
+
+        if c == "." or c > node.key:
+            self._keys_that_match(node.right, prefix_list, i, pattern, keys)
+
+
+ +
+ +
+ + +

Classes

+ +
+

class TST

+ + +

Methods or fields that start with an underscore _ are considered private, +so they should not be access and never modified from a client of this class.

+

This TST does not allow (through public methods) empty strings to be inserted.

+

In general the way the ternary search tree looks like +depends highly on the order of insertion of the keys, +that is, inserting the same keys but in different orders +produces internally a different structure or shape of the TST.

+
+ +
+
class TST:
+    """Methods or fields that start with an underscore _ are considered private,
+    so they should not be access and never modified from a client of this class.
+
+    This TST does not allow (through public methods) empty strings to be inserted.
+
+    In general the way the ternary search tree looks like
+    depends highly on the order of insertion of the keys,
+    that is, inserting the same keys but in different orders
+    produces internally a different structure or shape of the TST."""
+
+    def __init__(self):
+        self._n = 0
+        self._root = None
+
+    def __invariants__(self) -> None:
+        """These propositions should always be true at the BEGINNING
+        and END of every PUBLIC method of this TST.
+
+        Call this method if you want to ensure the invariants are holding."""
+        assert self._n >= 0
+        if self._n == 0:
+            assert self._root is None
+        elif self._n > 0:
+            assert isinstance(self._root, TSTNode)
+            assert self._root.parent is None
+
+    def _is_root(self, u: TSTNode) -> bool:
+        result = (self._root == u)
+        if result:
+            assert u.parent is None
+        else:
+            assert u.parent is not None
+        return result
+
+    def size(self) -> int:
+        return self._n
+
+    def count(self) -> int:
+        """Counts the number of strings in self.
+
+        This method recursively passes through all the nodes
+        and counts the ones which have a non None value.
+
+        You should clearly use size instead: 
+        this method is here only for the fun of writing code!
+
+        **Time complexity:** O(n), where n is the number of nodes in this TST."""
+        c = self._count(self._root, 0)
+        assert c == self.size()
+        return c
+
+    def _count(self, node: TSTNode, counter: int) -> int:
+        """Helper method to `self.count`.
+
+        **Time complexity:** O(m), where m is the number of nodes under `node`."""
+        if node is None:  # base case
+            return counter
+
+        counter = self._count(node.left, counter)
+        if node.value is not None:
+            counter += 1
+
+        counter = self._count(node.mid, counter)
+        counter = self._count(node.right, counter)
+
+        return counter
+
+    def is_empty(self) -> bool:
+        """**Time complexity:** O(1)."""
+        return self._n == 0
+
+    def insert(self, key: str, value: object) -> None:
+        """Inserts the `key` into the symbol table and associates with it `value`,
+        overwriting an eventual associated old value, if the `key` is already in self.
+
+        If `key` is not an instance of `str`, `TypeError` is raised.
+        If `key` is an empty string, `ValueError` is raised.
+        If `value` is None, `ValueError` is raised.
+
+        Nodes whose value is not None represent the last character of an inserted word.
+
+        **Time complexity:** O(m + h), where m = length(key),
+        which also represents how many times we follow the middle link,
+        and h is the number of left and right turns.
+        So a lower bound of the complexity would be &Omega(m);."""
+        self.__invariants__()
+        if not isinstance(key, str):
+            raise TypeError("key must be an instance of type str.")
+        if not key:
+            raise ValueError("key must be a string of length >= 1.")
+        if value is None:
+            raise ValueError("value cannot be None.")
+        self._root = self._insert(self._root, key, value, 0)
+        self.__invariants__()
+
+    def _insert(self, node: TSTNode, key: str, value: object, index: int):
+        """Inserts `key` with `value` into self starting from `node`."""
+        if node is None:
+            node = TSTNode(key[index])
+
+        if key[index] < node.key:
+            node.left = self._insert(node.left, key, value, index)
+            node.left.parent = node
+        elif key[index] > node.key:
+            node.right = self._insert(node.right, key, value, index)
+            node.right.parent = node
+        else:  # key[index] == node.key
+            if index < len(key) - 1:
+                # If we're NOT at the end of the key, this is a match,
+                # so we recursively call self._insert from index + 1,
+                # and we move to the mid node (char) of node.
+                # Note that the last index of the key is len(key) - 1.
+                node.mid = self._insert(node.mid, key, value, index + 1)
+                node.mid.parent = node
+            else:
+                if node.value is None:
+                    self._n += 1
+                node.value = value
+
+        return node
+
+    def search(self, key: str) -> object:
+        """Returns the value associated with `key`, if `key` is in self, else None.
+
+        If `key` is not an instance of `str`, `TypeError` is raised.
+        If `key` is an empty string, `ValueError` is raised.
+
+        The search in a TST works as follows.
+
+        We start at the root and we compare its character with the first character of key.
+            - If they are the same, we follow the middle link of the root node.
+            - If the first character of key is smaller lexicographically
+            than the key at the root, then we take the left link or pointer.
+            We do this because we know that all strings that start with characters
+            that are smaller lexicographically than key[0] are on its left subtree.
+            - If the first character of key is greater lexicographically
+            than the key at the root, we take similarly the right link or pointer.
+
+        We keep applying this idea at every node.
+        Moreover, WHEN THERE'S A MATCH, next time we compare the key
+        of the next node with the next character of key.
+
+        For example, if there's a match between the first node (the root) and key[0],
+        we follow the middle link, and the next comparison is between
+        the key of the specific next node and key[1], not key[0]!
+
+        **Time complexity:** O(m + h).
+        Check self.insert to see what m and h are."""
+        if not isinstance(key, str):
+            raise TypeError("key must be an instance of type str.")
+        if not key:
+            raise ValueError("key must be a string of length >= 1.")
+
+        node = self._search(self._root, key, 0)
+
+        if node is not None:
+            assert self.search_iteratively(key) == node.value
+            return node.value
+        else:
+            assert self.search_iteratively(key) is None
+            return None
+
+    def _search(self, node: TSTNode, key: str, index: int) -> TSTNode:
+        """Searches for the node containing the value associated with `key` starting from `node`.
+        If returns None OR a node with value None if there's no such node."""
+        if node is None:
+            return None
+
+        if key[index] < node.key:
+            return self._search(node.left, key, index)
+        elif key[index] > node.key:
+            return self._search(node.right, key, index)
+        elif index < len(key) - 1:  # This is a match, but we're not at the last character of key.
+            return self._search(node.mid, key, index + 1)
+        else:  # This is a match and we're at the last character of key.
+            return node  # node could be None!!
+
+    def search_iteratively(self, key: str) -> object:
+        """Iterative alternative to self.search."""
+        if not isinstance(key, str):
+            raise TypeError("key must be an instance of type str.")
+        if not key:
+            raise ValueError("key must be a string of length >= 1.")
+
+        node = self._root
+
+        if node is None:
+            return None
+
+        # Up to the penultimate index (i.e. len(key) - 1)
+        # because if we reach the penultimate character and it's a match,
+        # then we follow the mid node (i.e. we end up in what's possibly the last node).
+        index = 0
+
+        while index < len(key) - 1:
+            while node and key[index] != node.key:
+                if key[index] < node.key:
+                    node = node.left
+                else:
+                    node = node.right
+
+            if node is None:  # Unsuccessful search.
+                return None
+            else:
+                # Arriving here only if exited from the while loop
+                # because the condition key[i] != node.key was false,
+                # that is key[index] == node.key, thus we follow the middle link.
+                node = node.mid
+                index += 1
+
+        assert index == len(key) - 1
+
+        # If node is not None, then we may still need to go left or right,
+        # and we stop when either we find a node which has the same key as the last character of key,
+        # or when `node` ends up being set to None, i.e. the key does not exist in this TST.
+        while node and key[index] != node.key:
+            if key[index] < node.key:
+                node = node.left
+            else:
+                node = node.right
+
+        if node is None:  # Unsuccessful search.
+            return None
+        else:  # We exit the previous while loop because key[index] == node.key.
+            return node.value  # could also be None!!
+
+    def contains(self, key: str) -> bool:
+        """Returns True if `key` is in self, False otherwise.
+
+        **Time complexity:** O(m + h).
+        See the complexity analysis of self.insert for more info about m and h."""
+        return self.search(key) is not None
+
+    def delete(self, key: str) -> TSTNode:
+        """Deletes and returns the value associated with `key` in this TST,
+        if `key` is in this TST, otherwise it returns None.
+
+        If `key` is not an instance of `str`, `TypeError` is raised.
+        If `key` is an empty string, `ValueError` is raised.
+
+        **Time complexity:** O(m + h + k).
+        Check self.search to see what m and h are.
+        k is the number of "no more necessary" cleaned up
+        after deletion of the node associated with `key`.
+        Unnecessary nodes are nodes with no children and value equal to None."""
+        self.__invariants__()
+
+        if not isinstance(key, str):
+            raise TypeError("key must be an instance of type str.")
+        if not key:
+            raise ValueError("key must be a string of length >= 1.")
+
+        # Note: calling self._search, since self.search does not return a Node,
+        # but the value associated with the key passed as parameter.
+        node = self._search(self._root, key, 0)
+
+        if node is not None and node.value is not None:
+            # If node.value is None, it means
+            result = node.value  # forgetting the string tracked by node.
+            node.value = None
+            self._n -= 1
+            self._delete_fix(node)
+        else:
+            result = None
+
+        self.__invariants__()
+        return result
+
+    def _delete_fix(self, u: TSTNode) -> None:
+        """Does the clean up of this TST after deletion of node `u`."""
+        assert u.value is None
+
+        # While u has no children and his value is None,
+        # forget about u and start from his parent.
+        # So, this while loop terminates when either u is None,
+        # u has at least one child, or u's value is not None.
+        while u and not u.has_children() and u.value is None:
+            if self._is_root(u):
+                assert self._n == 0
+                self._root = None
+                break
+
+            if u.is_left_child():
+                u.parent.left = None
+            elif u.is_right_child():
+                u.parent.right = None
+            else:
+                u.parent.mid = None
+
+            p = u.parent
+            u.parent = None
+            u = p
+
+        if u.has_children() and u.value is None:
+            assert self._count(u, 0) > 0
+
+    def traverse(self) -> None:
+        """Traverses all nodes in this TST and prints the key: value associations.
+
+        **Time complexity:** O(n), where n is the number of nodes in self."""
+        self._traverse(self._root, "")
+
+    def _traverse(self, node: TSTNode, prefix: str) -> None:
+        """Helper method to self.traverse.
+
+        **Time complexity:** O(m), where m is the number of nodes under `node`."""
+        if node is None:  # base case
+            return
+
+        self._traverse(node.left, prefix)
+        if node.value is not None:
+            print(prefix + node.key, ": ", node.value)
+
+        self._traverse(node.mid, prefix + node.key)
+        self._traverse(node.right, prefix)
+
+    def keys_with_prefix(self, prefix: str) -> list:
+        """Returns all keys in this TST that start with `prefix`.
+
+        If `prefix` is not an instance of `str`, `TypeError` is raised.
+
+        If `prefix` is an empty string, then all keys in this TST
+        that start with an empty string, thus all keys are returned."""
+        if not isinstance(prefix, str):
+            raise TypeError("prefix must be an instance of str!")
+
+        kwp = []
+
+        if not prefix:
+            self._keys_with_prefix(self._root, [], kwp)
+        else:
+            node = self._search(self._root, prefix, 0)
+
+            if node is not None:
+                if node.value is not None:
+                    # A `key` equals to prefix was found in the TST with an associated value.
+                    kwp.append(prefix)
+
+                self._keys_with_prefix(node.mid, list(prefix), kwp)
+
+        return kwp
+
+    def _keys_with_prefix(self, node: TSTNode, prefix_list: list, kwp: list) -> None:
+        """Returns all keys rooted at `node` given the prefix given as a list of characters `prefix_list`."""
+        if node is None:
+            return
+
+        self._keys_with_prefix(node.left, prefix_list, kwp)
+
+        if node.value is not None:
+            kwp.append("".join(prefix_list + [node.key]))
+
+        prefix_list.append(node.key)
+        self._keys_with_prefix(node.mid, prefix_list, kwp)
+
+        prefix_list.pop()
+        self._keys_with_prefix(node.right, prefix_list, kwp)
+
+    def all_pairs(self) -> dict:
+        """Returns all pairs of key:value from this TST as a Python `dict`."""
+        pairs = {}
+        self._all_pairs(self._root, [], pairs)
+        return pairs
+
+    def _all_pairs(self, node: TSTNode, key_list: list, all_dict: list) -> None:
+        if node is None:
+            return
+
+        self._all_pairs(node.left, key_list, all_dict)
+
+        if node.value is not None:
+            key = "".join(key_list + [node.key])
+            assert key not in all_dict
+            all_dict[key] = node.value
+
+        key_list.append(node.key)
+        self._all_pairs(node.mid, key_list, all_dict)
+
+        key_list.pop()
+        self._all_pairs(node.right, key_list, all_dict)
+
+    def longest_prefix_of(self, query: str) -> str:
+        """Returns the key in this TST which is the longest prefix of `query`,
+        if such a key exists, else it returns None.
+
+        If `query` is not a string `TypeError` is raised.
+        If `query` is a string but empty, `ValueError` is raised.
+
+        If this TST is empty, it returns an empty string."""
+        if not isinstance(query, str):
+            raise TypeError("query is not an instance of str!")
+        if not query:
+            raise ValueError("empty strings not allowed in this TST!")
+
+        length = 0  # It keeps track of the length of the longest prefix of query.
+        x = self._root
+        i = 0
+
+        while x is not None and i < len(query):
+            c = query[i]
+
+            if c < x.key:
+                x = x.left
+            elif c > x.key:
+                x = x.right
+            else:
+                i += 1
+                if x.value is not None:
+                    length = i
+                x = x.mid
+
+        return query[:length]
+
+    def keys_that_match(self, pattern: str) -> list:
+        """Returns a list of keys of this TST that match `pattern`.
+
+        A key `k` of length `m` matches `pattern` if:
+
+        1. m = length(pattern), and
+        2. Either k[i] == pattern[i] or k[i] == '.'.
+            - Example: if `pattern == ".ood"`,
+            then `k == "good"` would match, but not `k == "foodie"`.
+
+        If `pattern` is not a `str`, `TypeError` is raised.
+        If `pattern` is an empty string, `ValueError` is raised."""
+        if not isinstance(pattern, str):
+            raise TypeError("pattern is not an instance of str!")
+        if not pattern:
+            raise ValueError("pattern cannot be an empty string")
+
+        keys = []
+        self._keys_that_match(self._root, [], 0, pattern, keys)
+        return keys
+
+    def _keys_that_match(self, node: TSTNode, prefix_list: list, i: int, pattern: str, keys: list) -> None:
+        """Stores in the list `keys` the of keys that match `pattern` starting from `node`."""
+        if node is None:
+            return
+
+        c = pattern[i]
+
+        if c == "." or c < node.key:
+            self._keys_that_match(node.left, prefix_list, i, pattern, keys)
+
+        if c == "." or c == node.key:
+
+            if i == len(pattern) - 1 and node.value is not None:
+                # If i is the last index and its value is not None
+                keys.append("".join(prefix_list + [node.key]))
+
+            if i < len(pattern) - 1:
+                prefix_list.append(node.key)
+                self._keys_that_match(node.mid, prefix_list, i + 1, pattern, keys)
+                prefix_list.pop()
+
+        if c == "." or c > node.key:
+            self._keys_that_match(node.right, prefix_list, i, pattern, keys)
+
+
+
+ + +
+

Ancestors (in MRO)

+
    +
  • TST
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self):
+    self._n = 0
+    self._root = None
+
+
+
+ +
+ + +
+
+

def all_pairs(

self)

+
+ + + + +

Returns all pairs of key:value from this TST as a Python dict.

+
+ +
+
def all_pairs(self) -> dict:
+    """Returns all pairs of key:value from this TST as a Python `dict`."""
+    pairs = {}
+    self._all_pairs(self._root, [], pairs)
+    return pairs
+
+
+
+ +
+ + +
+
+

def contains(

self, key)

+
+ + + + +

Returns True if key is in self, False otherwise.

+

Time complexity: O(m + h). +See the complexity analysis of self.insert for more info about m and h.

+
+ +
+
def contains(self, key: str) -> bool:
+    """Returns True if `key` is in self, False otherwise.
+    **Time complexity:** O(m + h).
+    See the complexity analysis of self.insert for more info about m and h."""
+    return self.search(key) is not None
+
+
+
+ +
+ + +
+
+

def count(

self)

+
+ + + + +

Counts the number of strings in self.

+

This method recursively passes through all the nodes +and counts the ones which have a non None value.

+

You should clearly use size instead: +this method is here only for the fun of writing code!

+

Time complexity: O(n), where n is the number of nodes in this TST.

+
+ +
+
def count(self) -> int:
+    """Counts the number of strings in self.
+    This method recursively passes through all the nodes
+    and counts the ones which have a non None value.
+    You should clearly use size instead: 
+    this method is here only for the fun of writing code!
+    **Time complexity:** O(n), where n is the number of nodes in this TST."""
+    c = self._count(self._root, 0)
+    assert c == self.size()
+    return c
+
+
+
+ +
+ + +
+
+

def delete(

self, key)

+
+ + + + +

Deletes and returns the value associated with key in this TST, +if key is in this TST, otherwise it returns None.

+

If key is not an instance of str, TypeError is raised. +If key is an empty string, ValueError is raised.

+

Time complexity: O(m + h + k). +Check self.search to see what m and h are. +k is the number of "no more necessary" cleaned up +after deletion of the node associated with key. +Unnecessary nodes are nodes with no children and value equal to None.

+
+ +
+
def delete(self, key: str) -> TSTNode:
+    """Deletes and returns the value associated with `key` in this TST,
+    if `key` is in this TST, otherwise it returns None.
+    If `key` is not an instance of `str`, `TypeError` is raised.
+    If `key` is an empty string, `ValueError` is raised.
+    **Time complexity:** O(m + h + k).
+    Check self.search to see what m and h are.
+    k is the number of "no more necessary" cleaned up
+    after deletion of the node associated with `key`.
+    Unnecessary nodes are nodes with no children and value equal to None."""
+    self.__invariants__()
+    if not isinstance(key, str):
+        raise TypeError("key must be an instance of type str.")
+    if not key:
+        raise ValueError("key must be a string of length >= 1.")
+    # Note: calling self._search, since self.search does not return a Node,
+    # but the value associated with the key passed as parameter.
+    node = self._search(self._root, key, 0)
+    if node is not None and node.value is not None:
+        # If node.value is None, it means
+        result = node.value  # forgetting the string tracked by node.
+        node.value = None
+        self._n -= 1
+        self._delete_fix(node)
+    else:
+        result = None
+    self.__invariants__()
+    return result
+
+
+
+ +
+ + +
+
+

def insert(

self, key, value)

+
+ + + + +

Inserts the key into the symbol table and associates with it value, +overwriting an eventual associated old value, if the key is already in self.

+

If key is not an instance of str, TypeError is raised. +If key is an empty string, ValueError is raised. +If value is None, ValueError is raised.

+

Nodes whose value is not None represent the last character of an inserted word.

+

Time complexity: O(m + h), where m = length(key), +which also represents how many times we follow the middle link, +and h is the number of left and right turns. +So a lower bound of the complexity would be &Omega(m);.

+
+ +
+
def insert(self, key: str, value: object) -> None:
+    """Inserts the `key` into the symbol table and associates with it `value`,
+    overwriting an eventual associated old value, if the `key` is already in self.
+    If `key` is not an instance of `str`, `TypeError` is raised.
+    If `key` is an empty string, `ValueError` is raised.
+    If `value` is None, `ValueError` is raised.
+    Nodes whose value is not None represent the last character of an inserted word.
+    **Time complexity:** O(m + h), where m = length(key),
+    which also represents how many times we follow the middle link,
+    and h is the number of left and right turns.
+    So a lower bound of the complexity would be &Omega(m);."""
+    self.__invariants__()
+    if not isinstance(key, str):
+        raise TypeError("key must be an instance of type str.")
+    if not key:
+        raise ValueError("key must be a string of length >= 1.")
+    if value is None:
+        raise ValueError("value cannot be None.")
+    self._root = self._insert(self._root, key, value, 0)
+    self.__invariants__()
+
+
+
+ +
+ + +
+
+

def is_empty(

self)

+
+ + + + +

Time complexity: O(1).

+
+ +
+
def is_empty(self) -> bool:
+    """**Time complexity:** O(1)."""
+    return self._n == 0
+
+
+
+ +
+ + +
+
+

def keys_that_match(

self, pattern)

+
+ + + + +

Returns a list of keys of this TST that match pattern.

+

A key k of length m matches pattern if:

+
    +
  1. m = length(pattern), and
  2. +
  3. Either k[i] == pattern[i] or k[i] == '.'.
      +
    • Example: if pattern == ".ood", +then k == "good" would match, but not k == "foodie".
    • +
    +
  4. +
+

If pattern is not a str, TypeError is raised. +If pattern is an empty string, ValueError is raised.

+
+ +
+
def keys_that_match(self, pattern: str) -> list:
+    """Returns a list of keys of this TST that match `pattern`.
+    A key `k` of length `m` matches `pattern` if:
+    1. m = length(pattern), and
+    2. Either k[i] == pattern[i] or k[i] == '.'.
+        - Example: if `pattern == ".ood"`,
+        then `k == "good"` would match, but not `k == "foodie"`.
+    If `pattern` is not a `str`, `TypeError` is raised.
+    If `pattern` is an empty string, `ValueError` is raised."""
+    if not isinstance(pattern, str):
+        raise TypeError("pattern is not an instance of str!")
+    if not pattern:
+        raise ValueError("pattern cannot be an empty string")
+    keys = []
+    self._keys_that_match(self._root, [], 0, pattern, keys)
+    return keys
+
+
+
+ +
+ + +
+
+

def keys_with_prefix(

self, prefix)

+
+ + + + +

Returns all keys in this TST that start with prefix.

+

If prefix is not an instance of str, TypeError is raised.

+

If prefix is an empty string, then all keys in this TST +that start with an empty string, thus all keys are returned.

+
+ +
+
def keys_with_prefix(self, prefix: str) -> list:
+    """Returns all keys in this TST that start with `prefix`.
+    If `prefix` is not an instance of `str`, `TypeError` is raised.
+    If `prefix` is an empty string, then all keys in this TST
+    that start with an empty string, thus all keys are returned."""
+    if not isinstance(prefix, str):
+        raise TypeError("prefix must be an instance of str!")
+    kwp = []
+    if not prefix:
+        self._keys_with_prefix(self._root, [], kwp)
+    else:
+        node = self._search(self._root, prefix, 0)
+        if node is not None:
+            if node.value is not None:
+                # A `key` equals to prefix was found in the TST with an associated value.
+                kwp.append(prefix)
+            self._keys_with_prefix(node.mid, list(prefix), kwp)
+    return kwp
+
+
+
+ +
+ + +
+
+

def longest_prefix_of(

self, query)

+
+ + + + +

Returns the key in this TST which is the longest prefix of query, +if such a key exists, else it returns None.

+

If query is not a string TypeError is raised. +If query is a string but empty, ValueError is raised.

+

If this TST is empty, it returns an empty string.

+
+ +
+
def longest_prefix_of(self, query: str) -> str:
+    """Returns the key in this TST which is the longest prefix of `query`,
+    if such a key exists, else it returns None.
+    If `query` is not a string `TypeError` is raised.
+    If `query` is a string but empty, `ValueError` is raised.
+    If this TST is empty, it returns an empty string."""
+    if not isinstance(query, str):
+        raise TypeError("query is not an instance of str!")
+    if not query:
+        raise ValueError("empty strings not allowed in this TST!")
+    length = 0  # It keeps track of the length of the longest prefix of query.
+    x = self._root
+    i = 0
+    while x is not None and i < len(query):
+        c = query[i]
+        if c < x.key:
+            x = x.left
+        elif c > x.key:
+            x = x.right
+        else:
+            i += 1
+            if x.value is not None:
+                length = i
+            x = x.mid
+    return query[:length]
+
+
+
+ +
+ + +
+
+

def search(

self, key)

+
+ + + + +

Returns the value associated with key, if key is in self, else None.

+

If key is not an instance of str, TypeError is raised. +If key is an empty string, ValueError is raised.

+

The search in a TST works as follows.

+

We start at the root and we compare its character with the first character of key. + - If they are the same, we follow the middle link of the root node. + - If the first character of key is smaller lexicographically + than the key at the root, then we take the left link or pointer. + We do this because we know that all strings that start with characters + that are smaller lexicographically than key[0] are on its left subtree. + - If the first character of key is greater lexicographically + than the key at the root, we take similarly the right link or pointer.

+

We keep applying this idea at every node. +Moreover, WHEN THERE'S A MATCH, next time we compare the key +of the next node with the next character of key.

+

For example, if there's a match between the first node (the root) and key[0], +we follow the middle link, and the next comparison is between +the key of the specific next node and key[1], not key[0]!

+

Time complexity: O(m + h). +Check self.insert to see what m and h are.

+
+ +
+
def search(self, key: str) -> object:
+    """Returns the value associated with `key`, if `key` is in self, else None.
+    If `key` is not an instance of `str`, `TypeError` is raised.
+    If `key` is an empty string, `ValueError` is raised.
+    The search in a TST works as follows.
+    We start at the root and we compare its character with the first character of key.
+        - If they are the same, we follow the middle link of the root node.
+        - If the first character of key is smaller lexicographically
+        than the key at the root, then we take the left link or pointer.
+        We do this because we know that all strings that start with characters
+        that are smaller lexicographically than key[0] are on its left subtree.
+        - If the first character of key is greater lexicographically
+        than the key at the root, we take similarly the right link or pointer.
+    We keep applying this idea at every node.
+    Moreover, WHEN THERE'S A MATCH, next time we compare the key
+    of the next node with the next character of key.
+    For example, if there's a match between the first node (the root) and key[0],
+    we follow the middle link, and the next comparison is between
+    the key of the specific next node and key[1], not key[0]!
+    **Time complexity:** O(m + h).
+    Check self.insert to see what m and h are."""
+    if not isinstance(key, str):
+        raise TypeError("key must be an instance of type str.")
+    if not key:
+        raise ValueError("key must be a string of length >= 1.")
+    node = self._search(self._root, key, 0)
+    if node is not None:
+        assert self.search_iteratively(key) == node.value
+        return node.value
+    else:
+        assert self.search_iteratively(key) is None
+        return None
+
+
+
+ +
+ + +
+
+

def search_iteratively(

self, key)

+
+ + + + +

Iterative alternative to self.search.

+
+ +
+
def search_iteratively(self, key: str) -> object:
+    """Iterative alternative to self.search."""
+    if not isinstance(key, str):
+        raise TypeError("key must be an instance of type str.")
+    if not key:
+        raise ValueError("key must be a string of length >= 1.")
+    node = self._root
+    if node is None:
+        return None
+    # Up to the penultimate index (i.e. len(key) - 1)
+    # because if we reach the penultimate character and it's a match,
+    # then we follow the mid node (i.e. we end up in what's possibly the last node).
+    index = 0
+    while index < len(key) - 1:
+        while node and key[index] != node.key:
+            if key[index] < node.key:
+                node = node.left
+            else:
+                node = node.right
+        if node is None:  # Unsuccessful search.
+            return None
+        else:
+            # Arriving here only if exited from the while loop
+            # because the condition key[i] != node.key was false,
+            # that is key[index] == node.key, thus we follow the middle link.
+            node = node.mid
+            index += 1
+    assert index == len(key) - 1
+    # If node is not None, then we may still need to go left or right,
+    # and we stop when either we find a node which has the same key as the last character of key,
+    # or when `node` ends up being set to None, i.e. the key does not exist in this TST.
+    while node and key[index] != node.key:
+        if key[index] < node.key:
+            node = node.left
+        else:
+            node = node.right
+    if node is None:  # Unsuccessful search.
+        return None
+    else:  # We exit the previous while loop because key[index] == node.key.
+        return node.value  # could also be None!!
+
+
+
+ +
+ + +
+
+

def size(

self)

+
+ + + + +
+ +
+
def size(self) -> int:
+    return self._n
+
+
+
+ +
+ + +
+
+

def traverse(

self)

+
+ + + + +

Traverses all nodes in this TST and prints the key: value associations.

+

Time complexity: O(n), where n is the number of nodes in self.

+
+ +
+
def traverse(self) -> None:
+    """Traverses all nodes in this TST and prints the key: value associations.
+    **Time complexity:** O(n), where n is the number of nodes in self."""
+    self._traverse(self._root, "")
+
+
+
+ +
+ +
+
+ +
+

class TSTNode

+ + +

A TSTNode has 6 fields:

+
    +
  • key, which is a character;
  • +
  • value, which is None if self is not a terminal node (of an inserted string in the TST);
  • +
  • parent, which is a pointer to a TSTNode representing the parent of self;
  • +
  • left, which is a pointer to a TSTNode whose key is smaller lexicographically than key;
  • +
  • right, which is similarly a pointer to a TSTNode whose key is greater lexicographically than key;
  • +
  • mid, which is a pointer to a TSTNode whose key is the following character of key in an inserted string.
  • +
+
+ +
+
class TSTNode:
+    """A TSTNode has 6 fields:
+
+        - key, which is a character;
+        - value, which is None if self is not a terminal node (of an inserted string in the TST);
+        - parent, which is a pointer to a TSTNode representing the parent of self;
+        - left, which is a pointer to a TSTNode whose key is smaller lexicographically than key;
+        - right, which is similarly a pointer to a TSTNode whose key is greater lexicographically than key;
+        - mid, which is a pointer to a TSTNode whose key is the following character of key in an inserted string."""
+
+    def __init__(self, key, value=None, parent=None, left=None, mid=None, right=None):
+        if not isinstance(key, str):
+            raise TypeError("key must be an instance of str.")
+        if not key:
+            raise ValueError("key must be a string of length >= 1.")
+
+        self.key = key
+        self.value = value
+        self.parent = parent
+        self.left = left
+        self.mid = mid
+        self.right = right
+
+    def is_left_child(self) -> bool:
+        if not self.parent:
+            raise AttributeError("self does not have a parent.")
+        if self.parent.left:
+            return self.parent.left == self
+        else:
+            return False
+
+    def is_right_child(self) -> bool:
+        if not self.parent:
+            raise AttributeError("self does not have a parent.")
+        if self.parent.right:
+            return self.parent.right == self
+        else:
+            return False
+
+    def is_mid_child(self) -> bool:
+        if not self.parent:
+            raise AttributeError("self does not have a parent.")
+        if self.parent.mid:
+            return self.parent.mid == self
+        else:
+            return False
+
+    def has_children(self) -> bool:
+        return self.left or self.right or self.mid
+
+    def __str__(self):
+        return "{0}: {1}".format(self.key, self.value)
+
+    def __repr__(self):
+        return self.__str__()
+
+
+
+ + +
+

Ancestors (in MRO)

+ +

Static methods

+ +
+
+

def __init__(

self, key, value=None, parent=None, left=None, mid=None, right=None)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, key, value=None, parent=None, left=None, mid=None, right=None):
+    if not isinstance(key, str):
+        raise TypeError("key must be an instance of str.")
+    if not key:
+        raise ValueError("key must be a string of length >= 1.")
+    self.key = key
+    self.value = value
+    self.parent = parent
+    self.left = left
+    self.mid = mid
+    self.right = right
+
+
+
+ +
+ + +
+
+

def has_children(

self)

+
+ + + + +
+ +
+
def has_children(self) -> bool:
+    return self.left or self.right or self.mid
+
+
+
+ +
+ + +
+
+

def is_left_child(

self)

+
+ + + + +
+ +
+
def is_left_child(self) -> bool:
+    if not self.parent:
+        raise AttributeError("self does not have a parent.")
+    if self.parent.left:
+        return self.parent.left == self
+    else:
+        return False
+
+
+
+ +
+ + +
+
+

def is_mid_child(

self)

+
+ + + + +
+ +
+
def is_mid_child(self) -> bool:
+    if not self.parent:
+        raise AttributeError("self does not have a parent.")
+    if self.parent.mid:
+        return self.parent.mid == self
+    else:
+        return False
+
+
+
+ +
+ + +
+
+

def is_right_child(

self)

+
+ + + + +
+ +
+
def is_right_child(self) -> bool:
+    if not self.parent:
+        raise AttributeError("self does not have a parent.")
+    if self.parent.right:
+        return self.parent.right == self
+    else:
+        return False
+
+
+
+ +
+ +

Instance variables

+
+

var key

+ + + + +
+
+ +
+
+

var left

+ + + + +
+
+ +
+
+

var mid

+ + + + +
+
+ +
+
+

var parent

+ + + + +
+
+ +
+
+

var right

+ + + + +
+
+ +
+
+

var value

+ + + + +
+
+ +
+
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/heap.m.html b/docs/ands/ds/heap.m.html new file mode 100644 index 00000000..6398a9fd --- /dev/null +++ b/docs/ands/ds/heap.m.html @@ -0,0 +1,2786 @@ + + + + + + ands.ds.heap API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds.heap module

+

Meta info

+

Author: Nelson Brochado

+

Created: 01/07/2015

+

Updated: 05/02/2017

+

Description

+

This module contains currently the classes HeapNode, which is a class to represent nodes of heaps, +the class BinaryHeap and a function which returns a pretty string representation of a heap passed as parameter.

+

References

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+"""
+# Meta info
+
+Author: Nelson Brochado
+
+Created: 01/07/2015
+
+Updated: 05/02/2017
+
+# Description
+
+This module contains currently the classes `HeapNode`, which is a class to represent nodes of heaps,
+the class `BinaryHeap` and a function which returns a pretty string representation of a heap passed as parameter.
+
+# References
+
+- [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
+- Slides by prof. A. Carzaniga
+- Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS
+- [NotImplementedError](https://docs.python.org/3/library/exceptions.html#NotImplementedError)
+
+"""
+
+import io
+import math
+from collections import Iterable
+
+__all__ = ["BinaryHeap", "HeapNode", "build_pretty_binary_heap"]
+
+
+class HeapNode:
+    """All elements of heap objects are represented with objects of the class HeapNode."""
+
+    def __init__(self, key, value=None):
+        """`key` is the priority used to heapify the heap,
+        and it must be a non-None comparable value.
+        `value` can be used for example for the name of the `HeapNode` object."""
+        if key is None:
+            raise ValueError("key cannot be None.")
+        self.key = key
+        self.value = value if value is not None else self.key
+
+    def __eq__(self, o):
+        return self.key == o.key and self.value == o.value
+
+    def __ne__(self, o):
+        return not self.__eq__(o)
+
+    def __le__(self, o):
+        return self.key <= o.key
+
+    def __ge__(self, o):
+        return self.key >= o.key
+
+    def __lt__(self, o):
+        return not self.__ge__(o)
+
+    def __gt__(self, o):
+        return not self.__le__(o)
+
+    def __str__(self):
+        return str(self.key)
+
+    def __repr__(self):
+        return str(self.value) + " -> " + str(self.key)
+
+
+class BinaryHeap:
+    """Abstract class to represent binary heaps.
+
+    `MinHeap`, `MaxHeap` and `MinMaxHeap` all derive from this class."""
+
+    def __init__(self, ls=None):
+        if ls is None:
+            ls = []
+        self.heap = BinaryHeap._create_list_of_heap_nodes(ls)
+        self.build_heap()
+
+    def push_down(self, i: int) -> None:
+        """Classical so-called heapify operation for heaps."""
+        raise NotImplementedError()
+
+    def push_up(self, i: int) -> None:
+        """Classical reverse-heapify operation for heaps."""
+        raise NotImplementedError()
+
+    def delete(self, i: int) -> HeapNode:
+        raise NotImplementedError()
+
+    def replace(self, i: int, x) -> HeapNode:
+        """Replaces the `HeapNode` object at index `i` with `x`.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, an `HeapNode` is first created,
+        whose key and value are equal to `x`."""
+        raise NotImplementedError()
+
+    def build_heap(self) -> list:
+        """Builds the heap data structure from `self.heap`."""
+        if self.heap:
+            for index in range(len(self.heap) // 2, -1, -1):
+                self.push_down(index)
+        return self.heap
+
+    def add(self, x) -> None:
+        """Adds `x` to this heap.
+
+        In practice, it places `x` at an available leaf,
+        then "bubbles up" from there,
+        in order to maintain the heap property.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, an `HeapNode` is first created,
+        whose key and value are equal to `x`.
+
+        **Time Complexity:** O(log2 n)."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, HeapNode):
+            x = HeapNode(x)
+        self.heap.append(x)
+        if self.size() > 1:
+            self.push_up(self.size() - 1)
+
+    def search(self, x) -> int:
+        """Searches for `x` in this heap,
+        and, if present, returns its index, otherwise returns -1.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, an `HeapNode` is first created,
+        whose key and value are equal to `x`.
+
+        **Time Complexity:** O(n)."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, HeapNode):
+            x = HeapNode(x)
+        for i, node in enumerate(self.heap):
+            if node == x:
+                return i
+        return -1
+
+    def search_by_value(self, val) -> int:
+        """Returns the index of the `HeapNode` object with `value=val`.
+        -1 is returned if no such a `HeapNode` object exists.
+
+        If `val` and the values in this heap are not comparable,
+        the behaviour of this method is undefined.
+
+        By construction, HeapNode objects can't be initialized with None values,
+        but that field could also be set manually after creation.
+
+        **Time Complexity:** O(n)."""
+        for i, node in enumerate(self.heap):
+            if node.value == val:
+                return i
+        return -1
+
+    def contains(self, x) -> bool:
+        """Returns `True`, if `x` is in this heap. `False` otherwise.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, an `HeapNode` is first created,
+        whose key and value are equal to `x`.
+
+        **Time Complexity:** O(n)."""
+        return self.search(x) != -1
+
+    def merge(self, o) -> list:
+        """Merges this heap with the `o` heap.
+
+        Returns the `list` object representing internally the new merged heap.
+
+        **Time Complexity:** O(n + m).
+
+        Time complexity analysis based on:
+        [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
+        self.heap += o.heap
+        return self.build_heap()
+
+    def size(self) -> int:
+        """Returns the size of this heaps.
+
+        **Time Complexity:** O(1)."""
+        return len(self.heap)
+
+    def is_empty(self) -> bool:
+        """Returns `True` if this heap is empty.
+
+        **Time Complexity:** O(1)."""
+        return self.size() == 0
+
+    def clear(self) -> None:
+        """Clears all nodes from this heap.
+        This mean that if you call `is_empty`,
+        it will return `True`.
+
+        **Time Complexity:** O(1)."""
+        self.heap.clear()
+
+    def swap(self, i: int, j: int) -> None:
+        """Swaps elements at indexes `i` and `j`,
+        if they are valid indexes,
+        otherwise an `IndexError` is raised.
+
+        **Time Complexity:** O(1)."""
+        if self.is_good_index(i) and self.is_good_index(j):
+            self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
+        else:
+            raise IndexError("i or j are not valid indexes.")
+
+    # INDEX FUNCTIONS
+
+    def is_good_index(self, i: int) -> bool:
+        """Returns `True` if `i` is valid index for `self.heap`,
+        `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        if not isinstance(i, int):
+            raise TypeError("indexes can only be int.")
+        return False if (i < 0 or i >= self.size()) else True
+
+    def parent_index(self, i: int) -> int:
+        """Returns the parent's index of the node at index `i`.
+        If `i = 0`, then -1 is returned, because the root has no parent.
+
+        If `i` is not a valid index, an `IndexError` is raised.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        return -1 if i == 0 else (i - 1) // 2
+
+    def grandparent_index(self, i: int) -> int:
+        """Returns the grandparent's index of the node at index `i`.
+
+        -1 is returned either if `i` has not a parent or
+        the parent of `i` does not have a parent.
+
+        **Time Complexity:** O(1)."""
+        p = self.parent_index(i)
+        return -1 if p == -1 else self.parent_index(p)
+
+    def left_index(self, i: int) -> int:
+        """Returns the left child's index of the node at index `i`,
+        if it exists, otherwise this function returns -1.
+
+        If `i` is not a valid index, an `IndexError` is raised.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        left = i * 2 + 1
+        return left if self.is_good_index(left) else -1
+
+    def right_index(self, i: int) -> int:
+        """Returns the right child's index of the node at index `i`,
+        if it exists, otherwise this function returns -1.
+
+        If `i` is not a valid index, an `IndexError` is raised.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        right = i * 2 + 2
+        return right if self.is_good_index(right) else -1
+
+    def has_children(self, i: int) -> bool:
+        """Returns `True` if the node at index `i`
+        has at least one child, `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        return self.left_index(i) != -1 or self.right_index(i) != -1
+
+    def is_child(self, c: int, i: int) -> bool:
+        """Returns `True` if `c` is a child of `i`. `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(c) or not self.is_good_index(i):
+            raise IndexError("i or c are not valid indexes.")
+        return c == self.left_index(i) or c == self.right_index(i)
+
+    def is_grandchild(self, g: int, i: int) -> bool:
+        """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        l = self.left_index(i)
+        if l == -1:
+            assert self.right_index(i) == -1
+            if not self.is_good_index(g):
+                raise IndexError("g is not a valid index.")
+            return False
+        r = self.right_index(i)
+        if r == -1:
+            return self.is_child(g, l)
+        else:
+            return self.is_child(g, l) or self.is_child(g, r)
+
+    def is_parent(self, p: int, i: int) -> bool:
+        """Returns `True` if `p` is the index of the parent
+        of the node at `i`, `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(p):
+            raise IndexError("p is not a valid index.")
+        return self.parent_index(i) == p
+
+    def is_grandparent(self, g: int, i: int) -> bool:
+        """Returns `True` if `g` is the index of the grandparent
+        of the node at `i`, `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(g):
+            raise IndexError("g is not a valid index.")
+        p = self.parent_index(i)
+        return False if p == -1 else self.is_parent(g, p)
+
+    def is_on_even_level(self, i: int) -> bool:
+        """Returns `True` if node at index `i` is on a even-level,
+        i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
+        If `i` is not a valid index, an `IndexError` is raised.
+
+        **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        return int(math.log2(i + 1) % 2) == 0
+
+    def is_on_odd_level(self, i: int) -> bool:
+        """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
+        return not self.is_on_even_level(i)
+
+    def __str__(self) -> str:
+        return str(self.heap)
+
+    def __repr__(self) -> str:
+        return build_pretty_binary_heap(self.heap)
+
+    @staticmethod
+    def _create_list_of_heap_nodes(ls: list) -> list:
+        """Creates and returns a list of `HeapNode`
+        objects with the objects in `ls`.
+
+        **Time Complexity:** O(n)."""
+        nodes = []
+        for _, x in enumerate(ls):
+            # x represents also its priority.
+            if isinstance(x, (int, float)):
+                nodes.append(HeapNode(x))
+            else:
+                if len(x) != 2:
+                    raise ValueError("x should be a tuple or list of 2 elements.")
+                # x[0] := priority
+                # x[1] := value associated with x[0]
+                if x[0] is None or x[1] is None:
+                    raise ValueError("keys or values cannot be None.")
+                nodes.append(HeapNode(key=x[0], value=x[1]))
+        return nodes
+
+
+def build_pretty_binary_heap(heap: list, total_width=36, fill=" ") -> str:
+    """Returns a string (which can be printed) representing `heap` as a tree.
+
+    Adapted for Python 3 from: [http://pymotw.com/2/heapq/](http://pymotw.com/2/heapq/).
+
+    To increase/decrease the horizontal space between nodes,
+    just increase/decrease the float number h_space.
+
+    To increase/decrease the vertical space between nodes,
+    just increase/decrease the integer number v_space.
+    Note that v_space must be an integer.
+
+    To change the length of the line under the heap,
+    you can simply change the line_length variable."""
+    if not isinstance(heap, Iterable):
+        raise TypeError("heap must be an iterable object")
+    if len(heap) == 0:
+        return "Nothing to print: heap is empty."
+
+    output = io.StringIO()
+    last_row = -1
+
+    h_space = 3.0  # float
+    v_space = 2  # int
+
+    for i, heap_node in enumerate(heap):
+        if i:
+            row = int(math.floor(math.log(i + 1, 2)))
+        else:
+            row = 0
+
+        if row != last_row:
+            output.write("\n" * v_space)
+
+        columns = 2 ** row
+
+        column_width = int(math.floor((total_width * h_space) / columns))
+        output.write(str(heap_node).center(column_width, fill))
+        last_row = row
+
+    s = output.getvalue() + "\n"
+
+    line_length = total_width + 15  # int
+    s += ('-' * line_length + "\n")
+    return s
+
+
+ +
+ +
+ +

Functions

+ +
+
+

def build_pretty_binary_heap(

heap, total_width=36, fill=' ')

+
+ + + + +

Returns a string (which can be printed) representing heap as a tree.

+

Adapted for Python 3 from: http://pymotw.com/2/heapq/.

+

To increase/decrease the horizontal space between nodes, +just increase/decrease the float number h_space.

+

To increase/decrease the vertical space between nodes, +just increase/decrease the integer number v_space. +Note that v_space must be an integer.

+

To change the length of the line under the heap, +you can simply change the line_length variable.

+
+ +
+
def build_pretty_binary_heap(heap: list, total_width=36, fill=" ") -> str:
+    """Returns a string (which can be printed) representing `heap` as a tree.
+
+    Adapted for Python 3 from: [http://pymotw.com/2/heapq/](http://pymotw.com/2/heapq/).
+
+    To increase/decrease the horizontal space between nodes,
+    just increase/decrease the float number h_space.
+
+    To increase/decrease the vertical space between nodes,
+    just increase/decrease the integer number v_space.
+    Note that v_space must be an integer.
+
+    To change the length of the line under the heap,
+    you can simply change the line_length variable."""
+    if not isinstance(heap, Iterable):
+        raise TypeError("heap must be an iterable object")
+    if len(heap) == 0:
+        return "Nothing to print: heap is empty."
+
+    output = io.StringIO()
+    last_row = -1
+
+    h_space = 3.0  # float
+    v_space = 2  # int
+
+    for i, heap_node in enumerate(heap):
+        if i:
+            row = int(math.floor(math.log(i + 1, 2)))
+        else:
+            row = 0
+
+        if row != last_row:
+            output.write("\n" * v_space)
+
+        columns = 2 ** row
+
+        column_width = int(math.floor((total_width * h_space) / columns))
+        output.write(str(heap_node).center(column_width, fill))
+        last_row = row
+
+    s = output.getvalue() + "\n"
+
+    line_length = total_width + 15  # int
+    s += ('-' * line_length + "\n")
+    return s
+
+
+
+ +
+ + +

Classes

+ +
+

class BinaryHeap

+ + +

Abstract class to represent binary heaps.

+

MinHeap, MaxHeap and MinMaxHeap all derive from this class.

+
+ +
+
class BinaryHeap:
+    """Abstract class to represent binary heaps.
+
+    `MinHeap`, `MaxHeap` and `MinMaxHeap` all derive from this class."""
+
+    def __init__(self, ls=None):
+        if ls is None:
+            ls = []
+        self.heap = BinaryHeap._create_list_of_heap_nodes(ls)
+        self.build_heap()
+
+    def push_down(self, i: int) -> None:
+        """Classical so-called heapify operation for heaps."""
+        raise NotImplementedError()
+
+    def push_up(self, i: int) -> None:
+        """Classical reverse-heapify operation for heaps."""
+        raise NotImplementedError()
+
+    def delete(self, i: int) -> HeapNode:
+        raise NotImplementedError()
+
+    def replace(self, i: int, x) -> HeapNode:
+        """Replaces the `HeapNode` object at index `i` with `x`.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, an `HeapNode` is first created,
+        whose key and value are equal to `x`."""
+        raise NotImplementedError()
+
+    def build_heap(self) -> list:
+        """Builds the heap data structure from `self.heap`."""
+        if self.heap:
+            for index in range(len(self.heap) // 2, -1, -1):
+                self.push_down(index)
+        return self.heap
+
+    def add(self, x) -> None:
+        """Adds `x` to this heap.
+
+        In practice, it places `x` at an available leaf,
+        then "bubbles up" from there,
+        in order to maintain the heap property.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, an `HeapNode` is first created,
+        whose key and value are equal to `x`.
+
+        **Time Complexity:** O(log2 n)."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, HeapNode):
+            x = HeapNode(x)
+        self.heap.append(x)
+        if self.size() > 1:
+            self.push_up(self.size() - 1)
+
+    def search(self, x) -> int:
+        """Searches for `x` in this heap,
+        and, if present, returns its index, otherwise returns -1.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, an `HeapNode` is first created,
+        whose key and value are equal to `x`.
+
+        **Time Complexity:** O(n)."""
+        if x is None:
+            raise ValueError("x cannot be None.")
+        if not isinstance(x, HeapNode):
+            x = HeapNode(x)
+        for i, node in enumerate(self.heap):
+            if node == x:
+                return i
+        return -1
+
+    def search_by_value(self, val) -> int:
+        """Returns the index of the `HeapNode` object with `value=val`.
+        -1 is returned if no such a `HeapNode` object exists.
+
+        If `val` and the values in this heap are not comparable,
+        the behaviour of this method is undefined.
+
+        By construction, HeapNode objects can't be initialized with None values,
+        but that field could also be set manually after creation.
+
+        **Time Complexity:** O(n)."""
+        for i, node in enumerate(self.heap):
+            if node.value == val:
+                return i
+        return -1
+
+    def contains(self, x) -> bool:
+        """Returns `True`, if `x` is in this heap. `False` otherwise.
+
+        `x` can either be a key or a `HeapNode` object.
+        If it's a key, an `HeapNode` is first created,
+        whose key and value are equal to `x`.
+
+        **Time Complexity:** O(n)."""
+        return self.search(x) != -1
+
+    def merge(self, o) -> list:
+        """Merges this heap with the `o` heap.
+
+        Returns the `list` object representing internally the new merged heap.
+
+        **Time Complexity:** O(n + m).
+
+        Time complexity analysis based on:
+        [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
+        self.heap += o.heap
+        return self.build_heap()
+
+    def size(self) -> int:
+        """Returns the size of this heaps.
+
+        **Time Complexity:** O(1)."""
+        return len(self.heap)
+
+    def is_empty(self) -> bool:
+        """Returns `True` if this heap is empty.
+
+        **Time Complexity:** O(1)."""
+        return self.size() == 0
+
+    def clear(self) -> None:
+        """Clears all nodes from this heap.
+        This mean that if you call `is_empty`,
+        it will return `True`.
+
+        **Time Complexity:** O(1)."""
+        self.heap.clear()
+
+    def swap(self, i: int, j: int) -> None:
+        """Swaps elements at indexes `i` and `j`,
+        if they are valid indexes,
+        otherwise an `IndexError` is raised.
+
+        **Time Complexity:** O(1)."""
+        if self.is_good_index(i) and self.is_good_index(j):
+            self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
+        else:
+            raise IndexError("i or j are not valid indexes.")
+
+    # INDEX FUNCTIONS
+
+    def is_good_index(self, i: int) -> bool:
+        """Returns `True` if `i` is valid index for `self.heap`,
+        `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        if not isinstance(i, int):
+            raise TypeError("indexes can only be int.")
+        return False if (i < 0 or i >= self.size()) else True
+
+    def parent_index(self, i: int) -> int:
+        """Returns the parent's index of the node at index `i`.
+        If `i = 0`, then -1 is returned, because the root has no parent.
+
+        If `i` is not a valid index, an `IndexError` is raised.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        return -1 if i == 0 else (i - 1) // 2
+
+    def grandparent_index(self, i: int) -> int:
+        """Returns the grandparent's index of the node at index `i`.
+
+        -1 is returned either if `i` has not a parent or
+        the parent of `i` does not have a parent.
+
+        **Time Complexity:** O(1)."""
+        p = self.parent_index(i)
+        return -1 if p == -1 else self.parent_index(p)
+
+    def left_index(self, i: int) -> int:
+        """Returns the left child's index of the node at index `i`,
+        if it exists, otherwise this function returns -1.
+
+        If `i` is not a valid index, an `IndexError` is raised.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        left = i * 2 + 1
+        return left if self.is_good_index(left) else -1
+
+    def right_index(self, i: int) -> int:
+        """Returns the right child's index of the node at index `i`,
+        if it exists, otherwise this function returns -1.
+
+        If `i` is not a valid index, an `IndexError` is raised.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        right = i * 2 + 2
+        return right if self.is_good_index(right) else -1
+
+    def has_children(self, i: int) -> bool:
+        """Returns `True` if the node at index `i`
+        has at least one child, `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        return self.left_index(i) != -1 or self.right_index(i) != -1
+
+    def is_child(self, c: int, i: int) -> bool:
+        """Returns `True` if `c` is a child of `i`. `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(c) or not self.is_good_index(i):
+            raise IndexError("i or c are not valid indexes.")
+        return c == self.left_index(i) or c == self.right_index(i)
+
+    def is_grandchild(self, g: int, i: int) -> bool:
+        """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        l = self.left_index(i)
+        if l == -1:
+            assert self.right_index(i) == -1
+            if not self.is_good_index(g):
+                raise IndexError("g is not a valid index.")
+            return False
+        r = self.right_index(i)
+        if r == -1:
+            return self.is_child(g, l)
+        else:
+            return self.is_child(g, l) or self.is_child(g, r)
+
+    def is_parent(self, p: int, i: int) -> bool:
+        """Returns `True` if `p` is the index of the parent
+        of the node at `i`, `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(p):
+            raise IndexError("p is not a valid index.")
+        return self.parent_index(i) == p
+
+    def is_grandparent(self, g: int, i: int) -> bool:
+        """Returns `True` if `g` is the index of the grandparent
+        of the node at `i`, `False` otherwise.
+
+        **Time Complexity:** O(1)."""
+        if not self.is_good_index(g):
+            raise IndexError("g is not a valid index.")
+        p = self.parent_index(i)
+        return False if p == -1 else self.is_parent(g, p)
+
+    def is_on_even_level(self, i: int) -> bool:
+        """Returns `True` if node at index `i` is on a even-level,
+        i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
+        If `i` is not a valid index, an `IndexError` is raised.
+
+        **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
+        if not self.is_good_index(i):
+            raise IndexError("i is not a valid index.")
+        return int(math.log2(i + 1) % 2) == 0
+
+    def is_on_odd_level(self, i: int) -> bool:
+        """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
+        return not self.is_on_even_level(i)
+
+    def __str__(self) -> str:
+        return str(self.heap)
+
+    def __repr__(self) -> str:
+        return build_pretty_binary_heap(self.heap)
+
+    @staticmethod
+    def _create_list_of_heap_nodes(ls: list) -> list:
+        """Creates and returns a list of `HeapNode`
+        objects with the objects in `ls`.
+
+        **Time Complexity:** O(n)."""
+        nodes = []
+        for _, x in enumerate(ls):
+            # x represents also its priority.
+            if isinstance(x, (int, float)):
+                nodes.append(HeapNode(x))
+            else:
+                if len(x) != 2:
+                    raise ValueError("x should be a tuple or list of 2 elements.")
+                # x[0] := priority
+                # x[1] := value associated with x[0]
+                if x[0] is None or x[1] is None:
+                    raise ValueError("keys or values cannot be None.")
+                nodes.append(HeapNode(key=x[0], value=x[1]))
+        return nodes
+
+
+
+ + +
+

Ancestors (in MRO)

+ +

Static methods

+ +
+
+

def __init__(

self, ls=None)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self, ls=None):
+    if ls is None:
+        ls = []
+    self.heap = BinaryHeap._create_list_of_heap_nodes(ls)
+    self.build_heap()
+
+
+
+ +
+ + +
+
+

def add(

self, x)

+
+ + + + +

Adds x to this heap.

+

In practice, it places x at an available leaf, +then "bubbles up" from there, +in order to maintain the heap property.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(log2 n).

+
+ +
+
def add(self, x) -> None:
+    """Adds `x` to this heap.
+    In practice, it places `x` at an available leaf,
+    then "bubbles up" from there,
+    in order to maintain the heap property.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(log2 n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    self.heap.append(x)
+    if self.size() > 1:
+        self.push_up(self.size() - 1)
+
+
+
+ +
+ + +
+
+

def build_heap(

self)

+
+ + + + +

Builds the heap data structure from self.heap.

+
+ +
+
def build_heap(self) -> list:
+    """Builds the heap data structure from `self.heap`."""
+    if self.heap:
+        for index in range(len(self.heap) // 2, -1, -1):
+            self.push_down(index)
+    return self.heap
+
+
+
+ +
+ + +
+
+

def clear(

self)

+
+ + + + +

Clears all nodes from this heap. +This mean that if you call is_empty, +it will return True.

+

Time Complexity: O(1).

+
+ +
+
def clear(self) -> None:
+    """Clears all nodes from this heap.
+    This mean that if you call `is_empty`,
+    it will return `True`.
+    **Time Complexity:** O(1)."""
+    self.heap.clear()
+
+
+
+ +
+ + +
+
+

def contains(

self, x)

+
+ + + + +

Returns True, if x is in this heap. False otherwise.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(n).

+
+ +
+
def contains(self, x) -> bool:
+    """Returns `True`, if `x` is in this heap. `False` otherwise.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(n)."""
+    return self.search(x) != -1
+
+
+
+ +
+ + +
+
+

def delete(

self, i)

+
+ + + + +
+ +
+
def delete(self, i: int) -> HeapNode:
+    raise NotImplementedError()
+
+
+
+ +
+ + +
+
+

def grandparent_index(

self, i)

+
+ + + + +

Returns the grandparent's index of the node at index i.

+

-1 is returned either if i has not a parent or +the parent of i does not have a parent.

+

Time Complexity: O(1).

+
+ +
+
def grandparent_index(self, i: int) -> int:
+    """Returns the grandparent's index of the node at index `i`.
+    -1 is returned either if `i` has not a parent or
+    the parent of `i` does not have a parent.
+    **Time Complexity:** O(1)."""
+    p = self.parent_index(i)
+    return -1 if p == -1 else self.parent_index(p)
+
+
+
+ +
+ + +
+
+

def has_children(

self, i)

+
+ + + + +

Returns True if the node at index i +has at least one child, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def has_children(self, i: int) -> bool:
+    """Returns `True` if the node at index `i`
+    has at least one child, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return self.left_index(i) != -1 or self.right_index(i) != -1
+
+
+
+ +
+ + +
+
+

def is_child(

self, c, i)

+
+ + + + +

Returns True if c is a child of i. False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_child(self, c: int, i: int) -> bool:
+    """Returns `True` if `c` is a child of `i`. `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(c) or not self.is_good_index(i):
+        raise IndexError("i or c are not valid indexes.")
+    return c == self.left_index(i) or c == self.right_index(i)
+
+
+
+ +
+ + +
+
+

def is_empty(

self)

+
+ + + + +

Returns True if this heap is empty.

+

Time Complexity: O(1).

+
+ +
+
def is_empty(self) -> bool:
+    """Returns `True` if this heap is empty.
+    **Time Complexity:** O(1)."""
+    return self.size() == 0
+
+
+
+ +
+ + +
+
+

def is_good_index(

self, i)

+
+ + + + +

Returns True if i is valid index for self.heap, +False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_good_index(self, i: int) -> bool:
+    """Returns `True` if `i` is valid index for `self.heap`,
+    `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not isinstance(i, int):
+        raise TypeError("indexes can only be int.")
+    return False if (i < 0 or i >= self.size()) else True
+
+
+
+ +
+ + +
+
+

def is_grandchild(

self, g, i)

+
+ + + + +

Returns True if g is a grandchild of i. False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_grandchild(self, g: int, i: int) -> bool:
+    """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
+    **Time Complexity:** O(1)."""
+    l = self.left_index(i)
+    if l == -1:
+        assert self.right_index(i) == -1
+        if not self.is_good_index(g):
+            raise IndexError("g is not a valid index.")
+        return False
+    r = self.right_index(i)
+    if r == -1:
+        return self.is_child(g, l)
+    else:
+        return self.is_child(g, l) or self.is_child(g, r)
+
+
+
+ +
+ + +
+
+

def is_grandparent(

self, g, i)

+
+ + + + +

Returns True if g is the index of the grandparent +of the node at i, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_grandparent(self, g: int, i: int) -> bool:
+    """Returns `True` if `g` is the index of the grandparent
+    of the node at `i`, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(g):
+        raise IndexError("g is not a valid index.")
+    p = self.parent_index(i)
+    return False if p == -1 else self.is_parent(g, p)
+
+
+
+ +
+ + +
+
+

def is_on_even_level(

self, i)

+
+ + + + +

Returns True if node at index i is on a even-level, +i.e., if i is on a level multiple of 2 (0, 2, 4, 6,...). +If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(int(math.log2(i + 1) % 2) == 0).

+
+ +
+
def is_on_even_level(self, i: int) -> bool:
+    """Returns `True` if node at index `i` is on a even-level,
+    i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return int(math.log2(i + 1) % 2) == 0
+
+
+
+ +
+ + +
+
+

def is_on_odd_level(

self, i)

+
+ + + + +

Returns True (False) if self.is_on_even_level(i) returns False (True).

+
+ +
+
def is_on_odd_level(self, i: int) -> bool:
+    """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
+    return not self.is_on_even_level(i)
+
+
+
+ +
+ + +
+
+

def is_parent(

self, p, i)

+
+ + + + +

Returns True if p is the index of the parent +of the node at i, False otherwise.

+

Time Complexity: O(1).

+
+ +
+
def is_parent(self, p: int, i: int) -> bool:
+    """Returns `True` if `p` is the index of the parent
+    of the node at `i`, `False` otherwise.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(p):
+        raise IndexError("p is not a valid index.")
+    return self.parent_index(i) == p
+
+
+
+ +
+ + +
+
+

def left_index(

self, i)

+
+ + + + +

Returns the left child's index of the node at index i, +if it exists, otherwise this function returns -1.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def left_index(self, i: int) -> int:
+    """Returns the left child's index of the node at index `i`,
+    if it exists, otherwise this function returns -1.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    left = i * 2 + 1
+    return left if self.is_good_index(left) else -1
+
+
+
+ +
+ + +
+
+

def merge(

self, o)

+
+ + + + +

Merges this heap with the o heap.

+

Returns the list object representing internally the new merged heap.

+

Time Complexity: O(n + m).

+

Time complexity analysis based on: +http://stackoverflow.com/a/29197855/3924118.

+
+ +
+
def merge(self, o) -> list:
+    """Merges this heap with the `o` heap.
+    Returns the `list` object representing internally the new merged heap.
+    **Time Complexity:** O(n + m).
+    Time complexity analysis based on:
+    [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
+    self.heap += o.heap
+    return self.build_heap()
+
+
+
+ +
+ + +
+
+

def parent_index(

self, i)

+
+ + + + +

Returns the parent's index of the node at index i. +If i = 0, then -1 is returned, because the root has no parent.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def parent_index(self, i: int) -> int:
+    """Returns the parent's index of the node at index `i`.
+    If `i = 0`, then -1 is returned, because the root has no parent.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    return -1 if i == 0 else (i - 1) // 2
+
+
+
+ +
+ + +
+
+

def push_down(

self, i)

+
+ + + + +

Classical so-called heapify operation for heaps.

+
+ +
+
def push_down(self, i: int) -> None:
+    """Classical so-called heapify operation for heaps."""
+    raise NotImplementedError()
+
+
+
+ +
+ + +
+
+

def push_up(

self, i)

+
+ + + + +

Classical reverse-heapify operation for heaps.

+
+ +
+
def push_up(self, i: int) -> None:
+    """Classical reverse-heapify operation for heaps."""
+    raise NotImplementedError()
+
+
+
+ +
+ + +
+
+

def replace(

self, i, x)

+
+ + + + +

Replaces the HeapNode object at index i with x.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+
+ +
+
def replace(self, i: int, x) -> HeapNode:
+    """Replaces the `HeapNode` object at index `i` with `x`.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`."""
+    raise NotImplementedError()
+
+
+
+ +
+ + +
+
+

def right_index(

self, i)

+
+ + + + +

Returns the right child's index of the node at index i, +if it exists, otherwise this function returns -1.

+

If i is not a valid index, an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def right_index(self, i: int) -> int:
+    """Returns the right child's index of the node at index `i`,
+    if it exists, otherwise this function returns -1.
+    If `i` is not a valid index, an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if not self.is_good_index(i):
+        raise IndexError("i is not a valid index.")
+    right = i * 2 + 2
+    return right if self.is_good_index(right) else -1
+
+
+
+ +
+ + +
+
+

def search(

self, x)

+
+ + + + +

Searches for x in this heap, +and, if present, returns its index, otherwise returns -1.

+

x can either be a key or a HeapNode object. +If it's a key, an HeapNode is first created, +whose key and value are equal to x.

+

Time Complexity: O(n).

+
+ +
+
def search(self, x) -> int:
+    """Searches for `x` in this heap,
+    and, if present, returns its index, otherwise returns -1.
+    `x` can either be a key or a `HeapNode` object.
+    If it's a key, an `HeapNode` is first created,
+    whose key and value are equal to `x`.
+    **Time Complexity:** O(n)."""
+    if x is None:
+        raise ValueError("x cannot be None.")
+    if not isinstance(x, HeapNode):
+        x = HeapNode(x)
+    for i, node in enumerate(self.heap):
+        if node == x:
+            return i
+    return -1
+
+
+
+ +
+ + +
+
+

def search_by_value(

self, val)

+
+ + + + +

Returns the index of the HeapNode object with value=val. +-1 is returned if no such a HeapNode object exists.

+

If val and the values in this heap are not comparable, +the behaviour of this method is undefined.

+

By construction, HeapNode objects can't be initialized with None values, +but that field could also be set manually after creation.

+

Time Complexity: O(n).

+
+ +
+
def search_by_value(self, val) -> int:
+    """Returns the index of the `HeapNode` object with `value=val`.
+    -1 is returned if no such a `HeapNode` object exists.
+    If `val` and the values in this heap are not comparable,
+    the behaviour of this method is undefined.
+    By construction, HeapNode objects can't be initialized with None values,
+    but that field could also be set manually after creation.
+    **Time Complexity:** O(n)."""
+    for i, node in enumerate(self.heap):
+        if node.value == val:
+            return i
+    return -1
+
+
+
+ +
+ + +
+
+

def size(

self)

+
+ + + + +

Returns the size of this heaps.

+

Time Complexity: O(1).

+
+ +
+
def size(self) -> int:
+    """Returns the size of this heaps.
+    **Time Complexity:** O(1)."""
+    return len(self.heap)
+
+
+
+ +
+ + +
+
+

def swap(

self, i, j)

+
+ + + + +

Swaps elements at indexes i and j, +if they are valid indexes, +otherwise an IndexError is raised.

+

Time Complexity: O(1).

+
+ +
+
def swap(self, i: int, j: int) -> None:
+    """Swaps elements at indexes `i` and `j`,
+    if they are valid indexes,
+    otherwise an `IndexError` is raised.
+    **Time Complexity:** O(1)."""
+    if self.is_good_index(i) and self.is_good_index(j):
+        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
+    else:
+        raise IndexError("i or j are not valid indexes.")
+
+
+
+ +
+ +

Instance variables

+
+

var heap

+ + + + +
+
+ +
+
+
+ +
+

class HeapNode

+ + +

All elements of heap objects are represented with objects of the class HeapNode.

+
+ +
+
class HeapNode:
+    """All elements of heap objects are represented with objects of the class HeapNode."""
+
+    def __init__(self, key, value=None):
+        """`key` is the priority used to heapify the heap,
+        and it must be a non-None comparable value.
+        `value` can be used for example for the name of the `HeapNode` object."""
+        if key is None:
+            raise ValueError("key cannot be None.")
+        self.key = key
+        self.value = value if value is not None else self.key
+
+    def __eq__(self, o):
+        return self.key == o.key and self.value == o.value
+
+    def __ne__(self, o):
+        return not self.__eq__(o)
+
+    def __le__(self, o):
+        return self.key <= o.key
+
+    def __ge__(self, o):
+        return self.key >= o.key
+
+    def __lt__(self, o):
+        return not self.__ge__(o)
+
+    def __gt__(self, o):
+        return not self.__le__(o)
+
+    def __str__(self):
+        return str(self.key)
+
+    def __repr__(self):
+        return str(self.value) + " -> " + str(self.key)
+
+
+
+ + +
+

Ancestors (in MRO)

+ +

Static methods

+ +
+
+

def __init__(

self, key, value=None)

+
+ + + + +

key is the priority used to heapify the heap, +and it must be a non-None comparable value. +value can be used for example for the name of the HeapNode object.

+
+ +
+
def __init__(self, key, value=None):
+    """`key` is the priority used to heapify the heap,
+    and it must be a non-None comparable value.
+    `value` can be used for example for the name of the `HeapNode` object."""
+    if key is None:
+        raise ValueError("key cannot be None.")
+    self.key = key
+    self.value = value if value is not None else self.key
+
+
+
+ +
+ +

Instance variables

+
+

var key

+ + + + +
+
+ +
+
+

var value

+ + + + +
+
+ +
+
+
+ +
+ +
+
+ +
+ + diff --git a/docs/ands/ds/index.html b/docs/ands/ds/index.html new file mode 100644 index 00000000..86bf3591 --- /dev/null +++ b/docs/ands/ds/index.html @@ -0,0 +1,1235 @@ + + + + + + ands.ds API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands.ds module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.ds.BST

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 01/07/2015

+

Updated: 28/08/2016

+

Description

+

Coding Conventions

+

In general, if a variable name has more than one word, +those words are separated by _ (underscores). +Functions' names should roughly describe what the function does. +Names of functi...

+ +
+
+

ands.ds.DSForests

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 21/02/2016

+

Updated: 03/01/2016

+

Description

+

A disjoint-set (forests) or union-find data structure is a data structure which keeps track of a set of elements +partitioned into disjoint (non-overlapping, i.e. their intersection is the empty set) sets. +...

+ +
+
+

ands.ds.Graph

+ + +

Author: Nelson Brochado

+

Creation: July, 2015

+

Last update: 01/09/16

+

Graph data structure using adjacency list representation.

+

You can represent a undirected graph by adding +each endpoint (node) of an edge to the adjacency list of the other endpoint. +For example, suppose we have node A and B. +To ...

+ +
+
+

ands.ds.HashTable

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 01/06/2015

+

Updated: 21/02/2016

+

Description

+

Hash table that re-sizes if no more slot is available. +The process of re-sizing doubles the current capacity of the hash table each time (for now). +It uses [linear probing](https://en.wikipedia.org/wiki/Li...

+ +
+
+

ands.ds.MaxHeap

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 15/02/2016

+

Updated: 05/02/2017

+

Description

+

Mirror-class to the MinHeap class. +For more info, see the introductory doc-strings of the file MinHeap.py.

+

References

+
    +
  • [https://en.wikipedia.org/wiki/Binary_heap](https://en.wikipedia....
  • +
+ +
+
+

ands.ds.MinHeap

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 01/07/2015

+

Updated: 05/02/2017

+

Description

+

A binary min-heap is a data structure similar to a binary tree, +where the parent nodes are smaller or equal to their children.

+

In addition to the previous constraint, a binary min-heap is a complete binar...

+ +
+
+

ands.ds.MinMaxHeap

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 18/02/2016

+

Updated: 29/12/2016

+

Description

+

Min-Max Heap is a heap that supports find-min and find-max operations in constant time. +Moreover, both remove-min and remove-max are supported in logarithmic time. +It's therefore an useful data structure t...

+ +
+
+

ands.ds.MinPriorityQueue

+ + +

Author: Nelson Brochado

+

Created: 24/01/2017

+ +
+
+

ands.ds.Queue

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 02/07/2015

+

Updated: 04/02/2017

+

Description

+

Basic queue, which is FIFO (first-in-first-out) data structure. +It's implemented using a deque, because a deque supports better the dequeue operation than lists.

+

References

+
    +
  • [https://docs.python.org...
  • +
+ +
+
+

ands.ds.RBT

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 01/08/2015

+

Updated: 28/08/2016

+

Description

+

Red-black Tree Property

+
    +
  1. +

    Every node is either red or black.

    +
  2. +
  3. +

    The root is black.

    +
  4. +
  5. +

    Every NIL or leaf node is black.

    +
  6. +
  7. +

    If a node is red, then both its children are black, +in other words, there c...

    +
  8. +
+ +
+
+

ands.ds.Stack

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 05/07/2015

+

Updated: 04/02/2017

+

Description

+

A stack is one of the most simple and, at the same time, useful abstract data types in computer science.

+

An abstract data type (or, in short, ADT) is a logical description or specification +of a certain wa...

+ +
+
+

ands.ds.TST

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 05/09/2015

+

Updated: 03/02/2017

+

Description

+

Ternary-search tries (or trees) combine the time efficiency of other tries +with the space efficiency of binary-search trees.

+

An advantage compared to hash maps is that ternary search tries support sorting...

+ +
+
+

ands.ds.heap

+ + +

Meta info

+

Author: Nelson Brochado

+

Created: 01/07/2015

+

Updated: 05/02/2017

+

Description

+

This module contains currently the classes HeapNode, which is a class to represent nodes of heaps, +the class BinaryHeap and a function which returns a pretty string representation of a heap passed as p...

+ +
+
+ +
+
+ +
+ + diff --git a/docs/ands/index.html b/docs/ands/index.html new file mode 100644 index 00000000..a0a34f6e --- /dev/null +++ b/docs/ands/index.html @@ -0,0 +1,1045 @@ + + + + + + ands API documentation + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

ands module

+ + + +
+
#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+
+# File to allow this directory to be treated as a python package.
+
+# From Python 3.3+ this way of constructing subpackages
+# should no more be necessary, but for some reason,
+# at the moment of this writing, pdoc does not seem to work
+# as expected if I omit these __init__.py files.
+
+
+ +
+ +
+ + + +

Sub-modules

+
+

ands.algorithms

+ + + +
+
+

ands.ds

+ + + +
+
+ +
+
+ +
+ + diff --git a/tests/README.md b/tests/README.md index 873b8db0..38d630a5 100644 --- a/tests/README.md +++ b/tests/README.md @@ -6,7 +6,9 @@ - Queue, - Stack, - - DSForests, + - DSForests, + - HeapNode + - BinaryHeap - TST which, as all other data structures, may nonetheless need to be improved! diff --git a/tests/algorithms/crypto/test_caesar.py b/tests/algorithms/crypto/test_caesar.py index 5bc92663..9ee82bd4 100755 --- a/tests/algorithms/crypto/test_caesar.py +++ b/tests/algorithms/crypto/test_caesar.py @@ -1,8 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- + """ +# Meta info + Author: Nelson Brochado +Created: 01/01/2017 + +# Description Tests for the caesar cipher algorithms. """ diff --git a/tests/algorithms/crypto/test_one_time_pad.py b/tests/algorithms/crypto/test_one_time_pad.py index ebbd129e..fe51d3b9 100755 --- a/tests/algorithms/crypto/test_one_time_pad.py +++ b/tests/algorithms/crypto/test_one_time_pad.py @@ -1,8 +1,14 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- + """ +# Meta info + Author: Nelson Brochado +Created: 01/01/2017 + +# Description Testing the one_time_pad algorithm. """ @@ -38,7 +44,3 @@ def test_random_size(self): it = randint(3, 11) size = randint(10, 1000) self.template_test(it, size) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/algorithms/ode/test_forward_euler.py b/tests/algorithms/ode/test_forward_euler.py index c9cd18c8..f6131ac5 100644 --- a/tests/algorithms/ode/test_forward_euler.py +++ b/tests/algorithms/ode/test_forward_euler.py @@ -1,8 +1,16 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- + """ +# Meta info + Author: Nelson Brochado +Created: 01/05/2016 + +# Description + +Testing the functions under forward_euler.py """ import unittest @@ -11,7 +19,6 @@ class TestForwardEuler(unittest.TestCase): - def f(self, ti, yi): return yi @@ -56,7 +63,3 @@ def test_1(self): self.assertIsNotNone(t) self.assertIsNotNone(y) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/algorithms/recursion/test_ackermann.py b/tests/algorithms/recursion/test_ackermann.py index 509a174c..2d43867d 100644 --- a/tests/algorithms/recursion/test_ackermann.py +++ b/tests/algorithms/recursion/test_ackermann.py @@ -1,11 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- + """ -Author: Nelson Brochado +# Meta info +Author: Nelson Brochado Created: 18/01/2017 +# Description + Testing the first 3*3 = 9 permutations for two inputs m and n from a domain {0, 1, 2} """ diff --git a/tests/algorithms/recursion/test_count.py b/tests/algorithms/recursion/test_count.py index 515e3350..a3fe9678 100644 --- a/tests/algorithms/recursion/test_count.py +++ b/tests/algorithms/recursion/test_count.py @@ -2,10 +2,13 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info +Author: Nelson Brochado Created: 15/01/2017 +# Description + Testing the method `count` from `ands.algorithms.recursion.count`. """ diff --git a/tests/algorithms/recursion/test_factorial.py b/tests/algorithms/recursion/test_factorial.py index a8fbe4e9..78955aae 100644 --- a/tests/algorithms/recursion/test_factorial.py +++ b/tests/algorithms/recursion/test_factorial.py @@ -2,9 +2,14 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info +Author: Nelson Brochado Created: 21/01/2017 + +# Description + +Testing methods under `factorial.py`. """ import math diff --git a/tests/algorithms/recursion/test_hanoi.py b/tests/algorithms/recursion/test_hanoi.py index bc3b1cb2..d2bb259f 100644 --- a/tests/algorithms/recursion/test_hanoi.py +++ b/tests/algorithms/recursion/test_hanoi.py @@ -2,9 +2,14 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info +Author: Nelson Brochado Created: 18/01/2017 + +# Description + +Testing hanoi function. """ import unittest diff --git a/tests/algorithms/recursion/test_is_sorted.py b/tests/algorithms/recursion/test_is_sorted.py index 93b8938b..57afcafe 100644 --- a/tests/algorithms/recursion/test_is_sorted.py +++ b/tests/algorithms/recursion/test_is_sorted.py @@ -2,9 +2,14 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info +Author: Nelson Brochado Created: 21/01/2017 + +# Description + +Testing functions under is_sorted.py. """ import unittest diff --git a/tests/algorithms/recursion/test_make_decimal.py b/tests/algorithms/recursion/test_make_decimal.py index 85d81e4c..b8f68a4f 100644 --- a/tests/algorithms/recursion/test_make_decimal.py +++ b/tests/algorithms/recursion/test_make_decimal.py @@ -2,9 +2,14 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info +Author: Nelson Brochado Created: 20/01/2017 + +# Description + +Testing make_decimal function. """ import string diff --git a/tests/algorithms/recursion/test_palindrome.py b/tests/algorithms/recursion/test_palindrome.py index 11122d5a..c7bc841b 100644 --- a/tests/algorithms/recursion/test_palindrome.py +++ b/tests/algorithms/recursion/test_palindrome.py @@ -2,9 +2,14 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info +Author: Nelson Brochado Created: 20/01/2017 + +# Description + +Testing the recursive palindrome function. """ import string diff --git a/tests/algorithms/recursion/test_power.py b/tests/algorithms/recursion/test_power.py index 3188ad2d..9725129d 100644 --- a/tests/algorithms/recursion/test_power.py +++ b/tests/algorithms/recursion/test_power.py @@ -2,9 +2,14 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info +Author: Nelson Brochado Created: 18/01/2017 + +# Description + +Testing the recursive power function. """ import unittest diff --git a/tests/algorithms/recursion/test_reverse.py b/tests/algorithms/recursion/test_reverse.py index 7dc70f74..0b99da87 100644 --- a/tests/algorithms/recursion/test_reverse.py +++ b/tests/algorithms/recursion/test_reverse.py @@ -1,11 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- + """ -Author: Nelson Brochado +# Meta info +Author: Nelson Brochado Created: 16/01/2017 +# Description + Testing the recursive implementation of reversing a list. """ diff --git a/tests/ds/test_BST.py b/tests/ds/test_BST.py index 174275c2..6f1c83d1 100755 --- a/tests/ds/test_BST.py +++ b/tests/ds/test_BST.py @@ -2,11 +2,13 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info -Creation: 13/02/16 +Author: Nelson Brochado +Created: 13/02/2016 +Updated: 30/08/2016 -Last update: 30/08/16 +# Description Tests for the BST class. """ @@ -324,7 +326,3 @@ def asserts(): b._switch(b.search(10), b.search(10).left.right) asserts() - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/ds/test_BSTNode.py b/tests/ds/test_BSTNode.py index c015e77f..bb0deb3a 100755 --- a/tests/ds/test_BSTNode.py +++ b/tests/ds/test_BSTNode.py @@ -2,11 +2,13 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info -Creation: 15/02/16 +Author: Nelson Brochado +Created: 15/02/2016 +Updated: 30/08/2016 -Last update: 30/08/16 +# Description Tests for the BSTNode class. """ @@ -147,7 +149,3 @@ def test_uncle(self): self.assertIsNone(n.uncle) self.assertIsNone(n.sibling) self.assertRaises(AttributeError, n.is_left_child) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/ds/test_BinaryHeap.py b/tests/ds/test_BinaryHeap.py new file mode 100755 index 00000000..e183efaa --- /dev/null +++ b/tests/ds/test_BinaryHeap.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +""" +# Meta info + +Author: Nelson Brochado +Created: 14/02/2016 +Updated: 05/02/2017 + +# Description + +Tests for the abstract class BinaryHeap. +""" + +import unittest + +from ands.ds.heap import BinaryHeap + + +class TestBinaryHeap(unittest.TestCase): + def test_heap_creation(self): + self.assertRaises(NotImplementedError, BinaryHeap, [12, 14, 28]) + self.assertIsNotNone(BinaryHeap()) + self.assertEqual(BinaryHeap().heap, []) + self.assertEqual(BinaryHeap([]).heap, []) diff --git a/tests/ds/test_DSForests.py b/tests/ds/test_DSForests.py index b2d0a2c9..29149da7 100755 --- a/tests/ds/test_DSForests.py +++ b/tests/ds/test_DSForests.py @@ -2,19 +2,17 @@ # -*- coding: utf-8 -*- """ -## Meta info +# Meta info Author: Nelson Brochado +Created: 22/02/16 +Updated: 03/01/17 -Creation: 22/02/16 - -Last update: 03/01/17 - -## Description +# Description Tests for the DSForests class and associated classes. -## Note +# Note Since find_iteratively internally asserts that its result is equal to find, in these tests I'm using find_iteratively @@ -193,7 +191,3 @@ def test_print_set(self): ds.union(8, 13) ds.print_set(3) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/ds/test_HashTable.py b/tests/ds/test_HashTable.py index 51c9fbe4..a90c3987 100755 --- a/tests/ds/test_HashTable.py +++ b/tests/ds/test_HashTable.py @@ -1,12 +1,15 @@ #!/usr/bin/env python3 # -*- coding: utf-8 -*- + """ -Author: Nelson Brochado +# Meta info -Creation: 21/02/16 +Author: Nelson Brochado +Created: 21/02/2016 +Updated: 08/10/2016 -Last Update: 08/10/16 +# Description Test the HashTable class. """ @@ -212,7 +215,3 @@ def test_empty_hash_table_capacity(self): h = HashTable(capacity=i) self.assertEqual(h.capacity, i) self.assertEqual(h.size, 0) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/ds/test_Heap.py b/tests/ds/test_Heap.py deleted file mode 100755 index 29c8e4ac..00000000 --- a/tests/ds/test_Heap.py +++ /dev/null @@ -1,87 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -""" -Author: Nelson Brochado - -Creation: 14/02/16 - -Last update: 06/09/16 - -Tests for the abstract class Heap. -""" - -import unittest - -from ands.ds.Heap import Heap, HeapNode - - -class TestHeap(unittest.TestCase): - def test_heap_creation(self): - self.assertRaises(NotImplementedError, Heap, [12, 14, 28]) - self.assertIsNotNone(Heap()) - self.assertEqual(Heap().heap, []) - self.assertEqual(Heap([]).heap, []) - - -class TestHeapNode(unittest.TestCase): - def test_None(self): - self.assertRaises(ValueError, HeapNode, None) - - def test_creation_key_value_same(self): - n = HeapNode(26) - self.assertIsNotNone(n.value) - self.assertEqual(n.key, n.value) - - def test_creation_key_value_different(self): - n = HeapNode(2, "two") - self.assertIsNotNone(n.value) - self.assertNotEqual(n.value, n.key) - - def test_less_and_greater_than(self): - h = HeapNode(12) - h2 = HeapNode(14) - h3 = HeapNode(12, "Twelve") - - self.assertLess(h, h2) - self.assertGreater(h2, h) - self.assertLess(h3, h2) - self.assertGreater(h2, h3) - self.assertFalse(h < h) - self.assertFalse(h > h) - self.assertFalse(h2 < h2) - self.assertFalse(h2 > h2) - self.assertFalse(h3 < h3) - self.assertFalse(h3 > h3) - - def test_equal_not_equal(self): - h = HeapNode(12) - h2 = HeapNode(14) - h3 = HeapNode(12) - h4 = HeapNode(14, "fourteen") - - self.assertEqual(h, h) - self.assertEqual(h4, h4) - self.assertEqual(h, h3) - self.assertNotEqual(h, h2) - self.assertNotEqual(h3, h2) - self.assertNotEqual(h2, h4) - self.assertEqual(h4, HeapNode(14, "fourteen")) - self.assertNotEqual(h4, HeapNode("fourteen", 14)) - - def test_less_and_greater_or_equal(self): - h = HeapNode(12) - h2 = HeapNode(14) - h3 = HeapNode(12, "Twelve") - - self.assertLessEqual(h, h3) - self.assertLessEqual(h3, h) - self.assertGreaterEqual(h, h3) - self.assertGreaterEqual(h3, h) - self.assertLessEqual(h3, h3) - self.assertLessEqual(h2, h2) - self.assertLessEqual(h, h) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/ds/test_HeapNode.py b/tests/ds/test_HeapNode.py new file mode 100644 index 00000000..4fbd2c87 --- /dev/null +++ b/tests/ds/test_HeapNode.py @@ -0,0 +1,121 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + + +""" +# Meta info + +Author: Nelson Brochado +Created: 05/02/2017 +Updated: 05/02/2017 + +# Description + +Tests for the HeapNode class in heap.py. + +""" + +import unittest + +from ands.ds.heap import HeapNode + + +class TestHeapNode(unittest.TestCase): + def test_creation_argument_None(self): + self.assertRaises(ValueError, HeapNode, None) + + def test_after_default_creation_same_key_value(self): + n = HeapNode(26) + self.assertIsNotNone(n.value) + self.assertEqual(n.key, n.value) + + def test_creation_with_custom_value(self): + n = HeapNode(2, "two") + self.assertIsNotNone(n.value) + self.assertNotEqual(n.value, n.key) + + def test_heap_node_equal_to_itself(self): + h = HeapNode(3, "three") + self.assertEqual(h, h) + + def test_not_equal_when_different_values_but_same_keys(self): + a = HeapNode("3", 3) + b = HeapNode("3", "three") + self.assertNotEqual(a, b) + + def test_not_equal_when_different_keys_but_same_values(self): + a = HeapNode("1", "x") + b = HeapNode("3", "x") + self.assertNotEqual(a, b) + + def test_not_equal_when_different_keys_and_values(self): + a = HeapNode(2, "x") + b = HeapNode(3, "y") + self.assertNotEqual(a, b) + + def test_equal_when_same_key_and_value(self): + a = HeapNode(2, "two") + b = HeapNode(2, "two") + self.assertEqual(a, b) + + def test_less_than_when_key_is_smaller_and_values_equal_to_keys(self): + a = HeapNode(12) + b = HeapNode(14) + self.assertLess(a, b) + + def test_greater_than_when_key_is_greater_and_values_equal_to_keys(self): + a = HeapNode(12) + b = HeapNode(14) + self.assertGreater(b, a) + + def test_less_than_only_key_is_used(self): + a = HeapNode(3, "three") + b = HeapNode(5, "zero") + self.assertLess(a, b) + + def test_greater_than_only_key_is_used(self): + a = HeapNode(3, 100) + b = HeapNode(4, "zero") + self.assertGreater(b, a) + + def test_heap_node_not_greater_than_itself(self): + a = HeapNode(11) + b = HeapNode(11, "eleven") + self.assertFalse(a > a) + self.assertFalse(b > b) + + def test_heap_node_not_smaller_than_itself(self): + a = HeapNode(11) + b = HeapNode(13, "thirteen") + self.assertFalse(a < a) + self.assertFalse(b < b) + + def test_heap_node_is_less_than_or_equal_to_itself(self): + a = HeapNode(2, "two") + self.assertTrue(a <= a) + self.assertLessEqual(a, a) + + def test_heap_node_is_greater_than_or_equal_to_itself(self): + a = HeapNode(2, "two") + self.assertTrue(a >= a) + self.assertGreaterEqual(a, a) + + def test_greater_than_or_equal_keys_equal_to_values(self): + a = HeapNode(13) + b = HeapNode(17) + self.assertGreaterEqual(b, a) + + def test_less_than_or_equal_keys_equal_to_values(self): + a = HeapNode(13) + b = HeapNode(17) + self.assertLessEqual(a, b) + + def test_greater_than_or_equal_keys_not_equal_to_values(self): + a = HeapNode(13, "13") + b = HeapNode(17, "seventeen") + self.assertGreaterEqual(b, a) + + def test_less_than_or_equal_keys_not_equal_to_values(self): + a = HeapNode(13, "one three") + b = HeapNode(17, "17") + self.assertLessEqual(a, b) diff --git a/tests/ds/test_MaxHeap.py b/tests/ds/test_MaxHeap.py index 969de632..45297536 100755 --- a/tests/ds/test_MaxHeap.py +++ b/tests/ds/test_MaxHeap.py @@ -2,11 +2,13 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info -Creation: 17/02/16 +Author: Nelson Brochado +Created: 17/02/2016 +Updated: 05/02/2017 -Last update: 15/10/16 +# Description Tests for the MaxHeap class. """ @@ -522,7 +524,3 @@ def test_is_on_odd_level(self): self.assertFalse(h.is_on_odd_level(6)) self.assertTrue(h.is_on_odd_level(7)) self.assertTrue(h.is_on_odd_level(8)) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/ds/test_MinHeap.py b/tests/ds/test_MinHeap.py index 8aeb8c2c..e3c452ac 100755 --- a/tests/ds/test_MinHeap.py +++ b/tests/ds/test_MinHeap.py @@ -2,11 +2,13 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info -Creation: 14/02/16 +Author: Nelson Brochado +Created: 14/02/2016 +Updated: 05/02/2017 -Last update: 01/01/17 +# Description Tests for the MinHeap class. """ @@ -580,7 +582,3 @@ def test_is_on_odd_level(self): self.assertFalse(h.is_on_odd_level(6)) self.assertTrue(h.is_on_odd_level(7)) self.assertTrue(h.is_on_odd_level(8)) - - -if __name__ == "__main__": - unittest.main(verbosity=2) diff --git a/tests/ds/test_MinMaxHeap.py b/tests/ds/test_MinMaxHeap.py index 71ed1ad4..8a6e369c 100755 --- a/tests/ds/test_MinMaxHeap.py +++ b/tests/ds/test_MinMaxHeap.py @@ -2,11 +2,13 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info -Creation: 20/02/16 +Author: Nelson Brochado +Created: 20/02/2016 +Updated: 05/02/2017 -Last Update: 08/10/16 +# Description Tests for the MinMaxHeap class. """ diff --git a/tests/ds/test_Queue.py b/tests/ds/test_Queue.py index 6f8cdf43..16d9b19c 100644 --- a/tests/ds/test_Queue.py +++ b/tests/ds/test_Queue.py @@ -3,8 +3,14 @@ """ +# Meta info + Author: Nelson Brochado Created: 24/01/2017 + +# Description + +Tests for the Queue class. """ import unittest diff --git a/tests/ds/test_RBT.py b/tests/ds/test_RBT.py index 36484a3c..b01911e8 100755 --- a/tests/ds/test_RBT.py +++ b/tests/ds/test_RBT.py @@ -2,13 +2,16 @@ # -*- coding: utf-8 -*- """ -Author: Nelson Brochado +# Meta info -Creation: 15/02/16 +Author: Nelson Brochado +Created: 15/02/2016 +Updated: 08/10/2017 -Last update: 08/10/16 +# Description Tests for the RBT class. + """ import unittest @@ -435,7 +438,3 @@ def test_remove_min(self): _m = rbt.remove_min() self.assertEqual(m, _m) self.assertIsNone(rbt.search(6)) - - -if __name__ == "__main__": - unittest.main(verbosity=2) From 1f9445b09c75b9ae3d906af71b790e8dacd7f938 Mon Sep 17 00:00:00 2001 From: nelson-brochado Date: Sun, 5 Feb 2017 03:16:44 +0100 Subject: [PATCH 2/3] Forgot to remove docs --- automate.sh | 2 +- docs/ands/algorithms/crypto/caesar.m.html | 1222 ----- docs/ands/algorithms/crypto/index.html | 1049 ---- .../algorithms/crypto/one_time_pad.m.html | 1096 ---- docs/ands/algorithms/dac/binary_search.m.html | 1225 ----- .../algorithms/dac/find_max_and_min.m.html | 1146 ----- docs/ands/algorithms/dac/find_peak.m.html | 1164 ----- docs/ands/algorithms/dac/index.html | 1074 ---- docs/ands/algorithms/dac/select.m.html | 1151 ----- docs/ands/algorithms/dp/change_making.m.html | 1349 ----- docs/ands/algorithms/dp/edit_distance.m.html | 1485 ------ docs/ands/algorithms/dp/fibonacci.m.html | 1238 ----- docs/ands/algorithms/dp/index.html | 1186 ----- .../dp/longest_common_subsequence.m.html | 1712 ------- .../dp/longest_common_substring.m.html | 1110 ---- .../dp/longest_increasing_subsequence.m.html | 1254 ----- .../dp/max_non_adjacent_seq_weight.m.html | 1122 ---- .../dp/max_sum_contiguous_subsequence.m.html | 1417 ------ docs/ands/algorithms/dp/plus_sign_game.m.html | 1227 ----- docs/ands/algorithms/dp/rod_cut.m.html | 1580 ------ docs/ands/algorithms/dp/subset_sum.m.html | 1246 ----- .../algorithms/dp/zero_one_knapsack.m.html | 1424 ------ .../graphs/best_team_of_three.m.html | 1261 ----- .../graphs/build_shortest_path.m.html | 1134 ----- docs/ands/algorithms/graphs/dfs.m.html | 1456 ------ .../algorithms/graphs/find_triangle.m.html | 1266 ----- docs/ands/algorithms/graphs/four_cycle.m.html | 1213 ----- docs/ands/algorithms/graphs/index.html | 1134 ----- docs/ands/algorithms/graphs/prim.m.html | 1361 ----- docs/ands/algorithms/graphs/top_sort.m.html | 1638 ------ .../top_three_friends_of_friends.m.html | 1235 ----- .../greedy/activity_selection.m.html | 1261 ----- .../greedy/fractional_knapsack.m.html | 1250 ----- docs/ands/algorithms/greedy/huffman.m.html | 1427 ------ docs/ands/algorithms/greedy/index.html | 1064 ---- docs/ands/algorithms/index.html | 1103 ---- .../algorithms/math/arithmetic/index.html | 1035 ---- .../algorithms/math/arithmetic/sum.m.html | 1176 ----- .../algorithms/math/combinatorics/index.html | 1041 ---- .../math/combinatorics/n_choose_k.m.html | 1238 ----- docs/ands/algorithms/math/index.html | 1040 ---- docs/ands/algorithms/parsing/index.html | 1036 ---- docs/ands/algorithms/parsing/smep.m.html | 1469 ------ docs/ands/algorithms/primes/index.html | 1035 ---- docs/ands/algorithms/primes/is_prime.m.html | 1269 ----- .../algorithms/recursion/ackermann.m.html | 1119 ---- docs/ands/algorithms/recursion/count.m.html | 1103 ---- .../algorithms/recursion/factorial.m.html | 1310 ----- docs/ands/algorithms/recursion/hanoi.m.html | 1197 ----- docs/ands/algorithms/recursion/index.html | 1167 ----- .../algorithms/recursion/is_sorted.m.html | 1211 ----- .../algorithms/recursion/make_decimal.m.html | 1178 ----- .../algorithms/recursion/palindrome.m.html | 1115 ---- docs/ands/algorithms/recursion/power.m.html | 1101 ---- docs/ands/algorithms/recursion/reverse.m.html | 1100 ---- .../algorithms/sorting/bubble_sort.m.html | 1098 ---- docs/ands/algorithms/sorting/heap_sort.m.html | 1168 ----- docs/ands/algorithms/sorting/index.html | 1137 ----- .../algorithms/sorting/insertion_sort.m.html | 1102 ---- .../ands/algorithms/sorting/merge_sort.m.html | 1258 ----- .../ands/algorithms/sorting/quick_sort.m.html | 1228 ----- .../algorithms/sorting/selection_sort.m.html | 1102 ---- docs/ands/algorithms/unclassified/index.html | 1033 ---- .../unclassified/max_num_dups.m.html | 1110 ---- docs/ands/ds/BST.m.html | 4518 ----------------- docs/ands/ds/DSForests.m.html | 1945 ------- docs/ands/ds/Graph.m.html | 2766 ---------- docs/ands/ds/HashTable.m.html | 1805 ------- docs/ands/ds/MaxHeap.m.html | 2324 --------- docs/ands/ds/MinHeap.m.html | 2374 --------- docs/ands/ds/MinMaxHeap.m.html | 3013 ----------- docs/ands/ds/MinPriorityQueue.m.html | 2360 --------- docs/ands/ds/Queue.m.html | 1349 ----- docs/ands/ds/RBT.m.html | 3715 -------------- docs/ands/ds/Stack.m.html | 1439 ------ docs/ands/ds/TST.m.html | 3002 ----------- docs/ands/ds/heap.m.html | 2786 ---------- docs/ands/ds/index.html | 1235 ----- docs/ands/index.html | 1045 ---- 79 files changed, 1 insertion(+), 112133 deletions(-) delete mode 100644 docs/ands/algorithms/crypto/caesar.m.html delete mode 100644 docs/ands/algorithms/crypto/index.html delete mode 100644 docs/ands/algorithms/crypto/one_time_pad.m.html delete mode 100644 docs/ands/algorithms/dac/binary_search.m.html delete mode 100644 docs/ands/algorithms/dac/find_max_and_min.m.html delete mode 100644 docs/ands/algorithms/dac/find_peak.m.html delete mode 100644 docs/ands/algorithms/dac/index.html delete mode 100644 docs/ands/algorithms/dac/select.m.html delete mode 100644 docs/ands/algorithms/dp/change_making.m.html delete mode 100644 docs/ands/algorithms/dp/edit_distance.m.html delete mode 100644 docs/ands/algorithms/dp/fibonacci.m.html delete mode 100644 docs/ands/algorithms/dp/index.html delete mode 100644 docs/ands/algorithms/dp/longest_common_subsequence.m.html delete mode 100644 docs/ands/algorithms/dp/longest_common_substring.m.html delete mode 100644 docs/ands/algorithms/dp/longest_increasing_subsequence.m.html delete mode 100644 docs/ands/algorithms/dp/max_non_adjacent_seq_weight.m.html delete mode 100644 docs/ands/algorithms/dp/max_sum_contiguous_subsequence.m.html delete mode 100644 docs/ands/algorithms/dp/plus_sign_game.m.html delete mode 100644 docs/ands/algorithms/dp/rod_cut.m.html delete mode 100644 docs/ands/algorithms/dp/subset_sum.m.html delete mode 100644 docs/ands/algorithms/dp/zero_one_knapsack.m.html delete mode 100644 docs/ands/algorithms/graphs/best_team_of_three.m.html delete mode 100644 docs/ands/algorithms/graphs/build_shortest_path.m.html delete mode 100644 docs/ands/algorithms/graphs/dfs.m.html delete mode 100644 docs/ands/algorithms/graphs/find_triangle.m.html delete mode 100644 docs/ands/algorithms/graphs/four_cycle.m.html delete mode 100644 docs/ands/algorithms/graphs/index.html delete mode 100644 docs/ands/algorithms/graphs/prim.m.html delete mode 100644 docs/ands/algorithms/graphs/top_sort.m.html delete mode 100644 docs/ands/algorithms/graphs/top_three_friends_of_friends.m.html delete mode 100644 docs/ands/algorithms/greedy/activity_selection.m.html delete mode 100644 docs/ands/algorithms/greedy/fractional_knapsack.m.html delete mode 100644 docs/ands/algorithms/greedy/huffman.m.html delete mode 100644 docs/ands/algorithms/greedy/index.html delete mode 100644 docs/ands/algorithms/index.html delete mode 100644 docs/ands/algorithms/math/arithmetic/index.html delete mode 100644 docs/ands/algorithms/math/arithmetic/sum.m.html delete mode 100644 docs/ands/algorithms/math/combinatorics/index.html delete mode 100644 docs/ands/algorithms/math/combinatorics/n_choose_k.m.html delete mode 100644 docs/ands/algorithms/math/index.html delete mode 100644 docs/ands/algorithms/parsing/index.html delete mode 100644 docs/ands/algorithms/parsing/smep.m.html delete mode 100644 docs/ands/algorithms/primes/index.html delete mode 100644 docs/ands/algorithms/primes/is_prime.m.html delete mode 100644 docs/ands/algorithms/recursion/ackermann.m.html delete mode 100644 docs/ands/algorithms/recursion/count.m.html delete mode 100644 docs/ands/algorithms/recursion/factorial.m.html delete mode 100644 docs/ands/algorithms/recursion/hanoi.m.html delete mode 100644 docs/ands/algorithms/recursion/index.html delete mode 100644 docs/ands/algorithms/recursion/is_sorted.m.html delete mode 100644 docs/ands/algorithms/recursion/make_decimal.m.html delete mode 100644 docs/ands/algorithms/recursion/palindrome.m.html delete mode 100644 docs/ands/algorithms/recursion/power.m.html delete mode 100644 docs/ands/algorithms/recursion/reverse.m.html delete mode 100644 docs/ands/algorithms/sorting/bubble_sort.m.html delete mode 100644 docs/ands/algorithms/sorting/heap_sort.m.html delete mode 100644 docs/ands/algorithms/sorting/index.html delete mode 100644 docs/ands/algorithms/sorting/insertion_sort.m.html delete mode 100644 docs/ands/algorithms/sorting/merge_sort.m.html delete mode 100644 docs/ands/algorithms/sorting/quick_sort.m.html delete mode 100644 docs/ands/algorithms/sorting/selection_sort.m.html delete mode 100644 docs/ands/algorithms/unclassified/index.html delete mode 100644 docs/ands/algorithms/unclassified/max_num_dups.m.html delete mode 100644 docs/ands/ds/BST.m.html delete mode 100644 docs/ands/ds/DSForests.m.html delete mode 100644 docs/ands/ds/Graph.m.html delete mode 100644 docs/ands/ds/HashTable.m.html delete mode 100644 docs/ands/ds/MaxHeap.m.html delete mode 100644 docs/ands/ds/MinHeap.m.html delete mode 100644 docs/ands/ds/MinMaxHeap.m.html delete mode 100644 docs/ands/ds/MinPriorityQueue.m.html delete mode 100644 docs/ands/ds/Queue.m.html delete mode 100644 docs/ands/ds/RBT.m.html delete mode 100644 docs/ands/ds/Stack.m.html delete mode 100644 docs/ands/ds/TST.m.html delete mode 100644 docs/ands/ds/heap.m.html delete mode 100644 docs/ands/ds/index.html delete mode 100644 docs/ands/index.html diff --git a/automate.sh b/automate.sh index 69df68b8..d318fc7b 100755 --- a/automate.sh +++ b/automate.sh @@ -115,7 +115,7 @@ test_in_virtual_environment() run_tests fi - new_docs + #new_docs deactivate printf "${YELLOW}Exited from virtual environment.${NORMAL}\n\n" diff --git a/docs/ands/algorithms/crypto/caesar.m.html b/docs/ands/algorithms/crypto/caesar.m.html deleted file mode 100644 index 88b1d813..00000000 --- a/docs/ands/algorithms/crypto/caesar.m.html +++ /dev/null @@ -1,1222 +0,0 @@ - - - - - - ands.algorithms.crypto.caesar API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.crypto.caesar module

-

Author: Nelson Brochado

-

Caesar cipher that accepts messages of characters -whose value returned by the ord function is between 0 and 2**16 - 1. -Caeser cipher is far from being a good cryptographic algorithm, -so, in general, you should prefer other algorithms.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Caesar cipher that accepts messages of characters
-whose value returned by the `ord` function is between 0 and 2**16 - 1.
-Caeser cipher is far from being a good cryptographic algorithm,
-so, in general, you should prefer other algorithms.
-"""
-
-import random
-
-# Using `sys.maxunicode` causes some problems with encondings
-# i.e. an error is thrown because Tcl/Tk do not support certain characters
-MAX = 2 ** 16 - 1
-
-
-def move_char(c, k):
-    return chr(ord(c) + k)
-
-
-def encrypt(m, k):
-    return "".join(move_char(c, k) for c in m)
-
-
-def decrypt(cipher, k):
-    return "".join(move_char(c, -k) for c in cipher)
-
-
-# Example of polyalphabetic encryption
-
-def multi_encrypt(m, keys):
-    """Given a message m and a set of keys,
-    it encrypts each symbol of m with a random key from keys.
-    The random pattern is the second item of the tuple returned."""
-    pattern = []
-    cipher = []
-
-    for c in m:
-        k = random.choice(keys)
-        pattern.append(k)
-        cipher.append(move_char(c, k))
-
-    return "".join(cipher), pattern
-
-
-def multi_decrypt(cipher, pattern):
-    """`len(pattern) == len(keys)`,
-    where `keys` are the keys passed to `multi_encrypt`."""
-    return "".join(move_char(cipher[i], -k) for i, k in enumerate(pattern))
-
-
- -
- -
-

Module variables

-
-

var MAX

- - -
-
- -
- -

Functions

- -
-
-

def decrypt(

cipher, k)

-
- - - - -
- -
-
def decrypt(cipher, k):
-    return "".join(move_char(c, -k) for c in cipher)
-
-
-
- -
- - -
-
-

def encrypt(

m, k)

-
- - - - -
- -
-
def encrypt(m, k):
-    return "".join(move_char(c, k) for c in m)
-
-
-
- -
- - -
-
-

def move_char(

c, k)

-
- - - - -
- -
-
def move_char(c, k):
-    return chr(ord(c) + k)
-
-
-
- -
- - -
-
-

def multi_decrypt(

cipher, pattern)

-
- - - - -

len(pattern) == len(keys), -where keys are the keys passed to multi_encrypt.

-
- -
-
def multi_decrypt(cipher, pattern):
-    """`len(pattern) == len(keys)`,
-    where `keys` are the keys passed to `multi_encrypt`."""
-    return "".join(move_char(cipher[i], -k) for i, k in enumerate(pattern))
-
-
-
- -
- - -
-
-

def multi_encrypt(

m, keys)

-
- - - - -

Given a message m and a set of keys, -it encrypts each symbol of m with a random key from keys. -The random pattern is the second item of the tuple returned.

-
- -
-
def multi_encrypt(m, keys):
-    """Given a message m and a set of keys,
-    it encrypts each symbol of m with a random key from keys.
-    The random pattern is the second item of the tuple returned."""
-    pattern = []
-    cipher = []
-
-    for c in m:
-        k = random.choice(keys)
-        pattern.append(k)
-        cipher.append(move_char(c, k))
-
-    return "".join(cipher), pattern
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/crypto/index.html b/docs/ands/algorithms/crypto/index.html deleted file mode 100644 index 26eede58..00000000 --- a/docs/ands/algorithms/crypto/index.html +++ /dev/null @@ -1,1049 +0,0 @@ - - - - - - ands.algorithms.crypto API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.crypto module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms.crypto.caesar

- - -

Author: Nelson Brochado

-

Caesar cipher that accepts messages of characters -whose value returned by the ord function is between 0 and 2**16 - 1. -Caeser cipher is far from being a good cryptographic algorithm, -so, in general, you should prefer other algorithms.

- -
-
-

ands.algorithms.crypto.one_time_pad

- - -

Author: Nelson Brochado

-

One Time Pad cipher algorithm, which provides 'perfect secrecy', -but has some drawbacks, for example the key used -must be at least of the same length of the original message.

- -
-
- -
-
- -
- - diff --git a/docs/ands/algorithms/crypto/one_time_pad.m.html b/docs/ands/algorithms/crypto/one_time_pad.m.html deleted file mode 100644 index b6d70e5a..00000000 --- a/docs/ands/algorithms/crypto/one_time_pad.m.html +++ /dev/null @@ -1,1096 +0,0 @@ - - - - - - ands.algorithms.crypto.one_time_pad API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.crypto.one_time_pad module

-

Author: Nelson Brochado

-

One Time Pad cipher algorithm, which provides 'perfect secrecy', -but has some drawbacks, for example the key used -must be at least of the same length of the original message.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-One Time Pad cipher algorithm, which provides 'perfect secrecy',
-but has some drawbacks, for example the key used
-must be at least of the same length of the original message.
-"""
-
-
-def encrypt(message, key):
-    """Encrypt message using key according to the one-time-pad algorithm."""
-    return "".join(chr(ord(i) ^ ord(j)) for (i, j) in zip(message, key))
-
-
-def decrypt(ciphertext, key):
-    """Decript ciphertext using key according to the OTP algorithm."""
-    return encrypt(ciphertext, key)
-
-
- -
- -
- -

Functions

- -
-
-

def decrypt(

ciphertext, key)

-
- - - - -

Decript ciphertext using key according to the OTP algorithm.

-
- -
-
def decrypt(ciphertext, key):
-    """Decript ciphertext using key according to the OTP algorithm."""
-    return encrypt(ciphertext, key)
-
-
-
- -
- - -
-
-

def encrypt(

message, key)

-
- - - - -

Encrypt message using key according to the one-time-pad algorithm.

-
- -
-
def encrypt(message, key):
-    """Encrypt message using key according to the one-time-pad algorithm."""
-    return "".join(chr(ord(i) ^ ord(j)) for (i, j) in zip(message, key))
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dac/binary_search.m.html b/docs/ands/algorithms/dac/binary_search.m.html deleted file mode 100644 index bbc0da09..00000000 --- a/docs/ands/algorithms/dac/binary_search.m.html +++ /dev/null @@ -1,1225 +0,0 @@ - - - - - - ands.algorithms.dac.binary_search API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dac.binary_search module

-

Author: Nelson Brochado

-

Binary search is a "divide and conquer" search algorithm -that operates on a sorted list.

-

If you want to know more about binary search:

-
    -
  • http://en.wikipedia.org/wiki/Binary_search_algorithm
  • -
  • http://interactivepython.org/runestone/static/pythonds/SortSearch/TheBinarySearch.html
  • -
-

Note that no serious tests have been made on this algorihm.

- - - - -
- -
- -

Functions

- -
-
-

def binary_search_i(

ls, value)

-
- - - - -

Iterative binary search algorithm.

-

Time complexity: O(n*log_2(n))

-
- -
-
def binary_search_i(ls, value):
-    """Iterative binary search algorithm.
-
-    Time complexity: O(n*log_2(n))
-    """
-    if len(ls) == 0:
-        return False
-    else:
-        start = 0
-        end = len(ls) - 1
-        while start <= end:
-            mid = (start + end) // 2
-
-            if ls[mid] == value:
-                return True
-
-            elif ls[mid] < value:  # search on the right
-                start = mid + 1
-
-            else:  # search on the left
-                end = mid - 1
-        return False
-
-
-
- -
- - -
-
-

def binary_search_r(

ls, value)

-
- - - - -

Recursive binary search.

-

Note that this algorithm uses the slice operator, -which creates a sub-lists. -slides is an operation that runs in O(k) time... -To repair this, we can pass the indices, -instead of creating a new sub-list using the slice operator

-
- -
-
def binary_search_r(ls, value):
-    """Recursive binary search.
-
-    Note that this algorithm uses the slice operator,
-    which creates a sub-lists.
-    slides is an operation that runs in O(k) time...
-    To repair this, we can pass the indices,
-    instead of creating a new sub-list using the slice operator"""
-    if len(ls) == 0:  # basis
-        return False
-    else:
-        mid = len(ls) // 2
-        if ls[mid] == value:
-            return True
-        elif ls[mid] < value:
-            return binary_search_r(ls[mid + 1:], value)
-        else:
-            return binary_search_r(ls[0:mid], value)
-
-
-
- -
- - -
- - - - - -

Searches for item in ls

-

Time complexity: O(n), -where n is the size of ls.

-
- - -
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dac/find_max_and_min.m.html b/docs/ands/algorithms/dac/find_max_and_min.m.html deleted file mode 100644 index 8aebb777..00000000 --- a/docs/ands/algorithms/dac/find_max_and_min.m.html +++ /dev/null @@ -1,1146 +0,0 @@ - - - - - - ands.algorithms.dac.find_max_and_min API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dac.find_max_and_min module

-

Author: Nelson Brochado

-

Finding the minimum and the maximum -of a list of numbers using the "Divide and Conquer" strategy.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Finding the minimum and the maximum
-of a list of numbers using the "Divide and Conquer" strategy.
-"""
-
-
-def find_max(ls):
-    """Divide and Conquer not in-place algorithm
-    to find the maximum element of a list or tuple"""
-
-    if len(ls) == 1:
-        return ls[0]
-    elif len(ls) == 2:
-        return ls[0] if ls[0] > ls[1] else ls[1]
-    else:
-        mid = len(ls) // 2
-        m1 = find_max(ls[0:mid])
-        m2 = find_max(ls[mid:])
-        return m1 if m1 > m2 else m2
-
-
-def find_min(ls):
-    """'Divide and Conquer' not in-place algorithm
-    to find the minimum element of a list or tuple."""
-
-    if len(ls) == 1:
-        return ls[0]
-    elif len(ls) == 2:
-        return ls[0] if ls[0] < ls[1] else ls[1]
-    else:
-        mid = len(ls) // 2
-        m1 = find_min(ls[0:mid])
-        m2 = find_min(ls[mid:])
-        return m1 if m1 < m2 else m2
-
-
-if __name__ == "__main__":
-    from random import randint
-
-    a = [randint(0, 10) for _ in range(10)]
-    print("List:", a)
-
-    print("Min:", find_min(a))
-    print("Max:", find_max(a))
-
-
- -
- -
- -

Functions

- -
-
-

def find_max(

ls)

-
- - - - -

Divide and Conquer not in-place algorithm -to find the maximum element of a list or tuple

-
- -
-
def find_max(ls):
-    """Divide and Conquer not in-place algorithm
-    to find the maximum element of a list or tuple"""
-
-    if len(ls) == 1:
-        return ls[0]
-    elif len(ls) == 2:
-        return ls[0] if ls[0] > ls[1] else ls[1]
-    else:
-        mid = len(ls) // 2
-        m1 = find_max(ls[0:mid])
-        m2 = find_max(ls[mid:])
-        return m1 if m1 > m2 else m2
-
-
-
- -
- - -
-
-

def find_min(

ls)

-
- - - - -

'Divide and Conquer' not in-place algorithm -to find the minimum element of a list or tuple.

-
- -
-
def find_min(ls):
-    """'Divide and Conquer' not in-place algorithm
-    to find the minimum element of a list or tuple."""
-
-    if len(ls) == 1:
-        return ls[0]
-    elif len(ls) == 2:
-        return ls[0] if ls[0] < ls[1] else ls[1]
-    else:
-        mid = len(ls) // 2
-        m1 = find_min(ls[0:mid])
-        m2 = find_min(ls[mid:])
-        return m1 if m1 < m2 else m2
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dac/find_peak.m.html b/docs/ands/algorithms/dac/find_peak.m.html deleted file mode 100644 index 5d91b6c2..00000000 --- a/docs/ands/algorithms/dac/find_peak.m.html +++ /dev/null @@ -1,1164 +0,0 @@ - - - - - - ands.algorithms.dac.find_peak API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dac.find_peak module

-

Author: Nelson Brochado

-

The two algorithms to find the peak below -can return different correct answers, -because they operate differently.

-

Resources: -- https://www.youtube.com/watch?v=HtSuA80QTyo&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb&spfreload=10

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-The two algorithms to find the peak below
-can return different correct answers,
-because they operate differently.
-
-Resources:
-- https://www.youtube.com/watch?v=HtSuA80QTyo&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb&spfreload=10
-"""
-
-
-def find_peak_brute_force(ls):
-    """Finds the first peak in ls.
-    If there's no peak, None is returned.
-
-    A peak ls[i] satisfies the following condition:
-    ls[i - 1] <= ls[i] >= ls[i + 1]
-    for i=1...len(ls) - 2.
-    In other words, A[i] is a peak
-    if it is not smaller than its neighbors,
-
-    Time complexity: O(n),
-    where n is the size of ls."""
-    for i in range(1, len(ls) - 1):
-        if ls[i - 1] <= ls[i] >= ls[i + 1]:
-            return i
-
-
-def _find_peak_aux(ls, i, j):
-    """Using Divide-and-Conquer paradigm."""
-    m = (i + j) // 2
-
-    if 0 < m < len(ls) - 1:
-
-        if ls[m - 1] <= ls[m] >= ls[m + 1]:
-            return m
-
-        elif ls[m - 1] > ls[m]:
-            return _find_peak_aux(ls, i, m - 1)
-
-        elif ls[m] < ls[m + 1]:
-            return _find_peak_aux(ls, m + 1, j)
-    else:
-        return -1
-
-
-def find_peak(ls):
-    """Returns the index of a peak in ls.
-    If there's no peak, -1 is returned."""
-    return _find_peak_aux(ls, 0, len(ls) - 1)
-
-
-if __name__ == "__main__":
-    from random import randint
-
-    a = [randint(0, 10) for _ in range(10)]
-    print("List:", a)
-
-    print(find_peak_brute_force(a))
-    print(find_peak(a))
-
-
- -
- -
- -

Functions

- -
-
-

def find_peak(

ls)

-
- - - - -

Returns the index of a peak in ls. -If there's no peak, -1 is returned.

-
- -
-
def find_peak(ls):
-    """Returns the index of a peak in ls.
-    If there's no peak, -1 is returned."""
-    return _find_peak_aux(ls, 0, len(ls) - 1)
-
-
-
- -
- - -
-
-

def find_peak_brute_force(

ls)

-
- - - - -

Finds the first peak in ls. -If there's no peak, None is returned.

-

A peak ls[i] satisfies the following condition: -ls[i - 1] <= ls[i] >= ls[i + 1] -for i=1...len(ls) - 2. -In other words, A[i] is a peak -if it is not smaller than its neighbors,

-

Time complexity: O(n), -where n is the size of ls.

-
- -
-
def find_peak_brute_force(ls):
-    """Finds the first peak in ls.
-    If there's no peak, None is returned.
-
-    A peak ls[i] satisfies the following condition:
-    ls[i - 1] <= ls[i] >= ls[i + 1]
-    for i=1...len(ls) - 2.
-    In other words, A[i] is a peak
-    if it is not smaller than its neighbors,
-
-    Time complexity: O(n),
-    where n is the size of ls."""
-    for i in range(1, len(ls) - 1):
-        if ls[i - 1] <= ls[i] >= ls[i + 1]:
-            return i
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dac/index.html b/docs/ands/algorithms/dac/index.html deleted file mode 100644 index a17f76b5..00000000 --- a/docs/ands/algorithms/dac/index.html +++ /dev/null @@ -1,1074 +0,0 @@ - - - - - - ands.algorithms.dac API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dac module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms.dac.binary_search

- - -

Author: Nelson Brochado

-

Binary search is a "divide and conquer" search algorithm -that operates on a sorted list.

-

If you want to know more about binary search:

-
    -
  • http://en.wikipedia.org/wiki/Binary_search_algorithm
  • -
  • http://interactivepython.org/runestone/static/pythonds/SortSearch/TheBinarySearch...
  • -
- -
-
-

ands.algorithms.dac.find_max_and_min

- - -

Author: Nelson Brochado

-

Finding the minimum and the maximum -of a list of numbers using the "Divide and Conquer" strategy.

- -
-
-

ands.algorithms.dac.find_peak

- - -

Author: Nelson Brochado

-

The two algorithms to find the peak below -can return different correct answers, -because they operate differently.

-

Resources: -- https://www.youtube.com/watch?v=HtSuA80QTyo&list=PLUl4u3cNGP61Oq3tWYp6V_F-5jb5L2iHb&spfreload=10

- -
-
-

ands.algorithms.dac.select

- - -

Author: Nelson Brochado

-

Find an element x in a list, -such that at most k elements of the list are less than x.

- -
-
- -
-
- -
- - diff --git a/docs/ands/algorithms/dac/select.m.html b/docs/ands/algorithms/dac/select.m.html deleted file mode 100644 index b384311b..00000000 --- a/docs/ands/algorithms/dac/select.m.html +++ /dev/null @@ -1,1151 +0,0 @@ - - - - - - ands.algorithms.dac.select API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dac.select module

-

Author: Nelson Brochado

-

Find an element x in a list, -such that at most k elements of the list are less than x.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Find an element x in a list,
-such that at most k elements of the list are less than x.
-"""
-
-
-def partition(ls: list, start: int, end: int):
-    """Partition algorithm used also by the quick-sort algorithm.
-    It basically returns the index i of a number in ls,
-    such that all elements on the left of ls[i] are less than ls[i],
-    and all elements on the right of ls[i] are greater than ls[i].
-    """
-    pivot = ls[end]
-    p = start
-
-    for i in range(start, end):
-        if ls[i] <= pivot:
-            ls[p], ls[i] = ls[i], ls[p]
-            p += 1
-
-    ls[p], ls[end] = ls[end], ls[p]
-    return p
-
-
-def select(ls: list, k: int):
-    """Find an element x in ls,
-    such that at most k elements of ls are less than x.
-    """
-    p = partition(ls, 0, len(ls) - 1)  # p := pivot's index
-
-    if p == k:
-        return ls[p]
-    elif p > k:
-        return select(ls[0:p], k)
-    else:  # p < k
-        return select(ls[p + 1:], k - p - 1)
-
-
-if __name__ == "__main__":
-    from random import randint
-
-    a = [randint(0, 10) for _ in range(10)]
-    print("List:", a)
-
-    print("Selected:", select(a, 4))
-
-
- -
- -
- -

Functions

- -
-
-

def partition(

ls, start, end)

-
- - - - -

Partition algorithm used also by the quick-sort algorithm. -It basically returns the index i of a number in ls, -such that all elements on the left of ls[i] are less than ls[i], -and all elements on the right of ls[i] are greater than ls[i].

-
- -
-
def partition(ls: list, start: int, end: int):
-    """Partition algorithm used also by the quick-sort algorithm.
-    It basically returns the index i of a number in ls,
-    such that all elements on the left of ls[i] are less than ls[i],
-    and all elements on the right of ls[i] are greater than ls[i].
-    """
-    pivot = ls[end]
-    p = start
-
-    for i in range(start, end):
-        if ls[i] <= pivot:
-            ls[p], ls[i] = ls[i], ls[p]
-            p += 1
-
-    ls[p], ls[end] = ls[end], ls[p]
-    return p
-
-
-
- -
- - -
-
-

def select(

ls, k)

-
- - - - -

Find an element x in ls, -such that at most k elements of ls are less than x.

-
- -
-
def select(ls: list, k: int):
-    """Find an element x in ls,
-    such that at most k elements of ls are less than x.
-    """
-    p = partition(ls, 0, len(ls) - 1)  # p := pivot's index
-
-    if p == k:
-        return ls[p]
-    elif p > k:
-        return select(ls[0:p], k)
-    else:  # p < k
-        return select(ls[p + 1:], k - p - 1)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/change_making.m.html b/docs/ands/algorithms/dp/change_making.m.html deleted file mode 100644 index 81c8858e..00000000 --- a/docs/ands/algorithms/dp/change_making.m.html +++ /dev/null @@ -1,1349 +0,0 @@ - - - - - - ands.algorithms.dp.change_making API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.change_making module

-

Author: Nelson Brochado

-

Problem (https://en.wikipedia.org/wiki/Change-making_problem): -Given a set of coins, which is the smallest subset of these coins, -such that summed together yields a certain number n.

-

This problem is similar to the integer knapsack problem, -the different is that here values=weights.

-

This problem can be solved using dynamic programming.

-

Proof that it exhibits optimal substructure.

-

Suppose S is the optimal solution for making n cents. -Then S' = S - c, where c is a coin in the optimal solution S, -is an optimal solution for making n - c cents. -Suppose S' is not the optimal solution for making n - c cents, -then there exists an optimal solution X != S'. -Now, if we add c to X, we obtain an optimal solution for making n cents, -but this contradicts that fact that S is the optimal solution.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Problem (https://en.wikipedia.org/wiki/Change-making_problem):
-Given a set of coins, which is the smallest subset of these coins,
-such that summed together yields a certain number n.
-
-This problem is similar to the integer knapsack problem,
-the different is that here values=weights.
-
-
-This problem can be solved using dynamic programming.
-
-Proof that it exhibits optimal substructure.
-
-Suppose S is the optimal solution for making n cents.
-Then S' = S - c, where c is a coin in the optimal solution S,
-is an optimal solution for making n - c cents.
-Suppose S' is not the optimal solution for making n - c cents,
-then there exists an optimal solution X != S'.
-Now, if we add c to X, we obtain an optimal solution for making n cents,
-but this contradicts that fact that S is the optimal solution.
-"""
-
-from pprint import pprint
-
-
-def _get_change_making_matrix(set_of_coins, r):
-    m = [[0 for _ in range(r + 1)] for _ in range(len(set_of_coins) + 1)]
-
-    for i in range(r + 1):
-        m[0][i] = i
-
-    return m
-
-
-def _get_sets_of_coins_matrix(set_of_coins, r):
-    m = [[[] for _ in range(r + 1)] for _ in range(len(set_of_coins) + 1)]
-
-    for i in range(r + 1):
-        for _ in range(i):
-            m[0][i].append(1)
-
-    for j in range(len(set_of_coins) + 1):
-        m[j][0] = []
-
-    return m
-
-
-def change_making(coins, n):
-    """This function assumes that all coins are available infinitely.
-
-    I will assume that all coins and n are integers.
-
-    n is number that we need to obtain
-    with the fewest number of coins in set of coins.
-
-    Running time complexity: O(number of coins * quantity to obtain)
-
-    :type coins : list of int | tuple of int
-    :type n : int
-    """
-    m = _get_change_making_matrix(coins, n)
-
-    for c in range(1, len(coins) + 1):
-
-        for r in range(1, n + 1):
-
-            if coins[c - 1] == r:
-                m[c][r] = 1
-
-            elif coins[c - 1] > r:
-                m[c][r] = m[c - 1][r]
-
-            else:
-                m[c][r] = min(m[c - 1][r], 1 + m[c][r - coins[c - 1]])
-
-    return m[-1][-1]
-
-
-def extended_change_making(coins, rest):
-    """Returns the smallest amount of coins
-    you need to use to obtain the quantity "rest".
-
-    This function is a super version of change_making.
-
-    Running time complexity: O(number of coins * quantity to obtain)
-
-    :type coins : list of int | tuple of int
-    :type rest : int
-    """
-    m = _get_change_making_matrix(coins, rest)
-
-    # Matrix used to keep track of which coins are used
-    p = _get_sets_of_coins_matrix(coins, rest)
-
-    for c in range(1, len(coins) + 1):
-
-        for r in range(1, rest + 1):
-
-            # Just use the coin coins[c - 1].
-            if coins[c - 1] == r:
-                m[c][r] = 1
-                p[c][r].append(coins[c - 1])
-
-            # coins[c - 1] cannot be included.
-            # We use the previous solution for for making r,
-            # excluding coins[c - 1].
-            elif coins[c - 1] > r:
-                m[c][r] = m[c - 1][r]
-                p[c][r] = p[c - 1][r]
-
-            # We can use coins[c - 1].
-            # We need to decide which one of the following solutions is the best:
-            # 1. Using the previous solution for making r (without using coins[c - 1]).
-            # 2. Using coins[c - 1] + the optimal solution for making r -
-            # coins[c - 1].
-            else:
-                if m[c - 1][r] < 1 + m[c][r - coins[c - 1]]:
-                    p[c][r] = p[c - 1][r]
-                    m[c][r] = m[c - 1][r]
-                else:
-                    p[c][r] = [coins[c - 1]] + p[c][r - coins[c - 1]]
-                    m[c][r] = 1 + m[c][r - coins[c - 1]]
-
-    return p[-1][-1]
-
-
-def recursive_change_making(coins, n, index):
-    # http://algorithms.tutorialhorizon.com/dynamic-programming-coin-change-problem/
-    if n < 0:
-        return 0
-
-    if n == 0:
-        return 1
-
-    if index == len(coins) and n > 0:
-        return 0
-
-    return recursive_change_making(
-        coins, n - coins[index], index) + recursive_change_making(coins, n, index + 1)
-
-
-if __name__ == "__main__":
-    pprint(extended_change_making((12, 25, 1, 5), 16))
-    pprint(extended_change_making((1, 5, 10, 21, 25), 63))
-    print(recursive_change_making((4, 2, 1), 5, 0))
-
-
- -
- -
- -

Functions

- -
-
-

def change_making(

coins, n)

-
- - - - -

This function assumes that all coins are available infinitely.

-

I will assume that all coins and n are integers.

-

n is number that we need to obtain -with the fewest number of coins in set of coins.

-

Running time complexity: O(number of coins * quantity to obtain)

-

:type coins : list of int | tuple of int -:type n : int

-
- -
-
def change_making(coins, n):
-    """This function assumes that all coins are available infinitely.
-
-    I will assume that all coins and n are integers.
-
-    n is number that we need to obtain
-    with the fewest number of coins in set of coins.
-
-    Running time complexity: O(number of coins * quantity to obtain)
-
-    :type coins : list of int | tuple of int
-    :type n : int
-    """
-    m = _get_change_making_matrix(coins, n)
-
-    for c in range(1, len(coins) + 1):
-
-        for r in range(1, n + 1):
-
-            if coins[c - 1] == r:
-                m[c][r] = 1
-
-            elif coins[c - 1] > r:
-                m[c][r] = m[c - 1][r]
-
-            else:
-                m[c][r] = min(m[c - 1][r], 1 + m[c][r - coins[c - 1]])
-
-    return m[-1][-1]
-
-
-
- -
- - -
-
-

def extended_change_making(

coins, rest)

-
- - - - -

Returns the smallest amount of coins -you need to use to obtain the quantity "rest".

-

This function is a super version of change_making.

-

Running time complexity: O(number of coins * quantity to obtain)

-

:type coins : list of int | tuple of int -:type rest : int

-
- -
-
def extended_change_making(coins, rest):
-    """Returns the smallest amount of coins
-    you need to use to obtain the quantity "rest".
-
-    This function is a super version of change_making.
-
-    Running time complexity: O(number of coins * quantity to obtain)
-
-    :type coins : list of int | tuple of int
-    :type rest : int
-    """
-    m = _get_change_making_matrix(coins, rest)
-
-    # Matrix used to keep track of which coins are used
-    p = _get_sets_of_coins_matrix(coins, rest)
-
-    for c in range(1, len(coins) + 1):
-
-        for r in range(1, rest + 1):
-
-            # Just use the coin coins[c - 1].
-            if coins[c - 1] == r:
-                m[c][r] = 1
-                p[c][r].append(coins[c - 1])
-
-            # coins[c - 1] cannot be included.
-            # We use the previous solution for for making r,
-            # excluding coins[c - 1].
-            elif coins[c - 1] > r:
-                m[c][r] = m[c - 1][r]
-                p[c][r] = p[c - 1][r]
-
-            # We can use coins[c - 1].
-            # We need to decide which one of the following solutions is the best:
-            # 1. Using the previous solution for making r (without using coins[c - 1]).
-            # 2. Using coins[c - 1] + the optimal solution for making r -
-            # coins[c - 1].
-            else:
-                if m[c - 1][r] < 1 + m[c][r - coins[c - 1]]:
-                    p[c][r] = p[c - 1][r]
-                    m[c][r] = m[c - 1][r]
-                else:
-                    p[c][r] = [coins[c - 1]] + p[c][r - coins[c - 1]]
-                    m[c][r] = 1 + m[c][r - coins[c - 1]]
-
-    return p[-1][-1]
-
-
-
- -
- - -
-
-

def recursive_change_making(

coins, n, index)

-
- - - - -
- -
-
def recursive_change_making(coins, n, index):
-    # http://algorithms.tutorialhorizon.com/dynamic-programming-coin-change-problem/
-    if n < 0:
-        return 0
-
-    if n == 0:
-        return 1
-
-    if index == len(coins) and n > 0:
-        return 0
-
-    return recursive_change_making(
-        coins, n - coins[index], index) + recursive_change_making(coins, n, index + 1)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/edit_distance.m.html b/docs/ands/algorithms/dp/edit_distance.m.html deleted file mode 100644 index e32addf7..00000000 --- a/docs/ands/algorithms/dp/edit_distance.m.html +++ /dev/null @@ -1,1485 +0,0 @@ - - - - - - ands.algorithms.dp.edit_distance API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.edit_distance module

-

Author: Nelson Brochado -Creation: 31/08/15

-

Calculate the edit distance between two strings.

-

Based on: -- https://github.com/dossan/interview/blob/master/src/com/interview/dynamic/EditDistance.java -- https://www.youtube.com/watch?v=We3YDTzNXEk

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 31/08/15
-
-Calculate the edit distance between two strings.
-
-Based on:
-- https://github.com/dossan/interview/blob/master/src/com/interview/dynamic/EditDistance.java
-- https://www.youtube.com/watch?v=We3YDTzNXEk
-"""
-
-from pprint import pprint
-
-
-def _get_edit_distance_matrix(s1, s2):
-    """Returns a len(s2) + 1 by len(s1) + 1 matrix,
-    where the first row and column are filled with 0s,
-    the rest is filled with -1s.
-
-    :type s1 : str
-    :type s2 : str
-    """
-    matrix = [[-1 for _ in range(len(s2) + 1)] for _ in range(len(s1) + 1)]
-
-    for j in range(len(matrix[0])):
-        matrix[0][j] = j
-
-    for i, _ in enumerate(matrix):
-        matrix[i][0] = i
-
-    return matrix
-
-
-def _get_coordinates_matrix(s1, s2):
-    return [[(0, 0) for _ in range(len(s2) + 1)] for _ in range(len(s1) + 1)]
-
-
-def min_edit_distance(s1, s2, return_matrix=False):
-    """Returns the edit distance of s1 to s2.
-
-    The edit distance is the minimum number of operations,
-    among insertion, deletion and substitution,
-    that we need to turn s1 into s2.
-
-    If return_matrix = True,
-    the matrix used to calculate the edit distance is returned,
-    instead of the edit distance.
-
-    This algorithm uses a dynamic programming solution.
-
-    Running time complexity: O(m * n),
-    where m is the length of s1 and n is the length of s2.
-
-    :type s1 : str
-    :type s2 : str
-    """
-    m = _get_edit_distance_matrix(s1, s2)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-            # How do we obtain the m[i][j] value?
-            # We need to look at three positions while iterating:
-            # 1. m[i - 1][j -1]
-            # 2. m[i][j - 1]
-            # 3. m[i - 1][j]
-
-            # s1[i - 1] and s2[j - 1] are the characters
-
-            # (node that i and j start from 1!)
-            # that we are currently comparing.
-            # If the characters are equal,
-            # we don't need to perform any of the operations:
-            # insertion, deletion or substitution,
-            # and the minimum edit distance to convert s1[i - 1] to s2[j - 1]
-            # is the same as the one to convert s1[i] to s[j],
-            # because, as stated above, s1[i - 1] and s2[j - 1] are equal,
-            # so we don't have to perform any other operation.
-            if s1[i - 1] == s2[j - 1]:
-                m[i][j] = m[i - 1][j - 1]
-            else:
-                m[i][j] = min(m[i - 1][j - 1] + 1, m[i - 1]
-                              [j] + 1, m[i][j - 1] + 1)
-
-                # pprint(m)
-                # input()
-                # print()
-
-    return m[len(s1)][len(s2)] if not return_matrix else m
-
-
-def extended_min_edit_distance(s1, s2):
-    """Returns a tuple whose first item is the minimum edit distance,
-    and the second item is a list of lists containing the instructions
-    (in the language of coordinates) to convert a string to another.
-
-    Running time complexity: O(m * n),
-    where m is the length of s1 and n is the length of s2.
-
-    :type s1 : str
-    :type s2 : str
-    """
-    m = _get_edit_distance_matrix(s1, s2)
-
-    o = _get_coordinates_matrix(s1, s2)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-
-            coordinates = (i - 1, j - 1)
-
-            if s1[i - 1] == s2[j - 1]:
-                m[i][j] = m[i - 1][j - 1]
-            else:
-                _min = -1
-                if m[i][j - 1] + 1 < m[i - 1][j] + 1:
-                    _min = m[i][j - 1] + 1
-                    coordinates = (i, j - 1)
-                else:
-                    _min = m[i - 1][j] + 1
-                    coordinates = (i - 1, j)
-
-                if m[i - 1][j - 1] + 1 < _min:
-                    _min = m[i - 1][j - 1] + 1
-                    coordinates = (i - 1, j - 1)
-
-                m[i][j] = _min
-            o[i][j] = coordinates
-
-    return m[len(s1)][len(s2)], o
-
-
-def build_min_edit_instructions(s1, s2, o):
-    """Interprets the coordinates o
-    and creates a comprehensible list of instructions.
-
-    :type s1 : str
-    :type s2 : str
-    :type o : list
-    """
-
-    i = []  # List for the instructions
-
-    c = (len(s1), len(s2))  # Initial coordinates for o
-
-    while c != (0, 0):
-        # Three Cases:
-        # 1. Go diagonally (to the left) => Replace, if characters are different
-        # 2. Go left  => Remove from the first string
-        # 3. Go up  => Remove from the second string
-
-        next_c = o[c[0]][c[1]]
-
-        # Case 1
-        if next_c[0] < c[0] and next_c[1] < c[1]:
-
-            if s1[c[0] - 1] != s2[c[1] - 1]:
-                i.append("Replace char at index " + str(c[0] - 1) + " (" + s1[c[0] - 1] + ") from '" + s1 +
-                         "' with char at index " + str(c[1] - 1) + " (" + s2[c[1] - 1] + ") from '" + s2 + "'.")
-        # Case 3
-        elif next_c[0] == c[0] and next_c[1] < c[1]:
-            i.append("Insert into '" + s1 + "' at index " + str(c[1] - 1) + " char at index "
-                     + str(c[1] - 1) + " (" + s2[c[1] - 1] + ") from '" + s2 + "'.")
-
-        # Case 2
-        else:  # next_c[0] < c[0] and next_c[1] == c[1]
-            i.append("Delete from '" + s1 + "' char at index " +
-                     str(c[0] - 1) + " (" + s1[c[0] - 1] + ").")
-
-        c = next_c
-
-    i.reverse()
-    return i
-
-
-def convert(str1, str2):
-    print("Edit distance:", min_edit_distance(str1, str2))
-    print()
-
-    print("Matrix:")
-    pprint(min_edit_distance(str1, str2, return_matrix=True))
-    print()
-
-    instructions = build_min_edit_instructions(
-        str1, str2, extended_min_edit_distance(str1, str2)[1])
-
-    for i in instructions:
-        print(i)
-
-
-if __name__ == "__main__":
-    str1 = "Jazayeri"
-    str2 = "Carzaniga"
-
-    str3 = "BANA"
-    str4 = "ANA"
-
-    convert(str1, str2)
-    convert("kitten", "sitting")
-    convert(str3, str3)  # nothing will be indicated to do!
-    convert(str3, str4)
-
-
- -
- -
- -

Functions

- -
-
-

def build_min_edit_instructions(

s1, s2, o)

-
- - - - -

Interprets the coordinates o -and creates a comprehensible list of instructions.

-

:type s1 : str -:type s2 : str -:type o : list

-
- -
-
def build_min_edit_instructions(s1, s2, o):
-    """Interprets the coordinates o
-    and creates a comprehensible list of instructions.
-
-    :type s1 : str
-    :type s2 : str
-    :type o : list
-    """
-
-    i = []  # List for the instructions
-
-    c = (len(s1), len(s2))  # Initial coordinates for o
-
-    while c != (0, 0):
-        # Three Cases:
-        # 1. Go diagonally (to the left) => Replace, if characters are different
-        # 2. Go left  => Remove from the first string
-        # 3. Go up  => Remove from the second string
-
-        next_c = o[c[0]][c[1]]
-
-        # Case 1
-        if next_c[0] < c[0] and next_c[1] < c[1]:
-
-            if s1[c[0] - 1] != s2[c[1] - 1]:
-                i.append("Replace char at index " + str(c[0] - 1) + " (" + s1[c[0] - 1] + ") from '" + s1 +
-                         "' with char at index " + str(c[1] - 1) + " (" + s2[c[1] - 1] + ") from '" + s2 + "'.")
-        # Case 3
-        elif next_c[0] == c[0] and next_c[1] < c[1]:
-            i.append("Insert into '" + s1 + "' at index " + str(c[1] - 1) + " char at index "
-                     + str(c[1] - 1) + " (" + s2[c[1] - 1] + ") from '" + s2 + "'.")
-
-        # Case 2
-        else:  # next_c[0] < c[0] and next_c[1] == c[1]
-            i.append("Delete from '" + s1 + "' char at index " +
-                     str(c[0] - 1) + " (" + s1[c[0] - 1] + ").")
-
-        c = next_c
-
-    i.reverse()
-    return i
-
-
-
- -
- - -
-
-

def convert(

str1, str2)

-
- - - - -
- -
-
def convert(str1, str2):
-    print("Edit distance:", min_edit_distance(str1, str2))
-    print()
-
-    print("Matrix:")
-    pprint(min_edit_distance(str1, str2, return_matrix=True))
-    print()
-
-    instructions = build_min_edit_instructions(
-        str1, str2, extended_min_edit_distance(str1, str2)[1])
-
-    for i in instructions:
-        print(i)
-
-
-
- -
- - -
-
-

def extended_min_edit_distance(

s1, s2)

-
- - - - -

Returns a tuple whose first item is the minimum edit distance, -and the second item is a list of lists containing the instructions -(in the language of coordinates) to convert a string to another.

-

Running time complexity: O(m * n), -where m is the length of s1 and n is the length of s2.

-

:type s1 : str -:type s2 : str

-
- -
-
def extended_min_edit_distance(s1, s2):
-    """Returns a tuple whose first item is the minimum edit distance,
-    and the second item is a list of lists containing the instructions
-    (in the language of coordinates) to convert a string to another.
-
-    Running time complexity: O(m * n),
-    where m is the length of s1 and n is the length of s2.
-
-    :type s1 : str
-    :type s2 : str
-    """
-    m = _get_edit_distance_matrix(s1, s2)
-
-    o = _get_coordinates_matrix(s1, s2)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-
-            coordinates = (i - 1, j - 1)
-
-            if s1[i - 1] == s2[j - 1]:
-                m[i][j] = m[i - 1][j - 1]
-            else:
-                _min = -1
-                if m[i][j - 1] + 1 < m[i - 1][j] + 1:
-                    _min = m[i][j - 1] + 1
-                    coordinates = (i, j - 1)
-                else:
-                    _min = m[i - 1][j] + 1
-                    coordinates = (i - 1, j)
-
-                if m[i - 1][j - 1] + 1 < _min:
-                    _min = m[i - 1][j - 1] + 1
-                    coordinates = (i - 1, j - 1)
-
-                m[i][j] = _min
-            o[i][j] = coordinates
-
-    return m[len(s1)][len(s2)], o
-
-
-
- -
- - -
-
-

def min_edit_distance(

s1, s2, return_matrix=False)

-
- - - - -

Returns the edit distance of s1 to s2.

-

The edit distance is the minimum number of operations, -among insertion, deletion and substitution, -that we need to turn s1 into s2.

-

If return_matrix = True, -the matrix used to calculate the edit distance is returned, -instead of the edit distance.

-

This algorithm uses a dynamic programming solution.

-

Running time complexity: O(m * n), -where m is the length of s1 and n is the length of s2.

-

:type s1 : str -:type s2 : str

-
- -
-
def min_edit_distance(s1, s2, return_matrix=False):
-    """Returns the edit distance of s1 to s2.
-
-    The edit distance is the minimum number of operations,
-    among insertion, deletion and substitution,
-    that we need to turn s1 into s2.
-
-    If return_matrix = True,
-    the matrix used to calculate the edit distance is returned,
-    instead of the edit distance.
-
-    This algorithm uses a dynamic programming solution.
-
-    Running time complexity: O(m * n),
-    where m is the length of s1 and n is the length of s2.
-
-    :type s1 : str
-    :type s2 : str
-    """
-    m = _get_edit_distance_matrix(s1, s2)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-            # How do we obtain the m[i][j] value?
-            # We need to look at three positions while iterating:
-            # 1. m[i - 1][j -1]
-            # 2. m[i][j - 1]
-            # 3. m[i - 1][j]
-
-            # s1[i - 1] and s2[j - 1] are the characters
-
-            # (node that i and j start from 1!)
-            # that we are currently comparing.
-            # If the characters are equal,
-            # we don't need to perform any of the operations:
-            # insertion, deletion or substitution,
-            # and the minimum edit distance to convert s1[i - 1] to s2[j - 1]
-            # is the same as the one to convert s1[i] to s[j],
-            # because, as stated above, s1[i - 1] and s2[j - 1] are equal,
-            # so we don't have to perform any other operation.
-            if s1[i - 1] == s2[j - 1]:
-                m[i][j] = m[i - 1][j - 1]
-            else:
-                m[i][j] = min(m[i - 1][j - 1] + 1, m[i - 1]
-                              [j] + 1, m[i][j - 1] + 1)
-
-                # pprint(m)
-                # input()
-                # print()
-
-    return m[len(s1)][len(s2)] if not return_matrix else m
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/fibonacci.m.html b/docs/ands/algorithms/dp/fibonacci.m.html deleted file mode 100644 index 4aca3892..00000000 --- a/docs/ands/algorithms/dp/fibonacci.m.html +++ /dev/null @@ -1,1238 +0,0 @@ - - - - - - ands.algorithms.dp.fibonacci API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.fibonacci module

-

Author: Nelson Brochado -Creation: June, 2015

-

In this file you can find some functions -that return the nth fibonacci number, -but they do it in different ways, -which has also an impact on the performance (Big-O complexity) -of the same algorithms.

-

The dynamic programming versions run in O(n) time.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: June, 2015
-
-In this file you can find some functions
-that return the nth fibonacci number,
-but they do it in different ways,
-which has also an impact on the performance (Big-O complexity)
-of the same algorithms.
-
-The dynamic programming versions run in O(n) time.
-"""
-
-
-def recursive_fib(n):
-    """Returns the nth fibonacci number.
-
-    Running time complexity: O(2^n).
-
-    :type n : int
-    """
-    if n == 0:
-        return 0
-    elif n == 1:
-        return 1
-    else:
-        return recursive_fib(n - 1) + recursive_fib(n - 2)
-
-
-def _memoized_fib_aux(n, memo):
-    """Returns the nth fibonacci number.
-
-    This function uses recursion and memoisation
-    to calculate the n fibonacci number.
-
-    Running time complexity: O(n).
-
-    :type n : int
-    :type memo : dict
-    """
-    if n == 0 or n == 1:
-        return n
-    if n not in memo.keys():
-        memo[n] = _memoized_fib_aux(n - 1, memo) + \
-            _memoized_fib_aux(n - 2, memo)
-    return memo[n]
-
-
-def memoized_fib(n):
-    """Returns the nth fibonacci number using memoisation."""
-    memo = {}
-    return _memoized_fib_aux(n, memo)
-
-
-def bottom_up_fib(n, ls=False):
-    """Returns the nth fibonacci number if ls=False,
-    else it returns a list containing
-    all the ith fibonacci numbers, for i=0, ... , n
-
-    This function uses a dynamic programing bottom up approach,
-    because we start by finding the optimal solution
-    to smaller sub-problems, and from there,
-    we build the optimal solution to the initial problem.
-
-    :type n : int
-    :type ls : bool
-    """
-    if n == 0:
-        return n if not ls else [n]
-    if n == 1:
-        return n if not ls else [0, n]
-
-    fib = [0] * (n + 1)
-    fib[0] = 0
-    fib[1] = 1
-
-    for i in range(2, n + 1):
-        fib[i] = fib[i - 1] + fib[i - 2]
-
-    return fib[-1] if not ls else fib
-
-
-if __name__ == "__main__":
-    for f in range(10):
-        print(recursive_fib(f))
-        print(memoized_fib(f))
-        print(bottom_up_fib(f, ls=True))
-
-
- -
- -
- -

Functions

- -
-
-

def bottom_up_fib(

n, ls=False)

-
- - - - -

Returns the nth fibonacci number if ls=False, -else it returns a list containing -all the ith fibonacci numbers, for i=0, ... , n

-

This function uses a dynamic programing bottom up approach, -because we start by finding the optimal solution -to smaller sub-problems, and from there, -we build the optimal solution to the initial problem.

-

:type n : int -:type ls : bool

-
- -
-
def bottom_up_fib(n, ls=False):
-    """Returns the nth fibonacci number if ls=False,
-    else it returns a list containing
-    all the ith fibonacci numbers, for i=0, ... , n
-
-    This function uses a dynamic programing bottom up approach,
-    because we start by finding the optimal solution
-    to smaller sub-problems, and from there,
-    we build the optimal solution to the initial problem.
-
-    :type n : int
-    :type ls : bool
-    """
-    if n == 0:
-        return n if not ls else [n]
-    if n == 1:
-        return n if not ls else [0, n]
-
-    fib = [0] * (n + 1)
-    fib[0] = 0
-    fib[1] = 1
-
-    for i in range(2, n + 1):
-        fib[i] = fib[i - 1] + fib[i - 2]
-
-    return fib[-1] if not ls else fib
-
-
-
- -
- - -
-
-

def memoized_fib(

n)

-
- - - - -

Returns the nth fibonacci number using memoisation.

-
- -
-
def memoized_fib(n):
-    """Returns the nth fibonacci number using memoisation."""
-    memo = {}
-    return _memoized_fib_aux(n, memo)
-
-
-
- -
- - -
-
-

def recursive_fib(

n)

-
- - - - -

Returns the nth fibonacci number.

-

Running time complexity: O(2^n).

-

:type n : int

-
- -
-
def recursive_fib(n):
-    """Returns the nth fibonacci number.
-
-    Running time complexity: O(2^n).
-
-    :type n : int
-    """
-    if n == 0:
-        return 0
-    elif n == 1:
-        return 1
-    else:
-        return recursive_fib(n - 1) + recursive_fib(n - 2)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/index.html b/docs/ands/algorithms/dp/index.html deleted file mode 100644 index ef9eb30a..00000000 --- a/docs/ands/algorithms/dp/index.html +++ /dev/null @@ -1,1186 +0,0 @@ - - - - - - ands.algorithms.dp API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms.dp.change_making

- - -

Author: Nelson Brochado

-

Problem (https://en.wikipedia.org/wiki/Change-making_problem): -Given a set of coins, which is the smallest subset of these coins, -such that summed together yields a certain number n.

-

This problem is similar to the integer knapsack problem, -the different is that here values=...

- -
-
-

ands.algorithms.dp.edit_distance

- - -

Author: Nelson Brochado -Creation: 31/08/15

-

Calculate the edit distance between two strings.

-

Based on: -- https://github.com/dossan/interview/blob/master/src/com/interview/dynamic/EditDistance.java -- https://www.youtube.com/watch?v=We3YDTzNXEk

- -
-
-

ands.algorithms.dp.fibonacci

- - -

Author: Nelson Brochado -Creation: June, 2015

-

In this file you can find some functions -that return the nth fibonacci number, -but they do it in different ways, -which has also an impact on the performance (Big-O complexity) -of the same algorithms.

-

The dynamic programming versions run in O(n) time.

- -
-
-

ands.algorithms.dp.longest_common_subsequence

- - -

Meta info

-

Author: Nelson Brochado -Created: 02/09/2015 -Updated: 26/01/2017

-

Description

-

The longest common subsequence or, in short, lcs, of two strings x and y -is a common measure of similarity between the two strings.

-

More specifically the problem is as follows:

-
given two ...
-
- -
-
-

ands.algorithms.dp.longest_common_substring

- - -

Author: Nelson Brochado -Creation: 02/09/15

- -
-
-

ands.algorithms.dp.longest_increasing_subsequence

- - -

Author: Nelson Brochado -Creation: 29/08/2015

-

Based on the following answer: -http://stackoverflow.com/a/19639755/3924118

-

https://www.youtube.com/watch?v=CE2b_-XfVDk

- -
-
-

ands.algorithms.dp.max_non_adjacent_seq_weight

- - -

Author: Nelson Brochado -Creation: 04/09/15

-

See exercise 140.

-

Based on: http://www.geeksforgeeks.org/maximum-sum-such-that-no-two-elements-are-adjacent/

- -
-
-

ands.algorithms.dp.max_sum_contiguous_subsequence

- - -

Author: Nelson Brochado -Creation: 04/09/15

-

Based on: https://tkramesh.wordpress.com/2011/03/09/dynamic-programming-maximum-sum-contiguous-subsequence/

- -
-
-

ands.algorithms.dp.plus_sign_game

- - -

Author: Nelson Brochado -Creation: 01/09/15

-

Given a string s of numbers (s1 s2 s3...sn), -is it possible to insert some plus signs "+" -in the string so that the remaining expression -is equal to a certain number k?

-

Example: Is it possible to insert some + signs in 214, -so that the resulting expressio...

- -
-
-

ands.algorithms.dp.rod_cut

- - -

Author: Nelson Brochado -Creation: 30/08/15 -Last update: 18/11/2016

-

References:

-
    -
  • http://www.radford.edu/~nokie/classes/360/dp-rod-cutting.html
  • -
  • Introduction to Algorithms (3rd edition) by CLSR
  • -
  • Slides by prof. E. Papadopoulou
  • -
- -
-
-

ands.algorithms.dp.subset_sum

- - -

Author: Nelson Brochado -Creation: 03/09/15

- -
-
-

ands.algorithms.dp.zero_one_knapsack

- - -

Meta info

-

Author: Nelson Brochado -Created: 2015 -Updated: 26/01/2017

-

Description

-

Given n objects and a "knapsack". -Item i weighs w_i > 0 and has a value v_i > 0. -Knapsack has capacity W. -Goal: fill knapsack so as to maximize total value.

-

References

-
    -
  • Slides by prof. Evanthia Papadopoulo...
  • -
- -
-
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/longest_common_subsequence.m.html b/docs/ands/algorithms/dp/longest_common_subsequence.m.html deleted file mode 100644 index 74ba8f68..00000000 --- a/docs/ands/algorithms/dp/longest_common_subsequence.m.html +++ /dev/null @@ -1,1712 +0,0 @@ - - - - - - ands.algorithms.dp.longest_common_subsequence API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.longest_common_subsequence module

-

Meta info

-

Author: Nelson Brochado -Created: 02/09/2015 -Updated: 26/01/2017

-

Description

-

The longest common subsequence or, in short, lcs, of two strings x and y -is a common measure of similarity between the two strings.

-

More specifically the problem is as follows:

-
given two strings x = x_1 x_2 .. x_m and y = y_1 y_2 .. y_n,
-what is (the length of) the longest common subsequence between strings `x` and `y`,
-where characters in the subsequences are not necessarily contiguous?
-
-

The solution is not necessarily unique. -You can find a recursive and a two dynamic programming implementations for the lcs problem. -You can find just one implementation using dynamic programming that actually returns the lcs, -instead of just computing its length, like all other implementations do.

-

References

-
    -
  • Introduction to Algorithms (3rd ed.) by CLRS
  • -
  • Slides by prof. Evanthia Papadopoulou
  • -
-

Resources

- -

TODO

-
    -
  • Create a version with case insensitive matching.
  • -
- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-Created: 02/09/2015
-Updated: 26/01/2017
-
-## Description
-
-The longest common subsequence or, in short, _lcs_, of _two_ strings `x` and `y`
-is a common **measure of similarity** between the two strings.
-
-More specifically the problem is as follows:
-
-    given two strings x = x_1 x_2 .. x_m and y = y_1 y_2 .. y_n,
-    what is (the length of) the longest common subsequence between strings `x` and `y`,
-    where characters in the subsequences are not necessarily contiguous?
-    
-The solution is not necessarily unique.
-You can find a recursive and a two dynamic programming implementations for the lcs problem.
-You can find just one implementation using dynamic programming that actually returns the lcs,
-instead of just computing its length, like all other implementations do.
-
-## References
-
-- Introduction to Algorithms (3rd ed.) by CLRS
-- Slides by prof. Evanthia Papadopoulou
-
-## Resources
-
-- [https://en.wikipedia.org/wiki/Longest_common_subsequence_problem](https://en.wikipedia.org/wiki/Longest_common_subsequence_problem)
-
-## TODO
-
-- Create a version with case insensitive matching.
-"""
-
-
-def _get_lcs_length_matrix(s1: str, s2: str) -> list:
-    """Let m = len(s1) and n = len(s2),
-    then this function returns a (m + 1)x(n + 1) matrix,
-    specifically it returns a list of length m + 1,
-    whose elements are lists of length (n + 1).
-    The "+ 1" in (m + 1) and (n + 1) is because the first row and column
-    are reserved for the cases where we compare with _empty_ sequences.
-    """
-    return [[0 for _ in range(len(s2) + 1)] for _ in range(len(s1) + 1)]
-
-
-def _get_lcs_matrix(s1: str, s2: str) -> list:
-    m = []
-    for _ in range(len(s1) + 1):
-        m.append([])
-        for _ in range(len(s2) + 1):
-            m[-1].append([])
-    return m
-
-
-def _recursive_lcs_length_aux(s1: str, n: int, s2: str, m: int, result: int) -> int:
-    """Helper function of `recursive_lcs_length`."""
-    if n == 0 or m == 0:
-        return 0
-    elif s1[n - 1] == s2[m - 1]:
-        result = 1 + _recursive_lcs_length_aux(s1, n - 1, s2, m - 1, result)
-    else:
-        result = max(_recursive_lcs_length_aux(s1, n - 1, s2, m, result),
-                     _recursive_lcs_length_aux(s1, n, s2, m - 1, result))
-    return result
-
-
-def recursive_lcs_length(s1: str, s2: str) -> int:
-    """Returns the length of the longest common subsequence between s1 and s2.
-    This algorithm uses a recursive solution, as the name suggests,
-    but this results in an exponential algorithm.
-    
-    ### Idea
-    Given two strings x and y, how do we find the length of the lcs between x and y?
-    
-    For every subsequence of x check weather it's a subsequence of y.
-    There are 2^n subsequences of x to check, and each subsequence takes O(m) time to check:
-    scan y for the first letter, from there scan for the second, and so on.
-    
-    ### Definition
-    
-    lcs(i, j) = length of the longest comment subsequence between
-    x(i) = x(i)_1, x(i)_2, ..., x(i)_i and y(j) = y(j)_1, y(j)_2, ..., y(j)_j.
-    
-    where by x(i) it's meant a subsequence of x up to i,
-    and by x(i)_i it's meant the ith element of that same subsequence x(i).
-    A similar thing can be said for y(j) and y(j)_j.
-        
-    ### Goal 
-    lcs(n, m), where n = length(x) and m = length(y).
-    
-    ### Algorithm  
-  
-    If x(i) or y(j) is empty, lcs(i, j) = 0.
-    
-    If x(i)_i == y(j)_j, then x(i)_i and y(j)_j must be included in the lcs(i, j),
-    and we express it as lcs(i, j) = lcs(i - 1, j - 1) + 1,
-    where the +1 stands for the inclusion of x(i)_i and y(j)_j.
-    
-    If x(i)_i != y(j)_j, then we can either skip x(i)_i or y(j)_j (or both):
-    we need to choose the best!! So lets see these options more closely.
-    
-    Option 1: x(i)_i is not in the lcs, then lcs(i, j) = lcs(i - 1, j).
-    Option 2: y(j)_j is not in the lcs, then lcs(i, j) = lcs(i, j - 1).
-    
-    So, basically, what we do is: lcs(i, j) = max(lcs(i - 1, j), lcs(i, j - 1)).
-    
-    Note that we don't really need to include lcs(i - 1, j - 1),
-    for the case where neither x(i)_i nor y(j)_j are included in the lcs(i, j),
-    because max(lcs(i - 1, j), lcs(i, j - 1), lcs(i - 1, j - 1)) = max(lcs(i - 1, j), lcs(i, j - 1)),
-    i.e. the maximum "profit" can simply be retrieved from lcs(i - 1, j) and lcs(i, j - 1),
-    since for sure lcs(i - 1, j - 1) brings less (or equal) profit than lcs(i - 1, j) or lcs(i, j - 1).
-    
-    #### Summary
-    
-                +--
-                | 0                                   if i == 0 or j == 0.
-    lcs(i, j) = | lcs(i - 1, j - 1) + 1               if i, j > 0 and x(i)_i == y(j)_j.
-                | max(lcs(i - 1, j), lcs(i, j - 1))   if i, j > 0 and x(i)_i != y(j)_j.
-                +--
-                
-    ### Complexity
-    This plain recursive approach is very inefficient, 
-    because we keep on recomputing sub-problems.
-    
-    **Time complexity**: θ(m*2^n).
-    """
-    n = len(s1)
-    m = len(s2)
-    result = 0
-    return _recursive_lcs_length_aux(s1, n, s2, m, result)
-
-
-def _memoized_recursive_lcs_length_aux(s1: str, n: int, s2: str, m: int, result: list, matrix: list) -> int:
-    """Helper function of `recursive_lcs_length`."""
-    if n == 0 or m == 0:
-        return 0
-    elif matrix[n - 1][m - 1] is not None:
-        return matrix[n - 1][m - 1]
-    elif s1[n - 1] == s2[m - 1]:
-        result = 1 + _memoized_recursive_lcs_length_aux(s1, n - 1, s2, m - 1, result, matrix)
-    else:
-        result = max(_memoized_recursive_lcs_length_aux(s1, n - 1, s2, m, result, matrix),
-                     _memoized_recursive_lcs_length_aux(s1, n, s2, m - 1, result, matrix))
-
-    matrix[n - 1][m - 1] = result
-
-    return result
-
-
-def memoized_recursive_lcs_length(s1: str, s2: str) -> int:
-    """Returns the length of the longest common subsequence between strings s1 and s2.
-    This algorithm uses _memoization_ to improve performance with respect to `recursive_lcs_length`.
-    
-    If n = length(s1) and m = length(s2), then time complexity of this algorithm O(n*m),
-    which is very similar to the bottom-up version (below).
-    """
-    n = len(s1)
-    m = len(s2)
-    result = 0
-    matrix = [[None for _ in range(len(s2))] for _ in range(len(s1))]
-    
-    return _memoized_recursive_lcs_length_aux(s1, n, s2, m, result, matrix)
-
-
-def bottom_up_lcs_length(s1: str, s2: str, matrix: bool=False):
-    """Returns the length of the longest common subsequence between strings s1 and s2,
-    if `matrix` is set to `False`, 
-    else it returns the matrix used to calculate the length of the lcs of sub-problems.
-    
-    If n = length(s1) and m = length(s2), 
-    then the following are the asymptotic complexities of this algorithm.
-    
-    **Time complexity:** O(n*m)
-    **Space complexity:** O(n*m)
-    """
-    # m is initialized with zeros everywhere
-    m = _get_lcs_length_matrix(s1, s2)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-
-            # note that i and j start from 1,
-            # thus we index s1 and s2 using i - 1 and respectively j - 1,
-            # instead of simply i and j.
-            if s1[i - 1] == s2[j - 1]: 
-                m[i][j] = m[i - 1][j - 1] + 1
-            else:
-                m[i][j] = max(m[i - 1][j], m[i][j - 1])
-
-    return m[-1][-1] if not matrix else m
-
-
-def bottom_up_lcs_length_partial(s1: str, s2: str, c1: str, c2: str, partial_weight: int = 0.5, matrix: bool=False):
-    """Returns the length of the lcs between strings s1 and s2,
-    but considers c1 and c2 partially equal characters,
-    and thus instead of adding +1 to the length being computed `partial_weight` is added.
-    
-    **Time complexity:** O(n*m)
-    **Space complexity:** O(n*m)   
-    """
-    
-    m = _get_lcs_length_matrix(s1, s2)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-
-            if s1[i - 1] == s2[j - 1]:
-                m[i][j] = m[i - 1][j - 1] + 1
-            
-            # partial match
-            elif (s1[i - 1] == c1 and s2[j - 1] == c2) or (s1[i - 1] == c2 and s2[j - 1] == c1):
-                m[i][j] = max(m[i - 1][j], m[i][j - 1], m[i - 1][j - 1] + partial_weight)
-                
-            else: 
-                m[i][j] = max(m[i - 1][j], m[i][j - 1])
-
-    return m[-1][-1] if not matrix else m
-
-
-def backtrack(m: list, s1: str, s2: str, i: int, j: int):
-    if i == 1 or j == 1:
-        return ""
-    elif s1[i] == s2[j]:
-        print(s1[i])
-        return backtrack(m, s1, s2, i - 1, j - 1) + s1[i]
-    else:
-        if m[i][j - 1] > m[i - 1][j]:
-            return backtrack(m, s1, s2, i, j - 1)
-        else:
-            return backtrack(m, s1, s2, i - 1, j)
-
-
-def get_lcs(s1: str, s2: str) -> None:
-    m = bottom_up_lcs_length(s1, s2, matrix=True)
-    backtrack(m, s1, s2, len(s1) - 1, len(s2) - 1)
-
-
-def bottom_up_lcs(s1: str, s2: str):
-    """Builds all lists with all LCSs to sub-strings of sub-problems,
-    and then returns a list of characters representing
-    the longest common subsequence for the original problem.
-    :type s1 : str
-    :type s2 : str
-    :rtype : list of str"""
-
-    m = _get_lcs_matrix(s1, s2)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-
-            if s1[i - 1] == s2[j - 1]:
-                m[i][j] += m[i - 1][j - 1]
-                m[i][j].append(s2[j - 1])
-            else:
-                if len(m[i - 1][j]) > len(m[i][j - 1]):
-                    m[i][j] += m[i - 1][j]
-                else:
-                    m[i][j] += m[i][j - 1]
-
-    return m[-1][-1]
-
-
-if __name__ == "__main__":
-    examples = [("acbcf", "abcdaf"),
-                ("BANANA", "ATANA"),
-                ("abdeccbbaede", "bbdccedacde"),
-                ("abe", "eb")]
-
-    for a, b in examples:
-        print("a =", a, ", b =", b)
-        print(recursive_lcs_length(a, b))
-        print(bottom_up_lcs_length(a, b))        
-        print(memoized_recursive_lcs_length(a, b))
-        print(bottom_up_lcs_length_partial(a, b, 'a', 'e'))
-        print()
-        
-    #backtrack(m, a, b, len(a) - 1, len(b) - 1)
-
-
- -
- -
- -

Functions

- -
-
-

def backtrack(

m, s1, s2, i, j)

-
- - - - -
- -
-
def backtrack(m: list, s1: str, s2: str, i: int, j: int):
-    if i == 1 or j == 1:
-        return ""
-    elif s1[i] == s2[j]:
-        print(s1[i])
-        return backtrack(m, s1, s2, i - 1, j - 1) + s1[i]
-    else:
-        if m[i][j - 1] > m[i - 1][j]:
-            return backtrack(m, s1, s2, i, j - 1)
-        else:
-            return backtrack(m, s1, s2, i - 1, j)
-
-
-
- -
- - -
-
-

def bottom_up_lcs(

s1, s2)

-
- - - - -

Builds all lists with all LCSs to sub-strings of sub-problems, -and then returns a list of characters representing -the longest common subsequence for the original problem. -:type s1 : str -:type s2 : str -:rtype : list of str

-
- -
-
def bottom_up_lcs(s1: str, s2: str):
-    """Builds all lists with all LCSs to sub-strings of sub-problems,
-    and then returns a list of characters representing
-    the longest common subsequence for the original problem.
-    :type s1 : str
-    :type s2 : str
-    :rtype : list of str"""
-
-    m = _get_lcs_matrix(s1, s2)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-
-            if s1[i - 1] == s2[j - 1]:
-                m[i][j] += m[i - 1][j - 1]
-                m[i][j].append(s2[j - 1])
-            else:
-                if len(m[i - 1][j]) > len(m[i][j - 1]):
-                    m[i][j] += m[i - 1][j]
-                else:
-                    m[i][j] += m[i][j - 1]
-
-    return m[-1][-1]
-
-
-
- -
- - -
-
-

def bottom_up_lcs_length(

s1, s2, matrix=False)

-
- - - - -

Returns the length of the longest common subsequence between strings s1 and s2, -if matrix is set to False, -else it returns the matrix used to calculate the length of the lcs of sub-problems.

-

If n = length(s1) and m = length(s2), -then the following are the asymptotic complexities of this algorithm.

-

Time complexity: O(nm) -Space complexity: O(nm)

-
- -
-
def bottom_up_lcs_length(s1: str, s2: str, matrix: bool=False):
-    """Returns the length of the longest common subsequence between strings s1 and s2,
-    if `matrix` is set to `False`, 
-    else it returns the matrix used to calculate the length of the lcs of sub-problems.
-    
-    If n = length(s1) and m = length(s2), 
-    then the following are the asymptotic complexities of this algorithm.
-    
-    **Time complexity:** O(n*m)
-    **Space complexity:** O(n*m)
-    """
-    # m is initialized with zeros everywhere
-    m = _get_lcs_length_matrix(s1, s2)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-
-            # note that i and j start from 1,
-            # thus we index s1 and s2 using i - 1 and respectively j - 1,
-            # instead of simply i and j.
-            if s1[i - 1] == s2[j - 1]: 
-                m[i][j] = m[i - 1][j - 1] + 1
-            else:
-                m[i][j] = max(m[i - 1][j], m[i][j - 1])
-
-    return m[-1][-1] if not matrix else m
-
-
-
- -
- - -
-
-

def bottom_up_lcs_length_partial(

s1, s2, c1, c2, partial_weight=0.5, matrix=False)

-
- - - - -

Returns the length of the lcs between strings s1 and s2, -but considers c1 and c2 partially equal characters, -and thus instead of adding +1 to the length being computed partial_weight is added.

-

Time complexity: O(nm) -Space complexity: O(nm)

-
- -
-
def bottom_up_lcs_length_partial(s1: str, s2: str, c1: str, c2: str, partial_weight: int = 0.5, matrix: bool=False):
-    """Returns the length of the lcs between strings s1 and s2,
-    but considers c1 and c2 partially equal characters,
-    and thus instead of adding +1 to the length being computed `partial_weight` is added.
-    
-    **Time complexity:** O(n*m)
-    **Space complexity:** O(n*m)   
-    """
-    
-    m = _get_lcs_length_matrix(s1, s2)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-
-            if s1[i - 1] == s2[j - 1]:
-                m[i][j] = m[i - 1][j - 1] + 1
-            
-            # partial match
-            elif (s1[i - 1] == c1 and s2[j - 1] == c2) or (s1[i - 1] == c2 and s2[j - 1] == c1):
-                m[i][j] = max(m[i - 1][j], m[i][j - 1], m[i - 1][j - 1] + partial_weight)
-                
-            else: 
-                m[i][j] = max(m[i - 1][j], m[i][j - 1])
-
-    return m[-1][-1] if not matrix else m
-
-
-
- -
- - -
-
-

def get_lcs(

s1, s2)

-
- - - - -
- -
-
def get_lcs(s1: str, s2: str) -> None:
-    m = bottom_up_lcs_length(s1, s2, matrix=True)
-    backtrack(m, s1, s2, len(s1) - 1, len(s2) - 1)
-
-
-
- -
- - -
-
-

def memoized_recursive_lcs_length(

s1, s2)

-
- - - - -

Returns the length of the longest common subsequence between strings s1 and s2. -This algorithm uses memoization to improve performance with respect to recursive_lcs_length.

-

If n = length(s1) and m = length(s2), then time complexity of this algorithm O(n*m), -which is very similar to the bottom-up version (below).

-
- -
-
def memoized_recursive_lcs_length(s1: str, s2: str) -> int:
-    """Returns the length of the longest common subsequence between strings s1 and s2.
-    This algorithm uses _memoization_ to improve performance with respect to `recursive_lcs_length`.
-    
-    If n = length(s1) and m = length(s2), then time complexity of this algorithm O(n*m),
-    which is very similar to the bottom-up version (below).
-    """
-    n = len(s1)
-    m = len(s2)
-    result = 0
-    matrix = [[None for _ in range(len(s2))] for _ in range(len(s1))]
-    
-    return _memoized_recursive_lcs_length_aux(s1, n, s2, m, result, matrix)
-
-
-
- -
- - -
-
-

def recursive_lcs_length(

s1, s2)

-
- - - - -

Returns the length of the longest common subsequence between s1 and s2. -This algorithm uses a recursive solution, as the name suggests, -but this results in an exponential algorithm.

-

Idea

-

Given two strings x and y, how do we find the length of the lcs between x and y?

-

For every subsequence of x check weather it's a subsequence of y. -There are 2^n subsequences of x to check, and each subsequence takes O(m) time to check: -scan y for the first letter, from there scan for the second, and so on.

-

Definition

-

lcs(i, j) = length of the longest comment subsequence between -x(i) = x(i)_1, x(i)_2, ..., x(i)_i and y(j) = y(j)_1, y(j)_2, ..., y(j)_j.

-

where by x(i) it's meant a subsequence of x up to i, -and by x(i)_i it's meant the ith element of that same subsequence x(i). -A similar thing can be said for y(j) and y(j)_j.

-

Goal

-

lcs(n, m), where n = length(x) and m = length(y).

-

Algorithm

-

If x(i) or y(j) is empty, lcs(i, j) = 0.

-

If x(i)_i == y(j)_j, then x(i)_i and y(j)_j must be included in the lcs(i, j), -and we express it as lcs(i, j) = lcs(i - 1, j - 1) + 1, -where the +1 stands for the inclusion of x(i)_i and y(j)_j.

-

If x(i)_i != y(j)_j, then we can either skip x(i)_i or y(j)_j (or both): -we need to choose the best!! So lets see these options more closely.

-

Option 1: x(i)_i is not in the lcs, then lcs(i, j) = lcs(i - 1, j). -Option 2: y(j)_j is not in the lcs, then lcs(i, j) = lcs(i, j - 1).

-

So, basically, what we do is: lcs(i, j) = max(lcs(i - 1, j), lcs(i, j - 1)).

-

Note that we don't really need to include lcs(i - 1, j - 1), -for the case where neither x(i)_i nor y(j)_j are included in the lcs(i, j), -because max(lcs(i - 1, j), lcs(i, j - 1), lcs(i - 1, j - 1)) = max(lcs(i - 1, j), lcs(i, j - 1)), -i.e. the maximum "profit" can simply be retrieved from lcs(i - 1, j) and lcs(i, j - 1), -since for sure lcs(i - 1, j - 1) brings less (or equal) profit than lcs(i - 1, j) or lcs(i, j - 1).

-

Summary

-
        +--
-        | 0                                   if i == 0 or j == 0.
-
-

lcs(i, j) = | lcs(i - 1, j - 1) + 1 if i, j > 0 and x(i)_i == y(j)_j. - | max(lcs(i - 1, j), lcs(i, j - 1)) if i, j > 0 and x(i)_i != y(j)_j. - +--

-

Complexity

-

This plain recursive approach is very inefficient, -because we keep on recomputing sub-problems.

-

Time complexity: θ(m*2^n).

-
- -
-
def recursive_lcs_length(s1: str, s2: str) -> int:
-    """Returns the length of the longest common subsequence between s1 and s2.
-    This algorithm uses a recursive solution, as the name suggests,
-    but this results in an exponential algorithm.
-    
-    ### Idea
-    Given two strings x and y, how do we find the length of the lcs between x and y?
-    
-    For every subsequence of x check weather it's a subsequence of y.
-    There are 2^n subsequences of x to check, and each subsequence takes O(m) time to check:
-    scan y for the first letter, from there scan for the second, and so on.
-    
-    ### Definition
-    
-    lcs(i, j) = length of the longest comment subsequence between
-    x(i) = x(i)_1, x(i)_2, ..., x(i)_i and y(j) = y(j)_1, y(j)_2, ..., y(j)_j.
-    
-    where by x(i) it's meant a subsequence of x up to i,
-    and by x(i)_i it's meant the ith element of that same subsequence x(i).
-    A similar thing can be said for y(j) and y(j)_j.
-        
-    ### Goal 
-    lcs(n, m), where n = length(x) and m = length(y).
-    
-    ### Algorithm  
-  
-    If x(i) or y(j) is empty, lcs(i, j) = 0.
-    
-    If x(i)_i == y(j)_j, then x(i)_i and y(j)_j must be included in the lcs(i, j),
-    and we express it as lcs(i, j) = lcs(i - 1, j - 1) + 1,
-    where the +1 stands for the inclusion of x(i)_i and y(j)_j.
-    
-    If x(i)_i != y(j)_j, then we can either skip x(i)_i or y(j)_j (or both):
-    we need to choose the best!! So lets see these options more closely.
-    
-    Option 1: x(i)_i is not in the lcs, then lcs(i, j) = lcs(i - 1, j).
-    Option 2: y(j)_j is not in the lcs, then lcs(i, j) = lcs(i, j - 1).
-    
-    So, basically, what we do is: lcs(i, j) = max(lcs(i - 1, j), lcs(i, j - 1)).
-    
-    Note that we don't really need to include lcs(i - 1, j - 1),
-    for the case where neither x(i)_i nor y(j)_j are included in the lcs(i, j),
-    because max(lcs(i - 1, j), lcs(i, j - 1), lcs(i - 1, j - 1)) = max(lcs(i - 1, j), lcs(i, j - 1)),
-    i.e. the maximum "profit" can simply be retrieved from lcs(i - 1, j) and lcs(i, j - 1),
-    since for sure lcs(i - 1, j - 1) brings less (or equal) profit than lcs(i - 1, j) or lcs(i, j - 1).
-    
-    #### Summary
-    
-                +--
-                | 0                                   if i == 0 or j == 0.
-    lcs(i, j) = | lcs(i - 1, j - 1) + 1               if i, j > 0 and x(i)_i == y(j)_j.
-                | max(lcs(i - 1, j), lcs(i, j - 1))   if i, j > 0 and x(i)_i != y(j)_j.
-                +--
-                
-    ### Complexity
-    This plain recursive approach is very inefficient, 
-    because we keep on recomputing sub-problems.
-    
-    **Time complexity**: θ(m*2^n).
-    """
-    n = len(s1)
-    m = len(s2)
-    result = 0
-    return _recursive_lcs_length_aux(s1, n, s2, m, result)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/longest_common_substring.m.html b/docs/ands/algorithms/dp/longest_common_substring.m.html deleted file mode 100644 index 75968de3..00000000 --- a/docs/ands/algorithms/dp/longest_common_substring.m.html +++ /dev/null @@ -1,1110 +0,0 @@ - - - - - - ands.algorithms.dp.longest_common_substring API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.longest_common_substring module

-

Author: Nelson Brochado -Creation: 02/09/15

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 02/09/15
-"""
-
-
-def _get_longest_common_substring_matrix(s1, s2):
-    return [[0 for _ in range(len(s2) + 1)] for _ in range(len(s1) + 1)]
-
-
-def _build_longest_common_substring(s1, c, m):
-    lcs = []
-
-    while m[c[0]][c[1]] != 0:
-        lcs.append(s1[c[0] - 1])
-        c = (c[0] - 1, c[1] - 1)
-
-    lcs.reverse()
-    return lcs, c
-
-
-def longest_common_substring(s1, s2):
-    m = _get_longest_common_substring_matrix(s1, s2)
-    c = (0, 0)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-
-            if s2[j - 1] == s1[i - 1]:
-                m[i][j] = m[i - 1][j - 1] + 1
-
-                if m[i][j] > m[c[0]][c[1]]:
-                    c = (i, j)
-            else:
-                m[i][j] = 0
-
-    return _build_longest_common_substring(s1, c, m)
-
-
-if __name__ == "__main__":
-    print(longest_common_substring("abcdaf", "zbcdf"))
-    print(longest_common_substring("zbcdf", "abcdaf"))
-    print(longest_common_substring("Nelson Brochado", "John Lennon"))
-    print(longest_common_substring("Hello World!", "Halo Welt!"))
-
-
- -
- -
- -

Functions

- -
-
-

def longest_common_substring(

s1, s2)

-
- - - - -
- -
-
def longest_common_substring(s1, s2):
-    m = _get_longest_common_substring_matrix(s1, s2)
-    c = (0, 0)
-
-    for i in range(1, len(s1) + 1):
-
-        for j in range(1, len(s2) + 1):
-
-            if s2[j - 1] == s1[i - 1]:
-                m[i][j] = m[i - 1][j - 1] + 1
-
-                if m[i][j] > m[c[0]][c[1]]:
-                    c = (i, j)
-            else:
-                m[i][j] = 0
-
-    return _build_longest_common_substring(s1, c, m)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/longest_increasing_subsequence.m.html b/docs/ands/algorithms/dp/longest_increasing_subsequence.m.html deleted file mode 100644 index cdfcf1f2..00000000 --- a/docs/ands/algorithms/dp/longest_increasing_subsequence.m.html +++ /dev/null @@ -1,1254 +0,0 @@ - - - - - - ands.algorithms.dp.longest_increasing_subsequence API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.longest_increasing_subsequence module

-

Author: Nelson Brochado -Creation: 29/08/2015

-

Based on the following answer: -http://stackoverflow.com/a/19639755/3924118

-

https://www.youtube.com/watch?v=CE2b_-XfVDk

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 29/08/2015
-
-Based on the following answer:
-http://stackoverflow.com/a/19639755/3924118
-
-https://www.youtube.com/watch?v=CE2b_-XfVDk
-"""
-
-
-def build_longest_increasing_subsequence(s, prev, index_of_max_size):
-    a = []
-
-    while index_of_max_size != -1:
-        a.append(s[index_of_max_size])
-        index_of_max_size = prev[index_of_max_size]
-
-    a.reverse()
-    return a
-
-
-def lis(s):
-    """Returns a list with one of the possible
-    longest increasing subsequences of s.
-
-    This algorithm uses a dynamic programming strategy,
-    which runs in O(n^2) time, where n is the size of s.
-
-    :type s : list of int
-    :rtype : list of int
-    """
-
-    # At the end of the algorithm,
-    # each item of this list (indexed, say, by i)
-    # will be the size of the LIS seen so far
-    # starting from the beginning of s (s[0]) up to s[i].
-    # For example, suppose s = [3, 2, 5],
-    # then, at the end of the algorithm,
-    # a = [1, 1, 2]
-    # Why is this true?
-    # The maximum possible longest increasing subsequence
-    # from the beginning of s up to the beginning of s is 1,
-    # because the beginning of s contains only one element (3).
-    # The LIS from s[0] to s[1] is still one.
-    # The LIS from s[0] to s[2] is 2,
-    # because we can either pick 3 or 2 and 5.
-    a = [1] * len(s)
-
-    # This array is useful to retrieve information
-    # about the indexes of the chosen numbers to belong to the LIS.
-    # See the function build_longest_increasing_subsequence,
-    # if you understand how to retrieve the numbers in the LIS.
-    prev = [-1] * len(s)
-
-    # Current maximum size of the increasing subsequence
-    # Note that initially all numbers in s are increasing subsequences of size
-    # 1
-    current_max_size = 1
-
-    # Index of a, which contains the size of the current L.I.S.
-    index_of_max_size = 0
-
-    for i in range(1, len(s)):
-
-        for j in range(0, i):
-
-            if s[j] < s[i] and a[i] < a[j] + 1:
-                a[i] = a[j] + 1
-
-                prev[i] = j
-
-        # Updates the current index of where the size of the current L.I.S is in a,
-        # and also the current maximum size of the so far L.I.S.
-        if a[i] > current_max_size:
-            index_of_max_size = i
-            current_max_size = a[i]
-
-    return build_longest_increasing_subsequence(s, prev, index_of_max_size)
-
-
-def recursive_lis(s):
-    pass
-    # http://stackoverflow.com/questions/2631726/how-to-determine-the-longest-increasing-subsequence-using-dynamic-programming
-
-
-# print(lis([3, 2, 6, 4, 5, 1]))
-print(lis([0, 8, 4, 12, 2, 10, 6, 14, 1, 9, 5, 13, 3, 11, 7, 15]))
-
-
- -
- -
- -

Functions

- -
-
-

def build_longest_increasing_subsequence(

s, prev, index_of_max_size)

-
- - - - -
- -
-
def build_longest_increasing_subsequence(s, prev, index_of_max_size):
-    a = []
-
-    while index_of_max_size != -1:
-        a.append(s[index_of_max_size])
-        index_of_max_size = prev[index_of_max_size]
-
-    a.reverse()
-    return a
-
-
-
- -
- - -
-
-

def lis(

s)

-
- - - - -

Returns a list with one of the possible -longest increasing subsequences of s.

-

This algorithm uses a dynamic programming strategy, -which runs in O(n^2) time, where n is the size of s.

-

:type s : list of int -:rtype : list of int

-
- -
-
def lis(s):
-    """Returns a list with one of the possible
-    longest increasing subsequences of s.
-
-    This algorithm uses a dynamic programming strategy,
-    which runs in O(n^2) time, where n is the size of s.
-
-    :type s : list of int
-    :rtype : list of int
-    """
-
-    # At the end of the algorithm,
-    # each item of this list (indexed, say, by i)
-    # will be the size of the LIS seen so far
-    # starting from the beginning of s (s[0]) up to s[i].
-    # For example, suppose s = [3, 2, 5],
-    # then, at the end of the algorithm,
-    # a = [1, 1, 2]
-    # Why is this true?
-    # The maximum possible longest increasing subsequence
-    # from the beginning of s up to the beginning of s is 1,
-    # because the beginning of s contains only one element (3).
-    # The LIS from s[0] to s[1] is still one.
-    # The LIS from s[0] to s[2] is 2,
-    # because we can either pick 3 or 2 and 5.
-    a = [1] * len(s)
-
-    # This array is useful to retrieve information
-    # about the indexes of the chosen numbers to belong to the LIS.
-    # See the function build_longest_increasing_subsequence,
-    # if you understand how to retrieve the numbers in the LIS.
-    prev = [-1] * len(s)
-
-    # Current maximum size of the increasing subsequence
-    # Note that initially all numbers in s are increasing subsequences of size
-    # 1
-    current_max_size = 1
-
-    # Index of a, which contains the size of the current L.I.S.
-    index_of_max_size = 0
-
-    for i in range(1, len(s)):
-
-        for j in range(0, i):
-
-            if s[j] < s[i] and a[i] < a[j] + 1:
-                a[i] = a[j] + 1
-
-                prev[i] = j
-
-        # Updates the current index of where the size of the current L.I.S is in a,
-        # and also the current maximum size of the so far L.I.S.
-        if a[i] > current_max_size:
-            index_of_max_size = i
-            current_max_size = a[i]
-
-    return build_longest_increasing_subsequence(s, prev, index_of_max_size)
-
-
-
- -
- - -
-
-

def recursive_lis(

s)

-
- - - - -
- -
-
def recursive_lis(s):
-    pass
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/max_non_adjacent_seq_weight.m.html b/docs/ands/algorithms/dp/max_non_adjacent_seq_weight.m.html deleted file mode 100644 index f2844ae3..00000000 --- a/docs/ands/algorithms/dp/max_non_adjacent_seq_weight.m.html +++ /dev/null @@ -1,1122 +0,0 @@ - - - - - - ands.algorithms.dp.max_non_adjacent_seq_weight API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.max_non_adjacent_seq_weight module

-

Author: Nelson Brochado -Creation: 04/09/15

-

See exercise 140.

-

Based on: http://www.geeksforgeeks.org/maximum-sum-such-that-no-two-elements-are-adjacent/

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 04/09/15
-
-See exercise 140.
-
-Based on: http://www.geeksforgeeks.org/maximum-sum-such-that-no-two-elements-are-adjacent/
-"""
-
-
-def max_non_adj_seq_weight(s):
-    """
-    Time complexity: O(n).
-    """
-
-    include = s[0]  # Maximum sum including the previous element.
-    exclude = 0  # Maximum sum excluding the previous element.
-    # The maximum sum excluding the current element will be max(include, exclude).
-    # The maximum sum including the current element will be exclude + current_element
-    # because we cannot sum two consecutive numbers.
-
-    new_exclude = None
-
-    for i in range(1, len(s)):
-
-        if include > exclude:
-            new_exclude = include
-        else:
-            new_exclude = exclude
-
-        include = exclude + s[i]
-        exclude = new_exclude
-
-    return max(include, exclude)
-
-
-if __name__ == "__main__":
-    a = [5, 5, 9, 100, 11, 5]  # 5 + 5 + 100 = 110
-    b = [3, 2, 5, 10, 7]  # 3 + 5 + 7 = 15
-    c = [3, 2, 7, 10]  # 3 + 10 = 13
-    print(max_non_adj_seq_weight(a))
-    print(max_non_adj_seq_weight(b))
-    print(max_non_adj_seq_weight(c))
-
-
- -
- -
- -

Functions

- -
-
-

def max_non_adj_seq_weight(

s)

-
- - - - -

Time complexity: O(n).

-
- -
-
def max_non_adj_seq_weight(s):
-    """
-    Time complexity: O(n).
-    """
-
-    include = s[0]  # Maximum sum including the previous element.
-    exclude = 0  # Maximum sum excluding the previous element.
-    # The maximum sum excluding the current element will be max(include, exclude).
-    # The maximum sum including the current element will be exclude + current_element
-    # because we cannot sum two consecutive numbers.
-
-    new_exclude = None
-
-    for i in range(1, len(s)):
-
-        if include > exclude:
-            new_exclude = include
-        else:
-            new_exclude = exclude
-
-        include = exclude + s[i]
-        exclude = new_exclude
-
-    return max(include, exclude)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/max_sum_contiguous_subsequence.m.html b/docs/ands/algorithms/dp/max_sum_contiguous_subsequence.m.html deleted file mode 100644 index b6bff30a..00000000 --- a/docs/ands/algorithms/dp/max_sum_contiguous_subsequence.m.html +++ /dev/null @@ -1,1417 +0,0 @@ - - - - - - ands.algorithms.dp.max_sum_contiguous_subsequence API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.max_sum_contiguous_subsequence module

-

Author: Nelson Brochado -Creation: 04/09/15

-

Based on: https://tkramesh.wordpress.com/2011/03/09/dynamic-programming-maximum-sum-contiguous-subsequence/

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 04/09/15
-
-Based on: https://tkramesh.wordpress.com/2011/03/09/dynamic-programming-maximum-sum-contiguous-subsequence/
-"""
-
-
-def brute_force_max_sum_contiguous_subsequence(s):
-    """Brute force approach to compute the sum of all subsequences of s.
-    There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.
-
-    Running time complexity: O(n^3).
-
-    :type s : list or tuple
-    """
-    _sum = _max = s[0]
-    start = end = 0
-
-    for i in range(0, len(s)):
-
-        for j in range(i, len(s)):
-
-            _sum = 0
-
-            for k in range(i, j + 1):
-                _sum += s[k]
-
-            if _sum > _max:
-                _max = _sum
-                start = i
-                end = j
-
-    return _max, start, end
-
-
-def better_brute_force_max_sum_contiguous_subsequence(s):
-    """Brute force approach to compute the sum of all subsequences of s.
-    There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.
-
-    Running time complexity: O(n^2).
-
-    :type s : list or tuple
-    """
-    _sum = _max = s[0]
-    start = end = 0
-
-    for i in range(0, len(s)):
-
-        _sum = 0
-
-        for j in range(i, len(s)):
-            _sum += s[j]
-
-            # We need to update every iteration of the inner loop,
-            # because we need to check if from i to j
-            # we have found a better sum...
-            if _sum > _max:
-                _max = _sum
-                start = i
-                end = j
-
-    return _max, start, end
-
-
-def bottom_up_max_sum_contiguous_subsequence(s):
-    """
-    Let sum[k] denote the max contiguous sequence ending at k.
-    So, sum[k + 1] = max(s[k], sum[k] + s[k]).
-    sum[0] = s[0]
-
-    To keep track where the max contiguous subsequence starts,
-    we use another array.
-
-    Time complexity: O(n).
-    Space complexity: O(n).
-
-    :type s : list
-    """
-    indices = [0] * len(s)
-
-    _sum = [0] * len(s)
-    _sum[0] = s[0]
-    _max = _sum[0]
-
-    start = end = 0
-
-    for i in range(1, len(s)):
-
-        if _sum[i - 1] > 0:
-            _sum[i] = _sum[i - 1] + s[i]
-            indices[i] = indices[i - 1]
-
-        else:
-            _sum[i] = s[i]
-            indices[i] = i
-
-        if _sum[i] > _max:
-            _max = _sum[i]
-            end = i
-            start = indices[i]
-
-    return _max, start, end
-
-
-def better_bottom_up_max_sum_contiguous_subsequence(s):
-    """Returns a tuple or three elements (sum, start index, ending index),
-    where sum is the sum of the maximum contiguous subsequence,
-    start index is the starting index of the subsequence,
-    and ending index is the corresponding ending index.
-
-    Let sum[k] denote the max contiguous sequence ending at k.
-    So, sum[k + 1] = max(s[k], sum[k] + s[k]).
-    sum[0] = s[0]
-
-    To keep track where the max contiguous subsequence starts,
-    we use another array.
-
-    Time complexity: O(n).
-    Space complexity: O(1).
-
-    :type s : list
-    """
-    _max = s[0]
-    _sum = s[0]
-    index = 0
-
-    start = end = 0
-
-    for i in range(1, len(s)):
-
-        if _sum > 0:
-            _sum = _sum + s[i]
-        else:
-            _sum = s[i]
-            index = i
-
-        if _sum > _max:
-            _max = _sum
-            end = i
-            start = index
-
-    return _max, start, end
-
-
-if __name__ == "__main__":
-    seq = [4, 2, -4]
-
-    print(brute_force_max_sum_contiguous_subsequence(seq))
-    print(better_brute_force_max_sum_contiguous_subsequence(seq))
-    print(bottom_up_max_sum_contiguous_subsequence(seq))
-    print(better_bottom_up_max_sum_contiguous_subsequence(seq))
-
-
- -
- -
- -

Functions

- -
-
-

def better_bottom_up_max_sum_contiguous_subsequence(

s)

-
- - - - -

Returns a tuple or three elements (sum, start index, ending index), -where sum is the sum of the maximum contiguous subsequence, -start index is the starting index of the subsequence, -and ending index is the corresponding ending index.

-

Let sum[k] denote the max contiguous sequence ending at k. -So, sum[k + 1] = max(s[k], sum[k] + s[k]). -sum[0] = s[0]

-

To keep track where the max contiguous subsequence starts, -we use another array.

-

Time complexity: O(n). -Space complexity: O(1).

-

:type s : list

-
- -
-
def better_bottom_up_max_sum_contiguous_subsequence(s):
-    """Returns a tuple or three elements (sum, start index, ending index),
-    where sum is the sum of the maximum contiguous subsequence,
-    start index is the starting index of the subsequence,
-    and ending index is the corresponding ending index.
-
-    Let sum[k] denote the max contiguous sequence ending at k.
-    So, sum[k + 1] = max(s[k], sum[k] + s[k]).
-    sum[0] = s[0]
-
-    To keep track where the max contiguous subsequence starts,
-    we use another array.
-
-    Time complexity: O(n).
-    Space complexity: O(1).
-
-    :type s : list
-    """
-    _max = s[0]
-    _sum = s[0]
-    index = 0
-
-    start = end = 0
-
-    for i in range(1, len(s)):
-
-        if _sum > 0:
-            _sum = _sum + s[i]
-        else:
-            _sum = s[i]
-            index = i
-
-        if _sum > _max:
-            _max = _sum
-            end = i
-            start = index
-
-    return _max, start, end
-
-
-
- -
- - -
-
-

def better_brute_force_max_sum_contiguous_subsequence(

s)

-
- - - - -

Brute force approach to compute the sum of all subsequences of s. -There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.

-

Running time complexity: O(n^2).

-

:type s : list or tuple

-
- -
-
def better_brute_force_max_sum_contiguous_subsequence(s):
-    """Brute force approach to compute the sum of all subsequences of s.
-    There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.
-
-    Running time complexity: O(n^2).
-
-    :type s : list or tuple
-    """
-    _sum = _max = s[0]
-    start = end = 0
-
-    for i in range(0, len(s)):
-
-        _sum = 0
-
-        for j in range(i, len(s)):
-            _sum += s[j]
-
-            # We need to update every iteration of the inner loop,
-            # because we need to check if from i to j
-            # we have found a better sum...
-            if _sum > _max:
-                _max = _sum
-                start = i
-                end = j
-
-    return _max, start, end
-
-
-
- -
- - -
-
-

def bottom_up_max_sum_contiguous_subsequence(

s)

-
- - - - -

Let sum[k] denote the max contiguous sequence ending at k. -So, sum[k + 1] = max(s[k], sum[k] + s[k]). -sum[0] = s[0]

-

To keep track where the max contiguous subsequence starts, -we use another array.

-

Time complexity: O(n). -Space complexity: O(n).

-

:type s : list

-
- -
-
def bottom_up_max_sum_contiguous_subsequence(s):
-    """
-    Let sum[k] denote the max contiguous sequence ending at k.
-    So, sum[k + 1] = max(s[k], sum[k] + s[k]).
-    sum[0] = s[0]
-
-    To keep track where the max contiguous subsequence starts,
-    we use another array.
-
-    Time complexity: O(n).
-    Space complexity: O(n).
-
-    :type s : list
-    """
-    indices = [0] * len(s)
-
-    _sum = [0] * len(s)
-    _sum[0] = s[0]
-    _max = _sum[0]
-
-    start = end = 0
-
-    for i in range(1, len(s)):
-
-        if _sum[i - 1] > 0:
-            _sum[i] = _sum[i - 1] + s[i]
-            indices[i] = indices[i - 1]
-
-        else:
-            _sum[i] = s[i]
-            indices[i] = i
-
-        if _sum[i] > _max:
-            _max = _sum[i]
-            end = i
-            start = indices[i]
-
-    return _max, start, end
-
-
-
- -
- - -
-
-

def brute_force_max_sum_contiguous_subsequence(

s)

-
- - - - -

Brute force approach to compute the sum of all subsequences of s. -There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.

-

Running time complexity: O(n^3).

-

:type s : list or tuple

-
- -
-
def brute_force_max_sum_contiguous_subsequence(s):
-    """Brute force approach to compute the sum of all subsequences of s.
-    There are n + (n - 1) + (n - 2) + ... + 1 different subsequences.
-
-    Running time complexity: O(n^3).
-
-    :type s : list or tuple
-    """
-    _sum = _max = s[0]
-    start = end = 0
-
-    for i in range(0, len(s)):
-
-        for j in range(i, len(s)):
-
-            _sum = 0
-
-            for k in range(i, j + 1):
-                _sum += s[k]
-
-            if _sum > _max:
-                _max = _sum
-                start = i
-                end = j
-
-    return _max, start, end
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/plus_sign_game.m.html b/docs/ands/algorithms/dp/plus_sign_game.m.html deleted file mode 100644 index a84a58dc..00000000 --- a/docs/ands/algorithms/dp/plus_sign_game.m.html +++ /dev/null @@ -1,1227 +0,0 @@ - - - - - - ands.algorithms.dp.plus_sign_game API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.plus_sign_game module

-

Author: Nelson Brochado -Creation: 01/09/15

-

Given a string s of numbers (s1 s2 s3...sn), -is it possible to insert some plus signs "+" -in the string so that the remaining expression -is equal to a certain number k?

-

Example: Is it possible to insert some + signs in 214, -so that the resulting expression is 25? -Yes, if we insert a + sign between 1 and 4, -that is 21 + 4, we obtain 25.

-

TODO: PROVE THAT THIS PROBLEM EXHIBITS OR NOT OPTIMAL SUBSTRUCTURE!

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 01/09/15
-
-Given a string s of numbers (s1 s2 s3...sn),
-is it possible to insert some plus signs "+"
-in the string so that the remaining expression
-is equal to a certain number k?
-
-Example: Is it possible to insert some + signs in 214,
-so that the resulting expression is 25?
-Yes, if we insert a + sign between 1 and 4,
-that is 21 + 4, we obtain 25.
-
-TODO: PROVE THAT THIS PROBLEM EXHIBITS OR NOT OPTIMAL SUBSTRUCTURE!
-"""
-
-
-def _generate_combinations_matrix(p, combinations):
-    m = [["" for _ in range(p)] for _ in range(combinations)]
-
-    c = combinations - 1
-    interval = combinations // 2
-
-    j = interval
-    sign = "+"
-
-    for i in range(p):
-
-        while c >= 0:
-
-            m[c][i] = sign
-            c -= 1
-            j -= 1
-
-            if j == 0:
-                j = interval
-                sign = "" if sign == "+" else "+"
-
-        sign = "+"
-        c = combinations - 1
-        interval //= 2
-        j = interval
-
-    return m
-
-
-def _build_expressions(string, m):
-    combinations = [["" for _ in range(len(m[0]))] for _ in range(len(m))]
-
-    for i, c in enumerate(m):
-
-        result = ""
-
-        for j, sign in enumerate(c):
-
-            if result == "":
-                result = string[j] + sign + string[j + 1]
-            else:
-                result += sign + string[j + 1]
-
-            combinations[i] = result
-
-    return combinations
-
-
-def _evaluate_combinations(combinations, k):
-    """Returns the right combination (if it exists),
-    among all the combinations in "combinations",
-    of inserting plus signs in a certain string of numbers s,
-    such that the resulting expression is equal to k.
-
-    If no combination yields k, None is returned.
-
-    :type combinations : list of str
-    :type k : int
-    """
-    for c in combinations:
-        if int(eval(c)) == k:
-            return c
-    return None
-
-
-def plus_sign_game(s, k):
-    """Given a string s of numbers (s1 s2 s3...sn),
-    is it possible to insert_key some plus signs "+"
-    in the string so that the remaining expression
-    is equal to a certain number k?
-
-    Example: Is it possible to insert_key some + signs in 214,
-    so that the resulting expression is 25?
-    Yes, if we insert_key a + sign between 1 and 4,
-    that is 21 + 4, we obtain 25.
-
-    :type s : str | int
-    :type k : int
-    """
-
-    s = str(s)
-
-    # Number of places where to place a + sign
-    p = len(s) - 1
-
-    # Number of possible combinations
-    combinations = 2 ** p
-
-    # Generating a matrix with all possible combinations
-    # of alternating between +  and no +
-    m = _generate_combinations_matrix(p, combinations)
-
-    # Building all possible expressions
-    combinations = _build_expressions(s, m)
-
-    return _evaluate_combinations(combinations, k)
-
-
-if __name__ == "__main__":
-    print(plus_sign_game("21347823", 2000))
-    print(plus_sign_game("214", 25))
-    print(plus_sign_game("1214", 26))
-    print(plus_sign_game("1214", 215))
-    print(plus_sign_game("1214", 125))
-
-    # Do not run this script with the following statement uncommented
-    # if you are in a hurry.
-    # print(plus_sign_game("646805736141599100791159198", 472004))
-
-
- -
- -
- -

Functions

- -
-
-

def plus_sign_game(

s, k)

-
- - - - -

Given a string s of numbers (s1 s2 s3...sn), -is it possible to insert_key some plus signs "+" -in the string so that the remaining expression -is equal to a certain number k?

-

Example: Is it possible to insert_key some + signs in 214, -so that the resulting expression is 25? -Yes, if we insert_key a + sign between 1 and 4, -that is 21 + 4, we obtain 25.

-

:type s : str | int -:type k : int

-
- -
-
def plus_sign_game(s, k):
-    """Given a string s of numbers (s1 s2 s3...sn),
-    is it possible to insert_key some plus signs "+"
-    in the string so that the remaining expression
-    is equal to a certain number k?
-
-    Example: Is it possible to insert_key some + signs in 214,
-    so that the resulting expression is 25?
-    Yes, if we insert_key a + sign between 1 and 4,
-    that is 21 + 4, we obtain 25.
-
-    :type s : str | int
-    :type k : int
-    """
-
-    s = str(s)
-
-    # Number of places where to place a + sign
-    p = len(s) - 1
-
-    # Number of possible combinations
-    combinations = 2 ** p
-
-    # Generating a matrix with all possible combinations
-    # of alternating between +  and no +
-    m = _generate_combinations_matrix(p, combinations)
-
-    # Building all possible expressions
-    combinations = _build_expressions(s, m)
-
-    return _evaluate_combinations(combinations, k)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/rod_cut.m.html b/docs/ands/algorithms/dp/rod_cut.m.html deleted file mode 100644 index d5130f15..00000000 --- a/docs/ands/algorithms/dp/rod_cut.m.html +++ /dev/null @@ -1,1580 +0,0 @@ - - - - - - ands.algorithms.dp.rod_cut API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.rod_cut module

-

Author: Nelson Brochado -Creation: 30/08/15 -Last update: 18/11/2016

-

References:

-
    -
  • http://www.radford.edu/~nokie/classes/360/dp-rod-cutting.html
  • -
  • Introduction to Algorithms (3rd edition) by CLSR
  • -
  • Slides by prof. E. Papadopoulou
  • -
- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 30/08/15
-Last update: 18/11/2016
-
-## References:
-- http://www.radford.edu/~nokie/classes/360/dp-rod-cutting.html
-- Introduction to Algorithms (3rd edition) by CLSR
-- Slides by prof. E. Papadopoulou
-"""
-
-import sys
-
-
-def recursive_rod_cut(prices, n):
-    """Returns the maximum revenue of cutting a rod of length n.
-
-    It does not return which rod pieces
-    you need to pick to obtain the maximum revenue.
-
-    The rod cutting problem is the following:
-    given a rod of length n,
-    and a table of prices p_i, for i = 0, 1, 2, 3, ...,
-    determine the maximum revenue r_n obtainable
-    by cutting up the rod and selling the pieces.
-
-    Note that if the price p_n
-    for a rod of length n is large enough,
-    an optimal solution may require no cutting at all.
-
-    We can cut up a rod of length n in 2^{n - 1} different ways,
-    since we have an independent option of cutting, or not cutting,
-    at distance i inches from the left end,
-    for i = 1, 2, ... , n - 1.
-
-    prices contains the prices for each rod of different length.
-    prices[0] would be the price of the rod of length 0 (not useful).
-    prices[1] would be the price of the rod of length 1,
-    prices[2] would be the price for a rod of length 2, and so on.
-
-    Running time complexity: O(2^n)
-
-    :type prices : list | tuple
-    :type n : int
-    """
-
-    if n == 0:  # Base case
-        return 0
-
-    max_revenue = -sys.maxsize
-
-    for i in range(1, n + 1):  # Last i is n.
-        max_revenue = max(max_revenue, prices[i] + recursive_rod_cut(prices, n - i))
-
-    return max_revenue
-
-
-def _memoized_rod_cut_aux(prices, n, revenues, s):
-    """Auxiliary function for the memoized_rod_cut function.
-
-    :type prices : list | tuple
-    :type n : int
-    :type revenues : list | tuple
-    """
-    # If the following condition is true,
-    # that would mean that the revenue
-    # for a rod of length n has already been "memoised",
-    # and we don't need to recompute it again,
-    # but we simply return it.
-    if revenues[n] >= 0:
-        return revenues[n]
-
-    max_revenue = -sys.maxsize
-
-    if n == 0:  # If the size of the rod is 0, then max_revenue is 0.
-        max_revenue = 0
-    else:
-        for i in range(1, n + 1):
-            q = _memoized_rod_cut_aux(prices, n - i, revenues, s)
-
-            if prices[i] + q > max_revenue:
-                max_revenue = prices[i] + q
-                s[n] = i
-
-    # Memoising the maximum revenue for sub-problem with a rod of length n.
-    revenues[n] = max_revenue
-
-    return max_revenue
-
-
-def memoized_rod_cut(prices, n):
-    """_Top-down_ dynamic programming version of `recursive_rod_cut`,
-    using _memoisation_ to store sub problems' solutions.
-    _Memoisation_ is basically the name to the technique 
-    of storing what it's been computed previously.
-
-    In this algorithm, as opppose to the plain recursive one,
-    instead of repeatedly solving the same subproblems,
-    we store the solution to a subproblem in a table,
-    the first time we solve the subproblem,
-    so that this solution can simply be looked up, if needed again.
-
-    The disadvantge of this solution is that we need additional memory,
-    i.e., a table, to store intermediary solutions.
-
-    Running time complexity: theta(n^2)
-
-    :type prices : list | tuple
-    :type n : int
-    :rtype : int
-    """
-
-    # Initialing the revenues list f
-    # or the sub-problems length i = 0, 1, 2, ... , n
-    # to a small and negative number,
-    # which simply means that we have not yet computed
-    # the revenue for those sub-problems.
-    # Note that revenue values are always nonnegative,
-    # unless prices contain negative numbers.
-    revenues = [-sys.maxsize] * (n + 1)
-
-    # optimal first cut for rods of length 0..n
-    s = [0] * (n + 1)
-    
-    return _memoized_rod_cut_aux(prices, n, revenues, s), s
-
-
-def bottom_up_rod_cut(prices, n):
-    """_Bottom-up_ dynamic programming solution to the rod cut problem.
-
-    Running time complexity: theta(n^2)
-
-    :type prices : list | tuple
-    :type n : int
-    """
-    revenues = [-sys.maxsize] * (n + 1)
-    revenues[0] = 0  # Revenue for rod of length 0 is 0.
-
-    for i in range(1, n + 1):        
-        max_revenue = -sys.maxsize
-
-        for j in range(1, i + 1): # Find the max cut position for length i
-            max_revenue = max(max_revenue, prices[j] + revenues[i - j])
-
-        revenues[i] = max_revenue
-
-    return revenues[n]
-
-
-def extended_bottom_up_rod_cut(prices, n):
-    """Dynamic programming solution to the rod cut problem.
-
-    This dynamic programming version uses a bottom-up approach.
-
-    It returns a tuple, whose first item is a list of the revenues
-    and second is a list containing the rod pieces that are used in the revenue.
-
-    Running time complexity: O(n^2)
-
-    :type prices : list
-    :type n : int
-    :rtype : tuple
-    """
-    revenues = [-sys.maxsize] * (n + 1)
-    s = [[]] * (n + 1) # Used to store the optimal choices
-
-    revenues[0] = 0
-    s[0] = [0]
-
-    for i in range(1, n + 1):
-
-        max_revenue = -sys.maxsize
-
-        for j in range(1, i + 1):
-            # Note that j + (i - j) = i.
-            # What does this mean, or why should this fact be useful?
-            # Note that at each iteration of the outer loop,
-            # we are trying to find the max_revenue for a rod of length i
-            # (and we want also to find which items we are including to obtain that max_revenue).
-            # To obtain a rod of size i, we need at least 2 other smaller rods,
-            # unless we do not cut the rod.
-            # Now, to obtain a rod of length i,
-            # we need to insert together a rod of length j < i and a rod of length i - j < j,
-            # because j + (i - j) = i, as we stated at the beginning.
-            if max_revenue < prices[j] + revenues[i - j]:
-
-                max_revenue = prices[j] + revenues[i - j]
-
-                if revenues[i - j] != 0:
-                    s[i] = [j] + s[i - j]
-                else:
-                    # revenue[i] (current) uses a rod of length j
-                    # left most cut is at j
-                    s[i] = [j]  
-
-        revenues[i] = max_revenue
-
-    return revenues, s
-
-
-def rod_cut_solution_print(prices, n, s):
-    """prices is the list of initial prices.
-    n is the number of those prices - 1.
-    s is the solution returned by memoized_rod_cut."""
-    while n > 0:
-        print(s[n], end=" ")
-        n = n - s[n]
-    print()
-
-
-if __name__ == "__main__":
-    p1 = [0, 1, 5, 8, 9, 10, 17, 17, 20]
-
-    def test0():
-        r = recursive_rod_cut(p1, len(p1) - 1)
-        print("Revenue:", r)
-        print("--------------------------------------------")
-
-    def test1():
-        r, s = memoized_rod_cut(p1, len(p1) - 1)
-        print("Revenue:", r)
-        print("s:", s) 
-        rod_cut_solution_print(p1, len(p1) - 1, s)
-        print("--------------------------------------------")
-
-    def test2():
-        r = bottom_up_rod_cut(p1, len(p1) - 1)
-        print("Revenue:", r)
-        print("--------------------------------------------")
-
-    def test3():
-        r, s = extended_bottom_up_rod_cut(p1, len(p1) - 1)
-        print("Revenues:", r)
-        print("s:", s)
-        print("--------------------------------------------")
-
-    test0()
-    test1()
-    test2()
-    test3()
-
-
- -
- -
- -

Functions

- -
-
-

def bottom_up_rod_cut(

prices, n)

-
- - - - -

Bottom-up dynamic programming solution to the rod cut problem.

-

Running time complexity: theta(n^2)

-

:type prices : list | tuple -:type n : int

-
- -
-
def bottom_up_rod_cut(prices, n):
-    """_Bottom-up_ dynamic programming solution to the rod cut problem.
-
-    Running time complexity: theta(n^2)
-
-    :type prices : list | tuple
-    :type n : int
-    """
-    revenues = [-sys.maxsize] * (n + 1)
-    revenues[0] = 0  # Revenue for rod of length 0 is 0.
-
-    for i in range(1, n + 1):        
-        max_revenue = -sys.maxsize
-
-        for j in range(1, i + 1): # Find the max cut position for length i
-            max_revenue = max(max_revenue, prices[j] + revenues[i - j])
-
-        revenues[i] = max_revenue
-
-    return revenues[n]
-
-
-
- -
- - -
-
-

def extended_bottom_up_rod_cut(

prices, n)

-
- - - - -

Dynamic programming solution to the rod cut problem.

-

This dynamic programming version uses a bottom-up approach.

-

It returns a tuple, whose first item is a list of the revenues -and second is a list containing the rod pieces that are used in the revenue.

-

Running time complexity: O(n^2)

-

:type prices : list -:type n : int -:rtype : tuple

-
- -
-
def extended_bottom_up_rod_cut(prices, n):
-    """Dynamic programming solution to the rod cut problem.
-
-    This dynamic programming version uses a bottom-up approach.
-
-    It returns a tuple, whose first item is a list of the revenues
-    and second is a list containing the rod pieces that are used in the revenue.
-
-    Running time complexity: O(n^2)
-
-    :type prices : list
-    :type n : int
-    :rtype : tuple
-    """
-    revenues = [-sys.maxsize] * (n + 1)
-    s = [[]] * (n + 1) # Used to store the optimal choices
-
-    revenues[0] = 0
-    s[0] = [0]
-
-    for i in range(1, n + 1):
-
-        max_revenue = -sys.maxsize
-
-        for j in range(1, i + 1):
-            # Note that j + (i - j) = i.
-            # What does this mean, or why should this fact be useful?
-            # Note that at each iteration of the outer loop,
-            # we are trying to find the max_revenue for a rod of length i
-            # (and we want also to find which items we are including to obtain that max_revenue).
-            # To obtain a rod of size i, we need at least 2 other smaller rods,
-            # unless we do not cut the rod.
-            # Now, to obtain a rod of length i,
-            # we need to insert together a rod of length j < i and a rod of length i - j < j,
-            # because j + (i - j) = i, as we stated at the beginning.
-            if max_revenue < prices[j] + revenues[i - j]:
-
-                max_revenue = prices[j] + revenues[i - j]
-
-                if revenues[i - j] != 0:
-                    s[i] = [j] + s[i - j]
-                else:
-                    # revenue[i] (current) uses a rod of length j
-                    # left most cut is at j
-                    s[i] = [j]  
-
-        revenues[i] = max_revenue
-
-    return revenues, s
-
-
-
- -
- - -
-
-

def memoized_rod_cut(

prices, n)

-
- - - - -

Top-down dynamic programming version of recursive_rod_cut, -using memoisation to store sub problems' solutions. -Memoisation is basically the name to the technique -of storing what it's been computed previously.

-

In this algorithm, as opppose to the plain recursive one, -instead of repeatedly solving the same subproblems, -we store the solution to a subproblem in a table, -the first time we solve the subproblem, -so that this solution can simply be looked up, if needed again.

-

The disadvantge of this solution is that we need additional memory, -i.e., a table, to store intermediary solutions.

-

Running time complexity: theta(n^2)

-

:type prices : list | tuple -:type n : int -:rtype : int

-
- -
-
def memoized_rod_cut(prices, n):
-    """_Top-down_ dynamic programming version of `recursive_rod_cut`,
-    using _memoisation_ to store sub problems' solutions.
-    _Memoisation_ is basically the name to the technique 
-    of storing what it's been computed previously.
-
-    In this algorithm, as opppose to the plain recursive one,
-    instead of repeatedly solving the same subproblems,
-    we store the solution to a subproblem in a table,
-    the first time we solve the subproblem,
-    so that this solution can simply be looked up, if needed again.
-
-    The disadvantge of this solution is that we need additional memory,
-    i.e., a table, to store intermediary solutions.
-
-    Running time complexity: theta(n^2)
-
-    :type prices : list | tuple
-    :type n : int
-    :rtype : int
-    """
-
-    # Initialing the revenues list f
-    # or the sub-problems length i = 0, 1, 2, ... , n
-    # to a small and negative number,
-    # which simply means that we have not yet computed
-    # the revenue for those sub-problems.
-    # Note that revenue values are always nonnegative,
-    # unless prices contain negative numbers.
-    revenues = [-sys.maxsize] * (n + 1)
-
-    # optimal first cut for rods of length 0..n
-    s = [0] * (n + 1)
-    
-    return _memoized_rod_cut_aux(prices, n, revenues, s), s
-
-
-
- -
- - -
-
-

def recursive_rod_cut(

prices, n)

-
- - - - -

Returns the maximum revenue of cutting a rod of length n.

-

It does not return which rod pieces -you need to pick to obtain the maximum revenue.

-

The rod cutting problem is the following: -given a rod of length n, -and a table of prices p_i, for i = 0, 1, 2, 3, ..., -determine the maximum revenue r_n obtainable -by cutting up the rod and selling the pieces.

-

Note that if the price p_n -for a rod of length n is large enough, -an optimal solution may require no cutting at all.

-

We can cut up a rod of length n in 2^{n - 1} different ways, -since we have an independent option of cutting, or not cutting, -at distance i inches from the left end, -for i = 1, 2, ... , n - 1.

-

prices contains the prices for each rod of different length. -prices[0] would be the price of the rod of length 0 (not useful). -prices[1] would be the price of the rod of length 1, -prices[2] would be the price for a rod of length 2, and so on.

-

Running time complexity: O(2^n)

-

:type prices : list | tuple -:type n : int

-
- -
-
def recursive_rod_cut(prices, n):
-    """Returns the maximum revenue of cutting a rod of length n.
-
-    It does not return which rod pieces
-    you need to pick to obtain the maximum revenue.
-
-    The rod cutting problem is the following:
-    given a rod of length n,
-    and a table of prices p_i, for i = 0, 1, 2, 3, ...,
-    determine the maximum revenue r_n obtainable
-    by cutting up the rod and selling the pieces.
-
-    Note that if the price p_n
-    for a rod of length n is large enough,
-    an optimal solution may require no cutting at all.
-
-    We can cut up a rod of length n in 2^{n - 1} different ways,
-    since we have an independent option of cutting, or not cutting,
-    at distance i inches from the left end,
-    for i = 1, 2, ... , n - 1.
-
-    prices contains the prices for each rod of different length.
-    prices[0] would be the price of the rod of length 0 (not useful).
-    prices[1] would be the price of the rod of length 1,
-    prices[2] would be the price for a rod of length 2, and so on.
-
-    Running time complexity: O(2^n)
-
-    :type prices : list | tuple
-    :type n : int
-    """
-
-    if n == 0:  # Base case
-        return 0
-
-    max_revenue = -sys.maxsize
-
-    for i in range(1, n + 1):  # Last i is n.
-        max_revenue = max(max_revenue, prices[i] + recursive_rod_cut(prices, n - i))
-
-    return max_revenue
-
-
-
- -
- - -
-
-

def rod_cut_solution_print(

prices, n, s)

-
- - - - -

prices is the list of initial prices. -n is the number of those prices - 1. -s is the solution returned by memoized_rod_cut.

-
- -
-
def rod_cut_solution_print(prices, n, s):
-    """prices is the list of initial prices.
-    n is the number of those prices - 1.
-    s is the solution returned by memoized_rod_cut."""
-    while n > 0:
-        print(s[n], end=" ")
-        n = n - s[n]
-    print()
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/subset_sum.m.html b/docs/ands/algorithms/dp/subset_sum.m.html deleted file mode 100644 index 575c8dd0..00000000 --- a/docs/ands/algorithms/dp/subset_sum.m.html +++ /dev/null @@ -1,1246 +0,0 @@ - - - - - - ands.algorithms.dp.subset_sum API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.subset_sum module

-

Author: Nelson Brochado -Creation: 03/09/15

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 03/09/15
-"""
-
-from pprint import pprint
-
-
-def m_print(m):
-    pprint(m)
-    input()
-
-
-def _check_negativeness(subset):
-    """Returns the largest negative number in the subset.
-    If no negative number exists, it returns 0."""
-    s = 0
-    for n in subset:
-        if n < s:  # Initially s is 0
-            s = n
-    return s
-
-
-def _shift_numbers(subset, smallest):
-    m = -smallest
-    for i, _ in enumerate(subset):
-        subset[i] += m
-
-
-def _recursive_subset_sum_aux(subset, current_sum, index, n, solution):
-    if current_sum == n:  # Solution was found...
-        print("Subset found.")
-
-        for i, s in enumerate(solution):
-            if s == 1:
-                print(subset[i])
-
-    elif index == len(subset):
-        return
-    else:
-        # Include the current ith element
-        solution[index] = 1
-        current_sum += subset[index]
-        _recursive_subset_sum_aux(subset, current_sum, index + 1, n, solution)
-
-        # do not to include the ith element
-        solution[index] = 0
-        current_sum -= subset[index]
-        _recursive_subset_sum_aux(subset, current_sum, index + 1, n, solution)
-
-
-def recursive_subset_sum(subset, s):
-    # Allows negative numbers too...
-    c_sum = 0
-    i = 0
-    solution = [0] * len(subset)
-
-    return _recursive_subset_sum_aux(subset, c_sum, i, s, solution)
-
-
-def _get_subset_sum_matrix(subset, s):
-    m = [[0 for _ in range(s + 1)] for _ in range(len(subset) + 1)]
-
-    for i in range(1, s + 1):
-        m[0][i] = 0
-
-    for j in range(0, len(subset) + 1):
-        m[j][0] = 1
-
-    return m
-
-
-def bottom_up_subset_sum(subset, s, return_matrix=False):
-    """Returns 1 if there's a subset
-    whose sum of the numbers is equal to s,
-    if return_matrix == True,
-    else it returns the matrix used during the computation.
-
-    NOTE: the subset can only contain positive integers!
-
-    :type subset : list or tuple
-    :type s : int
-    """
-
-    m = _get_subset_sum_matrix(subset, s)
-
-    for i in range(1, len(subset) + 1):
-
-        for j in range(1, s + 1):
-
-            if subset[i - 1] == j:
-                m[i][j] = 1
-            else:
-                # We can include the current element,
-                # because it is less than the current number j.
-                if subset[i - 1] <= j:
-                    m[i][j] = max(m[i - 1][j], m[i - 1][j - subset[i - 1]])
-                else:
-                    m[i][j] = m[i - 1][j]
-
-    return m[-1][-1] if not return_matrix else m
-
-
-if __name__ == "__main__":
-    # print(bottom_up_subset_sum((1, 3, 5, 5, 2, 1, 1, 6), 12))
-
-    pprint(bottom_up_subset_sum([2, 2, 2, 6], 6, return_matrix=True))
-
-    print(bottom_up_subset_sum((1, 1, 6), 2, return_matrix=True))
-
-    recursive_subset_sum([-2, 8, 6], 6)
-    # recursive_subset_sum((4, 2, 6), 6)
-    # recursive_subset_sum((0, 0, 6), 6)
-    # recursive_subset_sum((1, 3, 5, 5, 2, 1, 1, 6), 12)
-
-
- -
- -
- -

Functions

- -
-
-

def bottom_up_subset_sum(

subset, s, return_matrix=False)

-
- - - - -

Returns 1 if there's a subset -whose sum of the numbers is equal to s, -if return_matrix == True, -else it returns the matrix used during the computation.

-

NOTE: the subset can only contain positive integers!

-

:type subset : list or tuple -:type s : int

-
- -
-
def bottom_up_subset_sum(subset, s, return_matrix=False):
-    """Returns 1 if there's a subset
-    whose sum of the numbers is equal to s,
-    if return_matrix == True,
-    else it returns the matrix used during the computation.
-
-    NOTE: the subset can only contain positive integers!
-
-    :type subset : list or tuple
-    :type s : int
-    """
-
-    m = _get_subset_sum_matrix(subset, s)
-
-    for i in range(1, len(subset) + 1):
-
-        for j in range(1, s + 1):
-
-            if subset[i - 1] == j:
-                m[i][j] = 1
-            else:
-                # We can include the current element,
-                # because it is less than the current number j.
-                if subset[i - 1] <= j:
-                    m[i][j] = max(m[i - 1][j], m[i - 1][j - subset[i - 1]])
-                else:
-                    m[i][j] = m[i - 1][j]
-
-    return m[-1][-1] if not return_matrix else m
-
-
-
- -
- - -
-
-

def m_print(

m)

-
- - - - -
- -
-
def m_print(m):
-    pprint(m)
-    input()
-
-
-
- -
- - -
-
-

def recursive_subset_sum(

subset, s)

-
- - - - -
- -
-
def recursive_subset_sum(subset, s):
-    # Allows negative numbers too...
-    c_sum = 0
-    i = 0
-    solution = [0] * len(subset)
-
-    return _recursive_subset_sum_aux(subset, c_sum, i, s, solution)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/dp/zero_one_knapsack.m.html b/docs/ands/algorithms/dp/zero_one_knapsack.m.html deleted file mode 100644 index 6eaa4695..00000000 --- a/docs/ands/algorithms/dp/zero_one_knapsack.m.html +++ /dev/null @@ -1,1424 +0,0 @@ - - - - - - ands.algorithms.dp.zero_one_knapsack API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.dp.zero_one_knapsack module

-

Meta info

-

Author: Nelson Brochado -Created: 2015 -Updated: 26/01/2017

-

Description

-

Given n objects and a "knapsack". -Item i weighs w_i > 0 and has a value v_i > 0. -Knapsack has capacity W. -Goal: fill knapsack so as to maximize total value.

-

References

-
    -
  • Slides by prof. Evanthia Papadopoulou
  • -
- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-Created: 2015
-Updated: 26/01/2017
-
-## Description
-
-Given n objects and a "knapsack".
-Item i weighs w_i > 0 and has a value v_i > 0.
-Knapsack has capacity W.
-Goal: fill knapsack so as to maximize total value.
-
-## References
-
-- Slides by prof. Evanthia Papadopoulou
-
-"""
-
-from pprint import pprint
-
-
-def _get_zero_one_knapsack_matrix(total_weight: int, n: int) -> list:
-    """Returns a matrix for a dynamic programming solution to the 0/1 knapsack problem.
-
-    The first row of this matrix contains the numbers
-    corresponding to the weights of the (sub)problems.
-    The first column contains an enumeration of the items,
-    starting from the fact that we could not include any item,
-    and this is represented with a 0.
-
-    m[0][0] is 0 just because of the alignment,
-    it does make any logical sense for this purpose,
-    it could be None, or any other value.
-
-    :type total_weight : int
-    :type n : int
-    """
-    m = [[0 for _ in range(total_weight + 2)] for _ in range(n + 2)]
-
-    for x in range(1, total_weight + 2):
-        m[0][x] = x - 1
-
-    for j in range(1, n + 2):
-        m[j][0] = j - 1
-        m[j][1] = 0
-
-    return m
-
-
-def zero_one_knapsack_verbose(total_weight: int, weights, values) -> int:
-    """Verbose version of zero_one_knapsack."""
-    assert len(weights) == len(values)
-    assert total_weight >= 0
-
-    n = len(weights)
-
-    profits = _get_zero_one_knapsack_matrix(total_weight, n)
-
-    print("Initial empty profits matrix:\n")
-    pprint(profits)
-    print()
-
-    for i in range(2, n + 2):
-
-        for w in range(2, total_weight + 2):
-
-            print("-" * 30)
-            print("Weight of item", i - 1, ":", weights[i - 2])
-            print("Value of item", i - 1, ":", values[i - 2])
-            print("Current total weight:", w - 1)
-            print()
-
-            if weights[i - 2] > w - 1:
-                profits[i][w] = profits[i - 1][w]
-            else:
-                profits[i][w] = max(profits[i - 1][w], values[i - 2] + profits[i - 1][w - weights[i - 2]])
-
-            print("Profits matrix after calculation:\n")
-            pprint(profits)
-            input()
-
-    return profits[-1][-1]
-
-
-def zero_one_knapsack(total_weight: int, weights, values) -> int:
-    """Returns the maximum profit that can be obtained by
-    using items with weights and values and a total_weight.
-
-    This version does not tell which items to pick.
-
-    **Time complexity**: O(n * total_weight), where n is the number of items.
-    This is consider a pseudo-polynomial time algorithm, **not** polynomial!
-
-    The decision version of this problem is actually NP-Complete,
-    and the running time complexity above does not contradict it:
-    total_weight is not polynomial in the length of the input!
-
-    :type weights : list | tuple
-    :type values : list | tuple
-    """
-    assert len(weights) == len(values)
-    assert total_weight >= 0
-
-    n = len(weights)
-
-    profits = _get_zero_one_knapsack_matrix(total_weight, n)
-
-    # Iterating through the items
-    for i in range(2, n + 2):
-
-        # Iterating through the weights
-        for w in range(2, total_weight + 2):
-
-            # If the weight of the (i - 2)th item is greater than w - 1,
-            # which is the current weight being analysed.
-            # Note that the weights in the matrix `profits` are shifted to the right by 1.
-            if weights[i - 2] > w - 1:
-                profits[i][w] = profits[i - 1][w]
-            else:
-                # Note: indices in the `profits` matrix are also shifted 2 positions to the bottom.
-
-                # The weight of the current item is less (or equal) than the total weight,
-                # but we need to decide if it is convenient to include this item or not.
-                # To do this, we compare if we gain more by including it or not.
-
-                # `profits[i - 1][w]` refers to the profit of not including current item.
-                # `values[i - 2]` refers to the value of the current item.
-                # `w - weights[i - 2]` is the remaining weight, if we include the current item.
-                # Note: `weights[i - 2]` is the weight of the current item.
-                profits[i][w] = max(profits[i - 1][w], values[i - 2] + profits[i - 1][w - weights[i - 2]])
-
-    return profits[-1][-1]
-
-
-def _recursive_01_knapsack_aux(capacity: int, w, v, value: int) -> int:
-    """Either takes the last element or it doesn't.
-
-    This algorithm takes exponential time.
-    """
-    if capacity == 0:
-        return 0
-    if len(w) > 0 and len(v) > 0:
-        if w[-1] > capacity:  # We cannot include the nth item
-            value = _recursive_01_knapsack_aux(capacity, w[:-1], v[:-1], value)
-        else:
-            value = max(v[-1] + _recursive_01_knapsack_aux(capacity - w[-1], w[:-1], v[:-1], value),
-                        _recursive_01_knapsack_aux(capacity, w[:-1], v[:-1], value))
-    return value
-
-
-def recursive_01_knapsack(total_weight: int, weights, values):
-    assert len(weights) == len(values)
-    assert total_weight >= 0
-    
-    value = 0
-    return _recursive_01_knapsack_aux(total_weight, weights, values, value)
-
-
-def _memoized_01_knapsack_aux(capacity: int, w, v, value: int, m: list) -> int:
-    """Either takes the last element or it doesn't.
-
-    Memoization version of _recursive_01_knapsack_aux
-    """
-    if capacity == 0:
-        return 0
-
-    if m[len(w) - 1][capacity - 1] is not None:
-        return m[len(w) - 1][capacity - 1]
-
-    if len(w) > 0 and len(v) > 0:
-
-        if w[-1] > capacity:  # We cannot include the nth item
-            value = _memoized_01_knapsack_aux(capacity, w[:-1], v[:-1], value, m)
-        else:
-            value = max(v[-1] + _memoized_01_knapsack_aux(capacity - w[-1], w[:-1], v[:-1], value, m),
-                        _memoized_01_knapsack_aux(capacity, w[:-1], v[:-1], value, m))
-
-    m[len(w) - 1][capacity - 1] = value
-
-    return value
-
-
-def memoized_01_knapsack(capacity: int, weights, values) -> int:
-    result = 0
-    m = [[None for _ in range(capacity)] for _ in range(len(weights))]
-    return _memoized_01_knapsack_aux(capacity, weights, values, result, m)
-
-
-if __name__ == "__main__":
-    tw = 7  # total weight that you can carry
-
-    ws = [1, 3, 4, 5]  # weights
-    vs = [1, 4, 5, 7]  # values
-
-    print(recursive_01_knapsack(tw, ws, vs))
-    print(memoized_01_knapsack(tw, ws, vs))
-    # print(zero_one_knapsack_verbose(tw, ws, vs))
-
-
- -
- -
- -

Functions

- -
-
-

def memoized_01_knapsack(

capacity, weights, values)

-
- - - - -
- -
-
def memoized_01_knapsack(capacity: int, weights, values) -> int:
-    result = 0
-    m = [[None for _ in range(capacity)] for _ in range(len(weights))]
-    return _memoized_01_knapsack_aux(capacity, weights, values, result, m)
-
-
-
- -
- - -
-
-

def recursive_01_knapsack(

total_weight, weights, values)

-
- - - - -
- -
-
def recursive_01_knapsack(total_weight: int, weights, values):
-    assert len(weights) == len(values)
-    assert total_weight >= 0
-    
-    value = 0
-    return _recursive_01_knapsack_aux(total_weight, weights, values, value)
-
-
-
- -
- - -
-
-

def zero_one_knapsack(

total_weight, weights, values)

-
- - - - -

Returns the maximum profit that can be obtained by -using items with weights and values and a total_weight.

-

This version does not tell which items to pick.

-

Time complexity: O(n * total_weight), where n is the number of items. -This is consider a pseudo-polynomial time algorithm, not polynomial!

-

The decision version of this problem is actually NP-Complete, -and the running time complexity above does not contradict it: -total_weight is not polynomial in the length of the input!

-

:type weights : list | tuple -:type values : list | tuple

-
- -
-
def zero_one_knapsack(total_weight: int, weights, values) -> int:
-    """Returns the maximum profit that can be obtained by
-    using items with weights and values and a total_weight.
-
-    This version does not tell which items to pick.
-
-    **Time complexity**: O(n * total_weight), where n is the number of items.
-    This is consider a pseudo-polynomial time algorithm, **not** polynomial!
-
-    The decision version of this problem is actually NP-Complete,
-    and the running time complexity above does not contradict it:
-    total_weight is not polynomial in the length of the input!
-
-    :type weights : list | tuple
-    :type values : list | tuple
-    """
-    assert len(weights) == len(values)
-    assert total_weight >= 0
-
-    n = len(weights)
-
-    profits = _get_zero_one_knapsack_matrix(total_weight, n)
-
-    # Iterating through the items
-    for i in range(2, n + 2):
-
-        # Iterating through the weights
-        for w in range(2, total_weight + 2):
-
-            # If the weight of the (i - 2)th item is greater than w - 1,
-            # which is the current weight being analysed.
-            # Note that the weights in the matrix `profits` are shifted to the right by 1.
-            if weights[i - 2] > w - 1:
-                profits[i][w] = profits[i - 1][w]
-            else:
-                # Note: indices in the `profits` matrix are also shifted 2 positions to the bottom.
-
-                # The weight of the current item is less (or equal) than the total weight,
-                # but we need to decide if it is convenient to include this item or not.
-                # To do this, we compare if we gain more by including it or not.
-
-                # `profits[i - 1][w]` refers to the profit of not including current item.
-                # `values[i - 2]` refers to the value of the current item.
-                # `w - weights[i - 2]` is the remaining weight, if we include the current item.
-                # Note: `weights[i - 2]` is the weight of the current item.
-                profits[i][w] = max(profits[i - 1][w], values[i - 2] + profits[i - 1][w - weights[i - 2]])
-
-    return profits[-1][-1]
-
-
-
- -
- - -
-
-

def zero_one_knapsack_verbose(

total_weight, weights, values)

-
- - - - -

Verbose version of zero_one_knapsack.

-
- -
-
def zero_one_knapsack_verbose(total_weight: int, weights, values) -> int:
-    """Verbose version of zero_one_knapsack."""
-    assert len(weights) == len(values)
-    assert total_weight >= 0
-
-    n = len(weights)
-
-    profits = _get_zero_one_knapsack_matrix(total_weight, n)
-
-    print("Initial empty profits matrix:\n")
-    pprint(profits)
-    print()
-
-    for i in range(2, n + 2):
-
-        for w in range(2, total_weight + 2):
-
-            print("-" * 30)
-            print("Weight of item", i - 1, ":", weights[i - 2])
-            print("Value of item", i - 1, ":", values[i - 2])
-            print("Current total weight:", w - 1)
-            print()
-
-            if weights[i - 2] > w - 1:
-                profits[i][w] = profits[i - 1][w]
-            else:
-                profits[i][w] = max(profits[i - 1][w], values[i - 2] + profits[i - 1][w - weights[i - 2]])
-
-            print("Profits matrix after calculation:\n")
-            pprint(profits)
-            input()
-
-    return profits[-1][-1]
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/graphs/best_team_of_three.m.html b/docs/ands/algorithms/graphs/best_team_of_three.m.html deleted file mode 100644 index c6f7e686..00000000 --- a/docs/ands/algorithms/graphs/best_team_of_three.m.html +++ /dev/null @@ -1,1261 +0,0 @@ - - - - - - ands.algorithms.graphs.best_team_of_three API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.graphs.best_team_of_three module

-

Author: Nelson Brochado -Creation: 06/09/15, 12:37

-

My solution to exercise 139 from the series of exercises by prof. A. Carzaniga

-

Consider a weighted undirected graph G = (V, E) -representing a group of programmers and their affinity for team work, -such that the weight w(e) of an edge e = (u, v) is a number -representing the ability of programmers u and v -to work together on the same project.

-

Write an algorithm Best-Team-Of-Three -that outputs the best team of three programmers.

-

The value of a team is considered to be the lowest affinity level -between any two members of the team.

-

So, the best team is the group of programmers -for which the lowest affinity level between members of the group is maximal.

-

Ideas: -Basically we are going to have graph with weighted edges. -The weight of these edges represents the affinity -between the nodes (or programmers) of the edge.

-

As far as I have understood, two programmers work best together, -if their affinity or the weight of the edge between them is high. -But the overall affinity of the group is the smallest of the affinities.

-

We need to find some kind of triangle in an undirected graph. -A triangle can be found using dfs.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 06/09/15, 12:37
-
-My solution to exercise 139 from the series of exercises by prof. A. Carzaniga
-
-Consider a weighted undirected graph G = (V, E)
-representing a group of programmers and their affinity for team work,
-such that the weight w(e) of an edge e = (u, v) is a number
-representing the ability of programmers u and v
-to work together on the same project.
-
-Write an algorithm Best-Team-Of-Three
-that outputs the best team of three programmers.
-
-The value of a team is considered to be the lowest affinity level
-between any two members of the team.
-
-So, the best team is the group of programmers
-for which the lowest affinity level between members of the group is maximal.
-
-Ideas:
-Basically we are going to have graph with weighted edges.
-The weight of these edges represents the affinity
-between the nodes (or programmers) of the edge.
-
-As far as I have understood, two programmers work best together,
-if their affinity or the weight of the edge between them is high.
-But the overall affinity of the group is the smallest of the affinities.
-
-We need to find some kind of triangle in an undirected graph.
-A triangle can be found using dfs.
-"""
-
-import sys
-
-from ands.ds.Graph import *
-
-
-def is_an_edge(u, v):
-    return v in u.get_adjacent_nodes()
-
-
-def best_team_of_three(g):
-    """Returns a set of programmers (nodes) with the best affinity.
-
-    :type g : Graph
-    """
-    best_3 = set()
-    max_lowest_affinity = -sys.maxsize
-
-    for u in g.nodes:
-
-        for v in u.get_adjacent_nodes():
-
-            for z in v.get_adjacent_nodes():
-
-                if is_an_edge(z, u):
-
-                    triple = (u, v, z)
-
-                    x = min(g.weight(u, v), g.weight(v, z), g.weight(z, u))
-
-                    if x > max_lowest_affinity:
-                        max_lowest_affinity = x
-                        best_3.clear()
-                        best_3 = best_3.union(triple)
-    return best_3
-
-
-if __name__ == "__main__":
-    g = Graph()
-
-    a = GraphNode("a")
-    b = GraphNode("b")
-    c = GraphNode("c")
-    d = GraphNode("d")
-    e = GraphNode("e")
-
-    g.add_undirected_edge(a, d, 1)
-    g.add_undirected_edge(a, b, 2)
-    g.add_undirected_edge(a, c, 2)
-
-    g.add_undirected_edge(d, b, 3)
-    g.add_undirected_edge(b, c, 3)
-
-    g.add_undirected_edge(d, e, 4)
-    g.add_undirected_edge(e, b, 5)
-
-    print(best_team_of_three(g))
-
-
- -
- -
-

Module variables

-
-

var BLACK

- - -
-
- -
-
-

var GREY

- - -
-
- -
-
-

var INFINITY

- - -
-
- -
-
-

var NIL

- - -
-
- -
-
-

var WHITE

- - -
-
- -
- -

Functions

- -
-
-

def best_team_of_three(

g)

-
- - - - -

Returns a set of programmers (nodes) with the best affinity.

-

:type g : Graph

-
- -
-
def best_team_of_three(g):
-    """Returns a set of programmers (nodes) with the best affinity.
-
-    :type g : Graph
-    """
-    best_3 = set()
-    max_lowest_affinity = -sys.maxsize
-
-    for u in g.nodes:
-
-        for v in u.get_adjacent_nodes():
-
-            for z in v.get_adjacent_nodes():
-
-                if is_an_edge(z, u):
-
-                    triple = (u, v, z)
-
-                    x = min(g.weight(u, v), g.weight(v, z), g.weight(z, u))
-
-                    if x > max_lowest_affinity:
-                        max_lowest_affinity = x
-                        best_3.clear()
-                        best_3 = best_3.union(triple)
-    return best_3
-
-
-
- -
- - -
-
-

def is_an_edge(

u, v)

-
- - - - -
- -
-
def is_an_edge(u, v):
-    return v in u.get_adjacent_nodes()
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/graphs/build_shortest_path.m.html b/docs/ands/algorithms/graphs/build_shortest_path.m.html deleted file mode 100644 index 42bccaf6..00000000 --- a/docs/ands/algorithms/graphs/build_shortest_path.m.html +++ /dev/null @@ -1,1134 +0,0 @@ - - - - - - ands.algorithms.graphs.build_shortest_path API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.graphs.build_shortest_path module

-

Author: Nelson Brochado -Creation: 29/08/15, 17:57

-

build_shortest_path can be used -to construct a list of nodes -that represent the shortest path -from a source node to a destination node.

-

Note that you should call this function -only after an algorithm, such as BFS, -has run on the graph, -i.e. precedecessors are set correctly.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 29/08/15, 17:57
-
-build_shortest_path can be used
-to construct a list of nodes
-that represent the shortest path
-from a source node to a destination node.
-
-Note that you should call this function
-only after an algorithm, such as BFS,
-has run on the graph,
-i.e. precedecessors are set correctly.
-"""
-
-
-def build_shortest_path(destination):
-    """Returns a list with the nodes of the shortest path
-    from source to destination (both included).
-
-    source is the node from which the search started.
-
-    This function should be called only after
-    an algorithm for calculating the shortest path,
-    for example bfs or bellman_ford,
-    has calculated and updated the shortest distances
-    of every vertex from a source node.
-
-    :type destination : GraphNode
-    """
-
-    shortest_path = []
-    tmp = destination
-
-    while tmp is not None:
-        shortest_path.append(tmp)
-        tmp = tmp.predecessor
-
-    shortest_path.reverse()
-
-    return shortest_path
-
-
- -
- -
- -

Functions

- -
-
-

def build_shortest_path(

destination)

-
- - - - -

Returns a list with the nodes of the shortest path -from source to destination (both included).

-

source is the node from which the search started.

-

This function should be called only after -an algorithm for calculating the shortest path, -for example bfs or bellman_ford, -has calculated and updated the shortest distances -of every vertex from a source node.

-

:type destination : GraphNode

-
- -
-
def build_shortest_path(destination):
-    """Returns a list with the nodes of the shortest path
-    from source to destination (both included).
-
-    source is the node from which the search started.
-
-    This function should be called only after
-    an algorithm for calculating the shortest path,
-    for example bfs or bellman_ford,
-    has calculated and updated the shortest distances
-    of every vertex from a source node.
-
-    :type destination : GraphNode
-    """
-
-    shortest_path = []
-    tmp = destination
-
-    while tmp is not None:
-        shortest_path.append(tmp)
-        tmp = tmp.predecessor
-
-    shortest_path.reverse()
-
-    return shortest_path
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/graphs/dfs.m.html b/docs/ands/algorithms/graphs/dfs.m.html deleted file mode 100644 index 41b5329e..00000000 --- a/docs/ands/algorithms/graphs/dfs.m.html +++ /dev/null @@ -1,1456 +0,0 @@ - - - - - - ands.algorithms.graphs.dfs API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.graphs.dfs module

-

Author: Nelson Brochado -Creation: July, 2015

-

Implementation of depth-first search, -which uses starting and ending times of visiting nodes, -and keeps also track of the predecessor of each node, -in order to be able to backtrack from a node to the source, if necessary.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: July, 2015
-
-Implementation of depth-first search,
-which uses starting and ending times of visiting nodes,
-and keeps also track of the predecessor of each node,
-in order to be able to backtrack from a node to the source, if necessary.
-"""
-
-from ands.ds.Graph import *
-
-dfs_global_time = INFINITY
-
-
-def dfs_iterative(graph):
-    # TODO (eventually because it's complex...)
-    pass
-
-
-def dfs_aux(graph: Graph, n: GraphNode):
-    """This function is called by dfs to further explore child nodes."""
-    global dfs_global_time
-
-    n.color = GREY
-    dfs_global_time += 1
-    n.start = dfs_global_time
-
-    for v in n.get_adjacent_nodes():
-
-        if v.color == WHITE:
-            v.predecessor = n
-            dfs_aux(graph, v)
-
-    n.color = BLACK
-    dfs_global_time += 1
-    n.end = dfs_global_time
-
-
-def dfs(graph: Graph):
-    """Typical dfs algorithm that traverses all nodes
-    that have not been explored yet,
-    and keeps track of the visited and finished times."""
-    global dfs_global_time
-
-    for node in graph.nodes:
-        node.predecessor = NIL
-        node.starting_time = INFINITY
-        node.ending_time = INFINITY
-        node.color = WHITE
-
-    dfs_global_time = 0  # global time for dfs
-
-    for n in graph.nodes:
-        if n.color == WHITE:  # if it is not visited
-            dfs_aux(graph, n)
-
-
-# TESTS
-
-def test_dfs():
-    g = Graph()
-
-    # total_nodes for the graph
-    A = GraphNode("A")
-    B = GraphNode("B")
-    C = GraphNode("C")
-    D = GraphNode("D")
-    E = GraphNode("E")
-    F = GraphNode("F")
-
-    # add the just created total_nodes to the graph
-    g.add_node(A)
-    g.add_node(B)
-    g.add_node(C)
-    g.add_node(D)
-    g.add_node(E)
-    g.add_node(F)
-
-    # establish the connections between total_nodes
-    A.add_adjacent_node(B)
-    A.add_adjacent_node(D)
-    A.add_adjacent_node(C)
-
-    B.add_adjacent_node(A)
-    B.add_adjacent_node(E)
-
-    C.add_adjacent_node(D)
-    C.add_adjacent_node(F)
-    C.add_adjacent_node(A)
-
-    D.add_adjacent_node(A)
-    D.add_adjacent_node(C)
-
-    E.add_adjacent_node(B)
-    E.add_adjacent_node(F)
-
-    F.add_adjacent_node(E)
-    F.add_adjacent_node(C)
-
-    # g.show_nodes()
-
-    dfs(g)
-
-    g.show_nodes()
-
-
-def test_dfs2():
-    g = Graph()
-
-    a = GraphNode("a")
-    b = GraphNode("b")
-    c = GraphNode("c")
-    d = GraphNode("d")
-    e = GraphNode("e")
-    f = GraphNode("f")
-
-    g.add_node(a)
-    g.add_node(b)
-    g.add_node(c)
-    g.add_node(d)
-    g.add_node(e)
-    g.add_node(f)
-
-    a.add_adjacent_node(b)
-    b.add_adjacent_node(e)
-    b.add_adjacent_node(c)
-
-    c.add_adjacent_node(a)
-
-    d.add_adjacent_node(c)
-
-    e.add_adjacent_node(d)
-
-    f.add_adjacent_node(a)
-    f.add_adjacent_node(e)
-
-    dfs(g)
-
-    g.show_nodes()
-
-
-if __name__ == "__main__":
-    test_dfs()
-    # test_dfs2()
-
-
- -
- -
-

Module variables

-
-

var BLACK

- - -
-
- -
-
-

var GREY

- - -
-
- -
-
-

var INFINITY

- - -
-
- -
-
-

var NIL

- - -
-
- -
-
-

var WHITE

- - -
-
- -
-
-

var dfs_global_time

- - -
-
- -
- -

Functions

- -
-
-

def dfs(

graph)

-
- - - - -

Typical dfs algorithm that traverses all nodes -that have not been explored yet, -and keeps track of the visited and finished times.

-
- -
-
def dfs(graph: Graph):
-    """Typical dfs algorithm that traverses all nodes
-    that have not been explored yet,
-    and keeps track of the visited and finished times."""
-    global dfs_global_time
-
-    for node in graph.nodes:
-        node.predecessor = NIL
-        node.starting_time = INFINITY
-        node.ending_time = INFINITY
-        node.color = WHITE
-
-    dfs_global_time = 0  # global time for dfs
-
-    for n in graph.nodes:
-        if n.color == WHITE:  # if it is not visited
-            dfs_aux(graph, n)
-
-
-
- -
- - -
-
-

def dfs_aux(

graph, n)

-
- - - - -

This function is called by dfs to further explore child nodes.

-
- -
-
def dfs_aux(graph: Graph, n: GraphNode):
-    """This function is called by dfs to further explore child nodes."""
-    global dfs_global_time
-
-    n.color = GREY
-    dfs_global_time += 1
-    n.start = dfs_global_time
-
-    for v in n.get_adjacent_nodes():
-
-        if v.color == WHITE:
-            v.predecessor = n
-            dfs_aux(graph, v)
-
-    n.color = BLACK
-    dfs_global_time += 1
-    n.end = dfs_global_time
-
-
-
- -
- - -
-
-

def dfs_iterative(

graph)

-
- - - - -
- -
-
def dfs_iterative(graph):
-    # TODO (eventually because it's complex...)
-    pass
-
-
-
- -
- - -
-
-

def test_dfs(

)

-
- - - - -
- -
-
def test_dfs():
-    g = Graph()
-
-    # total_nodes for the graph
-    A = GraphNode("A")
-    B = GraphNode("B")
-    C = GraphNode("C")
-    D = GraphNode("D")
-    E = GraphNode("E")
-    F = GraphNode("F")
-
-    # add the just created total_nodes to the graph
-    g.add_node(A)
-    g.add_node(B)
-    g.add_node(C)
-    g.add_node(D)
-    g.add_node(E)
-    g.add_node(F)
-
-    # establish the connections between total_nodes
-    A.add_adjacent_node(B)
-    A.add_adjacent_node(D)
-    A.add_adjacent_node(C)
-
-    B.add_adjacent_node(A)
-    B.add_adjacent_node(E)
-
-    C.add_adjacent_node(D)
-    C.add_adjacent_node(F)
-    C.add_adjacent_node(A)
-
-    D.add_adjacent_node(A)
-    D.add_adjacent_node(C)
-
-    E.add_adjacent_node(B)
-    E.add_adjacent_node(F)
-
-    F.add_adjacent_node(E)
-    F.add_adjacent_node(C)
-
-    # g.show_nodes()
-
-    dfs(g)
-
-    g.show_nodes()
-
-
-
- -
- - -
-
-

def test_dfs2(

)

-
- - - - -
- -
-
def test_dfs2():
-    g = Graph()
-
-    a = GraphNode("a")
-    b = GraphNode("b")
-    c = GraphNode("c")
-    d = GraphNode("d")
-    e = GraphNode("e")
-    f = GraphNode("f")
-
-    g.add_node(a)
-    g.add_node(b)
-    g.add_node(c)
-    g.add_node(d)
-    g.add_node(e)
-    g.add_node(f)
-
-    a.add_adjacent_node(b)
-    b.add_adjacent_node(e)
-    b.add_adjacent_node(c)
-
-    c.add_adjacent_node(a)
-
-    d.add_adjacent_node(c)
-
-    e.add_adjacent_node(d)
-
-    f.add_adjacent_node(a)
-    f.add_adjacent_node(e)
-
-    dfs(g)
-
-    g.show_nodes()
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/graphs/find_triangle.m.html b/docs/ands/algorithms/graphs/find_triangle.m.html deleted file mode 100644 index 68aec4b9..00000000 --- a/docs/ands/algorithms/graphs/find_triangle.m.html +++ /dev/null @@ -1,1266 +0,0 @@ - - - - - - ands.algorithms.graphs.find_triangle API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.graphs.find_triangle module

-

Author: Nelson Brochado -Creation: 05/09/15

-

Exercise 112.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 05/09/15
-
-Exercise 112.
-"""
-
-from ands.ds.Graph import *
-
-edges = {}
-
-
-def is_an_edge(u, v):
-    """Returns true if v is an adjacent node to u.
-
-    Running time complexity: O(number of adjacent nodes to u).
-
-    :type u : GraphNode
-    :type v : GraphNode
-    """
-    if (u, v) not in edges:
-        edges[(u, v)] = w in u.get_adjacent_nodes()
-    return edges[(u, v)]
-
-
-def find_triangle(g):
-    """Returns true if there's a triangle in g.
-
-    A triangle in a graph is a triple of vertices u, v and w,
-    such that all three edges  (u, v), (v, w) and (u, w) are in the graph.
-
-    Note that a triangle is not composed of the edges (u, v), (v, w) and (w, u)!
-
-    :type g : Graph
-    """
-    for u in g.nodes:
-        for v in u.get_adjacent_nodes():
-            for w in g.nodes:
-                if is_an_edge(v, w) and is_an_edge(u, w):
-                    return True
-    return False
-
-
-def improved_find_triangle(g):
-    for e in g.edges:  # e = (u, v)
-        for w in g.nodes:
-            if is_an_edge(e[1], w) and is_an_edge(e[0], w):
-                return True
-    return False
-
-
-if __name__ == "__main__":
-    g = Graph()
-
-    u = GraphNode("u")
-    w = GraphNode("w")
-    v = GraphNode("v")
-    z = GraphNode("z")
-
-    u.add_adjacent_node(v)
-    u.add_adjacent_node(w)  # comment this line to remove the triangle
-
-    v.add_adjacent_node(w)
-    v.add_adjacent_node(z)
-
-    # w.add_adjacent_node(u)
-    w.add_adjacent_node(z)
-
-    g.add_nodes((u, v, w, z))
-
-    print(g)
-
-    print(find_triangle(g))
-    print(improved_find_triangle(g))
-
-
- -
- -
-

Module variables

-
-

var BLACK

- - -
-
- -
-
-

var GREY

- - -
-
- -
-
-

var INFINITY

- - -
-
- -
-
-

var NIL

- - -
-
- -
-
-

var WHITE

- - -
-
- -
-
-

var edges

- - -
-
- -
- -

Functions

- -
-
-

def find_triangle(

g)

-
- - - - -

Returns true if there's a triangle in g.

-

A triangle in a graph is a triple of vertices u, v and w, -such that all three edges (u, v), (v, w) and (u, w) are in the graph.

-

Note that a triangle is not composed of the edges (u, v), (v, w) and (w, u)!

-

:type g : Graph

-
- -
-
def find_triangle(g):
-    """Returns true if there's a triangle in g.
-
-    A triangle in a graph is a triple of vertices u, v and w,
-    such that all three edges  (u, v), (v, w) and (u, w) are in the graph.
-
-    Note that a triangle is not composed of the edges (u, v), (v, w) and (w, u)!
-
-    :type g : Graph
-    """
-    for u in g.nodes:
-        for v in u.get_adjacent_nodes():
-            for w in g.nodes:
-                if is_an_edge(v, w) and is_an_edge(u, w):
-                    return True
-    return False
-
-
-
- -
- - -
-
-

def improved_find_triangle(

g)

-
- - - - -
- -
-
def improved_find_triangle(g):
-    for e in g.edges:  # e = (u, v)
-        for w in g.nodes:
-            if is_an_edge(e[1], w) and is_an_edge(e[0], w):
-                return True
-    return False
-
-
-
- -
- - -
-
-

def is_an_edge(

u, v)

-
- - - - -

Returns true if v is an adjacent node to u.

-

Running time complexity: O(number of adjacent nodes to u).

-

:type u : GraphNode -:type v : GraphNode

-
- -
-
def is_an_edge(u, v):
-    """Returns true if v is an adjacent node to u.
-
-    Running time complexity: O(number of adjacent nodes to u).
-
-    :type u : GraphNode
-    :type v : GraphNode
-    """
-    if (u, v) not in edges:
-        edges[(u, v)] = w in u.get_adjacent_nodes()
-    return edges[(u, v)]
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/graphs/four_cycle.m.html b/docs/ands/algorithms/graphs/four_cycle.m.html deleted file mode 100644 index 9acca93c..00000000 --- a/docs/ands/algorithms/graphs/four_cycle.m.html +++ /dev/null @@ -1,1213 +0,0 @@ - - - - - - ands.algorithms.graphs.four_cycle API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.graphs.four_cycle module

-

Author: Nelson Brochado -Creation: 06/09/15

-

My solution to exercise 146 from the series of exercise by prof. A. Carzaniga

-

Write an algorithm called Four-Cycle(G) -that takes a directed graph represented with its adjacency matrix G, -and that returns true if and only if G contains a 4-cycle.

-

A 4-cycle is a sequence of four distinct vertexes a, b, c, d -such that there is an arc from a to b, from b to c, from c to d, -and from d to a.

-

Also, analyze the complexity of Four-Cycle(G).

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 06/09/15
-
-My solution to exercise 146 from the series of exercise by prof. A. Carzaniga
-
-Write an algorithm called Four-Cycle(G)
-that takes a directed graph represented with its adjacency matrix G,
-and that returns true if and only if G contains a 4-cycle.
-
-A 4-cycle is a sequence of four distinct vertexes a, b, c, d
-such that there is an arc from a to b, from b to c, from c to d,
-and from d to a.
-
-Also, analyze the complexity of Four-Cycle(G).
-"""
-
-from ands.ds.Graph import *
-
-
-def is_an_edge(u, v):
-    return v in u.get_adjacent_nodes()
-
-
-def four_cycle(g):
-    """Detects a four cycle in a graph represented as an adjacency list.
-
-    Running time complexity(O^5),
-    but using a adjacency matrix representation,
-    it would only be O(n^4),
-    because checking if there's an edge is a constant operation.
-
-    :type g : Graph
-    """
-    for a in g.nodes:
-        for b in a.get_adjacent_nodes():
-            for c in b.get_adjacent_nodes():
-                for d in c.get_adjacent_nodes():
-                    if is_an_edge(d, a):
-                        return True
-
-    return False
-
-
-if __name__ == "__main__":
-    g = Graph()
-
-    a = GraphNode("a")
-    b = GraphNode("b")
-    c = GraphNode("c")
-    d = GraphNode("d")
-
-    g.add_directed_edge(a, b)
-    g.add_directed_edge(b, c)
-    g.add_directed_edge(c, a)  # change a to d, i.e. (c, d) to form a 4-cycle
-    g.add_directed_edge(d, a)
-
-    print(four_cycle(g))
-
-
- -
- -
-

Module variables

-
-

var BLACK

- - -
-
- -
-
-

var GREY

- - -
-
- -
-
-

var INFINITY

- - -
-
- -
-
-

var NIL

- - -
-
- -
-
-

var WHITE

- - -
-
- -
- -

Functions

- -
-
-

def four_cycle(

g)

-
- - - - -

Detects a four cycle in a graph represented as an adjacency list.

-

Running time complexity(O^5), -but using a adjacency matrix representation, -it would only be O(n^4), -because checking if there's an edge is a constant operation.

-

:type g : Graph

-
- -
-
def four_cycle(g):
-    """Detects a four cycle in a graph represented as an adjacency list.
-
-    Running time complexity(O^5),
-    but using a adjacency matrix representation,
-    it would only be O(n^4),
-    because checking if there's an edge is a constant operation.
-
-    :type g : Graph
-    """
-    for a in g.nodes:
-        for b in a.get_adjacent_nodes():
-            for c in b.get_adjacent_nodes():
-                for d in c.get_adjacent_nodes():
-                    if is_an_edge(d, a):
-                        return True
-
-    return False
-
-
-
- -
- - -
-
-

def is_an_edge(

u, v)

-
- - - - -
- -
-
def is_an_edge(u, v):
-    return v in u.get_adjacent_nodes()
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/graphs/index.html b/docs/ands/algorithms/graphs/index.html deleted file mode 100644 index ff5ba132..00000000 --- a/docs/ands/algorithms/graphs/index.html +++ /dev/null @@ -1,1134 +0,0 @@ - - - - - - ands.algorithms.graphs API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.graphs module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms.graphs.best_team_of_three

- - -

Author: Nelson Brochado -Creation: 06/09/15, 12:37

-

My solution to exercise 139 from the series of exercises by prof. A. Carzaniga

-

Consider a weighted undirected graph G = (V, E) -representing a group of programmers and their affinity for team work, -such that the weight w(e) of an edge e = (u, v) is ...

- -
-
-

ands.algorithms.graphs.build_shortest_path

- - -

Author: Nelson Brochado -Creation: 29/08/15, 17:57

-

build_shortest_path can be used -to construct a list of nodes -that represent the shortest path -from a source node to a destination node.

-

Note that you should call this function -only after an algorithm, such as BFS, -has run on the graph, -i.e. precede...

- -
-
-

ands.algorithms.graphs.dfs

- - -

Author: Nelson Brochado -Creation: July, 2015

-

Implementation of depth-first search, -which uses starting and ending times of visiting nodes, -and keeps also track of the predecessor of each node, -in order to be able to backtrack from a node to the source, if necessary.

- -
-
-

ands.algorithms.graphs.find_triangle

- - -

Author: Nelson Brochado -Creation: 05/09/15

-

Exercise 112.

- -
-
-

ands.algorithms.graphs.four_cycle

- - -

Author: Nelson Brochado -Creation: 06/09/15

-

My solution to exercise 146 from the series of exercise by prof. A. Carzaniga

-

Write an algorithm called Four-Cycle(G) -that takes a directed graph represented with its adjacency matrix G, -and that returns true if and only if G contains a 4-cycle.

-

A 4-cycl...

- -
-
-

ands.algorithms.graphs.prim

- - -

Author: Nelson Brochado -Creation: August, 2015

-

How to find MST of a weighted graph using Prim's algorithm: -https://www.youtube.com/watch?v=YyLaRffCdk4

- -
-
-

ands.algorithms.graphs.top_sort

- - -

Author: Nelson Brochado -Creation: 25/08/15

-

Notes: To reduce dependencies between modules, -I decided to included a clean version of dfs here, -specifically for the top_sort algorithm.

-

Explanation: -A topological ordering (or sorting, sort, order) of a directed graph with no cycles (DAG), -is an order ...

- -
-
-

ands.algorithms.graphs.top_three_friends_of_friends

- - -

Author: Nelson Brochado -Creation: 05/09/15

-

Exercise 127.

-

Consider a social network system that, for each user u, -stores u's friends in a list friends(u). -Implement an algorithm Top-Three-Friends-Of-Friends(u) that, -given a user u, recommends the three other users that are not -already among u's fri...

- -
-
- -
-
- -
- - diff --git a/docs/ands/algorithms/graphs/prim.m.html b/docs/ands/algorithms/graphs/prim.m.html deleted file mode 100644 index 7b1c1998..00000000 --- a/docs/ands/algorithms/graphs/prim.m.html +++ /dev/null @@ -1,1361 +0,0 @@ - - - - - - ands.algorithms.graphs.prim API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.graphs.prim module

-

Author: Nelson Brochado -Creation: August, 2015

-

How to find MST of a weighted graph using Prim's algorithm: -https://www.youtube.com/watch?v=YyLaRffCdk4

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: August, 2015
-
-How to find MST of a weighted graph using Prim's algorithm:
-https://www.youtube.com/watch?v=YyLaRffCdk4
-"""
-
-from ands.ds.MinPriorityQueue import *
-
-from ands.ds.Graph import *
-
-
-def _nodes_and_values(g):
-    """Returns a list of tuples,
-    each of them having 2 items:
-    t_i[0] = reference to the node i
-    t_i[1] = value (in this case is the priority) of node i.
-
-    This method was created as a utility
-    for the Prim's algorithm to find a MST.
-    """
-    ls = []
-    for node in g.nodes:
-        ls.append((node, node.value))
-    return ls
-
-
-def _initialise_prim_mst(g: Graph, s: GraphNode):
-    for u in g.nodes:
-        u.predecessor = NIL
-        u.value = INFINITY
-    s.value = 0
-
-
-def prim_mst(g: Graph, s: GraphNode):
-    """Creates a MST.
-
-    During execution of this algorithm,
-    nodes that are NOT yet in the tree,
-    reside in the min priority queue,
-    which is based on the "value" attribute.
-
-    For each vertex v in the g,
-    the attribute v.value is the minimum weight of any edge
-    connecting v to a vertex in the tree.
-    The attribute v.predecessor names the parent of v in the tree.
-
-    The algorithm implicitly maintains a set A:
-    A = {(v, v.predecessor) : v in V - {r} - Q}
-
-    When the algorithm terminates, the mpq is empty,
-    and A = {(v, v.predecessor) : v in V - {r}}."""
-
-    _initialise_prim_mst(g, s)
-
-    q = MinPriorityQueue(_nodes_and_values(g))
-
-    last_node_added = s
-
-    while not q.is_empty():
-
-        u = q.extract_min()  # Returns the minimum element
-
-        for v in u.get_adjacent_nodes():
-            if q.contains_element(v) and g.weight(u, v) < v.value:
-                v.value = g.weight(u, v)
-                v.predecessor = u
-                q.change_priority(v, new_priority=v.value)
-
-        last_node_added = u
-
-    return last_node_added
-
-
-def print_prim_mst(last):
-    while last is not None:
-        print("Node:", last)
-        print("Edge's weight:", last.value)
-        print("Predecessor:", last.predecessor, end="\n\n")
-        last = last.predecessor
-
-
-def build_prim_mst(last):
-    mst = Graph()
-
-    while last is not None:
-        n = GraphNode(last, last.value)
-        if last.predecessor is not None:
-            n.add_adjacent_node(last.predecessor, last.value)
-            n.predecessor = last.predecessor
-        mst.add_node(n)
-        last = last.predecessor
-
-    return mst
-
-
-if __name__ == "__main__":
-    graph = Graph()
-
-    a = GraphNode("a")
-    b = GraphNode("b")
-    c = GraphNode("c")
-    d = GraphNode("d")
-    e = GraphNode("e")
-    f = GraphNode("f")
-    g = GraphNode("g")
-    h = GraphNode("h")
-    i = GraphNode("i")
-
-    a.add_adjacent_node(b, 4)
-    a.add_adjacent_node(h, 8)
-
-    b.add_adjacent_node(a, 4)
-    b.add_adjacent_node(h, 11)
-    b.add_adjacent_node(c, 8)
-
-    c.add_adjacent_node(b, 8)
-    c.add_adjacent_node(i, 2)
-    c.add_adjacent_node(d, 7)
-    c.add_adjacent_node(f, 4)
-
-    d.add_adjacent_node(c, 7)
-    d.add_adjacent_node(f, 14)
-    d.add_adjacent_node(e, 9)
-
-    e.add_adjacent_node(d, 9)
-    e.add_adjacent_node(f, 10)
-
-    f.add_adjacent_node(e, 10)
-    f.add_adjacent_node(d, 14)
-    f.add_adjacent_node(c, 4)
-    f.add_adjacent_node(g, 2)
-
-    g.add_adjacent_node(f, 2)
-    g.add_adjacent_node(h, 1)
-    g.add_adjacent_node(i, 6)
-
-    i.add_adjacent_node(g, 6)
-    i.add_adjacent_node(c, 2)
-    i.add_adjacent_node(h, 7)
-
-    h.add_adjacent_node(a, 8)
-    h.add_adjacent_node(g, 1)
-    h.add_adjacent_node(i, 7)
-    h.add_adjacent_node(b, 11)
-
-    graph.add_nodes((a, b, c, d, e, f, g, h, i))
-
-    build_prim_mst(prim_mst(graph, a)).show_nodes()
-
-
- -
- -
-

Module variables

-
-

var BLACK

- - -
-
- -
-
-

var GREY

- - -
-
- -
-
-

var INFINITY

- - -
-
- -
-
-

var NIL

- - -
-
- -
-
-

var WHITE

- - -
-
- -
- -

Functions

- -
-
-

def build_prim_mst(

last)

-
- - - - -
- -
-
def build_prim_mst(last):
-    mst = Graph()
-
-    while last is not None:
-        n = GraphNode(last, last.value)
-        if last.predecessor is not None:
-            n.add_adjacent_node(last.predecessor, last.value)
-            n.predecessor = last.predecessor
-        mst.add_node(n)
-        last = last.predecessor
-
-    return mst
-
-
-
- -
- - -
-
-

def prim_mst(

g, s)

-
- - - - -

Creates a MST.

-

During execution of this algorithm, -nodes that are NOT yet in the tree, -reside in the min priority queue, -which is based on the "value" attribute.

-

For each vertex v in the g, -the attribute v.value is the minimum weight of any edge -connecting v to a vertex in the tree. -The attribute v.predecessor names the parent of v in the tree.

-

The algorithm implicitly maintains a set A: -A = {(v, v.predecessor) : v in V - {r} - Q}

-

When the algorithm terminates, the mpq is empty, -and A = {(v, v.predecessor) : v in V - {r}}.

-
- -
-
def prim_mst(g: Graph, s: GraphNode):
-    """Creates a MST.
-
-    During execution of this algorithm,
-    nodes that are NOT yet in the tree,
-    reside in the min priority queue,
-    which is based on the "value" attribute.
-
-    For each vertex v in the g,
-    the attribute v.value is the minimum weight of any edge
-    connecting v to a vertex in the tree.
-    The attribute v.predecessor names the parent of v in the tree.
-
-    The algorithm implicitly maintains a set A:
-    A = {(v, v.predecessor) : v in V - {r} - Q}
-
-    When the algorithm terminates, the mpq is empty,
-    and A = {(v, v.predecessor) : v in V - {r}}."""
-
-    _initialise_prim_mst(g, s)
-
-    q = MinPriorityQueue(_nodes_and_values(g))
-
-    last_node_added = s
-
-    while not q.is_empty():
-
-        u = q.extract_min()  # Returns the minimum element
-
-        for v in u.get_adjacent_nodes():
-            if q.contains_element(v) and g.weight(u, v) < v.value:
-                v.value = g.weight(u, v)
-                v.predecessor = u
-                q.change_priority(v, new_priority=v.value)
-
-        last_node_added = u
-
-    return last_node_added
-
-
-
- -
- - -
-
-

def print_prim_mst(

last)

-
- - - - -
- -
-
def print_prim_mst(last):
-    while last is not None:
-        print("Node:", last)
-        print("Edge's weight:", last.value)
-        print("Predecessor:", last.predecessor, end="\n\n")
-        last = last.predecessor
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/graphs/top_sort.m.html b/docs/ands/algorithms/graphs/top_sort.m.html deleted file mode 100644 index fe51d72e..00000000 --- a/docs/ands/algorithms/graphs/top_sort.m.html +++ /dev/null @@ -1,1638 +0,0 @@ - - - - - - ands.algorithms.graphs.top_sort API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.graphs.top_sort module

-

Author: Nelson Brochado -Creation: 25/08/15

-

Notes: To reduce dependencies between modules, -I decided to included a clean version of dfs here, -specifically for the top_sort algorithm.

-

Explanation: -A topological ordering (or sorting, sort, order) of a directed graph with no cycles (DAG), -is an order of the vertices, such that all the directed edges only go forward.

-

Knowing the topological ordering of a DAG can be useful -for example when we want to schedule certain tasks, -where some tasks have higher precedence respect to others: -in this example, vertices of the graph represent the tasks, -and a directed edge from a certain node A to node B -represents the fact that A must be "computed" or "executed" before B, -A has higher precedence respect to B.

-

When does a graph have a topological sorting?

-

A graph has a topological sorting if and only if it has no cycles.

-

The fact that a graph with a topological order -has no cycles is quite easy to understand: -Suppose a graph G has a cycle, -then when searching for a topological ordering in the cycle, -we are going at some point to return to a node that we have already visited, -and this would mean that there's an edge that goes backward, -which contradicts the fact that G has a topological ordering, -by the definition of topological ordering.

-

What if we don't have a cycle, do we always have a topological ordering? -Yes [missing proof].

-

We can find a topological ordering: -1. without dfs (the straightforward solution) -2. with depth-first search -3. another solution

-
    -
  1. Topological ordering without dfs (the straightforward solution)
  2. -
-

First, we need to have the notion of a "sink vertex", -which simply is a vertex with no outgoing edges.

-

Lemma: Every DAG must have a sink vertex. -Proof: Suppose DAG g has no sink vertex and it has a finite number of vertices, -then, when you are searching on the graph, -you will always find at least one outgoing edge from every vertex. -Since g has a finite number of vertices, -we will find at some point a vertex that we have already visited, -which would contradict the assumptions that g is a directed graph with no cycles (DAG).

-

PSEUDO-CODE:

-

TOP-SORT(DAG): - top_sort_list = []

-
create a copy of DAG called g
-
-while g is not empty:
-    // the next sink vertex is also removed from g,
-    // so its size is decreased at each iteration.
-    top_sort_list.add(g.get_next_sink_vertex())
-
-return top_sort_list
-
-

Note that by removing a node from a DAG, we will never create a cycle, -and thus we will always be able to find a sink vertex.

-
    -
  1. Topological ordering with dfs -Check the function implemented below. You should also check the function dfs.
  2. -
-

But using dfs should be as easy as the following pseudo-code shows:

-

TOPOLOGICAL-SORT(G): - DFS(G) - output vertices V in reversed order of finish times.

-
    -
  1. TOPOLOGICAL-SORT-SOL-3(G):
      -
    1. Find an vertex v with no incoming edges
    2. -
    3. Remove outgoing edge from v
    4. -
    5. Go back to step 1
    6. -
    -
  2. -
-

To improve the performance of this algorithm, -- we can pre-compute the number of incoming edges of each vertex. -- insert_key all nodes with degree of 0 into a queue -- repeat the following until the queue is not empty - * Take u from the queue - * for each v adj[u]: - decrement deg[v] (essentially removing edge u - v - if deg[v] == 0: - insert_key v into the queue

-

PROOF OF CORRECTNESS: -We basically need to show_nodes that, if (u, v) is a directed edge from u to v, -then u comes before v in the topological sort.

-

We have 2 cases: -1. u is visited by dfs before v -2. v is visited by dfs before u

-

Assume that u is visited before v (1). -Then there's nothing to prove, -because v will be set explored (and therefore at the right of u) before u.

-

So assume that v is visited before u (2). -This is only true if v was visited from another vertex -(and note that u has not yet been visited). -v will be therefore set as explored before even visiting u, -and thus v will be to the right of u in the topological order, -since u will be added to the topological order only when it is completely explored.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 25/08/15
-
-Notes: To reduce dependencies between modules,
-I decided to included a clean version of dfs here,
-specifically for the top_sort algorithm.
-
-Explanation:
-A topological ordering (or sorting, sort, order) of a directed graph with no cycles (DAG),
-is an order of the vertices, such that all the directed edges only go forward.
-
-Knowing the topological ordering of a DAG can be useful
-for example when we want to schedule certain tasks,
-where some tasks have higher precedence respect to others:
-in this example, vertices of the graph represent the tasks,
-and a directed edge from a certain node A to node B
-represents the fact that A must be "computed" or "executed" before B,
-A has higher precedence respect to B.
-
-When does a graph have a topological sorting?
-
-A graph has a topological sorting if and only if it has no cycles.
-
-The fact that a graph with a topological order
-has no cycles is quite easy to understand:
-Suppose a graph G has a cycle,
-then when searching for a topological ordering in the cycle,
-we are going at some point to return to a node that we have already visited,
-and this would mean that there's an edge that goes backward,
-which contradicts the fact that G has a topological ordering,
-by the definition of topological ordering.
-
-What if we don't have a cycle, do we always have a topological ordering?
-Yes [missing proof].
-
-We can find a topological ordering:
-1. without dfs (the straightforward solution)
-2. with depth-first search
-3. another solution
-
-1. Topological ordering without dfs (the straightforward solution)
-
-First, we need to have the notion of a "sink vertex",
-which simply is a vertex with no outgoing edges.
-
-Lemma: Every DAG must have a sink vertex.
-Proof: Suppose DAG g has no sink vertex and it has a finite number of vertices,
-then, when you are searching on the graph,
-you will always find at least one outgoing edge from every vertex.
-Since g has a finite number of vertices,
-we will find at some point a vertex that we have already visited,
-which would contradict the assumptions that g is a directed graph with no cycles (DAG).
-
-PSEUDO-CODE:
-
-TOP-SORT(DAG):
-    top_sort_list = []
-
-    create a copy of DAG called g
-
-    while g is not empty:
-        // the next sink vertex is also removed from g,
-        // so its size is decreased at each iteration.
-        top_sort_list.add(g.get_next_sink_vertex())
-
-    return top_sort_list
-
-Note that by removing a node from a DAG, we will never create a cycle,
-and thus we will always be able to find a sink vertex.
-
-
-2. Topological ordering with dfs
-Check the function implemented below. You should also check the function dfs.
-
-But using dfs should be as easy as the following pseudo-code shows:
-
-TOPOLOGICAL-SORT(G):
-    DFS(G)
-    output vertices V in reversed order of finish times.
-
-
-3. TOPOLOGICAL-SORT-SOL-3(G):
-    1. Find an vertex v with no incoming edges
-    2. Remove outgoing edge from v
-    3. Go back to step 1
-
-To improve the performance of this algorithm,
-- we can pre-compute the number of incoming edges of each vertex.
-- insert_key all nodes with degree of 0 into a queue
-- repeat the following until the queue is not empty
-    * Take u from the queue
-    * for each v adj[u]:
-        decrement deg[v] (essentially removing edge u - v
-        if deg[v] == 0:
-            insert_key v into the queue
-
-
-PROOF OF CORRECTNESS:
-We basically need to show_nodes that, if (u, v) is a directed edge from u to v,
-then u comes before v in the topological sort.
-
-We have 2 cases:
-1. u is visited by dfs before v
-2. v is visited by dfs before u
-
-Assume that u is visited before v (1).
-Then there's nothing to prove,
-because v will be set explored (and therefore at the right of u) before u.
-
-So assume that v is visited before u (2).
-This is only true if v was visited from another vertex
-(and note that u has not yet been visited).
-v will be therefore set as explored before even visiting u,
-and thus v will be to the right of u in the topological order,
-since u will be added to the topological order only when it is completely explored.
-
-"""
-
-from ands.ds.Graph import *
-
-
-def dfs_visit(graph: Graph, n: GraphNode):
-    """This function is called by dfs to further explore child nodes."""
-
-    n.color = GREY
-
-    for v in n.get_adjacent_nodes():
-        if v.color == WHITE:
-            dfs_visit(graph, v)
-
-    n.color = BLACK
-
-    # adding the just explored node to the list
-    # which represents the topological sort
-    graph.topological_sort.append(n)
-
-
-def dfs(graph: Graph):
-    """Typical dfs algorithm that traverses
-    all nodes that have not been explored yet
-    and keeps track of the visited and finished times."""
-
-    for n in graph.nodes:
-        n.color = WHITE
-
-    for n in graph.nodes:
-        if n.color == WHITE:
-            dfs_visit(graph, n)
-
-
-# TODO: DETECT CYCLE IN A "SUPPOSED" DAG
-
-def top_sort(dag: Graph):
-    """dag = DAG = Direct A-cycle Graph
-
-    If dag is not really a directed graph with no cycles (DAG),
-    the behaviour of this function is undefined.
-
-    This algorithm creates a topological sort
-    by using the ending visited times of each node:
-    once a node has been completely explored,
-    it is added to a list representing the topological sort.
-
-    Time complexity: O(|V| + |E|)"""
-    dfs(dag)
-
-    # dag.top_sort_with_dfs is an object of type TopologicalSortStack,
-    # so the first element of this object
-    # is the element at the bottom of the _stack
-    # and the last element is the element at the top
-    dag.topological_sort.reverse()
-
-    return dag.topological_sort
-
-
-def test_topological_sort():
-    g = Graph()
-
-    a = GraphNode("A")
-    b = GraphNode("B")
-    c = GraphNode("C")
-    d = GraphNode("D")
-
-    a.add_adjacent_node(b)
-    a.add_adjacent_node(c)
-
-    b.add_adjacent_node(d)
-
-    c.add_adjacent_node(d)
-
-    g.add_node(a)
-    g.add_node(b)
-    g.add_node(c)
-    g.add_node(d)
-
-    ts = top_sort(g)
-    ts = [x.key for x in ts]
-    print(ts)
-
-
-# https://en.wikipedia.org/wiki/Topological_sorting
-def test_topological_sort2():
-    g = Graph()
-
-    n7 = GraphNode(7)
-    n5 = GraphNode(5)
-    n3 = GraphNode(3)
-    n11 = GraphNode(11)
-    n8 = GraphNode(8)
-    n2 = GraphNode(2)
-    n9 = GraphNode(9)
-    n10 = GraphNode(10)
-
-    g.add_node(n2)
-    g.add_node(n7)
-    g.add_node(n5)
-    g.add_node(n3)
-    g.add_node(n11)
-    g.add_node(n8)
-    g.add_node(n9)
-    g.add_node(n10)
-
-    n7.add_adjacent_node(n11)
-    n7.add_adjacent_node(n8)
-
-    n5.add_adjacent_node(n11)
-
-    n3.add_adjacent_node(n8)
-    n3.add_adjacent_node(n10)
-
-    n11.add_adjacent_node(n2)
-    n11.add_adjacent_node(n9)
-    n11.add_adjacent_node(n10)
-
-    n8.add_adjacent_node(n9)
-
-    ts = top_sort(g)
-    ts = [x.key for x in ts]
-    print(ts)
-
-
-if __name__ == "__main__":
-    test_topological_sort()
-    test_topological_sort2()
-
-
- -
- -
-

Module variables

-
-

var BLACK

- - -
-
- -
-
-

var GREY

- - -
-
- -
-
-

var INFINITY

- - -
-
- -
-
-

var NIL

- - -
-
- -
-
-

var WHITE

- - -
-
- -
- -

Functions

- -
-
-

def dfs(

graph)

-
- - - - -

Typical dfs algorithm that traverses -all nodes that have not been explored yet -and keeps track of the visited and finished times.

-
- -
-
def dfs(graph: Graph):
-    """Typical dfs algorithm that traverses
-    all nodes that have not been explored yet
-    and keeps track of the visited and finished times."""
-
-    for n in graph.nodes:
-        n.color = WHITE
-
-    for n in graph.nodes:
-        if n.color == WHITE:
-            dfs_visit(graph, n)
-
-
-
- -
- - -
-
-

def dfs_visit(

graph, n)

-
- - - - -

This function is called by dfs to further explore child nodes.

-
- -
-
def dfs_visit(graph: Graph, n: GraphNode):
-    """This function is called by dfs to further explore child nodes."""
-
-    n.color = GREY
-
-    for v in n.get_adjacent_nodes():
-        if v.color == WHITE:
-            dfs_visit(graph, v)
-
-    n.color = BLACK
-
-    # adding the just explored node to the list
-    # which represents the topological sort
-    graph.topological_sort.append(n)
-
-
-
- -
- - -
-
-

def test_topological_sort(

)

-
- - - - -
- -
-
def test_topological_sort():
-    g = Graph()
-
-    a = GraphNode("A")
-    b = GraphNode("B")
-    c = GraphNode("C")
-    d = GraphNode("D")
-
-    a.add_adjacent_node(b)
-    a.add_adjacent_node(c)
-
-    b.add_adjacent_node(d)
-
-    c.add_adjacent_node(d)
-
-    g.add_node(a)
-    g.add_node(b)
-    g.add_node(c)
-    g.add_node(d)
-
-    ts = top_sort(g)
-    ts = [x.key for x in ts]
-    print(ts)
-
-
-
- -
- - -
-
-

def test_topological_sort2(

)

-
- - - - -
- -
-
def test_topological_sort2():
-    g = Graph()
-
-    n7 = GraphNode(7)
-    n5 = GraphNode(5)
-    n3 = GraphNode(3)
-    n11 = GraphNode(11)
-    n8 = GraphNode(8)
-    n2 = GraphNode(2)
-    n9 = GraphNode(9)
-    n10 = GraphNode(10)
-
-    g.add_node(n2)
-    g.add_node(n7)
-    g.add_node(n5)
-    g.add_node(n3)
-    g.add_node(n11)
-    g.add_node(n8)
-    g.add_node(n9)
-    g.add_node(n10)
-
-    n7.add_adjacent_node(n11)
-    n7.add_adjacent_node(n8)
-
-    n5.add_adjacent_node(n11)
-
-    n3.add_adjacent_node(n8)
-    n3.add_adjacent_node(n10)
-
-    n11.add_adjacent_node(n2)
-    n11.add_adjacent_node(n9)
-    n11.add_adjacent_node(n10)
-
-    n8.add_adjacent_node(n9)
-
-    ts = top_sort(g)
-    ts = [x.key for x in ts]
-    print(ts)
-
-
-
- -
- - -
-
-

def top_sort(

dag)

-
- - - - -

dag = DAG = Direct A-cycle Graph

-

If dag is not really a directed graph with no cycles (DAG), -the behaviour of this function is undefined.

-

This algorithm creates a topological sort -by using the ending visited times of each node: -once a node has been completely explored, -it is added to a list representing the topological sort.

-

Time complexity: O(|V| + |E|)

-
- -
-
def top_sort(dag: Graph):
-    """dag = DAG = Direct A-cycle Graph
-
-    If dag is not really a directed graph with no cycles (DAG),
-    the behaviour of this function is undefined.
-
-    This algorithm creates a topological sort
-    by using the ending visited times of each node:
-    once a node has been completely explored,
-    it is added to a list representing the topological sort.
-
-    Time complexity: O(|V| + |E|)"""
-    dfs(dag)
-
-    # dag.top_sort_with_dfs is an object of type TopologicalSortStack,
-    # so the first element of this object
-    # is the element at the bottom of the _stack
-    # and the last element is the element at the top
-    dag.topological_sort.reverse()
-
-    return dag.topological_sort
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/graphs/top_three_friends_of_friends.m.html b/docs/ands/algorithms/graphs/top_three_friends_of_friends.m.html deleted file mode 100644 index 3e07e4ca..00000000 --- a/docs/ands/algorithms/graphs/top_three_friends_of_friends.m.html +++ /dev/null @@ -1,1235 +0,0 @@ - - - - - - ands.algorithms.graphs.top_three_friends_of_friends API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.graphs.top_three_friends_of_friends module

-

Author: Nelson Brochado -Creation: 05/09/15

-

Exercise 127.

-

Consider a social network system that, for each user u, -stores u's friends in a list friends(u). -Implement an algorithm Top-Three-Friends-Of-Friends(u) that, -given a user u, recommends the three other users that are not -already among u's friends but are among -the friends of most of u's friends. -Also, analyze the complexity of the Top-Three-Friends-Of-Friends algorithm.

-

My idea: -Build the network system as a graph.

-

But how do we find the 3 friends of friends to suggest to the user u?

-

Given a user u, we can iterate through its friends.

-

First, we should create an empty list "suggestions" to store the suggestions.

-

Then we iterate through each adjacent node w -of each adjacent node "v" of the node "user", -and we check two things: - 1. that w is not adjacent to "user" - 2. w is not already in the list "suggestions".

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-Creation: 05/09/15
-
-Exercise 127.
-
-Consider a social network system that, for each user u,
-stores u's friends in a list friends(u).
-Implement an algorithm Top-Three-Friends-Of-Friends(u) that,
-given a user u, recommends the three other users that are not
-already among u's friends but are among
-the friends of most of u's friends.
-Also, analyze the complexity of the Top-Three-Friends-Of-Friends algorithm.
-
-
-My idea:
-Build the network system as a graph.
-
-But how do we find the 3 friends of friends to suggest to the user u?
-
-Given a user u, we can iterate through its friends.
-
-First, we should create an empty list "suggestions" to store the suggestions.
-
-Then we iterate through each adjacent node w
-of each adjacent node "v" of the node "user",
-and we check two things:
-    1. that w is not adjacent to "user"
-    2. w is not already in the list "suggestions".
-"""
-
-from pprint import pprint
-
-from ands.ds.Graph import *
-
-
-def top_three_friends_of_friends(user, k=3):
-    """Return a list of suggestions of friends for user.
-
-    Running time complexity:
-     - O(n^3) in the worst case
-     - O(n^2) in the average case.
-
-    Node that searching for element in a set
-    is in average a constant operation,
-    but in the worst case it can be linear.
-
-    :type user : GraphNode
-    """
-    friends = set(user.get_adjacent_nodes())
-    suggestions = set()
-
-    for friend in friends:
-        for ff in friend.get_adjacent_nodes():
-            if ff != user and ff not in friends and \
-                    ff not in suggestions and len(suggestions) < k:
-                suggestions.add(ff)
-
-    return suggestions
-
-
-if __name__ == "__main__":
-    sn = Graph("Social Network")
-
-    a = GraphNode("a")
-    b = GraphNode("b")
-    c = GraphNode("c")
-    d = GraphNode("d")
-    e = GraphNode("e")
-    f = GraphNode("f")
-    g = GraphNode("g")
-
-    sn.add_nodes((a, b, c, d, e, f, g))
-
-    sn.add_undirected_edge(a, d)
-    sn.add_undirected_edge(a, c)
-    sn.add_undirected_edge(a, b)
-    sn.add_undirected_edge(b, g)
-    sn.add_undirected_edge(d, f)
-    sn.add_undirected_edge(d, e)
-
-    pprint(top_three_friends_of_friends(a))
-
-
- -
- -
-

Module variables

-
-

var BLACK

- - -
-
- -
-
-

var GREY

- - -
-
- -
-
-

var INFINITY

- - -
-
- -
-
-

var NIL

- - -
-
- -
-
-

var WHITE

- - -
-
- -
- -

Functions

- -
-
-

def top_three_friends_of_friends(

user, k=3)

-
- - - - -

Return a list of suggestions of friends for user.

-

Running time complexity: - - O(n^3) in the worst case - - O(n^2) in the average case.

-

Node that searching for element in a set -is in average a constant operation, -but in the worst case it can be linear.

-

:type user : GraphNode

-
- -
-
def top_three_friends_of_friends(user, k=3):
-    """Return a list of suggestions of friends for user.
-
-    Running time complexity:
-     - O(n^3) in the worst case
-     - O(n^2) in the average case.
-
-    Node that searching for element in a set
-    is in average a constant operation,
-    but in the worst case it can be linear.
-
-    :type user : GraphNode
-    """
-    friends = set(user.get_adjacent_nodes())
-    suggestions = set()
-
-    for friend in friends:
-        for ff in friend.get_adjacent_nodes():
-            if ff != user and ff not in friends and \
-                    ff not in suggestions and len(suggestions) < k:
-                suggestions.add(ff)
-
-    return suggestions
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/greedy/activity_selection.m.html b/docs/ands/algorithms/greedy/activity_selection.m.html deleted file mode 100644 index 4bfef6a5..00000000 --- a/docs/ands/algorithms/greedy/activity_selection.m.html +++ /dev/null @@ -1,1261 +0,0 @@ - - - - - - ands.algorithms.greedy.activity_selection API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.greedy.activity_selection module

-

Author: Nelson Brochado

-

Consider a set of requests for a room.

-

Only one person can reserve the room at a time, -and you want to allow the maximum number of requests.

-

The requests for periods (si=start time for i, fi=finish time for i) are:

-

(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11), (8, 12), (2, 13), (12, 14)

-

Where for example in (1, 4), s1 = 1 and f1 = 4.

-

Which ones should we schedule?

-

We can solve this problem using dynamic programming, -but it we can also solve it using a simple greedy algorithm.

-

The algorithm consists of basically choosing the next activity -with the smallest finish time. -So, to do this, we need first to sort the activities by finish time. -This algorithm is a top-down algorithm, -in the sense that we can start by choosing an activity, -the one with the earliest finish time, -and then we can do the same for the remaining sub-problems.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Consider a set of requests for a room.
-
-Only one person can reserve the room at a time,
-and you want to allow the maximum number of requests.
-
-The requests for periods (si=start time for i, fi=finish time for i) are:
-
-(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), (8, 11), (8, 12), (2, 13), (12, 14)
-
-Where for example in (1, 4), s1 = 1 and f1 = 4.
-
-Which ones should we schedule?
-
-We can solve this problem using dynamic programming,
-but it we can also solve it using a simple greedy algorithm.
-
-The algorithm consists of basically choosing the next activity
-with the smallest finish time.
-So, to do this, we need first to sort the activities by finish time.
-This algorithm is a top-down algorithm,
-in the sense that we can start by choosing an activity,
-the one with the earliest finish time,
-and then we can do the same for the remaining sub-problems.
-"""
-
-import operator
-
-from tabulate import tabulate
-
-activities = [["a", 12, 14], ["b", 0, 6], ["c", 2, 13], ["d", 3, 5],
-              ["e", 5, 7], ["f", 1, 4], ["g", 5, 9], ["h", 3, 8],
-              ["i", 12, 14], ["j", 6, 10], ["k", 8, 11]]
-
-
-def ask_activities():
-    print("Welcome to the Activity Selection problem!\n\n" +
-          "You tell me your activities and their starting\n" +
-          "and ending times, and I will tell you which\n" +
-          "activities are compatible, and should take place.")
-
-    print("=" * 48, end="\n\n")
-    activities.clear()
-
-    while True:
-        name = input("Enter name of the activity: ")
-        starting_time = int(input("Enter the starting time of " + name + ": "))
-        ending_time = int(input("Enter the ending time of " + name + ": "))
-        activities.append([name, starting_time, ending_time])
-
-        a = input("\nType q if you don't have more activities: ")
-
-        if a.lower() == "q":
-            break
-        else:
-            print("-" * 48, end="\n\n")
-
-
-def activity_selector(activities, verbose=True):
-    # sorting activities by finish time
-    activities.sort(key=operator.itemgetter(2))
-
-    if verbose:
-        print("\nAll activities ordered by finish time:")
-        print(tabulate(activities,
-                       headers=(
-                           "Activity's Name",
-                           "Starting Time",
-                           "Ending Time"),
-                       tablefmt="grid"))
-
-    last_selected_activity = activities[0]
-
-    selected_activities = [activities[0]]
-
-    for i in range(1, len(activities)):
-        # if the starting time of the ith activity
-        # is greater or equal to the ending time
-        # of the last selected activity
-        # then add the ith activity to the selected activities.
-        if activities[i][1] >= last_selected_activity[2]:
-            selected_activities.append(activities[i])
-            last_selected_activity = activities[i]
-
-    if verbose:
-        print("\n\nYou should schedule your activities in the following way:")
-        print(tabulate(selected_activities,
-                       headers=(
-                           "Activity's Name",
-                           "Starting Time",
-                           "Ending Time"),
-                       tablefmt="grid"))
-
-    return selected_activities
-
-
-if __name__ == "__main__":
-    # ask_activities()  # uncomment this line if you want to choose your
-    # activities manually
-    sa = activity_selector(activities)
-    print(sa)
-
-
- -
- -
-

Module variables

-
-

var activities

- - -
-
- -
- -

Functions

- -
-
-

def activity_selector(

activities, verbose=True)

-
- - - - -
- -
-
def activity_selector(activities, verbose=True):
-    # sorting activities by finish time
-    activities.sort(key=operator.itemgetter(2))
-
-    if verbose:
-        print("\nAll activities ordered by finish time:")
-        print(tabulate(activities,
-                       headers=(
-                           "Activity's Name",
-                           "Starting Time",
-                           "Ending Time"),
-                       tablefmt="grid"))
-
-    last_selected_activity = activities[0]
-
-    selected_activities = [activities[0]]
-
-    for i in range(1, len(activities)):
-        # if the starting time of the ith activity
-        # is greater or equal to the ending time
-        # of the last selected activity
-        # then add the ith activity to the selected activities.
-        if activities[i][1] >= last_selected_activity[2]:
-            selected_activities.append(activities[i])
-            last_selected_activity = activities[i]
-
-    if verbose:
-        print("\n\nYou should schedule your activities in the following way:")
-        print(tabulate(selected_activities,
-                       headers=(
-                           "Activity's Name",
-                           "Starting Time",
-                           "Ending Time"),
-                       tablefmt="grid"))
-
-    return selected_activities
-
-
-
- -
- - -
-
-

def ask_activities(

)

-
- - - - -
- -
-
def ask_activities():
-    print("Welcome to the Activity Selection problem!\n\n" +
-          "You tell me your activities and their starting\n" +
-          "and ending times, and I will tell you which\n" +
-          "activities are compatible, and should take place.")
-
-    print("=" * 48, end="\n\n")
-    activities.clear()
-
-    while True:
-        name = input("Enter name of the activity: ")
-        starting_time = int(input("Enter the starting time of " + name + ": "))
-        ending_time = int(input("Enter the ending time of " + name + ": "))
-        activities.append([name, starting_time, ending_time])
-
-        a = input("\nType q if you don't have more activities: ")
-
-        if a.lower() == "q":
-            break
-        else:
-            print("-" * 48, end="\n\n")
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/greedy/fractional_knapsack.m.html b/docs/ands/algorithms/greedy/fractional_knapsack.m.html deleted file mode 100644 index 65519fb6..00000000 --- a/docs/ands/algorithms/greedy/fractional_knapsack.m.html +++ /dev/null @@ -1,1250 +0,0 @@ - - - - - - ands.algorithms.greedy.fractional_knapsack API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.greedy.fractional_knapsack module

-

Author: Nelson Brochado

-

The time complexity of the fractional knapsack is O(n*log(n)), -because of the call to sort the items by value/weight ratio.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-The time complexity of the fractional knapsack is O(n*log(n)),
-because of the call to sort the items by value/weight ratio.
-"""
-
-import operator
-
-from tabulate import tabulate
-
-
-def ask_objects():
-    objects = []
-    print("Welcome to the Fractional Knapsack problem!\n\n" +
-          "You will tell me the objects that you have,\n" +
-          "their path_cost and their weight.\n\n" +
-          "You should also tell me after that\n"
-          "how much weight you can carry with you.\n\n" +
-          "I will tell you then which items or\n" +
-          "fraction of items you should take.\n")
-
-    input("When you are ready, press ENTER.\n" + "=" * 40 + "\n\n")
-
-    while True:
-        name = input("Enter the name of the object: ")
-        cost = int(input("Enter the value of " + name + ": "))
-        weight = int(input("Enter the weight (in grams) of " + name + ": "))
-        objects.append([name, cost, weight])
-        yn = input("\nDo you have other items (y/n)? ")
-
-        if yn.lower() in ("n", "no"):
-            break
-        else:
-            print("-" * 40, end="\n\n")
-
-    for obj in objects:
-        # adding as forth property of each object its path_cost/weight ratio
-        obj.append(obj[1] / obj[2])
-
-    objects.sort(key=operator.itemgetter(3), reverse=True)
-
-    print("\n\nThe following are the items that you have:\n")
-    print(
-        tabulate(
-            objects,
-            tablefmt="grid",
-            headers=(
-                "Name",
-                "Value",
-                "Weight",
-                "Value/Weight Ratio")))
-    capacity = int(
-        input("\nEnter the maximum weight you can bring (in grams): "))
-
-    return objects, capacity
-
-
-def interactive_fractional_knapsack():
-    objects, capacity = ask_objects()
-    current_weight = 0
-    knapsack_objects = []
-
-    for i, obj in enumerate(objects):
-        if obj[2] + current_weight <= capacity:
-            current_weight += obj[2]
-            knapsack_objects.append(i)
-        else:
-            remaining_weight = capacity - current_weight
-            knapsack_objects.append((i, remaining_weight))
-            break
-    output_fractional_knapsack(knapsack_objects, objects)
-
-
-def output_fractional_knapsack(knapsack_objects, objects):
-    s = "You should take "
-
-    for i, item in enumerate(knapsack_objects):
-        if not isinstance(item, tuple):
-            s += str(objects[item][2]) + " gram(s) of " + objects[item][0]
-            if i < len(knapsack_objects) - 1:
-                s += ", "
-        else:
-            s += " and " + str(item[1]) + " gram(s) of " + \
-                 objects[item[0]][0] + "."
-
-    print("\n\n" + s)
-
-# if __name__ == "__main__":
-# interactive_fractional_knapsack()
-
-
- -
- -
- -

Functions

- -
-
-

def ask_objects(

)

-
- - - - -
- -
-
def ask_objects():
-    objects = []
-    print("Welcome to the Fractional Knapsack problem!\n\n" +
-          "You will tell me the objects that you have,\n" +
-          "their path_cost and their weight.\n\n" +
-          "You should also tell me after that\n"
-          "how much weight you can carry with you.\n\n" +
-          "I will tell you then which items or\n" +
-          "fraction of items you should take.\n")
-
-    input("When you are ready, press ENTER.\n" + "=" * 40 + "\n\n")
-
-    while True:
-        name = input("Enter the name of the object: ")
-        cost = int(input("Enter the value of " + name + ": "))
-        weight = int(input("Enter the weight (in grams) of " + name + ": "))
-        objects.append([name, cost, weight])
-        yn = input("\nDo you have other items (y/n)? ")
-
-        if yn.lower() in ("n", "no"):
-            break
-        else:
-            print("-" * 40, end="\n\n")
-
-    for obj in objects:
-        # adding as forth property of each object its path_cost/weight ratio
-        obj.append(obj[1] / obj[2])
-
-    objects.sort(key=operator.itemgetter(3), reverse=True)
-
-    print("\n\nThe following are the items that you have:\n")
-    print(
-        tabulate(
-            objects,
-            tablefmt="grid",
-            headers=(
-                "Name",
-                "Value",
-                "Weight",
-                "Value/Weight Ratio")))
-    capacity = int(
-        input("\nEnter the maximum weight you can bring (in grams): "))
-
-    return objects, capacity
-
-
-
- -
- - -
-
-

def interactive_fractional_knapsack(

)

-
- - - - -
- -
-
def interactive_fractional_knapsack():
-    objects, capacity = ask_objects()
-    current_weight = 0
-    knapsack_objects = []
-
-    for i, obj in enumerate(objects):
-        if obj[2] + current_weight <= capacity:
-            current_weight += obj[2]
-            knapsack_objects.append(i)
-        else:
-            remaining_weight = capacity - current_weight
-            knapsack_objects.append((i, remaining_weight))
-            break
-    output_fractional_knapsack(knapsack_objects, objects)
-
-
-
- -
- - -
-
-

def output_fractional_knapsack(

knapsack_objects, objects)

-
- - - - -
- -
-
def output_fractional_knapsack(knapsack_objects, objects):
-    s = "You should take "
-
-    for i, item in enumerate(knapsack_objects):
-        if not isinstance(item, tuple):
-            s += str(objects[item][2]) + " gram(s) of " + objects[item][0]
-            if i < len(knapsack_objects) - 1:
-                s += ", "
-        else:
-            s += " and " + str(item[1]) + " gram(s) of " + \
-                 objects[item[0]][0] + "."
-
-    print("\n\n" + s)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/greedy/huffman.m.html b/docs/ands/algorithms/greedy/huffman.m.html deleted file mode 100644 index 1b6c39fd..00000000 --- a/docs/ands/algorithms/greedy/huffman.m.html +++ /dev/null @@ -1,1427 +0,0 @@ - - - - - - ands.algorithms.greedy.huffman API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.greedy.huffman module

-

Author: Nelson Brochado

-

Warning

-

There are still some functions that I need to implement, -but the basic idea of the huffman algorithm is here, -so I decided to include it already in this repository.

-

Description

-

Huffman coding to encode messages with a variable length of bits.

-

The tree created by the huffman algorithm is not a binary search tree, -but simply a binary tree, that is the left child is not necessarily smaller -than the node i, or the right child is not necessarily greater than the node i, -for all nodes i in the tree.

-

The tree produce by the huffman algorithm is a full tree: -if C is the alphabet from which the characters are drawn and -all characters frequencies are positive, -then the tree for an optimal prefix code as exactly |C| leaves, -one for each character in C, and exactly |C| - 1 internal nodes, -and by the way we need |C| - 1 merges to construct the tree.

-

Note that the codewords produced by the build_huffman_codes -contain distinct prefixes among each other, -and this is very useful when decoding the huffman codes, -because there's no ambiguity.

-

Given a tree T corresponding to a prefix code, -we can easily compute the number of bits necessary to encode a message, -given a certain alphabet C: -For each character c in the alphabet C, -let c.freq be the frequency of c in the message to encode, -and let d(c) denote the depth of the leaf c in the tree -(note that d(c) is also the length of the codeword to encode c), -the number of bits to encode the message is therefore:

-

bits(T):= sum {for all c in C} of c.freq * d(c).

-

TODO

-
    -
  • Correctness of Huffman algorithm
  • -
- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-### Warning
-
-There are still some functions that I need to implement,
-but the basic idea of the huffman algorithm is here,
-so I decided to include it already in this repository.
-
-### Description
-
-Huffman coding to encode messages with a variable length of bits.
-
-The tree created by the huffman algorithm is not a binary search tree,
-but simply a binary tree, that is the left child is not necessarily smaller
-than the node i, or the right child is not necessarily greater than the node i,
-for all nodes i in the tree.
-
-The tree produce by the huffman algorithm is a full tree:
-if C is the alphabet from which the characters are drawn and
-all characters frequencies are positive,
-then the tree for an optimal prefix code as exactly |C| leaves,
-one for each character in C, and exactly |C| - 1 internal nodes,
-and by the way we need |C| - 1 merges to construct the tree.
-
-Note that the codewords produced by the build_huffman_codes
-contain distinct prefixes among each other,
-and this is very useful when decoding the huffman codes,
-because there's no ambiguity.
-
-Given a tree T corresponding to a prefix code,
-we can easily compute the number of bits necessary to encode a message,
-given a certain alphabet C:
-For each character c in the alphabet C,
-let c.freq be the frequency of c in the message to encode,
-and let d(c) denote the depth of the leaf c in the tree
-(note that d(c) is also the length of the codeword to encode c),
-the number of bits to encode the message is therefore:
-
-bits(T):= sum {for all c in C} of c.freq * d(c).
-
-
-### TODO
-- Correctness of Huffman algorithm
-"""
-
-from ands.ds.MinPriorityQueue import MinPriorityQueue
-
-from ands.ds.heap import HeapNode
-
-# export only functions and classes of this module that are implemented
-__all__ = ["calculate_frequencies",
-           "print_frequencies",
-           "huffman",
-           "build_huffman_codes",
-           "huffman_fibonacci_encoder"]
-
-
-def calculate_frequencies(message):
-    frequencies = {}
-    for char in message:
-        if char not in frequencies.keys():
-            frequencies[char] = 1
-        else:
-            frequencies[char] += 1
-    return frequencies
-
-
-def print_frequencies(frequencies):
-    from tabulate import tabulate
-    import operator
-    print(tabulate(sorted(frequencies.items(), key=operator.itemgetter(1)), headers=("Letter", "Frequency"),
-                   tablefmt="grid"))
-
-
-def huffman(message: str, verbose=False):
-    """Creates a Huffman tree representing all the codewords for message.
-
-    **Time Complexity**: O(n*log2(n))."""
-
-    # Counting the frequencies of each character or symbol
-    frequencies = calculate_frequencies(message)
-
-    if verbose:
-        print_frequencies(frequencies)
-
-    # Creates a queue in O(n) time using the build-min-heap algorithm.
-    mpq = MinPriorityQueue(frequencies.items())
-
-    while not mpq.is_empty():
-
-        left = mpq.extract_min(heap_node=True)
-        right = mpq.extract_min(heap_node=True)
-
-        if right is None:
-            return left
-
-        node = HeapNode(key=left.key + right.key, value=left.value + right.value)
-
-        node.left = left
-        node.right = right
-
-        left.parent = node
-        right.parent = node
-
-        mpq.insert_with_priority(node)
-
-
-def build_huffman_codes(root: HeapNode):
-    """Starting from the `root` node obtained by the `huffman` algorithm,
-    this function builds a dictionary where keys are the original characters,
-    and their values are the corresponding huffman codes."""
-    huffman_codes = {}
-
-    for char in root.value:
-        current_node = root
-        huffman_code = ""  # huffman code of char
-
-        while True:
-            if current_node.left is not None and \
-                    char in current_node.left.value:
-                huffman_code += "0"
-                current_node = current_node.left
-            elif current_node.right is not None:
-                huffman_code += "1"
-                current_node = current_node.right
-            else:
-                break
-
-        huffman_codes[char] = huffman_code
-
-    return huffman_codes
-
-
-# TODO: CREATE AN ENCODE AND DECODE FUNCTIONS
-
-def huffman_encoder(huffman_codes):
-    pass
-
-
-def huffman_decoder(encoded_message):
-    pass
-
-
-def huffman_fibonacci_encoder(fn: "list of list"):
-    """`fn` is supposed to be a `list` of `tuple`s,
-    whose first element is the character or symbol,
-    whereas the second element is its corresponding frequency,
-    that happen to be the Fibonacci numbers."""
-    import operator
-
-    fn.sort(key=operator.itemgetter(1), reverse=True)  # sorts by frequency
-    huffman_codes = {fn[-1][0]: "0" * (len(fn) - 1)}
-
-    for i, char in enumerate(fn[:-1]):
-        prefix = "0" * i
-        codeword = prefix + "1"
-        huffman_codes[char[0]] = codeword
-    return huffman_codes
-
-
-hfc = huffman_fibonacci_encoder([("a", 1), ("b", 1), ("c", 2), ("d", 3),
-                                 ("e", 5), ("f", 8), ("g", 13), ("h", 21)])
-
-
-def huffman_fibonacci_decoder(encoded_message):
-    # TODO: Huffman decoder for Fibonacci numbers
-    pass
-
-
-if __name__ == "__main__":
-    root1 = huffman("Cyka Blyat")
-    root2 = huffman("Shook Ones")
-    print(build_huffman_codes(root1))
-    print(build_huffman_codes(root2))
-
-
- -
- -
- -

Functions

- -
-
-

def build_huffman_codes(

root)

-
- - - - -

Starting from the root node obtained by the huffman algorithm, -this function builds a dictionary where keys are the original characters, -and their values are the corresponding huffman codes.

-
- -
-
def build_huffman_codes(root: HeapNode):
-    """Starting from the `root` node obtained by the `huffman` algorithm,
-    this function builds a dictionary where keys are the original characters,
-    and their values are the corresponding huffman codes."""
-    huffman_codes = {}
-
-    for char in root.value:
-        current_node = root
-        huffman_code = ""  # huffman code of char
-
-        while True:
-            if current_node.left is not None and \
-                    char in current_node.left.value:
-                huffman_code += "0"
-                current_node = current_node.left
-            elif current_node.right is not None:
-                huffman_code += "1"
-                current_node = current_node.right
-            else:
-                break
-
-        huffman_codes[char] = huffman_code
-
-    return huffman_codes
-
-
-
- -
- - -
-
-

def calculate_frequencies(

message)

-
- - - - -
- -
-
def calculate_frequencies(message):
-    frequencies = {}
-    for char in message:
-        if char not in frequencies.keys():
-            frequencies[char] = 1
-        else:
-            frequencies[char] += 1
-    return frequencies
-
-
-
- -
- - -
-
-

def huffman(

message, verbose=False)

-
- - - - -

Creates a Huffman tree representing all the codewords for message.

-

Time Complexity: O(n*log2(n)).

-
- -
-
def huffman(message: str, verbose=False):
-    """Creates a Huffman tree representing all the codewords for message.
-
-    **Time Complexity**: O(n*log2(n))."""
-
-    # Counting the frequencies of each character or symbol
-    frequencies = calculate_frequencies(message)
-
-    if verbose:
-        print_frequencies(frequencies)
-
-    # Creates a queue in O(n) time using the build-min-heap algorithm.
-    mpq = MinPriorityQueue(frequencies.items())
-
-    while not mpq.is_empty():
-
-        left = mpq.extract_min(heap_node=True)
-        right = mpq.extract_min(heap_node=True)
-
-        if right is None:
-            return left
-
-        node = HeapNode(key=left.key + right.key, value=left.value + right.value)
-
-        node.left = left
-        node.right = right
-
-        left.parent = node
-        right.parent = node
-
-        mpq.insert_with_priority(node)
-
-
-
- -
- - -
-
-

def huffman_fibonacci_encoder(

fn)

-
- - - - -

fn is supposed to be a list of tuples, -whose first element is the character or symbol, -whereas the second element is its corresponding frequency, -that happen to be the Fibonacci numbers.

-
- -
-
def huffman_fibonacci_encoder(fn: "list of list"):
-    """`fn` is supposed to be a `list` of `tuple`s,
-    whose first element is the character or symbol,
-    whereas the second element is its corresponding frequency,
-    that happen to be the Fibonacci numbers."""
-    import operator
-
-    fn.sort(key=operator.itemgetter(1), reverse=True)  # sorts by frequency
-    huffman_codes = {fn[-1][0]: "0" * (len(fn) - 1)}
-
-    for i, char in enumerate(fn[:-1]):
-        prefix = "0" * i
-        codeword = prefix + "1"
-        huffman_codes[char[0]] = codeword
-    return huffman_codes
-
-
-
- -
- - -
-
-

def print_frequencies(

frequencies)

-
- - - - -
- -
-
def print_frequencies(frequencies):
-    from tabulate import tabulate
-    import operator
-    print(tabulate(sorted(frequencies.items(), key=operator.itemgetter(1)), headers=("Letter", "Frequency"),
-                   tablefmt="grid"))
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/greedy/index.html b/docs/ands/algorithms/greedy/index.html deleted file mode 100644 index 3c65cc2b..00000000 --- a/docs/ands/algorithms/greedy/index.html +++ /dev/null @@ -1,1064 +0,0 @@ - - - - - - ands.algorithms.greedy API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.greedy module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms.greedy.activity_selection

- - -

Author: Nelson Brochado

-

Consider a set of requests for a room.

-

Only one person can reserve the room at a time, -and you want to allow the maximum number of requests.

-

The requests for periods (si=start time for i, fi=finish time for i) are:

-

(1, 4), (3, 5), (0, 6), (5, 7), (3, 8), (5, 9), (6, 10), ...

- -
-
-

ands.algorithms.greedy.fractional_knapsack

- - -

Author: Nelson Brochado

-

The time complexity of the fractional knapsack is O(n*log(n)), -because of the call to sort the items by value/weight ratio.

- -
-
-

ands.algorithms.greedy.huffman

- - -

Author: Nelson Brochado

-

Warning

-

There are still some functions that I need to implement, -but the basic idea of the huffman algorithm is here, -so I decided to include it already in this repository.

-

Description

-

Huffman coding to encode messages with a variable length of bits.

-

The tree cre...

- -
-
- -
-
- -
- - diff --git a/docs/ands/algorithms/index.html b/docs/ands/algorithms/index.html deleted file mode 100644 index 776f5afb..00000000 --- a/docs/ands/algorithms/index.html +++ /dev/null @@ -1,1103 +0,0 @@ - - - - - - ands.algorithms API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

- - -
-

ands.algorithms.dp

- - - -
- - - - - - - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/math/arithmetic/index.html b/docs/ands/algorithms/math/arithmetic/index.html deleted file mode 100644 index b08faebd..00000000 --- a/docs/ands/algorithms/math/arithmetic/index.html +++ /dev/null @@ -1,1035 +0,0 @@ - - - - - - ands.algorithms.math.arithmetic API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.math.arithmetic module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms.math.arithmetic.sum

- - -

Author: Nelson Brochado

-

Created: 21/01/2017

- -
-
- -
-
- -
- - diff --git a/docs/ands/algorithms/math/arithmetic/sum.m.html b/docs/ands/algorithms/math/arithmetic/sum.m.html deleted file mode 100644 index 28d87a29..00000000 --- a/docs/ands/algorithms/math/arithmetic/sum.m.html +++ /dev/null @@ -1,1176 +0,0 @@ - - - - - - ands.algorithms.math.arithmetic.sum API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.math.arithmetic.sum module

-

Author: Nelson Brochado

-

Created: 21/01/2017

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Created: 21/01/2017
-"""
-
-from ands.algorithms.math.combinatorics.n_choose_k import *
-
-
-def a_plus_b_all_to_n(a, b, n):
-    """Solve polynomials of the form (a + b)n."""
-    s = 0
-    for k in range(0, n + 1):
-        s += n_choose_k(n, k) * (a ** k) * (b ** (n - k))
-    return s
-
-
-def sum_first_m_pos_nat_nums(m):
-    """Sum first `m` natural numbers (starting from 1).
-
-    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
-    return n_choose_k_2(m + 1, 2)
-
-
-def sum_first_m_pos_nat_nums_2(m):
-    """Sum first m natural numbers (starting from 1).
-
-    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
-    return ((m + 1) * m) / (2)
-
-
-def sum_first_m_pos_nat_nums_3(m):
-    """Sum first m natural numbers (starting from 1).
-
-    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
-    s = 0
-    for i in range(m):
-        s += (i + 1)
-    return s
-
-
- -
- -
- -

Functions

- -
-
-

def a_plus_b_all_to_n(

a, b, n)

-
- - - - -

Solve polynomials of the form (a + b)n.

-
- -
-
def a_plus_b_all_to_n(a, b, n):
-    """Solve polynomials of the form (a + b)n."""
-    s = 0
-    for k in range(0, n + 1):
-        s += n_choose_k(n, k) * (a ** k) * (b ** (n - k))
-    return s
-
-
-
- -
- - -
-
-

def sum_first_m_pos_nat_nums(

m)

-
- - - - -

Sum first m natural numbers (starting from 1).

-

sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)

-
- -
-
def sum_first_m_pos_nat_nums(m):
-    """Sum first `m` natural numbers (starting from 1).
-
-    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
-    return n_choose_k_2(m + 1, 2)
-
-
-
- -
- - -
-
-

def sum_first_m_pos_nat_nums_2(

m)

-
- - - - -

Sum first m natural numbers (starting from 1).

-

sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)

-
- -
-
def sum_first_m_pos_nat_nums_2(m):
-    """Sum first m natural numbers (starting from 1).
-
-    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
-    return ((m + 1) * m) / (2)
-
-
-
- -
- - -
-
-

def sum_first_m_pos_nat_nums_3(

m)

-
- - - - -

Sum first m natural numbers (starting from 1).

-

sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)

-
- -
-
def sum_first_m_pos_nat_nums_3(m):
-    """Sum first m natural numbers (starting from 1).
-
-    sum_{j=1}^{m} j = 'm + 1 choose 2' = ((m + 1)*m)/(2)"""
-    s = 0
-    for i in range(m):
-        s += (i + 1)
-    return s
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/math/combinatorics/index.html b/docs/ands/algorithms/math/combinatorics/index.html deleted file mode 100644 index 1e40d113..00000000 --- a/docs/ands/algorithms/math/combinatorics/index.html +++ /dev/null @@ -1,1041 +0,0 @@ - - - - - - ands.algorithms.math.combinatorics API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.math.combinatorics module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms.math.combinatorics.n_choose_k

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 20/01/2017

-

Description

-

"n choose k" is an operation that appears often in combinatorics. -"n choose k" is defined as (n!)/(k! * (n - k)!). -It basically represents the number of ways to choose a subset of size k elements, -disregarding their order, f...

- -
-
- -
-
- -
- - diff --git a/docs/ands/algorithms/math/combinatorics/n_choose_k.m.html b/docs/ands/algorithms/math/combinatorics/n_choose_k.m.html deleted file mode 100644 index 5d8a4f68..00000000 --- a/docs/ands/algorithms/math/combinatorics/n_choose_k.m.html +++ /dev/null @@ -1,1238 +0,0 @@ - - - - - - ands.algorithms.math.combinatorics.n_choose_k API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.math.combinatorics.n_choose_k module

-

Meta info

-

Author: Nelson Brochado

-

Created: 20/01/2017

-

Description

-

"n choose k" is an operation that appears often in combinatorics. -"n choose k" is defined as (n!)/(k! * (n - k)!). -It basically represents the number of ways to choose a subset of size k elements, -disregarding their order, from a set of n elements. -"Disregarding the order" means that, e.g., if you have a set A = {x, y, z}, -then the collections of two elements B = [x, y] and C = [y, x] would be considered the same, -because the order of the elements does not count, i.e. x, y ~= y, x,

-

Resources

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-
-Created: 20/01/2017
-
-## Description
-
-"n choose k" is an operation that appears often in combinatorics.
-"n choose k" is defined as (n!)/(k! * (n - k)!).
-It basically represents the number of ways to choose a subset of size k elements,
-disregarding their order, from a set of n elements.
-"Disregarding the order" means that, e.g., if you have a set A = {x, y, z},
-then the collections of two elements B = [x, y] and C = [y, x] would be considered the same,
-because the order of the elements does not count, i.e. x, y ~= y, x,
-
-## Resources
-
-- [https://en.wikipedia.org/wiki/Binomial_coefficient](https://en.wikipedia.org/wiki/Binomial_coefficient)
-
-"""
-
-from ands.algorithms.recursion.factorial import *
-
-
-def n_choose_k(n: int, k: int) -> int:
-    """Returns the number of ways of choosing `k` elements,
-    disregarding their order, from a set of `n` elements.
-
-    Assumes n >= 0 and k >= 0.
-    This is because not everyone defines "n choose k" for negative `n`s  or `k`s.
-    See here: http://math.stackexchange.com/questions/527831/is-n-choose-k-defined-when-k-0-what-about-n-k"""
-    assert n >= 0 and k >= 0
-
-    if n == k or k == 0:
-        return 1
-    if k > n:
-        return 0
-
-    return factorial(n) / (factorial(k) * factorial(n - k))
-
-
-def n_choose_k_2(n: int, k: int) -> int:
-    """'n choose k' operation can be recursively defined as:
-    'n - 1 choose k - 1' + 'n - 1 choose k',
-    for all integers `n` and `k`, such that `1 <= k <= n - 1`.
-
-    **Proof:**
-
-    Suppose that we have n persons, one named Fred,
-    and that we want to select a committee of size k.
-    There are 'n choose k' different committees.
-    On the other hand, there are 'n - 1 choose k - 1' committees
-    with Fred as a member, and 'n - 1 choose k' committees
-    without Fred as a member.
-    The sum of these two numbers is also the number of committees.
-    """
-    return n_choose_k(n - 1, k - 1) + n_choose_k(n - 1, k)
-
-
-def n_choose_k_3(n: int, k: int) -> int:
-    """Proof this formula is equivalent to the one in `n_choose_k`:
-
-    Note that if we select a subset of size `k`
-    from a set of size `n`, then we leave a subset
-    of size `n − k` behind (the complement).
-    Thus A -> Ac is a one-to-one correspondence
-    between subsets of size `k` and subsets of size `n − k`."""
-    return n_choose_k(n, n - k)
-
-
- -
- -
- -

Functions

- -
-
-

def n_choose_k(

n, k)

-
- - - - -

Returns the number of ways of choosing k elements, -disregarding their order, from a set of n elements.

-

Assumes n >= 0 and k >= 0. -This is because not everyone defines "n choose k" for negative ns or ks. -See here: http://math.stackexchange.com/questions/527831/is-n-choose-k-defined-when-k-0-what-about-n-k

-
- -
-
def n_choose_k(n: int, k: int) -> int:
-    """Returns the number of ways of choosing `k` elements,
-    disregarding their order, from a set of `n` elements.
-
-    Assumes n >= 0 and k >= 0.
-    This is because not everyone defines "n choose k" for negative `n`s  or `k`s.
-    See here: http://math.stackexchange.com/questions/527831/is-n-choose-k-defined-when-k-0-what-about-n-k"""
-    assert n >= 0 and k >= 0
-
-    if n == k or k == 0:
-        return 1
-    if k > n:
-        return 0
-
-    return factorial(n) / (factorial(k) * factorial(n - k))
-
-
-
- -
- - -
-
-

def n_choose_k_2(

n, k)

-
- - - - -

'n choose k' operation can be recursively defined as: -'n - 1 choose k - 1' + 'n - 1 choose k', -for all integers n and k, such that 1 <= k <= n - 1.

-

Proof:

-

Suppose that we have n persons, one named Fred, -and that we want to select a committee of size k. -There are 'n choose k' different committees. -On the other hand, there are 'n - 1 choose k - 1' committees -with Fred as a member, and 'n - 1 choose k' committees -without Fred as a member. -The sum of these two numbers is also the number of committees.

-
- -
-
def n_choose_k_2(n: int, k: int) -> int:
-    """'n choose k' operation can be recursively defined as:
-    'n - 1 choose k - 1' + 'n - 1 choose k',
-    for all integers `n` and `k`, such that `1 <= k <= n - 1`.
-
-    **Proof:**
-
-    Suppose that we have n persons, one named Fred,
-    and that we want to select a committee of size k.
-    There are 'n choose k' different committees.
-    On the other hand, there are 'n - 1 choose k - 1' committees
-    with Fred as a member, and 'n - 1 choose k' committees
-    without Fred as a member.
-    The sum of these two numbers is also the number of committees.
-    """
-    return n_choose_k(n - 1, k - 1) + n_choose_k(n - 1, k)
-
-
-
- -
- - -
-
-

def n_choose_k_3(

n, k)

-
- - - - -

Proof this formula is equivalent to the one in n_choose_k:

-

Note that if we select a subset of size k -from a set of size n, then we leave a subset -of size n − k behind (the complement). -Thus A -> Ac is a one-to-one correspondence -between subsets of size k and subsets of size n − k.

-
- -
-
def n_choose_k_3(n: int, k: int) -> int:
-    """Proof this formula is equivalent to the one in `n_choose_k`:
-
-    Note that if we select a subset of size `k`
-    from a set of size `n`, then we leave a subset
-    of size `n − k` behind (the complement).
-    Thus A -> Ac is a one-to-one correspondence
-    between subsets of size `k` and subsets of size `n − k`."""
-    return n_choose_k(n, n - k)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/math/index.html b/docs/ands/algorithms/math/index.html deleted file mode 100644 index ebdd2f1d..00000000 --- a/docs/ands/algorithms/math/index.html +++ /dev/null @@ -1,1040 +0,0 @@ - - - - - - ands.algorithms.math API documentation - - - - - - - - - - - - - - -Top - -
- - - - - -
- -
- - diff --git a/docs/ands/algorithms/parsing/index.html b/docs/ands/algorithms/parsing/index.html deleted file mode 100644 index 843c0ef1..00000000 --- a/docs/ands/algorithms/parsing/index.html +++ /dev/null @@ -1,1036 +0,0 @@ - - - - - - ands.algorithms.parsing API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.parsing module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms.parsing.smep

- - -

Author: Nelson Brochado

-

An mathematical infix-to-postfix expression parser/converter. -Includes also a calculator that receives a postfix expression.

- -
-
- -
-
- -
- - diff --git a/docs/ands/algorithms/parsing/smep.m.html b/docs/ands/algorithms/parsing/smep.m.html deleted file mode 100644 index 3c8c5d65..00000000 --- a/docs/ands/algorithms/parsing/smep.m.html +++ /dev/null @@ -1,1469 +0,0 @@ - - - - - - ands.algorithms.parsing.smep API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.parsing.smep module

-

Author: Nelson Brochado

-

An mathematical infix-to-postfix expression parser/converter. -Includes also a calculator that receives a postfix expression.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-An mathematical infix-to-postfix expression parser/converter.
-Includes also a calculator that receives a postfix expression.
-"""
-
-import operator
-import re
-
-# higher number => higher precedence
-OPERATORS = {
-    "+": 1,
-    "-": 1,
-    "*": 2,
-    "/": 2,
-    "%": 2,
-    "^": 3
-}
-
-# Associates symbols with functions
-OPS = {
-    "+": operator.add,
-    "-": operator.sub,
-    "*": operator.mul,
-    "/": operator.truediv,
-    "%": operator.mod,
-    "^": operator.pow
-}
-
-PARENTHESIS = {"(", ")"}
-
-REGEX = re.compile(r"(\d+|\w+|[-+*/^%()])")
-
-
-def parse(e, regex=REGEX):
-    """Parses a string expression e to a infix represation list."""
-    return regex.findall(e)
-
-
-def infix_to_postfix(infix):
-    """Return a list representing the postfix representation of the list `infix`.
-
-    Algorithm based on:
-
-    - https://www.youtube.com/watch?v=vXPL6UavUeA
-
-    - http://interactivepython.org/runestone/static/pythonds/BasicDS/InfixPrefixandPostfixExpressions.html"""
-    stack = []
-    postfix = []
-
-    # Used for counting the number of opening and closing parenthesis,
-    # which should be the same
-    opening_paren = 0
-    closing_paren = 0
-
-    for i, c in enumerate(infix):
-        if c in OPERATORS:
-            if i > 0 and infix[i - 1] in OPERATORS:
-                raise SyntaxError("no two operators can be in a row")
-
-            if len(stack) > 0:
-                top = stack[-1]
-
-                if top in OPERATORS:
-                    if OPERATORS[c] > OPERATORS[top]:
-                        stack.append(c)
-                    else:
-                        while top in OPERATORS and OPERATORS[
-                                top] >= OPERATORS[c]:
-                            op = stack.pop()
-                            postfix.append(op)
-                            if len(stack) > 0:
-                                top = stack[-1]
-                            else:
-                                break
-                        stack.append(c)
-                else:
-                    stack.append(c)
-            else:
-                stack.append(c)
-
-        elif c in PARENTHESIS:
-            if c == ")":
-                if len(stack) > 0:
-                    top = stack[-1]
-                    while top != "(":
-                        try:
-                            # pop throws an IndexError if the list is empty
-                            r = stack.pop()
-                            # Adding what's in between ( ) to the postfix list
-                            postfix.append(r)
-                            top = stack[-1]
-                        except IndexError:
-                            raise SyntaxError("'(' not found when popping")
-
-                    stack.pop()  # Removes ( from the top of the stack
-                else:
-                    raise SyntaxError(
-                        "')' cannot be added to the stack if it is empty")
-                closing_paren += 1
-            else:
-                stack.append(c)  # c == '('
-                opening_paren += 1
-        else:  # All the rest is considered an operand
-            if i > 0 and infix[
-                    i - 1] not in OPERATORS and infix[i - 1] not in PARENTHESIS:
-                raise SyntaxError("no two operands can be in a row")
-
-            postfix.append(c)
-
-    if opening_paren != closing_paren:
-        raise SyntaxError(
-            "number of opening and closing parenthesis do not match")
-
-    while len(stack) > 0:
-        top = stack.pop()
-        if top in OPERATORS:
-            postfix.append(top)
-
-    return postfix
-
-
-def calc(postfix):
-    """Simple mathematical postfix expression calculator."""
-    stack = []
-    for c in postfix:
-        if c not in OPERATORS:
-            stack.append(c)
-        else:
-            top = int(stack.pop())
-            top2 = int(stack.pop())
-            stack.append(OPS[c](top2, top))
-    return stack
-
-
-def tostr(ls):
-    return " ".join(ls)
-
-
-# TESTS
-
-def test1():
-    # ifx = "a+b/c*(d+e)-f"
-    # ifx = "(12)^3 * x^3 +  (4  * 3)^3 + ()"
-    # ifx = "a+b*c"
-    # ifx = "3 + 2 * 2 ^ 3 % 3"
-    # ifx = "(((3 + 2) * 4))"
-
-    ifx = "((12*2^3+44*3)*3)"
-    print("Infix:", ifx)
-
-    ls = parse(ifx)
-    print("Infix list:", ls)
-
-    # ls = ['(', '(', '(', '3', '+', '2', '(', ')', ')', '*', '4', ')', ')']
-    # print("Infix:", tostr(ls))
-
-    pfx = infix_to_postfix(ls)
-    print("Postfix list:", pfx)
-    print("Calculated:", calc(pfx))
-
-    pfx_str = tostr(pfx)
-    print("Postfix:", pfx_str)
-
-
-if __name__ == "__main__":
-    test1()
-
-
- -
- -
-

Module variables

-
-

var OPERATORS

- - -
-
- -
-
-

var OPS

- - -
-
- -
-
-

var PARENTHESIS

- - -
-
- -
-
-

var REGEX

- - -
-
- -
- -

Functions

- -
-
-

def calc(

postfix)

-
- - - - -

Simple mathematical postfix expression calculator.

-
- -
-
def calc(postfix):
-    """Simple mathematical postfix expression calculator."""
-    stack = []
-    for c in postfix:
-        if c not in OPERATORS:
-            stack.append(c)
-        else:
-            top = int(stack.pop())
-            top2 = int(stack.pop())
-            stack.append(OPS[c](top2, top))
-    return stack
-
-
-
- -
- - -
-
-

def infix_to_postfix(

infix)

-
- - - - -

Return a list representing the postfix representation of the list infix.

-

Algorithm based on:

-
    -
  • -

    https://www.youtube.com/watch?v=vXPL6UavUeA

    -
  • -
  • -

    http://interactivepython.org/runestone/static/pythonds/BasicDS/InfixPrefixandPostfixExpressions.html

    -
  • -
-
- -
-
def infix_to_postfix(infix):
-    """Return a list representing the postfix representation of the list `infix`.
-
-    Algorithm based on:
-
-    - https://www.youtube.com/watch?v=vXPL6UavUeA
-
-    - http://interactivepython.org/runestone/static/pythonds/BasicDS/InfixPrefixandPostfixExpressions.html"""
-    stack = []
-    postfix = []
-
-    # Used for counting the number of opening and closing parenthesis,
-    # which should be the same
-    opening_paren = 0
-    closing_paren = 0
-
-    for i, c in enumerate(infix):
-        if c in OPERATORS:
-            if i > 0 and infix[i - 1] in OPERATORS:
-                raise SyntaxError("no two operators can be in a row")
-
-            if len(stack) > 0:
-                top = stack[-1]
-
-                if top in OPERATORS:
-                    if OPERATORS[c] > OPERATORS[top]:
-                        stack.append(c)
-                    else:
-                        while top in OPERATORS and OPERATORS[
-                                top] >= OPERATORS[c]:
-                            op = stack.pop()
-                            postfix.append(op)
-                            if len(stack) > 0:
-                                top = stack[-1]
-                            else:
-                                break
-                        stack.append(c)
-                else:
-                    stack.append(c)
-            else:
-                stack.append(c)
-
-        elif c in PARENTHESIS:
-            if c == ")":
-                if len(stack) > 0:
-                    top = stack[-1]
-                    while top != "(":
-                        try:
-                            # pop throws an IndexError if the list is empty
-                            r = stack.pop()
-                            # Adding what's in between ( ) to the postfix list
-                            postfix.append(r)
-                            top = stack[-1]
-                        except IndexError:
-                            raise SyntaxError("'(' not found when popping")
-
-                    stack.pop()  # Removes ( from the top of the stack
-                else:
-                    raise SyntaxError(
-                        "')' cannot be added to the stack if it is empty")
-                closing_paren += 1
-            else:
-                stack.append(c)  # c == '('
-                opening_paren += 1
-        else:  # All the rest is considered an operand
-            if i > 0 and infix[
-                    i - 1] not in OPERATORS and infix[i - 1] not in PARENTHESIS:
-                raise SyntaxError("no two operands can be in a row")
-
-            postfix.append(c)
-
-    if opening_paren != closing_paren:
-        raise SyntaxError(
-            "number of opening and closing parenthesis do not match")
-
-    while len(stack) > 0:
-        top = stack.pop()
-        if top in OPERATORS:
-            postfix.append(top)
-
-    return postfix
-
-
-
- -
- - -
-
-

def parse(

e, regex=re.compile('(\\d+|\\w+|[-+*/^%()])'))

-
- - - - -

Parses a string expression e to a infix represation list.

-
- -
-
def parse(e, regex=REGEX):
-    """Parses a string expression e to a infix represation list."""
-    return regex.findall(e)
-
-
-
- -
- - -
-
-

def test1(

)

-
- - - - -
- -
-
def test1():
-    # ifx = "a+b/c*(d+e)-f"
-    # ifx = "(12)^3 * x^3 +  (4  * 3)^3 + ()"
-    # ifx = "a+b*c"
-    # ifx = "3 + 2 * 2 ^ 3 % 3"
-    # ifx = "(((3 + 2) * 4))"
-
-    ifx = "((12*2^3+44*3)*3)"
-    print("Infix:", ifx)
-
-    ls = parse(ifx)
-    print("Infix list:", ls)
-
-    # ls = ['(', '(', '(', '3', '+', '2', '(', ')', ')', '*', '4', ')', ')']
-    # print("Infix:", tostr(ls))
-
-    pfx = infix_to_postfix(ls)
-    print("Postfix list:", pfx)
-    print("Calculated:", calc(pfx))
-
-    pfx_str = tostr(pfx)
-    print("Postfix:", pfx_str)
-
-
-
- -
- - -
-
-

def tostr(

ls)

-
- - - - -
- -
-
def tostr(ls):
-    return " ".join(ls)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/primes/index.html b/docs/ands/algorithms/primes/index.html deleted file mode 100644 index 31a0121a..00000000 --- a/docs/ands/algorithms/primes/index.html +++ /dev/null @@ -1,1035 +0,0 @@ - - - - - - ands.algorithms.primes API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.primes module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms.primes.is_prime

- - -

Author: Nelson Brochado

-

Primality Tests.

- -
-
- -
-
- -
- - diff --git a/docs/ands/algorithms/primes/is_prime.m.html b/docs/ands/algorithms/primes/is_prime.m.html deleted file mode 100644 index 72847e50..00000000 --- a/docs/ands/algorithms/primes/is_prime.m.html +++ /dev/null @@ -1,1269 +0,0 @@ - - - - - - ands.algorithms.primes.is_prime API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.primes.is_prime module

-

Author: Nelson Brochado

-

Primality Tests.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Primality Tests.
-"""
-
-
-def is_prime(n):
-    """Return `True` if `n` is prime, `False` otherwise."""
-    if n < 2:  # primes are greater than 1
-        return False
-    if n % 2 == 0:
-        return n == 2  # returns True if n == 2 because 2 is a prime
-    i = 3
-    while i * i <= n:
-        if n % i == 0:
-            return False
-        i += 2
-    return True
-
-
-def _is_prime_r(n, i):
-    if i * i <= n:
-        if n % i == 0:
-            return False
-        else:
-            return _is_prime_r(n, i + 2)
-    return True
-
-
-def is_prime_r(n):
-    """Return `True` if `n` is prime, `False` otherwise.
-
-    This function uses recursion.
-    In general, you should prefer an iterative approach,
-    because the stack has a limit,
-    and if exceeded an exception is thrown."""
-    if n <= 1:  # primes are greater than 1
-        return False
-    if n % 2 == 0:
-        return n == 2
-    return _is_prime_r(n, 3)
-
-
-def is_prime_2(n):
-    """Return `True` if `n` is prime, `False` otherwise.
-
-    This algorithm seems to perform better than `is_prime`.
-
-    **Time Complexity:** O(√n/2 * O(n % i == 0))."""
-    if n == 2 or n == 3:
-        return True
-    if n % 2 == 0 or n < 2:
-        return False
-    for i in range(3, int(n ** 0.5) + 1, 2):
-        if n % i == 0:
-            return False
-    return True
-
-
-# TESTS
-
-def test1():
-    from time import time
-    a = time()
-    for i in range(10000000):
-        is_prime_2(i)
-    # assert is_prime(i) == is_prime_2(i)
-    b = time()
-    print(b - a)
-
-
-def test2():
-    from time import time
-    a = time()
-    is_prime_2(10093100991010310111)
-    b = time()
-    print(b - a)
-
-
-if __name__ == "__main__":
-    test2()
-    # test1()
-
-
- -
- -
- -

Functions

- -
-
-

def is_prime(

n)

-
- - - - -

Return True if n is prime, False otherwise.

-
- -
-
def is_prime(n):
-    """Return `True` if `n` is prime, `False` otherwise."""
-    if n < 2:  # primes are greater than 1
-        return False
-    if n % 2 == 0:
-        return n == 2  # returns True if n == 2 because 2 is a prime
-    i = 3
-    while i * i <= n:
-        if n % i == 0:
-            return False
-        i += 2
-    return True
-
-
-
- -
- - -
-
-

def is_prime_2(

n)

-
- - - - -

Return True if n is prime, False otherwise.

-

This algorithm seems to perform better than is_prime.

-

Time Complexity: O(√n/2 * O(n % i == 0)).

-
- -
-
def is_prime_2(n):
-    """Return `True` if `n` is prime, `False` otherwise.
-
-    This algorithm seems to perform better than `is_prime`.
-
-    **Time Complexity:** O(√n/2 * O(n % i == 0))."""
-    if n == 2 or n == 3:
-        return True
-    if n % 2 == 0 or n < 2:
-        return False
-    for i in range(3, int(n ** 0.5) + 1, 2):
-        if n % i == 0:
-            return False
-    return True
-
-
-
- -
- - -
-
-

def is_prime_r(

n)

-
- - - - -

Return True if n is prime, False otherwise.

-

This function uses recursion. -In general, you should prefer an iterative approach, -because the stack has a limit, -and if exceeded an exception is thrown.

-
- -
-
def is_prime_r(n):
-    """Return `True` if `n` is prime, `False` otherwise.
-
-    This function uses recursion.
-    In general, you should prefer an iterative approach,
-    because the stack has a limit,
-    and if exceeded an exception is thrown."""
-    if n <= 1:  # primes are greater than 1
-        return False
-    if n % 2 == 0:
-        return n == 2
-    return _is_prime_r(n, 3)
-
-
-
- -
- - -
-
-

def test1(

)

-
- - - - -
- -
-
def test1():
-    from time import time
-    a = time()
-    for i in range(10000000):
-        is_prime_2(i)
-    # assert is_prime(i) == is_prime_2(i)
-    b = time()
-    print(b - a)
-
-
-
- -
- - -
-
-

def test2(

)

-
- - - - -
- -
-
def test2():
-    from time import time
-    a = time()
-    is_prime_2(10093100991010310111)
-    b = time()
-    print(b - a)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/recursion/ackermann.m.html b/docs/ands/algorithms/recursion/ackermann.m.html deleted file mode 100644 index 6492494d..00000000 --- a/docs/ands/algorithms/recursion/ackermann.m.html +++ /dev/null @@ -1,1119 +0,0 @@ - - - - - - ands.algorithms.recursion.ackermann API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.recursion.ackermann module

-

Meta info

-

Author: Nelson Brochado

-

Creation: 22/02/16

-

Updated: 18/01/2017

-

Description

-

The Ackermann function is the simplest example of a well defined total function. -Total function means that it's defined for all possible inputs. -The function is computable, but not primitive recursive. -A primitive recursive function is a function that can be implemented using only "for" loops, -i.e. loops that have a fixed number of iterations. -A computable function is a function that can be implemented using "while" loops. -Note that "do" loops are a particular case of while loops.

-

It grows faster than an exponential function, or even a multiple exponential function.

-

References

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-
-Creation: 22/02/16
-
-Updated: 18/01/2017
-
-## Description
-
-The Ackermann function is the simplest example of a well defined total function.
-Total function means that it's defined for all possible inputs.
-The function is computable, but **not** primitive recursive.
-A primitive recursive function is a function that can be implemented using only "for" loops,
-i.e. loops that have a fixed number of iterations.
-A computable function is a function that can be implemented using "while" loops.
-Note that "do" loops are a particular case of while loops.
-
-It grows faster than an exponential function, or even a multiple exponential function.
-
-## References
-
-- [http://mathworld.wolfram.com/AckermannFunction.html](http://mathworld.wolfram.com/AckermannFunction.html)
-- [http://math.stackexchange.com/questions/75296/what-is-the-difference-between-total-recursive-and-primitive-recursive-functions](http://math.stackexchange.com/questions/75296/what-is-the-difference-between-total-recursive-and-primitive-recursive-functions)
-- [https://en.wikipedia.org/wiki/Ackermann_function](https://en.wikipedia.org/wiki/Ackermann_function)
-"""
-
-
-def ackermann(m: int, n: int) -> int:
-    assert m >= 0 and n >= 0
-    if m == 0:
-        return n + 1
-    elif n == 0:
-        return ackermann(m - 1, 1)
-    else:
-        return ackermann(m - 1, ackermann(m, n - 1))
-
-
- -
- -
- -

Functions

- -
-
-

def ackermann(

m, n)

-
- - - - -
- -
-
def ackermann(m: int, n: int) -> int:
-    assert m >= 0 and n >= 0
-    if m == 0:
-        return n + 1
-    elif n == 0:
-        return ackermann(m - 1, 1)
-    else:
-        return ackermann(m - 1, ackermann(m, n - 1))
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/recursion/count.m.html b/docs/ands/algorithms/recursion/count.m.html deleted file mode 100644 index 607a88a7..00000000 --- a/docs/ands/algorithms/recursion/count.m.html +++ /dev/null @@ -1,1103 +0,0 @@ - - - - - - ands.algorithms.recursion.count API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.recursion.count module

-

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 21/01/2017

-

Description

-

A very simple example of how to count the number occurrences -of a certain object o in a list ls.

-

You should not use recursion in general for doing this task: -for example, in Python the stack limit is quite small: 1000 -This is just an example of recursive algorithm!

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-
-Created: 2015
-
-Updated: 21/01/2017
-
-## Description
-
-A very simple example of how to count the number occurrences
-of a certain object `o` in a list `ls`.
-
-You should not use recursion in general for doing this task:
-for example, in Python the stack limit is quite small: 1000
-This is just an example of recursive algorithm!
-"""
-
-__all__ = ["count"]
-
-
-def _count(elem: object, ls, index: int) -> int:
-    if index < len(ls):
-        if ls[index] == elem:
-            return 1 + _count(elem, ls, index + 1)
-        else:
-            return _count(elem, ls, index + 1)
-    return 0
-
-
-def count(elem: object, ls) -> int:
-    """Counts how many times `elem` appears in the list or tuple `ls`."""
-    return _count(elem, ls, 0)
-
-
- -
- -
- -

Functions

- -
-
-

def count(

elem, ls)

-
- - - - -

Counts how many times elem appears in the list or tuple ls.

-
- -
-
def count(elem: object, ls) -> int:
-    """Counts how many times `elem` appears in the list or tuple `ls`."""
-    return _count(elem, ls, 0)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/recursion/factorial.m.html b/docs/ands/algorithms/recursion/factorial.m.html deleted file mode 100644 index c1aefd64..00000000 --- a/docs/ands/algorithms/recursion/factorial.m.html +++ /dev/null @@ -1,1310 +0,0 @@ - - - - - - ands.algorithms.recursion.factorial API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.recursion.factorial module

-

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 20/02/2017

-

Description

-

The factorial of a number n is defined recursively as follows:

-
fact(n):
-    # Assume n is int and n >= 0
-    if n == 0 or n == 1:
-        return 1
-    else:
-        return n * fact(n - 1)  # n * (n - 1)!
-
-

Resources

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-
-Created: 2015
-
-Updated: 20/02/2017
-
-## Description
-
-The factorial of a number n is defined recursively as follows:
-
-    fact(n):
-        # Assume n is int and n >= 0
-        if n == 0 or n == 1:
-            return 1
-        else:
-            return n * fact(n - 1)  # n * (n - 1)!
-
-### Resources
-
-- [http://www.math.uah.edu/stat/foundations/Structures.html#com2](http://www.math.uah.edu/stat/foundations/Structures.html#com2)
-
-"""
-
-__all__ = ["factorial", "iterative_factorial", "smallest_geq", "multiple_factorial"]
-
-
-def factorial(n: int) -> int:
-    """Returns the factorial of `n`, which is calculated recursively,
-    as it's usually defined mathematically.
-
-    Assumes that `n >= 0`."""
-    assert n >= 0
-
-    if n == 0:
-        return 1
-    elif n == 1 or n == 2:
-        return n
-    else:
-        return n * factorial(n - 1)
-
-
-def iterative_factorial(n: int) -> int:
-    """Returns the factorial of `n`, which is calculated iteratively.
-    This is just for comparison with the recursive implementation.
-
-    Since the "factorial" is a primitive recursive function,
-    it can be implemented iteratively.
-
-    Proof that factorial is a primitive recursive function:
-    https://proofwiki.org/wiki/Factorial_is_Primitive_Recursive.
-
-    A primitive recursive function is a recursive function which can be implemented with "for" loops.
-    See here: http://mathworld.wolfram.com/PrimitiveRecursiveFunction.html"""
-    assert n >= 0
-
-    if n == 0 or n == 1:
-        return 1
-
-    f = 1
-    for i in range(2, n + 1):
-        f *= i
-
-    return f
-
-
-def smallest_geq(x: int) -> int:
-    """Returns the smallest number `n` such that `n! >= x`.
-
-    Assumes a non-negative integer `x` as input.
-
-    "geq" stands for greater or equal."""
-    assert x >= 0
-
-    n = 0
-    while iterative_factorial(n) < x:
-        n += 1
-
-    return n
-
-
-def _multiple_factorial(n: int, i: int, a: list) -> list:
-    if i <= n:
-        a.append(factorial(i))
-        _multiple_factorial(n, i + 1, a)
-    return a
-
-
-def multiple_factorial(n: int) -> list:
-    """Returns a list L of factorials from 0 to n, that is:
-        L[0] := 0!
-        L[1] := 1!
-        ...
-        L[n] := n!
-    If n is a negative number, returns an empty list.
-
-    Assumes n >= 0."""
-    assert n >= 0
-    return _multiple_factorial(n, 0, [])
-
-
- -
- -
- -

Functions

- -
-
-

def factorial(

n)

-
- - - - -

Returns the factorial of n, which is calculated recursively, -as it's usually defined mathematically.

-

Assumes that n >= 0.

-
- -
-
def factorial(n: int) -> int:
-    """Returns the factorial of `n`, which is calculated recursively,
-    as it's usually defined mathematically.
-
-    Assumes that `n >= 0`."""
-    assert n >= 0
-
-    if n == 0:
-        return 1
-    elif n == 1 or n == 2:
-        return n
-    else:
-        return n * factorial(n - 1)
-
-
-
- -
- - -
-
-

def iterative_factorial(

n)

-
- - - - -

Returns the factorial of n, which is calculated iteratively. -This is just for comparison with the recursive implementation.

-

Since the "factorial" is a primitive recursive function, -it can be implemented iteratively.

-

Proof that factorial is a primitive recursive function: -https://proofwiki.org/wiki/Factorial_is_Primitive_Recursive.

-

A primitive recursive function is a recursive function which can be implemented with "for" loops. -See here: http://mathworld.wolfram.com/PrimitiveRecursiveFunction.html

-
- -
-
def iterative_factorial(n: int) -> int:
-    """Returns the factorial of `n`, which is calculated iteratively.
-    This is just for comparison with the recursive implementation.
-
-    Since the "factorial" is a primitive recursive function,
-    it can be implemented iteratively.
-
-    Proof that factorial is a primitive recursive function:
-    https://proofwiki.org/wiki/Factorial_is_Primitive_Recursive.
-
-    A primitive recursive function is a recursive function which can be implemented with "for" loops.
-    See here: http://mathworld.wolfram.com/PrimitiveRecursiveFunction.html"""
-    assert n >= 0
-
-    if n == 0 or n == 1:
-        return 1
-
-    f = 1
-    for i in range(2, n + 1):
-        f *= i
-
-    return f
-
-
-
- -
- - -
-
-

def multiple_factorial(

n)

-
- - - - -

Returns a list L of factorials from 0 to n, that is: - L[0] := 0! - L[1] := 1! - ... - L[n] := n! -If n is a negative number, returns an empty list.

-

Assumes n >= 0.

-
- -
-
def multiple_factorial(n: int) -> list:
-    """Returns a list L of factorials from 0 to n, that is:
-        L[0] := 0!
-        L[1] := 1!
-        ...
-        L[n] := n!
-    If n is a negative number, returns an empty list.
-
-    Assumes n >= 0."""
-    assert n >= 0
-    return _multiple_factorial(n, 0, [])
-
-
-
- -
- - -
-
-

def smallest_geq(

x)

-
- - - - -

Returns the smallest number n such that n! >= x.

-

Assumes a non-negative integer x as input.

-

"geq" stands for greater or equal.

-
- -
-
def smallest_geq(x: int) -> int:
-    """Returns the smallest number `n` such that `n! >= x`.
-
-    Assumes a non-negative integer `x` as input.
-
-    "geq" stands for greater or equal."""
-    assert x >= 0
-
-    n = 0
-    while iterative_factorial(n) < x:
-        n += 1
-
-    return n
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/recursion/hanoi.m.html b/docs/ands/algorithms/recursion/hanoi.m.html deleted file mode 100644 index d6fd99a7..00000000 --- a/docs/ands/algorithms/recursion/hanoi.m.html +++ /dev/null @@ -1,1197 +0,0 @@ - - - - - - ands.algorithms.recursion.hanoi API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.recursion.hanoi module

-

Meta info

-

Author: Nelson Brochado

-

Creation: 27/02/16

-

Updated: 18/01/2017

-

Description

-

Towers of Hanoi is a mathematical game. -It consists of 3 rods, and a number of disks of different sizes, which can slide onto any rod. -The game starts with the disks in a neat stack in ascending order of size on one rod, -the smallest at the top, thus making a conical shape.

-

The objective of the game is to move the entire stack to another rod, -obeying the following rules:

-
    -
  1. -

    Only 1 disk can be moved at a time.

    -
  2. -
  3. -

    Each move consists of taking the upper disk -from one of the stacks and placing it on top of another stack, -i.e. a disk can only be moved if it is the uppermost disk on its stack.

    -
  4. -
  5. -

    No disk may be placed on top of a smaller disk.

    -
  6. -
-

With 3 disks, the game can be solved with at least 7 moves (best case). -The minimum number of moves required to solve a tower of hanoi game -is 2^n - 1, where n is the number of disks.

-

For simplicity, in the following algorithm -the source (='A'), auxiliary (='B') and destination (='C') rodes are fixed, -and therefore the algorithm always shows the steps to go from 'A' to 'C'.

-

References

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-
-Creation: 27/02/16
-
-Updated: 18/01/2017
-
-## Description
-
-Towers of Hanoi is a mathematical game.
-It consists of 3 rods, and a number of disks of different sizes, which can slide onto any rod.
-The game starts with the disks in a neat stack in ascending order of size on one rod,
-the smallest at the top, thus making a conical shape.
-
-The objective of the game is to move the entire stack to another rod,
-obeying the following rules:
-
-1. Only 1 disk can be moved at a time.
-
-2. Each move consists of taking the upper disk
-from one of the stacks and placing it on top of another stack,
-i.e. a disk can only be moved if it is the uppermost disk on its stack.
-
-3. No disk may be placed on top of a smaller disk.
-
-With 3 disks, the game can be solved with at least 7 moves (best case).
-The minimum number of moves required to solve a tower of hanoi game
-is 2^n - 1, where n is the number of disks.
-
-For simplicity, in the following algorithm
-the source (='A'), auxiliary (='B') and destination (='C') rodes are fixed,
-and therefore the algorithm always shows the steps to go from 'A' to 'C'.
-
-## References
-
-- [https://en.wikipedia.org/wiki/Tower_of_Hanoi](https://en.wikipedia.org/wiki/Tower_of_Hanoi)
-- [http://www.cut-the-knot.org/recurrence/hanoi.shtml](http://www.cut-the-knot.org/recurrence/hanoi.shtml)
-- [http://stackoverflow.com/questions/105838/real-world-examples-of-recursion](http://stackoverflow.com/questions/105838/real-world-examples-of-recursion)
-"""
-
-__all__ = ["hanoi"]
-
-
-def _hanoi(n: int, ls: list, src='A', aux='B', dst='C') -> list:
-    """Recursively solve the Towers of Hanoi game for `n` disks.
-
-    The smallest disk, which is the topmost one at the beginning,
-    is called 1, and the largest one is called `n`.
-
-    `src` is the start rod where all disks are set in a neat stack in ascending order.
-    `aux` is the third rod.
-    `dst` is similarly the destination rod."""
-    if n > 0:
-        _hanoi(n - 1, ls, src, dst, aux)
-        ls.append((n, src, dst))
-        _hanoi(n - 1, ls, aux, src, dst)
-    return ls
-
-
-def hanoi(n: int) -> list:
-    """Returns a list L of tuples each of them representing a move to be done.
-
-    `n` is the number of disks.
-    The number of rods is clearly always 3.
-
-    L[i] must be done before L[i + 1], for all i.
-    L[i][0] := the disk number (or id).
-    Numbers start from 1 and go up to n.
-    L[i][1] := the source rod from which to move L[i][0].
-    L[i][2] := the destination rod to which to move L[i][0].
-
-    The disk with the smallest radius (at the top) is the disk number 1,
-    its successor in terms or radius' size is disk number 2, and so on.
-    So the largest disk is disk number n."""
-    assert n >= 0
-    return _hanoi(n, [])
-
-
- -
- -
- -

Functions

- -
-
-

def hanoi(

n)

-
- - - - -

Returns a list L of tuples each of them representing a move to be done.

-

n is the number of disks. -The number of rods is clearly always 3.

-

L[i] must be done before L[i + 1], for all i. -L[i][0] := the disk number (or id). -Numbers start from 1 and go up to n. -L[i][1] := the source rod from which to move L[i][0]. -L[i][2] := the destination rod to which to move L[i][0].

-

The disk with the smallest radius (at the top) is the disk number 1, -its successor in terms or radius' size is disk number 2, and so on. -So the largest disk is disk number n.

-
- -
-
def hanoi(n: int) -> list:
-    """Returns a list L of tuples each of them representing a move to be done.
-
-    `n` is the number of disks.
-    The number of rods is clearly always 3.
-
-    L[i] must be done before L[i + 1], for all i.
-    L[i][0] := the disk number (or id).
-    Numbers start from 1 and go up to n.
-    L[i][1] := the source rod from which to move L[i][0].
-    L[i][2] := the destination rod to which to move L[i][0].
-
-    The disk with the smallest radius (at the top) is the disk number 1,
-    its successor in terms or radius' size is disk number 2, and so on.
-    So the largest disk is disk number n."""
-    assert n >= 0
-    return _hanoi(n, [])
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/recursion/index.html b/docs/ands/algorithms/recursion/index.html deleted file mode 100644 index df9c0d78..00000000 --- a/docs/ands/algorithms/recursion/index.html +++ /dev/null @@ -1,1167 +0,0 @@ - - - - - - ands.algorithms.recursion API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.recursion module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms.recursion.ackermann

- - -

Meta info

-

Author: Nelson Brochado

-

Creation: 22/02/16

-

Updated: 18/01/2017

-

Description

-

The Ackermann function is the simplest example of a well defined total function. -Total function means that it's defined for all possible inputs. -The function is computable, but not primitive recursive...

- -
-
-

ands.algorithms.recursion.count

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 21/01/2017

-

Description

-

A very simple example of how to count the number occurrences -of a certain object o in a list ls.

-

You should not use recursion in general for doing this task: -for example, in Python the stack limit is quit...

- -
-
-

ands.algorithms.recursion.factorial

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 20/02/2017

-

Description

-

The factorial of a number n is defined recursively as follows:

-
fact(n):
-    # Assume n is int and n >= 0
-    if n == 0 or n == 1:
-        return 1
-    else:
-        return n * fact(n -...
-
- -
-
-

ands.algorithms.recursion.hanoi

- - -

Meta info

-

Author: Nelson Brochado

-

Creation: 27/02/16

-

Updated: 18/01/2017

-

Description

-

Towers of Hanoi is a mathematical game. -It consists of 3 rods, and a number of disks of different sizes, which can slide onto any rod. -The game starts with the disks in a neat stack in ascending order of ...

- -
-
-

ands.algorithms.recursion.is_sorted

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 21/01/2017

-

Description

-

is_sorted check if a list or tuple contains elements in sorted order by using recursion. -This algorithm can potentially be modified to work with other collections. -The other versions are here just for comparison.

- -
-
-

ands.algorithms.recursion.make_decimal

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 20/01/2017

-

Description

-

Converts a number in a certain base in the range [2, 36] to a decimal number (number in base 10). -Bases greater than 10 take in order as digits the letters of the English alphabet. -For example, a number system...

- -
-
-

ands.algorithms.recursion.palindrome

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 20/01/2017

-

Description

-

Checking recursively if a string is a palindrome. -A palindrome is a string that reads the same way forward and backward. -For example, "anna" is a palindrome, whereas "prime" is not.

-

Resources

-
    -
  • [https://e...
  • -
- -
-
-

ands.algorithms.recursion.power

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 18/01/2017

-

Description

-

Raising an integer a to the k >= 0 using recursion, i.e., ak = b.

- -
-
-

ands.algorithms.recursion.reverse

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 16/01/2017

-

Description

-

Reverses in-place the elements of a list using recursion. -This method could also be adapted to work with other mutable collections.

- -
-
- -
-
- -
- - diff --git a/docs/ands/algorithms/recursion/is_sorted.m.html b/docs/ands/algorithms/recursion/is_sorted.m.html deleted file mode 100644 index 75447027..00000000 --- a/docs/ands/algorithms/recursion/is_sorted.m.html +++ /dev/null @@ -1,1211 +0,0 @@ - - - - - - ands.algorithms.recursion.is_sorted API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.recursion.is_sorted module

-

Meta info

-

Author: Nelson Brochado

-

Created: 21/01/2017

-

Description

-

is_sorted check if a list or tuple contains elements in sorted order by using recursion. -This algorithm can potentially be modified to work with other collections. -The other versions are here just for comparison.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-
-Created: 21/01/2017
-
-## Description
-
-`is_sorted` check if a list or tuple contains elements in sorted order by using recursion.
-This algorithm can potentially be modified to work with other collections.
-The other versions are here just for comparison.
-
-"""
-
-import operator
-
-__all__ = ["is_sorted", "iterative_is_sorted", "pythonic_is_sorted"]
-
-
-def _is_sorted(a, i: int, op) -> bool:
-    """`i` is used to index the two adjacent elements of `a`.
-
-    op can either be >, if `a` should be in ascending order,
-    or <, if `a` should be in descending order."""
-    if i == len(a) - 1:  # If i is the last index, there's nothing more to check, thus the list is sorted.
-        return True
-    if op(a[i], a[i + 1]):
-        return False
-    else:
-        return _is_sorted(a, i + 1, op)
-
-
-def is_sorted(a, rev=False) -> bool:
-    """Checks recursively if `a` is sorted.
-
-    If `rev` is `True`, this function checks if `a` is sorted in descending order,
-    else if it's sorted in ascending order."""
-    if len(a) < 2:
-        return True
-
-    op = operator.gt
-    if rev:
-        op = operator.lt
-
-    return _is_sorted(a, 0, op)
-
-
-def iterative_is_sorted(a, rev=False) -> bool:
-    """Iterative alternative to `is_sorted`.
-
-    **Time Complexity**: O(n)."""
-    if len(a) < 2:
-        return True
-
-    op = operator.gt
-    if rev:
-        op = operator.lt
-
-    for i in range(len(a) - 1):
-        if op(a[i], a[i + 1]):
-            return False
-
-    return True
-
-
-def pythonic_is_sorted(a, rev=False) -> bool:
-    """Checking if a is sorted in a shorter way by using the `all` function."""
-    op = operator.le
-    if rev:
-        op = operator.ge
-    return all(op(a[i], a[i + 1]) for i in range(len(a) - 1))
-
-
- -
- -
- -

Functions

- -
-
-

def is_sorted(

a, rev=False)

-
- - - - -

Checks recursively if a is sorted.

-

If rev is True, this function checks if a is sorted in descending order, -else if it's sorted in ascending order.

-
- -
-
def is_sorted(a, rev=False) -> bool:
-    """Checks recursively if `a` is sorted.
-
-    If `rev` is `True`, this function checks if `a` is sorted in descending order,
-    else if it's sorted in ascending order."""
-    if len(a) < 2:
-        return True
-
-    op = operator.gt
-    if rev:
-        op = operator.lt
-
-    return _is_sorted(a, 0, op)
-
-
-
- -
- - -
-
-

def iterative_is_sorted(

a, rev=False)

-
- - - - -

Iterative alternative to is_sorted.

-

Time Complexity: O(n).

-
- -
-
def iterative_is_sorted(a, rev=False) -> bool:
-    """Iterative alternative to `is_sorted`.
-
-    **Time Complexity**: O(n)."""
-    if len(a) < 2:
-        return True
-
-    op = operator.gt
-    if rev:
-        op = operator.lt
-
-    for i in range(len(a) - 1):
-        if op(a[i], a[i + 1]):
-            return False
-
-    return True
-
-
-
- -
- - -
-
-

def pythonic_is_sorted(

a, rev=False)

-
- - - - -

Checking if a is sorted in a shorter way by using the all function.

-
- -
-
def pythonic_is_sorted(a, rev=False) -> bool:
-    """Checking if a is sorted in a shorter way by using the `all` function."""
-    op = operator.le
-    if rev:
-        op = operator.ge
-    return all(op(a[i], a[i + 1]) for i in range(len(a) - 1))
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/recursion/make_decimal.m.html b/docs/ands/algorithms/recursion/make_decimal.m.html deleted file mode 100644 index 64962d33..00000000 --- a/docs/ands/algorithms/recursion/make_decimal.m.html +++ /dev/null @@ -1,1178 +0,0 @@ - - - - - - ands.algorithms.recursion.make_decimal API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.recursion.make_decimal module

-

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 20/01/2017

-

Description

-

Converts a number in a certain base in the range [2, 36] to a decimal number (number in base 10). -Bases greater than 10 take in order as digits the letters of the English alphabet. -For example, a number system with base 11, would take 'a' as the 11th digit.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-
-Created: 2015
-
-Updated: 20/01/2017
-
-## Description
-
-Converts a number in a certain base in the range [2, 36] to a decimal number (number in base 10).
-Bases greater than 10 take in order as digits the letters of the English alphabet.
-For example, a number system with base 11, would take 'a' as the 11th digit.
-"""
-
-from string import ascii_lowercase, digits
-
-__all__ = ["make_decimal", "ALPHA_NUMERIC_ALPHABET"]
-
-
-def _build_alpha_numeric_alphabet() -> dict:
-    """Returns a dictionary whose keys are all nine digits from 0 to 9
-    and the 26 letters of the English alphabet.
-    The values of the numbers are the numbers themselves;
-    the values of the letters are 10 for 'a', 11 for 'b', and so on until 36 for 'z'."""
-    alphabet = {}
-    for i, char in enumerate(ascii_lowercase):
-        # Letters of the alphabet start after digit 9.
-        alphabet[char] = i + 10
-    for i, char in enumerate(digits):
-        alphabet[char] = i
-    return alphabet
-
-
-ALPHA_NUMERIC_ALPHABET = _build_alpha_numeric_alphabet()
-
-
-def _make_decimal(n: str, b: int, pos: int) -> int:
-    """Suppose we have a number `n` (represented as string) in base `b`: xyz
-    The algorithm of converting it to a decimal representation is as follows.
-
-        b^{0} * decimal_value(z) + b^{1} * decimal_value(y) + b^{2} * decimal_value(x),
-
-    where decimal_value(z) is the decimal value of `z`.
-
-    For example, suppose "ef2" is an hexadecimal number
-    that we want to convert to decimal, which should yield 3826.
-
-        16^{0} * decimal_value(2) + 16^{1} * decimal_value(f) + 16^{2} * decimal_value(e) =
-        1 * 2 + 16 * 15 + 256 * 14 =
-        2 + 240 + 3584 =
-        3826
-
-    Note: in any number `xyz` in any base `b`,
-    z is in the "ones" position (has the smaller "value"),
-    y is in the "tens" position and
-    x is in the "hundreds" position (has the greatest "value").
-    See here: http://www.math.com/school/subject1/lessons/S1U1L1GL.html."""
-    if len(n) == 0:
-        return 0
-    else:
-        last = b ** pos * ALPHA_NUMERIC_ALPHABET[n[-1]]
-        return _make_decimal(n[:-1], b, pos + 1) + last
-
-
-def make_decimal(n: str, base: int) -> int:
-    """`n` is a number in any base in the range [2, 36].
-    `base` is the base in which `n` is currently represented.
-
-    Assumes `n` only contains digits in the range 0..9
-    and letters of the English alphabet.
-
-    Returns the decimal representation of `n` (as a int)."""
-    if not n:
-        raise ValueError("n cannot be an empty string or None")
-    if base > 36 or base < 2:
-        raise ValueError("not base >= 2 and base <= 36")
-    else:
-        return _make_decimal(n, base, 0)
-
-
- -
- -
-

Module variables

-
-

var ALPHA_NUMERIC_ALPHABET

- - -
-
- -
- -

Functions

- -
-
-

def make_decimal(

n, base)

-
- - - - -

n is a number in any base in the range [2, 36]. -base is the base in which n is currently represented.

-

Assumes n only contains digits in the range 0..9 -and letters of the English alphabet.

-

Returns the decimal representation of n (as a int).

-
- -
-
def make_decimal(n: str, base: int) -> int:
-    """`n` is a number in any base in the range [2, 36].
-    `base` is the base in which `n` is currently represented.
-
-    Assumes `n` only contains digits in the range 0..9
-    and letters of the English alphabet.
-
-    Returns the decimal representation of `n` (as a int)."""
-    if not n:
-        raise ValueError("n cannot be an empty string or None")
-    if base > 36 or base < 2:
-        raise ValueError("not base >= 2 and base <= 36")
-    else:
-        return _make_decimal(n, base, 0)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/recursion/palindrome.m.html b/docs/ands/algorithms/recursion/palindrome.m.html deleted file mode 100644 index b4551bfb..00000000 --- a/docs/ands/algorithms/recursion/palindrome.m.html +++ /dev/null @@ -1,1115 +0,0 @@ - - - - - - ands.algorithms.recursion.palindrome API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.recursion.palindrome module

-

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 20/01/2017

-

Description

-

Checking recursively if a string is a palindrome. -A palindrome is a string that reads the same way forward and backward. -For example, "anna" is a palindrome, whereas "prime" is not.

-

Resources

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-
-Created: 2015
-
-Updated: 20/01/2017
-
-## Description
-
-Checking recursively if a string is a palindrome.
-A palindrome is a string that reads the same way forward and backward.
-For example, "anna" is a palindrome, whereas "prime" is not.
-
-## Resources
-
-- [https://en.wikipedia.org/wiki/Palindrome](https://en.wikipedia.org/wiki/Palindrome)
-
-"""
-
-__all__ = ["is_palindrome"]
-
-
-def _is_palindrome(s: str, l: int, r: int) -> bool:
-    """`l` is the index that indexes `s` from the left
-    and, similarly, `r` indexes it from the right."""
-    if l >= r:
-        return True
-    if s[l] == s[r]:
-        return _is_palindrome(s, l + 1, r - 1)
-    else:
-        return False
-
-
-def is_palindrome(s: str) -> bool:
-    """Returns `True` if the string `s` is a palindrome, `False` otherwise."""
-    if len(s) <= 1:
-        return True
-    else:
-        return _is_palindrome(s, 0, len(s) - 1)
-
-
- -
- -
- -

Functions

- -
-
-

def is_palindrome(

s)

-
- - - - -

Returns True if the string s is a palindrome, False otherwise.

-
- -
-
def is_palindrome(s: str) -> bool:
-    """Returns `True` if the string `s` is a palindrome, `False` otherwise."""
-    if len(s) <= 1:
-        return True
-    else:
-        return _is_palindrome(s, 0, len(s) - 1)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/recursion/power.m.html b/docs/ands/algorithms/recursion/power.m.html deleted file mode 100644 index caf1ea67..00000000 --- a/docs/ands/algorithms/recursion/power.m.html +++ /dev/null @@ -1,1101 +0,0 @@ - - - - - - ands.algorithms.recursion.power API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.recursion.power module

-

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 18/01/2017

-

Description

-

Raising an integer a to the k >= 0 using recursion, i.e., ak = b.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-
-Created: 2015
-
-Updated: 18/01/2017
-
-## Description
-
-Raising an integer `a` to the `k >= 0` using recursion, i.e., ak = b.
-"""
-
-
-def power(base: int, p: int) -> int:
-    """Assumes inputs are integers and that the power `p >= 0`.
-
-    Base case: a0 = 1.
-    Recursive step: an + 1 = an * a."""
-    assert p >= 0
-
-    if p == 0:
-        return 1
-    else:
-        return base * power(base, p - 1)
-
-
- -
- -
- -

Functions

- -
-
-

def power(

base, p)

-
- - - - -

Assumes inputs are integers and that the power p >= 0.

-

Base case: a0 = 1. -Recursive step: an + 1 = an * a.

-
- -
-
def power(base: int, p: int) -> int:
-    """Assumes inputs are integers and that the power `p >= 0`.
-
-    Base case: a0 = 1.
-    Recursive step: an + 1 = an * a."""
-    assert p >= 0
-
-    if p == 0:
-        return 1
-    else:
-        return base * power(base, p - 1)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/recursion/reverse.m.html b/docs/ands/algorithms/recursion/reverse.m.html deleted file mode 100644 index 0eed2562..00000000 --- a/docs/ands/algorithms/recursion/reverse.m.html +++ /dev/null @@ -1,1100 +0,0 @@ - - - - - - ands.algorithms.recursion.reverse API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.recursion.reverse module

-

Meta info

-

Author: Nelson Brochado

-

Created: 2015

-

Updated: 16/01/2017

-

Description

-

Reverses in-place the elements of a list using recursion. -This method could also be adapted to work with other mutable collections.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-## Meta info
-
-Author: Nelson Brochado
-
-Created: 2015
-
-Updated: 16/01/2017
-
-## Description
-
-Reverses in-place the elements of a list using recursion.
-This method could also be adapted to work with other mutable collections.
-"""
-
-__all__ = ["reverse"]
-
-
-def _reverse(ls: list, i: int, j: int) -> list:
-    if (j - i) >= 1:
-        ls[j], ls[i] = ls[i], ls[j]
-        _reverse(ls, i + 1, j - 1)
-    return ls
-
-
-def reverse(ls: list) -> list:
-    """Returns the reverse of the list `ls` using recursion."""
-    if len(ls) < 2:
-        return ls
-    else:
-        return _reverse(ls, 0, len(ls) - 1)
-
-
- -
- -
- -

Functions

- -
-
-

def reverse(

ls)

-
- - - - -

Returns the reverse of the list ls using recursion.

-
- -
-
def reverse(ls: list) -> list:
-    """Returns the reverse of the list `ls` using recursion."""
-    if len(ls) < 2:
-        return ls
-    else:
-        return _reverse(ls, 0, len(ls) - 1)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/sorting/bubble_sort.m.html b/docs/ands/algorithms/sorting/bubble_sort.m.html deleted file mode 100644 index f2b66e8d..00000000 --- a/docs/ands/algorithms/sorting/bubble_sort.m.html +++ /dev/null @@ -1,1098 +0,0 @@ - - - - - - ands.algorithms.sorting.bubble_sort API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.sorting.bubble_sort module

-

Author: Nelson Brochado

-

Modified: 01/07/16

-

Resources

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Modified: 01/07/16
-
-### Resources
-- [Bubble Sort](http://en.wikipedia.org/wiki/Bubble_sort), Wiki's article
-
-- [The Bubble Sort](http://interactivepython.org/runestone/static/pythonds/SortSearch/TheBubbleSort.html),
-article by [http://interactivepython.org](http://interactivepython.org)
-"""
-
-
-def bubble_sort(ls: list):
-    """In-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity:** O(n2)."""
-    for i in range(len(ls) - 1):
-        for j in range(len(ls) - 1 - i):
-            if ls[j] > ls[j + 1]:
-                ls[j], ls[j + 1] = ls[j + 1], ls[j]
-    return ls
-
-
- -
- -
- -

Functions

- -
-
-

def bubble_sort(

ls)

-
- - - - -

In-place sorting algorithm. -Returns a reference to ls.

-

Time Complexity: O(n2).

-
- -
-
def bubble_sort(ls: list):
-    """In-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity:** O(n2)."""
-    for i in range(len(ls) - 1):
-        for j in range(len(ls) - 1 - i):
-            if ls[j] > ls[j + 1]:
-                ls[j], ls[j + 1] = ls[j + 1], ls[j]
-    return ls
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/sorting/heap_sort.m.html b/docs/ands/algorithms/sorting/heap_sort.m.html deleted file mode 100644 index cef080bb..00000000 --- a/docs/ands/algorithms/sorting/heap_sort.m.html +++ /dev/null @@ -1,1168 +0,0 @@ - - - - - - ands.algorithms.sorting.heap_sort API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.sorting.heap_sort module

-

Author: Nelson Brochado

-

Creation: 09/09/15

-

Modified: 01/07/16

-

Resources

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Creation: 09/09/15
-
-Modified: 01/07/16
-
-### Resources
-- [Heap Sort](https://en.wikipedia.org/wiki/Heapsort), Wiki's article
-- [MIT's video lecture on Heaps and Heapsort](http://video.mit.edu/watch/introduction-to-algorithms-lecture-4-heaps-and-heap-sort-14154/)
-"""
-
-
-def max_heapify(ls: list, heap_size: int, i: int):
-    m = i
-    left = 2 * i + 1
-    right = 2 * i + 2
-    if left < heap_size and ls[left] > ls[m]:
-        m = left
-    if right < heap_size and ls[right] > ls[m]:
-        m = right
-    if i != m:
-        ls[i], ls[m] = ls[m], ls[i]
-        max_heapify(ls, heap_size, m)
-
-
-def build_max_heap(ls: list):
-    for i in range(len(ls) // 2, -1, -1):
-        max_heapify(ls, len(ls), i)
-    return ls
-
-
-def heap_sort(ls: list):
-    """In-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity:** O(n*log2(n))."""
-    build_max_heap(ls)
-    for i in range(len(ls) - 1, 0, -1):
-        ls[i], ls[0] = ls[0], ls[i]
-        max_heapify(ls, i, 0)
-    return ls
-
-
- -
- -
- -

Functions

- -
-
-

def build_max_heap(

ls)

-
- - - - -
- -
-
def build_max_heap(ls: list):
-    for i in range(len(ls) // 2, -1, -1):
-        max_heapify(ls, len(ls), i)
-    return ls
-
-
-
- -
- - -
-
-

def heap_sort(

ls)

-
- - - - -

In-place sorting algorithm. -Returns a reference to ls.

-

Time Complexity: O(n*log2(n)).

-
- -
-
def heap_sort(ls: list):
-    """In-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity:** O(n*log2(n))."""
-    build_max_heap(ls)
-    for i in range(len(ls) - 1, 0, -1):
-        ls[i], ls[0] = ls[0], ls[i]
-        max_heapify(ls, i, 0)
-    return ls
-
-
-
- -
- - -
-
-

def max_heapify(

ls, heap_size, i)

-
- - - - -
- -
-
def max_heapify(ls: list, heap_size: int, i: int):
-    m = i
-    left = 2 * i + 1
-    right = 2 * i + 2
-    if left < heap_size and ls[left] > ls[m]:
-        m = left
-    if right < heap_size and ls[right] > ls[m]:
-        m = right
-    if i != m:
-        ls[i], ls[m] = ls[m], ls[i]
-        max_heapify(ls, heap_size, m)
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/sorting/index.html b/docs/ands/algorithms/sorting/index.html deleted file mode 100644 index 3f9d8411..00000000 --- a/docs/ands/algorithms/sorting/index.html +++ /dev/null @@ -1,1137 +0,0 @@ - - - - - - ands.algorithms.sorting API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.sorting module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms.sorting.bubble_sort

- - -

Author: Nelson Brochado

-

Modified: 01/07/16

-

Resources

-
- -
-
-

ands.algorithms.sorting.heap_sort

- - -

Author: Nelson Brochado

-

Creation: 09/09/15

-

Modified: 01/07/16

-

Resources

-
- -
-
-

ands.algorithms.sorting.insertion_sort

- - -

Author: Nelson Brochado

-

Modified: 01/07/16

-

Resources

-
- -
-
-

ands.algorithms.sorting.merge_sort

- - -

Author: Nelson Brochado

-

Modified: 01/07/16

-

Resources

-
    -
  • -

    Merge Sort, Wiki's article

    -
  • -
  • -

    The Merge Sort, -section of online book on searching and sorting by -[http://inte...

    -
  • -
- -
-
-

ands.algorithms.sorting.quick_sort

- - -

Author: Nelson Brochado

-

Modified: 01/07/16

-

Parts of the quicksort algorithm

-
    -
  1. -

    Partition

    -

    All elements smaller than a number usually called "pivot" -are put to the left of the pivot.

    -

    In this quick sort algorithm, the pivot is chosen to be -the last element of the range [`...

    -
  2. -
- -
-
-

ands.algorithms.sorting.selection_sort

- - -

Author: Nelson Brochado

-

Modified: 01/07/16

-

Resources

-
- -
-
- -
-
- -
- - diff --git a/docs/ands/algorithms/sorting/insertion_sort.m.html b/docs/ands/algorithms/sorting/insertion_sort.m.html deleted file mode 100644 index 324c7e98..00000000 --- a/docs/ands/algorithms/sorting/insertion_sort.m.html +++ /dev/null @@ -1,1102 +0,0 @@ - - - - - - ands.algorithms.sorting.insertion_sort API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.sorting.insertion_sort module

-

Author: Nelson Brochado

-

Modified: 01/07/16

-

Resources

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Modified: 01/07/16
-
-### Resources
-
-- [Insertion Sort](http://en.wikipedia.org/wiki/Insertion_sort), Wiki's article
-
-- [The Insertion Sort](http://interactivepython.org/runestone/static/pythonds/SortSearch/TheInsertionSort.html),
-article by [http://interactivepython.org](http://interactivepython.org)
-"""
-
-
-def insertion_sort(ls: list):
-    """In-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity**: O(n2)."""
-    for i in range(1, len(ls)):
-        n = i
-        while n > 0 and ls[n] < ls[n - 1]:
-            ls[n], ls[n - 1] = ls[n - 1], ls[n]
-            n -= 1
-    return ls
-
-
- -
- -
- -

Functions

- -
-
-

def insertion_sort(

ls)

-
- - - - -

In-place sorting algorithm. -Returns a reference to ls.

-

Time Complexity: O(n2).

-
- -
-
def insertion_sort(ls: list):
-    """In-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity**: O(n2)."""
-    for i in range(1, len(ls)):
-        n = i
-        while n > 0 and ls[n] < ls[n - 1]:
-            ls[n], ls[n - 1] = ls[n - 1], ls[n]
-            n -= 1
-    return ls
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/sorting/merge_sort.m.html b/docs/ands/algorithms/sorting/merge_sort.m.html deleted file mode 100644 index d36d944e..00000000 --- a/docs/ands/algorithms/sorting/merge_sort.m.html +++ /dev/null @@ -1,1258 +0,0 @@ - - - - - - ands.algorithms.sorting.merge_sort API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.sorting.merge_sort module

-

Author: Nelson Brochado

-

Modified: 01/07/16

-

Resources

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Modified: 01/07/16
-
-### Resources
-- [Merge Sort](http://en.wikipedia.org/wiki/Merge_sort), Wiki's article
-
-- [The Merge Sort](http://interactivepython.org/runestone/static/pythonds/SortSearch/TheMergeSort.html),
-section of online book on searching and sorting by
-[http://interactivepython.org](http://interactivepython.org)
-"""
-
-
-def merge(left: list, right: list):
-    """Merges 2 sorted lists (`left` and `right`) in 1 single list,
-    which is returned at the end.
-
-    **Time Complexity**: O(m), where `m = len(left) + len(right)`."""
-    mid = []
-    i = 0  # Used to index the left list.
-    j = 0  # Used to index the right list.
-
-    while i < len(left) and j < len(right):
-        if left[i] < right[j]:
-            mid.append(left[i])
-            i += 1
-        else:
-            mid.append(right[j])
-            j += 1
-
-    while i < len(left):
-        mid.append(left[i])
-        i += 1
-
-    while j < len(right):
-        mid.append(right[j])
-        j += 1
-
-    return mid
-
-
-def merge_r(left: list, right: list):
-    """Equivalent to `merge`, but using recursion
-    and creating new sub-lists at each recursion call.
-
-    You should use `merge` instead of this function,
-    because the space complexity of this algorithm is higher."""
-    if len(left) == 0:
-        return right
-    elif len(right) == 0:
-        return left
-    elif left[0] < right[0]:
-        return [left[0]] + merge_r(left[1:], right)
-    else:
-        return [right[0]] + merge_r(left, right[1:])
-
-
-def _merge_sort_aux(ls: list):
-    """Not-in-place sorting algorithm.
-
-    Splits the original list `ls` until we have many sub-lists
-    of one element (which is by the way the base case).
-
-    Note that a list of 1 element is sorted by definition.
-
-    Using the merge algorithm,
-    we can easily merge two sorted lists of size 1,
-    to obtain a merged sorted list of size 2.
-    We keep merging greater sorted lists,
-    until we obtain the final sorted list.
-
-    **Time Complexity**: O(n*log2(n))"""
-
-    # Base case, where "ls" contains either 1 or 0 items,
-    # and it is by definition sorted.
-    if len(ls) < 2:
-        return ls
-
-    # Calls merge_sort on the left half part of ls.
-    left = merge_sort(ls[0:len(ls) // 2])
-
-    # Calls merge_sort on the right half part of ls.
-    right = merge_sort(ls[len(ls) // 2:])
-
-    # Note that in the previous 2 statements,
-    # we are creating new sub-lists using ls[0:len(ls)//2],
-    # for the first case, for example.
-
-    # Returns a new sorted list composed of the items in left and right.
-    return merge(left, right)
-
-
-def merge_sort(ls: list):
-    """Not-in-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity**: O(n*log2(n))."""
-    ls = _merge_sort_aux(ls)
-    return ls
-
-
- -
- -
- -

Functions

- -
-
-

def merge(

left, right)

-
- - - - -

Merges 2 sorted lists (left and right) in 1 single list, -which is returned at the end.

-

Time Complexity: O(m), where m = len(left) + len(right).

-
- -
-
def merge(left: list, right: list):
-    """Merges 2 sorted lists (`left` and `right`) in 1 single list,
-    which is returned at the end.
-
-    **Time Complexity**: O(m), where `m = len(left) + len(right)`."""
-    mid = []
-    i = 0  # Used to index the left list.
-    j = 0  # Used to index the right list.
-
-    while i < len(left) and j < len(right):
-        if left[i] < right[j]:
-            mid.append(left[i])
-            i += 1
-        else:
-            mid.append(right[j])
-            j += 1
-
-    while i < len(left):
-        mid.append(left[i])
-        i += 1
-
-    while j < len(right):
-        mid.append(right[j])
-        j += 1
-
-    return mid
-
-
-
- -
- - -
-
-

def merge_r(

left, right)

-
- - - - -

Equivalent to merge, but using recursion -and creating new sub-lists at each recursion call.

-

You should use merge instead of this function, -because the space complexity of this algorithm is higher.

-
- -
-
def merge_r(left: list, right: list):
-    """Equivalent to `merge`, but using recursion
-    and creating new sub-lists at each recursion call.
-
-    You should use `merge` instead of this function,
-    because the space complexity of this algorithm is higher."""
-    if len(left) == 0:
-        return right
-    elif len(right) == 0:
-        return left
-    elif left[0] < right[0]:
-        return [left[0]] + merge_r(left[1:], right)
-    else:
-        return [right[0]] + merge_r(left, right[1:])
-
-
-
- -
- - -
-
-

def merge_sort(

ls)

-
- - - - -

Not-in-place sorting algorithm. -Returns a reference to ls.

-

Time Complexity: O(n*log2(n)).

-
- -
-
def merge_sort(ls: list):
-    """Not-in-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity**: O(n*log2(n))."""
-    ls = _merge_sort_aux(ls)
-    return ls
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/sorting/quick_sort.m.html b/docs/ands/algorithms/sorting/quick_sort.m.html deleted file mode 100644 index f9dab29b..00000000 --- a/docs/ands/algorithms/sorting/quick_sort.m.html +++ /dev/null @@ -1,1228 +0,0 @@ - - - - - - ands.algorithms.sorting.quick_sort API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.sorting.quick_sort module

-

Author: Nelson Brochado

-

Modified: 01/07/16

-

Parts of the quicksort algorithm

-
    -
  1. -

    Partition

    -

    All elements smaller than a number usually called "pivot" -are put to the left of the pivot.

    -

    In this quick sort algorithm, the pivot is chosen to be -the last element of the range [start, end], -but it could also have been choosen, e.g., to be the middle element.

    -

    We keep searching for elements less than the pivot, -from the left to the right of the range [start, end[, -and we insert them at the position tracked by the variable p_index.

    -

    So, p_index keeps track of the position (or index) -in the range [start, end[, where all elements to the left of p_index are smaller than the pivot. -Before returning this position (p_index), the pivot is inserted in that position. -Note that doing this, the pivot will be already in its final sorted position.

    -
  2. -
  3. -

    Recursive Calls

    -

    quick_sort is called recursively -on the left of the pivot and on the right until start >= end.

    -
  4. -
-

Resources

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Modified: 01/07/16
-
-
-### Parts of the quicksort algorithm
-
-1. **Partition**
-
-    All elements smaller than a number  usually called "pivot"
-    are put to the left of the pivot.
-
-    In this quick sort algorithm, the pivot is chosen to be
-    the last element of the range [`start`, `end`],
-    but it could also have been choosen, e.g., to be the middle element.
-
-    We keep searching for elements less than the pivot,
-    from the left to the right of the range [`start`, `end`[,
-    and we insert them at the position tracked by the variable `p_index`.
-
-    So, `p_index` keeps track of the position (or index)
-    in the range [`start`, `end`[, where all elements to the left of `p_index` are smaller than the pivot.
-    Before returning this position (`p_index`), the pivot is inserted in that position.
-    Note that doing this, the pivot will be already in its final sorted position.
-
-2. **Recursive Calls**
-
-    `quick_sort` is called recursively
-    on the left of the pivot and on the right until `start >= end`.
-
-
-## Resources
-- [Quicksort](http://en.wikipedia.org/wiki/Quicksort), Wiki's article
-- [The Quick Sort](http://interactivepython.org/runestone/static/pythonds/SortSearch/TheQuickSort.html),
-section of online book on searching and sorting by
-[http://interactivepython.org](http://interactivepython.org)
-"""
-
-
-def partition(ls: list, start: int, end: int):
-    """Shifts all elements in `ls` that are less than the pivot
-    to the left of the position `p_index`, which is at the end returned.
-
-    **Time Complexity:** O(k), where k is the size of `ls`."""
-    pivot = ls[end]  # Take last element as pivot.
-    p_index = start
-
-    for i in range(start, end):
-        if ls[i] <= pivot:
-            ls[p_index], ls[i] = ls[i], ls[p_index]
-            p_index += 1
-
-    # Insert the pivot at index p_index (the pivot's index).
-    ls[p_index], ls[end] = ls[end], ls[p_index]
-    return p_index
-
-
-def _quick_sort_aux(ls: list, start: int, end: int):
-    """Keeps calling partition to find the pivot index,
-    and then calls itself recursively on the left and right
-    sides of the pivot index (`p_index`)."""
-    if start < end:
-        # Returns the pivot index after partition.
-        p_index = partition(ls, start, end)
-
-        # Calling _quick_sort_aux on the left side of the pivot.
-        _quick_sort_aux(ls, start, p_index - 1)
-
-        # Calling quick_sort on the right side of the pivot.
-        _quick_sort_aux(ls, p_index + 1, end)
-
-
-def quick_sort(ls: list):
-    """In-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity**
-
-    - Worst Case: O(n2)
-
-    - Average Case: O(n*log2(n))"""
-    _quick_sort_aux(ls, 0, len(ls) - 1)
-    return ls
-
-
- -
- -
- -

Functions

- -
-
-

def partition(

ls, start, end)

-
- - - - -

Shifts all elements in ls that are less than the pivot -to the left of the position p_index, which is at the end returned.

-

Time Complexity: O(k), where k is the size of ls.

-
- -
-
def partition(ls: list, start: int, end: int):
-    """Shifts all elements in `ls` that are less than the pivot
-    to the left of the position `p_index`, which is at the end returned.
-
-    **Time Complexity:** O(k), where k is the size of `ls`."""
-    pivot = ls[end]  # Take last element as pivot.
-    p_index = start
-
-    for i in range(start, end):
-        if ls[i] <= pivot:
-            ls[p_index], ls[i] = ls[i], ls[p_index]
-            p_index += 1
-
-    # Insert the pivot at index p_index (the pivot's index).
-    ls[p_index], ls[end] = ls[end], ls[p_index]
-    return p_index
-
-
-
- -
- - -
-
-

def quick_sort(

ls)

-
- - - - -

In-place sorting algorithm. -Returns a reference to ls.

-

Time Complexity

-
    -
  • -

    Worst Case: O(n2)

    -
  • -
  • -

    Average Case: O(n*log2(n))

    -
  • -
-
- -
-
def quick_sort(ls: list):
-    """In-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity**
-
-    - Worst Case: O(n2)
-
-    - Average Case: O(n*log2(n))"""
-    _quick_sort_aux(ls, 0, len(ls) - 1)
-    return ls
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/sorting/selection_sort.m.html b/docs/ands/algorithms/sorting/selection_sort.m.html deleted file mode 100644 index 37437c08..00000000 --- a/docs/ands/algorithms/sorting/selection_sort.m.html +++ /dev/null @@ -1,1102 +0,0 @@ - - - - - - ands.algorithms.sorting.selection_sort API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.sorting.selection_sort module

-

Author: Nelson Brochado

-

Modified: 01/07/16

-

Resources

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Modified: 01/07/16
-
-### Resources
-
-- [Selection Sort](http://en.wikipedia.org/wiki/Selection_sort), Wiki's article
-
-- [The Selection Sort](http://interactivepython.org/runestone/static/pythonds/SortSearch/TheSelectionSort.html),
-article at [http://interactivepython.org](http://interactivepython.org)
-"""
-
-
-def selection_sort(ls: list):
-    """In-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity**: O(n2)."""
-    for i in range(len(ls) - 1):
-        k = i
-        for j in range(i + 1, len(ls)):
-            if ls[j] < ls[k]:
-                ls[k], ls[j] = ls[j], ls[k]
-    return ls
-
-
- -
- -
- -

Functions

- -
-
-

def selection_sort(

ls)

-
- - - - -

In-place sorting algorithm. -Returns a reference to ls.

-

Time Complexity: O(n2).

-
- -
-
def selection_sort(ls: list):
-    """In-place sorting algorithm.
-    Returns a reference to `ls`.
-
-    **Time Complexity**: O(n2)."""
-    for i in range(len(ls) - 1):
-        k = i
-        for j in range(i + 1, len(ls)):
-            if ls[j] < ls[k]:
-                ls[k], ls[j] = ls[j], ls[k]
-    return ls
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/algorithms/unclassified/index.html b/docs/ands/algorithms/unclassified/index.html deleted file mode 100644 index bd696260..00000000 --- a/docs/ands/algorithms/unclassified/index.html +++ /dev/null @@ -1,1033 +0,0 @@ - - - - - - ands.algorithms.unclassified API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.unclassified module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

- -
- -
-
- -
- - diff --git a/docs/ands/algorithms/unclassified/max_num_dups.m.html b/docs/ands/algorithms/unclassified/max_num_dups.m.html deleted file mode 100644 index 34604953..00000000 --- a/docs/ands/algorithms/unclassified/max_num_dups.m.html +++ /dev/null @@ -1,1110 +0,0 @@ - - - - - - ands.algorithms.unclassified.max_num_dups API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.algorithms.unclassified.max_num_dups module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-
-def max_num_dups(A):
-    """Find the maximum number of duplicated numbers.
-
-    A must be sorted!!
-    """
-    m = 0
-    c = 1
-    for i in range(len(A) - 1):
-        if A[i] == A[i + 1]:
-            c += 1
-        elif c > m:
-            m = c
-            c = 1
-    return m
-
-
-def test():
-    ls = [12, 12, 12, 92, 92]
-    print("List:", ls)
-    print("Max number of duplicates:", max_num_dups(ls))
-
-
-if __name__ == '__main__':
-    test()
-
-
- -
- -
- -

Functions

- -
-
-

def max_num_dups(

A)

-
- - - - -

Find the maximum number of duplicated numbers.

-

A must be sorted!!

-
- -
-
def max_num_dups(A):
-    """Find the maximum number of duplicated numbers.
-
-    A must be sorted!!
-    """
-    m = 0
-    c = 1
-    for i in range(len(A) - 1):
-        if A[i] == A[i + 1]:
-            c += 1
-        elif c > m:
-            m = c
-            c = 1
-    return m
-
-
-
- -
- - -
-
-

def test(

)

-
- - - - -
- -
-
def test():
-    ls = [12, 12, 12, 92, 92]
-    print("List:", ls)
-    print("Max number of duplicates:", max_num_dups(ls))
-
-
-
- -
- - - -
- -
-
- -
- - diff --git a/docs/ands/ds/BST.m.html b/docs/ands/ds/BST.m.html deleted file mode 100644 index c5265174..00000000 --- a/docs/ands/ds/BST.m.html +++ /dev/null @@ -1,4518 +0,0 @@ - - - - - - ands.ds.BST API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.BST module

-

Meta info

-

Author: Nelson Brochado

-

Created: 01/07/2015

-

Updated: 28/08/2016

-

Description

-

Coding Conventions

-

In general, if a variable name has more than one word, -those words are separated by _ (underscores). -Functions' names should roughly describe what the function does. -Names of functions' local variables are usually short, -and not so self-descriptive, but, on the other hand, -comments are usually provide on the first occurrence of the name, -in order to explain the purpose of such a variable.

-

Functions

-
    -
  • Methods that start with _ should not be called, -because they might either be "helper" or private functions.
  • -
-

Parameters

-
    -
  • -

    u, v, z and w are used to indicate that a general BSTNode object is expected.

    -
  • -
  • -

    s is used to indicate that a source node is expected.

    -
  • -
  • -

    x is used when the parameter's expected type can either be a BSTNode object -or any other comparable object to represent keys.

    -
  • -
  • -

    ls is usually used to indicate that a list or a tuple is expected.

    -
  • -
-

Local Variables

-
    -
  • -

    c usually indicates some "current" changing variable.

    -
  • -
  • -

    p is usually c's parent.

    -
  • -
-

Docstrings

-

Under methods' signatures, h in O(h) is the height of the tree. -Note that the height of a BST varies depending on how elements -are inserted and removed. -m in O(m) is the height of the subtree rooted at the node passed -as parameter.

-

Other names are self-descriptive. -For example, "key" and "value" are self-descriptive.

-

TODO

-
    -
  • Improve the "randomness" of insertion into the BSTImproved class.
  • -
  • Add functions "intersection" and "union".
  • -
  • Implement a recursive version of insert (OPTIONAL).
  • -
  • implement "is balanced" function (http://codereview.stackexchange.com/questions/108459/binary-tree-data-structure)
  • -
  • Maybe the methods of the BSTNode need an improvement in terms of implementation...
  • -
-

Resources

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-# Meta info
-
-Author: Nelson Brochado
-
-Created: 01/07/2015
-
-Updated: 28/08/2016
-
-# Description
-
-### Coding Conventions
-
-In general, if a variable name has more than one word,
-those words are separated by _ (underscores).
-Functions' names should roughly describe what the function does.
-Names of functions' local variables are usually short,
-and not so self-descriptive, but, on the other hand,
-comments are usually provide on the first occurrence of the name,
-in order to explain the purpose of such a variable.
-
-#### Functions
-
-- Methods that start with _ should not be called,
-because they might either be "helper" or private functions.
-
-#### Parameters
-
-- `u`, `v`, `z` and `w` are used to indicate that a general `BSTNode` object is expected.
-
-- `s` is used to indicate that a source node is expected.
-
-- `x` is used when the parameter's expected type can either be a `BSTNode` object
-or any other comparable object to represent keys.
-
-- `ls` is usually used to indicate that a list or a tuple is expected.
-
-#### Local Variables
-
-- `c` usually indicates some "current" changing variable.
-
-- `p` is usually `c`'s parent.
-
-#### Docstrings
-
-Under methods' signatures, h in O(h) is the height of the tree.
-Note that the height of a BST varies depending on how elements
-are inserted and removed.
-m in O(m) is the height of the subtree rooted at the node passed
-as parameter.
-
-Other names are self-descriptive.
-For example, "key" and "value" are self-descriptive.
-
-# TODO
-
-- Improve the "randomness" of insertion into the BSTImproved class.
-- Add functions "intersection" and "union".
-- Implement a recursive version of insert (OPTIONAL).
-- implement "is balanced" function (http://codereview.stackexchange.com/questions/108459/binary-tree-data-structure)
-- Maybe the methods of the BSTNode need an improvement in terms of implementation...
-
-# Resources
-
-- [https://en.wikipedia.org/wiki/Binary_search_tree](https://en.wikipedia.org/wiki/Binary_search_tree)
-- [Introduction to Algorithms (3rd edition)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS, chapter 12
-- [http://algs4.cs.princeton.edu/32bst/](http://algs4.cs.princeton.edu/32bst/)
-- [http://www.cs.princeton.edu/courses/archive/spr04/cos226/lectures/bst.4up.pdf](http://www.cs.princeton.edu/courses/archive/spr04/cos226/lectures/bst.4up.pdf)
-- [http://algs4.cs.princeton.edu/32bst/BST.java.html](http://algs4.cs.princeton.edu/32bst/BST.java.html)
-
-"""
-
-from random import randint
-
-from tabulate import tabulate
-
-__all__ = ["BST", "BSTNode", "is_bst"]
-
-
-class BSTNode:
-    """Class to represent a BST's node."""
-
-    def __init__(self, key, value=None, parent=None, left=None, right=None):
-        if key is None:
-            raise ValueError("key cannot be None")
-        self.key = key
-        self.value = value
-        self.parent = parent
-        self.left = left
-        self.right = right
-        # Used for printing purposes.
-        self.label = "[" + str(self.key) + "]"
-
-    @property
-    def sibling(self):
-        """Returns the sibling node of this node,
-        which can of course be `None`."""
-        if self.parent is not None:
-            if self.is_left_child():
-                return self.parent.right
-            else:
-                return self.parent.left
-
-    @property
-    def grandparent(self):
-        """Returns the parent of the parent of this node."""
-        if self.parent is not None:
-            return self.parent.parent
-
-    @property
-    def uncle(self):
-        """Returns the uncle node of this node.
-        The uncle is the sibling of the parent of this node,
-        if it exists. `None` is returned if it doesn't exist,
-        or the parent or grandparent of this node is `None`."""
-        if self.grandparent is not None:  # implies that also parent is not None
-            if self.parent == self.grandparent.left:
-                return self.grandparent.right
-            else:  # self.parent == self.grandparent.right:
-                return self.grandparent.left
-
-    def reset(self):
-        self.parent = None
-        self.left = None
-        self.right = None
-
-    def is_left_child(self) -> bool:
-        if self.parent is not None:
-            if self.parent.left is not None:
-                return self.parent.left == self
-        else:
-            raise AttributeError("self does not have a parent.")
-
-    def is_right_child(self) -> bool:
-        if self.parent is not None:
-            if self.parent.right is not None:
-                return self.parent.right == self
-        else:
-            raise AttributeError("self does not have a parent.")
-
-    def has_children(self) -> bool:
-        """Returns `True` if `self` has at least one child. `False` otherwise."""
-        return self.left or self.right
-
-    def has_one_child(self) -> bool:
-        """Returns `True` only if `self` has exactly one child. `False` otherwise."""
-        return (self.left and not self.right) or (not self.left and self.right)
-
-    def has_two_children(self) -> bool:
-        """Returns `True` if self has exactly two children. `False` otherwise."""
-        return self.left and self.right
-
-    def count(self) -> int:
-        """Counts the numbers of nodes under `self` (including `self`)."""
-
-        def _count(u, c: int):
-            if u is None:
-                return c
-            else:
-                c += 1
-            c = _count(u.left, c)
-            c = _count(u.right, c)
-            return c
-
-        if not self.has_children():
-            return 1
-        else:
-            c = 0
-            return _count(self, c)
-
-    def __str__(self):
-        return "{" + str(self.key) + ": " + str(self.value) + "}"
-
-    def __fields(self):
-        return [["Node (Key)", self.key],
-                ["Value", self.value],
-                ["Parent", self.parent],
-                ["Left child", self.left],
-                ["Right child", self.right],
-                ["Sibling", self.sibling],
-                ["Grandparent", self.grandparent],
-                ["Uncle", self.uncle]]
-
-    def __repr__(self):
-        return tabulate(self.__fields(), tablefmt="fancy_grid")
-
-    def show(self):
-        print(self.__repr__())
-
-
-class BST:
-    """`BST` is a class that represents a classical binary search tree."""
-
-    def __init__(self, root=None, name="BST"):
-        self.root = root
-        self.name = name
-        self.n = 0  # number of nodes
-        if root is not None:
-            self._initialise(root)
-
-    # INITIALISE
-
-    def _initialise(self, u: BSTNode):
-        """Sets `u` as the new root and unique node of this tree."""
-        self.root = u
-        self.root.parent = None
-        self.root.left = None
-        self.root.right = None
-        self.n = 1
-
-    def size(self):
-        """Returns the total number of nodes.
-
-        **Time Complexity**: O(1)."""
-        return self.n
-
-    def is_empty(self):
-        """Returns `True` if this tree has 0 nodes.
-
-        **Time Complexity**: O(1)."""
-        return self.size() == 0
-
-    def is_root(self, u: BSTNode):
-        """Checks if `u` is the root.
-
-        **Time Complexity**: O(1)."""
-        if u == self.root:
-            assert u.parent is None
-        return u == self.root
-
-    def clear(self):
-        """Removes all nodes from this tree.
-
-        **Time Complexity**: O(1)."""
-        self.root = None
-        self.n = 0
-
-    # INSERTIONS
-
-    def insert(self, x, value=None):
-        """Inserts (normally) `x` into this BST object.
-
-        **Time Complexity**: O(h)."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, BSTNode):
-            x = BSTNode(x, value)
-        if x.left or x.right or x.parent:
-            raise ValueError(
-                "x cannot have left or right children, or parent.")
-
-        if self.root is None:
-            self._initialise(x)
-        else:
-            c = self.root  # c is the current node
-            p = self.root.parent  # parent of c
-
-            while c is not None:
-                p = c
-                if x.key < c.key:
-                    c = c.left
-                else:
-                    c = c.right
-
-            if x.key < p.key:
-                p.left = x
-            else:
-                p.right = x
-
-            x.parent = p
-            self.n += 1
-
-    def insert_many(self, ls):
-        """Calls `self.insert` for all elements of `ls`.
-        Therefore the elements of `ls` should either be
-        `BSTNode` objects or they should represent keys.
-
-        **Time Complexity**: O(len(ls)*h)."""
-        for i in ls:
-            self.insert(i)
-
-    # SEARCH
-
-    def search(self, key, s: BSTNode = None) -> BSTNode:
-        """Searches for the key in the tree.
-        If `s` is specified, then this procedure starts searching from `s`.
-
-        `key` must be a comparable object of the same type as the other keys.
-
-        **Time Complexity**: O(h)."""
-        if key is None:
-            raise ValueError("key cannot be None.")
-        if s is None:
-            return self.search_i(key)
-        else:
-            return BST._search_i(key, s)
-
-    def search_r(self, key) -> BSTNode:
-        """Searches recursively for `key` starting from `self.root`.
-
-        **Time Complexity**: O(h)."""
-        return self._search_r(key, self.root)
-
-    def _search_r(self, key, s: BSTNode) -> BSTNode:
-        """Searches recursively for `key` in the subtree rooted at `s`.
-
-        `key` must be a comparable object of the same type as the other keys.
-
-        **Time Complexity**: O(m),
-        where `m` is the height of the subtree rooted at `s`,
-        if `s` is not `None`. Else the time complexity is O(1)."""
-        if s is None or key == s.key:
-            return s
-        elif key < s.key:
-            return self._search_r(key, s.left)
-        else:
-            return self._search_r(key, s.right)
-
-    def search_i(self, key) -> BSTNode:
-        """Searches iteratively for key starting from the root.
-
-        **Time Complexity**: O(h)."""
-        return BST._search_i(key, self.root)
-
-    @staticmethod
-    def _search_i(key, s: BSTNode):
-        """Searches iteratively for key in the subtree rooted at `s`.
-
-        **Time Complexity**: O(m)."""
-        c = s  # c is the current node
-        while c:
-            if key == c.key:
-                return c
-            elif key < c.key:
-                c = c.left
-            else:
-                c = c.right
-
-    # CONTAINS
-
-    def contains(self, key) -> bool:
-        """Returns `True` if a `BSTNode` object with `key` exists in the tree.
-
-        **Time Complexity**: O(h)."""
-        return self.search_r(key) is not None
-
-    # SELECT
-
-    def rank(self, key) -> int:
-        """Returns the number of keys strictly less than `key`.
-
-        **Time Complexity**: O(h)."""
-        if key is None:
-            raise ValueError("key cannot be None.")
-        if not self.search(key):
-            raise LookupError("key was not found.")
-        if self.root is None:
-            return 0
-        else:
-            r = 0
-            return self._rank(self.root, key, r)
-
-    def _rank(self, u: BSTNode, key, r: int) -> int:
-        if u is None:
-            return r
-        if u.key < key:
-            r += 1
-        r = self._rank(u.left, key, r)
-        r = self._rank(u.right, key, r)
-        return r
-
-    def height(self) -> int:
-        """Returns the maximum depth or height of the tree.
-
-        **Time Complexity**: O(h)."""
-        if self.root is None:
-            return 0
-        return self._height(self.root)
-
-    def _height(self, u: BSTNode) -> int:
-        if u is None:
-            return 0
-        return 1 + max(self._height(u.left), self._height(u.right))
-
-    # TRAVERSALS
-
-    def in_order_traversal(self):
-        """Prints the elements of the tree in increasing order.
-
-        **Time Complexity**: O(h)."""
-        self._in_order_traversal(self.root)
-        print("\n")
-
-    def _in_order_traversal(self, u: BSTNode, e=", "):
-        if u:
-            self._in_order_traversal(u.left)
-            print(u, end=e)
-            self._in_order_traversal(u.right)
-
-    def pre_order_traversal(self):
-        """Prints the keys of this tree in pre-order.
-        The pre-order consists of recursively printing first a node `u`,
-        then its left child node and then its right child node.
-
-        **Time Complexity**: O(h)."""
-        self._pre_order_traversal(self.root)
-        print("\n")
-
-    def _pre_order_traversal(self, u: BSTNode, e=", "):
-        if u:
-            print(u, end=e)
-            self._pre_order_traversal(u.left)
-            self._pre_order_traversal(u.right)
-
-    def post_order_traversal(self):
-        """Prints the keys of this tree in post-order.
-        It does the opposite of `pre_order_traversal`.
-
-        **Time Complexity**: O(h)."""
-        self._post_order_traversal(self.root)
-        print("\n")
-
-    def _post_order_traversal(self, u: BSTNode, e=", "):
-        if u:
-            self._post_order_traversal(u.left)
-            self._post_order_traversal(u.right)
-            print(u, end=e)
-
-    def reverse_in_order_traversal(self):
-        """Prints the keys of this tree in decreasing order.
-
-        It does the opposite of `self.in_order_traversal`.
-
-        **Time Complexity**: O(h)."""
-        self._reverse_in_order_traversal(self.root)
-        print("\n")
-
-    def _reverse_in_order_traversal(self, u: BSTNode, e=", "):
-        if u:
-            self._reverse_in_order_traversal(u.right)
-            print(u, end=e)
-            self._reverse_in_order_traversal(u.left)
-
-    # ROTATIONS
-
-    def left_rotate(self, x):
-        """Left rotates the subtree rooted at node `x`.
-
-        `x` can be a `BSTNode` object, and in that case,
-        this function performs in constant time O(1);
-        else, if node is not a `BSTNode` object,
-        it tries to search for a `BSTNode` object with key=x,
-        and, in that case, it performs in O(h) time.
-
-        Returns the node which is at the previous position of `x`,
-        that is it returns the parent of `x`.
-
-        **Time Complexity**: O(1)."""
-        c = None  # It will rotate the subtree rooted at c.
-
-        if not isinstance(x, BSTNode):
-            c = self.search(x)
-            if c is None:
-                raise LookupError("key node was not found in the tree.")
-        else:
-            c = x
-
-        # To left rotate a node, its right child must exist.
-        if c.right is None:
-            raise ValueError("Left rotation cannot be performed on " + str(c) +
-                             " because it does not have a right child.")
-
-        c.right.parent = c.parent
-
-        # Only the root has a None parent.
-        if c.parent is None:
-            self.root = c.right
-
-        # Checking if c is a left or a right child,
-        # in order to set the new left
-        # or right child respectively of its parent.
-        elif c.is_left_child():
-            c.parent.left = c.right
-        else:
-            c.parent.right = c.right
-
-        c.parent = c.right
-
-        # The new right child of c becomes what is
-        # the left child of its previous right child.
-        c.right = c.parent.left
-
-        # Set c to be the parent of its new right child.
-        if c.right is not None:
-            c.right.parent = c
-
-        # Set c to be the new left child of its new parent.
-        c.parent.left = c
-
-        return c.parent
-
-    def right_rotate(self, x):
-        """Right rotates the subtree rooted at node `x`.
-        See doc-strings of `self.left_rotate`.
-
-        **Time Complexity**: O(1)."""
-        c = None
-
-        if not isinstance(x, BSTNode):
-            c = self.search(x)
-            if c is None:
-                raise LookupError("key node was not found in the tree.")
-        else:
-            c = x
-
-        if c.left is None:
-            raise ValueError("Right rotation cannot be performed on " + str(c) +
-                             " because it does not have a left child.")
-
-        c.left.parent = c.parent
-
-        if c.parent is None:
-            self.root = c.left
-        elif c.is_left_child():
-            c.parent.left = c.left
-        else:
-            c.parent.right = c.left
-
-        c.parent = c.left
-        c.left = c.parent.right
-
-        if c.left is not None:
-            c.left.parent = c
-
-        c.parent.right = c
-        return c.parent
-
-    # MINIMUM AND MAXIMUM
-
-    def minimum(self):
-        """Calls `BST._minimum_r(self.root)` if `self.root` is evaluated to `True`.
-
-        **Time Complexity**: O(h)."""
-        if self.root:
-            return BST._minimum_r(self.root)
-
-    @staticmethod
-    def _minimum_r(u: BSTNode):
-        """Recursive version of the `BST._minimum(u)` function."""
-        if u.left:
-            u = BST._minimum_r(u.left)
-        return u
-
-    @staticmethod
-    def _minimum(u: BSTNode):
-        """Returns the node (rooted at u) with the minimum key."""
-        while u.left:
-            u = u.left
-        return u
-
-    def maximum(self):
-        """Calls `BST._maximum_r(self.root)` if `self.root` is evaluated to `True`.
-
-        **Time Complexity**: O(h)."""
-        if self.root:
-            return BST._maximum_r(self.root)
-
-    @staticmethod
-    def _maximum_r(u: BSTNode):
-        """Recursive version of `BST._maximum`."""
-        if u.right:
-            u = BST._maximum_r(u.right)
-        return u
-
-    @staticmethod
-    def _maximum(u: BSTNode):
-        """Returns the node (rooted at u) with the maximum key."""
-        while u.right:
-            u = u.right
-        return u
-
-    # SUCCESSOR AND PREDECESSOR
-
-    def successor(self, x):
-        """Finds the successor of `x`,
-        i.e. the smallest element greater than `x`.
-
-        If `x` has a right subtree,
-        then the successor of `x` is the minimum of that right subtree.
-
-        Otherwise it is the first ancestor of `x`, lets call it `A`,
-        such that `x` falls in the left subtree of `A`.
-
-        `x` can either be a reference to an actual `BSTNode` object,
-        or it can be a key of a supposed node in self.
-
-        **Time Complexity**: O(h)."""
-        if not isinstance(x, BSTNode):
-            x = self.search(x)
-            if x is None:
-                raise LookupError("No node was found with key=x.")
-
-        if x.right:
-            return BST._minimum_r(x.right)
-
-        p = x.parent
-
-        while p and p.right == x:
-            x = p
-            p = x.parent
-        return p
-
-    def predecessor(self, x):
-        """Finds the predecessor of the node `x`,
-        i.e. the greatest element smaller than `x`.
-
-        `x` can either be a reference to an actual `BSTNode` object,
-        or it can be a key of a supposed node in self.
-
-        **Time Complexity**: O(h)."""
-        if not isinstance(x, BSTNode):
-            x = self.search_r(x)
-            if x is None:
-                raise LookupError("No node was found with key=x.")
-        if x.left:
-            return BST._maximum_r(x.left)
-
-        p = x.parent
-
-        while p and x == p.left:
-            x = p
-            p = x.parent
-        return p
-
-    # REMOVALS
-
-    def remove_max(self):
-        """Removes and returns the maximum element of the tree, if it is not empty.
-
-        **Time Complexity**: O(h)."""
-
-        def _remove_max(u: BSTNode):
-            """Removes the maximum element of the subtree rooted at `u`.
-
-            Note that the maximum element is all the way to the right,
-            and it cannot have a right child,
-            but it can still have a left subtree.
-
-            If `u` is `None`, exceptions will be thrown."""
-            m = BST._maximum_r(u)
-
-            if m.left:  # m has a left subtree.
-                if self.is_root(m):  # m is the root.
-                    self.root = m.left
-                    m.left.parent = None  # self.root.parent = None
-                else:  # m is NOT the root.
-                    m.left.parent = m.parent
-                    m.parent.right = m.left
-            else:  # m has NO children
-                if self.is_root(m):
-                    self.root = None
-                else:
-                    m.parent.right = None
-
-            m.parent = m.left = None
-            self.n -= 1
-            return m
-
-        if self.n > 0:
-            return _remove_max(self.root)
-
-    def remove_min(self):
-        """Removes and returns the minimum element of the tree, if it is not empty.
-
-        **Time Complexity**: O(h)."""
-
-        def _remove_min(u: BSTNode):
-            """Removes and returns the minimum element of the subtree rooted at `u`.
-            If `u` is `None`, exceptions will be thrown."""
-            m = BST._minimum_r(u)
-
-            if m.right:
-                if self.is_root(m):
-                    self.root = m.right
-                    m.right.parent = None
-                else:
-                    m.right.parent = m.parent
-                    m.parent.left = m.right
-            else:  # m has not right subtree.
-                if self.is_root(m):
-                    self.root = None
-                else:  # m is an internal node with no right subtree.
-                    m.parent.left = None
-
-            m.right = m.parent = None
-            self.n -= 1
-            return m
-
-        if self.n > 0:
-            return _remove_min(self.root)
-
-    # DELETION
-
-    def delete(self, x):
-        """Deletes `x` from self (if it exists).
-
-        There are 3 cases of deletion:
-            1. `x` has no children
-            2. `x` has one subtree (or child)
-            3. `x` has the left and right subtrees (or children).
-
-        `x` can either be a reference to an actual `BSTNode` object,
-        or it can be a key of a supposed node in `self`.
-
-        **Time Complexity**: O(h)."""
-
-        def delete_helper(u: BSTNode):
-            """This is a helper method to the delete method,
-            thus it should not be called by clients.
-
-            When deleting a node u from a BST, we have basically to consider 3 cases:
-            1. u has no children
-            2. u has one child
-            3. u has two children
-
-            1. u has no children, then we simply remove it
-            by modifying its parent to replace u with None.
-            If u.parent is None, then u must be the root,
-            and thus we simply set the root to None.
-
-            2. u has just one child,
-            but we first need to decide which one (left or right).
-            Then we elevate this child to u's position in the tree
-            by modifying u's parent to replace u by u's child.
-            But if u's parent is None, that means u was the root,
-            and the new root becomes u's child.
-
-            3. u has two children, then we search for u's successor s,
-            (which must be in the u's right subtree,
-            and it's the smallest of that subtree)
-            which takes u's position in the tree.
-            The rest of the u's subtree becomes the s's right subtree,
-            and the u's left subtree becomes the new s's left subtree.
-            This case is a little bit tricky,
-            because it matters whether s is u's right child.
-
-            Suppose s is the right child of u, then we replace u by s,
-            which might or not have a right subtree, but no left subtree.
-
-            Suppose s is not the right child of u,
-            in this case, we replace s by its own right child,
-            and then we replace u by s.
-
-            Note that "delete_two_children" does NOT exactly do that,
-            but instead it simply replaces the positions of u and s,
-            as if s was u and u was s.
-
-            After that, delete_helper is called again on u,
-            but note that u is now in the previous s's position,
-            and thus u has now no left subtree, but at most a right subtree."""
-
-            def delete_at_most_one_child(v: BSTNode):
-                """Removes v from the tree, when v has at most one child.
-                This means that v could have 0 or 1 child."""
-                child = v.right
-                if v.left:
-                    child = v.left
-                if v.parent is None:  # v is the root.
-                    self.root = child
-                else:  # v has a parent, so it is not the root.
-                    if v.is_left_child():
-                        v.parent.left = child
-                    else:
-                        v.parent.right = child
-                # child is None iff v.right and v.left are None.
-                if child:
-                    child.parent = v.parent
-
-            def delete_two_children(v: BSTNode):
-                """Called by `delete_helper` when a node has two children."""
-                # Replace v with its successor s.
-                self._switch(v, self.successor(v))
-                # v has at most a right child now.
-                delete_helper(v)
-
-            if u.has_two_children():
-                delete_two_children(u)
-            else:  # u has at most one child
-                delete_at_most_one_child(u)
-            u.right = u.left = u.parent = None
-            return u
-
-        if x is None:
-            raise ValueError("x cannot be None.")
-
-        if not isinstance(x, BSTNode):
-            x = self.search_r(x)
-            if x is None:
-                raise LookupError("No node was found with key=x.")
-
-        if x.parent is None and not self.is_root(x):
-            raise ValueError("x is not a valid node.")
-
-        self.n -= 1
-        return delete_helper(x)
-
-    def _switch(self, u: BSTNode, v: BSTNode, search_first=False):
-        """"Switches the roles of `u` and `v` in the tree by moving references.
-        If `search_first` is set to `True`,
-        then `u` and `v` are first searched in the tree
-        to check if they are present, and if not, a `LookupError` is raised.
-        In general, clients should not use this function."""
-        if not u:
-            raise ValueError("u cannot be None.")
-        if not v:
-            raise ValueError("v cannot be None.")
-        if u == v:
-            raise ValueError("u cannot be equal to v.")
-
-        if search_first:
-            if not self.search(u.key) or not self.search(v.key):
-                raise LookupError("u or v not found.")
-
-        def switch_parent_son(p, s):
-            """Switches the roles of p and s,
-            where p (parent) is the direct parent of s (son)."""
-            assert s.parent == p
-
-            if s.is_left_child():
-                p.left = s.left
-                if s.left:
-                    s.left.parent = p
-
-                s.left = p
-
-                s.right, p.right = p.right, s.right
-                if s.right:
-                    s.right.parent = s
-                if p.right:
-                    p.right.parent = p
-            else:
-                p.right = s.right
-                if s.right:
-                    s.right.parent = p
-
-                s.right = p
-
-                s.left, p.left = p.left, s.left
-                if s.left:
-                    s.left.parent = s
-                if p.left:
-                    p.left.parent = p
-
-            if p.parent:
-                if p.is_left_child():
-                    p.parent.left = s
-                else:
-                    p.parent.right = s
-            else:  # p is the root
-                self.root = s
-
-            s.parent = p.parent
-            p.parent = s
-
-        def switch_non_parent_son(z, w):
-            """`z` and `w` are nodes in the tree
-            that are not related by a parent-child.
-
-            **Time Complexity**: O(1)."""
-            assert z.parent != w and w.parent != z
-
-            if not z.parent:
-                self.root = w
-                if w.is_left_child():
-                    w.parent.left = z
-                else:
-                    w.parent.right = z
-            elif not w.parent:
-                self.root = z
-                if z.is_left_child():
-                    z.parent.left = w
-                else:
-                    z.parent.right = w
-            else:  # neither z nor w is the root
-                if z.is_left_child():
-                    if w.is_left_child():
-                        w.parent.left, z.parent.left = z, w
-                    else:
-                        w.parent.right, z.parent.left = z, w
-                else:
-                    if w.is_left_child():
-                        w.parent.left, z.parent.right = z, w
-                    else:
-                        w.parent.right, z.parent.right = z, w
-
-            w.parent, z.parent = z.parent, w.parent
-            z.left, w.left = w.left, z.left
-            z.right, w.right = w.right, z.right
-
-            if z.left:
-                z.left.parent = z
-            if z.right:
-                z.right.parent = z
-            if w.left:
-                w.left.parent = w
-            if w.right:
-                w.right.parent = w
-
-        if u.parent == v:
-            switch_parent_son(v, u)
-        elif v.parent == u:
-            switch_parent_son(u, v)
-        else:
-            switch_non_parent_son(u, v)
-
-    def show(self):
-        """Pretty-prints this tree using `print`."""
-        print(self)
-
-    def __str__(self):
-        if self.root is None:
-            return 'Nothing to print: this BST is empty.'
-        return '\n'.join(BSTPrinter.print_1(self.root)[0]) + "\n"
-
-    def __repr__(self):
-        return self.__str__()
-
-
-class BSTPrinter:
-    """Based on: http://ocw.mit.edu/courses/electrical-engineering-and-computer-science/6-006-introduction-to-algorithms-fall-2011/readings/binary-search-trees/bst.py"""
-
-    @staticmethod
-    def print_1(node):
-        """Pretty-prints this BST object."""
-        if node is None:
-            return [], 0, 0
-
-        fill = "_"
-
-        left_lines, left_pos, left_width = BSTPrinter.print_1(node.left)
-        right_lines, right_pos, right_width = BSTPrinter.print_1(node.right)
-        middle = max(right_pos + left_width - left_pos + 1, len(node.label), 2)
-        pos = left_pos + middle // 2
-        width = left_pos + middle + right_width - right_pos
-
-        while len(left_lines) < len(right_lines):
-            left_lines.append(' ' * left_width)
-
-        while len(right_lines) < len(left_lines):
-            right_lines.append(' ' * right_width)
-
-        if (middle - len(node.label)) % 2 == 1 and node.parent is not None and \
-                        node is node.parent.left and len(node.label) < middle:
-            node.label += fill
-
-        node.label = node.label.center(middle, fill)
-
-        if node.label[0] == fill:
-            node.label = ' ' + node.label[1:]
-
-        if node.label[-1] == fill:
-            node.label = node.label[:-1] + ' '
-
-        lines = [' ' * left_pos + node.label + ' ' * (right_width - right_pos),
-                 ' ' * left_pos + '/' + ' ' * (middle - 2) +
-                 '\\' + ' ' * (right_width - right_pos)] + \
-                [left_line + ' ' * (width - left_width - right_width) +
-
-                 right_line
-
-                 for left_line, right_line in zip(left_lines, right_lines)]
-        return lines, pos, width
-
-
-def is_bst(bst):
-    """Returns `True` if `bst` is a valid `BST` object. `False` otherwise.
-
-    Invariant: for each node `n` in `bst`,
-    if `n.left` exists, then `n.left <= n`,
-    and if `n.right` exists, then `n.right >= n`."""
-
-    def all_bst_nodes(t):
-        def h(n):
-            if n is not None:
-                if not isinstance(n, BSTNode):
-                    return False
-                return h(n.left) and h(n.right)
-            return True
-
-        return h(t.root)
-
-    def h(n):
-        if n is not None:
-            if n.left and n.key < n.left.key:
-                return False
-            if n.right and n.key > n.right.key:
-                return False
-
-            # Asserting n.left and n.right have n as parent
-            if n.left:
-                assert n.left.parent == n
-            if n.right:
-                assert n.right.parent == n
-
-            return h(n.left) and h(n.right)
-        return True
-
-    if not isinstance(bst, BST):
-        return False
-
-    return all_bst_nodes(bst) and h(bst.root)
-
-
-class BSTImproved(BST):
-    """Binary-search tree that provides somehow randomness at insertion."""
-
-    def __init__(self, root=None, name="BSTImproved"):
-        BST.__init__(self, root, name)
-
-    def insert(self, x, value=None):
-        """Inserts `x` into this tree.
-
-        `x` can either be a `BSTNode` object,
-        or it can be a _key_ of any other type,
-        but it should be comparable with the other keys,
-        and these keys should be comparable objects.
-
-        Note that the height of a `BST` varies
-        depending on how elements are inserted and removed.
-        For example, if we insert a list of numbers in increasing order,
-        the resulting `BST` object will look like a chain with height **n - 1**,
-        where `n` is the number of elements inserted.
-        In general, the optimal height is logarithmic on the number of nodes,
-        and to get closer to the optimal height,
-        randomly insertion of numbers is usually used.
-
-        If we have `n` keys to insert, there are `n!` (n-factorial)
-        ways of inserting those `n` keys into the binary search tree.
-        When we randomly insert them, those permutations are equally likely.
-
-        So, the expected height of a tree created with randomly insertions is O(log2(n)).
-        For a proof, see chapter 12 of Introduction to Algorithms (3rd ed.) by CLRS.
-
-        This function does a pseudo-random insertion of keys."""
-        r = randint(0, self.size() * 3 // 8)  # * 3 // 8 is just a random operation...
-        if r == 0:
-            self.root_insert(x, value)
-        else:
-            self.tail_insert(x, value)
-
-    def tail_insert(self, x, value=None):
-        """Inserts (normally) `x` into this BST object.
-
-        **Time Complexity**: O(h)."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-
-        if not isinstance(x, BSTNode):
-            x = BSTNode(x, value)
-
-        if x.left or x.right or x.parent:
-            raise ValueError("x cannot have left or right children, or parent.")
-
-        if self.root is None:
-            self._initialise(x)
-        else:
-            c = self.root  # c is the current node
-            p = self.root.parent  # parent of c
-
-            while c is not None:
-                p = c
-                if x.key < c.key:
-                    c = c.left
-                else:
-                    c = c.right
-            if x.key < p.key:
-                p.left = x
-            else:
-                p.right = x
-
-            x.parent = p
-            self.n += 1
-
-    def root_insert(self, x, value=None):
-        """Inserts `x` as the root of this tree.
-
-        **Time Complexity**: O(h)."""
-
-        def _root_insert(u: BSTNode, v: BSTNode):
-            """Helper method for `self.root_insert`."""
-            if u is None:
-                return v
-            if v.key < u.key:
-                u.left = _root_insert(u.left, v)
-                u = self.right_rotate(u)
-            else:
-                u.right = _root_insert(u.right, v)
-                u = self.left_rotate(u)
-            return u
-
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, BSTNode):
-            x = BSTNode(x, value)
-        if x.left or x.right or x.parent:
-            raise ValueError("x cannot have left or right children, or parent.")
-        if self.root is None:
-            self._initialise(x)
-        else:
-            _root_insert(self.root, x)
-            self.n += 1
-
-
- -
- -
- -

Functions

- -
-
-

def is_bst(

bst)

-
- - - - -

Returns True if bst is a valid BST object. False otherwise.

-

Invariant: for each node n in bst, -if n.left exists, then n.left <= n, -and if n.right exists, then n.right >= n.

-
- -
-
def is_bst(bst):
-    """Returns `True` if `bst` is a valid `BST` object. `False` otherwise.
-
-    Invariant: for each node `n` in `bst`,
-    if `n.left` exists, then `n.left <= n`,
-    and if `n.right` exists, then `n.right >= n`."""
-
-    def all_bst_nodes(t):
-        def h(n):
-            if n is not None:
-                if not isinstance(n, BSTNode):
-                    return False
-                return h(n.left) and h(n.right)
-            return True
-
-        return h(t.root)
-
-    def h(n):
-        if n is not None:
-            if n.left and n.key < n.left.key:
-                return False
-            if n.right and n.key > n.right.key:
-                return False
-
-            # Asserting n.left and n.right have n as parent
-            if n.left:
-                assert n.left.parent == n
-            if n.right:
-                assert n.right.parent == n
-
-            return h(n.left) and h(n.right)
-        return True
-
-    if not isinstance(bst, BST):
-        return False
-
-    return all_bst_nodes(bst) and h(bst.root)
-
-
-
- -
- - -

Classes

- -
-

class BST

- - -

BST is a class that represents a classical binary search tree.

-
- -
-
class BST:
-    """`BST` is a class that represents a classical binary search tree."""
-
-    def __init__(self, root=None, name="BST"):
-        self.root = root
-        self.name = name
-        self.n = 0  # number of nodes
-        if root is not None:
-            self._initialise(root)
-
-    # INITIALISE
-
-    def _initialise(self, u: BSTNode):
-        """Sets `u` as the new root and unique node of this tree."""
-        self.root = u
-        self.root.parent = None
-        self.root.left = None
-        self.root.right = None
-        self.n = 1
-
-    def size(self):
-        """Returns the total number of nodes.
-
-        **Time Complexity**: O(1)."""
-        return self.n
-
-    def is_empty(self):
-        """Returns `True` if this tree has 0 nodes.
-
-        **Time Complexity**: O(1)."""
-        return self.size() == 0
-
-    def is_root(self, u: BSTNode):
-        """Checks if `u` is the root.
-
-        **Time Complexity**: O(1)."""
-        if u == self.root:
-            assert u.parent is None
-        return u == self.root
-
-    def clear(self):
-        """Removes all nodes from this tree.
-
-        **Time Complexity**: O(1)."""
-        self.root = None
-        self.n = 0
-
-    # INSERTIONS
-
-    def insert(self, x, value=None):
-        """Inserts (normally) `x` into this BST object.
-
-        **Time Complexity**: O(h)."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, BSTNode):
-            x = BSTNode(x, value)
-        if x.left or x.right or x.parent:
-            raise ValueError(
-                "x cannot have left or right children, or parent.")
-
-        if self.root is None:
-            self._initialise(x)
-        else:
-            c = self.root  # c is the current node
-            p = self.root.parent  # parent of c
-
-            while c is not None:
-                p = c
-                if x.key < c.key:
-                    c = c.left
-                else:
-                    c = c.right
-
-            if x.key < p.key:
-                p.left = x
-            else:
-                p.right = x
-
-            x.parent = p
-            self.n += 1
-
-    def insert_many(self, ls):
-        """Calls `self.insert` for all elements of `ls`.
-        Therefore the elements of `ls` should either be
-        `BSTNode` objects or they should represent keys.
-
-        **Time Complexity**: O(len(ls)*h)."""
-        for i in ls:
-            self.insert(i)
-
-    # SEARCH
-
-    def search(self, key, s: BSTNode = None) -> BSTNode:
-        """Searches for the key in the tree.
-        If `s` is specified, then this procedure starts searching from `s`.
-
-        `key` must be a comparable object of the same type as the other keys.
-
-        **Time Complexity**: O(h)."""
-        if key is None:
-            raise ValueError("key cannot be None.")
-        if s is None:
-            return self.search_i(key)
-        else:
-            return BST._search_i(key, s)
-
-    def search_r(self, key) -> BSTNode:
-        """Searches recursively for `key` starting from `self.root`.
-
-        **Time Complexity**: O(h)."""
-        return self._search_r(key, self.root)
-
-    def _search_r(self, key, s: BSTNode) -> BSTNode:
-        """Searches recursively for `key` in the subtree rooted at `s`.
-
-        `key` must be a comparable object of the same type as the other keys.
-
-        **Time Complexity**: O(m),
-        where `m` is the height of the subtree rooted at `s`,
-        if `s` is not `None`. Else the time complexity is O(1)."""
-        if s is None or key == s.key:
-            return s
-        elif key < s.key:
-            return self._search_r(key, s.left)
-        else:
-            return self._search_r(key, s.right)
-
-    def search_i(self, key) -> BSTNode:
-        """Searches iteratively for key starting from the root.
-
-        **Time Complexity**: O(h)."""
-        return BST._search_i(key, self.root)
-
-    @staticmethod
-    def _search_i(key, s: BSTNode):
-        """Searches iteratively for key in the subtree rooted at `s`.
-
-        **Time Complexity**: O(m)."""
-        c = s  # c is the current node
-        while c:
-            if key == c.key:
-                return c
-            elif key < c.key:
-                c = c.left
-            else:
-                c = c.right
-
-    # CONTAINS
-
-    def contains(self, key) -> bool:
-        """Returns `True` if a `BSTNode` object with `key` exists in the tree.
-
-        **Time Complexity**: O(h)."""
-        return self.search_r(key) is not None
-
-    # SELECT
-
-    def rank(self, key) -> int:
-        """Returns the number of keys strictly less than `key`.
-
-        **Time Complexity**: O(h)."""
-        if key is None:
-            raise ValueError("key cannot be None.")
-        if not self.search(key):
-            raise LookupError("key was not found.")
-        if self.root is None:
-            return 0
-        else:
-            r = 0
-            return self._rank(self.root, key, r)
-
-    def _rank(self, u: BSTNode, key, r: int) -> int:
-        if u is None:
-            return r
-        if u.key < key:
-            r += 1
-        r = self._rank(u.left, key, r)
-        r = self._rank(u.right, key, r)
-        return r
-
-    def height(self) -> int:
-        """Returns the maximum depth or height of the tree.
-
-        **Time Complexity**: O(h)."""
-        if self.root is None:
-            return 0
-        return self._height(self.root)
-
-    def _height(self, u: BSTNode) -> int:
-        if u is None:
-            return 0
-        return 1 + max(self._height(u.left), self._height(u.right))
-
-    # TRAVERSALS
-
-    def in_order_traversal(self):
-        """Prints the elements of the tree in increasing order.
-
-        **Time Complexity**: O(h)."""
-        self._in_order_traversal(self.root)
-        print("\n")
-
-    def _in_order_traversal(self, u: BSTNode, e=", "):
-        if u:
-            self._in_order_traversal(u.left)
-            print(u, end=e)
-            self._in_order_traversal(u.right)
-
-    def pre_order_traversal(self):
-        """Prints the keys of this tree in pre-order.
-        The pre-order consists of recursively printing first a node `u`,
-        then its left child node and then its right child node.
-
-        **Time Complexity**: O(h)."""
-        self._pre_order_traversal(self.root)
-        print("\n")
-
-    def _pre_order_traversal(self, u: BSTNode, e=", "):
-        if u:
-            print(u, end=e)
-            self._pre_order_traversal(u.left)
-            self._pre_order_traversal(u.right)
-
-    def post_order_traversal(self):
-        """Prints the keys of this tree in post-order.
-        It does the opposite of `pre_order_traversal`.
-
-        **Time Complexity**: O(h)."""
-        self._post_order_traversal(self.root)
-        print("\n")
-
-    def _post_order_traversal(self, u: BSTNode, e=", "):
-        if u:
-            self._post_order_traversal(u.left)
-            self._post_order_traversal(u.right)
-            print(u, end=e)
-
-    def reverse_in_order_traversal(self):
-        """Prints the keys of this tree in decreasing order.
-
-        It does the opposite of `self.in_order_traversal`.
-
-        **Time Complexity**: O(h)."""
-        self._reverse_in_order_traversal(self.root)
-        print("\n")
-
-    def _reverse_in_order_traversal(self, u: BSTNode, e=", "):
-        if u:
-            self._reverse_in_order_traversal(u.right)
-            print(u, end=e)
-            self._reverse_in_order_traversal(u.left)
-
-    # ROTATIONS
-
-    def left_rotate(self, x):
-        """Left rotates the subtree rooted at node `x`.
-
-        `x` can be a `BSTNode` object, and in that case,
-        this function performs in constant time O(1);
-        else, if node is not a `BSTNode` object,
-        it tries to search for a `BSTNode` object with key=x,
-        and, in that case, it performs in O(h) time.
-
-        Returns the node which is at the previous position of `x`,
-        that is it returns the parent of `x`.
-
-        **Time Complexity**: O(1)."""
-        c = None  # It will rotate the subtree rooted at c.
-
-        if not isinstance(x, BSTNode):
-            c = self.search(x)
-            if c is None:
-                raise LookupError("key node was not found in the tree.")
-        else:
-            c = x
-
-        # To left rotate a node, its right child must exist.
-        if c.right is None:
-            raise ValueError("Left rotation cannot be performed on " + str(c) +
-                             " because it does not have a right child.")
-
-        c.right.parent = c.parent
-
-        # Only the root has a None parent.
-        if c.parent is None:
-            self.root = c.right
-
-        # Checking if c is a left or a right child,
-        # in order to set the new left
-        # or right child respectively of its parent.
-        elif c.is_left_child():
-            c.parent.left = c.right
-        else:
-            c.parent.right = c.right
-
-        c.parent = c.right
-
-        # The new right child of c becomes what is
-        # the left child of its previous right child.
-        c.right = c.parent.left
-
-        # Set c to be the parent of its new right child.
-        if c.right is not None:
-            c.right.parent = c
-
-        # Set c to be the new left child of its new parent.
-        c.parent.left = c
-
-        return c.parent
-
-    def right_rotate(self, x):
-        """Right rotates the subtree rooted at node `x`.
-        See doc-strings of `self.left_rotate`.
-
-        **Time Complexity**: O(1)."""
-        c = None
-
-        if not isinstance(x, BSTNode):
-            c = self.search(x)
-            if c is None:
-                raise LookupError("key node was not found in the tree.")
-        else:
-            c = x
-
-        if c.left is None:
-            raise ValueError("Right rotation cannot be performed on " + str(c) +
-                             " because it does not have a left child.")
-
-        c.left.parent = c.parent
-
-        if c.parent is None:
-            self.root = c.left
-        elif c.is_left_child():
-            c.parent.left = c.left
-        else:
-            c.parent.right = c.left
-
-        c.parent = c.left
-        c.left = c.parent.right
-
-        if c.left is not None:
-            c.left.parent = c
-
-        c.parent.right = c
-        return c.parent
-
-    # MINIMUM AND MAXIMUM
-
-    def minimum(self):
-        """Calls `BST._minimum_r(self.root)` if `self.root` is evaluated to `True`.
-
-        **Time Complexity**: O(h)."""
-        if self.root:
-            return BST._minimum_r(self.root)
-
-    @staticmethod
-    def _minimum_r(u: BSTNode):
-        """Recursive version of the `BST._minimum(u)` function."""
-        if u.left:
-            u = BST._minimum_r(u.left)
-        return u
-
-    @staticmethod
-    def _minimum(u: BSTNode):
-        """Returns the node (rooted at u) with the minimum key."""
-        while u.left:
-            u = u.left
-        return u
-
-    def maximum(self):
-        """Calls `BST._maximum_r(self.root)` if `self.root` is evaluated to `True`.
-
-        **Time Complexity**: O(h)."""
-        if self.root:
-            return BST._maximum_r(self.root)
-
-    @staticmethod
-    def _maximum_r(u: BSTNode):
-        """Recursive version of `BST._maximum`."""
-        if u.right:
-            u = BST._maximum_r(u.right)
-        return u
-
-    @staticmethod
-    def _maximum(u: BSTNode):
-        """Returns the node (rooted at u) with the maximum key."""
-        while u.right:
-            u = u.right
-        return u
-
-    # SUCCESSOR AND PREDECESSOR
-
-    def successor(self, x):
-        """Finds the successor of `x`,
-        i.e. the smallest element greater than `x`.
-
-        If `x` has a right subtree,
-        then the successor of `x` is the minimum of that right subtree.
-
-        Otherwise it is the first ancestor of `x`, lets call it `A`,
-        such that `x` falls in the left subtree of `A`.
-
-        `x` can either be a reference to an actual `BSTNode` object,
-        or it can be a key of a supposed node in self.
-
-        **Time Complexity**: O(h)."""
-        if not isinstance(x, BSTNode):
-            x = self.search(x)
-            if x is None:
-                raise LookupError("No node was found with key=x.")
-
-        if x.right:
-            return BST._minimum_r(x.right)
-
-        p = x.parent
-
-        while p and p.right == x:
-            x = p
-            p = x.parent
-        return p
-
-    def predecessor(self, x):
-        """Finds the predecessor of the node `x`,
-        i.e. the greatest element smaller than `x`.
-
-        `x` can either be a reference to an actual `BSTNode` object,
-        or it can be a key of a supposed node in self.
-
-        **Time Complexity**: O(h)."""
-        if not isinstance(x, BSTNode):
-            x = self.search_r(x)
-            if x is None:
-                raise LookupError("No node was found with key=x.")
-        if x.left:
-            return BST._maximum_r(x.left)
-
-        p = x.parent
-
-        while p and x == p.left:
-            x = p
-            p = x.parent
-        return p
-
-    # REMOVALS
-
-    def remove_max(self):
-        """Removes and returns the maximum element of the tree, if it is not empty.
-
-        **Time Complexity**: O(h)."""
-
-        def _remove_max(u: BSTNode):
-            """Removes the maximum element of the subtree rooted at `u`.
-
-            Note that the maximum element is all the way to the right,
-            and it cannot have a right child,
-            but it can still have a left subtree.
-
-            If `u` is `None`, exceptions will be thrown."""
-            m = BST._maximum_r(u)
-
-            if m.left:  # m has a left subtree.
-                if self.is_root(m):  # m is the root.
-                    self.root = m.left
-                    m.left.parent = None  # self.root.parent = None
-                else:  # m is NOT the root.
-                    m.left.parent = m.parent
-                    m.parent.right = m.left
-            else:  # m has NO children
-                if self.is_root(m):
-                    self.root = None
-                else:
-                    m.parent.right = None
-
-            m.parent = m.left = None
-            self.n -= 1
-            return m
-
-        if self.n > 0:
-            return _remove_max(self.root)
-
-    def remove_min(self):
-        """Removes and returns the minimum element of the tree, if it is not empty.
-
-        **Time Complexity**: O(h)."""
-
-        def _remove_min(u: BSTNode):
-            """Removes and returns the minimum element of the subtree rooted at `u`.
-            If `u` is `None`, exceptions will be thrown."""
-            m = BST._minimum_r(u)
-
-            if m.right:
-                if self.is_root(m):
-                    self.root = m.right
-                    m.right.parent = None
-                else:
-                    m.right.parent = m.parent
-                    m.parent.left = m.right
-            else:  # m has not right subtree.
-                if self.is_root(m):
-                    self.root = None
-                else:  # m is an internal node with no right subtree.
-                    m.parent.left = None
-
-            m.right = m.parent = None
-            self.n -= 1
-            return m
-
-        if self.n > 0:
-            return _remove_min(self.root)
-
-    # DELETION
-
-    def delete(self, x):
-        """Deletes `x` from self (if it exists).
-
-        There are 3 cases of deletion:
-            1. `x` has no children
-            2. `x` has one subtree (or child)
-            3. `x` has the left and right subtrees (or children).
-
-        `x` can either be a reference to an actual `BSTNode` object,
-        or it can be a key of a supposed node in `self`.
-
-        **Time Complexity**: O(h)."""
-
-        def delete_helper(u: BSTNode):
-            """This is a helper method to the delete method,
-            thus it should not be called by clients.
-
-            When deleting a node u from a BST, we have basically to consider 3 cases:
-            1. u has no children
-            2. u has one child
-            3. u has two children
-
-            1. u has no children, then we simply remove it
-            by modifying its parent to replace u with None.
-            If u.parent is None, then u must be the root,
-            and thus we simply set the root to None.
-
-            2. u has just one child,
-            but we first need to decide which one (left or right).
-            Then we elevate this child to u's position in the tree
-            by modifying u's parent to replace u by u's child.
-            But if u's parent is None, that means u was the root,
-            and the new root becomes u's child.
-
-            3. u has two children, then we search for u's successor s,
-            (which must be in the u's right subtree,
-            and it's the smallest of that subtree)
-            which takes u's position in the tree.
-            The rest of the u's subtree becomes the s's right subtree,
-            and the u's left subtree becomes the new s's left subtree.
-            This case is a little bit tricky,
-            because it matters whether s is u's right child.
-
-            Suppose s is the right child of u, then we replace u by s,
-            which might or not have a right subtree, but no left subtree.
-
-            Suppose s is not the right child of u,
-            in this case, we replace s by its own right child,
-            and then we replace u by s.
-
-            Note that "delete_two_children" does NOT exactly do that,
-            but instead it simply replaces the positions of u and s,
-            as if s was u and u was s.
-
-            After that, delete_helper is called again on u,
-            but note that u is now in the previous s's position,
-            and thus u has now no left subtree, but at most a right subtree."""
-
-            def delete_at_most_one_child(v: BSTNode):
-                """Removes v from the tree, when v has at most one child.
-                This means that v could have 0 or 1 child."""
-                child = v.right
-                if v.left:
-                    child = v.left
-                if v.parent is None:  # v is the root.
-                    self.root = child
-                else:  # v has a parent, so it is not the root.
-                    if v.is_left_child():
-                        v.parent.left = child
-                    else:
-                        v.parent.right = child
-                # child is None iff v.right and v.left are None.
-                if child:
-                    child.parent = v.parent
-
-            def delete_two_children(v: BSTNode):
-                """Called by `delete_helper` when a node has two children."""
-                # Replace v with its successor s.
-                self._switch(v, self.successor(v))
-                # v has at most a right child now.
-                delete_helper(v)
-
-            if u.has_two_children():
-                delete_two_children(u)
-            else:  # u has at most one child
-                delete_at_most_one_child(u)
-            u.right = u.left = u.parent = None
-            return u
-
-        if x is None:
-            raise ValueError("x cannot be None.")
-
-        if not isinstance(x, BSTNode):
-            x = self.search_r(x)
-            if x is None:
-                raise LookupError("No node was found with key=x.")
-
-        if x.parent is None and not self.is_root(x):
-            raise ValueError("x is not a valid node.")
-
-        self.n -= 1
-        return delete_helper(x)
-
-    def _switch(self, u: BSTNode, v: BSTNode, search_first=False):
-        """"Switches the roles of `u` and `v` in the tree by moving references.
-        If `search_first` is set to `True`,
-        then `u` and `v` are first searched in the tree
-        to check if they are present, and if not, a `LookupError` is raised.
-        In general, clients should not use this function."""
-        if not u:
-            raise ValueError("u cannot be None.")
-        if not v:
-            raise ValueError("v cannot be None.")
-        if u == v:
-            raise ValueError("u cannot be equal to v.")
-
-        if search_first:
-            if not self.search(u.key) or not self.search(v.key):
-                raise LookupError("u or v not found.")
-
-        def switch_parent_son(p, s):
-            """Switches the roles of p and s,
-            where p (parent) is the direct parent of s (son)."""
-            assert s.parent == p
-
-            if s.is_left_child():
-                p.left = s.left
-                if s.left:
-                    s.left.parent = p
-
-                s.left = p
-
-                s.right, p.right = p.right, s.right
-                if s.right:
-                    s.right.parent = s
-                if p.right:
-                    p.right.parent = p
-            else:
-                p.right = s.right
-                if s.right:
-                    s.right.parent = p
-
-                s.right = p
-
-                s.left, p.left = p.left, s.left
-                if s.left:
-                    s.left.parent = s
-                if p.left:
-                    p.left.parent = p
-
-            if p.parent:
-                if p.is_left_child():
-                    p.parent.left = s
-                else:
-                    p.parent.right = s
-            else:  # p is the root
-                self.root = s
-
-            s.parent = p.parent
-            p.parent = s
-
-        def switch_non_parent_son(z, w):
-            """`z` and `w` are nodes in the tree
-            that are not related by a parent-child.
-
-            **Time Complexity**: O(1)."""
-            assert z.parent != w and w.parent != z
-
-            if not z.parent:
-                self.root = w
-                if w.is_left_child():
-                    w.parent.left = z
-                else:
-                    w.parent.right = z
-            elif not w.parent:
-                self.root = z
-                if z.is_left_child():
-                    z.parent.left = w
-                else:
-                    z.parent.right = w
-            else:  # neither z nor w is the root
-                if z.is_left_child():
-                    if w.is_left_child():
-                        w.parent.left, z.parent.left = z, w
-                    else:
-                        w.parent.right, z.parent.left = z, w
-                else:
-                    if w.is_left_child():
-                        w.parent.left, z.parent.right = z, w
-                    else:
-                        w.parent.right, z.parent.right = z, w
-
-            w.parent, z.parent = z.parent, w.parent
-            z.left, w.left = w.left, z.left
-            z.right, w.right = w.right, z.right
-
-            if z.left:
-                z.left.parent = z
-            if z.right:
-                z.right.parent = z
-            if w.left:
-                w.left.parent = w
-            if w.right:
-                w.right.parent = w
-
-        if u.parent == v:
-            switch_parent_son(v, u)
-        elif v.parent == u:
-            switch_parent_son(u, v)
-        else:
-            switch_non_parent_son(u, v)
-
-    def show(self):
-        """Pretty-prints this tree using `print`."""
-        print(self)
-
-    def __str__(self):
-        if self.root is None:
-            return 'Nothing to print: this BST is empty.'
-        return '\n'.join(BSTPrinter.print_1(self.root)[0]) + "\n"
-
-    def __repr__(self):
-        return self.__str__()
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • BST
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self, root=None, name='BST')

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, root=None, name="BST"):
-    self.root = root
-    self.name = name
-    self.n = 0  # number of nodes
-    if root is not None:
-        self._initialise(root)
-
-
-
- -
- - -
-
-

def clear(

self)

-
- - - - -

Removes all nodes from this tree.

-

Time Complexity: O(1).

-
- -
-
def clear(self):
-    """Removes all nodes from this tree.
-    **Time Complexity**: O(1)."""
-    self.root = None
-    self.n = 0
-
-
-
- -
- - -
-
-

def contains(

self, key)

-
- - - - -

Returns True if a BSTNode object with key exists in the tree.

-

Time Complexity: O(h).

-
- -
-
def contains(self, key) -> bool:
-    """Returns `True` if a `BSTNode` object with `key` exists in the tree.
-    **Time Complexity**: O(h)."""
-    return self.search_r(key) is not None
-
-
-
- -
- - -
-
-

def delete(

self, x)

-
- - - - -

Deletes x from self (if it exists).

-

There are 3 cases of deletion: - 1. x has no children - 2. x has one subtree (or child) - 3. x has the left and right subtrees (or children).

-

x can either be a reference to an actual BSTNode object, -or it can be a key of a supposed node in self.

-

Time Complexity: O(h).

-
- -
-
def delete(self, x):
-    """Deletes `x` from self (if it exists).
-    There are 3 cases of deletion:
-        1. `x` has no children
-        2. `x` has one subtree (or child)
-        3. `x` has the left and right subtrees (or children).
-    `x` can either be a reference to an actual `BSTNode` object,
-    or it can be a key of a supposed node in `self`.
-    **Time Complexity**: O(h)."""
-    def delete_helper(u: BSTNode):
-        """This is a helper method to the delete method,
-        thus it should not be called by clients.
-        When deleting a node u from a BST, we have basically to consider 3 cases:
-        1. u has no children
-        2. u has one child
-        3. u has two children
-        1. u has no children, then we simply remove it
-        by modifying its parent to replace u with None.
-        If u.parent is None, then u must be the root,
-        and thus we simply set the root to None.
-        2. u has just one child,
-        but we first need to decide which one (left or right).
-        Then we elevate this child to u's position in the tree
-        by modifying u's parent to replace u by u's child.
-        But if u's parent is None, that means u was the root,
-        and the new root becomes u's child.
-        3. u has two children, then we search for u's successor s,
-        (which must be in the u's right subtree,
-        and it's the smallest of that subtree)
-        which takes u's position in the tree.
-        The rest of the u's subtree becomes the s's right subtree,
-        and the u's left subtree becomes the new s's left subtree.
-        This case is a little bit tricky,
-        because it matters whether s is u's right child.
-        Suppose s is the right child of u, then we replace u by s,
-        which might or not have a right subtree, but no left subtree.
-        Suppose s is not the right child of u,
-        in this case, we replace s by its own right child,
-        and then we replace u by s.
-        Note that "delete_two_children" does NOT exactly do that,
-        but instead it simply replaces the positions of u and s,
-        as if s was u and u was s.
-        After that, delete_helper is called again on u,
-        but note that u is now in the previous s's position,
-        and thus u has now no left subtree, but at most a right subtree."""
-        def delete_at_most_one_child(v: BSTNode):
-            """Removes v from the tree, when v has at most one child.
-            This means that v could have 0 or 1 child."""
-            child = v.right
-            if v.left:
-                child = v.left
-            if v.parent is None:  # v is the root.
-                self.root = child
-            else:  # v has a parent, so it is not the root.
-                if v.is_left_child():
-                    v.parent.left = child
-                else:
-                    v.parent.right = child
-            # child is None iff v.right and v.left are None.
-            if child:
-                child.parent = v.parent
-        def delete_two_children(v: BSTNode):
-            """Called by `delete_helper` when a node has two children."""
-            # Replace v with its successor s.
-            self._switch(v, self.successor(v))
-            # v has at most a right child now.
-            delete_helper(v)
-        if u.has_two_children():
-            delete_two_children(u)
-        else:  # u has at most one child
-            delete_at_most_one_child(u)
-        u.right = u.left = u.parent = None
-        return u
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, BSTNode):
-        x = self.search_r(x)
-        if x is None:
-            raise LookupError("No node was found with key=x.")
-    if x.parent is None and not self.is_root(x):
-        raise ValueError("x is not a valid node.")
-    self.n -= 1
-    return delete_helper(x)
-
-
-
- -
- - -
-
-

def height(

self)

-
- - - - -

Returns the maximum depth or height of the tree.

-

Time Complexity: O(h).

-
- -
-
def height(self) -> int:
-    """Returns the maximum depth or height of the tree.
-    **Time Complexity**: O(h)."""
-    if self.root is None:
-        return 0
-    return self._height(self.root)
-
-
-
- -
- - -
-
-

def in_order_traversal(

self)

-
- - - - -

Prints the elements of the tree in increasing order.

-

Time Complexity: O(h).

-
- -
-
def in_order_traversal(self):
-    """Prints the elements of the tree in increasing order.
-    **Time Complexity**: O(h)."""
-    self._in_order_traversal(self.root)
-    print("\n")
-
-
-
- -
- - -
-
-

def insert(

self, x, value=None)

-
- - - - -

Inserts (normally) x into this BST object.

-

Time Complexity: O(h).

-
- -
-
def insert(self, x, value=None):
-    """Inserts (normally) `x` into this BST object.
-    **Time Complexity**: O(h)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, BSTNode):
-        x = BSTNode(x, value)
-    if x.left or x.right or x.parent:
-        raise ValueError(
-            "x cannot have left or right children, or parent.")
-    if self.root is None:
-        self._initialise(x)
-    else:
-        c = self.root  # c is the current node
-        p = self.root.parent  # parent of c
-        while c is not None:
-            p = c
-            if x.key < c.key:
-                c = c.left
-            else:
-                c = c.right
-        if x.key < p.key:
-            p.left = x
-        else:
-            p.right = x
-        x.parent = p
-        self.n += 1
-
-
-
- -
- - -
-
-

def insert_many(

self, ls)

-
- - - - -

Calls self.insert for all elements of ls. -Therefore the elements of ls should either be -BSTNode objects or they should represent keys.

-

Time Complexity: O(len(ls)*h).

-
- -
-
def insert_many(self, ls):
-    """Calls `self.insert` for all elements of `ls`.
-    Therefore the elements of `ls` should either be
-    `BSTNode` objects or they should represent keys.
-    **Time Complexity**: O(len(ls)*h)."""
-    for i in ls:
-        self.insert(i)
-
-
-
- -
- - -
-
-

def is_empty(

self)

-
- - - - -

Returns True if this tree has 0 nodes.

-

Time Complexity: O(1).

-
- -
-
def is_empty(self):
-    """Returns `True` if this tree has 0 nodes.
-    **Time Complexity**: O(1)."""
-    return self.size() == 0
-
-
-
- -
- - -
-
-

def is_root(

self, u)

-
- - - - -

Checks if u is the root.

-

Time Complexity: O(1).

-
- -
-
def is_root(self, u: BSTNode):
-    """Checks if `u` is the root.
-    **Time Complexity**: O(1)."""
-    if u == self.root:
-        assert u.parent is None
-    return u == self.root
-
-
-
- -
- - -
-
-

def left_rotate(

self, x)

-
- - - - -

Left rotates the subtree rooted at node x.

-

x can be a BSTNode object, and in that case, -this function performs in constant time O(1); -else, if node is not a BSTNode object, -it tries to search for a BSTNode object with key=x, -and, in that case, it performs in O(h) time.

-

Returns the node which is at the previous position of x, -that is it returns the parent of x.

-

Time Complexity: O(1).

-
- -
-
def left_rotate(self, x):
-    """Left rotates the subtree rooted at node `x`.
-    `x` can be a `BSTNode` object, and in that case,
-    this function performs in constant time O(1);
-    else, if node is not a `BSTNode` object,
-    it tries to search for a `BSTNode` object with key=x,
-    and, in that case, it performs in O(h) time.
-    Returns the node which is at the previous position of `x`,
-    that is it returns the parent of `x`.
-    **Time Complexity**: O(1)."""
-    c = None  # It will rotate the subtree rooted at c.
-    if not isinstance(x, BSTNode):
-        c = self.search(x)
-        if c is None:
-            raise LookupError("key node was not found in the tree.")
-    else:
-        c = x
-    # To left rotate a node, its right child must exist.
-    if c.right is None:
-        raise ValueError("Left rotation cannot be performed on " + str(c) +
-                         " because it does not have a right child.")
-    c.right.parent = c.parent
-    # Only the root has a None parent.
-    if c.parent is None:
-        self.root = c.right
-    # Checking if c is a left or a right child,
-    # in order to set the new left
-    # or right child respectively of its parent.
-    elif c.is_left_child():
-        c.parent.left = c.right
-    else:
-        c.parent.right = c.right
-    c.parent = c.right
-    # The new right child of c becomes what is
-    # the left child of its previous right child.
-    c.right = c.parent.left
-    # Set c to be the parent of its new right child.
-    if c.right is not None:
-        c.right.parent = c
-    # Set c to be the new left child of its new parent.
-    c.parent.left = c
-    return c.parent
-
-
-
- -
- - -
-
-

def maximum(

self)

-
- - - - -

Calls BST._maximum_r(self.root) if self.root is evaluated to True.

-

Time Complexity: O(h).

-
- -
-
def maximum(self):
-    """Calls `BST._maximum_r(self.root)` if `self.root` is evaluated to `True`.
-    **Time Complexity**: O(h)."""
-    if self.root:
-        return BST._maximum_r(self.root)
-
-
-
- -
- - -
-
-

def minimum(

self)

-
- - - - -

Calls BST._minimum_r(self.root) if self.root is evaluated to True.

-

Time Complexity: O(h).

-
- -
-
def minimum(self):
-    """Calls `BST._minimum_r(self.root)` if `self.root` is evaluated to `True`.
-    **Time Complexity**: O(h)."""
-    if self.root:
-        return BST._minimum_r(self.root)
-
-
-
- -
- - -
-
-

def post_order_traversal(

self)

-
- - - - -

Prints the keys of this tree in post-order. -It does the opposite of pre_order_traversal.

-

Time Complexity: O(h).

-
- -
-
def post_order_traversal(self):
-    """Prints the keys of this tree in post-order.
-    It does the opposite of `pre_order_traversal`.
-    **Time Complexity**: O(h)."""
-    self._post_order_traversal(self.root)
-    print("\n")
-
-
-
- -
- - -
-
-

def pre_order_traversal(

self)

-
- - - - -

Prints the keys of this tree in pre-order. -The pre-order consists of recursively printing first a node u, -then its left child node and then its right child node.

-

Time Complexity: O(h).

-
- -
-
def pre_order_traversal(self):
-    """Prints the keys of this tree in pre-order.
-    The pre-order consists of recursively printing first a node `u`,
-    then its left child node and then its right child node.
-    **Time Complexity**: O(h)."""
-    self._pre_order_traversal(self.root)
-    print("\n")
-
-
-
- -
- - -
-
-

def predecessor(

self, x)

-
- - - - -

Finds the predecessor of the node x, -i.e. the greatest element smaller than x.

-

x can either be a reference to an actual BSTNode object, -or it can be a key of a supposed node in self.

-

Time Complexity: O(h).

-
- -
-
def predecessor(self, x):
-    """Finds the predecessor of the node `x`,
-    i.e. the greatest element smaller than `x`.
-    `x` can either be a reference to an actual `BSTNode` object,
-    or it can be a key of a supposed node in self.
-    **Time Complexity**: O(h)."""
-    if not isinstance(x, BSTNode):
-        x = self.search_r(x)
-        if x is None:
-            raise LookupError("No node was found with key=x.")
-    if x.left:
-        return BST._maximum_r(x.left)
-    p = x.parent
-    while p and x == p.left:
-        x = p
-        p = x.parent
-    return p
-
-
-
- -
- - -
-
-

def rank(

self, key)

-
- - - - -

Returns the number of keys strictly less than key.

-

Time Complexity: O(h).

-
- -
-
def rank(self, key) -> int:
-    """Returns the number of keys strictly less than `key`.
-    **Time Complexity**: O(h)."""
-    if key is None:
-        raise ValueError("key cannot be None.")
-    if not self.search(key):
-        raise LookupError("key was not found.")
-    if self.root is None:
-        return 0
-    else:
-        r = 0
-        return self._rank(self.root, key, r)
-
-
-
- -
- - -
-
-

def remove_max(

self)

-
- - - - -

Removes and returns the maximum element of the tree, if it is not empty.

-

Time Complexity: O(h).

-
- -
-
def remove_max(self):
-    """Removes and returns the maximum element of the tree, if it is not empty.
-    **Time Complexity**: O(h)."""
-    def _remove_max(u: BSTNode):
-        """Removes the maximum element of the subtree rooted at `u`.
-        Note that the maximum element is all the way to the right,
-        and it cannot have a right child,
-        but it can still have a left subtree.
-        If `u` is `None`, exceptions will be thrown."""
-        m = BST._maximum_r(u)
-        if m.left:  # m has a left subtree.
-            if self.is_root(m):  # m is the root.
-                self.root = m.left
-                m.left.parent = None  # self.root.parent = None
-            else:  # m is NOT the root.
-                m.left.parent = m.parent
-                m.parent.right = m.left
-        else:  # m has NO children
-            if self.is_root(m):
-                self.root = None
-            else:
-                m.parent.right = None
-        m.parent = m.left = None
-        self.n -= 1
-        return m
-    if self.n > 0:
-        return _remove_max(self.root)
-
-
-
- -
- - -
-
-

def remove_min(

self)

-
- - - - -

Removes and returns the minimum element of the tree, if it is not empty.

-

Time Complexity: O(h).

-
- -
-
def remove_min(self):
-    """Removes and returns the minimum element of the tree, if it is not empty.
-    **Time Complexity**: O(h)."""
-    def _remove_min(u: BSTNode):
-        """Removes and returns the minimum element of the subtree rooted at `u`.
-        If `u` is `None`, exceptions will be thrown."""
-        m = BST._minimum_r(u)
-        if m.right:
-            if self.is_root(m):
-                self.root = m.right
-                m.right.parent = None
-            else:
-                m.right.parent = m.parent
-                m.parent.left = m.right
-        else:  # m has not right subtree.
-            if self.is_root(m):
-                self.root = None
-            else:  # m is an internal node with no right subtree.
-                m.parent.left = None
-        m.right = m.parent = None
-        self.n -= 1
-        return m
-    if self.n > 0:
-        return _remove_min(self.root)
-
-
-
- -
- - -
-
-

def reverse_in_order_traversal(

self)

-
- - - - -

Prints the keys of this tree in decreasing order.

-

It does the opposite of self.in_order_traversal.

-

Time Complexity: O(h).

-
- -
-
def reverse_in_order_traversal(self):
-    """Prints the keys of this tree in decreasing order.
-    It does the opposite of `self.in_order_traversal`.
-    **Time Complexity**: O(h)."""
-    self._reverse_in_order_traversal(self.root)
-    print("\n")
-
-
-
- -
- - -
-
-

def right_rotate(

self, x)

-
- - - - -

Right rotates the subtree rooted at node x. -See doc-strings of self.left_rotate.

-

Time Complexity: O(1).

-
- -
-
def right_rotate(self, x):
-    """Right rotates the subtree rooted at node `x`.
-    See doc-strings of `self.left_rotate`.
-    **Time Complexity**: O(1)."""
-    c = None
-    if not isinstance(x, BSTNode):
-        c = self.search(x)
-        if c is None:
-            raise LookupError("key node was not found in the tree.")
-    else:
-        c = x
-    if c.left is None:
-        raise ValueError("Right rotation cannot be performed on " + str(c) +
-                         " because it does not have a left child.")
-    c.left.parent = c.parent
-    if c.parent is None:
-        self.root = c.left
-    elif c.is_left_child():
-        c.parent.left = c.left
-    else:
-        c.parent.right = c.left
-    c.parent = c.left
-    c.left = c.parent.right
-    if c.left is not None:
-        c.left.parent = c
-    c.parent.right = c
-    return c.parent
-
-
-
- -
- - -
-
-

def search(

self, key, s=None)

-
- - - - -

Searches for the key in the tree. -If s is specified, then this procedure starts searching from s.

-

key must be a comparable object of the same type as the other keys.

-

Time Complexity: O(h).

-
- -
-
def search(self, key, s: BSTNode = None) -> BSTNode:
-    """Searches for the key in the tree.
-    If `s` is specified, then this procedure starts searching from `s`.
-    `key` must be a comparable object of the same type as the other keys.
-    **Time Complexity**: O(h)."""
-    if key is None:
-        raise ValueError("key cannot be None.")
-    if s is None:
-        return self.search_i(key)
-    else:
-        return BST._search_i(key, s)
-
-
-
- -
- - -
-
-

def search_i(

self, key)

-
- - - - -

Searches iteratively for key starting from the root.

-

Time Complexity: O(h).

-
- -
-
def search_i(self, key) -> BSTNode:
-    """Searches iteratively for key starting from the root.
-    **Time Complexity**: O(h)."""
-    return BST._search_i(key, self.root)
-
-
-
- -
- - -
-
-

def search_r(

self, key)

-
- - - - -

Searches recursively for key starting from self.root.

-

Time Complexity: O(h).

-
- -
-
def search_r(self, key) -> BSTNode:
-    """Searches recursively for `key` starting from `self.root`.
-    **Time Complexity**: O(h)."""
-    return self._search_r(key, self.root)
-
-
-
- -
- - -
-
-

def show(

self)

-
- - - - -

Pretty-prints this tree using print.

-
- -
-
def show(self):
-    """Pretty-prints this tree using `print`."""
-    print(self)
-
-
-
- -
- - -
-
-

def size(

self)

-
- - - - -

Returns the total number of nodes.

-

Time Complexity: O(1).

-
- -
-
def size(self):
-    """Returns the total number of nodes.
-    **Time Complexity**: O(1)."""
-    return self.n
-
-
-
- -
- - -
-
-

def successor(

self, x)

-
- - - - -

Finds the successor of x, -i.e. the smallest element greater than x.

-

If x has a right subtree, -then the successor of x is the minimum of that right subtree.

-

Otherwise it is the first ancestor of x, lets call it A, -such that x falls in the left subtree of A.

-

x can either be a reference to an actual BSTNode object, -or it can be a key of a supposed node in self.

-

Time Complexity: O(h).

-
- -
-
def successor(self, x):
-    """Finds the successor of `x`,
-    i.e. the smallest element greater than `x`.
-    If `x` has a right subtree,
-    then the successor of `x` is the minimum of that right subtree.
-    Otherwise it is the first ancestor of `x`, lets call it `A`,
-    such that `x` falls in the left subtree of `A`.
-    `x` can either be a reference to an actual `BSTNode` object,
-    or it can be a key of a supposed node in self.
-    **Time Complexity**: O(h)."""
-    if not isinstance(x, BSTNode):
-        x = self.search(x)
-        if x is None:
-            raise LookupError("No node was found with key=x.")
-    if x.right:
-        return BST._minimum_r(x.right)
-    p = x.parent
-    while p and p.right == x:
-        x = p
-        p = x.parent
-    return p
-
-
-
- -
- -

Instance variables

-
-

var n

- - - - -
-
- -
-
-

var name

- - - - -
-
- -
-
-

var root

- - - - -
-
- -
-
-
- -
-

class BSTNode

- - -

Class to represent a BST's node.

-
- -
-
class BSTNode:
-    """Class to represent a BST's node."""
-
-    def __init__(self, key, value=None, parent=None, left=None, right=None):
-        if key is None:
-            raise ValueError("key cannot be None")
-        self.key = key
-        self.value = value
-        self.parent = parent
-        self.left = left
-        self.right = right
-        # Used for printing purposes.
-        self.label = "[" + str(self.key) + "]"
-
-    @property
-    def sibling(self):
-        """Returns the sibling node of this node,
-        which can of course be `None`."""
-        if self.parent is not None:
-            if self.is_left_child():
-                return self.parent.right
-            else:
-                return self.parent.left
-
-    @property
-    def grandparent(self):
-        """Returns the parent of the parent of this node."""
-        if self.parent is not None:
-            return self.parent.parent
-
-    @property
-    def uncle(self):
-        """Returns the uncle node of this node.
-        The uncle is the sibling of the parent of this node,
-        if it exists. `None` is returned if it doesn't exist,
-        or the parent or grandparent of this node is `None`."""
-        if self.grandparent is not None:  # implies that also parent is not None
-            if self.parent == self.grandparent.left:
-                return self.grandparent.right
-            else:  # self.parent == self.grandparent.right:
-                return self.grandparent.left
-
-    def reset(self):
-        self.parent = None
-        self.left = None
-        self.right = None
-
-    def is_left_child(self) -> bool:
-        if self.parent is not None:
-            if self.parent.left is not None:
-                return self.parent.left == self
-        else:
-            raise AttributeError("self does not have a parent.")
-
-    def is_right_child(self) -> bool:
-        if self.parent is not None:
-            if self.parent.right is not None:
-                return self.parent.right == self
-        else:
-            raise AttributeError("self does not have a parent.")
-
-    def has_children(self) -> bool:
-        """Returns `True` if `self` has at least one child. `False` otherwise."""
-        return self.left or self.right
-
-    def has_one_child(self) -> bool:
-        """Returns `True` only if `self` has exactly one child. `False` otherwise."""
-        return (self.left and not self.right) or (not self.left and self.right)
-
-    def has_two_children(self) -> bool:
-        """Returns `True` if self has exactly two children. `False` otherwise."""
-        return self.left and self.right
-
-    def count(self) -> int:
-        """Counts the numbers of nodes under `self` (including `self`)."""
-
-        def _count(u, c: int):
-            if u is None:
-                return c
-            else:
-                c += 1
-            c = _count(u.left, c)
-            c = _count(u.right, c)
-            return c
-
-        if not self.has_children():
-            return 1
-        else:
-            c = 0
-            return _count(self, c)
-
-    def __str__(self):
-        return "{" + str(self.key) + ": " + str(self.value) + "}"
-
-    def __fields(self):
-        return [["Node (Key)", self.key],
-                ["Value", self.value],
-                ["Parent", self.parent],
-                ["Left child", self.left],
-                ["Right child", self.right],
-                ["Sibling", self.sibling],
-                ["Grandparent", self.grandparent],
-                ["Uncle", self.uncle]]
-
-    def __repr__(self):
-        return tabulate(self.__fields(), tablefmt="fancy_grid")
-
-    def show(self):
-        print(self.__repr__())
-
-
-
- - -
-

Ancestors (in MRO)

- -

Static methods

- -
-
-

def __init__(

self, key, value=None, parent=None, left=None, right=None)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, key, value=None, parent=None, left=None, right=None):
-    if key is None:
-        raise ValueError("key cannot be None")
-    self.key = key
-    self.value = value
-    self.parent = parent
-    self.left = left
-    self.right = right
-    # Used for printing purposes.
-    self.label = "[" + str(self.key) + "]"
-
-
-
- -
- - -
-
-

def count(

self)

-
- - - - -

Counts the numbers of nodes under self (including self).

-
- -
-
def count(self) -> int:
-    """Counts the numbers of nodes under `self` (including `self`)."""
-    def _count(u, c: int):
-        if u is None:
-            return c
-        else:
-            c += 1
-        c = _count(u.left, c)
-        c = _count(u.right, c)
-        return c
-    if not self.has_children():
-        return 1
-    else:
-        c = 0
-        return _count(self, c)
-
-
-
- -
- - -
-
-

def has_children(

self)

-
- - - - -

Returns True if self has at least one child. False otherwise.

-
- -
-
def has_children(self) -> bool:
-    """Returns `True` if `self` has at least one child. `False` otherwise."""
-    return self.left or self.right
-
-
-
- -
- - -
-
-

def has_one_child(

self)

-
- - - - -

Returns True only if self has exactly one child. False otherwise.

-
- -
-
def has_one_child(self) -> bool:
-    """Returns `True` only if `self` has exactly one child. `False` otherwise."""
-    return (self.left and not self.right) or (not self.left and self.right)
-
-
-
- -
- - -
-
-

def has_two_children(

self)

-
- - - - -

Returns True if self has exactly two children. False otherwise.

-
- -
-
def has_two_children(self) -> bool:
-    """Returns `True` if self has exactly two children. `False` otherwise."""
-    return self.left and self.right
-
-
-
- -
- - -
-
-

def is_left_child(

self)

-
- - - - -
- -
-
def is_left_child(self) -> bool:
-    if self.parent is not None:
-        if self.parent.left is not None:
-            return self.parent.left == self
-    else:
-        raise AttributeError("self does not have a parent.")
-
-
-
- -
- - -
-
-

def is_right_child(

self)

-
- - - - -
- -
-
def is_right_child(self) -> bool:
-    if self.parent is not None:
-        if self.parent.right is not None:
-            return self.parent.right == self
-    else:
-        raise AttributeError("self does not have a parent.")
-
-
-
- -
- - -
-
-

def reset(

self)

-
- - - - -
- -
-
def reset(self):
-    self.parent = None
-    self.left = None
-    self.right = None
-
-
-
- -
- - -
-
-

def show(

self)

-
- - - - -
- -
-
def show(self):
-    print(self.__repr__())
-
-
-
- -
- -

Instance variables

-
-

var grandparent

- - - - -

Returns the parent of the parent of this node.

-
-
- -
-
-

var key

- - - - -
-
- -
-
-

var label

- - - - -
-
- -
-
-

var left

- - - - -
-
- -
-
-

var parent

- - - - -
-
- -
-
-

var right

- - - - -
-
- -
-
-

var sibling

- - - - -

Returns the sibling node of this node, -which can of course be None.

-
-
- -
-
-

var uncle

- - - - -

Returns the uncle node of this node. -The uncle is the sibling of the parent of this node, -if it exists. None is returned if it doesn't exist, -or the parent or grandparent of this node is None.

-
-
- -
-
-

var value

- - - - -
-
- -
-
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/DSForests.m.html b/docs/ands/ds/DSForests.m.html deleted file mode 100644 index 7009786b..00000000 --- a/docs/ands/ds/DSForests.m.html +++ /dev/null @@ -1,1945 +0,0 @@ - - - - - - ands.ds.DSForests API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.DSForests module

-

Meta info

-

Author: Nelson Brochado

-

Created: 21/02/2016

-

Updated: 03/01/2016

-

Description

-

A disjoint-set (forests) or union-find data structure is a data structure which keeps track of a set of elements -partitioned into disjoint (non-overlapping, i.e. their intersection is the empty set) sets. -The usual operations supported by this data structure are:

-
    -
  1. -

    make-set(x): creates a single-element set containing x, and x is the representative of that set.

    -
  2. -
  3. -

    find(x): returns the "representative" of the set where the element x is. - If the data structure is implemented a tree, the representative is the root of the tree.

    -
  4. -
  5. -

    union(x, y): unions the sets where x and y are (if they do not belong already to the same set).

    -
  6. -
-

DSForests uses two heuristics that improve the performance with respect to a naive implementation.

-
    -
  1. -

    Union by rank: attach the smaller tree to the root of the larger tree

    -
  2. -
  3. -

    Path compression: is a way of flattening the structure of the tree whenever find is used on it.

    -
  4. -
-

These two techniques complement each other: applied together, the amortized time per operation is only O( α (n)).

-

TODO

-
    -
  • Deletion operation (OPTIONAL, since it's usually not part of the interface of a disjoint-set data structure)
  • -
  • Pretty-print(x), for some element x in the disjoint-set data structure.
  • -
  • Implement the version explained here
  • -
-

References

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-# Meta info
-
-Author: Nelson Brochado
-
-Created: 21/02/2016
-
-Updated: 03/01/2016
-
-# Description
-
-A disjoint-set (forests) or union-find data structure is a data structure which keeps track of a set of elements
-partitioned into disjoint (non-overlapping, i.e. their intersection is the empty set) sets.
-The usual operations supported by this data structure are:
-
-  1. make-set(x): creates a single-element set containing x, and x is the representative of that set.
-
-  2. find(x): returns the "representative" of the set where the element x is.
-    If the data structure is implemented a tree, the representative is the root of the tree.
-
-  3. union(x, y): unions the sets where x and y are (if they do not belong already to the same set).
-
-`DSForests` uses two heuristics that improve the performance with respect to a naive implementation.
-
-  1. Union by rank: attach the smaller tree to the root of the larger tree
-
-  2. Path compression: is a way of flattening the structure of the tree whenever find is used on it.
-
-These two techniques complement each other: applied together, the amortized time per operation is only O( α (n)).
-
-# TODO
-
-- Deletion operation (OPTIONAL, since it's usually not part of the interface of a disjoint-set data structure)
-- Pretty-print(x), for some element x in the disjoint-set data structure.
-- Implement the version explained [here](http://algs4.cs.princeton.edu/15uf/)
-
-# References
-
-- Introduction to algorithms, 3rd, by C.L.R.S., chapter 21.3
-- [https://en.wikipedia.org/wiki/Disjoint-set_data_structure](https://en.wikipedia.org/wiki/Disjoint-set_data_structure)
-- [http://orionsword.no-ip.org/blog/wordpress/?p=246](http://orionsword.no-ip.org/blog/wordpress/?p=246)
-- [http://stackoverflow.com/a/22945492/3924118](http://stackoverflow.com/a/22945492/3924118)
-- [http://stackoverflow.com/q/23055236/3924118](http://stackoverflow.com/q/23055236/3924118)
-- [https://www.cs.usfca.edu/~galles/JavascriptVisual/DisjointSets.html](https://www.cs.usfca.edu/~galles/JavascriptVisual/DisjointSets.html)
-to visualize how disjoint-sets work.
-
-"""
-
-
-class DSNode:
-    def __init__(self, x, rank=0):
-        # This attribute can contain any hashable value.
-        self.value = x
-
-        # The rank of node x only changes in one specific union(x, y) case:
-        # when x is the representative of its set
-        # and the representative of the set where y resides has the same rank as x.
-        # In the DSForests implementation below, if a situation as just described occurs,
-        # then the x.rank is increased by 1.
-        self.rank = rank
-
-        # Reference to the representative of the set where this node resides
-        # Since DSForests actually implements a tree,
-        # self.parent is also the root of that tree.
-        self.parent = self
-
-        # Reference used to help printing all nodes
-        # belonging to the set to which this node belongs in O(m) time,
-        # where m is the size of the mentioned set.
-        self.next = self
-
-    def is_root(self):
-        """A DSNode x is a root or representative of a set
-        whenever its parent pointer points to himself.
-        Of course this is only true if x is already in a DSForests object."""
-        return self.parent == self
-
-    def __str__(self):
-        return str(self.value)
-
-    def __repr__(self):
-        if self.parent == self:
-            return "(value: {0}, rank: {1}, parent: self)".format(self.value, self.rank)
-        else:
-            return "(value: {0}, rank: {1}, parent: {2})".format(self.value, self.rank, self.parent)
-
-
-class DSForests:
-    def __init__(self):
-        # keeps tracks of the DSNodes in this disjoint-set forests.
-        self.sets = {}
-
-    def make_set(self, x) -> DSNode:
-        """Creates a set object for `x`."""
-        assert x not in self.sets
-        self.sets[x] = DSNode(x)
-        return self.sets[x]
-
-    def find(self, x: DSNode) -> DSNode:
-        """Finds and returns the representative (or root) of `x`.
-        It follows parent nodes until it reaches
-        the root of the tree (set) to which `x` belongs.
-
-        It also uses a technique called "path compression",
-        which is a way of flattening the structure of the tree.
-
-        The idea is that each node visited on the way to a root node
-        may as well be attached directly to the root node;
-        they all share the same representative.
-
-        To effect this, as `self.find` recursively traverses up the tree,
-        it changes each node's parent reference
-        to point to the root that it found.
-
-        The resulting tree is much flatter,
-        speeding up future operations not only on these elements
-        but on those referencing them, directly or indirectly.
-
-        This algorithm does not change any ranks of the `Set` objects.
-
-        **Time Complexity:** O*(α (n)),
-        where α (n) is the inverse of the function n = f(x) = A(x, x),
-        and A is the extremely fast-growing **Ackermann** function.
-        Since α (n) is the inverse of this function,
-        α (n) is less than 5 for all remotely practical values of n.
-        Thus, the amortized running time per operation
-        is effectively a small constant."""
-        assert x
-        if x.parent != x:
-            x.parent = self.find(x.parent)
-        return x.parent
-
-    def find_iteratively(self, x: DSNode) -> DSNode:
-        """This version is just an iterative alternative to the find method."""
-        assert x
-
-        y = x
-
-        # find the representative of the set where x resides
-        while y != y.parent:
-            y = y.parent
-
-        # post-condition
-        assert y == self.find(x)
-
-        # now y is the representative of x,
-        # but we also want to do a path compression,
-        # i.e. connect all nodes in the path from x to y directly to y.
-        while x != x.parent:
-            p = x.parent
-            x.parent = y
-            x = p
-
-        return y
-
-    def union(self, x, y) -> DSNode:
-        """"Union by rank" 2 trees (sets) into one by attaching
-        the root of one to the root of the other.
-        Returns the `DSNode` object representing the representative of
-        the set resulted from the union of the sets containing `x` and `y`.
-
-        "Union by rank" consists of attaching the smaller tree
-        to the root of the larger tree.
-
-        Since it is the depth of the tree that affects the running time,
-        the tree with smaller depth gets added
-        under the root of the deeper tree,
-        which only increases the depth if the depths were equal.
-
-        In the context of this algorithm,
-        the term _rank_ is used instead of depth,
-        since it stops being equal to the depth
-        if path compression is also used.
-
-        The rank is an upper bound on the height of the node.
-
-        One-element trees are defined to have a rank of zero,
-        and whenever two trees of the same rank `r` are united,
-        the rank of the result is `r + 1`.
-
-        **Time Complexity:** O*(α (n)),
-        where α (n) is the inverse of the function n = f(x) = A(x, x),
-        and A is the extremely fast-growing **Ackermann** function.
-        Since α (n) is the inverse of this function,
-        α (n) is less than 5 for all remotely practical values of n.
-        Thus, the amortized running time per operation
-        is effectively a small constant."""
-        assert x in self.sets and y in self.sets
-
-        # Since the original values x and y are not used afterwards,
-        # and what we actually need in two places of this algorithm are the corresponding DSNodes
-        # we set x and y to be respectively their DSNode counter-part.
-        x = self.sets[x]
-        y = self.sets[y]
-
-        x_root = self.find(x)
-        y_root = self.find(y)
-
-        # x and y are already joined.
-        if x_root == y_root:
-            return
-
-        # Exchanging the next pointers of x and y.
-        # This is needed in order to print the elements of a set in O(m) time,
-        # where m is the size of the same set.
-        # Check here: http://stackoverflow.com/a/22945492/3924118.
-        x.next, y.next = y.next, x.next
-
-        # x and y are not in the same set, therefore we merge them.
-        if x_root.rank < y_root.rank:
-            x_root.parent = y_root
-            return y_root
-        else:
-            y_root.parent = x_root
-            if x_root.rank == y_root.rank:
-                x_root.rank += 1
-            return x_root
-
-    def print_set(self, x) -> None:
-        assert x in self.sets
-
-        x = self.sets[x]
-        y = x
-
-        print("{0} -> {{{1}".format(x, x), end="")
-        while y.next != x:
-            print(",", y.next, end="")
-            y = y.next
-        print("}")
-
-    def __str__(self):
-        return str(self.sets)
-
-
- -
- -
- - -

Classes

- -
-

class DSForests

- - -
- -
-
class DSForests:
-    def __init__(self):
-        # keeps tracks of the DSNodes in this disjoint-set forests.
-        self.sets = {}
-
-    def make_set(self, x) -> DSNode:
-        """Creates a set object for `x`."""
-        assert x not in self.sets
-        self.sets[x] = DSNode(x)
-        return self.sets[x]
-
-    def find(self, x: DSNode) -> DSNode:
-        """Finds and returns the representative (or root) of `x`.
-        It follows parent nodes until it reaches
-        the root of the tree (set) to which `x` belongs.
-
-        It also uses a technique called "path compression",
-        which is a way of flattening the structure of the tree.
-
-        The idea is that each node visited on the way to a root node
-        may as well be attached directly to the root node;
-        they all share the same representative.
-
-        To effect this, as `self.find` recursively traverses up the tree,
-        it changes each node's parent reference
-        to point to the root that it found.
-
-        The resulting tree is much flatter,
-        speeding up future operations not only on these elements
-        but on those referencing them, directly or indirectly.
-
-        This algorithm does not change any ranks of the `Set` objects.
-
-        **Time Complexity:** O*(α (n)),
-        where α (n) is the inverse of the function n = f(x) = A(x, x),
-        and A is the extremely fast-growing **Ackermann** function.
-        Since α (n) is the inverse of this function,
-        α (n) is less than 5 for all remotely practical values of n.
-        Thus, the amortized running time per operation
-        is effectively a small constant."""
-        assert x
-        if x.parent != x:
-            x.parent = self.find(x.parent)
-        return x.parent
-
-    def find_iteratively(self, x: DSNode) -> DSNode:
-        """This version is just an iterative alternative to the find method."""
-        assert x
-
-        y = x
-
-        # find the representative of the set where x resides
-        while y != y.parent:
-            y = y.parent
-
-        # post-condition
-        assert y == self.find(x)
-
-        # now y is the representative of x,
-        # but we also want to do a path compression,
-        # i.e. connect all nodes in the path from x to y directly to y.
-        while x != x.parent:
-            p = x.parent
-            x.parent = y
-            x = p
-
-        return y
-
-    def union(self, x, y) -> DSNode:
-        """"Union by rank" 2 trees (sets) into one by attaching
-        the root of one to the root of the other.
-        Returns the `DSNode` object representing the representative of
-        the set resulted from the union of the sets containing `x` and `y`.
-
-        "Union by rank" consists of attaching the smaller tree
-        to the root of the larger tree.
-
-        Since it is the depth of the tree that affects the running time,
-        the tree with smaller depth gets added
-        under the root of the deeper tree,
-        which only increases the depth if the depths were equal.
-
-        In the context of this algorithm,
-        the term _rank_ is used instead of depth,
-        since it stops being equal to the depth
-        if path compression is also used.
-
-        The rank is an upper bound on the height of the node.
-
-        One-element trees are defined to have a rank of zero,
-        and whenever two trees of the same rank `r` are united,
-        the rank of the result is `r + 1`.
-
-        **Time Complexity:** O*(α (n)),
-        where α (n) is the inverse of the function n = f(x) = A(x, x),
-        and A is the extremely fast-growing **Ackermann** function.
-        Since α (n) is the inverse of this function,
-        α (n) is less than 5 for all remotely practical values of n.
-        Thus, the amortized running time per operation
-        is effectively a small constant."""
-        assert x in self.sets and y in self.sets
-
-        # Since the original values x and y are not used afterwards,
-        # and what we actually need in two places of this algorithm are the corresponding DSNodes
-        # we set x and y to be respectively their DSNode counter-part.
-        x = self.sets[x]
-        y = self.sets[y]
-
-        x_root = self.find(x)
-        y_root = self.find(y)
-
-        # x and y are already joined.
-        if x_root == y_root:
-            return
-
-        # Exchanging the next pointers of x and y.
-        # This is needed in order to print the elements of a set in O(m) time,
-        # where m is the size of the same set.
-        # Check here: http://stackoverflow.com/a/22945492/3924118.
-        x.next, y.next = y.next, x.next
-
-        # x and y are not in the same set, therefore we merge them.
-        if x_root.rank < y_root.rank:
-            x_root.parent = y_root
-            return y_root
-        else:
-            y_root.parent = x_root
-            if x_root.rank == y_root.rank:
-                x_root.rank += 1
-            return x_root
-
-    def print_set(self, x) -> None:
-        assert x in self.sets
-
-        x = self.sets[x]
-        y = x
-
-        print("{0} -> {{{1}".format(x, x), end="")
-        while y.next != x:
-            print(",", y.next, end="")
-            y = y.next
-        print("}")
-
-    def __str__(self):
-        return str(self.sets)
-
-
-
- - -
-

Ancestors (in MRO)

- -

Static methods

- -
-
-

def __init__(

self)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self):
-    # keeps tracks of the DSNodes in this disjoint-set forests.
-    self.sets = {}
-
-
-
- -
- - -
-
-

def find(

self, x)

-
- - - - -

Finds and returns the representative (or root) of x. -It follows parent nodes until it reaches -the root of the tree (set) to which x belongs.

-

It also uses a technique called "path compression", -which is a way of flattening the structure of the tree.

-

The idea is that each node visited on the way to a root node -may as well be attached directly to the root node; -they all share the same representative.

-

To effect this, as self.find recursively traverses up the tree, -it changes each node's parent reference -to point to the root that it found.

-

The resulting tree is much flatter, -speeding up future operations not only on these elements -but on those referencing them, directly or indirectly.

-

This algorithm does not change any ranks of the Set objects.

-

Time Complexity: O*(α (n)), -where α (n) is the inverse of the function n = f(x) = A(x, x), -and A is the extremely fast-growing Ackermann function. -Since α (n) is the inverse of this function, -α (n) is less than 5 for all remotely practical values of n. -Thus, the amortized running time per operation -is effectively a small constant.

-
- -
-
def find(self, x: DSNode) -> DSNode:
-    """Finds and returns the representative (or root) of `x`.
-    It follows parent nodes until it reaches
-    the root of the tree (set) to which `x` belongs.
-    It also uses a technique called "path compression",
-    which is a way of flattening the structure of the tree.
-    The idea is that each node visited on the way to a root node
-    may as well be attached directly to the root node;
-    they all share the same representative.
-    To effect this, as `self.find` recursively traverses up the tree,
-    it changes each node's parent reference
-    to point to the root that it found.
-    The resulting tree is much flatter,
-    speeding up future operations not only on these elements
-    but on those referencing them, directly or indirectly.
-    This algorithm does not change any ranks of the `Set` objects.
-    **Time Complexity:** O*(α (n)),
-    where α (n) is the inverse of the function n = f(x) = A(x, x),
-    and A is the extremely fast-growing **Ackermann** function.
-    Since α (n) is the inverse of this function,
-    α (n) is less than 5 for all remotely practical values of n.
-    Thus, the amortized running time per operation
-    is effectively a small constant."""
-    assert x
-    if x.parent != x:
-        x.parent = self.find(x.parent)
-    return x.parent
-
-
-
- -
- - -
-
-

def find_iteratively(

self, x)

-
- - - - -

This version is just an iterative alternative to the find method.

-
- -
-
def find_iteratively(self, x: DSNode) -> DSNode:
-    """This version is just an iterative alternative to the find method."""
-    assert x
-    y = x
-    # find the representative of the set where x resides
-    while y != y.parent:
-        y = y.parent
-    # post-condition
-    assert y == self.find(x)
-    # now y is the representative of x,
-    # but we also want to do a path compression,
-    # i.e. connect all nodes in the path from x to y directly to y.
-    while x != x.parent:
-        p = x.parent
-        x.parent = y
-        x = p
-    return y
-
-
-
- -
- - -
-
-

def make_set(

self, x)

-
- - - - -

Creates a set object for x.

-
- -
-
def make_set(self, x) -> DSNode:
-    """Creates a set object for `x`."""
-    assert x not in self.sets
-    self.sets[x] = DSNode(x)
-    return self.sets[x]
-
-
-
- -
- - -
-
-

def print_set(

self, x)

-
- - - - -
- -
-
def print_set(self, x) -> None:
-    assert x in self.sets
-    x = self.sets[x]
-    y = x
-    print("{0} -> {{{1}".format(x, x), end="")
-    while y.next != x:
-        print(",", y.next, end="")
-        y = y.next
-    print("}")
-
-
-
- -
- - -
-
-

def union(

self, x, y)

-
- - - - -

"Union by rank" 2 trees (sets) into one by attaching -the root of one to the root of the other. -Returns the DSNode object representing the representative of -the set resulted from the union of the sets containing x and y.

-

"Union by rank" consists of attaching the smaller tree -to the root of the larger tree.

-

Since it is the depth of the tree that affects the running time, -the tree with smaller depth gets added -under the root of the deeper tree, -which only increases the depth if the depths were equal.

-

In the context of this algorithm, -the term rank is used instead of depth, -since it stops being equal to the depth -if path compression is also used.

-

The rank is an upper bound on the height of the node.

-

One-element trees are defined to have a rank of zero, -and whenever two trees of the same rank r are united, -the rank of the result is r + 1.

-

Time Complexity: O*(α (n)), -where α (n) is the inverse of the function n = f(x) = A(x, x), -and A is the extremely fast-growing Ackermann function. -Since α (n) is the inverse of this function, -α (n) is less than 5 for all remotely practical values of n. -Thus, the amortized running time per operation -is effectively a small constant.

-
- -
-
def union(self, x, y) -> DSNode:
-    """"Union by rank" 2 trees (sets) into one by attaching
-    the root of one to the root of the other.
-    Returns the `DSNode` object representing the representative of
-    the set resulted from the union of the sets containing `x` and `y`.
-    "Union by rank" consists of attaching the smaller tree
-    to the root of the larger tree.
-    Since it is the depth of the tree that affects the running time,
-    the tree with smaller depth gets added
-    under the root of the deeper tree,
-    which only increases the depth if the depths were equal.
-    In the context of this algorithm,
-    the term _rank_ is used instead of depth,
-    since it stops being equal to the depth
-    if path compression is also used.
-    The rank is an upper bound on the height of the node.
-    One-element trees are defined to have a rank of zero,
-    and whenever two trees of the same rank `r` are united,
-    the rank of the result is `r + 1`.
-    **Time Complexity:** O*(α (n)),
-    where α (n) is the inverse of the function n = f(x) = A(x, x),
-    and A is the extremely fast-growing **Ackermann** function.
-    Since α (n) is the inverse of this function,
-    α (n) is less than 5 for all remotely practical values of n.
-    Thus, the amortized running time per operation
-    is effectively a small constant."""
-    assert x in self.sets and y in self.sets
-    # Since the original values x and y are not used afterwards,
-    # and what we actually need in two places of this algorithm are the corresponding DSNodes
-    # we set x and y to be respectively their DSNode counter-part.
-    x = self.sets[x]
-    y = self.sets[y]
-    x_root = self.find(x)
-    y_root = self.find(y)
-    # x and y are already joined.
-    if x_root == y_root:
-        return
-    # Exchanging the next pointers of x and y.
-    # This is needed in order to print the elements of a set in O(m) time,
-    # where m is the size of the same set.
-    # Check here: http://stackoverflow.com/a/22945492/3924118.
-    x.next, y.next = y.next, x.next
-    # x and y are not in the same set, therefore we merge them.
-    if x_root.rank < y_root.rank:
-        x_root.parent = y_root
-        return y_root
-    else:
-        y_root.parent = x_root
-        if x_root.rank == y_root.rank:
-            x_root.rank += 1
-        return x_root
-
-
-
- -
- -

Instance variables

-
-

var sets

- - - - -
-
- -
-
-
- -
-

class DSNode

- - -
- -
-
class DSNode:
-    def __init__(self, x, rank=0):
-        # This attribute can contain any hashable value.
-        self.value = x
-
-        # The rank of node x only changes in one specific union(x, y) case:
-        # when x is the representative of its set
-        # and the representative of the set where y resides has the same rank as x.
-        # In the DSForests implementation below, if a situation as just described occurs,
-        # then the x.rank is increased by 1.
-        self.rank = rank
-
-        # Reference to the representative of the set where this node resides
-        # Since DSForests actually implements a tree,
-        # self.parent is also the root of that tree.
-        self.parent = self
-
-        # Reference used to help printing all nodes
-        # belonging to the set to which this node belongs in O(m) time,
-        # where m is the size of the mentioned set.
-        self.next = self
-
-    def is_root(self):
-        """A DSNode x is a root or representative of a set
-        whenever its parent pointer points to himself.
-        Of course this is only true if x is already in a DSForests object."""
-        return self.parent == self
-
-    def __str__(self):
-        return str(self.value)
-
-    def __repr__(self):
-        if self.parent == self:
-            return "(value: {0}, rank: {1}, parent: self)".format(self.value, self.rank)
-        else:
-            return "(value: {0}, rank: {1}, parent: {2})".format(self.value, self.rank, self.parent)
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • DSNode
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self, x, rank=0)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, x, rank=0):
-    # This attribute can contain any hashable value.
-    self.value = x
-    # The rank of node x only changes in one specific union(x, y) case:
-    # when x is the representative of its set
-    # and the representative of the set where y resides has the same rank as x.
-    # In the DSForests implementation below, if a situation as just described occurs,
-    # then the x.rank is increased by 1.
-    self.rank = rank
-    # Reference to the representative of the set where this node resides
-    # Since DSForests actually implements a tree,
-    # self.parent is also the root of that tree.
-    self.parent = self
-    # Reference used to help printing all nodes
-    # belonging to the set to which this node belongs in O(m) time,
-    # where m is the size of the mentioned set.
-    self.next = self
-
-
-
- -
- - -
-
-

def is_root(

self)

-
- - - - -

A DSNode x is a root or representative of a set -whenever its parent pointer points to himself. -Of course this is only true if x is already in a DSForests object.

-
- -
-
def is_root(self):
-    """A DSNode x is a root or representative of a set
-    whenever its parent pointer points to himself.
-    Of course this is only true if x is already in a DSForests object."""
-    return self.parent == self
-
-
-
- -
- -

Instance variables

-
-

var next

- - - - -
-
- -
-
-

var parent

- - - - -
-
- -
-
-

var rank

- - - - -
-
- -
-
-

var value

- - - - -
-
- -
-
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/Graph.m.html b/docs/ands/ds/Graph.m.html deleted file mode 100644 index a26121e6..00000000 --- a/docs/ands/ds/Graph.m.html +++ /dev/null @@ -1,2766 +0,0 @@ - - - - - - ands.ds.Graph API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.Graph module

-

Author: Nelson Brochado

-

Creation: July, 2015

-

Last update: 01/09/16

-

Graph data structure using adjacency list representation.

-

You can represent a undirected graph by adding -each endpoint (node) of an edge to the adjacency list of the other endpoint. -For example, suppose we have node A and B. -To create an undirected graph composed of A and B, you need to do:

-
1. `A.add_adjacent_node(B)`
-
-2. `B.add_adjacent_node(A)`
-
-

You can also call add_undirected_edge(A, B) directly, -which does the work described above for you.

-

To create directed graphs, you can simply add the pointed node -to the adjacency list of the starting node. -You could also call add_directed_edge (see its docstrings).

-

This graph also contains some "utility" functions (reset_nodes) -that can be used in those same algorithms.

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-Author: Nelson Brochado
-
-Creation: July, 2015
-
-Last update: 01/09/16
-
-
-Graph data structure using adjacency list representation.
-
-You can represent a undirected graph by adding
-each endpoint (node) of an edge to the adjacency list of the other endpoint.
-For example, suppose we have node A and B.
-To create an undirected graph composed of A and B, you need to do:
-
-    1. `A.add_adjacent_node(B)`
-
-    2. `B.add_adjacent_node(A)`
-
-You can also call `add_undirected_edge(A, B)` directly,
-which does the work described above for you.
-
-To create directed graphs, you can simply add the pointed node
-to the adjacency list of the starting node.
-You could also call `add_directed_edge` (see its docstrings).
-
-This graph also contains some "utility" functions (reset_nodes)
-that can be used in those same algorithms.
-"""
-
-import operator
-import sys
-
-from tabulate import tabulate
-
-__all__ = ["Graph", "GraphNode", "WHITE", "BLACK", "GREY", "INFINITY", "NIL"]
-
-WHITE = "WHITE"
-BLACK = "BLACK"
-GREY = "GREY"
-INFINITY = sys.maxsize
-NIL = None
-
-
-class GraphNode:
-    """GraphNode for the `Graph` data structure defined in `Graph.py`.
-    Nodes contain many fields that are used in the 2 main graph algorithms:
-    - `bfs`
-    - `dfs`
-    These fields are for example `self.predecessor`, `self.distance`, etc.
-
-    Nodes keep also track of the edges of which they are the starting point.
-    These edges are represented as a list of 3 items:
-
-        edge = [self, pointed_node, edge_weight]
-
-    where:
-    - `self` is the reference to the node itself,
-    - `pointed_node` is the ending node of the edge
-    - `edge_weigh` is the eventual weight of the edge."""
-
-    def __init__(self, key, value=None):
-        self.key = key
-        self.value = value
-        # Attributes used mostly in algorithms for graphs, such as in bfs or dfs.
-        self.predecessor = NIL
-        self.distance = INFINITY
-        self.color = WHITE  # To keep track if a node has been or not visited yet.
-        self.adjacent_nodes = []
-        self.outgoing_edges = {}  # [(self, other), w]
-
-        self.cc = NIL
-        """This variable can be used to keep track of which
-        connected component this node in the graph belongs to.
-
-        For example, we could name the possible different
-        connected components using simply numbers:
-        the first connected component is 1, the second is 2,
-        the third is 3, and so on.
-
-        When searching for connected components,
-        we can use this variable to associate this node
-        with a connected component:
-        if we set it to 1, that would mean that this node
-        belongs to the connected component 1."""
-
-        # These variables are useful for dfs
-        # to keep track when a node is first visited
-        # and when we finish visiting it,
-        # and we start backtracking.
-        self.start = INFINITY
-        self.end = INFINITY
-
-        self.in_degree = 0
-        self.out_degree = 0
-
-    def get_adjacent_nodes(self) -> list:
-        """Returns the adjacent nodes to self."""
-        return self.adjacent_nodes
-
-    def get_outgoing_edges(self) -> dict:
-        """Returns a dictionary containing the edges outgoing from this node.
-        Edges are of the form (self, other): weight."""
-        return self.outgoing_edges
-
-    def add_adjacent_node(self, u, weight=0):
-        """Adds u as adjacent node to self.
-
-        Creates a directed edge from self to u."""
-        self.adjacent_nodes.append(u)
-        self.outgoing_edges[(self, u)] = weight
-        self.out_degree += 1
-        u.in_degree += 1
-
-    def remove_adjacent_node(self, u):
-        """Removes u's reference from the adjacent_nodes list of self.
-
-        It also removes the directed edge from self to u."""
-        self.adjacent_nodes.remove(u)
-        w = self.outgoing_edges.pop((self, u))
-        self.out_degree -= 1
-        u.in_degree -= 1
-        return w
-
-    def reset(self, clear_nodes=False):
-        """Make self assume the default attribute values.
-
-        If clear_nodes=True, then all connections with other nodes are removed.
-        """
-        self.color = WHITE
-        self.predecessor = NIL
-        self.distance = INFINITY
-        self.cc = NIL
-        self.start = INFINITY
-        self.end = INFINITY
-        if clear_nodes:
-            self.clear_adjacent_nodes()
-
-    def clear_adjacent_nodes(self):
-        self._fix_degrees_before_clearing()
-        self.adjacent_nodes.clear()
-        self.outgoing_edges.clear()
-
-    def _fix_degrees_before_clearing(self):
-        for n in self.adjacent_nodes:
-            n.in_degree -= 1
-        self.out_degree = 0
-
-    def get_str_repr_of_adj_nodes(self):
-        """Returns a string representing all the adjacent total_nodes."""
-        str_repr = "["
-        for node in self.get_adjacent_nodes():
-            str_repr += str(node.key) + ", "
-        return "[]" if not str_repr[:-2] else str_repr[:-2] + "]"
-
-    def _get_list_of_attributes(self):
-        a = [["Name", self.key],
-             ["Adjacent nodes", self.get_str_repr_of_adj_nodes()],
-             ["Color", self.color],
-             ["Distance", self.distance],
-             ["Connected component", self.cc],
-             ["Starting visit time", self.start],
-             ["Ending visit time", self.end]]
-
-        if self.predecessor is not None:
-            a.append(["Predecessor", self.predecessor.key])
-        else:
-            a.append(["Predecessor", NIL])
-
-        return a
-
-    def show(self):
-        """Prints the current status of this node."""
-        print(
-            tabulate(
-                self._get_list_of_attributes(),
-                tablefmt="fancy_grid",
-                headers=(
-                    "ATTRIBUTES",
-                    "VALUES")))
-
-    def __str__(self):
-        return str(self.key)
-
-    def __repr__(self):
-        return self.__str__() + " => " + self.get_str_repr_of_adj_nodes()
-
-    def __eq__(self, other):
-        return self.value == other.value and self.key == other.key
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def __hash__(self):
-        return hash(self.key) + id(self)
-
-
-class Graph:
-    """Graph data structure using adjacency list representation."""
-
-    def __init__(self, name="Adjacency List Representation"):
-        self.nodes = []
-        self.name = name
-
-        # Used with the dfs algorithm
-        # to create a topological sort
-        # of a directed acyclic graph (DAG).
-        self.topological_sort = []
-
-        # List of lists, each of them contains
-        # the nodes in different connected components.
-        self.connected_components = []
-
-    def add_node(self, u: GraphNode):
-        """Adds a new node to this graph."""
-        self.nodes.append(u)
-
-    def add_nodes(self, ls: list):
-        """Adds all GraphNode objects in ls to this Graph object."""
-        for u in ls:
-            self.nodes.append(u)
-
-    def reset_nodes(self):
-        """Make all nodes to assume the default attribute values."""
-        for u in self.nodes:
-            u.reset()
-
-    def add_undirected_edge(self, u: GraphNode, v: GraphNode, weight=0):
-        """Adds v to the adjacency list of u,
-        and adds also u to the adjacency list of v,
-        creating an "virtual" undirected edge.
-
-        If u is still not a node of this graph,
-        it is added first before establish any connection with v.
-        The same can be said for v."""
-        if u not in self.nodes:
-            self.add_node(u)
-
-        if v not in self.nodes:
-            self.add_node(v)
-
-        u.add_adjacent_node(v, weight)
-        v.add_adjacent_node(u, weight)
-
-    def add_directed_edge(self, u: GraphNode, v: GraphNode, weight=0):
-        """Adds v to the adjacency list of u.
-
-        If u is still not a node of this graph,
-        it is added first before establish any connection with v.
-        The same can be said for v."""
-        if u not in self.nodes:
-            self.add_node(u)
-
-        if v not in self.nodes:
-            self.add_node(v)
-
-        u.add_adjacent_node(v, weight)
-
-    @property
-    def edges(self) -> dict:
-        """Returns all the edges of this graph as dict,
-        where keys are tuples,
-        whose first elements are the starting points of the edges,
-        and whose second elements are the ending points of the edges.
-        Values of these dict represent the weight of the edges.
-
-        Note that if the graph is undirected,
-        and suppose there's an edge between node A and B,
-        then this function will return both triples:
-        (A, B, weight_AB) and (B, A, weight_BA).
-        """
-        edges = {}
-        for node in self.nodes:
-            edges.update(node.get_outgoing_edges())
-        return edges
-
-    def get_sorted_edges(self, reversed_order=False, undirected=False) -> "list of tuple":
-        """Sorts edges, by default, in increasing order."""
-        edges = self.edges
-        edges_list = sorted(edges.items(), key=operator.itemgetter(1), reverse=reversed_order)
-        if undirected:
-            return self._remove_double_edges(edges_list)
-        else:
-            return edges_list
-
-    @staticmethod
-    def _remove_double_edges(sorted_edges: list) -> list:
-        """If the graph is undirected,
-        we don't need to have a double representation of each edge.
-        This function serves to this purpose:
-        it removes double representations of the same edge,
-        in an undirected graph.
-
-        Note that sorted_edges should be the edges returned by get_sorted_edges."""
-        i = 0
-        while i < len(sorted_edges) - 1:
-            if sorted_edges[i][0][0] == sorted_edges[i + 1][0][1] and \
-                            sorted_edges[i][0][1] == sorted_edges[i + 1][0][0]:
-                sorted_edges.remove(sorted_edges[i])
-            else:
-                i += 1
-        return sorted_edges
-
-    def num_of_nodes(self):
-        return len(self.nodes)
-
-    def num_of_edges(self):
-        return len(self.edges)
-
-    def has_even_degree(self):
-        """Returns True, if all vertices have an even degree, False otherwise."""
-        for n in self.nodes:
-            if n.in_degree != n.out_degree:
-                return False
-        return True
-
-    @staticmethod
-    def weight(node1: GraphNode, node2: GraphNode):
-        """
-        Returns the weight of the edge connecting node1 to node2.
-
-        It returns None if there's no connection between node1 and node2.
-
-        Note that this method is static,
-        because it does not need any field of self
-        to check if node1 and node2 are connected somehow.
-
-        So, this method could also be called on total_nodes
-        belonging to other graphs, or even belonging to no graph."""
-        return node1.get_outgoing_edges().get((node1, node2))
-
-    # PRINT FUNCTIONS
-
-    def show_edges(self, sorted_edges=False):
-        ls = []
-        if sorted_edges:
-            for edge in self.get_sorted_edges():
-                ls.append([edge[0][0].key, edge[0][1].key, edge[1]])
-        else:
-            for edge in self.edges:
-                ls.append([edge[0].key, edge[1].key, self.edges[edge]])
-        print(tabulate(ls, tablefmt="fancy_grid", headers=("FROM", "TO", "WEIGHT")))
-
-    def show_connected_components(self):
-        """This function is useful after finding
-        the connected components of this graph.
-
-        You can find the connected components both with bfs or dfs."""
-        ls = []
-        for i, connected_component in enumerate(self.connected_components):
-            a = []
-            for j in connected_component:
-                a.append(j.key)
-            ls.append([i + 1, a])
-        print(tabulate(ls, tablefmt="fancy_grid", headers=("CC", "ELEMENTS")))
-
-    def show_nodes(self):
-        """Shows nodes' fields."""
-        for node in self.nodes:
-            node.show()
-
-    def __str__(self):
-        return str(self.nodes)
-
-    def __repr__(self):
-        return self.__str__()
-
-
-if __name__ == "__main__":
-    g = Graph()
-
-    a = GraphNode("A")
-    b = GraphNode("B")
-    c = GraphNode("C")
-
-    a.add_adjacent_node(b, 2)
-    a.add_adjacent_node(c, 3)
-
-    b.add_adjacent_node(b, 4)
-    b.add_adjacent_node(c, 10)
-
-    c.add_adjacent_node(b, 1)
-
-    # Testing add_node
-    g.add_node(a)
-
-    # Testing add_nodes
-    g.add_nodes((b, c))
-
-    # Testing show_edges (not sorted)
-    g.show_edges()
-
-    print("Edge's weight between a and b:", Graph.weight(a, b))
-    a.remove_adjacent_node(b)
-
-    # Testing show_nodes edges (sorted)
-    g.show_edges(sorted_edges=True)
-
-    # Testing the Graph.weight static function
-    print("Edge's weight between a and b:", Graph.weight(a, b))
-
-    # Testing the show_nodes function
-    print(g)
-
-    # Function show_connected_components should be tested with bfs or dfs.
-
-    g.show_nodes()
-    print("Out-degree of a:", a.out_degree)
-    print("In-degree of a:", a.in_degree)
-
-    print("Out-degree of b:", b.out_degree)
-    print("In-degree of b:", b.in_degree)
-
-    print("Out-degree of c:", c.out_degree)
-    print("In-degree of c:", c.in_degree)
-
-    c.remove_adjacent_node(b)
-    a.remove_adjacent_node(c)
-
-    print("Out-degree of c:", c.out_degree)
-    print("In-degree of c:", c.in_degree)
-
-    g.show_connected_components()
-
-
- -
- -
-

Module variables

-
-

var BLACK

- - -
-
- -
-
-

var GREY

- - -
-
- -
-
-

var INFINITY

- - -
-
- -
-
-

var NIL

- - -
-
- -
-
-

var WHITE

- - -
-
- -
- - -

Classes

- -
-

class Graph

- - -

Graph data structure using adjacency list representation.

-
- -
-
class Graph:
-    """Graph data structure using adjacency list representation."""
-
-    def __init__(self, name="Adjacency List Representation"):
-        self.nodes = []
-        self.name = name
-
-        # Used with the dfs algorithm
-        # to create a topological sort
-        # of a directed acyclic graph (DAG).
-        self.topological_sort = []
-
-        # List of lists, each of them contains
-        # the nodes in different connected components.
-        self.connected_components = []
-
-    def add_node(self, u: GraphNode):
-        """Adds a new node to this graph."""
-        self.nodes.append(u)
-
-    def add_nodes(self, ls: list):
-        """Adds all GraphNode objects in ls to this Graph object."""
-        for u in ls:
-            self.nodes.append(u)
-
-    def reset_nodes(self):
-        """Make all nodes to assume the default attribute values."""
-        for u in self.nodes:
-            u.reset()
-
-    def add_undirected_edge(self, u: GraphNode, v: GraphNode, weight=0):
-        """Adds v to the adjacency list of u,
-        and adds also u to the adjacency list of v,
-        creating an "virtual" undirected edge.
-
-        If u is still not a node of this graph,
-        it is added first before establish any connection with v.
-        The same can be said for v."""
-        if u not in self.nodes:
-            self.add_node(u)
-
-        if v not in self.nodes:
-            self.add_node(v)
-
-        u.add_adjacent_node(v, weight)
-        v.add_adjacent_node(u, weight)
-
-    def add_directed_edge(self, u: GraphNode, v: GraphNode, weight=0):
-        """Adds v to the adjacency list of u.
-
-        If u is still not a node of this graph,
-        it is added first before establish any connection with v.
-        The same can be said for v."""
-        if u not in self.nodes:
-            self.add_node(u)
-
-        if v not in self.nodes:
-            self.add_node(v)
-
-        u.add_adjacent_node(v, weight)
-
-    @property
-    def edges(self) -> dict:
-        """Returns all the edges of this graph as dict,
-        where keys are tuples,
-        whose first elements are the starting points of the edges,
-        and whose second elements are the ending points of the edges.
-        Values of these dict represent the weight of the edges.
-
-        Note that if the graph is undirected,
-        and suppose there's an edge between node A and B,
-        then this function will return both triples:
-        (A, B, weight_AB) and (B, A, weight_BA).
-        """
-        edges = {}
-        for node in self.nodes:
-            edges.update(node.get_outgoing_edges())
-        return edges
-
-    def get_sorted_edges(self, reversed_order=False, undirected=False) -> "list of tuple":
-        """Sorts edges, by default, in increasing order."""
-        edges = self.edges
-        edges_list = sorted(edges.items(), key=operator.itemgetter(1), reverse=reversed_order)
-        if undirected:
-            return self._remove_double_edges(edges_list)
-        else:
-            return edges_list
-
-    @staticmethod
-    def _remove_double_edges(sorted_edges: list) -> list:
-        """If the graph is undirected,
-        we don't need to have a double representation of each edge.
-        This function serves to this purpose:
-        it removes double representations of the same edge,
-        in an undirected graph.
-
-        Note that sorted_edges should be the edges returned by get_sorted_edges."""
-        i = 0
-        while i < len(sorted_edges) - 1:
-            if sorted_edges[i][0][0] == sorted_edges[i + 1][0][1] and \
-                            sorted_edges[i][0][1] == sorted_edges[i + 1][0][0]:
-                sorted_edges.remove(sorted_edges[i])
-            else:
-                i += 1
-        return sorted_edges
-
-    def num_of_nodes(self):
-        return len(self.nodes)
-
-    def num_of_edges(self):
-        return len(self.edges)
-
-    def has_even_degree(self):
-        """Returns True, if all vertices have an even degree, False otherwise."""
-        for n in self.nodes:
-            if n.in_degree != n.out_degree:
-                return False
-        return True
-
-    @staticmethod
-    def weight(node1: GraphNode, node2: GraphNode):
-        """
-        Returns the weight of the edge connecting node1 to node2.
-
-        It returns None if there's no connection between node1 and node2.
-
-        Note that this method is static,
-        because it does not need any field of self
-        to check if node1 and node2 are connected somehow.
-
-        So, this method could also be called on total_nodes
-        belonging to other graphs, or even belonging to no graph."""
-        return node1.get_outgoing_edges().get((node1, node2))
-
-    # PRINT FUNCTIONS
-
-    def show_edges(self, sorted_edges=False):
-        ls = []
-        if sorted_edges:
-            for edge in self.get_sorted_edges():
-                ls.append([edge[0][0].key, edge[0][1].key, edge[1]])
-        else:
-            for edge in self.edges:
-                ls.append([edge[0].key, edge[1].key, self.edges[edge]])
-        print(tabulate(ls, tablefmt="fancy_grid", headers=("FROM", "TO", "WEIGHT")))
-
-    def show_connected_components(self):
-        """This function is useful after finding
-        the connected components of this graph.
-
-        You can find the connected components both with bfs or dfs."""
-        ls = []
-        for i, connected_component in enumerate(self.connected_components):
-            a = []
-            for j in connected_component:
-                a.append(j.key)
-            ls.append([i + 1, a])
-        print(tabulate(ls, tablefmt="fancy_grid", headers=("CC", "ELEMENTS")))
-
-    def show_nodes(self):
-        """Shows nodes' fields."""
-        for node in self.nodes:
-            node.show()
-
-    def __str__(self):
-        return str(self.nodes)
-
-    def __repr__(self):
-        return self.__str__()
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • Graph
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self, name='Adjacency List Representation')

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, name="Adjacency List Representation"):
-    self.nodes = []
-    self.name = name
-    # Used with the dfs algorithm
-    # to create a topological sort
-    # of a directed acyclic graph (DAG).
-    self.topological_sort = []
-    # List of lists, each of them contains
-    # the nodes in different connected components.
-    self.connected_components = []
-
-
-
- -
- - -
-
-

def add_directed_edge(

self, u, v, weight=0)

-
- - - - -

Adds v to the adjacency list of u.

-

If u is still not a node of this graph, -it is added first before establish any connection with v. -The same can be said for v.

-
- -
-
def add_directed_edge(self, u: GraphNode, v: GraphNode, weight=0):
-    """Adds v to the adjacency list of u.
-    If u is still not a node of this graph,
-    it is added first before establish any connection with v.
-    The same can be said for v."""
-    if u not in self.nodes:
-        self.add_node(u)
-    if v not in self.nodes:
-        self.add_node(v)
-    u.add_adjacent_node(v, weight)
-
-
-
- -
- - -
-
-

def add_node(

self, u)

-
- - - - -

Adds a new node to this graph.

-
- -
-
def add_node(self, u: GraphNode):
-    """Adds a new node to this graph."""
-    self.nodes.append(u)
-
-
-
- -
- - -
-
-

def add_nodes(

self, ls)

-
- - - - -

Adds all GraphNode objects in ls to this Graph object.

-
- -
-
def add_nodes(self, ls: list):
-    """Adds all GraphNode objects in ls to this Graph object."""
-    for u in ls:
-        self.nodes.append(u)
-
-
-
- -
- - -
-
-

def add_undirected_edge(

self, u, v, weight=0)

-
- - - - -

Adds v to the adjacency list of u, -and adds also u to the adjacency list of v, -creating an "virtual" undirected edge.

-

If u is still not a node of this graph, -it is added first before establish any connection with v. -The same can be said for v.

-
- -
-
def add_undirected_edge(self, u: GraphNode, v: GraphNode, weight=0):
-    """Adds v to the adjacency list of u,
-    and adds also u to the adjacency list of v,
-    creating an "virtual" undirected edge.
-    If u is still not a node of this graph,
-    it is added first before establish any connection with v.
-    The same can be said for v."""
-    if u not in self.nodes:
-        self.add_node(u)
-    if v not in self.nodes:
-        self.add_node(v)
-    u.add_adjacent_node(v, weight)
-    v.add_adjacent_node(u, weight)
-
-
-
- -
- - -
-
-

def get_sorted_edges(

self, reversed_order=False, undirected=False)

-
- - - - -

Sorts edges, by default, in increasing order.

-
- -
-
def get_sorted_edges(self, reversed_order=False, undirected=False) -> "list of tuple":
-    """Sorts edges, by default, in increasing order."""
-    edges = self.edges
-    edges_list = sorted(edges.items(), key=operator.itemgetter(1), reverse=reversed_order)
-    if undirected:
-        return self._remove_double_edges(edges_list)
-    else:
-        return edges_list
-
-
-
- -
- - -
-
-

def has_even_degree(

self)

-
- - - - -

Returns True, if all vertices have an even degree, False otherwise.

-
- -
-
def has_even_degree(self):
-    """Returns True, if all vertices have an even degree, False otherwise."""
-    for n in self.nodes:
-        if n.in_degree != n.out_degree:
-            return False
-    return True
-
-
-
- -
- - -
-
-

def num_of_edges(

self)

-
- - - - -
- -
-
def num_of_edges(self):
-    return len(self.edges)
-
-
-
- -
- - -
-
-

def num_of_nodes(

self)

-
- - - - -
- -
-
def num_of_nodes(self):
-    return len(self.nodes)
-
-
-
- -
- - -
-
-

def reset_nodes(

self)

-
- - - - -

Make all nodes to assume the default attribute values.

-
- -
-
def reset_nodes(self):
-    """Make all nodes to assume the default attribute values."""
-    for u in self.nodes:
-        u.reset()
-
-
-
- -
- - -
-
-

def show_connected_components(

self)

-
- - - - -

This function is useful after finding -the connected components of this graph.

-

You can find the connected components both with bfs or dfs.

-
- -
-
def show_connected_components(self):
-    """This function is useful after finding
-    the connected components of this graph.
-    You can find the connected components both with bfs or dfs."""
-    ls = []
-    for i, connected_component in enumerate(self.connected_components):
-        a = []
-        for j in connected_component:
-            a.append(j.key)
-        ls.append([i + 1, a])
-    print(tabulate(ls, tablefmt="fancy_grid", headers=("CC", "ELEMENTS")))
-
-
-
- -
- - -
-
-

def show_edges(

self, sorted_edges=False)

-
- - - - -
- -
-
def show_edges(self, sorted_edges=False):
-    ls = []
-    if sorted_edges:
-        for edge in self.get_sorted_edges():
-            ls.append([edge[0][0].key, edge[0][1].key, edge[1]])
-    else:
-        for edge in self.edges:
-            ls.append([edge[0].key, edge[1].key, self.edges[edge]])
-    print(tabulate(ls, tablefmt="fancy_grid", headers=("FROM", "TO", "WEIGHT")))
-
-
-
- -
- - -
-
-

def show_nodes(

self)

-
- - - - -

Shows nodes' fields.

-
- -
-
def show_nodes(self):
-    """Shows nodes' fields."""
-    for node in self.nodes:
-        node.show()
-
-
-
- -
- - -
-
-

def weight(

node1, node2)

-
- - - - -

Returns the weight of the edge connecting node1 to node2.

-

It returns None if there's no connection between node1 and node2.

-

Note that this method is static, -because it does not need any field of self -to check if node1 and node2 are connected somehow.

-

So, this method could also be called on total_nodes -belonging to other graphs, or even belonging to no graph.

-
- -
-
@staticmethod
-def weight(node1: GraphNode, node2: GraphNode):
-    """
-    Returns the weight of the edge connecting node1 to node2.
-    It returns None if there's no connection between node1 and node2.
-    Note that this method is static,
-    because it does not need any field of self
-    to check if node1 and node2 are connected somehow.
-    So, this method could also be called on total_nodes
-    belonging to other graphs, or even belonging to no graph."""
-    return node1.get_outgoing_edges().get((node1, node2))
-
-
-
- -
- -

Instance variables

-
-

var connected_components

- - - - -
-
- -
-
-

var edges

- - - - -

Returns all the edges of this graph as dict, -where keys are tuples, -whose first elements are the starting points of the edges, -and whose second elements are the ending points of the edges. -Values of these dict represent the weight of the edges.

-

Note that if the graph is undirected, -and suppose there's an edge between node A and B, -then this function will return both triples: -(A, B, weight_AB) and (B, A, weight_BA).

-
-
- -
-
-

var name

- - - - -
-
- -
-
-

var nodes

- - - - -
-
- -
-
-

var topological_sort

- - - - -
-
- -
-
-
- -
-

class GraphNode

- - -

GraphNode for the Graph data structure defined in Graph.py. -Nodes contain many fields that are used in the 2 main graph algorithms: -- bfs -- dfs -These fields are for example self.predecessor, self.distance, etc.

-

Nodes keep also track of the edges of which they are the starting point. -These edges are represented as a list of 3 items:

-
edge = [self, pointed_node, edge_weight]
-
-

where: -- self is the reference to the node itself, -- pointed_node is the ending node of the edge -- edge_weigh is the eventual weight of the edge.

-
- -
-
class GraphNode:
-    """GraphNode for the `Graph` data structure defined in `Graph.py`.
-    Nodes contain many fields that are used in the 2 main graph algorithms:
-    - `bfs`
-    - `dfs`
-    These fields are for example `self.predecessor`, `self.distance`, etc.
-
-    Nodes keep also track of the edges of which they are the starting point.
-    These edges are represented as a list of 3 items:
-
-        edge = [self, pointed_node, edge_weight]
-
-    where:
-    - `self` is the reference to the node itself,
-    - `pointed_node` is the ending node of the edge
-    - `edge_weigh` is the eventual weight of the edge."""
-
-    def __init__(self, key, value=None):
-        self.key = key
-        self.value = value
-        # Attributes used mostly in algorithms for graphs, such as in bfs or dfs.
-        self.predecessor = NIL
-        self.distance = INFINITY
-        self.color = WHITE  # To keep track if a node has been or not visited yet.
-        self.adjacent_nodes = []
-        self.outgoing_edges = {}  # [(self, other), w]
-
-        self.cc = NIL
-        """This variable can be used to keep track of which
-        connected component this node in the graph belongs to.
-
-        For example, we could name the possible different
-        connected components using simply numbers:
-        the first connected component is 1, the second is 2,
-        the third is 3, and so on.
-
-        When searching for connected components,
-        we can use this variable to associate this node
-        with a connected component:
-        if we set it to 1, that would mean that this node
-        belongs to the connected component 1."""
-
-        # These variables are useful for dfs
-        # to keep track when a node is first visited
-        # and when we finish visiting it,
-        # and we start backtracking.
-        self.start = INFINITY
-        self.end = INFINITY
-
-        self.in_degree = 0
-        self.out_degree = 0
-
-    def get_adjacent_nodes(self) -> list:
-        """Returns the adjacent nodes to self."""
-        return self.adjacent_nodes
-
-    def get_outgoing_edges(self) -> dict:
-        """Returns a dictionary containing the edges outgoing from this node.
-        Edges are of the form (self, other): weight."""
-        return self.outgoing_edges
-
-    def add_adjacent_node(self, u, weight=0):
-        """Adds u as adjacent node to self.
-
-        Creates a directed edge from self to u."""
-        self.adjacent_nodes.append(u)
-        self.outgoing_edges[(self, u)] = weight
-        self.out_degree += 1
-        u.in_degree += 1
-
-    def remove_adjacent_node(self, u):
-        """Removes u's reference from the adjacent_nodes list of self.
-
-        It also removes the directed edge from self to u."""
-        self.adjacent_nodes.remove(u)
-        w = self.outgoing_edges.pop((self, u))
-        self.out_degree -= 1
-        u.in_degree -= 1
-        return w
-
-    def reset(self, clear_nodes=False):
-        """Make self assume the default attribute values.
-
-        If clear_nodes=True, then all connections with other nodes are removed.
-        """
-        self.color = WHITE
-        self.predecessor = NIL
-        self.distance = INFINITY
-        self.cc = NIL
-        self.start = INFINITY
-        self.end = INFINITY
-        if clear_nodes:
-            self.clear_adjacent_nodes()
-
-    def clear_adjacent_nodes(self):
-        self._fix_degrees_before_clearing()
-        self.adjacent_nodes.clear()
-        self.outgoing_edges.clear()
-
-    def _fix_degrees_before_clearing(self):
-        for n in self.adjacent_nodes:
-            n.in_degree -= 1
-        self.out_degree = 0
-
-    def get_str_repr_of_adj_nodes(self):
-        """Returns a string representing all the adjacent total_nodes."""
-        str_repr = "["
-        for node in self.get_adjacent_nodes():
-            str_repr += str(node.key) + ", "
-        return "[]" if not str_repr[:-2] else str_repr[:-2] + "]"
-
-    def _get_list_of_attributes(self):
-        a = [["Name", self.key],
-             ["Adjacent nodes", self.get_str_repr_of_adj_nodes()],
-             ["Color", self.color],
-             ["Distance", self.distance],
-             ["Connected component", self.cc],
-             ["Starting visit time", self.start],
-             ["Ending visit time", self.end]]
-
-        if self.predecessor is not None:
-            a.append(["Predecessor", self.predecessor.key])
-        else:
-            a.append(["Predecessor", NIL])
-
-        return a
-
-    def show(self):
-        """Prints the current status of this node."""
-        print(
-            tabulate(
-                self._get_list_of_attributes(),
-                tablefmt="fancy_grid",
-                headers=(
-                    "ATTRIBUTES",
-                    "VALUES")))
-
-    def __str__(self):
-        return str(self.key)
-
-    def __repr__(self):
-        return self.__str__() + " => " + self.get_str_repr_of_adj_nodes()
-
-    def __eq__(self, other):
-        return self.value == other.value and self.key == other.key
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def __hash__(self):
-        return hash(self.key) + id(self)
-
-
-
- - -
-

Ancestors (in MRO)

- -

Static methods

- -
-
-

def __init__(

self, key, value=None)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, key, value=None):
-    self.key = key
-    self.value = value
-    # Attributes used mostly in algorithms for graphs, such as in bfs or dfs.
-    self.predecessor = NIL
-    self.distance = INFINITY
-    self.color = WHITE  # To keep track if a node has been or not visited yet.
-    self.adjacent_nodes = []
-    self.outgoing_edges = {}  # [(self, other), w]
-    self.cc = NIL
-    """This variable can be used to keep track of which
-    connected component this node in the graph belongs to.
-    For example, we could name the possible different
-    connected components using simply numbers:
-    the first connected component is 1, the second is 2,
-    the third is 3, and so on.
-    When searching for connected components,
-    we can use this variable to associate this node
-    with a connected component:
-    if we set it to 1, that would mean that this node
-    belongs to the connected component 1."""
-    # These variables are useful for dfs
-    # to keep track when a node is first visited
-    # and when we finish visiting it,
-    # and we start backtracking.
-    self.start = INFINITY
-    self.end = INFINITY
-    self.in_degree = 0
-    self.out_degree = 0
-
-
-
- -
- - -
-
-

def add_adjacent_node(

self, u, weight=0)

-
- - - - -

Adds u as adjacent node to self.

-

Creates a directed edge from self to u.

-
- -
-
def add_adjacent_node(self, u, weight=0):
-    """Adds u as adjacent node to self.
-    Creates a directed edge from self to u."""
-    self.adjacent_nodes.append(u)
-    self.outgoing_edges[(self, u)] = weight
-    self.out_degree += 1
-    u.in_degree += 1
-
-
-
- -
- - -
-
-

def clear_adjacent_nodes(

self)

-
- - - - -
- -
-
def clear_adjacent_nodes(self):
-    self._fix_degrees_before_clearing()
-    self.adjacent_nodes.clear()
-    self.outgoing_edges.clear()
-
-
-
- -
- - -
-
-

def get_adjacent_nodes(

self)

-
- - - - -

Returns the adjacent nodes to self.

-
- -
-
def get_adjacent_nodes(self) -> list:
-    """Returns the adjacent nodes to self."""
-    return self.adjacent_nodes
-
-
-
- -
- - -
-
-

def get_outgoing_edges(

self)

-
- - - - -

Returns a dictionary containing the edges outgoing from this node. -Edges are of the form (self, other): weight.

-
- -
-
def get_outgoing_edges(self) -> dict:
-    """Returns a dictionary containing the edges outgoing from this node.
-    Edges are of the form (self, other): weight."""
-    return self.outgoing_edges
-
-
-
- -
- - -
-
-

def get_str_repr_of_adj_nodes(

self)

-
- - - - -

Returns a string representing all the adjacent total_nodes.

-
- -
-
def get_str_repr_of_adj_nodes(self):
-    """Returns a string representing all the adjacent total_nodes."""
-    str_repr = "["
-    for node in self.get_adjacent_nodes():
-        str_repr += str(node.key) + ", "
-    return "[]" if not str_repr[:-2] else str_repr[:-2] + "]"
-
-
-
- -
- - -
-
-

def remove_adjacent_node(

self, u)

-
- - - - -

Removes u's reference from the adjacent_nodes list of self.

-

It also removes the directed edge from self to u.

-
- -
-
def remove_adjacent_node(self, u):
-    """Removes u's reference from the adjacent_nodes list of self.
-    It also removes the directed edge from self to u."""
-    self.adjacent_nodes.remove(u)
-    w = self.outgoing_edges.pop((self, u))
-    self.out_degree -= 1
-    u.in_degree -= 1
-    return w
-
-
-
- -
- - -
-
-

def reset(

self, clear_nodes=False)

-
- - - - -

Make self assume the default attribute values.

-

If clear_nodes=True, then all connections with other nodes are removed.

-
- -
-
def reset(self, clear_nodes=False):
-    """Make self assume the default attribute values.
-    If clear_nodes=True, then all connections with other nodes are removed.
-    """
-    self.color = WHITE
-    self.predecessor = NIL
-    self.distance = INFINITY
-    self.cc = NIL
-    self.start = INFINITY
-    self.end = INFINITY
-    if clear_nodes:
-        self.clear_adjacent_nodes()
-
-
-
- -
- - -
-
-

def show(

self)

-
- - - - -

Prints the current status of this node.

-
- -
-
def show(self):
-    """Prints the current status of this node."""
-    print(
-        tabulate(
-            self._get_list_of_attributes(),
-            tablefmt="fancy_grid",
-            headers=(
-                "ATTRIBUTES",
-                "VALUES")))
-
-
-
- -
- -

Instance variables

-
-

var adjacent_nodes

- - - - -
-
- -
-
-

var cc

- - - - -

This variable can be used to keep track of which -connected component this node in the graph belongs to.

-

For example, we could name the possible different -connected components using simply numbers: -the first connected component is 1, the second is 2, -the third is 3, and so on.

-

When searching for connected components, -we can use this variable to associate this node -with a connected component: -if we set it to 1, that would mean that this node -belongs to the connected component 1.

-
-
- -
-
-

var color

- - - - -
-
- -
-
-

var distance

- - - - -
-
- -
-
-

var end

- - - - -
-
- -
-
-

var in_degree

- - - - -
-
- -
-
-

var key

- - - - -
-
- -
-
-

var out_degree

- - - - -
-
- -
-
-

var outgoing_edges

- - - - -
-
- -
-
-

var predecessor

- - - - -
-
- -
-
-

var start

- - - - -
-
- -
-
-

var value

- - - - -
-
- -
-
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/HashTable.m.html b/docs/ands/ds/HashTable.m.html deleted file mode 100644 index c0196a69..00000000 --- a/docs/ands/ds/HashTable.m.html +++ /dev/null @@ -1,1805 +0,0 @@ - - - - - - ands.ds.HashTable API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.HashTable module

-

Meta info

-

Author: Nelson Brochado

-

Created: 01/06/2015

-

Updated: 21/02/2016

-

Description

-

Hash table that re-sizes if no more slot is available. -The process of re-sizing doubles the current capacity of the hash table each time (for now). -It uses linear probing when there's a collision. -The hash function uses both the Python's built-in hash function and the % operator. -You can access and put an item in the hash table by using the same convinient notation -that is used by the Python's standard dict class, that is:

-
h = HashTable()
-h[12] = 3
-print(h[12])
-
-

References

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-# Meta info
-
-Author: Nelson Brochado
-
-Created: 01/06/2015
-
-Updated: 21/02/2016
-
-# Description
-
-Hash table that re-sizes if no more slot is available.
-The process of re-sizing doubles the current capacity of the hash table each time (for now).
-It uses [linear probing](https://en.wikipedia.org/wiki/Linear_probing) when there's a collision.
-The hash function uses both the Python's built-in `hash` function and the `%` operator.
-You can access and put an item in the hash table by using the same convinient notation
-that is used by the Python's standard `dict` class, that is:
-
-    h = HashTable()
-    h[12] = 3
-    print(h[12])
-
-# References
-
-- [http://interactivepython.org/runestone/static/pythonds/SortSearch/Hashing.html](http://interactivepython.org/runestone/static/pythonds/SortSearch/Hashing.html)
-- [http://stackoverflow.com/questions/279539/best-way-to-remove-an-entry-from-a-hash-table](http://stackoverflow.com/questions/279539/best-way-to-remove-an-entry-from-a-hash-table)
-
-"""
-
-from tabulate import tabulate
-
-__all__ = ["HashTable", "has_duplicates", "find_duplicates"]
-
-
-class HashTable:
-    def __init__(self, capacity: int = 11):
-        self.n = capacity
-        self.keys = [None] * self.n
-        self.values = [None] * self.n
-
-    # HASH FUNCTIONS
-
-    def hash_code(self, key, size: int) -> int:
-        """Returns a hash code (an int) between 0 and `size` (excluded).
-
-        `size` must be the size of the buffer based on which
-        this function should return a hash value."""
-        return hash(key) % size
-
-    def rehash(self, old_hash: int, size: int) -> int:
-        """Returns a new hash value based on the previous one called `old_hash`.
-
-        `size` must be the size of the buffer based on which
-        we want to have a new hash value from the old hash value."""
-        return (old_hash + 1) % size
-
-    # PUT
-
-    def put(self, key: object, value: object):
-        """Inserts the pair `key`-`value` in this map.
-
-        If `key` is `None`, a `TypeError` is raised,
-        because keys cannot be `None`."""
-        if key is None:
-            raise TypeError("key cannot be None.")
-
-        assert not has_duplicates(self.keys)
-        a = self._put(key, value, self.n)
-        assert not has_duplicates(self.keys)
-        return a
-
-    def _put(self, key, value, size):
-        assert not has_duplicates(self.keys), "precondition in _put"
-
-        hash_value = self.hash_code(key, size)
-
-        # No need to allocate new space.
-        if self.keys[hash_value] is None:
-            self.keys[hash_value] = key
-            self.values[hash_value] = value
-
-        # If self already contains key, then its value is overridden.
-        elif self.keys[hash_value] == key:
-            self.values[hash_value] = value
-
-        # Collision: there's already a key-value pair
-        # at the slot dedicated to this key-value pair,
-        # according to the self.hash_code function.
-        # We need to rehash, i.e. find another slot for this key-value pair.
-        else:
-            next_slot = self.rehash(hash_value, size)
-            rehashed = False
-
-            while self.keys[next_slot] is not None and self.keys[
-                next_slot] != key:
-
-                next_slot = self.rehash(next_slot, size)
-
-                # Allocate new buffer of length len(self.keys)*2 + 1
-                if next_slot == hash_value:
-                    rehashed = True
-
-                    keys = self.keys
-                    values = self.values
-
-                    new_size = len(self.keys) * 2 + 1
-                    self.keys = [None] * new_size
-                    self.values = [None] * new_size
-
-                    # Reashing and putting all elements again
-                    # Note that the following call to self._put
-                    # will never reach this statement
-                    # because there will be slots available
-                    for k in keys:
-                        v = self._get(k, keys, values, self.n)
-                        self._put(k, v, new_size)
-
-                    self._put(key, value, new_size)
-                    self.n = new_size
-
-            # We exited the loop either because
-            # we have found a free slot or a slot containing our key.
-            # (and not after having re-sized the table!)
-            if not rehashed:
-                if self.keys[next_slot] is None:
-                    self.keys[next_slot] = key
-                    self.values[next_slot] = value
-                else:
-                    assert self.keys[next_slot] == key
-                    self.values[next_slot] = value
-
-        if has_duplicates(self.keys):
-            find_duplicates(self.keys)
-
-        assert not has_duplicates(self.keys), "postcondition in _put"
-
-    def get(self, key):
-        """Returns the value associated with `key`.
-        It returns `None` if there's no value associated with `key`.
-
-        If `key` is `None`, a `TypeError` is raised,
-        because keys cannot be None."""
-        if key is None:
-            raise TypeError("key cannot be None.")
-        return self._get(key, self.keys, self.values, self.n)
-
-    def _get(self, key, keys, values, size):
-        assert not has_duplicates(keys), "precondition in _get"
-
-        hash_value = self.hash_code(key, size)
-
-        data = None
-        stop = False
-        found = False
-        position = hash_value
-
-        while keys[position] is not None and not found and not stop:
-
-            if keys[position] == key:
-                found = True
-                data = values[position]
-            else:
-                # Find a new possible position by rehashing
-                position = self.rehash(position, size)
-
-                # We are at the initial slot,
-                # and thus nothing was found.
-                if position == hash_value:
-                    stop = True
-
-        assert not has_duplicates(keys), "postcondition _get"
-        return data
-
-    def __getitem__(self, key):
-        return self.get(key)
-
-    def __setitem__(self, key, value):
-        self.put(key, value)
-
-    def delete(self, key):
-        """Deletes the mapping (if any) between `key`
-        and its corresponding associated value.
-        If there's no mapping, `None` is returned."""
-        try:
-            i = self.keys.index(key)
-            v = self.values[i]
-            self.keys[i] = self.values[i] = None
-            return v
-        except ValueError:
-            return None
-
-    @property
-    def size(self):
-        """Returns the number of pairs key-value in this map."""
-        assert len(self.keys) == len(self.values) == self.n
-        return sum(k is not None for k in self.keys)
-
-    @property
-    def capacity(self):
-        """Returns the size of the internal buffers that store the keys and the values."""
-        assert len(self.keys) == len(self.values) == self.n
-        return len(self.keys)
-
-    def show(self):
-        """Pretty-prints (using `tabulate.tabulate()`) this table."""
-        c = 0
-        data = []
-        for i in range(len(self.keys)):
-            if self.keys[i] is not None:
-                c += 1
-                data.append([c, self.keys[i], self.values[i]])
-        print(tabulate(data, headers=["#", "Keys", "Values"], tablefmt="grid"))
-
-    def __str__(self):
-        return str([(k, v)
-                    for k, v in zip(self.keys, self.values) if k is not None])
-
-    def __repr__(self):
-        return self.__str__()
-
-
-def has_duplicates(ls):
-    ls = [item for item in ls if item is not None]
-    return len(ls) != len(set(ls))
-
-
-def find_duplicates(ls):
-    return [item for item, count in collections.Counter(
-        ls).items() if (count > 1 and item is not None)]
-
-
- -
- -
- -

Functions

- -
-
-

def find_duplicates(

ls)

-
- - - - -
- -
-
def find_duplicates(ls):
-    return [item for item, count in collections.Counter(
-        ls).items() if (count > 1 and item is not None)]
-
-
-
- -
- - -
-
-

def has_duplicates(

ls)

-
- - - - -
- -
-
def has_duplicates(ls):
-    ls = [item for item in ls if item is not None]
-    return len(ls) != len(set(ls))
-
-
-
- -
- - -

Classes

- -
-

class HashTable

- - -
- -
-
class HashTable:
-    def __init__(self, capacity: int = 11):
-        self.n = capacity
-        self.keys = [None] * self.n
-        self.values = [None] * self.n
-
-    # HASH FUNCTIONS
-
-    def hash_code(self, key, size: int) -> int:
-        """Returns a hash code (an int) between 0 and `size` (excluded).
-
-        `size` must be the size of the buffer based on which
-        this function should return a hash value."""
-        return hash(key) % size
-
-    def rehash(self, old_hash: int, size: int) -> int:
-        """Returns a new hash value based on the previous one called `old_hash`.
-
-        `size` must be the size of the buffer based on which
-        we want to have a new hash value from the old hash value."""
-        return (old_hash + 1) % size
-
-    # PUT
-
-    def put(self, key: object, value: object):
-        """Inserts the pair `key`-`value` in this map.
-
-        If `key` is `None`, a `TypeError` is raised,
-        because keys cannot be `None`."""
-        if key is None:
-            raise TypeError("key cannot be None.")
-
-        assert not has_duplicates(self.keys)
-        a = self._put(key, value, self.n)
-        assert not has_duplicates(self.keys)
-        return a
-
-    def _put(self, key, value, size):
-        assert not has_duplicates(self.keys), "precondition in _put"
-
-        hash_value = self.hash_code(key, size)
-
-        # No need to allocate new space.
-        if self.keys[hash_value] is None:
-            self.keys[hash_value] = key
-            self.values[hash_value] = value
-
-        # If self already contains key, then its value is overridden.
-        elif self.keys[hash_value] == key:
-            self.values[hash_value] = value
-
-        # Collision: there's already a key-value pair
-        # at the slot dedicated to this key-value pair,
-        # according to the self.hash_code function.
-        # We need to rehash, i.e. find another slot for this key-value pair.
-        else:
-            next_slot = self.rehash(hash_value, size)
-            rehashed = False
-
-            while self.keys[next_slot] is not None and self.keys[
-                next_slot] != key:
-
-                next_slot = self.rehash(next_slot, size)
-
-                # Allocate new buffer of length len(self.keys)*2 + 1
-                if next_slot == hash_value:
-                    rehashed = True
-
-                    keys = self.keys
-                    values = self.values
-
-                    new_size = len(self.keys) * 2 + 1
-                    self.keys = [None] * new_size
-                    self.values = [None] * new_size
-
-                    # Reashing and putting all elements again
-                    # Note that the following call to self._put
-                    # will never reach this statement
-                    # because there will be slots available
-                    for k in keys:
-                        v = self._get(k, keys, values, self.n)
-                        self._put(k, v, new_size)
-
-                    self._put(key, value, new_size)
-                    self.n = new_size
-
-            # We exited the loop either because
-            # we have found a free slot or a slot containing our key.
-            # (and not after having re-sized the table!)
-            if not rehashed:
-                if self.keys[next_slot] is None:
-                    self.keys[next_slot] = key
-                    self.values[next_slot] = value
-                else:
-                    assert self.keys[next_slot] == key
-                    self.values[next_slot] = value
-
-        if has_duplicates(self.keys):
-            find_duplicates(self.keys)
-
-        assert not has_duplicates(self.keys), "postcondition in _put"
-
-    def get(self, key):
-        """Returns the value associated with `key`.
-        It returns `None` if there's no value associated with `key`.
-
-        If `key` is `None`, a `TypeError` is raised,
-        because keys cannot be None."""
-        if key is None:
-            raise TypeError("key cannot be None.")
-        return self._get(key, self.keys, self.values, self.n)
-
-    def _get(self, key, keys, values, size):
-        assert not has_duplicates(keys), "precondition in _get"
-
-        hash_value = self.hash_code(key, size)
-
-        data = None
-        stop = False
-        found = False
-        position = hash_value
-
-        while keys[position] is not None and not found and not stop:
-
-            if keys[position] == key:
-                found = True
-                data = values[position]
-            else:
-                # Find a new possible position by rehashing
-                position = self.rehash(position, size)
-
-                # We are at the initial slot,
-                # and thus nothing was found.
-                if position == hash_value:
-                    stop = True
-
-        assert not has_duplicates(keys), "postcondition _get"
-        return data
-
-    def __getitem__(self, key):
-        return self.get(key)
-
-    def __setitem__(self, key, value):
-        self.put(key, value)
-
-    def delete(self, key):
-        """Deletes the mapping (if any) between `key`
-        and its corresponding associated value.
-        If there's no mapping, `None` is returned."""
-        try:
-            i = self.keys.index(key)
-            v = self.values[i]
-            self.keys[i] = self.values[i] = None
-            return v
-        except ValueError:
-            return None
-
-    @property
-    def size(self):
-        """Returns the number of pairs key-value in this map."""
-        assert len(self.keys) == len(self.values) == self.n
-        return sum(k is not None for k in self.keys)
-
-    @property
-    def capacity(self):
-        """Returns the size of the internal buffers that store the keys and the values."""
-        assert len(self.keys) == len(self.values) == self.n
-        return len(self.keys)
-
-    def show(self):
-        """Pretty-prints (using `tabulate.tabulate()`) this table."""
-        c = 0
-        data = []
-        for i in range(len(self.keys)):
-            if self.keys[i] is not None:
-                c += 1
-                data.append([c, self.keys[i], self.values[i]])
-        print(tabulate(data, headers=["#", "Keys", "Values"], tablefmt="grid"))
-
-    def __str__(self):
-        return str([(k, v)
-                    for k, v in zip(self.keys, self.values) if k is not None])
-
-    def __repr__(self):
-        return self.__str__()
-
-
-
- - -
-

Ancestors (in MRO)

- -

Static methods

- -
-
-

def __init__(

self, capacity=11)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, capacity: int = 11):
-    self.n = capacity
-    self.keys = [None] * self.n
-    self.values = [None] * self.n
-
-
-
- -
- - -
-
-

def delete(

self, key)

-
- - - - -

Deletes the mapping (if any) between key -and its corresponding associated value. -If there's no mapping, None is returned.

-
- -
-
def delete(self, key):
-    """Deletes the mapping (if any) between `key`
-    and its corresponding associated value.
-    If there's no mapping, `None` is returned."""
-    try:
-        i = self.keys.index(key)
-        v = self.values[i]
-        self.keys[i] = self.values[i] = None
-        return v
-    except ValueError:
-        return None
-
-
-
- -
- - -
-
-

def get(

self, key)

-
- - - - -

Returns the value associated with key. -It returns None if there's no value associated with key.

-

If key is None, a TypeError is raised, -because keys cannot be None.

-
- -
-
def get(self, key):
-    """Returns the value associated with `key`.
-    It returns `None` if there's no value associated with `key`.
-    If `key` is `None`, a `TypeError` is raised,
-    because keys cannot be None."""
-    if key is None:
-        raise TypeError("key cannot be None.")
-    return self._get(key, self.keys, self.values, self.n)
-
-
-
- -
- - -
-
-

def hash_code(

self, key, size)

-
- - - - -

Returns a hash code (an int) between 0 and size (excluded).

-

size must be the size of the buffer based on which -this function should return a hash value.

-
- -
-
def hash_code(self, key, size: int) -> int:
-    """Returns a hash code (an int) between 0 and `size` (excluded).
-    `size` must be the size of the buffer based on which
-    this function should return a hash value."""
-    return hash(key) % size
-
-
-
- -
- - -
-
-

def put(

self, key, value)

-
- - - - -

Inserts the pair key-value in this map.

-

If key is None, a TypeError is raised, -because keys cannot be None.

-
- -
-
def put(self, key: object, value: object):
-    """Inserts the pair `key`-`value` in this map.
-    If `key` is `None`, a `TypeError` is raised,
-    because keys cannot be `None`."""
-    if key is None:
-        raise TypeError("key cannot be None.")
-    assert not has_duplicates(self.keys)
-    a = self._put(key, value, self.n)
-    assert not has_duplicates(self.keys)
-    return a
-
-
-
- -
- - -
-
-

def rehash(

self, old_hash, size)

-
- - - - -

Returns a new hash value based on the previous one called old_hash.

-

size must be the size of the buffer based on which -we want to have a new hash value from the old hash value.

-
- -
-
def rehash(self, old_hash: int, size: int) -> int:
-    """Returns a new hash value based on the previous one called `old_hash`.
-    `size` must be the size of the buffer based on which
-    we want to have a new hash value from the old hash value."""
-    return (old_hash + 1) % size
-
-
-
- -
- - -
-
-

def show(

self)

-
- - - - -

Pretty-prints (using tabulate.tabulate()) this table.

-
- -
-
def show(self):
-    """Pretty-prints (using `tabulate.tabulate()`) this table."""
-    c = 0
-    data = []
-    for i in range(len(self.keys)):
-        if self.keys[i] is not None:
-            c += 1
-            data.append([c, self.keys[i], self.values[i]])
-    print(tabulate(data, headers=["#", "Keys", "Values"], tablefmt="grid"))
-
-
-
- -
- -

Instance variables

-
-

var capacity

- - - - -

Returns the size of the internal buffers that store the keys and the values.

-
-
- -
-
-

var keys

- - - - -
-
- -
-
-

var n

- - - - -
-
- -
-
-

var size

- - - - -

Returns the number of pairs key-value in this map.

-
-
- -
-
-

var values

- - - - -
-
- -
-
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/MaxHeap.m.html b/docs/ands/ds/MaxHeap.m.html deleted file mode 100644 index b10a43a4..00000000 --- a/docs/ands/ds/MaxHeap.m.html +++ /dev/null @@ -1,2324 +0,0 @@ - - - - - - ands.ds.MaxHeap API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.MaxHeap module

-

Meta info

-

Author: Nelson Brochado

-

Created: 15/02/2016

-

Updated: 05/02/2017

-

Description

-

Mirror-class to the MinHeap class. -For more info, see the introductory doc-strings of the file MinHeap.py.

-

References

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-# Meta info
-
-Author: Nelson Brochado
-
-Created: 15/02/2016
-
-Updated: 05/02/2017
-
-# Description
-
-Mirror-class to the MinHeap class.
-For more info, see the introductory doc-strings of the file [`MinHeap.py`](MinHeap.py).
-
-# References
-
-- [https://en.wikipedia.org/wiki/Binary_heap](https://en.wikipedia.org/wiki/Binary_heap)
-- Slides by prof. A. Carzaniga
-- Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS
-
-"""
-
-from ands.ds.heap import BinaryHeap, HeapNode
-
-__all__ = ["MaxHeap", "is_max_heap"]
-
-
-class MaxHeap(BinaryHeap):
-    def __init__(self, ls=None):
-        BinaryHeap.__init__(self, ls)
-
-    def push_down(self, i: int) -> None:
-        """'Max-heapify' this max-heap starting from index `i`.
-
-        **Time Complexity:** O(log2 n)."""
-        m = i
-        l = self.left_index(i)
-        r = self.right_index(i)
-
-        if l != -1 and self.heap[l] > self.heap[m]:
-            m = l
-        if r != -1 and self.heap[r] > self.heap[m]:
-            m = r
-
-        if m != i:
-            self.swap(m, i)
-            self.push_down(m)
-
-    def push_up(self, i: int) -> None:
-        """Pushes up the node at index `i`.
-
-        Note that this operation only happens
-        if the node at index `i` is greater than its parent.
-
-        **Time Complexity:** O(log2 n)."""
-        c = i  # current index
-        p = self.parent_index(i)
-
-        if p != -1 and self.heap[c] > self.heap[p]:
-            c = p
-
-        if c != i:
-            self.swap(c, i)
-            self.push_up(c)
-
-    def find_max(self) -> HeapNode:
-        """Returns (without removing) the greatest element in this max-heap.
-
-        **Time Complexity:** O(1)."""
-        return self.heap[0] if not self.is_empty() else None
-
-    def remove_max(self) -> HeapNode:
-        """Removes and returns the greatest element in this max-heap.
-
-        **Time Complexity:** O(log2 n),
-        if removing the last element of a list is a constant-time operation."""
-        if not self.is_empty():
-            self.swap(0, self.size() - 1)
-            m = self.heap.pop()
-            if not self.is_empty():
-                self.push_down(0)
-            return m
-
-    def delete(self, i: int) -> HeapNode:
-        """Deletes and returns the `HeapNode` object at index `i`.
-
-        `IndexError` is raised if `i` is not a valid index.
-
-        Implementation based on:
-        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
-
-        **Time Complexity:** O(log2 h),
-        where `h` is the number of nodes rooted at `i`."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        if i == self.size() - 1:
-            return self.heap.pop()
-        self.swap(i, self.size() - 1)
-        d = self.heap.pop()
-        self.push_down(i)
-        return d
-
-    def replace(self, i: int, x) -> HeapNode:
-        """Replaces element at index `i` with `x`.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, then a `HeapNode` object
-        first created to represent `x`.
-
-        1. If `x == self.heap[i]`,
-        then just replace `self.heap[i]` with `x`.
-
-        2. Else if `x > self.heap[i]`,
-        then push_up(index).
-
-        3. Else `x < self.heap[i]`,
-        then call `self.push_down(i)`.
-
-        Returns the previous `HeapNode` object at `i`.
-
-        **Time Complexity:** O(log2 n)."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, HeapNode):
-            x = HeapNode(x)
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-
-        c = self.heap[i]
-        self.heap[i] = x
-
-        if x < c:
-            self.push_down(i)
-        elif x > c:
-            self.push_up(i)
-
-        return c
-
-
-def is_max_heap(h) -> bool:
-    """Returns `True` if `h` is a valid `MaxHeap`. `False` otherwise."""
-    if not isinstance(h, MaxHeap):
-        return False
-    if h.heap:
-        for item in h.heap:
-            if not isinstance(item, HeapNode):
-                return False
-        for i, item in enumerate(h.heap):
-            l = h.left_index(i)
-            r = h.right_index(i)
-            if r != -1 and l == -1:
-                return False
-            if l != -1 and item < h.heap[l]:
-                return False
-            if r != -1 and item < h.heap[r]:
-                return False
-    return True
-
-
- -
- -
- -

Functions

- -
-
-

def is_max_heap(

h)

-
- - - - -

Returns True if h is a valid MaxHeap. False otherwise.

-
- -
-
def is_max_heap(h) -> bool:
-    """Returns `True` if `h` is a valid `MaxHeap`. `False` otherwise."""
-    if not isinstance(h, MaxHeap):
-        return False
-    if h.heap:
-        for item in h.heap:
-            if not isinstance(item, HeapNode):
-                return False
-        for i, item in enumerate(h.heap):
-            l = h.left_index(i)
-            r = h.right_index(i)
-            if r != -1 and l == -1:
-                return False
-            if l != -1 and item < h.heap[l]:
-                return False
-            if r != -1 and item < h.heap[r]:
-                return False
-    return True
-
-
-
- -
- - -

Classes

- -
-

class MaxHeap

- - -

Abstract class to represent binary heaps.

-

MinHeap, MaxHeap and MinMaxHeap all derive from this class.

-
- -
-
class MaxHeap(BinaryHeap):
-    def __init__(self, ls=None):
-        BinaryHeap.__init__(self, ls)
-
-    def push_down(self, i: int) -> None:
-        """'Max-heapify' this max-heap starting from index `i`.
-
-        **Time Complexity:** O(log2 n)."""
-        m = i
-        l = self.left_index(i)
-        r = self.right_index(i)
-
-        if l != -1 and self.heap[l] > self.heap[m]:
-            m = l
-        if r != -1 and self.heap[r] > self.heap[m]:
-            m = r
-
-        if m != i:
-            self.swap(m, i)
-            self.push_down(m)
-
-    def push_up(self, i: int) -> None:
-        """Pushes up the node at index `i`.
-
-        Note that this operation only happens
-        if the node at index `i` is greater than its parent.
-
-        **Time Complexity:** O(log2 n)."""
-        c = i  # current index
-        p = self.parent_index(i)
-
-        if p != -1 and self.heap[c] > self.heap[p]:
-            c = p
-
-        if c != i:
-            self.swap(c, i)
-            self.push_up(c)
-
-    def find_max(self) -> HeapNode:
-        """Returns (without removing) the greatest element in this max-heap.
-
-        **Time Complexity:** O(1)."""
-        return self.heap[0] if not self.is_empty() else None
-
-    def remove_max(self) -> HeapNode:
-        """Removes and returns the greatest element in this max-heap.
-
-        **Time Complexity:** O(log2 n),
-        if removing the last element of a list is a constant-time operation."""
-        if not self.is_empty():
-            self.swap(0, self.size() - 1)
-            m = self.heap.pop()
-            if not self.is_empty():
-                self.push_down(0)
-            return m
-
-    def delete(self, i: int) -> HeapNode:
-        """Deletes and returns the `HeapNode` object at index `i`.
-
-        `IndexError` is raised if `i` is not a valid index.
-
-        Implementation based on:
-        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
-
-        **Time Complexity:** O(log2 h),
-        where `h` is the number of nodes rooted at `i`."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        if i == self.size() - 1:
-            return self.heap.pop()
-        self.swap(i, self.size() - 1)
-        d = self.heap.pop()
-        self.push_down(i)
-        return d
-
-    def replace(self, i: int, x) -> HeapNode:
-        """Replaces element at index `i` with `x`.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, then a `HeapNode` object
-        first created to represent `x`.
-
-        1. If `x == self.heap[i]`,
-        then just replace `self.heap[i]` with `x`.
-
-        2. Else if `x > self.heap[i]`,
-        then push_up(index).
-
-        3. Else `x < self.heap[i]`,
-        then call `self.push_down(i)`.
-
-        Returns the previous `HeapNode` object at `i`.
-
-        **Time Complexity:** O(log2 n)."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, HeapNode):
-            x = HeapNode(x)
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-
-        c = self.heap[i]
-        self.heap[i] = x
-
-        if x < c:
-            self.push_down(i)
-        elif x > c:
-            self.push_up(i)
-
-        return c
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • MaxHeap
  • -
  • ands.ds.heap.BinaryHeap
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self, ls=None)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, ls=None):
-    BinaryHeap.__init__(self, ls)
-
-
-
- -
- - -
-
-

def add(

self, x)

-
- - - - -

Adds x to this heap.

-

In practice, it places x at an available leaf, -then "bubbles up" from there, -in order to maintain the heap property.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(log2 n).

-
- -
-
def add(self, x) -> None:
-    """Adds `x` to this heap.
-    In practice, it places `x` at an available leaf,
-    then "bubbles up" from there,
-    in order to maintain the heap property.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(log2 n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    self.heap.append(x)
-    if self.size() > 1:
-        self.push_up(self.size() - 1)
-
-
-
- -
- - -
-
-

def build_heap(

self)

-
- - - - -

Builds the heap data structure from self.heap.

-
- -
-
def build_heap(self) -> list:
-    """Builds the heap data structure from `self.heap`."""
-    if self.heap:
-        for index in range(len(self.heap) // 2, -1, -1):
-            self.push_down(index)
-    return self.heap
-
-
-
- -
- - -
-
-

def clear(

self)

-
- - - - -

Clears all nodes from this heap. -This mean that if you call is_empty, -it will return True.

-

Time Complexity: O(1).

-
- -
-
def clear(self) -> None:
-    """Clears all nodes from this heap.
-    This mean that if you call `is_empty`,
-    it will return `True`.
-    **Time Complexity:** O(1)."""
-    self.heap.clear()
-
-
-
- -
- - -
-
-

def contains(

self, x)

-
- - - - -

Returns True, if x is in this heap. False otherwise.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(n).

-
- -
-
def contains(self, x) -> bool:
-    """Returns `True`, if `x` is in this heap. `False` otherwise.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(n)."""
-    return self.search(x) != -1
-
-
-
- -
- - -
-
-

def delete(

self, i)

-
- - - - -

Deletes and returns the HeapNode object at index i.

-

IndexError is raised if i is not a valid index.

-

Implementation based on: -http://www.math.clemson.edu/~warner/M865/HeapDelete.html

-

Time Complexity: O(log2 h), -where h is the number of nodes rooted at i.

-
- -
-
def delete(self, i: int) -> HeapNode:
-    """Deletes and returns the `HeapNode` object at index `i`.
-    `IndexError` is raised if `i` is not a valid index.
-    Implementation based on:
-    [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
-    **Time Complexity:** O(log2 h),
-    where `h` is the number of nodes rooted at `i`."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    if i == self.size() - 1:
-        return self.heap.pop()
-    self.swap(i, self.size() - 1)
-    d = self.heap.pop()
-    self.push_down(i)
-    return d
-
-
-
- -
- - -
-
-

def find_max(

self)

-
- - - - -

Returns (without removing) the greatest element in this max-heap.

-

Time Complexity: O(1).

-
- -
-
def find_max(self) -> HeapNode:
-    """Returns (without removing) the greatest element in this max-heap.
-    **Time Complexity:** O(1)."""
-    return self.heap[0] if not self.is_empty() else None
-
-
-
- -
- - -
-
-

def grandparent_index(

self, i)

-
- - - - -

Returns the grandparent's index of the node at index i.

-

-1 is returned either if i has not a parent or -the parent of i does not have a parent.

-

Time Complexity: O(1).

-
- -
-
def grandparent_index(self, i: int) -> int:
-    """Returns the grandparent's index of the node at index `i`.
-    -1 is returned either if `i` has not a parent or
-    the parent of `i` does not have a parent.
-    **Time Complexity:** O(1)."""
-    p = self.parent_index(i)
-    return -1 if p == -1 else self.parent_index(p)
-
-
-
- -
- - -
-
-

def has_children(

self, i)

-
- - - - -

Returns True if the node at index i -has at least one child, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def has_children(self, i: int) -> bool:
-    """Returns `True` if the node at index `i`
-    has at least one child, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return self.left_index(i) != -1 or self.right_index(i) != -1
-
-
-
- -
- - -
-
-

def is_child(

self, c, i)

-
- - - - -

Returns True if c is a child of i. False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_child(self, c: int, i: int) -> bool:
-    """Returns `True` if `c` is a child of `i`. `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(c) or not self.is_good_index(i):
-        raise IndexError("i or c are not valid indexes.")
-    return c == self.left_index(i) or c == self.right_index(i)
-
-
-
- -
- - -
-
-

def is_empty(

self)

-
- - - - -

Returns True if this heap is empty.

-

Time Complexity: O(1).

-
- -
-
def is_empty(self) -> bool:
-    """Returns `True` if this heap is empty.
-    **Time Complexity:** O(1)."""
-    return self.size() == 0
-
-
-
- -
- - -
-
-

def is_good_index(

self, i)

-
- - - - -

Returns True if i is valid index for self.heap, -False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_good_index(self, i: int) -> bool:
-    """Returns `True` if `i` is valid index for `self.heap`,
-    `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not isinstance(i, int):
-        raise TypeError("indexes can only be int.")
-    return False if (i < 0 or i >= self.size()) else True
-
-
-
- -
- - -
-
-

def is_grandchild(

self, g, i)

-
- - - - -

Returns True if g is a grandchild of i. False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_grandchild(self, g: int, i: int) -> bool:
-    """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
-    **Time Complexity:** O(1)."""
-    l = self.left_index(i)
-    if l == -1:
-        assert self.right_index(i) == -1
-        if not self.is_good_index(g):
-            raise IndexError("g is not a valid index.")
-        return False
-    r = self.right_index(i)
-    if r == -1:
-        return self.is_child(g, l)
-    else:
-        return self.is_child(g, l) or self.is_child(g, r)
-
-
-
- -
- - -
-
-

def is_grandparent(

self, g, i)

-
- - - - -

Returns True if g is the index of the grandparent -of the node at i, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_grandparent(self, g: int, i: int) -> bool:
-    """Returns `True` if `g` is the index of the grandparent
-    of the node at `i`, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(g):
-        raise IndexError("g is not a valid index.")
-    p = self.parent_index(i)
-    return False if p == -1 else self.is_parent(g, p)
-
-
-
- -
- - -
-
-

def is_on_even_level(

self, i)

-
- - - - -

Returns True if node at index i is on a even-level, -i.e., if i is on a level multiple of 2 (0, 2, 4, 6,...). -If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(int(math.log2(i + 1) % 2) == 0).

-
- -
-
def is_on_even_level(self, i: int) -> bool:
-    """Returns `True` if node at index `i` is on a even-level,
-    i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return int(math.log2(i + 1) % 2) == 0
-
-
-
- -
- - -
-
-

def is_on_odd_level(

self, i)

-
- - - - -

Returns True (False) if self.is_on_even_level(i) returns False (True).

-
- -
-
def is_on_odd_level(self, i: int) -> bool:
-    """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
-    return not self.is_on_even_level(i)
-
-
-
- -
- - -
-
-

def is_parent(

self, p, i)

-
- - - - -

Returns True if p is the index of the parent -of the node at i, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_parent(self, p: int, i: int) -> bool:
-    """Returns `True` if `p` is the index of the parent
-    of the node at `i`, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(p):
-        raise IndexError("p is not a valid index.")
-    return self.parent_index(i) == p
-
-
-
- -
- - -
-
-

def left_index(

self, i)

-
- - - - -

Returns the left child's index of the node at index i, -if it exists, otherwise this function returns -1.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def left_index(self, i: int) -> int:
-    """Returns the left child's index of the node at index `i`,
-    if it exists, otherwise this function returns -1.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    left = i * 2 + 1
-    return left if self.is_good_index(left) else -1
-
-
-
- -
- - -
-
-

def merge(

self, o)

-
- - - - -

Merges this heap with the o heap.

-

Returns the list object representing internally the new merged heap.

-

Time Complexity: O(n + m).

-

Time complexity analysis based on: -http://stackoverflow.com/a/29197855/3924118.

-
- -
-
def merge(self, o) -> list:
-    """Merges this heap with the `o` heap.
-    Returns the `list` object representing internally the new merged heap.
-    **Time Complexity:** O(n + m).
-    Time complexity analysis based on:
-    [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
-    self.heap += o.heap
-    return self.build_heap()
-
-
-
- -
- - -
-
-

def parent_index(

self, i)

-
- - - - -

Returns the parent's index of the node at index i. -If i = 0, then -1 is returned, because the root has no parent.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def parent_index(self, i: int) -> int:
-    """Returns the parent's index of the node at index `i`.
-    If `i = 0`, then -1 is returned, because the root has no parent.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return -1 if i == 0 else (i - 1) // 2
-
-
-
- -
- - -
-
-

def push_down(

self, i)

-
- - - - -

'Max-heapify' this max-heap starting from index i.

-

Time Complexity: O(log2 n).

-
- -
-
def push_down(self, i: int) -> None:
-    """'Max-heapify' this max-heap starting from index `i`.
-    **Time Complexity:** O(log2 n)."""
-    m = i
-    l = self.left_index(i)
-    r = self.right_index(i)
-    if l != -1 and self.heap[l] > self.heap[m]:
-        m = l
-    if r != -1 and self.heap[r] > self.heap[m]:
-        m = r
-    if m != i:
-        self.swap(m, i)
-        self.push_down(m)
-
-
-
- -
- - -
-
-

def push_up(

self, i)

-
- - - - -

Pushes up the node at index i.

-

Note that this operation only happens -if the node at index i is greater than its parent.

-

Time Complexity: O(log2 n).

-
- -
-
def push_up(self, i: int) -> None:
-    """Pushes up the node at index `i`.
-    Note that this operation only happens
-    if the node at index `i` is greater than its parent.
-    **Time Complexity:** O(log2 n)."""
-    c = i  # current index
-    p = self.parent_index(i)
-    if p != -1 and self.heap[c] > self.heap[p]:
-        c = p
-    if c != i:
-        self.swap(c, i)
-        self.push_up(c)
-
-
-
- -
- - -
-
-

def remove_max(

self)

-
- - - - -

Removes and returns the greatest element in this max-heap.

-

Time Complexity: O(log2 n), -if removing the last element of a list is a constant-time operation.

-
- -
-
def remove_max(self) -> HeapNode:
-    """Removes and returns the greatest element in this max-heap.
-    **Time Complexity:** O(log2 n),
-    if removing the last element of a list is a constant-time operation."""
-    if not self.is_empty():
-        self.swap(0, self.size() - 1)
-        m = self.heap.pop()
-        if not self.is_empty():
-            self.push_down(0)
-        return m
-
-
-
- -
- - -
-
-

def replace(

self, i, x)

-
- - - - -

Replaces element at index i with x.

-

x can either be a key or a HeapNode object. -If it's a key, then a HeapNode object -first created to represent x.

-
    -
  1. -

    If x == self.heap[i], -then just replace self.heap[i] with x.

    -
  2. -
  3. -

    Else if x > self.heap[i], -then push_up(index).

    -
  4. -
  5. -

    Else x < self.heap[i], -then call self.push_down(i).

    -
  6. -
-

Returns the previous HeapNode object at i.

-

Time Complexity: O(log2 n).

-
- -
-
def replace(self, i: int, x) -> HeapNode:
-    """Replaces element at index `i` with `x`.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, then a `HeapNode` object
-    first created to represent `x`.
-    1. If `x == self.heap[i]`,
-    then just replace `self.heap[i]` with `x`.
-    2. Else if `x > self.heap[i]`,
-    then push_up(index).
-    3. Else `x < self.heap[i]`,
-    then call `self.push_down(i)`.
-    Returns the previous `HeapNode` object at `i`.
-    **Time Complexity:** O(log2 n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    c = self.heap[i]
-    self.heap[i] = x
-    if x < c:
-        self.push_down(i)
-    elif x > c:
-        self.push_up(i)
-    return c
-
-
-
- -
- - -
-
-

def right_index(

self, i)

-
- - - - -

Returns the right child's index of the node at index i, -if it exists, otherwise this function returns -1.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def right_index(self, i: int) -> int:
-    """Returns the right child's index of the node at index `i`,
-    if it exists, otherwise this function returns -1.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    right = i * 2 + 2
-    return right if self.is_good_index(right) else -1
-
-
-
- -
- - -
-
-

def search(

self, x)

-
- - - - -

Searches for x in this heap, -and, if present, returns its index, otherwise returns -1.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(n).

-
- -
-
def search(self, x) -> int:
-    """Searches for `x` in this heap,
-    and, if present, returns its index, otherwise returns -1.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    for i, node in enumerate(self.heap):
-        if node == x:
-            return i
-    return -1
-
-
-
- -
- - -
-
-

def search_by_value(

self, val)

-
- - - - -

Returns the index of the HeapNode object with value=val. --1 is returned if no such a HeapNode object exists.

-

If val and the values in this heap are not comparable, -the behaviour of this method is undefined.

-

By construction, HeapNode objects can't be initialized with None values, -but that field could also be set manually after creation.

-

Time Complexity: O(n).

-
- -
-
def search_by_value(self, val) -> int:
-    """Returns the index of the `HeapNode` object with `value=val`.
-    -1 is returned if no such a `HeapNode` object exists.
-    If `val` and the values in this heap are not comparable,
-    the behaviour of this method is undefined.
-    By construction, HeapNode objects can't be initialized with None values,
-    but that field could also be set manually after creation.
-    **Time Complexity:** O(n)."""
-    for i, node in enumerate(self.heap):
-        if node.value == val:
-            return i
-    return -1
-
-
-
- -
- - -
-
-

def size(

self)

-
- - - - -

Returns the size of this heaps.

-

Time Complexity: O(1).

-
- -
-
def size(self) -> int:
-    """Returns the size of this heaps.
-    **Time Complexity:** O(1)."""
-    return len(self.heap)
-
-
-
- -
- - -
-
-

def swap(

self, i, j)

-
- - - - -

Swaps elements at indexes i and j, -if they are valid indexes, -otherwise an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def swap(self, i: int, j: int) -> None:
-    """Swaps elements at indexes `i` and `j`,
-    if they are valid indexes,
-    otherwise an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if self.is_good_index(i) and self.is_good_index(j):
-        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
-    else:
-        raise IndexError("i or j are not valid indexes.")
-
-
-
- -
- -
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/MinHeap.m.html b/docs/ands/ds/MinHeap.m.html deleted file mode 100644 index 53bcb3cf..00000000 --- a/docs/ands/ds/MinHeap.m.html +++ /dev/null @@ -1,2374 +0,0 @@ - - - - - - ands.ds.MinHeap API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.MinHeap module

-

Meta info

-

Author: Nelson Brochado

-

Created: 01/07/2015

-

Updated: 05/02/2017

-

Description

-

A binary min-heap is a data structure similar to a binary tree, -where the parent nodes are smaller or equal to their children.

-

In addition to the previous constraint, a binary min-heap is a complete binary tree, -that is, all levels of the tree, except possibly the deepest one are fully filled, -and, if the last level of the tree is not complete, -the nodes of that level are filled from left to right.

-

A min-heap can be implemented with a classic array or list in Python.

-

If we have a node at index i, then

-
    -
  • -

    its left child can be found at index i*2 + 1

    -
  • -
  • -

    its right child is found at i*2 + 2,

    -
  • -
  • -

    its parent can be found at index floor((i - 1) / 2), -where floor(x) truncates x to the smallest integer.

    -
  • -
-

Note that these indexes are for 0-index based lists (or arrays).

-

References

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-# Meta info
-
-Author: Nelson Brochado
-
-Created: 01/07/2015
-
-Updated: 05/02/2017
-
-# Description
-
-A binary min-heap is a data structure similar to a binary tree,
-where the parent nodes are smaller or equal to their children.
-
-In addition to the previous constraint, a binary min-heap is a complete binary tree,
-that is, all levels of the tree, except possibly the deepest one are fully filled,
-and, if the last level of the tree is not complete,
-the nodes of that level are filled from left to right.
-
-A min-heap can be implemented with a classic array or list in Python.
-
-If we have a node at index i, then
-
-- its left child can be found at index i*2 + 1
-
-- its right child is found at i*2 + 2,
-
-- its parent can be found at index floor((i - 1) / 2),
-where floor(x) truncates x to the smallest integer.
-
-Note that these indexes are for 0-index based lists (or arrays).
-
-# References
-
-- [https://en.wikipedia.org/wiki/Binary_heap](https://en.wikipedia.org/wiki/Binary_heap)
-- Slides by prof. A. Carzaniga
-- Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS
-"""
-
-from ands.ds.heap import BinaryHeap, HeapNode
-
-__all__ = ["MinHeap", "is_min_heap"]
-
-
-class MinHeap(BinaryHeap):
-    def __init__(self, ls=None):
-        BinaryHeap.__init__(self, ls)
-
-    def push_down(self, i: int) -> None:
-        """'Min-heapify' this min-heap starting from index `i`.
-
-        **Time Complexity:** O(log2 n)."""
-        m = i  # index of node with smallest value among i and its children
-        l = self.left_index(i)
-        r = self.right_index(i)
-
-        if l != -1 and self.heap[l] < self.heap[m]:
-            m = l
-        if r != -1 and self.heap[r] < self.heap[m]:
-            m = r
-
-        if m != i:
-            self.swap(m, i)
-            self.push_down(m)
-
-    def push_up(self, i: int) -> None:
-        """Pushes up the node at index `i`.
-
-        Note that this operation only happens
-        if the node at index `i` is smaller than its parent.
-
-        This function is simpler than `push_down` (or also called min-heapify),
-        because in this case we just need to compare
-        the current node's index with its parent's index.
-
-        **Time Complexity:** O(log2 n)."""
-        c = i  # current index
-        p = self.parent_index(i)
-
-        if p != -1 and self.heap[c] < self.heap[p]:
-            c = p
-
-        if c != i:
-            self.swap(c, i)
-            self.push_up(c)
-
-    def find_min(self) -> HeapNode:
-        """Returns (without removing) the smallest element in this min-heap.
-
-        **Time Complexity:** O(1)."""
-        return self.heap[0] if not self.is_empty() else None
-
-    def remove_min(self) -> HeapNode:
-        """Removes and returns the smallest element in this heap.
-
-        **Time Complexity:** O(log2 n),
-        if removing the last element of a list is a constant-time operation."""
-        if not self.is_empty():
-            self.swap(0, self.size() - 1)
-            m = self.heap.pop()
-            if not self.is_empty():
-                self.push_down(0)
-            return m
-
-    def delete(self, i: int) -> HeapNode:
-        """Deletes and returns the `HeapNode` object at index `i`.
-
-        `IndexError` is raised if `i` is not a valid index.
-
-        Implementation based on:
-        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
-
-        **Time Complexity:** O(log2 h),
-        where `h` is the number of nodes rooted at `i`."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        if i == self.size() - 1:
-            return self.heap.pop()
-        self.swap(i, self.size() - 1)
-        d = self.heap.pop()
-        self.push_down(i)
-        return d
-
-    def replace(self, i: int, x) -> HeapNode:
-        """Replaces element at index `i` with `x`.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, then a `HeapNode` object
-        first created to represent `x`.
-
-        1. If `x == self.heap[i]`,
-        then just replace `self.heap[i]` with `x`.
-
-        2. Else if `x < self.heap[i]`,
-        then push_up(index).
-
-        3. Else `x > self.heap[i]`,
-        then call `self.push_down(i)`.
-
-        Returns the previous `HeapNode` object at `i`.
-
-        **Time Complexity:** O(log2 n)."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, HeapNode):
-            x = HeapNode(x)
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-
-        c = self.heap[i]
-        self.heap[i] = x
-
-        if x > c:
-            self.push_down(i)
-        elif x < c:
-            self.push_up(i)
-
-        return c
-
-
-def is_min_heap(h) -> bool:
-    """Returns `True` if `h` is a valid `MinHeap`. `False` otherwise."""
-    if not isinstance(h, MinHeap):
-        return False
-    if h.heap:
-        for item in h.heap:
-            if not isinstance(item, HeapNode):
-                return False
-        for i, item in enumerate(h.heap):
-            l = h.left_index(i)
-            r = h.right_index(i)
-            if r != -1 and l == -1:
-                return False
-            if l != -1 and item > h.heap[l]:
-                return False
-            if r != -1 and item > h.heap[r]:
-                return False
-    return True  # h is empty
-
-
- -
- -
- -

Functions

- -
-
-

def is_min_heap(

h)

-
- - - - -

Returns True if h is a valid MinHeap. False otherwise.

-
- -
-
def is_min_heap(h) -> bool:
-    """Returns `True` if `h` is a valid `MinHeap`. `False` otherwise."""
-    if not isinstance(h, MinHeap):
-        return False
-    if h.heap:
-        for item in h.heap:
-            if not isinstance(item, HeapNode):
-                return False
-        for i, item in enumerate(h.heap):
-            l = h.left_index(i)
-            r = h.right_index(i)
-            if r != -1 and l == -1:
-                return False
-            if l != -1 and item > h.heap[l]:
-                return False
-            if r != -1 and item > h.heap[r]:
-                return False
-    return True  # h is empty
-
-
-
- -
- - -

Classes

- -
-

class MinHeap

- - -

Abstract class to represent binary heaps.

-

MinHeap, MaxHeap and MinMaxHeap all derive from this class.

-
- -
-
class MinHeap(BinaryHeap):
-    def __init__(self, ls=None):
-        BinaryHeap.__init__(self, ls)
-
-    def push_down(self, i: int) -> None:
-        """'Min-heapify' this min-heap starting from index `i`.
-
-        **Time Complexity:** O(log2 n)."""
-        m = i  # index of node with smallest value among i and its children
-        l = self.left_index(i)
-        r = self.right_index(i)
-
-        if l != -1 and self.heap[l] < self.heap[m]:
-            m = l
-        if r != -1 and self.heap[r] < self.heap[m]:
-            m = r
-
-        if m != i:
-            self.swap(m, i)
-            self.push_down(m)
-
-    def push_up(self, i: int) -> None:
-        """Pushes up the node at index `i`.
-
-        Note that this operation only happens
-        if the node at index `i` is smaller than its parent.
-
-        This function is simpler than `push_down` (or also called min-heapify),
-        because in this case we just need to compare
-        the current node's index with its parent's index.
-
-        **Time Complexity:** O(log2 n)."""
-        c = i  # current index
-        p = self.parent_index(i)
-
-        if p != -1 and self.heap[c] < self.heap[p]:
-            c = p
-
-        if c != i:
-            self.swap(c, i)
-            self.push_up(c)
-
-    def find_min(self) -> HeapNode:
-        """Returns (without removing) the smallest element in this min-heap.
-
-        **Time Complexity:** O(1)."""
-        return self.heap[0] if not self.is_empty() else None
-
-    def remove_min(self) -> HeapNode:
-        """Removes and returns the smallest element in this heap.
-
-        **Time Complexity:** O(log2 n),
-        if removing the last element of a list is a constant-time operation."""
-        if not self.is_empty():
-            self.swap(0, self.size() - 1)
-            m = self.heap.pop()
-            if not self.is_empty():
-                self.push_down(0)
-            return m
-
-    def delete(self, i: int) -> HeapNode:
-        """Deletes and returns the `HeapNode` object at index `i`.
-
-        `IndexError` is raised if `i` is not a valid index.
-
-        Implementation based on:
-        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
-
-        **Time Complexity:** O(log2 h),
-        where `h` is the number of nodes rooted at `i`."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        if i == self.size() - 1:
-            return self.heap.pop()
-        self.swap(i, self.size() - 1)
-        d = self.heap.pop()
-        self.push_down(i)
-        return d
-
-    def replace(self, i: int, x) -> HeapNode:
-        """Replaces element at index `i` with `x`.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, then a `HeapNode` object
-        first created to represent `x`.
-
-        1. If `x == self.heap[i]`,
-        then just replace `self.heap[i]` with `x`.
-
-        2. Else if `x < self.heap[i]`,
-        then push_up(index).
-
-        3. Else `x > self.heap[i]`,
-        then call `self.push_down(i)`.
-
-        Returns the previous `HeapNode` object at `i`.
-
-        **Time Complexity:** O(log2 n)."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, HeapNode):
-            x = HeapNode(x)
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-
-        c = self.heap[i]
-        self.heap[i] = x
-
-        if x > c:
-            self.push_down(i)
-        elif x < c:
-            self.push_up(i)
-
-        return c
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • MinHeap
  • -
  • ands.ds.heap.BinaryHeap
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self, ls=None)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, ls=None):
-    BinaryHeap.__init__(self, ls)
-
-
-
- -
- - -
-
-

def add(

self, x)

-
- - - - -

Adds x to this heap.

-

In practice, it places x at an available leaf, -then "bubbles up" from there, -in order to maintain the heap property.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(log2 n).

-
- -
-
def add(self, x) -> None:
-    """Adds `x` to this heap.
-    In practice, it places `x` at an available leaf,
-    then "bubbles up" from there,
-    in order to maintain the heap property.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(log2 n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    self.heap.append(x)
-    if self.size() > 1:
-        self.push_up(self.size() - 1)
-
-
-
- -
- - -
-
-

def build_heap(

self)

-
- - - - -

Builds the heap data structure from self.heap.

-
- -
-
def build_heap(self) -> list:
-    """Builds the heap data structure from `self.heap`."""
-    if self.heap:
-        for index in range(len(self.heap) // 2, -1, -1):
-            self.push_down(index)
-    return self.heap
-
-
-
- -
- - -
-
-

def clear(

self)

-
- - - - -

Clears all nodes from this heap. -This mean that if you call is_empty, -it will return True.

-

Time Complexity: O(1).

-
- -
-
def clear(self) -> None:
-    """Clears all nodes from this heap.
-    This mean that if you call `is_empty`,
-    it will return `True`.
-    **Time Complexity:** O(1)."""
-    self.heap.clear()
-
-
-
- -
- - -
-
-

def contains(

self, x)

-
- - - - -

Returns True, if x is in this heap. False otherwise.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(n).

-
- -
-
def contains(self, x) -> bool:
-    """Returns `True`, if `x` is in this heap. `False` otherwise.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(n)."""
-    return self.search(x) != -1
-
-
-
- -
- - -
-
-

def delete(

self, i)

-
- - - - -

Deletes and returns the HeapNode object at index i.

-

IndexError is raised if i is not a valid index.

-

Implementation based on: -http://www.math.clemson.edu/~warner/M865/HeapDelete.html

-

Time Complexity: O(log2 h), -where h is the number of nodes rooted at i.

-
- -
-
def delete(self, i: int) -> HeapNode:
-    """Deletes and returns the `HeapNode` object at index `i`.
-    `IndexError` is raised if `i` is not a valid index.
-    Implementation based on:
-    [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
-    **Time Complexity:** O(log2 h),
-    where `h` is the number of nodes rooted at `i`."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    if i == self.size() - 1:
-        return self.heap.pop()
-    self.swap(i, self.size() - 1)
-    d = self.heap.pop()
-    self.push_down(i)
-    return d
-
-
-
- -
- - -
-
-

def find_min(

self)

-
- - - - -

Returns (without removing) the smallest element in this min-heap.

-

Time Complexity: O(1).

-
- -
-
def find_min(self) -> HeapNode:
-    """Returns (without removing) the smallest element in this min-heap.
-    **Time Complexity:** O(1)."""
-    return self.heap[0] if not self.is_empty() else None
-
-
-
- -
- - -
-
-

def grandparent_index(

self, i)

-
- - - - -

Returns the grandparent's index of the node at index i.

-

-1 is returned either if i has not a parent or -the parent of i does not have a parent.

-

Time Complexity: O(1).

-
- -
-
def grandparent_index(self, i: int) -> int:
-    """Returns the grandparent's index of the node at index `i`.
-    -1 is returned either if `i` has not a parent or
-    the parent of `i` does not have a parent.
-    **Time Complexity:** O(1)."""
-    p = self.parent_index(i)
-    return -1 if p == -1 else self.parent_index(p)
-
-
-
- -
- - -
-
-

def has_children(

self, i)

-
- - - - -

Returns True if the node at index i -has at least one child, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def has_children(self, i: int) -> bool:
-    """Returns `True` if the node at index `i`
-    has at least one child, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return self.left_index(i) != -1 or self.right_index(i) != -1
-
-
-
- -
- - -
-
-

def is_child(

self, c, i)

-
- - - - -

Returns True if c is a child of i. False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_child(self, c: int, i: int) -> bool:
-    """Returns `True` if `c` is a child of `i`. `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(c) or not self.is_good_index(i):
-        raise IndexError("i or c are not valid indexes.")
-    return c == self.left_index(i) or c == self.right_index(i)
-
-
-
- -
- - -
-
-

def is_empty(

self)

-
- - - - -

Returns True if this heap is empty.

-

Time Complexity: O(1).

-
- -
-
def is_empty(self) -> bool:
-    """Returns `True` if this heap is empty.
-    **Time Complexity:** O(1)."""
-    return self.size() == 0
-
-
-
- -
- - -
-
-

def is_good_index(

self, i)

-
- - - - -

Returns True if i is valid index for self.heap, -False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_good_index(self, i: int) -> bool:
-    """Returns `True` if `i` is valid index for `self.heap`,
-    `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not isinstance(i, int):
-        raise TypeError("indexes can only be int.")
-    return False if (i < 0 or i >= self.size()) else True
-
-
-
- -
- - -
-
-

def is_grandchild(

self, g, i)

-
- - - - -

Returns True if g is a grandchild of i. False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_grandchild(self, g: int, i: int) -> bool:
-    """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
-    **Time Complexity:** O(1)."""
-    l = self.left_index(i)
-    if l == -1:
-        assert self.right_index(i) == -1
-        if not self.is_good_index(g):
-            raise IndexError("g is not a valid index.")
-        return False
-    r = self.right_index(i)
-    if r == -1:
-        return self.is_child(g, l)
-    else:
-        return self.is_child(g, l) or self.is_child(g, r)
-
-
-
- -
- - -
-
-

def is_grandparent(

self, g, i)

-
- - - - -

Returns True if g is the index of the grandparent -of the node at i, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_grandparent(self, g: int, i: int) -> bool:
-    """Returns `True` if `g` is the index of the grandparent
-    of the node at `i`, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(g):
-        raise IndexError("g is not a valid index.")
-    p = self.parent_index(i)
-    return False if p == -1 else self.is_parent(g, p)
-
-
-
- -
- - -
-
-

def is_on_even_level(

self, i)

-
- - - - -

Returns True if node at index i is on a even-level, -i.e., if i is on a level multiple of 2 (0, 2, 4, 6,...). -If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(int(math.log2(i + 1) % 2) == 0).

-
- -
-
def is_on_even_level(self, i: int) -> bool:
-    """Returns `True` if node at index `i` is on a even-level,
-    i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return int(math.log2(i + 1) % 2) == 0
-
-
-
- -
- - -
-
-

def is_on_odd_level(

self, i)

-
- - - - -

Returns True (False) if self.is_on_even_level(i) returns False (True).

-
- -
-
def is_on_odd_level(self, i: int) -> bool:
-    """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
-    return not self.is_on_even_level(i)
-
-
-
- -
- - -
-
-

def is_parent(

self, p, i)

-
- - - - -

Returns True if p is the index of the parent -of the node at i, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_parent(self, p: int, i: int) -> bool:
-    """Returns `True` if `p` is the index of the parent
-    of the node at `i`, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(p):
-        raise IndexError("p is not a valid index.")
-    return self.parent_index(i) == p
-
-
-
- -
- - -
-
-

def left_index(

self, i)

-
- - - - -

Returns the left child's index of the node at index i, -if it exists, otherwise this function returns -1.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def left_index(self, i: int) -> int:
-    """Returns the left child's index of the node at index `i`,
-    if it exists, otherwise this function returns -1.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    left = i * 2 + 1
-    return left if self.is_good_index(left) else -1
-
-
-
- -
- - -
-
-

def merge(

self, o)

-
- - - - -

Merges this heap with the o heap.

-

Returns the list object representing internally the new merged heap.

-

Time Complexity: O(n + m).

-

Time complexity analysis based on: -http://stackoverflow.com/a/29197855/3924118.

-
- -
-
def merge(self, o) -> list:
-    """Merges this heap with the `o` heap.
-    Returns the `list` object representing internally the new merged heap.
-    **Time Complexity:** O(n + m).
-    Time complexity analysis based on:
-    [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
-    self.heap += o.heap
-    return self.build_heap()
-
-
-
- -
- - -
-
-

def parent_index(

self, i)

-
- - - - -

Returns the parent's index of the node at index i. -If i = 0, then -1 is returned, because the root has no parent.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def parent_index(self, i: int) -> int:
-    """Returns the parent's index of the node at index `i`.
-    If `i = 0`, then -1 is returned, because the root has no parent.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return -1 if i == 0 else (i - 1) // 2
-
-
-
- -
- - -
-
-

def push_down(

self, i)

-
- - - - -

'Min-heapify' this min-heap starting from index i.

-

Time Complexity: O(log2 n).

-
- -
-
def push_down(self, i: int) -> None:
-    """'Min-heapify' this min-heap starting from index `i`.
-    **Time Complexity:** O(log2 n)."""
-    m = i  # index of node with smallest value among i and its children
-    l = self.left_index(i)
-    r = self.right_index(i)
-    if l != -1 and self.heap[l] < self.heap[m]:
-        m = l
-    if r != -1 and self.heap[r] < self.heap[m]:
-        m = r
-    if m != i:
-        self.swap(m, i)
-        self.push_down(m)
-
-
-
- -
- - -
-
-

def push_up(

self, i)

-
- - - - -

Pushes up the node at index i.

-

Note that this operation only happens -if the node at index i is smaller than its parent.

-

This function is simpler than push_down (or also called min-heapify), -because in this case we just need to compare -the current node's index with its parent's index.

-

Time Complexity: O(log2 n).

-
- -
-
def push_up(self, i: int) -> None:
-    """Pushes up the node at index `i`.
-    Note that this operation only happens
-    if the node at index `i` is smaller than its parent.
-    This function is simpler than `push_down` (or also called min-heapify),
-    because in this case we just need to compare
-    the current node's index with its parent's index.
-    **Time Complexity:** O(log2 n)."""
-    c = i  # current index
-    p = self.parent_index(i)
-    if p != -1 and self.heap[c] < self.heap[p]:
-        c = p
-    if c != i:
-        self.swap(c, i)
-        self.push_up(c)
-
-
-
- -
- - -
-
-

def remove_min(

self)

-
- - - - -

Removes and returns the smallest element in this heap.

-

Time Complexity: O(log2 n), -if removing the last element of a list is a constant-time operation.

-
- -
-
def remove_min(self) -> HeapNode:
-    """Removes and returns the smallest element in this heap.
-    **Time Complexity:** O(log2 n),
-    if removing the last element of a list is a constant-time operation."""
-    if not self.is_empty():
-        self.swap(0, self.size() - 1)
-        m = self.heap.pop()
-        if not self.is_empty():
-            self.push_down(0)
-        return m
-
-
-
- -
- - -
-
-

def replace(

self, i, x)

-
- - - - -

Replaces element at index i with x.

-

x can either be a key or a HeapNode object. -If it's a key, then a HeapNode object -first created to represent x.

-
    -
  1. -

    If x == self.heap[i], -then just replace self.heap[i] with x.

    -
  2. -
  3. -

    Else if x < self.heap[i], -then push_up(index).

    -
  4. -
  5. -

    Else x > self.heap[i], -then call self.push_down(i).

    -
  6. -
-

Returns the previous HeapNode object at i.

-

Time Complexity: O(log2 n).

-
- -
-
def replace(self, i: int, x) -> HeapNode:
-    """Replaces element at index `i` with `x`.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, then a `HeapNode` object
-    first created to represent `x`.
-    1. If `x == self.heap[i]`,
-    then just replace `self.heap[i]` with `x`.
-    2. Else if `x < self.heap[i]`,
-    then push_up(index).
-    3. Else `x > self.heap[i]`,
-    then call `self.push_down(i)`.
-    Returns the previous `HeapNode` object at `i`.
-    **Time Complexity:** O(log2 n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    c = self.heap[i]
-    self.heap[i] = x
-    if x > c:
-        self.push_down(i)
-    elif x < c:
-        self.push_up(i)
-    return c
-
-
-
- -
- - -
-
-

def right_index(

self, i)

-
- - - - -

Returns the right child's index of the node at index i, -if it exists, otherwise this function returns -1.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def right_index(self, i: int) -> int:
-    """Returns the right child's index of the node at index `i`,
-    if it exists, otherwise this function returns -1.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    right = i * 2 + 2
-    return right if self.is_good_index(right) else -1
-
-
-
- -
- - -
-
-

def search(

self, x)

-
- - - - -

Searches for x in this heap, -and, if present, returns its index, otherwise returns -1.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(n).

-
- -
-
def search(self, x) -> int:
-    """Searches for `x` in this heap,
-    and, if present, returns its index, otherwise returns -1.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    for i, node in enumerate(self.heap):
-        if node == x:
-            return i
-    return -1
-
-
-
- -
- - -
-
-

def search_by_value(

self, val)

-
- - - - -

Returns the index of the HeapNode object with value=val. --1 is returned if no such a HeapNode object exists.

-

If val and the values in this heap are not comparable, -the behaviour of this method is undefined.

-

By construction, HeapNode objects can't be initialized with None values, -but that field could also be set manually after creation.

-

Time Complexity: O(n).

-
- -
-
def search_by_value(self, val) -> int:
-    """Returns the index of the `HeapNode` object with `value=val`.
-    -1 is returned if no such a `HeapNode` object exists.
-    If `val` and the values in this heap are not comparable,
-    the behaviour of this method is undefined.
-    By construction, HeapNode objects can't be initialized with None values,
-    but that field could also be set manually after creation.
-    **Time Complexity:** O(n)."""
-    for i, node in enumerate(self.heap):
-        if node.value == val:
-            return i
-    return -1
-
-
-
- -
- - -
-
-

def size(

self)

-
- - - - -

Returns the size of this heaps.

-

Time Complexity: O(1).

-
- -
-
def size(self) -> int:
-    """Returns the size of this heaps.
-    **Time Complexity:** O(1)."""
-    return len(self.heap)
-
-
-
- -
- - -
-
-

def swap(

self, i, j)

-
- - - - -

Swaps elements at indexes i and j, -if they are valid indexes, -otherwise an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def swap(self, i: int, j: int) -> None:
-    """Swaps elements at indexes `i` and `j`,
-    if they are valid indexes,
-    otherwise an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if self.is_good_index(i) and self.is_good_index(j):
-        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
-    else:
-        raise IndexError("i or j are not valid indexes.")
-
-
-
- -
- -
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/MinMaxHeap.m.html b/docs/ands/ds/MinMaxHeap.m.html deleted file mode 100644 index 84281a19..00000000 --- a/docs/ands/ds/MinMaxHeap.m.html +++ /dev/null @@ -1,3013 +0,0 @@ - - - - - - ands.ds.MinMaxHeap API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.MinMaxHeap module

-

Meta info

-

Author: Nelson Brochado

-

Created: 18/02/2016

-

Updated: 29/12/2016

-

Description

-

Min-Max Heap is a heap that supports find-min and find-max operations in constant time. -Moreover, both remove-min and remove-max are supported in logarithmic time. -It's therefore an useful data structure to implement (or represent) double-ended priority queues.

-

The min-max heap ordering is the following:

-
-

values stored at nodes on even (or min) levels -are smaller than or equal to values stored at their descendants, -whereas values stored at nodes on odd (or max) levels -are greater than or equal to values stored at their descendants.

-
-

Even levels are 0, 2, 4, 6, etc, -whereas odd levels are 1, 3, 5, 7, etc.

-

The most important methods used to build and support the data structure are:

-
    -
  • trickle-down (or, also called, bubble-down or shift-down)
  • -
  • trickle-down-min, which is a helper method of trickle-down
  • -
  • trickle-down-max, which is also a helper method of trickle-down
  • -
  • trickle-up (or, also called, bubble-up or shift-up)
  • -
  • trickle-up-min, which is a helper method of trickle-up
  • -
  • trickle-up-max, which is also a helper method of trickle-up
  • -
  • parent-index
  • -
  • grandparent-index
  • -
  • left-child-index
  • -
  • right-child-index
  • -
  • is-on-min-level (or is-on-even-level)
  • -
  • is-on-max-level (or is-on-odd-level)
  • -
  • find-max-element-index
  • -
  • -

    swap

    -
  • -
  • -

    add in O(log n) time

    -
  • -
  • delete-at in O(log n) time
  • -
  • replace-at in O(log n) time
  • -
  • remove-min in O(log n) time
  • -
  • remove-max in O(log n) time
  • -
  • find-min in O(1) time
  • -
  • find-max in O(1) time
  • -
  • size in O(1) time
  • -
  • is-empty in O(1) time
  • -
  • contains in O(n) time
  • -
  • merge in O(n + m) time
  • -
  • clear in O(1) time
  • -
-

TODO

-
    -
  • find-kth, i.e. find the kth smallest element in the structure, in O(1) time
  • -
  • delete-kth, i.e. delete the kth smallest element, in O(log n) time
  • -
-

References

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-# Meta info
-
-Author: Nelson Brochado
-
-Created: 18/02/2016
-
-Updated: 29/12/2016
-
-# Description
-
-Min-Max Heap is a heap that supports find-min and find-max operations in constant time.
-Moreover, both remove-min and remove-max are supported in logarithmic time.
-It's therefore an useful data structure to implement (or represent) double-ended priority queues.
-
-The min-max heap ordering is the following:
-> values stored at nodes on even (or min) levels
-are smaller than or equal to values stored at their descendants,
-whereas values stored at nodes on odd (or max) levels
-are greater than or equal to values stored at their descendants.
-
-Even levels are 0, 2, 4, 6, etc,
-whereas odd levels are 1, 3, 5, 7, etc.
-
-The most important methods used to build and support the data structure are:
-
-- trickle-down (or, also called, bubble-down or shift-down)
-- trickle-down-min, which is a helper method of trickle-down
-- trickle-down-max, which is also a helper method of trickle-down
-- trickle-up (or, also called, bubble-up or shift-up)
-- trickle-up-min, which is a helper method of trickle-up
-- trickle-up-max, which is also a helper method of trickle-up
-- parent-index
-- grandparent-index
-- left-child-index
-- right-child-index
-- is-on-min-level (or is-on-even-level)
-- is-on-max-level (or is-on-odd-level)
-- find-max-element-index
-- swap
-
-- `add` in O(log n) time
-- `delete-at` in O(log n) time
-- `replace-at` in O(log n) time
-- `remove-min` in O(log n) time
-- `remove-max` in O(log n) time
-- `find-min` in O(1) time
-- `find-max` in O(1) time
-- `size` in O(1) time
-- `is-empty` in O(1) time
-- `contains` in O(n) time
-- `merge` in O(n + m) time
-- `clear` in O(1) time
-
-# TODO
-
-- `find-kth`, i.e. find the kth smallest element in the structure, in O(1) time 
-- `delete-kth`, i.e. delete the kth smallest element, in O(log n) time 
-
-# References
-
-- [Min-Max Heaps and Generalized Priority Queues](http://www.akira.ruc.dk/~keld/teaching/algoritmedesign_f03/Artikler/02/Atkinson86.pdf),
-original paper describing and introducing the min-max heap data structure, by M. D. Atkinson, J.R. Sack, N. Santoro and T. Strothotte.
-- [http://www.diku.dk/forskning/performance-engineering/Jesper/heaplab/heapsurvey_html/node11.html](http://www.diku.dk/forskning/performance-engineering/Jesper/heaplab/heapsurvey_html/node11.html)
-"""
-
-from ands.ds.heap import BinaryHeap, HeapNode
-
-
-class MinMaxHeap(BinaryHeap):
-    def __init__(self, ls=None):
-        BinaryHeap.__init__(self, ls)
-
-    def push_down(self, i: int) -> None:
-        """Also called `bubble-down` or `shift-down`."""
-        if self.is_on_even_level(i):
-            self.push_down_min(i)
-        else:
-            self.push_down_max(i)
-
-    def push_down_min(self, i: int) -> None:
-        """Helper method for `push_down`."""
-        if self.has_children(i):
-            m = self.index_of_min(i)
-
-            if self.is_grandchild(m, i):
-                if self.heap[m] < self.heap[i]:
-                    self.swap(i, m)
-
-                    mp = self.parent_index(m)
-                    if mp != -1 and self.heap[m] > self.heap[mp]:
-                        self.swap(m, mp)
-                    self.push_down_min(m)
-
-            else:  # self.heap[m] is a child of self.heap[i]
-                if self.heap[m] < self.heap[i]:
-                    self.swap(i, m)
-
-    def push_down_max(self, i: int) -> None:
-        """Helper method for `push_down`."""
-        if self.has_children(i):
-            m = self.index_of_max(i)
-
-            if self.is_grandchild(m, i):
-                if self.heap[m] > self.heap[i]:
-                    self.swap(i, m)
-
-                    mp = self.parent_index(m)
-                    if mp != -1 and self.heap[m] < self.heap[mp]:
-                        self.swap(m, mp)
-                    self.push_down_max(m)
-
-            else:  # self.heap[m] is a child of self.heap[i]
-                if self.heap[m] > self.heap[i]:
-                    self.swap(i, m)
-
-    def push_up(self, i: int) -> None:
-        """Also called `bubble-up` or `shift-up`."""
-        p = self.parent_index(i)
-
-        # Let x be the element at index i.
-        # If x has a parent at position p, we call it y.
-        if self.is_on_even_level(i):
-            if p != -1 and self.heap[i] > self.heap[p]:
-                # If x is greater than y, swap x with y.
-                # Now, x is at index p, and y at index i.
-                # push_up_max from the new index of x, i.e. p.
-                self.swap(i, p)
-                self.push_up_max(p)
-            else:
-                # x does not have a parent OR x <= y.
-                self.push_up_min(i)
-        else:
-            # Odd or max level.
-            if p != -1 and self.heap[i] < self.heap[p]:
-                self.swap(i, p)
-                self.push_up_min(p)
-            else:
-                self.push_up_max(i)
-
-    def push_up_min(self, i: int) -> None:
-        """Helper method for `push_up`."""
-        g = self.grandparent_index(i)
-        # Let x be the element at index i.
-        # If x has a grandparent at position g,
-        # we call it z.
-
-        # If the z exists and x is smaller than z,
-        # swap x and z. Now, x is at index g and z at index i.
-        if g != -1 and self.heap[i] < self.heap[g]:
-            self.swap(i, g)
-            self.push_up_min(g)
-
-    def push_up_max(self, i: int) -> None:
-        """Helper method for `push_up`."""
-        g = self.grandparent_index(i)
-        if g != -1 and self.heap[i] > self.heap[g]:
-            self.swap(i, g)
-            self.push_up_max(g)
-
-    def find_max(self) -> HeapNode:
-        """Returns the `HeapNode` object representing the maximum element.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_empty():
-            return self.heap[self.find_max_index()]
-
-    def find_min(self) -> HeapNode:
-        """Returns the `HeapNode` object representing the minimum element.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_empty():
-            return self.heap[0]
-
-    def remove_max(self) -> HeapNode:
-        """Deletes and returns the `HeapNode` object representing the maximum element.
-
-        **Time Complexity:** O(log2 n)."""
-        if not self.is_empty():
-            return self.delete(self.find_max_index())
-
-    def remove_min(self) -> HeapNode:
-        """Deletes and returns the `HeapNode` object representing the minimum element.
-
-        **Time Complexity:** O(log2 n)."""
-        if not self.is_empty():
-            return self.delete(0)
-
-    def delete(self, i: int) -> HeapNode:
-        """Deletes and returns the `HeapNode` object at index `i`.
-
-        `IndexError` is raised if `i` is not a valid index.
-
-        Implementation based on:
-        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
-
-        **Time Complexity:** O(log2 n)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        if i == self.size() - 1:
-            return self.heap.pop()
-        self.swap(i, self.size() - 1)
-        d = self.heap.pop()
-        self.push_up(i)
-        self.push_down(i)
-        return d
-
-    def replace(self, i: int, x):
-        """Replace node at index `i` with `x`.
-
-        `x` can either be a key for a HeapNode` object,
-        which is created automatically by this function,
-        and `x` becomes the key and value of that same `HeapNode` object,
-        or it can be (directly) a `HeapNode` object.
-
-        If `x` is NOT a `HeapNode`, it should be comparable
-        with the other keys in the other `HeapNode` objects.
-        If this is not true, the behaviour of this function is undefined.
-
-        If `x` is a `HeapNode`,
-        it's the responsibility of the client of this function
-        to make sure it's a "valid" `HeapNode` object,
-        i.e. it's comparable to the other `HeapNode` objects in this heap.
-
-        **Time Complexity:** O(log2 n)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, HeapNode):
-            x = HeapNode(x)
-        d = self.heap[i]
-        self.heap[i] = x
-        self.push_up(i)
-        self.push_down(i)
-        return d
-
-    def find_max_index(self) -> int:
-        """Returns the index of the maximum element in this min-max heap.
-
-        **Time Complexity:** O(1)."""
-        if self.is_empty():
-            return -1
-        elif self.size() == 1:
-            return 0
-        elif self.size() == 2:
-            return 1
-        else:
-            return 1 if self.heap[1] > self.heap[2] else 2
-
-    def index_of_min(self, i: int) -> int:
-        """Returns the index of the smallest element
-        among the children and grandchildren of the node at index `i`.
-
-        **Time Complexity:** O(1)."""
-        m = l = self.left_index(i)
-        r = self.right_index(i)
-
-        if r != -1 and self.heap[r] < self.heap[m]:
-            m = r
-
-        if l != -1:
-            gll = self.left_index(l)
-            if gll != -1 and self.heap[gll] < self.heap[m]:
-                m = gll
-            glr = self.right_index(l)
-            if glr != -1 and self.heap[glr] < self.heap[m]:
-                m = glr
-
-        if r != -1:
-            grl = self.left_index(r)
-            if grl != -1 and self.heap[grl] < self.heap[m]:
-                m = grl
-            grr = self.right_index(r)
-            if grr != -1 and self.heap[grr] < self.heap[m]:
-                m = grr
-
-        return m
-
-    def index_of_max(self, i: int) -> int:
-        """Returns the index of the largest element
-        among the children and grandchildren of the node at index `i`.
-
-        **Time Complexity:** O(1)."""
-        m = l = self.left_index(i)
-        r = self.right_index(i)
-
-        if r != -1 and self.heap[r] > self.heap[m]:
-            m = r
-
-        if l != -1:
-            gll = self.left_index(l)
-            if gll != -1 and self.heap[gll] > self.heap[m]:
-                m = gll
-            glr = self.right_index(l)
-            if glr != -1 and self.heap[glr] > self.heap[m]:
-                m = glr
-
-        if r != -1:
-            grl = self.left_index(r)
-            if grl != -1 and self.heap[grl] > self.heap[m]:
-                m = grl
-            grr = self.right_index(r)
-            if grr != -1 and self.heap[grr] > self.heap[m]:
-                m = grr
-
-        return m
-
-
-def is_min_max_heap(h) -> bool:
-    """Returns `True` if `h` is a valid `MinMaxHeap` object. `False` otherwise.
-
-    Min-max heap property:
-    each node at an EVEN level in the tree is LESS THAN all of its descendants
-    while each node at an ODD level in the tree is GREATER THAN all of its descendants."""
-    if not isinstance(h, MinMaxHeap):
-        return False
-
-    if h.heap:
-        for item in h.heap:
-            if not isinstance(item, HeapNode):
-                return False
-
-        if h.size() == 1:
-            return True
-
-        if h.size() == 2:
-            return max(h.heap) == h.heap[1] and min(h.heap) == h.heap[0]
-
-        if h.size() >= 3:
-            if h.heap[0] != min(h.heap) or (h.heap[1] != max(h.heap) and h.heap[2] != max(h.heap)):
-                return False
-
-        # i is the index of the current node
-        for i, item in reversed(list(enumerate(h.heap))):
-
-            p = h.parent_index(i)
-
-            if p != -1:
-                if h.is_on_even_level(i):
-                    if h.heap[p] < item:
-                        return False
-                else:
-                    if h.heap[p] > item:
-                        return False
-
-            g = h.grandparent_index(i)
-            if g != -1:
-                if h.is_on_even_level(i):
-                    if h.heap[g] > item:
-                        return False
-                else:
-                    if h.heap[g] < item:
-                        return False
-    return True
-
-
- -
- -
- -

Functions

- -
-
-

def is_min_max_heap(

h)

-
- - - - -

Returns True if h is a valid MinMaxHeap object. False otherwise.

-

Min-max heap property: -each node at an EVEN level in the tree is LESS THAN all of its descendants -while each node at an ODD level in the tree is GREATER THAN all of its descendants.

-
- -
-
def is_min_max_heap(h) -> bool:
-    """Returns `True` if `h` is a valid `MinMaxHeap` object. `False` otherwise.
-
-    Min-max heap property:
-    each node at an EVEN level in the tree is LESS THAN all of its descendants
-    while each node at an ODD level in the tree is GREATER THAN all of its descendants."""
-    if not isinstance(h, MinMaxHeap):
-        return False
-
-    if h.heap:
-        for item in h.heap:
-            if not isinstance(item, HeapNode):
-                return False
-
-        if h.size() == 1:
-            return True
-
-        if h.size() == 2:
-            return max(h.heap) == h.heap[1] and min(h.heap) == h.heap[0]
-
-        if h.size() >= 3:
-            if h.heap[0] != min(h.heap) or (h.heap[1] != max(h.heap) and h.heap[2] != max(h.heap)):
-                return False
-
-        # i is the index of the current node
-        for i, item in reversed(list(enumerate(h.heap))):
-
-            p = h.parent_index(i)
-
-            if p != -1:
-                if h.is_on_even_level(i):
-                    if h.heap[p] < item:
-                        return False
-                else:
-                    if h.heap[p] > item:
-                        return False
-
-            g = h.grandparent_index(i)
-            if g != -1:
-                if h.is_on_even_level(i):
-                    if h.heap[g] > item:
-                        return False
-                else:
-                    if h.heap[g] < item:
-                        return False
-    return True
-
-
-
- -
- - -

Classes

- -
-

class MinMaxHeap

- - -

Abstract class to represent binary heaps.

-

MinHeap, MaxHeap and MinMaxHeap all derive from this class.

-
- -
-
class MinMaxHeap(BinaryHeap):
-    def __init__(self, ls=None):
-        BinaryHeap.__init__(self, ls)
-
-    def push_down(self, i: int) -> None:
-        """Also called `bubble-down` or `shift-down`."""
-        if self.is_on_even_level(i):
-            self.push_down_min(i)
-        else:
-            self.push_down_max(i)
-
-    def push_down_min(self, i: int) -> None:
-        """Helper method for `push_down`."""
-        if self.has_children(i):
-            m = self.index_of_min(i)
-
-            if self.is_grandchild(m, i):
-                if self.heap[m] < self.heap[i]:
-                    self.swap(i, m)
-
-                    mp = self.parent_index(m)
-                    if mp != -1 and self.heap[m] > self.heap[mp]:
-                        self.swap(m, mp)
-                    self.push_down_min(m)
-
-            else:  # self.heap[m] is a child of self.heap[i]
-                if self.heap[m] < self.heap[i]:
-                    self.swap(i, m)
-
-    def push_down_max(self, i: int) -> None:
-        """Helper method for `push_down`."""
-        if self.has_children(i):
-            m = self.index_of_max(i)
-
-            if self.is_grandchild(m, i):
-                if self.heap[m] > self.heap[i]:
-                    self.swap(i, m)
-
-                    mp = self.parent_index(m)
-                    if mp != -1 and self.heap[m] < self.heap[mp]:
-                        self.swap(m, mp)
-                    self.push_down_max(m)
-
-            else:  # self.heap[m] is a child of self.heap[i]
-                if self.heap[m] > self.heap[i]:
-                    self.swap(i, m)
-
-    def push_up(self, i: int) -> None:
-        """Also called `bubble-up` or `shift-up`."""
-        p = self.parent_index(i)
-
-        # Let x be the element at index i.
-        # If x has a parent at position p, we call it y.
-        if self.is_on_even_level(i):
-            if p != -1 and self.heap[i] > self.heap[p]:
-                # If x is greater than y, swap x with y.
-                # Now, x is at index p, and y at index i.
-                # push_up_max from the new index of x, i.e. p.
-                self.swap(i, p)
-                self.push_up_max(p)
-            else:
-                # x does not have a parent OR x <= y.
-                self.push_up_min(i)
-        else:
-            # Odd or max level.
-            if p != -1 and self.heap[i] < self.heap[p]:
-                self.swap(i, p)
-                self.push_up_min(p)
-            else:
-                self.push_up_max(i)
-
-    def push_up_min(self, i: int) -> None:
-        """Helper method for `push_up`."""
-        g = self.grandparent_index(i)
-        # Let x be the element at index i.
-        # If x has a grandparent at position g,
-        # we call it z.
-
-        # If the z exists and x is smaller than z,
-        # swap x and z. Now, x is at index g and z at index i.
-        if g != -1 and self.heap[i] < self.heap[g]:
-            self.swap(i, g)
-            self.push_up_min(g)
-
-    def push_up_max(self, i: int) -> None:
-        """Helper method for `push_up`."""
-        g = self.grandparent_index(i)
-        if g != -1 and self.heap[i] > self.heap[g]:
-            self.swap(i, g)
-            self.push_up_max(g)
-
-    def find_max(self) -> HeapNode:
-        """Returns the `HeapNode` object representing the maximum element.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_empty():
-            return self.heap[self.find_max_index()]
-
-    def find_min(self) -> HeapNode:
-        """Returns the `HeapNode` object representing the minimum element.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_empty():
-            return self.heap[0]
-
-    def remove_max(self) -> HeapNode:
-        """Deletes and returns the `HeapNode` object representing the maximum element.
-
-        **Time Complexity:** O(log2 n)."""
-        if not self.is_empty():
-            return self.delete(self.find_max_index())
-
-    def remove_min(self) -> HeapNode:
-        """Deletes and returns the `HeapNode` object representing the minimum element.
-
-        **Time Complexity:** O(log2 n)."""
-        if not self.is_empty():
-            return self.delete(0)
-
-    def delete(self, i: int) -> HeapNode:
-        """Deletes and returns the `HeapNode` object at index `i`.
-
-        `IndexError` is raised if `i` is not a valid index.
-
-        Implementation based on:
-        [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
-
-        **Time Complexity:** O(log2 n)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        if i == self.size() - 1:
-            return self.heap.pop()
-        self.swap(i, self.size() - 1)
-        d = self.heap.pop()
-        self.push_up(i)
-        self.push_down(i)
-        return d
-
-    def replace(self, i: int, x):
-        """Replace node at index `i` with `x`.
-
-        `x` can either be a key for a HeapNode` object,
-        which is created automatically by this function,
-        and `x` becomes the key and value of that same `HeapNode` object,
-        or it can be (directly) a `HeapNode` object.
-
-        If `x` is NOT a `HeapNode`, it should be comparable
-        with the other keys in the other `HeapNode` objects.
-        If this is not true, the behaviour of this function is undefined.
-
-        If `x` is a `HeapNode`,
-        it's the responsibility of the client of this function
-        to make sure it's a "valid" `HeapNode` object,
-        i.e. it's comparable to the other `HeapNode` objects in this heap.
-
-        **Time Complexity:** O(log2 n)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, HeapNode):
-            x = HeapNode(x)
-        d = self.heap[i]
-        self.heap[i] = x
-        self.push_up(i)
-        self.push_down(i)
-        return d
-
-    def find_max_index(self) -> int:
-        """Returns the index of the maximum element in this min-max heap.
-
-        **Time Complexity:** O(1)."""
-        if self.is_empty():
-            return -1
-        elif self.size() == 1:
-            return 0
-        elif self.size() == 2:
-            return 1
-        else:
-            return 1 if self.heap[1] > self.heap[2] else 2
-
-    def index_of_min(self, i: int) -> int:
-        """Returns the index of the smallest element
-        among the children and grandchildren of the node at index `i`.
-
-        **Time Complexity:** O(1)."""
-        m = l = self.left_index(i)
-        r = self.right_index(i)
-
-        if r != -1 and self.heap[r] < self.heap[m]:
-            m = r
-
-        if l != -1:
-            gll = self.left_index(l)
-            if gll != -1 and self.heap[gll] < self.heap[m]:
-                m = gll
-            glr = self.right_index(l)
-            if glr != -1 and self.heap[glr] < self.heap[m]:
-                m = glr
-
-        if r != -1:
-            grl = self.left_index(r)
-            if grl != -1 and self.heap[grl] < self.heap[m]:
-                m = grl
-            grr = self.right_index(r)
-            if grr != -1 and self.heap[grr] < self.heap[m]:
-                m = grr
-
-        return m
-
-    def index_of_max(self, i: int) -> int:
-        """Returns the index of the largest element
-        among the children and grandchildren of the node at index `i`.
-
-        **Time Complexity:** O(1)."""
-        m = l = self.left_index(i)
-        r = self.right_index(i)
-
-        if r != -1 and self.heap[r] > self.heap[m]:
-            m = r
-
-        if l != -1:
-            gll = self.left_index(l)
-            if gll != -1 and self.heap[gll] > self.heap[m]:
-                m = gll
-            glr = self.right_index(l)
-            if glr != -1 and self.heap[glr] > self.heap[m]:
-                m = glr
-
-        if r != -1:
-            grl = self.left_index(r)
-            if grl != -1 and self.heap[grl] > self.heap[m]:
-                m = grl
-            grr = self.right_index(r)
-            if grr != -1 and self.heap[grr] > self.heap[m]:
-                m = grr
-
-        return m
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • MinMaxHeap
  • -
  • ands.ds.heap.BinaryHeap
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self, ls=None)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, ls=None):
-    BinaryHeap.__init__(self, ls)
-
-
-
- -
- - -
-
-

def add(

self, x)

-
- - - - -

Adds x to this heap.

-

In practice, it places x at an available leaf, -then "bubbles up" from there, -in order to maintain the heap property.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(log2 n).

-
- -
-
def add(self, x) -> None:
-    """Adds `x` to this heap.
-    In practice, it places `x` at an available leaf,
-    then "bubbles up" from there,
-    in order to maintain the heap property.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(log2 n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    self.heap.append(x)
-    if self.size() > 1:
-        self.push_up(self.size() - 1)
-
-
-
- -
- - -
-
-

def build_heap(

self)

-
- - - - -

Builds the heap data structure from self.heap.

-
- -
-
def build_heap(self) -> list:
-    """Builds the heap data structure from `self.heap`."""
-    if self.heap:
-        for index in range(len(self.heap) // 2, -1, -1):
-            self.push_down(index)
-    return self.heap
-
-
-
- -
- - -
-
-

def clear(

self)

-
- - - - -

Clears all nodes from this heap. -This mean that if you call is_empty, -it will return True.

-

Time Complexity: O(1).

-
- -
-
def clear(self) -> None:
-    """Clears all nodes from this heap.
-    This mean that if you call `is_empty`,
-    it will return `True`.
-    **Time Complexity:** O(1)."""
-    self.heap.clear()
-
-
-
- -
- - -
-
-

def contains(

self, x)

-
- - - - -

Returns True, if x is in this heap. False otherwise.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(n).

-
- -
-
def contains(self, x) -> bool:
-    """Returns `True`, if `x` is in this heap. `False` otherwise.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(n)."""
-    return self.search(x) != -1
-
-
-
- -
- - -
-
-

def delete(

self, i)

-
- - - - -

Deletes and returns the HeapNode object at index i.

-

IndexError is raised if i is not a valid index.

-

Implementation based on: -http://www.math.clemson.edu/~warner/M865/HeapDelete.html

-

Time Complexity: O(log2 n).

-
- -
-
def delete(self, i: int) -> HeapNode:
-    """Deletes and returns the `HeapNode` object at index `i`.
-    `IndexError` is raised if `i` is not a valid index.
-    Implementation based on:
-    [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
-    **Time Complexity:** O(log2 n)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    if i == self.size() - 1:
-        return self.heap.pop()
-    self.swap(i, self.size() - 1)
-    d = self.heap.pop()
-    self.push_up(i)
-    self.push_down(i)
-    return d
-
-
-
- -
- - -
-
-

def find_max(

self)

-
- - - - -

Returns the HeapNode object representing the maximum element.

-

Time Complexity: O(1).

-
- -
-
def find_max(self) -> HeapNode:
-    """Returns the `HeapNode` object representing the maximum element.
-    **Time Complexity:** O(1)."""
-    if not self.is_empty():
-        return self.heap[self.find_max_index()]
-
-
-
- -
- - -
-
-

def find_max_index(

self)

-
- - - - -

Returns the index of the maximum element in this min-max heap.

-

Time Complexity: O(1).

-
- -
-
def find_max_index(self) -> int:
-    """Returns the index of the maximum element in this min-max heap.
-    **Time Complexity:** O(1)."""
-    if self.is_empty():
-        return -1
-    elif self.size() == 1:
-        return 0
-    elif self.size() == 2:
-        return 1
-    else:
-        return 1 if self.heap[1] > self.heap[2] else 2
-
-
-
- -
- - -
-
-

def find_min(

self)

-
- - - - -

Returns the HeapNode object representing the minimum element.

-

Time Complexity: O(1).

-
- -
-
def find_min(self) -> HeapNode:
-    """Returns the `HeapNode` object representing the minimum element.
-    **Time Complexity:** O(1)."""
-    if not self.is_empty():
-        return self.heap[0]
-
-
-
- -
- - -
-
-

def grandparent_index(

self, i)

-
- - - - -

Returns the grandparent's index of the node at index i.

-

-1 is returned either if i has not a parent or -the parent of i does not have a parent.

-

Time Complexity: O(1).

-
- -
-
def grandparent_index(self, i: int) -> int:
-    """Returns the grandparent's index of the node at index `i`.
-    -1 is returned either if `i` has not a parent or
-    the parent of `i` does not have a parent.
-    **Time Complexity:** O(1)."""
-    p = self.parent_index(i)
-    return -1 if p == -1 else self.parent_index(p)
-
-
-
- -
- - -
-
-

def has_children(

self, i)

-
- - - - -

Returns True if the node at index i -has at least one child, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def has_children(self, i: int) -> bool:
-    """Returns `True` if the node at index `i`
-    has at least one child, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return self.left_index(i) != -1 or self.right_index(i) != -1
-
-
-
- -
- - -
-
-

def index_of_max(

self, i)

-
- - - - -

Returns the index of the largest element -among the children and grandchildren of the node at index i.

-

Time Complexity: O(1).

-
- -
-
def index_of_max(self, i: int) -> int:
-    """Returns the index of the largest element
-    among the children and grandchildren of the node at index `i`.
-    **Time Complexity:** O(1)."""
-    m = l = self.left_index(i)
-    r = self.right_index(i)
-    if r != -1 and self.heap[r] > self.heap[m]:
-        m = r
-    if l != -1:
-        gll = self.left_index(l)
-        if gll != -1 and self.heap[gll] > self.heap[m]:
-            m = gll
-        glr = self.right_index(l)
-        if glr != -1 and self.heap[glr] > self.heap[m]:
-            m = glr
-    if r != -1:
-        grl = self.left_index(r)
-        if grl != -1 and self.heap[grl] > self.heap[m]:
-            m = grl
-        grr = self.right_index(r)
-        if grr != -1 and self.heap[grr] > self.heap[m]:
-            m = grr
-    return m
-
-
-
- -
- - -
-
-

def index_of_min(

self, i)

-
- - - - -

Returns the index of the smallest element -among the children and grandchildren of the node at index i.

-

Time Complexity: O(1).

-
- -
-
def index_of_min(self, i: int) -> int:
-    """Returns the index of the smallest element
-    among the children and grandchildren of the node at index `i`.
-    **Time Complexity:** O(1)."""
-    m = l = self.left_index(i)
-    r = self.right_index(i)
-    if r != -1 and self.heap[r] < self.heap[m]:
-        m = r
-    if l != -1:
-        gll = self.left_index(l)
-        if gll != -1 and self.heap[gll] < self.heap[m]:
-            m = gll
-        glr = self.right_index(l)
-        if glr != -1 and self.heap[glr] < self.heap[m]:
-            m = glr
-    if r != -1:
-        grl = self.left_index(r)
-        if grl != -1 and self.heap[grl] < self.heap[m]:
-            m = grl
-        grr = self.right_index(r)
-        if grr != -1 and self.heap[grr] < self.heap[m]:
-            m = grr
-    return m
-
-
-
- -
- - -
-
-

def is_child(

self, c, i)

-
- - - - -

Returns True if c is a child of i. False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_child(self, c: int, i: int) -> bool:
-    """Returns `True` if `c` is a child of `i`. `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(c) or not self.is_good_index(i):
-        raise IndexError("i or c are not valid indexes.")
-    return c == self.left_index(i) or c == self.right_index(i)
-
-
-
- -
- - -
-
-

def is_empty(

self)

-
- - - - -

Returns True if this heap is empty.

-

Time Complexity: O(1).

-
- -
-
def is_empty(self) -> bool:
-    """Returns `True` if this heap is empty.
-    **Time Complexity:** O(1)."""
-    return self.size() == 0
-
-
-
- -
- - -
-
-

def is_good_index(

self, i)

-
- - - - -

Returns True if i is valid index for self.heap, -False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_good_index(self, i: int) -> bool:
-    """Returns `True` if `i` is valid index for `self.heap`,
-    `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not isinstance(i, int):
-        raise TypeError("indexes can only be int.")
-    return False if (i < 0 or i >= self.size()) else True
-
-
-
- -
- - -
-
-

def is_grandchild(

self, g, i)

-
- - - - -

Returns True if g is a grandchild of i. False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_grandchild(self, g: int, i: int) -> bool:
-    """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
-    **Time Complexity:** O(1)."""
-    l = self.left_index(i)
-    if l == -1:
-        assert self.right_index(i) == -1
-        if not self.is_good_index(g):
-            raise IndexError("g is not a valid index.")
-        return False
-    r = self.right_index(i)
-    if r == -1:
-        return self.is_child(g, l)
-    else:
-        return self.is_child(g, l) or self.is_child(g, r)
-
-
-
- -
- - -
-
-

def is_grandparent(

self, g, i)

-
- - - - -

Returns True if g is the index of the grandparent -of the node at i, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_grandparent(self, g: int, i: int) -> bool:
-    """Returns `True` if `g` is the index of the grandparent
-    of the node at `i`, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(g):
-        raise IndexError("g is not a valid index.")
-    p = self.parent_index(i)
-    return False if p == -1 else self.is_parent(g, p)
-
-
-
- -
- - -
-
-

def is_on_even_level(

self, i)

-
- - - - -

Returns True if node at index i is on a even-level, -i.e., if i is on a level multiple of 2 (0, 2, 4, 6,...). -If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(int(math.log2(i + 1) % 2) == 0).

-
- -
-
def is_on_even_level(self, i: int) -> bool:
-    """Returns `True` if node at index `i` is on a even-level,
-    i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return int(math.log2(i + 1) % 2) == 0
-
-
-
- -
- - -
-
-

def is_on_odd_level(

self, i)

-
- - - - -

Returns True (False) if self.is_on_even_level(i) returns False (True).

-
- -
-
def is_on_odd_level(self, i: int) -> bool:
-    """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
-    return not self.is_on_even_level(i)
-
-
-
- -
- - -
-
-

def is_parent(

self, p, i)

-
- - - - -

Returns True if p is the index of the parent -of the node at i, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_parent(self, p: int, i: int) -> bool:
-    """Returns `True` if `p` is the index of the parent
-    of the node at `i`, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(p):
-        raise IndexError("p is not a valid index.")
-    return self.parent_index(i) == p
-
-
-
- -
- - -
-
-

def left_index(

self, i)

-
- - - - -

Returns the left child's index of the node at index i, -if it exists, otherwise this function returns -1.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def left_index(self, i: int) -> int:
-    """Returns the left child's index of the node at index `i`,
-    if it exists, otherwise this function returns -1.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    left = i * 2 + 1
-    return left if self.is_good_index(left) else -1
-
-
-
- -
- - -
-
-

def merge(

self, o)

-
- - - - -

Merges this heap with the o heap.

-

Returns the list object representing internally the new merged heap.

-

Time Complexity: O(n + m).

-

Time complexity analysis based on: -http://stackoverflow.com/a/29197855/3924118.

-
- -
-
def merge(self, o) -> list:
-    """Merges this heap with the `o` heap.
-    Returns the `list` object representing internally the new merged heap.
-    **Time Complexity:** O(n + m).
-    Time complexity analysis based on:
-    [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
-    self.heap += o.heap
-    return self.build_heap()
-
-
-
- -
- - -
-
-

def parent_index(

self, i)

-
- - - - -

Returns the parent's index of the node at index i. -If i = 0, then -1 is returned, because the root has no parent.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def parent_index(self, i: int) -> int:
-    """Returns the parent's index of the node at index `i`.
-    If `i = 0`, then -1 is returned, because the root has no parent.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return -1 if i == 0 else (i - 1) // 2
-
-
-
- -
- - -
-
-

def push_down(

self, i)

-
- - - - -

Also called bubble-down or shift-down.

-
- -
-
def push_down(self, i: int) -> None:
-    """Also called `bubble-down` or `shift-down`."""
-    if self.is_on_even_level(i):
-        self.push_down_min(i)
-    else:
-        self.push_down_max(i)
-
-
-
- -
- - -
-
-

def push_down_max(

self, i)

-
- - - - -

Helper method for push_down.

-
- -
-
def push_down_max(self, i: int) -> None:
-    """Helper method for `push_down`."""
-    if self.has_children(i):
-        m = self.index_of_max(i)
-        if self.is_grandchild(m, i):
-            if self.heap[m] > self.heap[i]:
-                self.swap(i, m)
-                mp = self.parent_index(m)
-                if mp != -1 and self.heap[m] < self.heap[mp]:
-                    self.swap(m, mp)
-                self.push_down_max(m)
-        else:  # self.heap[m] is a child of self.heap[i]
-            if self.heap[m] > self.heap[i]:
-                self.swap(i, m)
-
-
-
- -
- - -
-
-

def push_down_min(

self, i)

-
- - - - -

Helper method for push_down.

-
- -
-
def push_down_min(self, i: int) -> None:
-    """Helper method for `push_down`."""
-    if self.has_children(i):
-        m = self.index_of_min(i)
-        if self.is_grandchild(m, i):
-            if self.heap[m] < self.heap[i]:
-                self.swap(i, m)
-                mp = self.parent_index(m)
-                if mp != -1 and self.heap[m] > self.heap[mp]:
-                    self.swap(m, mp)
-                self.push_down_min(m)
-        else:  # self.heap[m] is a child of self.heap[i]
-            if self.heap[m] < self.heap[i]:
-                self.swap(i, m)
-
-
-
- -
- - -
-
-

def push_up(

self, i)

-
- - - - -

Also called bubble-up or shift-up.

-
- -
-
def push_up(self, i: int) -> None:
-    """Also called `bubble-up` or `shift-up`."""
-    p = self.parent_index(i)
-    # Let x be the element at index i.
-    # If x has a parent at position p, we call it y.
-    if self.is_on_even_level(i):
-        if p != -1 and self.heap[i] > self.heap[p]:
-            # If x is greater than y, swap x with y.
-            # Now, x is at index p, and y at index i.
-            # push_up_max from the new index of x, i.e. p.
-            self.swap(i, p)
-            self.push_up_max(p)
-        else:
-            # x does not have a parent OR x <= y.
-            self.push_up_min(i)
-    else:
-        # Odd or max level.
-        if p != -1 and self.heap[i] < self.heap[p]:
-            self.swap(i, p)
-            self.push_up_min(p)
-        else:
-            self.push_up_max(i)
-
-
-
- -
- - -
-
-

def push_up_max(

self, i)

-
- - - - -

Helper method for push_up.

-
- -
-
def push_up_max(self, i: int) -> None:
-    """Helper method for `push_up`."""
-    g = self.grandparent_index(i)
-    if g != -1 and self.heap[i] > self.heap[g]:
-        self.swap(i, g)
-        self.push_up_max(g)
-
-
-
- -
- - -
-
-

def push_up_min(

self, i)

-
- - - - -

Helper method for push_up.

-
- -
-
def push_up_min(self, i: int) -> None:
-    """Helper method for `push_up`."""
-    g = self.grandparent_index(i)
-    # Let x be the element at index i.
-    # If x has a grandparent at position g,
-    # we call it z.
-    # If the z exists and x is smaller than z,
-    # swap x and z. Now, x is at index g and z at index i.
-    if g != -1 and self.heap[i] < self.heap[g]:
-        self.swap(i, g)
-        self.push_up_min(g)
-
-
-
- -
- - -
-
-

def remove_max(

self)

-
- - - - -

Deletes and returns the HeapNode object representing the maximum element.

-

Time Complexity: O(log2 n).

-
- -
-
def remove_max(self) -> HeapNode:
-    """Deletes and returns the `HeapNode` object representing the maximum element.
-    **Time Complexity:** O(log2 n)."""
-    if not self.is_empty():
-        return self.delete(self.find_max_index())
-
-
-
- -
- - -
-
-

def remove_min(

self)

-
- - - - -

Deletes and returns the HeapNode object representing the minimum element.

-

Time Complexity: O(log2 n).

-
- -
-
def remove_min(self) -> HeapNode:
-    """Deletes and returns the `HeapNode` object representing the minimum element.
-    **Time Complexity:** O(log2 n)."""
-    if not self.is_empty():
-        return self.delete(0)
-
-
-
- -
- - -
-
-

def replace(

self, i, x)

-
- - - - -

Replace node at index i with x.

-

x can either be a key for a HeapNodeobject, -which is created automatically by this function, -andxbecomes the key and value of that sameHeapNodeobject, -or it can be (directly) aHeapNode` object.

-

If x is NOT a HeapNode, it should be comparable -with the other keys in the other HeapNode objects. -If this is not true, the behaviour of this function is undefined.

-

If x is a HeapNode, -it's the responsibility of the client of this function -to make sure it's a "valid" HeapNode object, -i.e. it's comparable to the other HeapNode objects in this heap.

-

Time Complexity: O(log2 n).

-
- -
-
def replace(self, i: int, x):
-    """Replace node at index `i` with `x`.
-    `x` can either be a key for a HeapNode` object,
-    which is created automatically by this function,
-    and `x` becomes the key and value of that same `HeapNode` object,
-    or it can be (directly) a `HeapNode` object.
-    If `x` is NOT a `HeapNode`, it should be comparable
-    with the other keys in the other `HeapNode` objects.
-    If this is not true, the behaviour of this function is undefined.
-    If `x` is a `HeapNode`,
-    it's the responsibility of the client of this function
-    to make sure it's a "valid" `HeapNode` object,
-    i.e. it's comparable to the other `HeapNode` objects in this heap.
-    **Time Complexity:** O(log2 n)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    d = self.heap[i]
-    self.heap[i] = x
-    self.push_up(i)
-    self.push_down(i)
-    return d
-
-
-
- -
- - -
-
-

def right_index(

self, i)

-
- - - - -

Returns the right child's index of the node at index i, -if it exists, otherwise this function returns -1.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def right_index(self, i: int) -> int:
-    """Returns the right child's index of the node at index `i`,
-    if it exists, otherwise this function returns -1.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    right = i * 2 + 2
-    return right if self.is_good_index(right) else -1
-
-
-
- -
- - -
-
-

def search(

self, x)

-
- - - - -

Searches for x in this heap, -and, if present, returns its index, otherwise returns -1.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(n).

-
- -
-
def search(self, x) -> int:
-    """Searches for `x` in this heap,
-    and, if present, returns its index, otherwise returns -1.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    for i, node in enumerate(self.heap):
-        if node == x:
-            return i
-    return -1
-
-
-
- -
- - -
-
-

def search_by_value(

self, val)

-
- - - - -

Returns the index of the HeapNode object with value=val. --1 is returned if no such a HeapNode object exists.

-

If val and the values in this heap are not comparable, -the behaviour of this method is undefined.

-

By construction, HeapNode objects can't be initialized with None values, -but that field could also be set manually after creation.

-

Time Complexity: O(n).

-
- -
-
def search_by_value(self, val) -> int:
-    """Returns the index of the `HeapNode` object with `value=val`.
-    -1 is returned if no such a `HeapNode` object exists.
-    If `val` and the values in this heap are not comparable,
-    the behaviour of this method is undefined.
-    By construction, HeapNode objects can't be initialized with None values,
-    but that field could also be set manually after creation.
-    **Time Complexity:** O(n)."""
-    for i, node in enumerate(self.heap):
-        if node.value == val:
-            return i
-    return -1
-
-
-
- -
- - -
-
-

def size(

self)

-
- - - - -

Returns the size of this heaps.

-

Time Complexity: O(1).

-
- -
-
def size(self) -> int:
-    """Returns the size of this heaps.
-    **Time Complexity:** O(1)."""
-    return len(self.heap)
-
-
-
- -
- - -
-
-

def swap(

self, i, j)

-
- - - - -

Swaps elements at indexes i and j, -if they are valid indexes, -otherwise an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def swap(self, i: int, j: int) -> None:
-    """Swaps elements at indexes `i` and `j`,
-    if they are valid indexes,
-    otherwise an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if self.is_good_index(i) and self.is_good_index(j):
-        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
-    else:
-        raise IndexError("i or j are not valid indexes.")
-
-
-
- -
- -
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/MinPriorityQueue.m.html b/docs/ands/ds/MinPriorityQueue.m.html deleted file mode 100644 index 1a52427d..00000000 --- a/docs/ands/ds/MinPriorityQueue.m.html +++ /dev/null @@ -1,2360 +0,0 @@ - - - - - - ands.ds.MinPriorityQueue API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.MinPriorityQueue module

-

Author: Nelson Brochado

-

Created: 24/01/2017

- - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-
-Author: Nelson Brochado
-
-Created: 24/01/2017
-"""
-
-import sys
-
-from ands.ds.heap import HeapNode
-from ands.ds.MinHeap import MinHeap
-
-
-class MinPriorityQueue(MinHeap):
-    def __init__(self, ls=None):
-        """If `ls` is provided, it should be a list of tuples,
-        each of which contains 2 items:
-        The first item of the tuple is the priority of the element,
-        the second item of the tuple is the element.
-        A smaller number means a higher priority."""
-        MinHeap.__init__(self, ls)
-
-    def insert(self, element: object, priority=sys.maxsize):
-        """Adds `element` to this min priority queue.
-
-        You can specify the priority of the element
-        by assigning a value to "priority".
-
-        Note that the value assigned to priority
-        should be an object that overrides functions
-        such as `__lt__`, `__gt__`, `__le__`, `__ge__`,
-        `__eq__`, `__ne__`, in other words,
-        it should be a comparable object,
-        and it should be comparable to the other priorities
-        of the other elements.
-
-        If element is an `HeapNode`, the `priority` argument's value is ignored,
-        and the priority of the key field of `HeapNode` is used as priority."""
-        if not isinstance(element, HeapNode):
-            self.add(HeapNode(key=priority, value=element))
-        else:
-            self.add(element)
-
-    def extract_min(self, priority=False):
-        """Removes and returns the element with highest priority to exit from the queue.
-
-        In this `MinPriorityQueue` implementation,
-        if A has an higher priority than B,
-        then `A.priority <= B.priority`.
-
-        If `priority` is set to `True`, a tuple is returned,
-        whose first item is the initial added element
-        and the second item is the priority of the element.
-
-        If self is empty, `None` is returned.
-
-        **Time Complexity**: O(log2(n))."""
-        m = self.remove_min()
-        if m is not None:
-            if priority:
-                return (m.value, m.key)
-            else:
-                return m.value
-
-    def peek(self, priority=False):
-        """Returns (without removing) the element with highest priority.
-
-        In this `MinPriorityQueue` implementation,
-        if A has an higher priority than B,
-        then `A.priority <= B.priority`.
-
-        If priority is set to True,
-        a tuple of the form (element, priority) is returned,
-        otherwise only element is returned,
-        where element is the element in self with highest priority.
-
-        **Time Complexity**: O(1)."""
-        m = self.find_min()
-        if m is not None:
-            if priority:
-                return (m.value, m.key)
-            else:
-                return m.value
-
-    def contains(self, element):
-        """Returns `True` if element is in this min-priority queue, `False` otherwise.
-
-        **Time Complexity**: O(n)."""
-        return self.search_by_value(element) != -1
-
-    def change_priority(self, element, new_priority):
-        """Assigns `new_priority` to be the new priority of `element`.
-
-        If `element` is not in this min-priority queue,
-        a `LookupError` is raised.
-
-        **Time Complexity**: O(n)."""
-        i = self.search_by_value(element)
-        if i == -1:
-            raise LookupError("No element found.")
-        self.replace(i, HeapNode(key=new_priority, value=element))
-
-
- -
- -
- - -

Classes

- -
-

class MinPriorityQueue

- - -

Abstract class to represent binary heaps.

-

MinHeap, MaxHeap and MinMaxHeap all derive from this class.

-
- -
-
class MinPriorityQueue(MinHeap):
-    def __init__(self, ls=None):
-        """If `ls` is provided, it should be a list of tuples,
-        each of which contains 2 items:
-        The first item of the tuple is the priority of the element,
-        the second item of the tuple is the element.
-        A smaller number means a higher priority."""
-        MinHeap.__init__(self, ls)
-
-    def insert(self, element: object, priority=sys.maxsize):
-        """Adds `element` to this min priority queue.
-
-        You can specify the priority of the element
-        by assigning a value to "priority".
-
-        Note that the value assigned to priority
-        should be an object that overrides functions
-        such as `__lt__`, `__gt__`, `__le__`, `__ge__`,
-        `__eq__`, `__ne__`, in other words,
-        it should be a comparable object,
-        and it should be comparable to the other priorities
-        of the other elements.
-
-        If element is an `HeapNode`, the `priority` argument's value is ignored,
-        and the priority of the key field of `HeapNode` is used as priority."""
-        if not isinstance(element, HeapNode):
-            self.add(HeapNode(key=priority, value=element))
-        else:
-            self.add(element)
-
-    def extract_min(self, priority=False):
-        """Removes and returns the element with highest priority to exit from the queue.
-
-        In this `MinPriorityQueue` implementation,
-        if A has an higher priority than B,
-        then `A.priority <= B.priority`.
-
-        If `priority` is set to `True`, a tuple is returned,
-        whose first item is the initial added element
-        and the second item is the priority of the element.
-
-        If self is empty, `None` is returned.
-
-        **Time Complexity**: O(log2(n))."""
-        m = self.remove_min()
-        if m is not None:
-            if priority:
-                return (m.value, m.key)
-            else:
-                return m.value
-
-    def peek(self, priority=False):
-        """Returns (without removing) the element with highest priority.
-
-        In this `MinPriorityQueue` implementation,
-        if A has an higher priority than B,
-        then `A.priority <= B.priority`.
-
-        If priority is set to True,
-        a tuple of the form (element, priority) is returned,
-        otherwise only element is returned,
-        where element is the element in self with highest priority.
-
-        **Time Complexity**: O(1)."""
-        m = self.find_min()
-        if m is not None:
-            if priority:
-                return (m.value, m.key)
-            else:
-                return m.value
-
-    def contains(self, element):
-        """Returns `True` if element is in this min-priority queue, `False` otherwise.
-
-        **Time Complexity**: O(n)."""
-        return self.search_by_value(element) != -1
-
-    def change_priority(self, element, new_priority):
-        """Assigns `new_priority` to be the new priority of `element`.
-
-        If `element` is not in this min-priority queue,
-        a `LookupError` is raised.
-
-        **Time Complexity**: O(n)."""
-        i = self.search_by_value(element)
-        if i == -1:
-            raise LookupError("No element found.")
-        self.replace(i, HeapNode(key=new_priority, value=element))
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • MinPriorityQueue
  • -
  • ands.ds.MinHeap.MinHeap
  • -
  • ands.ds.heap.BinaryHeap
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self, ls=None)

-
- - - - -

If ls is provided, it should be a list of tuples, -each of which contains 2 items: -The first item of the tuple is the priority of the element, -the second item of the tuple is the element. -A smaller number means a higher priority.

-
- -
-
def __init__(self, ls=None):
-    """If `ls` is provided, it should be a list of tuples,
-    each of which contains 2 items:
-    The first item of the tuple is the priority of the element,
-    the second item of the tuple is the element.
-    A smaller number means a higher priority."""
-    MinHeap.__init__(self, ls)
-
-
-
- -
- - -
-
-

def add(

self, x)

-
- - - - -

Adds x to this heap.

-

In practice, it places x at an available leaf, -then "bubbles up" from there, -in order to maintain the heap property.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(log2 n).

-
- -
-
def add(self, x) -> None:
-    """Adds `x` to this heap.
-    In practice, it places `x` at an available leaf,
-    then "bubbles up" from there,
-    in order to maintain the heap property.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(log2 n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    self.heap.append(x)
-    if self.size() > 1:
-        self.push_up(self.size() - 1)
-
-
-
- -
- - -
-
-

def build_heap(

self)

-
- - - - -

Builds the heap data structure from self.heap.

-
- -
-
def build_heap(self) -> list:
-    """Builds the heap data structure from `self.heap`."""
-    if self.heap:
-        for index in range(len(self.heap) // 2, -1, -1):
-            self.push_down(index)
-    return self.heap
-
-
-
- -
- - -
-
-

def change_priority(

self, element, new_priority)

-
- - - - -

Assigns new_priority to be the new priority of element.

-

If element is not in this min-priority queue, -a LookupError is raised.

-

Time Complexity: O(n).

-
- -
-
def change_priority(self, element, new_priority):
-    """Assigns `new_priority` to be the new priority of `element`.
-    If `element` is not in this min-priority queue,
-    a `LookupError` is raised.
-    **Time Complexity**: O(n)."""
-    i = self.search_by_value(element)
-    if i == -1:
-        raise LookupError("No element found.")
-    self.replace(i, HeapNode(key=new_priority, value=element))
-
-
-
- -
- - -
-
-

def clear(

self)

-
- - - - -

Clears all nodes from this heap. -This mean that if you call is_empty, -it will return True.

-

Time Complexity: O(1).

-
- -
-
def clear(self) -> None:
-    """Clears all nodes from this heap.
-    This mean that if you call `is_empty`,
-    it will return `True`.
-    **Time Complexity:** O(1)."""
-    self.heap.clear()
-
-
-
- -
- - -
-
-

def contains(

self, element)

-
- - - - -

Returns True if element is in this min-priority queue, False otherwise.

-

Time Complexity: O(n).

-
- -
-
def contains(self, element):
-    """Returns `True` if element is in this min-priority queue, `False` otherwise.
-    **Time Complexity**: O(n)."""
-    return self.search_by_value(element) != -1
-
-
-
- -
- - -
-
-

def delete(

self, i)

-
- - - - -

Deletes and returns the HeapNode object at index i.

-

IndexError is raised if i is not a valid index.

-

Implementation based on: -http://www.math.clemson.edu/~warner/M865/HeapDelete.html

-

Time Complexity: O(log2 h), -where h is the number of nodes rooted at i.

-
- -
-
def delete(self, i: int) -> HeapNode:
-    """Deletes and returns the `HeapNode` object at index `i`.
-    `IndexError` is raised if `i` is not a valid index.
-    Implementation based on:
-    [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
-    **Time Complexity:** O(log2 h),
-    where `h` is the number of nodes rooted at `i`."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    if i == self.size() - 1:
-        return self.heap.pop()
-    self.swap(i, self.size() - 1)
-    d = self.heap.pop()
-    self.push_down(i)
-    return d
-
-
-
- -
- - -
-
-

def extract_min(

self, priority=False)

-
- - - - -

Removes and returns the element with highest priority to exit from the queue.

-

In this MinPriorityQueue implementation, -if A has an higher priority than B, -then A.priority <= B.priority.

-

If priority is set to True, a tuple is returned, -whose first item is the initial added element -and the second item is the priority of the element.

-

If self is empty, None is returned.

-

Time Complexity: O(log2(n)).

-
- -
-
def extract_min(self, priority=False):
-    """Removes and returns the element with highest priority to exit from the queue.
-    In this `MinPriorityQueue` implementation,
-    if A has an higher priority than B,
-    then `A.priority <= B.priority`.
-    If `priority` is set to `True`, a tuple is returned,
-    whose first item is the initial added element
-    and the second item is the priority of the element.
-    If self is empty, `None` is returned.
-    **Time Complexity**: O(log2(n))."""
-    m = self.remove_min()
-    if m is not None:
-        if priority:
-            return (m.value, m.key)
-        else:
-            return m.value
-
-
-
- -
- - -
-
-

def find_min(

self)

-
- - - - -

Returns (without removing) the smallest element in this min-heap.

-

Time Complexity: O(1).

-
- -
-
def find_min(self) -> HeapNode:
-    """Returns (without removing) the smallest element in this min-heap.
-    **Time Complexity:** O(1)."""
-    return self.heap[0] if not self.is_empty() else None
-
-
-
- -
- - -
-
-

def grandparent_index(

self, i)

-
- - - - -

Returns the grandparent's index of the node at index i.

-

-1 is returned either if i has not a parent or -the parent of i does not have a parent.

-

Time Complexity: O(1).

-
- -
-
def grandparent_index(self, i: int) -> int:
-    """Returns the grandparent's index of the node at index `i`.
-    -1 is returned either if `i` has not a parent or
-    the parent of `i` does not have a parent.
-    **Time Complexity:** O(1)."""
-    p = self.parent_index(i)
-    return -1 if p == -1 else self.parent_index(p)
-
-
-
- -
- - -
-
-

def has_children(

self, i)

-
- - - - -

Returns True if the node at index i -has at least one child, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def has_children(self, i: int) -> bool:
-    """Returns `True` if the node at index `i`
-    has at least one child, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return self.left_index(i) != -1 or self.right_index(i) != -1
-
-
-
- -
- - -
-
-

def insert(

self, element, priority=9223372036854775807)

-
- - - - -

Adds element to this min priority queue.

-

You can specify the priority of the element -by assigning a value to "priority".

-

Note that the value assigned to priority -should be an object that overrides functions -such as __lt__, __gt__, __le__, __ge__, -__eq__, __ne__, in other words, -it should be a comparable object, -and it should be comparable to the other priorities -of the other elements.

-

If element is an HeapNode, the priority argument's value is ignored, -and the priority of the key field of HeapNode is used as priority.

-
- -
-
def insert(self, element: object, priority=sys.maxsize):
-    """Adds `element` to this min priority queue.
-    You can specify the priority of the element
-    by assigning a value to "priority".
-    Note that the value assigned to priority
-    should be an object that overrides functions
-    such as `__lt__`, `__gt__`, `__le__`, `__ge__`,
-    `__eq__`, `__ne__`, in other words,
-    it should be a comparable object,
-    and it should be comparable to the other priorities
-    of the other elements.
-    If element is an `HeapNode`, the `priority` argument's value is ignored,
-    and the priority of the key field of `HeapNode` is used as priority."""
-    if not isinstance(element, HeapNode):
-        self.add(HeapNode(key=priority, value=element))
-    else:
-        self.add(element)
-
-
-
- -
- - -
-
-

def is_child(

self, c, i)

-
- - - - -

Returns True if c is a child of i. False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_child(self, c: int, i: int) -> bool:
-    """Returns `True` if `c` is a child of `i`. `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(c) or not self.is_good_index(i):
-        raise IndexError("i or c are not valid indexes.")
-    return c == self.left_index(i) or c == self.right_index(i)
-
-
-
- -
- - -
-
-

def is_empty(

self)

-
- - - - -

Returns True if this heap is empty.

-

Time Complexity: O(1).

-
- -
-
def is_empty(self) -> bool:
-    """Returns `True` if this heap is empty.
-    **Time Complexity:** O(1)."""
-    return self.size() == 0
-
-
-
- -
- - -
-
-

def is_good_index(

self, i)

-
- - - - -

Returns True if i is valid index for self.heap, -False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_good_index(self, i: int) -> bool:
-    """Returns `True` if `i` is valid index for `self.heap`,
-    `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not isinstance(i, int):
-        raise TypeError("indexes can only be int.")
-    return False if (i < 0 or i >= self.size()) else True
-
-
-
- -
- - -
-
-

def is_grandchild(

self, g, i)

-
- - - - -

Returns True if g is a grandchild of i. False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_grandchild(self, g: int, i: int) -> bool:
-    """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
-    **Time Complexity:** O(1)."""
-    l = self.left_index(i)
-    if l == -1:
-        assert self.right_index(i) == -1
-        if not self.is_good_index(g):
-            raise IndexError("g is not a valid index.")
-        return False
-    r = self.right_index(i)
-    if r == -1:
-        return self.is_child(g, l)
-    else:
-        return self.is_child(g, l) or self.is_child(g, r)
-
-
-
- -
- - -
-
-

def is_grandparent(

self, g, i)

-
- - - - -

Returns True if g is the index of the grandparent -of the node at i, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_grandparent(self, g: int, i: int) -> bool:
-    """Returns `True` if `g` is the index of the grandparent
-    of the node at `i`, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(g):
-        raise IndexError("g is not a valid index.")
-    p = self.parent_index(i)
-    return False if p == -1 else self.is_parent(g, p)
-
-
-
- -
- - -
-
-

def is_on_even_level(

self, i)

-
- - - - -

Returns True if node at index i is on a even-level, -i.e., if i is on a level multiple of 2 (0, 2, 4, 6,...). -If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(int(math.log2(i + 1) % 2) == 0).

-
- -
-
def is_on_even_level(self, i: int) -> bool:
-    """Returns `True` if node at index `i` is on a even-level,
-    i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return int(math.log2(i + 1) % 2) == 0
-
-
-
- -
- - -
-
-

def is_on_odd_level(

self, i)

-
- - - - -

Returns True (False) if self.is_on_even_level(i) returns False (True).

-
- -
-
def is_on_odd_level(self, i: int) -> bool:
-    """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
-    return not self.is_on_even_level(i)
-
-
-
- -
- - -
-
-

def is_parent(

self, p, i)

-
- - - - -

Returns True if p is the index of the parent -of the node at i, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_parent(self, p: int, i: int) -> bool:
-    """Returns `True` if `p` is the index of the parent
-    of the node at `i`, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(p):
-        raise IndexError("p is not a valid index.")
-    return self.parent_index(i) == p
-
-
-
- -
- - -
-
-

def left_index(

self, i)

-
- - - - -

Returns the left child's index of the node at index i, -if it exists, otherwise this function returns -1.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def left_index(self, i: int) -> int:
-    """Returns the left child's index of the node at index `i`,
-    if it exists, otherwise this function returns -1.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    left = i * 2 + 1
-    return left if self.is_good_index(left) else -1
-
-
-
- -
- - -
-
-

def merge(

self, o)

-
- - - - -

Merges this heap with the o heap.

-

Returns the list object representing internally the new merged heap.

-

Time Complexity: O(n + m).

-

Time complexity analysis based on: -http://stackoverflow.com/a/29197855/3924118.

-
- -
-
def merge(self, o) -> list:
-    """Merges this heap with the `o` heap.
-    Returns the `list` object representing internally the new merged heap.
-    **Time Complexity:** O(n + m).
-    Time complexity analysis based on:
-    [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
-    self.heap += o.heap
-    return self.build_heap()
-
-
-
- -
- - -
-
-

def parent_index(

self, i)

-
- - - - -

Returns the parent's index of the node at index i. -If i = 0, then -1 is returned, because the root has no parent.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def parent_index(self, i: int) -> int:
-    """Returns the parent's index of the node at index `i`.
-    If `i = 0`, then -1 is returned, because the root has no parent.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return -1 if i == 0 else (i - 1) // 2
-
-
-
- -
- - -
-
-

def peek(

self, priority=False)

-
- - - - -

Returns (without removing) the element with highest priority.

-

In this MinPriorityQueue implementation, -if A has an higher priority than B, -then A.priority <= B.priority.

-

If priority is set to True, -a tuple of the form (element, priority) is returned, -otherwise only element is returned, -where element is the element in self with highest priority.

-

Time Complexity: O(1).

-
- -
-
def peek(self, priority=False):
-    """Returns (without removing) the element with highest priority.
-    In this `MinPriorityQueue` implementation,
-    if A has an higher priority than B,
-    then `A.priority <= B.priority`.
-    If priority is set to True,
-    a tuple of the form (element, priority) is returned,
-    otherwise only element is returned,
-    where element is the element in self with highest priority.
-    **Time Complexity**: O(1)."""
-    m = self.find_min()
-    if m is not None:
-        if priority:
-            return (m.value, m.key)
-        else:
-            return m.value
-
-
-
- -
- - -
-
-

def push_down(

self, i)

-
- - - - -

'Min-heapify' this min-heap starting from index i.

-

Time Complexity: O(log2 n).

-
- -
-
def push_down(self, i: int) -> None:
-    """'Min-heapify' this min-heap starting from index `i`.
-    **Time Complexity:** O(log2 n)."""
-    m = i  # index of node with smallest value among i and its children
-    l = self.left_index(i)
-    r = self.right_index(i)
-    if l != -1 and self.heap[l] < self.heap[m]:
-        m = l
-    if r != -1 and self.heap[r] < self.heap[m]:
-        m = r
-    if m != i:
-        self.swap(m, i)
-        self.push_down(m)
-
-
-
- -
- - -
-
-

def push_up(

self, i)

-
- - - - -

Pushes up the node at index i.

-

Note that this operation only happens -if the node at index i is smaller than its parent.

-

This function is simpler than push_down (or also called min-heapify), -because in this case we just need to compare -the current node's index with its parent's index.

-

Time Complexity: O(log2 n).

-
- -
-
def push_up(self, i: int) -> None:
-    """Pushes up the node at index `i`.
-    Note that this operation only happens
-    if the node at index `i` is smaller than its parent.
-    This function is simpler than `push_down` (or also called min-heapify),
-    because in this case we just need to compare
-    the current node's index with its parent's index.
-    **Time Complexity:** O(log2 n)."""
-    c = i  # current index
-    p = self.parent_index(i)
-    if p != -1 and self.heap[c] < self.heap[p]:
-        c = p
-    if c != i:
-        self.swap(c, i)
-        self.push_up(c)
-
-
-
- -
- - -
-
-

def remove_min(

self)

-
- - - - -

Removes and returns the smallest element in this heap.

-

Time Complexity: O(log2 n), -if removing the last element of a list is a constant-time operation.

-
- -
-
def remove_min(self) -> HeapNode:
-    """Removes and returns the smallest element in this heap.
-    **Time Complexity:** O(log2 n),
-    if removing the last element of a list is a constant-time operation."""
-    if not self.is_empty():
-        self.swap(0, self.size() - 1)
-        m = self.heap.pop()
-        if not self.is_empty():
-            self.push_down(0)
-        return m
-
-
-
- -
- - -
-
-

def replace(

self, i, x)

-
- - - - -

Replaces element at index i with x.

-

x can either be a key or a HeapNode object. -If it's a key, then a HeapNode object -first created to represent x.

-
    -
  1. -

    If x == self.heap[i], -then just replace self.heap[i] with x.

    -
  2. -
  3. -

    Else if x < self.heap[i], -then push_up(index).

    -
  4. -
  5. -

    Else x > self.heap[i], -then call self.push_down(i).

    -
  6. -
-

Returns the previous HeapNode object at i.

-

Time Complexity: O(log2 n).

-
- -
-
def replace(self, i: int, x) -> HeapNode:
-    """Replaces element at index `i` with `x`.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, then a `HeapNode` object
-    first created to represent `x`.
-    1. If `x == self.heap[i]`,
-    then just replace `self.heap[i]` with `x`.
-    2. Else if `x < self.heap[i]`,
-    then push_up(index).
-    3. Else `x > self.heap[i]`,
-    then call `self.push_down(i)`.
-    Returns the previous `HeapNode` object at `i`.
-    **Time Complexity:** O(log2 n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    c = self.heap[i]
-    self.heap[i] = x
-    if x > c:
-        self.push_down(i)
-    elif x < c:
-        self.push_up(i)
-    return c
-
-
-
- -
- - -
-
-

def right_index(

self, i)

-
- - - - -

Returns the right child's index of the node at index i, -if it exists, otherwise this function returns -1.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def right_index(self, i: int) -> int:
-    """Returns the right child's index of the node at index `i`,
-    if it exists, otherwise this function returns -1.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    right = i * 2 + 2
-    return right if self.is_good_index(right) else -1
-
-
-
- -
- - -
-
-

def search(

self, x)

-
- - - - -

Searches for x in this heap, -and, if present, returns its index, otherwise returns -1.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(n).

-
- -
-
def search(self, x) -> int:
-    """Searches for `x` in this heap,
-    and, if present, returns its index, otherwise returns -1.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    for i, node in enumerate(self.heap):
-        if node == x:
-            return i
-    return -1
-
-
-
- -
- - -
-
-

def search_by_value(

self, val)

-
- - - - -

Returns the index of the HeapNode object with value=val. --1 is returned if no such a HeapNode object exists.

-

If val and the values in this heap are not comparable, -the behaviour of this method is undefined.

-

By construction, HeapNode objects can't be initialized with None values, -but that field could also be set manually after creation.

-

Time Complexity: O(n).

-
- -
-
def search_by_value(self, val) -> int:
-    """Returns the index of the `HeapNode` object with `value=val`.
-    -1 is returned if no such a `HeapNode` object exists.
-    If `val` and the values in this heap are not comparable,
-    the behaviour of this method is undefined.
-    By construction, HeapNode objects can't be initialized with None values,
-    but that field could also be set manually after creation.
-    **Time Complexity:** O(n)."""
-    for i, node in enumerate(self.heap):
-        if node.value == val:
-            return i
-    return -1
-
-
-
- -
- - -
-
-

def size(

self)

-
- - - - -

Returns the size of this heaps.

-

Time Complexity: O(1).

-
- -
-
def size(self) -> int:
-    """Returns the size of this heaps.
-    **Time Complexity:** O(1)."""
-    return len(self.heap)
-
-
-
- -
- - -
-
-

def swap(

self, i, j)

-
- - - - -

Swaps elements at indexes i and j, -if they are valid indexes, -otherwise an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def swap(self, i: int, j: int) -> None:
-    """Swaps elements at indexes `i` and `j`,
-    if they are valid indexes,
-    otherwise an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if self.is_good_index(i) and self.is_good_index(j):
-        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
-    else:
-        raise IndexError("i or j are not valid indexes.")
-
-
-
- -
- -
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/Queue.m.html b/docs/ands/ds/Queue.m.html deleted file mode 100644 index a096212c..00000000 --- a/docs/ands/ds/Queue.m.html +++ /dev/null @@ -1,1349 +0,0 @@ - - - - - - ands.ds.Queue API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.Queue module

-

Meta info

-

Author: Nelson Brochado

-

Created: 02/07/2015

-

Updated: 04/02/2017

-

Description

-

Basic queue, which is FIFO (first-in-first-out) data structure. -It's implemented using a deque, because a deque supports better the dequeue operation than lists.

-

References

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-# Meta info
-
-Author: Nelson Brochado
-
-Created: 02/07/2015
-
-Updated: 04/02/2017
-
-# Description
-
-Basic queue, which is FIFO (first-in-first-out) data structure.
-It's implemented using a deque, because a deque supports better the _dequeue_ operation than lists.
-
-# References
-
-- [https://docs.python.org/3.1/tutorial/datastructures.html#using-lists-as-queues](https://docs.python.org/3.1/tutorial/datastructures.html#using-lists-as-queues)
-
-"""
-
-from collections import deque, Iterable
-
-__all__ = ["Queue"]
-
-
-class Queue:
-    """This is a wrapper class around the Python deque data structure
-    to represent logically a queue (FIFO) data structure.
-
-    You can initialize the class using an iterable (list, tuple, etc) of values,
-    which will be assumed to be already in the FIFO order.
-
-    If `ls` is not an instance of `Iterable`, `TypeError` is raised.
-    If one of the values in `ls` is None, `ValueError` is raised.
-    A copy of `ls` is made, so that changes to the original self
-    do not reflect in the original iterable.
-
-    This class does not allow None to be inserted as value
-    to the data structure through the methods of the same.
-
-    It also returns None, instead of raising exceptions,
-    when trying to dequeue, when the data structure is empty.
-
-    Clients of this class should **never** access fields whose names start with _,
-    which are considered private fields."""
-
-    def __init__(self, ls=None):
-        if ls is not None:
-            if not isinstance(ls, Iterable):
-                raise TypeError("ls must be an iterable object")
-            if any(elem is None for elem in ls):
-                raise ValueError("all elements of ls must be not None")
-        else:
-            ls = []
-        self._q = deque(ls)
-
-    def enqueue(self, elem) -> None:
-        """Adds `elem` to the end of this queue.
-        If `elem` is None, ValueError is raised."""
-        if elem is None:
-            raise ValueError("elem cannot be None")
-        self._q.append(elem)
-
-    def dequeue(self):
-        """Returns the first element of this queue, or None if the queue is empty."""
-        return None if self.is_empty() else self._q.popleft()
-
-    def is_empty(self) -> bool:
-        """Returns `True` if this queue is empty, `False` otherwise."""
-        return self.size() == 0
-
-    def size(self) -> int:
-        """Returns the size of this queue."""
-        return len(self._q)
-
-    def __str__(self):
-        return str(list(self._q))
-
-    def __repr__(self):
-        return self.__str__()
-
-
- -
- -
- - -

Classes

- -
-

class Queue

- - -

This is a wrapper class around the Python deque data structure -to represent logically a queue (FIFO) data structure.

-

You can initialize the class using an iterable (list, tuple, etc) of values, -which will be assumed to be already in the FIFO order.

-

If ls is not an instance of Iterable, TypeError is raised. -If one of the values in ls is None, ValueError is raised. -A copy of ls is made, so that changes to the original self -do not reflect in the original iterable.

-

This class does not allow None to be inserted as value -to the data structure through the methods of the same.

-

It also returns None, instead of raising exceptions, -when trying to dequeue, when the data structure is empty.

-

Clients of this class should never access fields whose names start with _, -which are considered private fields.

-
- -
-
class Queue:
-    """This is a wrapper class around the Python deque data structure
-    to represent logically a queue (FIFO) data structure.
-
-    You can initialize the class using an iterable (list, tuple, etc) of values,
-    which will be assumed to be already in the FIFO order.
-
-    If `ls` is not an instance of `Iterable`, `TypeError` is raised.
-    If one of the values in `ls` is None, `ValueError` is raised.
-    A copy of `ls` is made, so that changes to the original self
-    do not reflect in the original iterable.
-
-    This class does not allow None to be inserted as value
-    to the data structure through the methods of the same.
-
-    It also returns None, instead of raising exceptions,
-    when trying to dequeue, when the data structure is empty.
-
-    Clients of this class should **never** access fields whose names start with _,
-    which are considered private fields."""
-
-    def __init__(self, ls=None):
-        if ls is not None:
-            if not isinstance(ls, Iterable):
-                raise TypeError("ls must be an iterable object")
-            if any(elem is None for elem in ls):
-                raise ValueError("all elements of ls must be not None")
-        else:
-            ls = []
-        self._q = deque(ls)
-
-    def enqueue(self, elem) -> None:
-        """Adds `elem` to the end of this queue.
-        If `elem` is None, ValueError is raised."""
-        if elem is None:
-            raise ValueError("elem cannot be None")
-        self._q.append(elem)
-
-    def dequeue(self):
-        """Returns the first element of this queue, or None if the queue is empty."""
-        return None if self.is_empty() else self._q.popleft()
-
-    def is_empty(self) -> bool:
-        """Returns `True` if this queue is empty, `False` otherwise."""
-        return self.size() == 0
-
-    def size(self) -> int:
-        """Returns the size of this queue."""
-        return len(self._q)
-
-    def __str__(self):
-        return str(list(self._q))
-
-    def __repr__(self):
-        return self.__str__()
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • Queue
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self, ls=None)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, ls=None):
-    if ls is not None:
-        if not isinstance(ls, Iterable):
-            raise TypeError("ls must be an iterable object")
-        if any(elem is None for elem in ls):
-            raise ValueError("all elements of ls must be not None")
-    else:
-        ls = []
-    self._q = deque(ls)
-
-
-
- -
- - -
-
-

def dequeue(

self)

-
- - - - -

Returns the first element of this queue, or None if the queue is empty.

-
- -
-
def dequeue(self):
-    """Returns the first element of this queue, or None if the queue is empty."""
-    return None if self.is_empty() else self._q.popleft()
-
-
-
- -
- - -
-
-

def enqueue(

self, elem)

-
- - - - -

Adds elem to the end of this queue. -If elem is None, ValueError is raised.

-
- -
-
def enqueue(self, elem) -> None:
-    """Adds `elem` to the end of this queue.
-    If `elem` is None, ValueError is raised."""
-    if elem is None:
-        raise ValueError("elem cannot be None")
-    self._q.append(elem)
-
-
-
- -
- - -
-
-

def is_empty(

self)

-
- - - - -

Returns True if this queue is empty, False otherwise.

-
- -
-
def is_empty(self) -> bool:
-    """Returns `True` if this queue is empty, `False` otherwise."""
-    return self.size() == 0
-
-
-
- -
- - -
-
-

def size(

self)

-
- - - - -

Returns the size of this queue.

-
- -
-
def size(self) -> int:
-    """Returns the size of this queue."""
-    return len(self._q)
-
-
-
- -
- -
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/RBT.m.html b/docs/ands/ds/RBT.m.html deleted file mode 100644 index 5a59d299..00000000 --- a/docs/ands/ds/RBT.m.html +++ /dev/null @@ -1,3715 +0,0 @@ - - - - - - ands.ds.RBT API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.RBT module

-

Meta info

-

Author: Nelson Brochado

-

Created: 01/08/2015

-

Updated: 28/08/2016

-

Description

-

Red-black Tree Property

-
    -
  1. -

    Every node is either red or black.

    -
  2. -
  3. -

    The root is black.

    -
  4. -
  5. -

    Every NIL or leaf node is black.

    -
  6. -
  7. -

    If a node is red, then both its children are black, -in other words, there cannot be two red nodes in a row.

    -
  8. -
  9. -

    For every node x, each path from x to its descendent leaves -has the same number of black nodes, i.e. bh(x).

    -
  10. -
-

Lemma

-

The height h(x) of a red-black tree with n = size(x) internal nodes -is at most 2 * log2(n + 1), that is, h(x) <= 2 * log2(n + 1), -which is equivalent to h(x)/2 <= log2(n + 1), which is equivalent to -n >= 2h(x)/2 - 1. If you don't understand exactly why this last statements -are equivalent, then do the reversed reasoning:

-
    -
  • -

    n >= 2h(x)/2 - 1

    -
  • -
  • -

    n + 1 >= 2h(x)/2

    -
  • -
-

Now we log both parts

-
    -
  • -

    log2(n + 1) >= log2(2h(x)/2)

    -
  • -
  • -

    log2(n + 1) >= h(x)/2 * log2(2)

    -
  • -
  • -

    log2(n + 1) >= h(x)/2 * 1

    -
  • -
  • -

    2 * log2(n + 1) >= h(x)

    -
  • -
-

Proof

-
    -
  1. -

    Prove that for all x, size(x) >= 2bh(x) - 1 by induction.

    -

    1.1. Base case: x is a leaf, so size(x) = 0 and bh(x) = 0.

    -

    1.2. Induction step: consider y1, y2, -and x such that y1.parent = y2.parent = x, -and assume (induction) that size(y1) >= 2bh(y1) - 1 -and size(y2) >= 2bh(y2) - 1. -Prove that size(x) >= 2bh(x) - 1.

    -

    Proof:

    -

    size(x) = size(y1) + size(y2) + 1 >= (2bh(y1) - 1) -+ (2bh(y2) - 1) + 1

    -

    Since bh(x) = {

    -
    bh(y), if color(y) = red
    -
    -bh(y) + 1, if color(y) = black
    -
    -

    }

    -

    size(x) >= (2bh(x) - 1 - 1) + (2bh(x) - 1 - 1) + 1 -= (2bh(x) - 1).

    -

    Since every red node has black children, -in every path from x to a leaf node, -at least half the nodes are black, thus bh(x) >= h(x)/2. -So, n = size(x) >= 2h(x)/2 - 1. Therefore -h(x) <= 2 * log2(n + 1).

    -

    A red-black tree works as a binary-search tree for search, -insert, etc, so the complexity of those operations is T(n) = O(h), -that is T(n) = O(log2 n), which is also the worst case complexity.

    -
  2. -
-

TODO

-
    -
  • Override needed methods inherited from BST.
  • -
-

References

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-# Meta info
-
-Author: Nelson Brochado
-
-Created: 01/08/2015
-
-Updated: 28/08/2016
-
-# Description
-
-## Red-black Tree Property
-
-1. Every node is either red or black.
-
-2. The root is black.
-
-3. Every NIL or leaf node is black.
-
-4. If a node is red, then both its children are black,
-in other words, there cannot be two red nodes in a row.
-
-5. For every node x, each path from x to its descendent leaves
-has the same number of black nodes, i.e. bh(x).
-
-## Lemma
-
-The height `h(x)` of a red-black tree with `n = size(x)` internal nodes
-is at most 2 * log2(n + 1), that is, h(x) <= 2 * log2(n + 1),
-which is equivalent to h(x)/2 <= log2(n + 1), which is equivalent to
-n >= 2h(x)/2 - 1. If you don't understand exactly why this last statements
-are equivalent, then do the reversed reasoning:
-
-* n >= 2h(x)/2 - 1
-
-* n + 1 >= 2h(x)/2
-
-Now we log both parts
-
-* log2(n + 1) >= log2(2h(x)/2)
-
-* log2(n + 1) >= h(x)/2 * log2(2)
-
-* log2(n + 1) >= h(x)/2 * 1
-
-* 2 * log2(n + 1) >= h(x)
-
-
-### Proof
-
-1. Prove that for all `x`, size(x) >= 2bh(x) - 1 by induction.
-
-    1.1. **Base case**: `x` is a leaf, so `size(x) = 0` and `bh(x) = 0`.
-
-    1.2. **Induction step**: consider y1, y2,
-    and `x` such that y1.parent = y2.parent = x,
-    and assume (induction) that size(y1) >= 2bh(y1) - 1
-    and size(y2) >= 2bh(y2) - 1.
-    Prove that size(x) >= 2bh(x) - 1.
-
-    **Proof**:
-
-    size(x) = size(y1) + size(y2) + 1 >= (2bh(y1) - 1)
-    + (2bh(y2) - 1) + 1
-
-    Since bh(x) = {
-
-        bh(y), if color(y) = red
-
-        bh(y) + 1, if color(y) = black
-    }
-
-    size(x) >= (2bh(x) - 1 - 1) + (2bh(x) - 1 - 1) + 1
-    = (2bh(x) - 1).
-
-    Since every red node has black children,
-    in every path from `x` to a leaf node,
-    at least half the nodes are black, thus bh(x) >= h(x)/2.
-    So, n = size(x) >= 2h(x)/2 - 1. Therefore
-    h(x) <= 2 * log2(n + 1).
-
-    A red-black tree works as a binary-search tree for search,
-    insert, etc, so the complexity of those operations is T(n) = O(h),
-    that is T(n) = O(log2 n), which is also the worst case complexity.
-
-# TODO
-
-- Override needed methods inherited from BST.
-
-# References
-
-- [https://en.wikipedia.org/wiki/Red%E2%80%93black_tree](https://en.wikipedia.org/wiki/Red%E2%80%93black_tree)
-- Slides by prof. A. Carzaniga
-- Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS
-
-"""
-
-import math
-
-from ands.ds.BST import BST, BSTNode, is_bst
-
-__all__ = ["RBT", "RBTNode", "is_rbt", "bh", "upper_bound_height"]
-
-RED = "RED"
-BLACK = "BLACK"
-
-
-class RBTNode(BSTNode):
-    """Class to represent a `RBT`'s node."""
-
-    def __init__(self, key, value=None, color=BLACK,
-                 parent=None, left=None, right=None):
-        BSTNode.__init__(self, key, value, parent, left, right)
-        self._color = color
-        self.label = "[" + str(self.key) + ", " + str(self._color) + "]"
-
-    @property
-    def color(self):
-        return self._color
-
-    @color.setter
-    def color(self, value):
-        self._color = value
-        self.label = "[" + str(self.key) + ", " + str(self._color) + "]"
-
-    def reset(self):
-        super().reset()
-        self.color = BLACK
-
-    def __fields(self):
-        """Used by __repr__."""
-        return [["Node (Key)", self.key],
-                ["Value", self.value],
-                ["Color", self.color],
-                ["Parent", self.parent],
-                ["Left child", self.left],
-                ["Right child", self.right],
-                ["Sibling", self.sibling],
-                ["Grandparent", self.grandparent],
-                ["Uncle", self.uncle]]
-
-
-class RBT(BST):
-    """Red-black tree, which is a self-balancing binary-search tree."""
-
-    def __init__(self, root=None, name="RBT"):
-        BST.__init__(self, root, name)
-
-    def insert(self, x, value=None) -> None:
-        """Inserts `x` into this `RBT`.
-
-        This operation is similar to the `insert` operation of a classical `BST`,
-        but, in this case, the red-black tree property must be maintained,
-        so addional work is needed.
-
-        There are several cases of inserting into a RBT to handle:
-
-        1. `x`  is the root node (first node).
-
-        2. `x.parent` is `BLACK`.
-
-        3. `x.parent` and the uncle of `x` are `RED`.
-
-            The uncle of `x` will be the left child of `x.parent.parent`,
-        if `x.parent` is the right child of `x.parent.parent`,
-        otherwise (`x.parent` is the left child of `x.parent.parent`)
-        the uncle will be the right child of `x.parent.parent`.
-
-        4. x.parent is RED, but x.uncle is BLACK (or None). x.grandparent exists because x.parent is RED.
-
-            4.1. `x` is added to the right of a left child of `x.parent.parent` (grandparent)
-
-            4.2. or `x` is added to the left of a right child of `x.parent.parent`.
-
-            4.3. `x` is added to the left of a left child of `x.parent.parent`.
-
-            4.4. or `x` is added to the right of a right child of `x.parent.parent`.
-
-        `_fix_insertion` handles these cases in the same order as just presented above.
-
-        **Time Complexity:** O(log2(n))."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-
-        if not isinstance(x, RBTNode):
-            x = RBTNode(x, value)
-
-        if x.left or x.right or x.parent:
-            raise ValueError("x cannot have left or right children, or parent.")
-
-        c = self.root  # Current node
-        p = None  # Current node's parent
-
-        while c is not None:
-            p = c
-            if x.key < c.key:
-                c = c.left
-            else:  # x.key >= c.key
-                c = c.right
-
-        x.parent = p
-
-        # The while loop was not executed even once.
-        # Case 1: node is inserted as root.
-        if p is None:
-            self.root = x
-        elif p.key > x.key:
-            p.left = x
-        else:  # p.key < x.key:
-            p.right = x
-
-        x.color = RED
-        self.n += 1
-        self._fix_insertion(x)
-
-    def _fix_insertion(self, u: RBTNode) -> None:
-        # u is the root and we color it BLACK.
-        if u.parent is None:
-            u.color = BLACK
-
-        elif u.parent.color == BLACK:
-            return
-
-        elif u.parent.color == RED and (u.uncle is not None and u.uncle.color == RED):
-            u.parent.color = BLACK
-            u.uncle.color = BLACK
-            u.grandparent.color = RED
-            self._fix_insertion(u.grandparent)
-
-        elif u.parent.color == RED and (u.uncle is None or u.uncle.color == BLACK):
-
-            # u is added as a right child to a node that is the left child.
-            if u.parent.is_left_child() and u.is_right_child():
-
-                # left_rotation does not violate the property:
-                # all paths from any given node to its leaf nodes
-                # contain the same number of black nodes.
-                self.left_rotate(u.parent)
-
-                # With the previous left_rotate call,
-                # u.parent has become the left child of u,
-                # or, u bas become the parent of what before was u.parent
-                # We can pass to case 5, where we have 2 red nodes in a row,
-                # specifically, u.parent and u,
-                # which are both left children of their parents.
-
-                self._fix_insertion(u.left)
-
-            # u is added as a left child to a node that is the right child.
-            elif u.parent.is_right_child() and u.is_left_child():
-                self.right_rotate(u.parent)
-                self._fix_insertion(u.right)
-
-            # u is added as a left child to a node that is the left child.
-            elif u.parent.is_left_child() and u.is_left_child():
-                # Note that grandparent is known to be black,
-                # since its former child could not have been RED
-                # without violating property 4.
-                self.right_rotate(u.grandparent)
-                u.parent.color = BLACK
-                u.parent.right.color = RED
-
-            # u is added as a right child to a node that is the right child.
-            elif u.parent.is_right_child() and u.is_right_child():
-                self.left_rotate(u.grandparent)
-                u.parent.color = BLACK
-                u.parent.left.color = RED
-
-            else:
-                assert False
-
-    def delete(self, x) -> RBTNode:
-        """Delete `x` from this `RBT` object.
-
-        `x` can either be a `RBTNode` object or a key.
-
-        If a key, then a search is performed first
-        to find the corresponding `RBTNode` object.
-        An exception is raised if a `RBTNode` object
-        with a key=x is not found.
-
-        If `x` is a `RBTNode` object, the only check
-        that is performed is that if it hasn't a parent,
-        then it must be the root. Similarly,
-        a node that isn't the root must have a parent.
-        If `x` has a parent, therefore it cannot be the root,
-        but there's no way of knowing if this node
-        really belongs to this `RBT` object,
-        because no search is performed (for now).
-
-        If it does NOT belong to this `RBT` object,
-        then the behaviour of this method is UNDEFINED!
-
-        **Time Complexity:** O(log2(n))."""
-
-        def delete_case1(v):
-            # this check is necessary because this function
-            # is also called from the delete_case3 function.
-            if v.parent is not None:
-                delete_case2(v)
-
-        def delete_case2(v):
-            if v.sibling.color == RED:
-
-                assert v.parent.color == BLACK
-
-                v.sibling.color = BLACK
-                v.parent.color = RED
-
-                if v.is_left_child():
-                    self.left_rotate(v.parent)
-                else:
-                    self.right_rotate(v.parent)
-
-                assert v.sibling.color == BLACK
-
-            delete_case3(v)
-
-        def delete_case3(v):
-            # not sure if the children of v.sibling can be None
-            if (v.parent.color == BLACK and v.sibling.color == BLACK and
-                    ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
-                    ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
-
-                v.sibling.color = RED
-                delete_case1(v.parent)
-            else:
-                delete_case4(v)
-
-        def delete_case4(v):
-            # not sure if the children of v.sibling can be None
-            if (v.parent.color == RED and v.sibling.color == BLACK and
-                    ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
-                    ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
-
-                v.sibling.color = RED
-                v.parent.color = BLACK
-            else:
-                delete_case5(v)
-
-        def delete_case5(v):
-            assert v.sibling is not None
-
-            if v.sibling.color == BLACK:
-                if (v.is_left_child() and
-                        (not v.sibling.right or v.sibling.right.color == BLACK) and
-                            v.sibling.left.color == RED):
-
-                    v.sibling.color = RED
-                    v.sibling.left.color = BLACK
-                    self.right_rotate(v.sibling)
-
-                elif (v.is_right_child() and
-                          (not v.sibling.left or v.sibling.left.color == BLACK) and
-                              v.sibling.right.color == RED):
-
-                    v.sibling.color = RED
-                    v.sibling.right.color = BLACK
-                    self.left_rotate(v.sibling)
-
-            delete_case6(v)
-
-        def delete_case6(v):
-            assert v.sibling is not None
-
-            v.sibling.color, v.parent.color = v.parent.color, v.sibling.color
-
-            if v.is_left_child():
-                assert v.sibling.right
-                v.sibling.right.color = BLACK
-                self.left_rotate(v.parent)
-            else:
-                assert v.sibling.left
-                v.sibling.left.color = BLACK
-                self.right_rotate(v.parent)
-
-        # a few checks of the inputs given
-        if x is None:
-            raise ValueError("x cannot be None.")
-
-        if not isinstance(x, RBTNode):
-            x = self.search(x)
-            if x is None:
-                raise LookupError("No node was found with key=x.")
-
-        if x.parent is None and not self.is_root(x):
-            raise ValueError("x is not a valid node.")
-
-        # If x has 2 non-leaf children, then replace x with its successor.
-        # Note that we exchange also the colors of x and its successor.
-        if x.left is not None and x.right is not None:
-            s = self.successor(x)
-            self._switch(x, s)
-            x.color, s.color = s.color, x.color
-
-        # At least one of the children must be None.
-        # Particularly, if `x` was exchanged with its successor,
-        # `x` now should NOT have a left child.
-        assert x.left is None or x.right is None
-
-        # At this point `x` has at most 1 child.
-        # Keep in mind this when reading the next cases.
-
-        # If `x` is a red node and it has a child,
-        # we simply replace it with its child `c`,
-        # which must be black by property 4.
-
-        # This can only occur when `x` has 2 leaf children,
-        # because if `x` had a black NON-leaf child on one side,
-        # but just a leaf child on the other side,
-        # then the count of black nodes on both sides would be different,
-        # thus the tree would violate property 5.
-        if x.color == RED:
-
-            # a few checks while in alpha stage
-            assert x.left is None and x.right is None
-            assert not self.is_root(x)
-
-            if x.is_left_child():
-                x.parent.left = None
-            else:
-                x.parent.right = None
-
-        else:  # x.color == BLACK
-
-            # One of the children of `x` is red.
-
-            # Simply removing `x` could break properties 4,
-            # i.e., both children of every red node are black,
-            # because x.parent could be red, and 5,
-            # i.e. all paths from any given node to its leaf nodes
-            # contain the same number of black nodes),
-            # but if we repaint `c` (the child) BLACK,
-            # both of these properties are preserved.
-
-            if x.left is not None and x.left.color == RED:
-                if not self.is_root(x):
-                    if x.is_left_child():
-                        x.parent.left = x.left
-                    else:
-                        x.parent.right = x.left
-
-                x.left.parent = x.parent
-                x.left.color = BLACK
-
-                if self.is_root(x):
-                    self.root = x.left
-
-            elif x.right is not None and x.right.color == RED:
-                if not self.is_root(x):
-                    if x.is_left_child():
-                        x.parent.left = x.right
-                    else:
-                        x.parent.right = x.right
-
-                x.right.parent = x.parent
-                x.right.color = BLACK
-
-                if self.is_root(x):
-                    self.root = x.right
-            else:
-                # This the complex case: both `x` and `c` (the child) are BLACK.
-
-                # This can only occur when deleting a black node
-                # which has 2 LEAF children, because if the black node `x`
-                # had a black NON-leaf child on one side
-                # but just a leaf child on the other side,
-                # then the count of black nodes on both sides would be different,
-                # thus the tree would have been an invalid red–black tree
-                # by violation of property 5.
-                assert x.left is None and x.right is None
-
-                # 6 cases
-                if not self.is_root(x):
-
-                    assert x.sibling is not None
-
-                    # Note that x.sibling cannot be None,
-                    # because otherwise the subtree containing it
-                    # would have fewer black nodes
-                    # than the subtree containing x.
-                    # Specifically, the subtree containing x
-                    # would have a black height of 2,
-                    # whereas the one containing the sibling
-                    # would have a black height of 1.
-
-                    delete_case1(x)
-
-                    # We begin by replacing x with its child c.
-                    # Note that both children of x are leaf children.
-                    if x.is_left_child():
-                        x.parent.left = None
-                    else:
-                        x.parent.right = None
-
-                else:
-                    self.root = None
-
-        self.n -= 1
-        # Ensures that x has no reference to any node of this RBT.
-        x.parent = x.left = x.right = None
-        return x
-
-    def remove_max(self) -> RBTNode:
-        """Removes and returns the element with the greatest value from `self`.
-
-        **Time Complexity:** O(log2(n))."""
-        if self.root:
-            m = self.maximum()
-            assert m
-            return self.delete(m)
-
-    def remove_min(self) -> RBTNode:
-        """Removes and returns the element with the smallest value from `self`.
-
-        **Time Complexity:** O(log2(n))."""
-        if self.root:
-            m = self.minimum()
-            assert m
-            return self.delete(m)
-
-
-def bh(n):
-    """Returns the black-height of the node `n`."""
-    if n is None:
-        return 1
-
-    left_bh = bh(n.left)
-    right_bh = bh(n.right)
-
-    if left_bh != right_bh:
-        return -1
-    else:
-        return left_bh + (1 if n.color == BLACK else 0)
-
-
-def upper_bound_height(t):
-    return t.height() <= 2 * math.log2(t.n + 1)
-
-
-def is_rbt(rbt):
-    """Returns `True` if `rbt` is a valid `RBT` object. `False` otherwise."""
-
-    def prop_1(t):
-        """Returns `True` if all colors are either `RED` or `BLACK`."""
-
-        def h(n):
-            if n:
-                if n.color != BLACK and n.color != RED:
-                    return False
-                return h(n.right) and h(n.left)
-            return True
-
-        return h(t.root)
-
-    def prop_2(t):
-        """Returns `True` if the root is `BLACK` (or it is `None`),
-        `False` otherwise."""
-        if t.root:
-            return t.root.color == BLACK
-        return True
-
-        # def prop_3(t):
-        # leaves are represented with Nones,
-        # so there's not need to check this property.
-
-    #    return True
-
-    def prop_4(t):
-        def h(n):
-            if n:
-                if n.parent and n.color == RED and n.parent.color == RED:
-                    return False
-                if not n.parent and n.color == RED:
-                    return False
-                return h(n.left) and h(n.right)
-            return True
-
-        return h(t.root)
-
-    def prop_5(t):
-        return bh(t.root) != -1
-
-    def all_rbt_nodes(t):
-        def h(n):
-            if n is not None:
-                if not isinstance(n, RBTNode):
-                    return False
-                return h(n.left) and h(n.right)
-            return True
-
-        return h(t.root)
-
-    if not is_bst(rbt):
-        return False
-
-    if not isinstance(rbt, RBT):
-        return False
-
-    if not all_rbt_nodes(rbt):
-        return False
-
-    return prop_1(rbt) and prop_2(rbt) and prop_4(rbt) and prop_5(rbt)
-
-
- -
- -
- -

Functions

- -
-
-

def bh(

n)

-
- - - - -

Returns the black-height of the node n.

-
- -
-
def bh(n):
-    """Returns the black-height of the node `n`."""
-    if n is None:
-        return 1
-
-    left_bh = bh(n.left)
-    right_bh = bh(n.right)
-
-    if left_bh != right_bh:
-        return -1
-    else:
-        return left_bh + (1 if n.color == BLACK else 0)
-
-
-
- -
- - -
-
-

def is_rbt(

rbt)

-
- - - - -

Returns True if rbt is a valid RBT object. False otherwise.

-
- -
-
def is_rbt(rbt):
-    """Returns `True` if `rbt` is a valid `RBT` object. `False` otherwise."""
-
-    def prop_1(t):
-        """Returns `True` if all colors are either `RED` or `BLACK`."""
-
-        def h(n):
-            if n:
-                if n.color != BLACK and n.color != RED:
-                    return False
-                return h(n.right) and h(n.left)
-            return True
-
-        return h(t.root)
-
-    def prop_2(t):
-        """Returns `True` if the root is `BLACK` (or it is `None`),
-        `False` otherwise."""
-        if t.root:
-            return t.root.color == BLACK
-        return True
-
-        # def prop_3(t):
-        # leaves are represented with Nones,
-        # so there's not need to check this property.
-
-    #    return True
-
-    def prop_4(t):
-        def h(n):
-            if n:
-                if n.parent and n.color == RED and n.parent.color == RED:
-                    return False
-                if not n.parent and n.color == RED:
-                    return False
-                return h(n.left) and h(n.right)
-            return True
-
-        return h(t.root)
-
-    def prop_5(t):
-        return bh(t.root) != -1
-
-    def all_rbt_nodes(t):
-        def h(n):
-            if n is not None:
-                if not isinstance(n, RBTNode):
-                    return False
-                return h(n.left) and h(n.right)
-            return True
-
-        return h(t.root)
-
-    if not is_bst(rbt):
-        return False
-
-    if not isinstance(rbt, RBT):
-        return False
-
-    if not all_rbt_nodes(rbt):
-        return False
-
-    return prop_1(rbt) and prop_2(rbt) and prop_4(rbt) and prop_5(rbt)
-
-
-
- -
- - -
-
-

def upper_bound_height(

t)

-
- - - - -
- -
-
def upper_bound_height(t):
-    return t.height() <= 2 * math.log2(t.n + 1)
-
-
-
- -
- - -

Classes

- -
-

class RBT

- - -

Red-black tree, which is a self-balancing binary-search tree.

-
- -
-
class RBT(BST):
-    """Red-black tree, which is a self-balancing binary-search tree."""
-
-    def __init__(self, root=None, name="RBT"):
-        BST.__init__(self, root, name)
-
-    def insert(self, x, value=None) -> None:
-        """Inserts `x` into this `RBT`.
-
-        This operation is similar to the `insert` operation of a classical `BST`,
-        but, in this case, the red-black tree property must be maintained,
-        so addional work is needed.
-
-        There are several cases of inserting into a RBT to handle:
-
-        1. `x`  is the root node (first node).
-
-        2. `x.parent` is `BLACK`.
-
-        3. `x.parent` and the uncle of `x` are `RED`.
-
-            The uncle of `x` will be the left child of `x.parent.parent`,
-        if `x.parent` is the right child of `x.parent.parent`,
-        otherwise (`x.parent` is the left child of `x.parent.parent`)
-        the uncle will be the right child of `x.parent.parent`.
-
-        4. x.parent is RED, but x.uncle is BLACK (or None). x.grandparent exists because x.parent is RED.
-
-            4.1. `x` is added to the right of a left child of `x.parent.parent` (grandparent)
-
-            4.2. or `x` is added to the left of a right child of `x.parent.parent`.
-
-            4.3. `x` is added to the left of a left child of `x.parent.parent`.
-
-            4.4. or `x` is added to the right of a right child of `x.parent.parent`.
-
-        `_fix_insertion` handles these cases in the same order as just presented above.
-
-        **Time Complexity:** O(log2(n))."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-
-        if not isinstance(x, RBTNode):
-            x = RBTNode(x, value)
-
-        if x.left or x.right or x.parent:
-            raise ValueError("x cannot have left or right children, or parent.")
-
-        c = self.root  # Current node
-        p = None  # Current node's parent
-
-        while c is not None:
-            p = c
-            if x.key < c.key:
-                c = c.left
-            else:  # x.key >= c.key
-                c = c.right
-
-        x.parent = p
-
-        # The while loop was not executed even once.
-        # Case 1: node is inserted as root.
-        if p is None:
-            self.root = x
-        elif p.key > x.key:
-            p.left = x
-        else:  # p.key < x.key:
-            p.right = x
-
-        x.color = RED
-        self.n += 1
-        self._fix_insertion(x)
-
-    def _fix_insertion(self, u: RBTNode) -> None:
-        # u is the root and we color it BLACK.
-        if u.parent is None:
-            u.color = BLACK
-
-        elif u.parent.color == BLACK:
-            return
-
-        elif u.parent.color == RED and (u.uncle is not None and u.uncle.color == RED):
-            u.parent.color = BLACK
-            u.uncle.color = BLACK
-            u.grandparent.color = RED
-            self._fix_insertion(u.grandparent)
-
-        elif u.parent.color == RED and (u.uncle is None or u.uncle.color == BLACK):
-
-            # u is added as a right child to a node that is the left child.
-            if u.parent.is_left_child() and u.is_right_child():
-
-                # left_rotation does not violate the property:
-                # all paths from any given node to its leaf nodes
-                # contain the same number of black nodes.
-                self.left_rotate(u.parent)
-
-                # With the previous left_rotate call,
-                # u.parent has become the left child of u,
-                # or, u bas become the parent of what before was u.parent
-                # We can pass to case 5, where we have 2 red nodes in a row,
-                # specifically, u.parent and u,
-                # which are both left children of their parents.
-
-                self._fix_insertion(u.left)
-
-            # u is added as a left child to a node that is the right child.
-            elif u.parent.is_right_child() and u.is_left_child():
-                self.right_rotate(u.parent)
-                self._fix_insertion(u.right)
-
-            # u is added as a left child to a node that is the left child.
-            elif u.parent.is_left_child() and u.is_left_child():
-                # Note that grandparent is known to be black,
-                # since its former child could not have been RED
-                # without violating property 4.
-                self.right_rotate(u.grandparent)
-                u.parent.color = BLACK
-                u.parent.right.color = RED
-
-            # u is added as a right child to a node that is the right child.
-            elif u.parent.is_right_child() and u.is_right_child():
-                self.left_rotate(u.grandparent)
-                u.parent.color = BLACK
-                u.parent.left.color = RED
-
-            else:
-                assert False
-
-    def delete(self, x) -> RBTNode:
-        """Delete `x` from this `RBT` object.
-
-        `x` can either be a `RBTNode` object or a key.
-
-        If a key, then a search is performed first
-        to find the corresponding `RBTNode` object.
-        An exception is raised if a `RBTNode` object
-        with a key=x is not found.
-
-        If `x` is a `RBTNode` object, the only check
-        that is performed is that if it hasn't a parent,
-        then it must be the root. Similarly,
-        a node that isn't the root must have a parent.
-        If `x` has a parent, therefore it cannot be the root,
-        but there's no way of knowing if this node
-        really belongs to this `RBT` object,
-        because no search is performed (for now).
-
-        If it does NOT belong to this `RBT` object,
-        then the behaviour of this method is UNDEFINED!
-
-        **Time Complexity:** O(log2(n))."""
-
-        def delete_case1(v):
-            # this check is necessary because this function
-            # is also called from the delete_case3 function.
-            if v.parent is not None:
-                delete_case2(v)
-
-        def delete_case2(v):
-            if v.sibling.color == RED:
-
-                assert v.parent.color == BLACK
-
-                v.sibling.color = BLACK
-                v.parent.color = RED
-
-                if v.is_left_child():
-                    self.left_rotate(v.parent)
-                else:
-                    self.right_rotate(v.parent)
-
-                assert v.sibling.color == BLACK
-
-            delete_case3(v)
-
-        def delete_case3(v):
-            # not sure if the children of v.sibling can be None
-            if (v.parent.color == BLACK and v.sibling.color == BLACK and
-                    ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
-                    ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
-
-                v.sibling.color = RED
-                delete_case1(v.parent)
-            else:
-                delete_case4(v)
-
-        def delete_case4(v):
-            # not sure if the children of v.sibling can be None
-            if (v.parent.color == RED and v.sibling.color == BLACK and
-                    ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
-                    ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
-
-                v.sibling.color = RED
-                v.parent.color = BLACK
-            else:
-                delete_case5(v)
-
-        def delete_case5(v):
-            assert v.sibling is not None
-
-            if v.sibling.color == BLACK:
-                if (v.is_left_child() and
-                        (not v.sibling.right or v.sibling.right.color == BLACK) and
-                            v.sibling.left.color == RED):
-
-                    v.sibling.color = RED
-                    v.sibling.left.color = BLACK
-                    self.right_rotate(v.sibling)
-
-                elif (v.is_right_child() and
-                          (not v.sibling.left or v.sibling.left.color == BLACK) and
-                              v.sibling.right.color == RED):
-
-                    v.sibling.color = RED
-                    v.sibling.right.color = BLACK
-                    self.left_rotate(v.sibling)
-
-            delete_case6(v)
-
-        def delete_case6(v):
-            assert v.sibling is not None
-
-            v.sibling.color, v.parent.color = v.parent.color, v.sibling.color
-
-            if v.is_left_child():
-                assert v.sibling.right
-                v.sibling.right.color = BLACK
-                self.left_rotate(v.parent)
-            else:
-                assert v.sibling.left
-                v.sibling.left.color = BLACK
-                self.right_rotate(v.parent)
-
-        # a few checks of the inputs given
-        if x is None:
-            raise ValueError("x cannot be None.")
-
-        if not isinstance(x, RBTNode):
-            x = self.search(x)
-            if x is None:
-                raise LookupError("No node was found with key=x.")
-
-        if x.parent is None and not self.is_root(x):
-            raise ValueError("x is not a valid node.")
-
-        # If x has 2 non-leaf children, then replace x with its successor.
-        # Note that we exchange also the colors of x and its successor.
-        if x.left is not None and x.right is not None:
-            s = self.successor(x)
-            self._switch(x, s)
-            x.color, s.color = s.color, x.color
-
-        # At least one of the children must be None.
-        # Particularly, if `x` was exchanged with its successor,
-        # `x` now should NOT have a left child.
-        assert x.left is None or x.right is None
-
-        # At this point `x` has at most 1 child.
-        # Keep in mind this when reading the next cases.
-
-        # If `x` is a red node and it has a child,
-        # we simply replace it with its child `c`,
-        # which must be black by property 4.
-
-        # This can only occur when `x` has 2 leaf children,
-        # because if `x` had a black NON-leaf child on one side,
-        # but just a leaf child on the other side,
-        # then the count of black nodes on both sides would be different,
-        # thus the tree would violate property 5.
-        if x.color == RED:
-
-            # a few checks while in alpha stage
-            assert x.left is None and x.right is None
-            assert not self.is_root(x)
-
-            if x.is_left_child():
-                x.parent.left = None
-            else:
-                x.parent.right = None
-
-        else:  # x.color == BLACK
-
-            # One of the children of `x` is red.
-
-            # Simply removing `x` could break properties 4,
-            # i.e., both children of every red node are black,
-            # because x.parent could be red, and 5,
-            # i.e. all paths from any given node to its leaf nodes
-            # contain the same number of black nodes),
-            # but if we repaint `c` (the child) BLACK,
-            # both of these properties are preserved.
-
-            if x.left is not None and x.left.color == RED:
-                if not self.is_root(x):
-                    if x.is_left_child():
-                        x.parent.left = x.left
-                    else:
-                        x.parent.right = x.left
-
-                x.left.parent = x.parent
-                x.left.color = BLACK
-
-                if self.is_root(x):
-                    self.root = x.left
-
-            elif x.right is not None and x.right.color == RED:
-                if not self.is_root(x):
-                    if x.is_left_child():
-                        x.parent.left = x.right
-                    else:
-                        x.parent.right = x.right
-
-                x.right.parent = x.parent
-                x.right.color = BLACK
-
-                if self.is_root(x):
-                    self.root = x.right
-            else:
-                # This the complex case: both `x` and `c` (the child) are BLACK.
-
-                # This can only occur when deleting a black node
-                # which has 2 LEAF children, because if the black node `x`
-                # had a black NON-leaf child on one side
-                # but just a leaf child on the other side,
-                # then the count of black nodes on both sides would be different,
-                # thus the tree would have been an invalid red–black tree
-                # by violation of property 5.
-                assert x.left is None and x.right is None
-
-                # 6 cases
-                if not self.is_root(x):
-
-                    assert x.sibling is not None
-
-                    # Note that x.sibling cannot be None,
-                    # because otherwise the subtree containing it
-                    # would have fewer black nodes
-                    # than the subtree containing x.
-                    # Specifically, the subtree containing x
-                    # would have a black height of 2,
-                    # whereas the one containing the sibling
-                    # would have a black height of 1.
-
-                    delete_case1(x)
-
-                    # We begin by replacing x with its child c.
-                    # Note that both children of x are leaf children.
-                    if x.is_left_child():
-                        x.parent.left = None
-                    else:
-                        x.parent.right = None
-
-                else:
-                    self.root = None
-
-        self.n -= 1
-        # Ensures that x has no reference to any node of this RBT.
-        x.parent = x.left = x.right = None
-        return x
-
-    def remove_max(self) -> RBTNode:
-        """Removes and returns the element with the greatest value from `self`.
-
-        **Time Complexity:** O(log2(n))."""
-        if self.root:
-            m = self.maximum()
-            assert m
-            return self.delete(m)
-
-    def remove_min(self) -> RBTNode:
-        """Removes and returns the element with the smallest value from `self`.
-
-        **Time Complexity:** O(log2(n))."""
-        if self.root:
-            m = self.minimum()
-            assert m
-            return self.delete(m)
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • RBT
  • -
  • ands.ds.BST.BST
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self, root=None, name='RBT')

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, root=None, name="RBT"):
-    BST.__init__(self, root, name)
-
-
-
- -
- - -
-
-

def clear(

self)

-
- - - - -

Removes all nodes from this tree.

-

Time Complexity: O(1).

-
- -
-
def clear(self):
-    """Removes all nodes from this tree.
-    **Time Complexity**: O(1)."""
-    self.root = None
-    self.n = 0
-
-
-
- -
- - -
-
-

def contains(

self, key)

-
- - - - -

Returns True if a BSTNode object with key exists in the tree.

-

Time Complexity: O(h).

-
- -
-
def contains(self, key) -> bool:
-    """Returns `True` if a `BSTNode` object with `key` exists in the tree.
-    **Time Complexity**: O(h)."""
-    return self.search_r(key) is not None
-
-
-
- -
- - -
-
-

def delete(

self, x)

-
- - - - -

Delete x from this RBT object.

-

x can either be a RBTNode object or a key.

-

If a key, then a search is performed first -to find the corresponding RBTNode object. -An exception is raised if a RBTNode object -with a key=x is not found.

-

If x is a RBTNode object, the only check -that is performed is that if it hasn't a parent, -then it must be the root. Similarly, -a node that isn't the root must have a parent. -If x has a parent, therefore it cannot be the root, -but there's no way of knowing if this node -really belongs to this RBT object, -because no search is performed (for now).

-

If it does NOT belong to this RBT object, -then the behaviour of this method is UNDEFINED!

-

Time Complexity: O(log2(n)).

-
- -
-
def delete(self, x) -> RBTNode:
-    """Delete `x` from this `RBT` object.
-    `x` can either be a `RBTNode` object or a key.
-    If a key, then a search is performed first
-    to find the corresponding `RBTNode` object.
-    An exception is raised if a `RBTNode` object
-    with a key=x is not found.
-    If `x` is a `RBTNode` object, the only check
-    that is performed is that if it hasn't a parent,
-    then it must be the root. Similarly,
-    a node that isn't the root must have a parent.
-    If `x` has a parent, therefore it cannot be the root,
-    but there's no way of knowing if this node
-    really belongs to this `RBT` object,
-    because no search is performed (for now).
-    If it does NOT belong to this `RBT` object,
-    then the behaviour of this method is UNDEFINED!
-    **Time Complexity:** O(log2(n))."""
-    def delete_case1(v):
-        # this check is necessary because this function
-        # is also called from the delete_case3 function.
-        if v.parent is not None:
-            delete_case2(v)
-    def delete_case2(v):
-        if v.sibling.color == RED:
-            assert v.parent.color == BLACK
-            v.sibling.color = BLACK
-            v.parent.color = RED
-            if v.is_left_child():
-                self.left_rotate(v.parent)
-            else:
-                self.right_rotate(v.parent)
-            assert v.sibling.color == BLACK
-        delete_case3(v)
-    def delete_case3(v):
-        # not sure if the children of v.sibling can be None
-        if (v.parent.color == BLACK and v.sibling.color == BLACK and
-                ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
-                ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
-            v.sibling.color = RED
-            delete_case1(v.parent)
-        else:
-            delete_case4(v)
-    def delete_case4(v):
-        # not sure if the children of v.sibling can be None
-        if (v.parent.color == RED and v.sibling.color == BLACK and
-                ((v.sibling.left and v.sibling.left.color == BLACK) or not v.sibling.left) and
-                ((v.sibling.right and v.sibling.right.color == BLACK) or not v.sibling.right)):
-            v.sibling.color = RED
-            v.parent.color = BLACK
-        else:
-            delete_case5(v)
-    def delete_case5(v):
-        assert v.sibling is not None
-        if v.sibling.color == BLACK:
-            if (v.is_left_child() and
-                    (not v.sibling.right or v.sibling.right.color == BLACK) and
-                        v.sibling.left.color == RED):
-                v.sibling.color = RED
-                v.sibling.left.color = BLACK
-                self.right_rotate(v.sibling)
-            elif (v.is_right_child() and
-                      (not v.sibling.left or v.sibling.left.color == BLACK) and
-                          v.sibling.right.color == RED):
-                v.sibling.color = RED
-                v.sibling.right.color = BLACK
-                self.left_rotate(v.sibling)
-        delete_case6(v)
-    def delete_case6(v):
-        assert v.sibling is not None
-        v.sibling.color, v.parent.color = v.parent.color, v.sibling.color
-        if v.is_left_child():
-            assert v.sibling.right
-            v.sibling.right.color = BLACK
-            self.left_rotate(v.parent)
-        else:
-            assert v.sibling.left
-            v.sibling.left.color = BLACK
-            self.right_rotate(v.parent)
-    # a few checks of the inputs given
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, RBTNode):
-        x = self.search(x)
-        if x is None:
-            raise LookupError("No node was found with key=x.")
-    if x.parent is None and not self.is_root(x):
-        raise ValueError("x is not a valid node.")
-    # If x has 2 non-leaf children, then replace x with its successor.
-    # Note that we exchange also the colors of x and its successor.
-    if x.left is not None and x.right is not None:
-        s = self.successor(x)
-        self._switch(x, s)
-        x.color, s.color = s.color, x.color
-    # At least one of the children must be None.
-    # Particularly, if `x` was exchanged with its successor,
-    # `x` now should NOT have a left child.
-    assert x.left is None or x.right is None
-    # At this point `x` has at most 1 child.
-    # Keep in mind this when reading the next cases.
-    # If `x` is a red node and it has a child,
-    # we simply replace it with its child `c`,
-    # which must be black by property 4.
-    # This can only occur when `x` has 2 leaf children,
-    # because if `x` had a black NON-leaf child on one side,
-    # but just a leaf child on the other side,
-    # then the count of black nodes on both sides would be different,
-    # thus the tree would violate property 5.
-    if x.color == RED:
-        # a few checks while in alpha stage
-        assert x.left is None and x.right is None
-        assert not self.is_root(x)
-        if x.is_left_child():
-            x.parent.left = None
-        else:
-            x.parent.right = None
-    else:  # x.color == BLACK
-        # One of the children of `x` is red.
-        # Simply removing `x` could break properties 4,
-        # i.e., both children of every red node are black,
-        # because x.parent could be red, and 5,
-        # i.e. all paths from any given node to its leaf nodes
-        # contain the same number of black nodes),
-        # but if we repaint `c` (the child) BLACK,
-        # both of these properties are preserved.
-        if x.left is not None and x.left.color == RED:
-            if not self.is_root(x):
-                if x.is_left_child():
-                    x.parent.left = x.left
-                else:
-                    x.parent.right = x.left
-            x.left.parent = x.parent
-            x.left.color = BLACK
-            if self.is_root(x):
-                self.root = x.left
-        elif x.right is not None and x.right.color == RED:
-            if not self.is_root(x):
-                if x.is_left_child():
-                    x.parent.left = x.right
-                else:
-                    x.parent.right = x.right
-            x.right.parent = x.parent
-            x.right.color = BLACK
-            if self.is_root(x):
-                self.root = x.right
-        else:
-            # This the complex case: both `x` and `c` (the child) are BLACK.
-            # This can only occur when deleting a black node
-            # which has 2 LEAF children, because if the black node `x`
-            # had a black NON-leaf child on one side
-            # but just a leaf child on the other side,
-            # then the count of black nodes on both sides would be different,
-            # thus the tree would have been an invalid red–black tree
-            # by violation of property 5.
-            assert x.left is None and x.right is None
-            # 6 cases
-            if not self.is_root(x):
-                assert x.sibling is not None
-                # Note that x.sibling cannot be None,
-                # because otherwise the subtree containing it
-                # would have fewer black nodes
-                # than the subtree containing x.
-                # Specifically, the subtree containing x
-                # would have a black height of 2,
-                # whereas the one containing the sibling
-                # would have a black height of 1.
-                delete_case1(x)
-                # We begin by replacing x with its child c.
-                # Note that both children of x are leaf children.
-                if x.is_left_child():
-                    x.parent.left = None
-                else:
-                    x.parent.right = None
-            else:
-                self.root = None
-    self.n -= 1
-    # Ensures that x has no reference to any node of this RBT.
-    x.parent = x.left = x.right = None
-    return x
-
-
-
- -
- - -
-
-

def height(

self)

-
- - - - -

Returns the maximum depth or height of the tree.

-

Time Complexity: O(h).

-
- -
-
def height(self) -> int:
-    """Returns the maximum depth or height of the tree.
-    **Time Complexity**: O(h)."""
-    if self.root is None:
-        return 0
-    return self._height(self.root)
-
-
-
- -
- - -
-
-

def in_order_traversal(

self)

-
- - - - -

Prints the elements of the tree in increasing order.

-

Time Complexity: O(h).

-
- -
-
def in_order_traversal(self):
-    """Prints the elements of the tree in increasing order.
-    **Time Complexity**: O(h)."""
-    self._in_order_traversal(self.root)
-    print("\n")
-
-
-
- -
- - -
-
-

def insert(

self, x, value=None)

-
- - - - -

Inserts x into this RBT.

-

This operation is similar to the insert operation of a classical BST, -but, in this case, the red-black tree property must be maintained, -so addional work is needed.

-

There are several cases of inserting into a RBT to handle:

-
    -
  1. -

    x is the root node (first node).

    -
  2. -
  3. -

    x.parent is BLACK.

    -
  4. -
  5. -

    x.parent and the uncle of x are RED.

    -

    The uncle of x will be the left child of x.parent.parent, -if x.parent is the right child of x.parent.parent, -otherwise (x.parent is the left child of x.parent.parent) -the uncle will be the right child of x.parent.parent.

    -
  6. -
  7. -

    x.parent is RED, but x.uncle is BLACK (or None). x.grandparent exists because x.parent is RED.

    -

    4.1. x is added to the right of a left child of x.parent.parent (grandparent)

    -

    4.2. or x is added to the left of a right child of x.parent.parent.

    -

    4.3. x is added to the left of a left child of x.parent.parent.

    -

    4.4. or x is added to the right of a right child of x.parent.parent.

    -
  8. -
-

_fix_insertion handles these cases in the same order as just presented above.

-

Time Complexity: O(log2(n)).

-
- -
-
def insert(self, x, value=None) -> None:
-    """Inserts `x` into this `RBT`.
-    This operation is similar to the `insert` operation of a classical `BST`,
-    but, in this case, the red-black tree property must be maintained,
-    so addional work is needed.
-    There are several cases of inserting into a RBT to handle:
-    1. `x`  is the root node (first node).
-    2. `x.parent` is `BLACK`.
-    3. `x.parent` and the uncle of `x` are `RED`.
-        The uncle of `x` will be the left child of `x.parent.parent`,
-    if `x.parent` is the right child of `x.parent.parent`,
-    otherwise (`x.parent` is the left child of `x.parent.parent`)
-    the uncle will be the right child of `x.parent.parent`.
-    4. x.parent is RED, but x.uncle is BLACK (or None). x.grandparent exists because x.parent is RED.
-        4.1. `x` is added to the right of a left child of `x.parent.parent` (grandparent)
-        4.2. or `x` is added to the left of a right child of `x.parent.parent`.
-        4.3. `x` is added to the left of a left child of `x.parent.parent`.
-        4.4. or `x` is added to the right of a right child of `x.parent.parent`.
-    `_fix_insertion` handles these cases in the same order as just presented above.
-    **Time Complexity:** O(log2(n))."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, RBTNode):
-        x = RBTNode(x, value)
-    if x.left or x.right or x.parent:
-        raise ValueError("x cannot have left or right children, or parent.")
-    c = self.root  # Current node
-    p = None  # Current node's parent
-    while c is not None:
-        p = c
-        if x.key < c.key:
-            c = c.left
-        else:  # x.key >= c.key
-            c = c.right
-    x.parent = p
-    # The while loop was not executed even once.
-    # Case 1: node is inserted as root.
-    if p is None:
-        self.root = x
-    elif p.key > x.key:
-        p.left = x
-    else:  # p.key < x.key:
-        p.right = x
-    x.color = RED
-    self.n += 1
-    self._fix_insertion(x)
-
-
-
- -
- - -
-
-

def insert_many(

self, ls)

-
- - - - -

Calls self.insert for all elements of ls. -Therefore the elements of ls should either be -BSTNode objects or they should represent keys.

-

Time Complexity: O(len(ls)*h).

-
- -
-
def insert_many(self, ls):
-    """Calls `self.insert` for all elements of `ls`.
-    Therefore the elements of `ls` should either be
-    `BSTNode` objects or they should represent keys.
-    **Time Complexity**: O(len(ls)*h)."""
-    for i in ls:
-        self.insert(i)
-
-
-
- -
- - -
-
-

def is_empty(

self)

-
- - - - -

Returns True if this tree has 0 nodes.

-

Time Complexity: O(1).

-
- -
-
def is_empty(self):
-    """Returns `True` if this tree has 0 nodes.
-    **Time Complexity**: O(1)."""
-    return self.size() == 0
-
-
-
- -
- - -
-
-

def is_root(

self, u)

-
- - - - -

Checks if u is the root.

-

Time Complexity: O(1).

-
- -
-
def is_root(self, u: BSTNode):
-    """Checks if `u` is the root.
-    **Time Complexity**: O(1)."""
-    if u == self.root:
-        assert u.parent is None
-    return u == self.root
-
-
-
- -
- - -
-
-

def left_rotate(

self, x)

-
- - - - -

Left rotates the subtree rooted at node x.

-

x can be a BSTNode object, and in that case, -this function performs in constant time O(1); -else, if node is not a BSTNode object, -it tries to search for a BSTNode object with key=x, -and, in that case, it performs in O(h) time.

-

Returns the node which is at the previous position of x, -that is it returns the parent of x.

-

Time Complexity: O(1).

-
- -
-
def left_rotate(self, x):
-    """Left rotates the subtree rooted at node `x`.
-    `x` can be a `BSTNode` object, and in that case,
-    this function performs in constant time O(1);
-    else, if node is not a `BSTNode` object,
-    it tries to search for a `BSTNode` object with key=x,
-    and, in that case, it performs in O(h) time.
-    Returns the node which is at the previous position of `x`,
-    that is it returns the parent of `x`.
-    **Time Complexity**: O(1)."""
-    c = None  # It will rotate the subtree rooted at c.
-    if not isinstance(x, BSTNode):
-        c = self.search(x)
-        if c is None:
-            raise LookupError("key node was not found in the tree.")
-    else:
-        c = x
-    # To left rotate a node, its right child must exist.
-    if c.right is None:
-        raise ValueError("Left rotation cannot be performed on " + str(c) +
-                         " because it does not have a right child.")
-    c.right.parent = c.parent
-    # Only the root has a None parent.
-    if c.parent is None:
-        self.root = c.right
-    # Checking if c is a left or a right child,
-    # in order to set the new left
-    # or right child respectively of its parent.
-    elif c.is_left_child():
-        c.parent.left = c.right
-    else:
-        c.parent.right = c.right
-    c.parent = c.right
-    # The new right child of c becomes what is
-    # the left child of its previous right child.
-    c.right = c.parent.left
-    # Set c to be the parent of its new right child.
-    if c.right is not None:
-        c.right.parent = c
-    # Set c to be the new left child of its new parent.
-    c.parent.left = c
-    return c.parent
-
-
-
- -
- - -
-
-

def maximum(

self)

-
- - - - -

Calls BST._maximum_r(self.root) if self.root is evaluated to True.

-

Time Complexity: O(h).

-
- -
-
def maximum(self):
-    """Calls `BST._maximum_r(self.root)` if `self.root` is evaluated to `True`.
-    **Time Complexity**: O(h)."""
-    if self.root:
-        return BST._maximum_r(self.root)
-
-
-
- -
- - -
-
-

def minimum(

self)

-
- - - - -

Calls BST._minimum_r(self.root) if self.root is evaluated to True.

-

Time Complexity: O(h).

-
- -
-
def minimum(self):
-    """Calls `BST._minimum_r(self.root)` if `self.root` is evaluated to `True`.
-    **Time Complexity**: O(h)."""
-    if self.root:
-        return BST._minimum_r(self.root)
-
-
-
- -
- - -
-
-

def post_order_traversal(

self)

-
- - - - -

Prints the keys of this tree in post-order. -It does the opposite of pre_order_traversal.

-

Time Complexity: O(h).

-
- -
-
def post_order_traversal(self):
-    """Prints the keys of this tree in post-order.
-    It does the opposite of `pre_order_traversal`.
-    **Time Complexity**: O(h)."""
-    self._post_order_traversal(self.root)
-    print("\n")
-
-
-
- -
- - -
-
-

def pre_order_traversal(

self)

-
- - - - -

Prints the keys of this tree in pre-order. -The pre-order consists of recursively printing first a node u, -then its left child node and then its right child node.

-

Time Complexity: O(h).

-
- -
-
def pre_order_traversal(self):
-    """Prints the keys of this tree in pre-order.
-    The pre-order consists of recursively printing first a node `u`,
-    then its left child node and then its right child node.
-    **Time Complexity**: O(h)."""
-    self._pre_order_traversal(self.root)
-    print("\n")
-
-
-
- -
- - -
-
-

def predecessor(

self, x)

-
- - - - -

Finds the predecessor of the node x, -i.e. the greatest element smaller than x.

-

x can either be a reference to an actual BSTNode object, -or it can be a key of a supposed node in self.

-

Time Complexity: O(h).

-
- -
-
def predecessor(self, x):
-    """Finds the predecessor of the node `x`,
-    i.e. the greatest element smaller than `x`.
-    `x` can either be a reference to an actual `BSTNode` object,
-    or it can be a key of a supposed node in self.
-    **Time Complexity**: O(h)."""
-    if not isinstance(x, BSTNode):
-        x = self.search_r(x)
-        if x is None:
-            raise LookupError("No node was found with key=x.")
-    if x.left:
-        return BST._maximum_r(x.left)
-    p = x.parent
-    while p and x == p.left:
-        x = p
-        p = x.parent
-    return p
-
-
-
- -
- - -
-
-

def rank(

self, key)

-
- - - - -

Returns the number of keys strictly less than key.

-

Time Complexity: O(h).

-
- -
-
def rank(self, key) -> int:
-    """Returns the number of keys strictly less than `key`.
-    **Time Complexity**: O(h)."""
-    if key is None:
-        raise ValueError("key cannot be None.")
-    if not self.search(key):
-        raise LookupError("key was not found.")
-    if self.root is None:
-        return 0
-    else:
-        r = 0
-        return self._rank(self.root, key, r)
-
-
-
- -
- - -
-
-

def remove_max(

self)

-
- - - - -

Removes and returns the element with the greatest value from self.

-

Time Complexity: O(log2(n)).

-
- -
-
def remove_max(self) -> RBTNode:
-    """Removes and returns the element with the greatest value from `self`.
-    **Time Complexity:** O(log2(n))."""
-    if self.root:
-        m = self.maximum()
-        assert m
-        return self.delete(m)
-
-
-
- -
- - -
-
-

def remove_min(

self)

-
- - - - -

Removes and returns the element with the smallest value from self.

-

Time Complexity: O(log2(n)).

-
- -
-
def remove_min(self) -> RBTNode:
-    """Removes and returns the element with the smallest value from `self`.
-    **Time Complexity:** O(log2(n))."""
-    if self.root:
-        m = self.minimum()
-        assert m
-        return self.delete(m)
-
-
-
- -
- - -
-
-

def reverse_in_order_traversal(

self)

-
- - - - -

Prints the keys of this tree in decreasing order.

-

It does the opposite of self.in_order_traversal.

-

Time Complexity: O(h).

-
- -
-
def reverse_in_order_traversal(self):
-    """Prints the keys of this tree in decreasing order.
-    It does the opposite of `self.in_order_traversal`.
-    **Time Complexity**: O(h)."""
-    self._reverse_in_order_traversal(self.root)
-    print("\n")
-
-
-
- -
- - -
-
-

def right_rotate(

self, x)

-
- - - - -

Right rotates the subtree rooted at node x. -See doc-strings of self.left_rotate.

-

Time Complexity: O(1).

-
- -
-
def right_rotate(self, x):
-    """Right rotates the subtree rooted at node `x`.
-    See doc-strings of `self.left_rotate`.
-    **Time Complexity**: O(1)."""
-    c = None
-    if not isinstance(x, BSTNode):
-        c = self.search(x)
-        if c is None:
-            raise LookupError("key node was not found in the tree.")
-    else:
-        c = x
-    if c.left is None:
-        raise ValueError("Right rotation cannot be performed on " + str(c) +
-                         " because it does not have a left child.")
-    c.left.parent = c.parent
-    if c.parent is None:
-        self.root = c.left
-    elif c.is_left_child():
-        c.parent.left = c.left
-    else:
-        c.parent.right = c.left
-    c.parent = c.left
-    c.left = c.parent.right
-    if c.left is not None:
-        c.left.parent = c
-    c.parent.right = c
-    return c.parent
-
-
-
- -
- - -
-
-

def search(

self, key, s=None)

-
- - - - -

Searches for the key in the tree. -If s is specified, then this procedure starts searching from s.

-

key must be a comparable object of the same type as the other keys.

-

Time Complexity: O(h).

-
- -
-
def search(self, key, s: BSTNode = None) -> BSTNode:
-    """Searches for the key in the tree.
-    If `s` is specified, then this procedure starts searching from `s`.
-    `key` must be a comparable object of the same type as the other keys.
-    **Time Complexity**: O(h)."""
-    if key is None:
-        raise ValueError("key cannot be None.")
-    if s is None:
-        return self.search_i(key)
-    else:
-        return BST._search_i(key, s)
-
-
-
- -
- - -
-
-

def search_i(

self, key)

-
- - - - -

Searches iteratively for key starting from the root.

-

Time Complexity: O(h).

-
- -
-
def search_i(self, key) -> BSTNode:
-    """Searches iteratively for key starting from the root.
-    **Time Complexity**: O(h)."""
-    return BST._search_i(key, self.root)
-
-
-
- -
- - -
-
-

def search_r(

self, key)

-
- - - - -

Searches recursively for key starting from self.root.

-

Time Complexity: O(h).

-
- -
-
def search_r(self, key) -> BSTNode:
-    """Searches recursively for `key` starting from `self.root`.
-    **Time Complexity**: O(h)."""
-    return self._search_r(key, self.root)
-
-
-
- -
- - -
-
-

def show(

self)

-
- - - - -

Pretty-prints this tree using print.

-
- -
-
def show(self):
-    """Pretty-prints this tree using `print`."""
-    print(self)
-
-
-
- -
- - -
-
-

def size(

self)

-
- - - - -

Returns the total number of nodes.

-

Time Complexity: O(1).

-
- -
-
def size(self):
-    """Returns the total number of nodes.
-    **Time Complexity**: O(1)."""
-    return self.n
-
-
-
- -
- - -
-
-

def successor(

self, x)

-
- - - - -

Finds the successor of x, -i.e. the smallest element greater than x.

-

If x has a right subtree, -then the successor of x is the minimum of that right subtree.

-

Otherwise it is the first ancestor of x, lets call it A, -such that x falls in the left subtree of A.

-

x can either be a reference to an actual BSTNode object, -or it can be a key of a supposed node in self.

-

Time Complexity: O(h).

-
- -
-
def successor(self, x):
-    """Finds the successor of `x`,
-    i.e. the smallest element greater than `x`.
-    If `x` has a right subtree,
-    then the successor of `x` is the minimum of that right subtree.
-    Otherwise it is the first ancestor of `x`, lets call it `A`,
-    such that `x` falls in the left subtree of `A`.
-    `x` can either be a reference to an actual `BSTNode` object,
-    or it can be a key of a supposed node in self.
-    **Time Complexity**: O(h)."""
-    if not isinstance(x, BSTNode):
-        x = self.search(x)
-        if x is None:
-            raise LookupError("No node was found with key=x.")
-    if x.right:
-        return BST._minimum_r(x.right)
-    p = x.parent
-    while p and p.right == x:
-        x = p
-        p = x.parent
-    return p
-
-
-
- -
- -
-
- -
-

class RBTNode

- - -

Class to represent a RBT's node.

-
- -
-
class RBTNode(BSTNode):
-    """Class to represent a `RBT`'s node."""
-
-    def __init__(self, key, value=None, color=BLACK,
-                 parent=None, left=None, right=None):
-        BSTNode.__init__(self, key, value, parent, left, right)
-        self._color = color
-        self.label = "[" + str(self.key) + ", " + str(self._color) + "]"
-
-    @property
-    def color(self):
-        return self._color
-
-    @color.setter
-    def color(self, value):
-        self._color = value
-        self.label = "[" + str(self.key) + ", " + str(self._color) + "]"
-
-    def reset(self):
-        super().reset()
-        self.color = BLACK
-
-    def __fields(self):
-        """Used by __repr__."""
-        return [["Node (Key)", self.key],
-                ["Value", self.value],
-                ["Color", self.color],
-                ["Parent", self.parent],
-                ["Left child", self.left],
-                ["Right child", self.right],
-                ["Sibling", self.sibling],
-                ["Grandparent", self.grandparent],
-                ["Uncle", self.uncle]]
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • RBTNode
  • -
  • ands.ds.BST.BSTNode
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self, key, value=None, color='BLACK', parent=None, left=None, right=None)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, key, value=None, color=BLACK,
-             parent=None, left=None, right=None):
-    BSTNode.__init__(self, key, value, parent, left, right)
-    self._color = color
-    self.label = "[" + str(self.key) + ", " + str(self._color) + "]"
-
-
-
- -
- - -
-
-

def count(

self)

-
- - - - -

Counts the numbers of nodes under self (including self).

-
- -
-
def count(self) -> int:
-    """Counts the numbers of nodes under `self` (including `self`)."""
-    def _count(u, c: int):
-        if u is None:
-            return c
-        else:
-            c += 1
-        c = _count(u.left, c)
-        c = _count(u.right, c)
-        return c
-    if not self.has_children():
-        return 1
-    else:
-        c = 0
-        return _count(self, c)
-
-
-
- -
- - -
-
-

def has_children(

self)

-
- - - - -

Returns True if self has at least one child. False otherwise.

-
- -
-
def has_children(self) -> bool:
-    """Returns `True` if `self` has at least one child. `False` otherwise."""
-    return self.left or self.right
-
-
-
- -
- - -
-
-

def has_one_child(

self)

-
- - - - -

Returns True only if self has exactly one child. False otherwise.

-
- -
-
def has_one_child(self) -> bool:
-    """Returns `True` only if `self` has exactly one child. `False` otherwise."""
-    return (self.left and not self.right) or (not self.left and self.right)
-
-
-
- -
- - -
-
-

def has_two_children(

self)

-
- - - - -

Returns True if self has exactly two children. False otherwise.

-
- -
-
def has_two_children(self) -> bool:
-    """Returns `True` if self has exactly two children. `False` otherwise."""
-    return self.left and self.right
-
-
-
- -
- - -
-
-

def is_left_child(

self)

-
- - - - -
- -
-
def is_left_child(self) -> bool:
-    if self.parent is not None:
-        if self.parent.left is not None:
-            return self.parent.left == self
-    else:
-        raise AttributeError("self does not have a parent.")
-
-
-
- -
- - -
-
-

def is_right_child(

self)

-
- - - - -
- -
-
def is_right_child(self) -> bool:
-    if self.parent is not None:
-        if self.parent.right is not None:
-            return self.parent.right == self
-    else:
-        raise AttributeError("self does not have a parent.")
-
-
-
- -
- - -
-
-

def reset(

self)

-
- - - - -
- -
-
def reset(self):
-    super().reset()
-    self.color = BLACK
-
-
-
- -
- - -
-
-

def show(

self)

-
- - - - -
- -
-
def show(self):
-    print(self.__repr__())
-
-
-
- -
- -

Instance variables

-
-

var color

- - - - -
-
- -
-
-

var grandparent

- - - - -

Returns the parent of the parent of this node.

-
-
- -
-
-

var label

- - - - -
-
- -
-
-

var sibling

- - - - -

Returns the sibling node of this node, -which can of course be None.

-
-
- -
-
-

var uncle

- - - - -

Returns the uncle node of this node. -The uncle is the sibling of the parent of this node, -if it exists. None is returned if it doesn't exist, -or the parent or grandparent of this node is None.

-
-
- -
-
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/Stack.m.html b/docs/ands/ds/Stack.m.html deleted file mode 100644 index 95e0c18d..00000000 --- a/docs/ands/ds/Stack.m.html +++ /dev/null @@ -1,1439 +0,0 @@ - - - - - - ands.ds.Stack API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.Stack module

-

Meta info

-

Author: Nelson Brochado

-

Created: 05/07/2015

-

Updated: 04/02/2017

-

Description

-

A stack is one of the most simple and, at the same time, useful abstract data types in computer science.

-

An abstract data type (or, in short, ADT) is a logical description or specification -of a certain way of viewing and/or organizing data, and which values and operations are allowed on this data. -An ADT is, as the same suggests, an abstract concept or mathematical model; -thus an ADT can be implemented as a data structure in many ways. -Essentially, ADTs is all about ideas or concepts of representing and manipulating data, -whereas a data structure is an implementation of a specific ADTs; -hence there can be more than one data structure for the same ADT.

-

What defines a stack is the order of insertion and removal of elements from it: -a stack is a "last in, first out" (or, as an acronym, LIFO) abstract data type, -that is, the last element inserted into the stack is the first to be removed. -Since this is an ADT, we don't care how the elements are stored in memory, -or how we manipulate them so that the last element inserted is the first to be removed. -The insertion of an element is usually called "push", whereas the removal is usually called "pop". -There's also another operation (i.e. "peek" or "top") -which consists in looking at the last element inserted into the stack. -Of course other operations, such as "size of the stack" (i.e. how many elements in the stack) -or "is empty" (i.e. checking if the stack contains elements or not) may also useful.

-

References

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-# Meta info
-
-Author: Nelson Brochado
-
-Created: 05/07/2015
-
-Updated: 04/02/2017
-
-# Description
-
-A stack is one of the most simple and, at the same time, useful abstract data types in computer science.
-
-An abstract data type (or, in short, ADT) is a logical description or specification
-of a certain way of viewing and/or organizing data, and which values and operations are allowed on this data.
-An ADT is, as the same suggests, an abstract concept or mathematical model;
-thus an ADT can be implemented as a data structure in many ways.
-Essentially, ADTs is all about ideas or concepts of representing and manipulating data,
-whereas a data structure is an implementation of a specific ADTs;
-hence there can be more than one data structure for the same ADT.
-
-What defines a stack is the order of insertion and removal of elements from it:
-a stack is a "last in, first out" (or, as an acronym, LIFO) abstract data type,
-that is, the last element inserted into the stack is the first to be removed.
-Since this is an ADT, we don't care how the elements are stored in memory,
-or how we manipulate them so that the last element inserted is the first to be removed.
-The insertion of an element is usually called "push", whereas the removal is usually called "pop".
-There's also another operation (i.e. "peek" or "top")
-which consists in looking at the last element inserted into the stack.
-Of course other operations, such as "size of the stack" (i.e. how many elements in the stack)
-or "is empty" (i.e. checking if the stack contains elements or not) may also useful.
-
-# References
-
-- [http://interactivepython.org/runestone/static/pythonds/Introduction/WhyStudyDataStructuresandAbstractDataTypes.html](http://interactivepython.org/runestone/static/pythonds/Introduction/WhyStudyDataStructuresandAbstractDataTypes.html)
-- [http://stackoverflow.com/questions/195625/what-is-the-time-complexity-of-popping-elements-from-list-in-python](http://stackoverflow.com/questions/195625/what-is-the-time-complexity-of-popping-elements-from-list-in-python),
-for the time complexity analysis of the pop operation of the last element on lists.
-- [http://stackoverflow.com/questions/1115313/cost-of-len-function](http://stackoverflow.com/questions/1115313/cost-of-len-function),
-for the time complexity analysis of the size operation.
-- [http://stackoverflow.com/questions/12342457/what-is-the-big-o-notation-for-the-len-function-in-python](http://stackoverflow.com/questions/12342457/what-is-the-big-o-notation-for-the-len-function-in-python),
-for other time complexity analysis of the list class.
-
-"""
-
-from collections import Iterable
-
-from tabulate import tabulate
-
-__all__ = ["Stack"]
-
-
-class Stack:
-    """This is a wrapper class around the Python's list to represent a stack data structure.
-
-    It doesn't allow you to insert None elements through the public methods.
-    It returns None whenever you try to pop from or peek at the stack, but it's empty.
-
-    The data structure can be initialized with an iterable object with not None values.
-    A copy of the given iterable is made, so the original iterable is not affected when performing operations."""
-
-    def __init__(self, s=None):
-        if s is not None:
-            if not isinstance(s, Iterable):
-                raise TypeError("s must be an instance of Iterable")
-            if any(elem is None for elem in s):
-                raise ValueError("all elements of s must be not None")
-        else:
-            s = []
-        self._stack = list(s)
-
-    def push(self, elem: object) -> None:
-        """Pushes `elem` on top of this stack.
-
-        If `elem` is None`, `ValueError` is raised.
-
-        **Time complexity:** O(1)."""
-        if elem is None:
-            raise ValueError("elem cannot be None")
-        self._stack.append(elem)
-
-    def pop(self) -> object:
-        """Returns the top of this stack, or `None` if the stack is empty.
-
-        **Time complexity:** O(1)."""
-        return None if self.is_empty() else self._stack.pop()
-
-    def size(self) -> int:
-        """Returns the size of this stack.
-
-        **Time complexity:** O(1)."""
-        return len(self._stack)
-
-    def is_empty(self) -> bool:
-        """Returns `True` if this stack is empty, `False` otherwise.
-
-        **Time complexity:** O(1)."""
-        return self.size() == 0
-
-    def top(self) -> object:
-        """Returns but does **not** pop the top of the stack.
-
-        If the stack is empty, `None` is returned.
-
-        This operation is also called "peek".
-
-        **Time complexity:** O(1)."""
-        return None if self.is_empty() else self._stack[-1]
-
-    def __str__(self):
-        return tabulate([[e] for e in reversed(self._stack)], tablefmt="grid")
-
-    def __repr__(self):
-        return self.__str__()
-
-
- -
- -
- - -

Classes

- -
-

class Stack

- - -

This is a wrapper class around the Python's list to represent a stack data structure.

-

It doesn't allow you to insert None elements through the public methods. -It returns None whenever you try to pop from or peek at the stack, but it's empty.

-

The data structure can be initialized with an iterable object with not None values. -A copy of the given iterable is made, so the original iterable is not affected when performing operations.

-
- -
-
class Stack:
-    """This is a wrapper class around the Python's list to represent a stack data structure.
-
-    It doesn't allow you to insert None elements through the public methods.
-    It returns None whenever you try to pop from or peek at the stack, but it's empty.
-
-    The data structure can be initialized with an iterable object with not None values.
-    A copy of the given iterable is made, so the original iterable is not affected when performing operations."""
-
-    def __init__(self, s=None):
-        if s is not None:
-            if not isinstance(s, Iterable):
-                raise TypeError("s must be an instance of Iterable")
-            if any(elem is None for elem in s):
-                raise ValueError("all elements of s must be not None")
-        else:
-            s = []
-        self._stack = list(s)
-
-    def push(self, elem: object) -> None:
-        """Pushes `elem` on top of this stack.
-
-        If `elem` is None`, `ValueError` is raised.
-
-        **Time complexity:** O(1)."""
-        if elem is None:
-            raise ValueError("elem cannot be None")
-        self._stack.append(elem)
-
-    def pop(self) -> object:
-        """Returns the top of this stack, or `None` if the stack is empty.
-
-        **Time complexity:** O(1)."""
-        return None if self.is_empty() else self._stack.pop()
-
-    def size(self) -> int:
-        """Returns the size of this stack.
-
-        **Time complexity:** O(1)."""
-        return len(self._stack)
-
-    def is_empty(self) -> bool:
-        """Returns `True` if this stack is empty, `False` otherwise.
-
-        **Time complexity:** O(1)."""
-        return self.size() == 0
-
-    def top(self) -> object:
-        """Returns but does **not** pop the top of the stack.
-
-        If the stack is empty, `None` is returned.
-
-        This operation is also called "peek".
-
-        **Time complexity:** O(1)."""
-        return None if self.is_empty() else self._stack[-1]
-
-    def __str__(self):
-        return tabulate([[e] for e in reversed(self._stack)], tablefmt="grid")
-
-    def __repr__(self):
-        return self.__str__()
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • Stack
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self, s=None)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, s=None):
-    if s is not None:
-        if not isinstance(s, Iterable):
-            raise TypeError("s must be an instance of Iterable")
-        if any(elem is None for elem in s):
-            raise ValueError("all elements of s must be not None")
-    else:
-        s = []
-    self._stack = list(s)
-
-
-
- -
- - -
-
-

def is_empty(

self)

-
- - - - -

Returns True if this stack is empty, False otherwise.

-

Time complexity: O(1).

-
- -
-
def is_empty(self) -> bool:
-    """Returns `True` if this stack is empty, `False` otherwise.
-    **Time complexity:** O(1)."""
-    return self.size() == 0
-
-
-
- -
- - -
-
-

def pop(

self)

-
- - - - -

Returns the top of this stack, or None if the stack is empty.

-

Time complexity: O(1).

-
- -
-
def pop(self) -> object:
-    """Returns the top of this stack, or `None` if the stack is empty.
-    **Time complexity:** O(1)."""
-    return None if self.is_empty() else self._stack.pop()
-
-
-
- -
- - -
-
-

def push(

self, elem)

-
- - - - -

Pushes elem on top of this stack.

-

If elem is None,ValueError` is raised.

-

Time complexity: O(1).

-
- -
-
def push(self, elem: object) -> None:
-    """Pushes `elem` on top of this stack.
-    If `elem` is None`, `ValueError` is raised.
-    **Time complexity:** O(1)."""
-    if elem is None:
-        raise ValueError("elem cannot be None")
-    self._stack.append(elem)
-
-
-
- -
- - -
-
-

def size(

self)

-
- - - - -

Returns the size of this stack.

-

Time complexity: O(1).

-
- -
-
def size(self) -> int:
-    """Returns the size of this stack.
-    **Time complexity:** O(1)."""
-    return len(self._stack)
-
-
-
- -
- - -
-
-

def top(

self)

-
- - - - -

Returns but does not pop the top of the stack.

-

If the stack is empty, None is returned.

-

This operation is also called "peek".

-

Time complexity: O(1).

-
- -
-
def top(self) -> object:
-    """Returns but does **not** pop the top of the stack.
-    If the stack is empty, `None` is returned.
-    This operation is also called "peek".
-    **Time complexity:** O(1)."""
-    return None if self.is_empty() else self._stack[-1]
-
-
-
- -
- -
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/TST.m.html b/docs/ands/ds/TST.m.html deleted file mode 100644 index 54005909..00000000 --- a/docs/ands/ds/TST.m.html +++ /dev/null @@ -1,3002 +0,0 @@ - - - - - - ands.ds.TST API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.TST module

-

Meta info

-

Author: Nelson Brochado

-

Created: 05/09/2015

-

Updated: 03/02/2017

-

Description

-

Ternary-search tries (or trees) combine the time efficiency of other tries -with the space efficiency of binary-search trees.

-

An advantage compared to hash maps is that ternary search tries support sorting, -but the keys of a ternary-search trie can only be strings, -whereas a hash map supports any kind of hashable keys.

-

TSTs vs Hashing

-

Hashing

-
    -
  • Need to examine entire key
  • -
  • Search miss and hits cost about the same
  • -
  • Performance relies on hash function
  • -
  • Does NOT support ordered symbol table operations
  • -
-

TSTs

-
    -
  • Works only for strings (or digital keys)
  • -
  • Only examines just enough key characters
  • -
  • Search miss may involve only a few characters
  • -
  • Supports ordered symbol table operations:
      -
    • keys-that-match
    • -
    • keys-with-prefix
    • -
    • longest-prefix-of
    • -
    -
  • -
-

Bottom line

-

TSTs are:

-
    -
  • faster than hashing (especially for search misses)
  • -
  • more flexible than red-black trees
  • -
-

References

- -

Resources

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-# Meta info
-
-Author: Nelson Brochado
-
-Created: 05/09/2015
-
-Updated: 03/02/2017
-
-# Description
-
-Ternary-search tries (or trees) combine the time efficiency of other tries
-with the space efficiency of binary-search trees.
-
-An advantage compared to hash maps is that ternary search tries support sorting,
-but the _keys_ of a ternary-search trie can only be _strings_,
-whereas a hash map supports any kind of hashable keys.
-
-## TSTs vs Hashing
-
-### Hashing
-
-- Need to examine entire key
-- Search miss and hits cost about the same
-- Performance relies on hash function
-- Does NOT support ordered symbol table operations
-
-### TSTs
-
-- Works only for strings (or digital keys)
-- Only examines just enough key characters
-- Search miss may involve only a few characters
-- Supports ordered symbol table operations:
-    - keys-that-match
-    - keys-with-prefix
-    - longest-prefix-of
-
-### Bottom line
-
-TSTs are:
-
-- faster than hashing (especially for search misses)
-- more flexible than red-black trees
-
-# References
-
-- [Ternary Search Trees](https://www.cs.upc.edu/~ps/downloads/tst/tst.html) by By Jon Bentley and Bob Sedgewick
-- [Fast Algorithms for Sorting and Searching Strings](https://www.cs.princeton.edu/~rs/strings/), by Jon Bentley and Robert Sedgewick
-- [TST.java](http://algs4.cs.princeton.edu/52trie/TST.java.html), Java implementation by Robert Sedgewick and Kevin Wayne
-- [Ternary Search Tries](https://www.youtube.com/watch?v=CIGyewO7868), video lecture by Robert Sedgewick
-- [Ternary search tree](https://en.wikipedia.org/wiki/Ternary_search_tree) at Wikipedia
-- [How to list in an alphabetical order the words of a ternary search tree?](http://stackoverflow.com/a/27178771/3924118)
-
-# Resources
-
-- [Ternary search tree introduction](https://www.youtube.com/watch?v=xv4oRyqSKiw),
-by [Balazs Holczer](https://www.udemy.com/user/holczerbalazs/)
-- [TernarySearchTree.hh](http://www.keithschwarz.com/interesting/code/?dir=ternary-search-tree),
-C++ implementation of a TST by Keith Schwarz, which provides a good analysis of the complexity of the operations of a TST.
-- [Remove method for Ternary Search Tree](http://p2p.wrox.com/book-beginning-algorithms/60350-remove-method-ternary-search-tree.html),
-at [http://p2p.wrox.com/book-beginning-algorithms](http://p2p.wrox.com/book-beginning-algorithms)
-- [Plant your data in a ternary search tree](http://www.javaworld.com/article/2075027/java-app-dev/plant-your-data-in-a-ternary-search-tree.html?page=1)
-
-"""
-
-
-class TSTNode:
-    """A TSTNode has 6 fields:
-
-        - key, which is a character;
-        - value, which is None if self is not a terminal node (of an inserted string in the TST);
-        - parent, which is a pointer to a TSTNode representing the parent of self;
-        - left, which is a pointer to a TSTNode whose key is smaller lexicographically than key;
-        - right, which is similarly a pointer to a TSTNode whose key is greater lexicographically than key;
-        - mid, which is a pointer to a TSTNode whose key is the following character of key in an inserted string."""
-
-    def __init__(self, key, value=None, parent=None, left=None, mid=None, right=None):
-        if not isinstance(key, str):
-            raise TypeError("key must be an instance of str.")
-        if not key:
-            raise ValueError("key must be a string of length >= 1.")
-
-        self.key = key
-        self.value = value
-        self.parent = parent
-        self.left = left
-        self.mid = mid
-        self.right = right
-
-    def is_left_child(self) -> bool:
-        if not self.parent:
-            raise AttributeError("self does not have a parent.")
-        if self.parent.left:
-            return self.parent.left == self
-        else:
-            return False
-
-    def is_right_child(self) -> bool:
-        if not self.parent:
-            raise AttributeError("self does not have a parent.")
-        if self.parent.right:
-            return self.parent.right == self
-        else:
-            return False
-
-    def is_mid_child(self) -> bool:
-        if not self.parent:
-            raise AttributeError("self does not have a parent.")
-        if self.parent.mid:
-            return self.parent.mid == self
-        else:
-            return False
-
-    def has_children(self) -> bool:
-        return self.left or self.right or self.mid
-
-    def __str__(self):
-        return "{0}: {1}".format(self.key, self.value)
-
-    def __repr__(self):
-        return self.__str__()
-
-
-class TST:
-    """Methods or fields that start with an underscore _ are considered private,
-    so they should not be access and never modified from a client of this class.
-
-    This TST does not allow (through public methods) empty strings to be inserted.
-
-    In general the way the ternary search tree looks like
-    depends highly on the order of insertion of the keys,
-    that is, inserting the same keys but in different orders
-    produces internally a different structure or shape of the TST."""
-
-    def __init__(self):
-        self._n = 0
-        self._root = None
-
-    def __invariants__(self) -> None:
-        """These propositions should always be true at the BEGINNING
-        and END of every PUBLIC method of this TST.
-
-        Call this method if you want to ensure the invariants are holding."""
-        assert self._n >= 0
-        if self._n == 0:
-            assert self._root is None
-        elif self._n > 0:
-            assert isinstance(self._root, TSTNode)
-            assert self._root.parent is None
-
-    def _is_root(self, u: TSTNode) -> bool:
-        result = (self._root == u)
-        if result:
-            assert u.parent is None
-        else:
-            assert u.parent is not None
-        return result
-
-    def size(self) -> int:
-        return self._n
-
-    def count(self) -> int:
-        """Counts the number of strings in self.
-
-        This method recursively passes through all the nodes
-        and counts the ones which have a non None value.
-
-        You should clearly use size instead: 
-        this method is here only for the fun of writing code!
-
-        **Time complexity:** O(n), where n is the number of nodes in this TST."""
-        c = self._count(self._root, 0)
-        assert c == self.size()
-        return c
-
-    def _count(self, node: TSTNode, counter: int) -> int:
-        """Helper method to `self.count`.
-
-        **Time complexity:** O(m), where m is the number of nodes under `node`."""
-        if node is None:  # base case
-            return counter
-
-        counter = self._count(node.left, counter)
-        if node.value is not None:
-            counter += 1
-
-        counter = self._count(node.mid, counter)
-        counter = self._count(node.right, counter)
-
-        return counter
-
-    def is_empty(self) -> bool:
-        """**Time complexity:** O(1)."""
-        return self._n == 0
-
-    def insert(self, key: str, value: object) -> None:
-        """Inserts the `key` into the symbol table and associates with it `value`,
-        overwriting an eventual associated old value, if the `key` is already in self.
-
-        If `key` is not an instance of `str`, `TypeError` is raised.
-        If `key` is an empty string, `ValueError` is raised.
-        If `value` is None, `ValueError` is raised.
-
-        Nodes whose value is not None represent the last character of an inserted word.
-
-        **Time complexity:** O(m + h), where m = length(key),
-        which also represents how many times we follow the middle link,
-        and h is the number of left and right turns.
-        So a lower bound of the complexity would be &Omega(m);."""
-        self.__invariants__()
-        if not isinstance(key, str):
-            raise TypeError("key must be an instance of type str.")
-        if not key:
-            raise ValueError("key must be a string of length >= 1.")
-        if value is None:
-            raise ValueError("value cannot be None.")
-        self._root = self._insert(self._root, key, value, 0)
-        self.__invariants__()
-
-    def _insert(self, node: TSTNode, key: str, value: object, index: int):
-        """Inserts `key` with `value` into self starting from `node`."""
-        if node is None:
-            node = TSTNode(key[index])
-
-        if key[index] < node.key:
-            node.left = self._insert(node.left, key, value, index)
-            node.left.parent = node
-        elif key[index] > node.key:
-            node.right = self._insert(node.right, key, value, index)
-            node.right.parent = node
-        else:  # key[index] == node.key
-            if index < len(key) - 1:
-                # If we're NOT at the end of the key, this is a match,
-                # so we recursively call self._insert from index + 1,
-                # and we move to the mid node (char) of node.
-                # Note that the last index of the key is len(key) - 1.
-                node.mid = self._insert(node.mid, key, value, index + 1)
-                node.mid.parent = node
-            else:
-                if node.value is None:
-                    self._n += 1
-                node.value = value
-
-        return node
-
-    def search(self, key: str) -> object:
-        """Returns the value associated with `key`, if `key` is in self, else None.
-
-        If `key` is not an instance of `str`, `TypeError` is raised.
-        If `key` is an empty string, `ValueError` is raised.
-
-        The search in a TST works as follows.
-
-        We start at the root and we compare its character with the first character of key.
-            - If they are the same, we follow the middle link of the root node.
-            - If the first character of key is smaller lexicographically
-            than the key at the root, then we take the left link or pointer.
-            We do this because we know that all strings that start with characters
-            that are smaller lexicographically than key[0] are on its left subtree.
-            - If the first character of key is greater lexicographically
-            than the key at the root, we take similarly the right link or pointer.
-
-        We keep applying this idea at every node.
-        Moreover, WHEN THERE'S A MATCH, next time we compare the key
-        of the next node with the next character of key.
-
-        For example, if there's a match between the first node (the root) and key[0],
-        we follow the middle link, and the next comparison is between
-        the key of the specific next node and key[1], not key[0]!
-
-        **Time complexity:** O(m + h).
-        Check self.insert to see what m and h are."""
-        if not isinstance(key, str):
-            raise TypeError("key must be an instance of type str.")
-        if not key:
-            raise ValueError("key must be a string of length >= 1.")
-
-        node = self._search(self._root, key, 0)
-
-        if node is not None:
-            assert self.search_iteratively(key) == node.value
-            return node.value
-        else:
-            assert self.search_iteratively(key) is None
-            return None
-
-    def _search(self, node: TSTNode, key: str, index: int) -> TSTNode:
-        """Searches for the node containing the value associated with `key` starting from `node`.
-        If returns None OR a node with value None if there's no such node."""
-        if node is None:
-            return None
-
-        if key[index] < node.key:
-            return self._search(node.left, key, index)
-        elif key[index] > node.key:
-            return self._search(node.right, key, index)
-        elif index < len(key) - 1:  # This is a match, but we're not at the last character of key.
-            return self._search(node.mid, key, index + 1)
-        else:  # This is a match and we're at the last character of key.
-            return node  # node could be None!!
-
-    def search_iteratively(self, key: str) -> object:
-        """Iterative alternative to self.search."""
-        if not isinstance(key, str):
-            raise TypeError("key must be an instance of type str.")
-        if not key:
-            raise ValueError("key must be a string of length >= 1.")
-
-        node = self._root
-
-        if node is None:
-            return None
-
-        # Up to the penultimate index (i.e. len(key) - 1)
-        # because if we reach the penultimate character and it's a match,
-        # then we follow the mid node (i.e. we end up in what's possibly the last node).
-        index = 0
-
-        while index < len(key) - 1:
-            while node and key[index] != node.key:
-                if key[index] < node.key:
-                    node = node.left
-                else:
-                    node = node.right
-
-            if node is None:  # Unsuccessful search.
-                return None
-            else:
-                # Arriving here only if exited from the while loop
-                # because the condition key[i] != node.key was false,
-                # that is key[index] == node.key, thus we follow the middle link.
-                node = node.mid
-                index += 1
-
-        assert index == len(key) - 1
-
-        # If node is not None, then we may still need to go left or right,
-        # and we stop when either we find a node which has the same key as the last character of key,
-        # or when `node` ends up being set to None, i.e. the key does not exist in this TST.
-        while node and key[index] != node.key:
-            if key[index] < node.key:
-                node = node.left
-            else:
-                node = node.right
-
-        if node is None:  # Unsuccessful search.
-            return None
-        else:  # We exit the previous while loop because key[index] == node.key.
-            return node.value  # could also be None!!
-
-    def contains(self, key: str) -> bool:
-        """Returns True if `key` is in self, False otherwise.
-
-        **Time complexity:** O(m + h).
-        See the complexity analysis of self.insert for more info about m and h."""
-        return self.search(key) is not None
-
-    def delete(self, key: str) -> TSTNode:
-        """Deletes and returns the value associated with `key` in this TST,
-        if `key` is in this TST, otherwise it returns None.
-
-        If `key` is not an instance of `str`, `TypeError` is raised.
-        If `key` is an empty string, `ValueError` is raised.
-
-        **Time complexity:** O(m + h + k).
-        Check self.search to see what m and h are.
-        k is the number of "no more necessary" cleaned up
-        after deletion of the node associated with `key`.
-        Unnecessary nodes are nodes with no children and value equal to None."""
-        self.__invariants__()
-
-        if not isinstance(key, str):
-            raise TypeError("key must be an instance of type str.")
-        if not key:
-            raise ValueError("key must be a string of length >= 1.")
-
-        # Note: calling self._search, since self.search does not return a Node,
-        # but the value associated with the key passed as parameter.
-        node = self._search(self._root, key, 0)
-
-        if node is not None and node.value is not None:
-            # If node.value is None, it means
-            result = node.value  # forgetting the string tracked by node.
-            node.value = None
-            self._n -= 1
-            self._delete_fix(node)
-        else:
-            result = None
-
-        self.__invariants__()
-        return result
-
-    def _delete_fix(self, u: TSTNode) -> None:
-        """Does the clean up of this TST after deletion of node `u`."""
-        assert u.value is None
-
-        # While u has no children and his value is None,
-        # forget about u and start from his parent.
-        # So, this while loop terminates when either u is None,
-        # u has at least one child, or u's value is not None.
-        while u and not u.has_children() and u.value is None:
-            if self._is_root(u):
-                assert self._n == 0
-                self._root = None
-                break
-
-            if u.is_left_child():
-                u.parent.left = None
-            elif u.is_right_child():
-                u.parent.right = None
-            else:
-                u.parent.mid = None
-
-            p = u.parent
-            u.parent = None
-            u = p
-
-        if u.has_children() and u.value is None:
-            assert self._count(u, 0) > 0
-
-    def traverse(self) -> None:
-        """Traverses all nodes in this TST and prints the key: value associations.
-
-        **Time complexity:** O(n), where n is the number of nodes in self."""
-        self._traverse(self._root, "")
-
-    def _traverse(self, node: TSTNode, prefix: str) -> None:
-        """Helper method to self.traverse.
-
-        **Time complexity:** O(m), where m is the number of nodes under `node`."""
-        if node is None:  # base case
-            return
-
-        self._traverse(node.left, prefix)
-        if node.value is not None:
-            print(prefix + node.key, ": ", node.value)
-
-        self._traverse(node.mid, prefix + node.key)
-        self._traverse(node.right, prefix)
-
-    def keys_with_prefix(self, prefix: str) -> list:
-        """Returns all keys in this TST that start with `prefix`.
-
-        If `prefix` is not an instance of `str`, `TypeError` is raised.
-
-        If `prefix` is an empty string, then all keys in this TST
-        that start with an empty string, thus all keys are returned."""
-        if not isinstance(prefix, str):
-            raise TypeError("prefix must be an instance of str!")
-
-        kwp = []
-
-        if not prefix:
-            self._keys_with_prefix(self._root, [], kwp)
-        else:
-            node = self._search(self._root, prefix, 0)
-
-            if node is not None:
-                if node.value is not None:
-                    # A `key` equals to prefix was found in the TST with an associated value.
-                    kwp.append(prefix)
-
-                self._keys_with_prefix(node.mid, list(prefix), kwp)
-
-        return kwp
-
-    def _keys_with_prefix(self, node: TSTNode, prefix_list: list, kwp: list) -> None:
-        """Returns all keys rooted at `node` given the prefix given as a list of characters `prefix_list`."""
-        if node is None:
-            return
-
-        self._keys_with_prefix(node.left, prefix_list, kwp)
-
-        if node.value is not None:
-            kwp.append("".join(prefix_list + [node.key]))
-
-        prefix_list.append(node.key)
-        self._keys_with_prefix(node.mid, prefix_list, kwp)
-
-        prefix_list.pop()
-        self._keys_with_prefix(node.right, prefix_list, kwp)
-
-    def all_pairs(self) -> dict:
-        """Returns all pairs of key:value from this TST as a Python `dict`."""
-        pairs = {}
-        self._all_pairs(self._root, [], pairs)
-        return pairs
-
-    def _all_pairs(self, node: TSTNode, key_list: list, all_dict: list) -> None:
-        if node is None:
-            return
-
-        self._all_pairs(node.left, key_list, all_dict)
-
-        if node.value is not None:
-            key = "".join(key_list + [node.key])
-            assert key not in all_dict
-            all_dict[key] = node.value
-
-        key_list.append(node.key)
-        self._all_pairs(node.mid, key_list, all_dict)
-
-        key_list.pop()
-        self._all_pairs(node.right, key_list, all_dict)
-
-    def longest_prefix_of(self, query: str) -> str:
-        """Returns the key in this TST which is the longest prefix of `query`,
-        if such a key exists, else it returns None.
-
-        If `query` is not a string `TypeError` is raised.
-        If `query` is a string but empty, `ValueError` is raised.
-
-        If this TST is empty, it returns an empty string."""
-        if not isinstance(query, str):
-            raise TypeError("query is not an instance of str!")
-        if not query:
-            raise ValueError("empty strings not allowed in this TST!")
-
-        length = 0  # It keeps track of the length of the longest prefix of query.
-        x = self._root
-        i = 0
-
-        while x is not None and i < len(query):
-            c = query[i]
-
-            if c < x.key:
-                x = x.left
-            elif c > x.key:
-                x = x.right
-            else:
-                i += 1
-                if x.value is not None:
-                    length = i
-                x = x.mid
-
-        return query[:length]
-
-    def keys_that_match(self, pattern: str) -> list:
-        """Returns a list of keys of this TST that match `pattern`.
-
-        A key `k` of length `m` matches `pattern` if:
-
-        1. m = length(pattern), and
-        2. Either k[i] == pattern[i] or k[i] == '.'.
-            - Example: if `pattern == ".ood"`,
-            then `k == "good"` would match, but not `k == "foodie"`.
-
-        If `pattern` is not a `str`, `TypeError` is raised.
-        If `pattern` is an empty string, `ValueError` is raised."""
-        if not isinstance(pattern, str):
-            raise TypeError("pattern is not an instance of str!")
-        if not pattern:
-            raise ValueError("pattern cannot be an empty string")
-
-        keys = []
-        self._keys_that_match(self._root, [], 0, pattern, keys)
-        return keys
-
-    def _keys_that_match(self, node: TSTNode, prefix_list: list, i: int, pattern: str, keys: list) -> None:
-        """Stores in the list `keys` the of keys that match `pattern` starting from `node`."""
-        if node is None:
-            return
-
-        c = pattern[i]
-
-        if c == "." or c < node.key:
-            self._keys_that_match(node.left, prefix_list, i, pattern, keys)
-
-        if c == "." or c == node.key:
-
-            if i == len(pattern) - 1 and node.value is not None:
-                # If i is the last index and its value is not None
-                keys.append("".join(prefix_list + [node.key]))
-
-            if i < len(pattern) - 1:
-                prefix_list.append(node.key)
-                self._keys_that_match(node.mid, prefix_list, i + 1, pattern, keys)
-                prefix_list.pop()
-
-        if c == "." or c > node.key:
-            self._keys_that_match(node.right, prefix_list, i, pattern, keys)
-
-
- -
- -
- - -

Classes

- -
-

class TST

- - -

Methods or fields that start with an underscore _ are considered private, -so they should not be access and never modified from a client of this class.

-

This TST does not allow (through public methods) empty strings to be inserted.

-

In general the way the ternary search tree looks like -depends highly on the order of insertion of the keys, -that is, inserting the same keys but in different orders -produces internally a different structure or shape of the TST.

-
- -
-
class TST:
-    """Methods or fields that start with an underscore _ are considered private,
-    so they should not be access and never modified from a client of this class.
-
-    This TST does not allow (through public methods) empty strings to be inserted.
-
-    In general the way the ternary search tree looks like
-    depends highly on the order of insertion of the keys,
-    that is, inserting the same keys but in different orders
-    produces internally a different structure or shape of the TST."""
-
-    def __init__(self):
-        self._n = 0
-        self._root = None
-
-    def __invariants__(self) -> None:
-        """These propositions should always be true at the BEGINNING
-        and END of every PUBLIC method of this TST.
-
-        Call this method if you want to ensure the invariants are holding."""
-        assert self._n >= 0
-        if self._n == 0:
-            assert self._root is None
-        elif self._n > 0:
-            assert isinstance(self._root, TSTNode)
-            assert self._root.parent is None
-
-    def _is_root(self, u: TSTNode) -> bool:
-        result = (self._root == u)
-        if result:
-            assert u.parent is None
-        else:
-            assert u.parent is not None
-        return result
-
-    def size(self) -> int:
-        return self._n
-
-    def count(self) -> int:
-        """Counts the number of strings in self.
-
-        This method recursively passes through all the nodes
-        and counts the ones which have a non None value.
-
-        You should clearly use size instead: 
-        this method is here only for the fun of writing code!
-
-        **Time complexity:** O(n), where n is the number of nodes in this TST."""
-        c = self._count(self._root, 0)
-        assert c == self.size()
-        return c
-
-    def _count(self, node: TSTNode, counter: int) -> int:
-        """Helper method to `self.count`.
-
-        **Time complexity:** O(m), where m is the number of nodes under `node`."""
-        if node is None:  # base case
-            return counter
-
-        counter = self._count(node.left, counter)
-        if node.value is not None:
-            counter += 1
-
-        counter = self._count(node.mid, counter)
-        counter = self._count(node.right, counter)
-
-        return counter
-
-    def is_empty(self) -> bool:
-        """**Time complexity:** O(1)."""
-        return self._n == 0
-
-    def insert(self, key: str, value: object) -> None:
-        """Inserts the `key` into the symbol table and associates with it `value`,
-        overwriting an eventual associated old value, if the `key` is already in self.
-
-        If `key` is not an instance of `str`, `TypeError` is raised.
-        If `key` is an empty string, `ValueError` is raised.
-        If `value` is None, `ValueError` is raised.
-
-        Nodes whose value is not None represent the last character of an inserted word.
-
-        **Time complexity:** O(m + h), where m = length(key),
-        which also represents how many times we follow the middle link,
-        and h is the number of left and right turns.
-        So a lower bound of the complexity would be &Omega(m);."""
-        self.__invariants__()
-        if not isinstance(key, str):
-            raise TypeError("key must be an instance of type str.")
-        if not key:
-            raise ValueError("key must be a string of length >= 1.")
-        if value is None:
-            raise ValueError("value cannot be None.")
-        self._root = self._insert(self._root, key, value, 0)
-        self.__invariants__()
-
-    def _insert(self, node: TSTNode, key: str, value: object, index: int):
-        """Inserts `key` with `value` into self starting from `node`."""
-        if node is None:
-            node = TSTNode(key[index])
-
-        if key[index] < node.key:
-            node.left = self._insert(node.left, key, value, index)
-            node.left.parent = node
-        elif key[index] > node.key:
-            node.right = self._insert(node.right, key, value, index)
-            node.right.parent = node
-        else:  # key[index] == node.key
-            if index < len(key) - 1:
-                # If we're NOT at the end of the key, this is a match,
-                # so we recursively call self._insert from index + 1,
-                # and we move to the mid node (char) of node.
-                # Note that the last index of the key is len(key) - 1.
-                node.mid = self._insert(node.mid, key, value, index + 1)
-                node.mid.parent = node
-            else:
-                if node.value is None:
-                    self._n += 1
-                node.value = value
-
-        return node
-
-    def search(self, key: str) -> object:
-        """Returns the value associated with `key`, if `key` is in self, else None.
-
-        If `key` is not an instance of `str`, `TypeError` is raised.
-        If `key` is an empty string, `ValueError` is raised.
-
-        The search in a TST works as follows.
-
-        We start at the root and we compare its character with the first character of key.
-            - If they are the same, we follow the middle link of the root node.
-            - If the first character of key is smaller lexicographically
-            than the key at the root, then we take the left link or pointer.
-            We do this because we know that all strings that start with characters
-            that are smaller lexicographically than key[0] are on its left subtree.
-            - If the first character of key is greater lexicographically
-            than the key at the root, we take similarly the right link or pointer.
-
-        We keep applying this idea at every node.
-        Moreover, WHEN THERE'S A MATCH, next time we compare the key
-        of the next node with the next character of key.
-
-        For example, if there's a match between the first node (the root) and key[0],
-        we follow the middle link, and the next comparison is between
-        the key of the specific next node and key[1], not key[0]!
-
-        **Time complexity:** O(m + h).
-        Check self.insert to see what m and h are."""
-        if not isinstance(key, str):
-            raise TypeError("key must be an instance of type str.")
-        if not key:
-            raise ValueError("key must be a string of length >= 1.")
-
-        node = self._search(self._root, key, 0)
-
-        if node is not None:
-            assert self.search_iteratively(key) == node.value
-            return node.value
-        else:
-            assert self.search_iteratively(key) is None
-            return None
-
-    def _search(self, node: TSTNode, key: str, index: int) -> TSTNode:
-        """Searches for the node containing the value associated with `key` starting from `node`.
-        If returns None OR a node with value None if there's no such node."""
-        if node is None:
-            return None
-
-        if key[index] < node.key:
-            return self._search(node.left, key, index)
-        elif key[index] > node.key:
-            return self._search(node.right, key, index)
-        elif index < len(key) - 1:  # This is a match, but we're not at the last character of key.
-            return self._search(node.mid, key, index + 1)
-        else:  # This is a match and we're at the last character of key.
-            return node  # node could be None!!
-
-    def search_iteratively(self, key: str) -> object:
-        """Iterative alternative to self.search."""
-        if not isinstance(key, str):
-            raise TypeError("key must be an instance of type str.")
-        if not key:
-            raise ValueError("key must be a string of length >= 1.")
-
-        node = self._root
-
-        if node is None:
-            return None
-
-        # Up to the penultimate index (i.e. len(key) - 1)
-        # because if we reach the penultimate character and it's a match,
-        # then we follow the mid node (i.e. we end up in what's possibly the last node).
-        index = 0
-
-        while index < len(key) - 1:
-            while node and key[index] != node.key:
-                if key[index] < node.key:
-                    node = node.left
-                else:
-                    node = node.right
-
-            if node is None:  # Unsuccessful search.
-                return None
-            else:
-                # Arriving here only if exited from the while loop
-                # because the condition key[i] != node.key was false,
-                # that is key[index] == node.key, thus we follow the middle link.
-                node = node.mid
-                index += 1
-
-        assert index == len(key) - 1
-
-        # If node is not None, then we may still need to go left or right,
-        # and we stop when either we find a node which has the same key as the last character of key,
-        # or when `node` ends up being set to None, i.e. the key does not exist in this TST.
-        while node and key[index] != node.key:
-            if key[index] < node.key:
-                node = node.left
-            else:
-                node = node.right
-
-        if node is None:  # Unsuccessful search.
-            return None
-        else:  # We exit the previous while loop because key[index] == node.key.
-            return node.value  # could also be None!!
-
-    def contains(self, key: str) -> bool:
-        """Returns True if `key` is in self, False otherwise.
-
-        **Time complexity:** O(m + h).
-        See the complexity analysis of self.insert for more info about m and h."""
-        return self.search(key) is not None
-
-    def delete(self, key: str) -> TSTNode:
-        """Deletes and returns the value associated with `key` in this TST,
-        if `key` is in this TST, otherwise it returns None.
-
-        If `key` is not an instance of `str`, `TypeError` is raised.
-        If `key` is an empty string, `ValueError` is raised.
-
-        **Time complexity:** O(m + h + k).
-        Check self.search to see what m and h are.
-        k is the number of "no more necessary" cleaned up
-        after deletion of the node associated with `key`.
-        Unnecessary nodes are nodes with no children and value equal to None."""
-        self.__invariants__()
-
-        if not isinstance(key, str):
-            raise TypeError("key must be an instance of type str.")
-        if not key:
-            raise ValueError("key must be a string of length >= 1.")
-
-        # Note: calling self._search, since self.search does not return a Node,
-        # but the value associated with the key passed as parameter.
-        node = self._search(self._root, key, 0)
-
-        if node is not None and node.value is not None:
-            # If node.value is None, it means
-            result = node.value  # forgetting the string tracked by node.
-            node.value = None
-            self._n -= 1
-            self._delete_fix(node)
-        else:
-            result = None
-
-        self.__invariants__()
-        return result
-
-    def _delete_fix(self, u: TSTNode) -> None:
-        """Does the clean up of this TST after deletion of node `u`."""
-        assert u.value is None
-
-        # While u has no children and his value is None,
-        # forget about u and start from his parent.
-        # So, this while loop terminates when either u is None,
-        # u has at least one child, or u's value is not None.
-        while u and not u.has_children() and u.value is None:
-            if self._is_root(u):
-                assert self._n == 0
-                self._root = None
-                break
-
-            if u.is_left_child():
-                u.parent.left = None
-            elif u.is_right_child():
-                u.parent.right = None
-            else:
-                u.parent.mid = None
-
-            p = u.parent
-            u.parent = None
-            u = p
-
-        if u.has_children() and u.value is None:
-            assert self._count(u, 0) > 0
-
-    def traverse(self) -> None:
-        """Traverses all nodes in this TST and prints the key: value associations.
-
-        **Time complexity:** O(n), where n is the number of nodes in self."""
-        self._traverse(self._root, "")
-
-    def _traverse(self, node: TSTNode, prefix: str) -> None:
-        """Helper method to self.traverse.
-
-        **Time complexity:** O(m), where m is the number of nodes under `node`."""
-        if node is None:  # base case
-            return
-
-        self._traverse(node.left, prefix)
-        if node.value is not None:
-            print(prefix + node.key, ": ", node.value)
-
-        self._traverse(node.mid, prefix + node.key)
-        self._traverse(node.right, prefix)
-
-    def keys_with_prefix(self, prefix: str) -> list:
-        """Returns all keys in this TST that start with `prefix`.
-
-        If `prefix` is not an instance of `str`, `TypeError` is raised.
-
-        If `prefix` is an empty string, then all keys in this TST
-        that start with an empty string, thus all keys are returned."""
-        if not isinstance(prefix, str):
-            raise TypeError("prefix must be an instance of str!")
-
-        kwp = []
-
-        if not prefix:
-            self._keys_with_prefix(self._root, [], kwp)
-        else:
-            node = self._search(self._root, prefix, 0)
-
-            if node is not None:
-                if node.value is not None:
-                    # A `key` equals to prefix was found in the TST with an associated value.
-                    kwp.append(prefix)
-
-                self._keys_with_prefix(node.mid, list(prefix), kwp)
-
-        return kwp
-
-    def _keys_with_prefix(self, node: TSTNode, prefix_list: list, kwp: list) -> None:
-        """Returns all keys rooted at `node` given the prefix given as a list of characters `prefix_list`."""
-        if node is None:
-            return
-
-        self._keys_with_prefix(node.left, prefix_list, kwp)
-
-        if node.value is not None:
-            kwp.append("".join(prefix_list + [node.key]))
-
-        prefix_list.append(node.key)
-        self._keys_with_prefix(node.mid, prefix_list, kwp)
-
-        prefix_list.pop()
-        self._keys_with_prefix(node.right, prefix_list, kwp)
-
-    def all_pairs(self) -> dict:
-        """Returns all pairs of key:value from this TST as a Python `dict`."""
-        pairs = {}
-        self._all_pairs(self._root, [], pairs)
-        return pairs
-
-    def _all_pairs(self, node: TSTNode, key_list: list, all_dict: list) -> None:
-        if node is None:
-            return
-
-        self._all_pairs(node.left, key_list, all_dict)
-
-        if node.value is not None:
-            key = "".join(key_list + [node.key])
-            assert key not in all_dict
-            all_dict[key] = node.value
-
-        key_list.append(node.key)
-        self._all_pairs(node.mid, key_list, all_dict)
-
-        key_list.pop()
-        self._all_pairs(node.right, key_list, all_dict)
-
-    def longest_prefix_of(self, query: str) -> str:
-        """Returns the key in this TST which is the longest prefix of `query`,
-        if such a key exists, else it returns None.
-
-        If `query` is not a string `TypeError` is raised.
-        If `query` is a string but empty, `ValueError` is raised.
-
-        If this TST is empty, it returns an empty string."""
-        if not isinstance(query, str):
-            raise TypeError("query is not an instance of str!")
-        if not query:
-            raise ValueError("empty strings not allowed in this TST!")
-
-        length = 0  # It keeps track of the length of the longest prefix of query.
-        x = self._root
-        i = 0
-
-        while x is not None and i < len(query):
-            c = query[i]
-
-            if c < x.key:
-                x = x.left
-            elif c > x.key:
-                x = x.right
-            else:
-                i += 1
-                if x.value is not None:
-                    length = i
-                x = x.mid
-
-        return query[:length]
-
-    def keys_that_match(self, pattern: str) -> list:
-        """Returns a list of keys of this TST that match `pattern`.
-
-        A key `k` of length `m` matches `pattern` if:
-
-        1. m = length(pattern), and
-        2. Either k[i] == pattern[i] or k[i] == '.'.
-            - Example: if `pattern == ".ood"`,
-            then `k == "good"` would match, but not `k == "foodie"`.
-
-        If `pattern` is not a `str`, `TypeError` is raised.
-        If `pattern` is an empty string, `ValueError` is raised."""
-        if not isinstance(pattern, str):
-            raise TypeError("pattern is not an instance of str!")
-        if not pattern:
-            raise ValueError("pattern cannot be an empty string")
-
-        keys = []
-        self._keys_that_match(self._root, [], 0, pattern, keys)
-        return keys
-
-    def _keys_that_match(self, node: TSTNode, prefix_list: list, i: int, pattern: str, keys: list) -> None:
-        """Stores in the list `keys` the of keys that match `pattern` starting from `node`."""
-        if node is None:
-            return
-
-        c = pattern[i]
-
-        if c == "." or c < node.key:
-            self._keys_that_match(node.left, prefix_list, i, pattern, keys)
-
-        if c == "." or c == node.key:
-
-            if i == len(pattern) - 1 and node.value is not None:
-                # If i is the last index and its value is not None
-                keys.append("".join(prefix_list + [node.key]))
-
-            if i < len(pattern) - 1:
-                prefix_list.append(node.key)
-                self._keys_that_match(node.mid, prefix_list, i + 1, pattern, keys)
-                prefix_list.pop()
-
-        if c == "." or c > node.key:
-            self._keys_that_match(node.right, prefix_list, i, pattern, keys)
-
-
-
- - -
-

Ancestors (in MRO)

-
    -
  • TST
  • -
  • builtins.object
  • -
-

Static methods

- -
-
-

def __init__(

self)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self):
-    self._n = 0
-    self._root = None
-
-
-
- -
- - -
-
-

def all_pairs(

self)

-
- - - - -

Returns all pairs of key:value from this TST as a Python dict.

-
- -
-
def all_pairs(self) -> dict:
-    """Returns all pairs of key:value from this TST as a Python `dict`."""
-    pairs = {}
-    self._all_pairs(self._root, [], pairs)
-    return pairs
-
-
-
- -
- - -
-
-

def contains(

self, key)

-
- - - - -

Returns True if key is in self, False otherwise.

-

Time complexity: O(m + h). -See the complexity analysis of self.insert for more info about m and h.

-
- -
-
def contains(self, key: str) -> bool:
-    """Returns True if `key` is in self, False otherwise.
-    **Time complexity:** O(m + h).
-    See the complexity analysis of self.insert for more info about m and h."""
-    return self.search(key) is not None
-
-
-
- -
- - -
-
-

def count(

self)

-
- - - - -

Counts the number of strings in self.

-

This method recursively passes through all the nodes -and counts the ones which have a non None value.

-

You should clearly use size instead: -this method is here only for the fun of writing code!

-

Time complexity: O(n), where n is the number of nodes in this TST.

-
- -
-
def count(self) -> int:
-    """Counts the number of strings in self.
-    This method recursively passes through all the nodes
-    and counts the ones which have a non None value.
-    You should clearly use size instead: 
-    this method is here only for the fun of writing code!
-    **Time complexity:** O(n), where n is the number of nodes in this TST."""
-    c = self._count(self._root, 0)
-    assert c == self.size()
-    return c
-
-
-
- -
- - -
-
-

def delete(

self, key)

-
- - - - -

Deletes and returns the value associated with key in this TST, -if key is in this TST, otherwise it returns None.

-

If key is not an instance of str, TypeError is raised. -If key is an empty string, ValueError is raised.

-

Time complexity: O(m + h + k). -Check self.search to see what m and h are. -k is the number of "no more necessary" cleaned up -after deletion of the node associated with key. -Unnecessary nodes are nodes with no children and value equal to None.

-
- -
-
def delete(self, key: str) -> TSTNode:
-    """Deletes and returns the value associated with `key` in this TST,
-    if `key` is in this TST, otherwise it returns None.
-    If `key` is not an instance of `str`, `TypeError` is raised.
-    If `key` is an empty string, `ValueError` is raised.
-    **Time complexity:** O(m + h + k).
-    Check self.search to see what m and h are.
-    k is the number of "no more necessary" cleaned up
-    after deletion of the node associated with `key`.
-    Unnecessary nodes are nodes with no children and value equal to None."""
-    self.__invariants__()
-    if not isinstance(key, str):
-        raise TypeError("key must be an instance of type str.")
-    if not key:
-        raise ValueError("key must be a string of length >= 1.")
-    # Note: calling self._search, since self.search does not return a Node,
-    # but the value associated with the key passed as parameter.
-    node = self._search(self._root, key, 0)
-    if node is not None and node.value is not None:
-        # If node.value is None, it means
-        result = node.value  # forgetting the string tracked by node.
-        node.value = None
-        self._n -= 1
-        self._delete_fix(node)
-    else:
-        result = None
-    self.__invariants__()
-    return result
-
-
-
- -
- - -
-
-

def insert(

self, key, value)

-
- - - - -

Inserts the key into the symbol table and associates with it value, -overwriting an eventual associated old value, if the key is already in self.

-

If key is not an instance of str, TypeError is raised. -If key is an empty string, ValueError is raised. -If value is None, ValueError is raised.

-

Nodes whose value is not None represent the last character of an inserted word.

-

Time complexity: O(m + h), where m = length(key), -which also represents how many times we follow the middle link, -and h is the number of left and right turns. -So a lower bound of the complexity would be &Omega(m);.

-
- -
-
def insert(self, key: str, value: object) -> None:
-    """Inserts the `key` into the symbol table and associates with it `value`,
-    overwriting an eventual associated old value, if the `key` is already in self.
-    If `key` is not an instance of `str`, `TypeError` is raised.
-    If `key` is an empty string, `ValueError` is raised.
-    If `value` is None, `ValueError` is raised.
-    Nodes whose value is not None represent the last character of an inserted word.
-    **Time complexity:** O(m + h), where m = length(key),
-    which also represents how many times we follow the middle link,
-    and h is the number of left and right turns.
-    So a lower bound of the complexity would be &Omega(m);."""
-    self.__invariants__()
-    if not isinstance(key, str):
-        raise TypeError("key must be an instance of type str.")
-    if not key:
-        raise ValueError("key must be a string of length >= 1.")
-    if value is None:
-        raise ValueError("value cannot be None.")
-    self._root = self._insert(self._root, key, value, 0)
-    self.__invariants__()
-
-
-
- -
- - -
-
-

def is_empty(

self)

-
- - - - -

Time complexity: O(1).

-
- -
-
def is_empty(self) -> bool:
-    """**Time complexity:** O(1)."""
-    return self._n == 0
-
-
-
- -
- - -
-
-

def keys_that_match(

self, pattern)

-
- - - - -

Returns a list of keys of this TST that match pattern.

-

A key k of length m matches pattern if:

-
    -
  1. m = length(pattern), and
  2. -
  3. Either k[i] == pattern[i] or k[i] == '.'.
      -
    • Example: if pattern == ".ood", -then k == "good" would match, but not k == "foodie".
    • -
    -
  4. -
-

If pattern is not a str, TypeError is raised. -If pattern is an empty string, ValueError is raised.

-
- -
-
def keys_that_match(self, pattern: str) -> list:
-    """Returns a list of keys of this TST that match `pattern`.
-    A key `k` of length `m` matches `pattern` if:
-    1. m = length(pattern), and
-    2. Either k[i] == pattern[i] or k[i] == '.'.
-        - Example: if `pattern == ".ood"`,
-        then `k == "good"` would match, but not `k == "foodie"`.
-    If `pattern` is not a `str`, `TypeError` is raised.
-    If `pattern` is an empty string, `ValueError` is raised."""
-    if not isinstance(pattern, str):
-        raise TypeError("pattern is not an instance of str!")
-    if not pattern:
-        raise ValueError("pattern cannot be an empty string")
-    keys = []
-    self._keys_that_match(self._root, [], 0, pattern, keys)
-    return keys
-
-
-
- -
- - -
-
-

def keys_with_prefix(

self, prefix)

-
- - - - -

Returns all keys in this TST that start with prefix.

-

If prefix is not an instance of str, TypeError is raised.

-

If prefix is an empty string, then all keys in this TST -that start with an empty string, thus all keys are returned.

-
- -
-
def keys_with_prefix(self, prefix: str) -> list:
-    """Returns all keys in this TST that start with `prefix`.
-    If `prefix` is not an instance of `str`, `TypeError` is raised.
-    If `prefix` is an empty string, then all keys in this TST
-    that start with an empty string, thus all keys are returned."""
-    if not isinstance(prefix, str):
-        raise TypeError("prefix must be an instance of str!")
-    kwp = []
-    if not prefix:
-        self._keys_with_prefix(self._root, [], kwp)
-    else:
-        node = self._search(self._root, prefix, 0)
-        if node is not None:
-            if node.value is not None:
-                # A `key` equals to prefix was found in the TST with an associated value.
-                kwp.append(prefix)
-            self._keys_with_prefix(node.mid, list(prefix), kwp)
-    return kwp
-
-
-
- -
- - -
-
-

def longest_prefix_of(

self, query)

-
- - - - -

Returns the key in this TST which is the longest prefix of query, -if such a key exists, else it returns None.

-

If query is not a string TypeError is raised. -If query is a string but empty, ValueError is raised.

-

If this TST is empty, it returns an empty string.

-
- -
-
def longest_prefix_of(self, query: str) -> str:
-    """Returns the key in this TST which is the longest prefix of `query`,
-    if such a key exists, else it returns None.
-    If `query` is not a string `TypeError` is raised.
-    If `query` is a string but empty, `ValueError` is raised.
-    If this TST is empty, it returns an empty string."""
-    if not isinstance(query, str):
-        raise TypeError("query is not an instance of str!")
-    if not query:
-        raise ValueError("empty strings not allowed in this TST!")
-    length = 0  # It keeps track of the length of the longest prefix of query.
-    x = self._root
-    i = 0
-    while x is not None and i < len(query):
-        c = query[i]
-        if c < x.key:
-            x = x.left
-        elif c > x.key:
-            x = x.right
-        else:
-            i += 1
-            if x.value is not None:
-                length = i
-            x = x.mid
-    return query[:length]
-
-
-
- -
- - -
-
-

def search(

self, key)

-
- - - - -

Returns the value associated with key, if key is in self, else None.

-

If key is not an instance of str, TypeError is raised. -If key is an empty string, ValueError is raised.

-

The search in a TST works as follows.

-

We start at the root and we compare its character with the first character of key. - - If they are the same, we follow the middle link of the root node. - - If the first character of key is smaller lexicographically - than the key at the root, then we take the left link or pointer. - We do this because we know that all strings that start with characters - that are smaller lexicographically than key[0] are on its left subtree. - - If the first character of key is greater lexicographically - than the key at the root, we take similarly the right link or pointer.

-

We keep applying this idea at every node. -Moreover, WHEN THERE'S A MATCH, next time we compare the key -of the next node with the next character of key.

-

For example, if there's a match between the first node (the root) and key[0], -we follow the middle link, and the next comparison is between -the key of the specific next node and key[1], not key[0]!

-

Time complexity: O(m + h). -Check self.insert to see what m and h are.

-
- -
-
def search(self, key: str) -> object:
-    """Returns the value associated with `key`, if `key` is in self, else None.
-    If `key` is not an instance of `str`, `TypeError` is raised.
-    If `key` is an empty string, `ValueError` is raised.
-    The search in a TST works as follows.
-    We start at the root and we compare its character with the first character of key.
-        - If they are the same, we follow the middle link of the root node.
-        - If the first character of key is smaller lexicographically
-        than the key at the root, then we take the left link or pointer.
-        We do this because we know that all strings that start with characters
-        that are smaller lexicographically than key[0] are on its left subtree.
-        - If the first character of key is greater lexicographically
-        than the key at the root, we take similarly the right link or pointer.
-    We keep applying this idea at every node.
-    Moreover, WHEN THERE'S A MATCH, next time we compare the key
-    of the next node with the next character of key.
-    For example, if there's a match between the first node (the root) and key[0],
-    we follow the middle link, and the next comparison is between
-    the key of the specific next node and key[1], not key[0]!
-    **Time complexity:** O(m + h).
-    Check self.insert to see what m and h are."""
-    if not isinstance(key, str):
-        raise TypeError("key must be an instance of type str.")
-    if not key:
-        raise ValueError("key must be a string of length >= 1.")
-    node = self._search(self._root, key, 0)
-    if node is not None:
-        assert self.search_iteratively(key) == node.value
-        return node.value
-    else:
-        assert self.search_iteratively(key) is None
-        return None
-
-
-
- -
- - -
-
-

def search_iteratively(

self, key)

-
- - - - -

Iterative alternative to self.search.

-
- -
-
def search_iteratively(self, key: str) -> object:
-    """Iterative alternative to self.search."""
-    if not isinstance(key, str):
-        raise TypeError("key must be an instance of type str.")
-    if not key:
-        raise ValueError("key must be a string of length >= 1.")
-    node = self._root
-    if node is None:
-        return None
-    # Up to the penultimate index (i.e. len(key) - 1)
-    # because if we reach the penultimate character and it's a match,
-    # then we follow the mid node (i.e. we end up in what's possibly the last node).
-    index = 0
-    while index < len(key) - 1:
-        while node and key[index] != node.key:
-            if key[index] < node.key:
-                node = node.left
-            else:
-                node = node.right
-        if node is None:  # Unsuccessful search.
-            return None
-        else:
-            # Arriving here only if exited from the while loop
-            # because the condition key[i] != node.key was false,
-            # that is key[index] == node.key, thus we follow the middle link.
-            node = node.mid
-            index += 1
-    assert index == len(key) - 1
-    # If node is not None, then we may still need to go left or right,
-    # and we stop when either we find a node which has the same key as the last character of key,
-    # or when `node` ends up being set to None, i.e. the key does not exist in this TST.
-    while node and key[index] != node.key:
-        if key[index] < node.key:
-            node = node.left
-        else:
-            node = node.right
-    if node is None:  # Unsuccessful search.
-        return None
-    else:  # We exit the previous while loop because key[index] == node.key.
-        return node.value  # could also be None!!
-
-
-
- -
- - -
-
-

def size(

self)

-
- - - - -
- -
-
def size(self) -> int:
-    return self._n
-
-
-
- -
- - -
-
-

def traverse(

self)

-
- - - - -

Traverses all nodes in this TST and prints the key: value associations.

-

Time complexity: O(n), where n is the number of nodes in self.

-
- -
-
def traverse(self) -> None:
-    """Traverses all nodes in this TST and prints the key: value associations.
-    **Time complexity:** O(n), where n is the number of nodes in self."""
-    self._traverse(self._root, "")
-
-
-
- -
- -
-
- -
-

class TSTNode

- - -

A TSTNode has 6 fields:

-
    -
  • key, which is a character;
  • -
  • value, which is None if self is not a terminal node (of an inserted string in the TST);
  • -
  • parent, which is a pointer to a TSTNode representing the parent of self;
  • -
  • left, which is a pointer to a TSTNode whose key is smaller lexicographically than key;
  • -
  • right, which is similarly a pointer to a TSTNode whose key is greater lexicographically than key;
  • -
  • mid, which is a pointer to a TSTNode whose key is the following character of key in an inserted string.
  • -
-
- -
-
class TSTNode:
-    """A TSTNode has 6 fields:
-
-        - key, which is a character;
-        - value, which is None if self is not a terminal node (of an inserted string in the TST);
-        - parent, which is a pointer to a TSTNode representing the parent of self;
-        - left, which is a pointer to a TSTNode whose key is smaller lexicographically than key;
-        - right, which is similarly a pointer to a TSTNode whose key is greater lexicographically than key;
-        - mid, which is a pointer to a TSTNode whose key is the following character of key in an inserted string."""
-
-    def __init__(self, key, value=None, parent=None, left=None, mid=None, right=None):
-        if not isinstance(key, str):
-            raise TypeError("key must be an instance of str.")
-        if not key:
-            raise ValueError("key must be a string of length >= 1.")
-
-        self.key = key
-        self.value = value
-        self.parent = parent
-        self.left = left
-        self.mid = mid
-        self.right = right
-
-    def is_left_child(self) -> bool:
-        if not self.parent:
-            raise AttributeError("self does not have a parent.")
-        if self.parent.left:
-            return self.parent.left == self
-        else:
-            return False
-
-    def is_right_child(self) -> bool:
-        if not self.parent:
-            raise AttributeError("self does not have a parent.")
-        if self.parent.right:
-            return self.parent.right == self
-        else:
-            return False
-
-    def is_mid_child(self) -> bool:
-        if not self.parent:
-            raise AttributeError("self does not have a parent.")
-        if self.parent.mid:
-            return self.parent.mid == self
-        else:
-            return False
-
-    def has_children(self) -> bool:
-        return self.left or self.right or self.mid
-
-    def __str__(self):
-        return "{0}: {1}".format(self.key, self.value)
-
-    def __repr__(self):
-        return self.__str__()
-
-
-
- - -
-

Ancestors (in MRO)

- -

Static methods

- -
-
-

def __init__(

self, key, value=None, parent=None, left=None, mid=None, right=None)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, key, value=None, parent=None, left=None, mid=None, right=None):
-    if not isinstance(key, str):
-        raise TypeError("key must be an instance of str.")
-    if not key:
-        raise ValueError("key must be a string of length >= 1.")
-    self.key = key
-    self.value = value
-    self.parent = parent
-    self.left = left
-    self.mid = mid
-    self.right = right
-
-
-
- -
- - -
-
-

def has_children(

self)

-
- - - - -
- -
-
def has_children(self) -> bool:
-    return self.left or self.right or self.mid
-
-
-
- -
- - -
-
-

def is_left_child(

self)

-
- - - - -
- -
-
def is_left_child(self) -> bool:
-    if not self.parent:
-        raise AttributeError("self does not have a parent.")
-    if self.parent.left:
-        return self.parent.left == self
-    else:
-        return False
-
-
-
- -
- - -
-
-

def is_mid_child(

self)

-
- - - - -
- -
-
def is_mid_child(self) -> bool:
-    if not self.parent:
-        raise AttributeError("self does not have a parent.")
-    if self.parent.mid:
-        return self.parent.mid == self
-    else:
-        return False
-
-
-
- -
- - -
-
-

def is_right_child(

self)

-
- - - - -
- -
-
def is_right_child(self) -> bool:
-    if not self.parent:
-        raise AttributeError("self does not have a parent.")
-    if self.parent.right:
-        return self.parent.right == self
-    else:
-        return False
-
-
-
- -
- -

Instance variables

-
-

var key

- - - - -
-
- -
-
-

var left

- - - - -
-
- -
-
-

var mid

- - - - -
-
- -
-
-

var parent

- - - - -
-
- -
-
-

var right

- - - - -
-
- -
-
-

var value

- - - - -
-
- -
-
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/heap.m.html b/docs/ands/ds/heap.m.html deleted file mode 100644 index 6398a9fd..00000000 --- a/docs/ands/ds/heap.m.html +++ /dev/null @@ -1,2786 +0,0 @@ - - - - - - ands.ds.heap API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds.heap module

-

Meta info

-

Author: Nelson Brochado

-

Created: 01/07/2015

-

Updated: 05/02/2017

-

Description

-

This module contains currently the classes HeapNode, which is a class to represent nodes of heaps, -the class BinaryHeap and a function which returns a pretty string representation of a heap passed as parameter.

-

References

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-"""
-# Meta info
-
-Author: Nelson Brochado
-
-Created: 01/07/2015
-
-Updated: 05/02/2017
-
-# Description
-
-This module contains currently the classes `HeapNode`, which is a class to represent nodes of heaps,
-the class `BinaryHeap` and a function which returns a pretty string representation of a heap passed as parameter.
-
-# References
-
-- [http://www.math.clemson.edu/~warner/M865/HeapDelete.html](http://www.math.clemson.edu/~warner/M865/HeapDelete.html)
-- Slides by prof. A. Carzaniga
-- Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS
-- [NotImplementedError](https://docs.python.org/3/library/exceptions.html#NotImplementedError)
-
-"""
-
-import io
-import math
-from collections import Iterable
-
-__all__ = ["BinaryHeap", "HeapNode", "build_pretty_binary_heap"]
-
-
-class HeapNode:
-    """All elements of heap objects are represented with objects of the class HeapNode."""
-
-    def __init__(self, key, value=None):
-        """`key` is the priority used to heapify the heap,
-        and it must be a non-None comparable value.
-        `value` can be used for example for the name of the `HeapNode` object."""
-        if key is None:
-            raise ValueError("key cannot be None.")
-        self.key = key
-        self.value = value if value is not None else self.key
-
-    def __eq__(self, o):
-        return self.key == o.key and self.value == o.value
-
-    def __ne__(self, o):
-        return not self.__eq__(o)
-
-    def __le__(self, o):
-        return self.key <= o.key
-
-    def __ge__(self, o):
-        return self.key >= o.key
-
-    def __lt__(self, o):
-        return not self.__ge__(o)
-
-    def __gt__(self, o):
-        return not self.__le__(o)
-
-    def __str__(self):
-        return str(self.key)
-
-    def __repr__(self):
-        return str(self.value) + " -> " + str(self.key)
-
-
-class BinaryHeap:
-    """Abstract class to represent binary heaps.
-
-    `MinHeap`, `MaxHeap` and `MinMaxHeap` all derive from this class."""
-
-    def __init__(self, ls=None):
-        if ls is None:
-            ls = []
-        self.heap = BinaryHeap._create_list_of_heap_nodes(ls)
-        self.build_heap()
-
-    def push_down(self, i: int) -> None:
-        """Classical so-called heapify operation for heaps."""
-        raise NotImplementedError()
-
-    def push_up(self, i: int) -> None:
-        """Classical reverse-heapify operation for heaps."""
-        raise NotImplementedError()
-
-    def delete(self, i: int) -> HeapNode:
-        raise NotImplementedError()
-
-    def replace(self, i: int, x) -> HeapNode:
-        """Replaces the `HeapNode` object at index `i` with `x`.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, an `HeapNode` is first created,
-        whose key and value are equal to `x`."""
-        raise NotImplementedError()
-
-    def build_heap(self) -> list:
-        """Builds the heap data structure from `self.heap`."""
-        if self.heap:
-            for index in range(len(self.heap) // 2, -1, -1):
-                self.push_down(index)
-        return self.heap
-
-    def add(self, x) -> None:
-        """Adds `x` to this heap.
-
-        In practice, it places `x` at an available leaf,
-        then "bubbles up" from there,
-        in order to maintain the heap property.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, an `HeapNode` is first created,
-        whose key and value are equal to `x`.
-
-        **Time Complexity:** O(log2 n)."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, HeapNode):
-            x = HeapNode(x)
-        self.heap.append(x)
-        if self.size() > 1:
-            self.push_up(self.size() - 1)
-
-    def search(self, x) -> int:
-        """Searches for `x` in this heap,
-        and, if present, returns its index, otherwise returns -1.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, an `HeapNode` is first created,
-        whose key and value are equal to `x`.
-
-        **Time Complexity:** O(n)."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, HeapNode):
-            x = HeapNode(x)
-        for i, node in enumerate(self.heap):
-            if node == x:
-                return i
-        return -1
-
-    def search_by_value(self, val) -> int:
-        """Returns the index of the `HeapNode` object with `value=val`.
-        -1 is returned if no such a `HeapNode` object exists.
-
-        If `val` and the values in this heap are not comparable,
-        the behaviour of this method is undefined.
-
-        By construction, HeapNode objects can't be initialized with None values,
-        but that field could also be set manually after creation.
-
-        **Time Complexity:** O(n)."""
-        for i, node in enumerate(self.heap):
-            if node.value == val:
-                return i
-        return -1
-
-    def contains(self, x) -> bool:
-        """Returns `True`, if `x` is in this heap. `False` otherwise.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, an `HeapNode` is first created,
-        whose key and value are equal to `x`.
-
-        **Time Complexity:** O(n)."""
-        return self.search(x) != -1
-
-    def merge(self, o) -> list:
-        """Merges this heap with the `o` heap.
-
-        Returns the `list` object representing internally the new merged heap.
-
-        **Time Complexity:** O(n + m).
-
-        Time complexity analysis based on:
-        [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
-        self.heap += o.heap
-        return self.build_heap()
-
-    def size(self) -> int:
-        """Returns the size of this heaps.
-
-        **Time Complexity:** O(1)."""
-        return len(self.heap)
-
-    def is_empty(self) -> bool:
-        """Returns `True` if this heap is empty.
-
-        **Time Complexity:** O(1)."""
-        return self.size() == 0
-
-    def clear(self) -> None:
-        """Clears all nodes from this heap.
-        This mean that if you call `is_empty`,
-        it will return `True`.
-
-        **Time Complexity:** O(1)."""
-        self.heap.clear()
-
-    def swap(self, i: int, j: int) -> None:
-        """Swaps elements at indexes `i` and `j`,
-        if they are valid indexes,
-        otherwise an `IndexError` is raised.
-
-        **Time Complexity:** O(1)."""
-        if self.is_good_index(i) and self.is_good_index(j):
-            self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
-        else:
-            raise IndexError("i or j are not valid indexes.")
-
-    # INDEX FUNCTIONS
-
-    def is_good_index(self, i: int) -> bool:
-        """Returns `True` if `i` is valid index for `self.heap`,
-        `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        if not isinstance(i, int):
-            raise TypeError("indexes can only be int.")
-        return False if (i < 0 or i >= self.size()) else True
-
-    def parent_index(self, i: int) -> int:
-        """Returns the parent's index of the node at index `i`.
-        If `i = 0`, then -1 is returned, because the root has no parent.
-
-        If `i` is not a valid index, an `IndexError` is raised.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        return -1 if i == 0 else (i - 1) // 2
-
-    def grandparent_index(self, i: int) -> int:
-        """Returns the grandparent's index of the node at index `i`.
-
-        -1 is returned either if `i` has not a parent or
-        the parent of `i` does not have a parent.
-
-        **Time Complexity:** O(1)."""
-        p = self.parent_index(i)
-        return -1 if p == -1 else self.parent_index(p)
-
-    def left_index(self, i: int) -> int:
-        """Returns the left child's index of the node at index `i`,
-        if it exists, otherwise this function returns -1.
-
-        If `i` is not a valid index, an `IndexError` is raised.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        left = i * 2 + 1
-        return left if self.is_good_index(left) else -1
-
-    def right_index(self, i: int) -> int:
-        """Returns the right child's index of the node at index `i`,
-        if it exists, otherwise this function returns -1.
-
-        If `i` is not a valid index, an `IndexError` is raised.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        right = i * 2 + 2
-        return right if self.is_good_index(right) else -1
-
-    def has_children(self, i: int) -> bool:
-        """Returns `True` if the node at index `i`
-        has at least one child, `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        return self.left_index(i) != -1 or self.right_index(i) != -1
-
-    def is_child(self, c: int, i: int) -> bool:
-        """Returns `True` if `c` is a child of `i`. `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(c) or not self.is_good_index(i):
-            raise IndexError("i or c are not valid indexes.")
-        return c == self.left_index(i) or c == self.right_index(i)
-
-    def is_grandchild(self, g: int, i: int) -> bool:
-        """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        l = self.left_index(i)
-        if l == -1:
-            assert self.right_index(i) == -1
-            if not self.is_good_index(g):
-                raise IndexError("g is not a valid index.")
-            return False
-        r = self.right_index(i)
-        if r == -1:
-            return self.is_child(g, l)
-        else:
-            return self.is_child(g, l) or self.is_child(g, r)
-
-    def is_parent(self, p: int, i: int) -> bool:
-        """Returns `True` if `p` is the index of the parent
-        of the node at `i`, `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(p):
-            raise IndexError("p is not a valid index.")
-        return self.parent_index(i) == p
-
-    def is_grandparent(self, g: int, i: int) -> bool:
-        """Returns `True` if `g` is the index of the grandparent
-        of the node at `i`, `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(g):
-            raise IndexError("g is not a valid index.")
-        p = self.parent_index(i)
-        return False if p == -1 else self.is_parent(g, p)
-
-    def is_on_even_level(self, i: int) -> bool:
-        """Returns `True` if node at index `i` is on a even-level,
-        i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
-        If `i` is not a valid index, an `IndexError` is raised.
-
-        **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        return int(math.log2(i + 1) % 2) == 0
-
-    def is_on_odd_level(self, i: int) -> bool:
-        """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
-        return not self.is_on_even_level(i)
-
-    def __str__(self) -> str:
-        return str(self.heap)
-
-    def __repr__(self) -> str:
-        return build_pretty_binary_heap(self.heap)
-
-    @staticmethod
-    def _create_list_of_heap_nodes(ls: list) -> list:
-        """Creates and returns a list of `HeapNode`
-        objects with the objects in `ls`.
-
-        **Time Complexity:** O(n)."""
-        nodes = []
-        for _, x in enumerate(ls):
-            # x represents also its priority.
-            if isinstance(x, (int, float)):
-                nodes.append(HeapNode(x))
-            else:
-                if len(x) != 2:
-                    raise ValueError("x should be a tuple or list of 2 elements.")
-                # x[0] := priority
-                # x[1] := value associated with x[0]
-                if x[0] is None or x[1] is None:
-                    raise ValueError("keys or values cannot be None.")
-                nodes.append(HeapNode(key=x[0], value=x[1]))
-        return nodes
-
-
-def build_pretty_binary_heap(heap: list, total_width=36, fill=" ") -> str:
-    """Returns a string (which can be printed) representing `heap` as a tree.
-
-    Adapted for Python 3 from: [http://pymotw.com/2/heapq/](http://pymotw.com/2/heapq/).
-
-    To increase/decrease the horizontal space between nodes,
-    just increase/decrease the float number h_space.
-
-    To increase/decrease the vertical space between nodes,
-    just increase/decrease the integer number v_space.
-    Note that v_space must be an integer.
-
-    To change the length of the line under the heap,
-    you can simply change the line_length variable."""
-    if not isinstance(heap, Iterable):
-        raise TypeError("heap must be an iterable object")
-    if len(heap) == 0:
-        return "Nothing to print: heap is empty."
-
-    output = io.StringIO()
-    last_row = -1
-
-    h_space = 3.0  # float
-    v_space = 2  # int
-
-    for i, heap_node in enumerate(heap):
-        if i:
-            row = int(math.floor(math.log(i + 1, 2)))
-        else:
-            row = 0
-
-        if row != last_row:
-            output.write("\n" * v_space)
-
-        columns = 2 ** row
-
-        column_width = int(math.floor((total_width * h_space) / columns))
-        output.write(str(heap_node).center(column_width, fill))
-        last_row = row
-
-    s = output.getvalue() + "\n"
-
-    line_length = total_width + 15  # int
-    s += ('-' * line_length + "\n")
-    return s
-
-
- -
- -
- -

Functions

- -
-
-

def build_pretty_binary_heap(

heap, total_width=36, fill=' ')

-
- - - - -

Returns a string (which can be printed) representing heap as a tree.

-

Adapted for Python 3 from: http://pymotw.com/2/heapq/.

-

To increase/decrease the horizontal space between nodes, -just increase/decrease the float number h_space.

-

To increase/decrease the vertical space between nodes, -just increase/decrease the integer number v_space. -Note that v_space must be an integer.

-

To change the length of the line under the heap, -you can simply change the line_length variable.

-
- -
-
def build_pretty_binary_heap(heap: list, total_width=36, fill=" ") -> str:
-    """Returns a string (which can be printed) representing `heap` as a tree.
-
-    Adapted for Python 3 from: [http://pymotw.com/2/heapq/](http://pymotw.com/2/heapq/).
-
-    To increase/decrease the horizontal space between nodes,
-    just increase/decrease the float number h_space.
-
-    To increase/decrease the vertical space between nodes,
-    just increase/decrease the integer number v_space.
-    Note that v_space must be an integer.
-
-    To change the length of the line under the heap,
-    you can simply change the line_length variable."""
-    if not isinstance(heap, Iterable):
-        raise TypeError("heap must be an iterable object")
-    if len(heap) == 0:
-        return "Nothing to print: heap is empty."
-
-    output = io.StringIO()
-    last_row = -1
-
-    h_space = 3.0  # float
-    v_space = 2  # int
-
-    for i, heap_node in enumerate(heap):
-        if i:
-            row = int(math.floor(math.log(i + 1, 2)))
-        else:
-            row = 0
-
-        if row != last_row:
-            output.write("\n" * v_space)
-
-        columns = 2 ** row
-
-        column_width = int(math.floor((total_width * h_space) / columns))
-        output.write(str(heap_node).center(column_width, fill))
-        last_row = row
-
-    s = output.getvalue() + "\n"
-
-    line_length = total_width + 15  # int
-    s += ('-' * line_length + "\n")
-    return s
-
-
-
- -
- - -

Classes

- -
-

class BinaryHeap

- - -

Abstract class to represent binary heaps.

-

MinHeap, MaxHeap and MinMaxHeap all derive from this class.

-
- -
-
class BinaryHeap:
-    """Abstract class to represent binary heaps.
-
-    `MinHeap`, `MaxHeap` and `MinMaxHeap` all derive from this class."""
-
-    def __init__(self, ls=None):
-        if ls is None:
-            ls = []
-        self.heap = BinaryHeap._create_list_of_heap_nodes(ls)
-        self.build_heap()
-
-    def push_down(self, i: int) -> None:
-        """Classical so-called heapify operation for heaps."""
-        raise NotImplementedError()
-
-    def push_up(self, i: int) -> None:
-        """Classical reverse-heapify operation for heaps."""
-        raise NotImplementedError()
-
-    def delete(self, i: int) -> HeapNode:
-        raise NotImplementedError()
-
-    def replace(self, i: int, x) -> HeapNode:
-        """Replaces the `HeapNode` object at index `i` with `x`.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, an `HeapNode` is first created,
-        whose key and value are equal to `x`."""
-        raise NotImplementedError()
-
-    def build_heap(self) -> list:
-        """Builds the heap data structure from `self.heap`."""
-        if self.heap:
-            for index in range(len(self.heap) // 2, -1, -1):
-                self.push_down(index)
-        return self.heap
-
-    def add(self, x) -> None:
-        """Adds `x` to this heap.
-
-        In practice, it places `x` at an available leaf,
-        then "bubbles up" from there,
-        in order to maintain the heap property.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, an `HeapNode` is first created,
-        whose key and value are equal to `x`.
-
-        **Time Complexity:** O(log2 n)."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, HeapNode):
-            x = HeapNode(x)
-        self.heap.append(x)
-        if self.size() > 1:
-            self.push_up(self.size() - 1)
-
-    def search(self, x) -> int:
-        """Searches for `x` in this heap,
-        and, if present, returns its index, otherwise returns -1.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, an `HeapNode` is first created,
-        whose key and value are equal to `x`.
-
-        **Time Complexity:** O(n)."""
-        if x is None:
-            raise ValueError("x cannot be None.")
-        if not isinstance(x, HeapNode):
-            x = HeapNode(x)
-        for i, node in enumerate(self.heap):
-            if node == x:
-                return i
-        return -1
-
-    def search_by_value(self, val) -> int:
-        """Returns the index of the `HeapNode` object with `value=val`.
-        -1 is returned if no such a `HeapNode` object exists.
-
-        If `val` and the values in this heap are not comparable,
-        the behaviour of this method is undefined.
-
-        By construction, HeapNode objects can't be initialized with None values,
-        but that field could also be set manually after creation.
-
-        **Time Complexity:** O(n)."""
-        for i, node in enumerate(self.heap):
-            if node.value == val:
-                return i
-        return -1
-
-    def contains(self, x) -> bool:
-        """Returns `True`, if `x` is in this heap. `False` otherwise.
-
-        `x` can either be a key or a `HeapNode` object.
-        If it's a key, an `HeapNode` is first created,
-        whose key and value are equal to `x`.
-
-        **Time Complexity:** O(n)."""
-        return self.search(x) != -1
-
-    def merge(self, o) -> list:
-        """Merges this heap with the `o` heap.
-
-        Returns the `list` object representing internally the new merged heap.
-
-        **Time Complexity:** O(n + m).
-
-        Time complexity analysis based on:
-        [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
-        self.heap += o.heap
-        return self.build_heap()
-
-    def size(self) -> int:
-        """Returns the size of this heaps.
-
-        **Time Complexity:** O(1)."""
-        return len(self.heap)
-
-    def is_empty(self) -> bool:
-        """Returns `True` if this heap is empty.
-
-        **Time Complexity:** O(1)."""
-        return self.size() == 0
-
-    def clear(self) -> None:
-        """Clears all nodes from this heap.
-        This mean that if you call `is_empty`,
-        it will return `True`.
-
-        **Time Complexity:** O(1)."""
-        self.heap.clear()
-
-    def swap(self, i: int, j: int) -> None:
-        """Swaps elements at indexes `i` and `j`,
-        if they are valid indexes,
-        otherwise an `IndexError` is raised.
-
-        **Time Complexity:** O(1)."""
-        if self.is_good_index(i) and self.is_good_index(j):
-            self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
-        else:
-            raise IndexError("i or j are not valid indexes.")
-
-    # INDEX FUNCTIONS
-
-    def is_good_index(self, i: int) -> bool:
-        """Returns `True` if `i` is valid index for `self.heap`,
-        `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        if not isinstance(i, int):
-            raise TypeError("indexes can only be int.")
-        return False if (i < 0 or i >= self.size()) else True
-
-    def parent_index(self, i: int) -> int:
-        """Returns the parent's index of the node at index `i`.
-        If `i = 0`, then -1 is returned, because the root has no parent.
-
-        If `i` is not a valid index, an `IndexError` is raised.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        return -1 if i == 0 else (i - 1) // 2
-
-    def grandparent_index(self, i: int) -> int:
-        """Returns the grandparent's index of the node at index `i`.
-
-        -1 is returned either if `i` has not a parent or
-        the parent of `i` does not have a parent.
-
-        **Time Complexity:** O(1)."""
-        p = self.parent_index(i)
-        return -1 if p == -1 else self.parent_index(p)
-
-    def left_index(self, i: int) -> int:
-        """Returns the left child's index of the node at index `i`,
-        if it exists, otherwise this function returns -1.
-
-        If `i` is not a valid index, an `IndexError` is raised.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        left = i * 2 + 1
-        return left if self.is_good_index(left) else -1
-
-    def right_index(self, i: int) -> int:
-        """Returns the right child's index of the node at index `i`,
-        if it exists, otherwise this function returns -1.
-
-        If `i` is not a valid index, an `IndexError` is raised.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        right = i * 2 + 2
-        return right if self.is_good_index(right) else -1
-
-    def has_children(self, i: int) -> bool:
-        """Returns `True` if the node at index `i`
-        has at least one child, `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        return self.left_index(i) != -1 or self.right_index(i) != -1
-
-    def is_child(self, c: int, i: int) -> bool:
-        """Returns `True` if `c` is a child of `i`. `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(c) or not self.is_good_index(i):
-            raise IndexError("i or c are not valid indexes.")
-        return c == self.left_index(i) or c == self.right_index(i)
-
-    def is_grandchild(self, g: int, i: int) -> bool:
-        """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        l = self.left_index(i)
-        if l == -1:
-            assert self.right_index(i) == -1
-            if not self.is_good_index(g):
-                raise IndexError("g is not a valid index.")
-            return False
-        r = self.right_index(i)
-        if r == -1:
-            return self.is_child(g, l)
-        else:
-            return self.is_child(g, l) or self.is_child(g, r)
-
-    def is_parent(self, p: int, i: int) -> bool:
-        """Returns `True` if `p` is the index of the parent
-        of the node at `i`, `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(p):
-            raise IndexError("p is not a valid index.")
-        return self.parent_index(i) == p
-
-    def is_grandparent(self, g: int, i: int) -> bool:
-        """Returns `True` if `g` is the index of the grandparent
-        of the node at `i`, `False` otherwise.
-
-        **Time Complexity:** O(1)."""
-        if not self.is_good_index(g):
-            raise IndexError("g is not a valid index.")
-        p = self.parent_index(i)
-        return False if p == -1 else self.is_parent(g, p)
-
-    def is_on_even_level(self, i: int) -> bool:
-        """Returns `True` if node at index `i` is on a even-level,
-        i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
-        If `i` is not a valid index, an `IndexError` is raised.
-
-        **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
-        if not self.is_good_index(i):
-            raise IndexError("i is not a valid index.")
-        return int(math.log2(i + 1) % 2) == 0
-
-    def is_on_odd_level(self, i: int) -> bool:
-        """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
-        return not self.is_on_even_level(i)
-
-    def __str__(self) -> str:
-        return str(self.heap)
-
-    def __repr__(self) -> str:
-        return build_pretty_binary_heap(self.heap)
-
-    @staticmethod
-    def _create_list_of_heap_nodes(ls: list) -> list:
-        """Creates and returns a list of `HeapNode`
-        objects with the objects in `ls`.
-
-        **Time Complexity:** O(n)."""
-        nodes = []
-        for _, x in enumerate(ls):
-            # x represents also its priority.
-            if isinstance(x, (int, float)):
-                nodes.append(HeapNode(x))
-            else:
-                if len(x) != 2:
-                    raise ValueError("x should be a tuple or list of 2 elements.")
-                # x[0] := priority
-                # x[1] := value associated with x[0]
-                if x[0] is None or x[1] is None:
-                    raise ValueError("keys or values cannot be None.")
-                nodes.append(HeapNode(key=x[0], value=x[1]))
-        return nodes
-
-
-
- - -
-

Ancestors (in MRO)

- -

Static methods

- -
-
-

def __init__(

self, ls=None)

-
- - - - -

Initialize self. See help(type(self)) for accurate signature.

-
- -
-
def __init__(self, ls=None):
-    if ls is None:
-        ls = []
-    self.heap = BinaryHeap._create_list_of_heap_nodes(ls)
-    self.build_heap()
-
-
-
- -
- - -
-
-

def add(

self, x)

-
- - - - -

Adds x to this heap.

-

In practice, it places x at an available leaf, -then "bubbles up" from there, -in order to maintain the heap property.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(log2 n).

-
- -
-
def add(self, x) -> None:
-    """Adds `x` to this heap.
-    In practice, it places `x` at an available leaf,
-    then "bubbles up" from there,
-    in order to maintain the heap property.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(log2 n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    self.heap.append(x)
-    if self.size() > 1:
-        self.push_up(self.size() - 1)
-
-
-
- -
- - -
-
-

def build_heap(

self)

-
- - - - -

Builds the heap data structure from self.heap.

-
- -
-
def build_heap(self) -> list:
-    """Builds the heap data structure from `self.heap`."""
-    if self.heap:
-        for index in range(len(self.heap) // 2, -1, -1):
-            self.push_down(index)
-    return self.heap
-
-
-
- -
- - -
-
-

def clear(

self)

-
- - - - -

Clears all nodes from this heap. -This mean that if you call is_empty, -it will return True.

-

Time Complexity: O(1).

-
- -
-
def clear(self) -> None:
-    """Clears all nodes from this heap.
-    This mean that if you call `is_empty`,
-    it will return `True`.
-    **Time Complexity:** O(1)."""
-    self.heap.clear()
-
-
-
- -
- - -
-
-

def contains(

self, x)

-
- - - - -

Returns True, if x is in this heap. False otherwise.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(n).

-
- -
-
def contains(self, x) -> bool:
-    """Returns `True`, if `x` is in this heap. `False` otherwise.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(n)."""
-    return self.search(x) != -1
-
-
-
- -
- - -
-
-

def delete(

self, i)

-
- - - - -
- -
-
def delete(self, i: int) -> HeapNode:
-    raise NotImplementedError()
-
-
-
- -
- - -
-
-

def grandparent_index(

self, i)

-
- - - - -

Returns the grandparent's index of the node at index i.

-

-1 is returned either if i has not a parent or -the parent of i does not have a parent.

-

Time Complexity: O(1).

-
- -
-
def grandparent_index(self, i: int) -> int:
-    """Returns the grandparent's index of the node at index `i`.
-    -1 is returned either if `i` has not a parent or
-    the parent of `i` does not have a parent.
-    **Time Complexity:** O(1)."""
-    p = self.parent_index(i)
-    return -1 if p == -1 else self.parent_index(p)
-
-
-
- -
- - -
-
-

def has_children(

self, i)

-
- - - - -

Returns True if the node at index i -has at least one child, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def has_children(self, i: int) -> bool:
-    """Returns `True` if the node at index `i`
-    has at least one child, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return self.left_index(i) != -1 or self.right_index(i) != -1
-
-
-
- -
- - -
-
-

def is_child(

self, c, i)

-
- - - - -

Returns True if c is a child of i. False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_child(self, c: int, i: int) -> bool:
-    """Returns `True` if `c` is a child of `i`. `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(c) or not self.is_good_index(i):
-        raise IndexError("i or c are not valid indexes.")
-    return c == self.left_index(i) or c == self.right_index(i)
-
-
-
- -
- - -
-
-

def is_empty(

self)

-
- - - - -

Returns True if this heap is empty.

-

Time Complexity: O(1).

-
- -
-
def is_empty(self) -> bool:
-    """Returns `True` if this heap is empty.
-    **Time Complexity:** O(1)."""
-    return self.size() == 0
-
-
-
- -
- - -
-
-

def is_good_index(

self, i)

-
- - - - -

Returns True if i is valid index for self.heap, -False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_good_index(self, i: int) -> bool:
-    """Returns `True` if `i` is valid index for `self.heap`,
-    `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not isinstance(i, int):
-        raise TypeError("indexes can only be int.")
-    return False if (i < 0 or i >= self.size()) else True
-
-
-
- -
- - -
-
-

def is_grandchild(

self, g, i)

-
- - - - -

Returns True if g is a grandchild of i. False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_grandchild(self, g: int, i: int) -> bool:
-    """Returns `True` if `g` is a grandchild of `i`. `False` otherwise.
-    **Time Complexity:** O(1)."""
-    l = self.left_index(i)
-    if l == -1:
-        assert self.right_index(i) == -1
-        if not self.is_good_index(g):
-            raise IndexError("g is not a valid index.")
-        return False
-    r = self.right_index(i)
-    if r == -1:
-        return self.is_child(g, l)
-    else:
-        return self.is_child(g, l) or self.is_child(g, r)
-
-
-
- -
- - -
-
-

def is_grandparent(

self, g, i)

-
- - - - -

Returns True if g is the index of the grandparent -of the node at i, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_grandparent(self, g: int, i: int) -> bool:
-    """Returns `True` if `g` is the index of the grandparent
-    of the node at `i`, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(g):
-        raise IndexError("g is not a valid index.")
-    p = self.parent_index(i)
-    return False if p == -1 else self.is_parent(g, p)
-
-
-
- -
- - -
-
-

def is_on_even_level(

self, i)

-
- - - - -

Returns True if node at index i is on a even-level, -i.e., if i is on a level multiple of 2 (0, 2, 4, 6,...). -If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(int(math.log2(i + 1) % 2) == 0).

-
- -
-
def is_on_even_level(self, i: int) -> bool:
-    """Returns `True` if node at index `i` is on a even-level,
-    i.e., if `i` is on a level multiple of 2 (0, 2, 4, 6,...).
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(int(math.log2(i + 1) % 2) == 0)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return int(math.log2(i + 1) % 2) == 0
-
-
-
- -
- - -
-
-

def is_on_odd_level(

self, i)

-
- - - - -

Returns True (False) if self.is_on_even_level(i) returns False (True).

-
- -
-
def is_on_odd_level(self, i: int) -> bool:
-    """Returns `True` (`False`) if `self.is_on_even_level(i)` returns `False` (`True`)."""
-    return not self.is_on_even_level(i)
-
-
-
- -
- - -
-
-

def is_parent(

self, p, i)

-
- - - - -

Returns True if p is the index of the parent -of the node at i, False otherwise.

-

Time Complexity: O(1).

-
- -
-
def is_parent(self, p: int, i: int) -> bool:
-    """Returns `True` if `p` is the index of the parent
-    of the node at `i`, `False` otherwise.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(p):
-        raise IndexError("p is not a valid index.")
-    return self.parent_index(i) == p
-
-
-
- -
- - -
-
-

def left_index(

self, i)

-
- - - - -

Returns the left child's index of the node at index i, -if it exists, otherwise this function returns -1.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def left_index(self, i: int) -> int:
-    """Returns the left child's index of the node at index `i`,
-    if it exists, otherwise this function returns -1.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    left = i * 2 + 1
-    return left if self.is_good_index(left) else -1
-
-
-
- -
- - -
-
-

def merge(

self, o)

-
- - - - -

Merges this heap with the o heap.

-

Returns the list object representing internally the new merged heap.

-

Time Complexity: O(n + m).

-

Time complexity analysis based on: -http://stackoverflow.com/a/29197855/3924118.

-
- -
-
def merge(self, o) -> list:
-    """Merges this heap with the `o` heap.
-    Returns the `list` object representing internally the new merged heap.
-    **Time Complexity:** O(n + m).
-    Time complexity analysis based on:
-    [http://stackoverflow.com/a/29197855/3924118](http://stackoverflow.com/a/29197855/3924118)."""
-    self.heap += o.heap
-    return self.build_heap()
-
-
-
- -
- - -
-
-

def parent_index(

self, i)

-
- - - - -

Returns the parent's index of the node at index i. -If i = 0, then -1 is returned, because the root has no parent.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def parent_index(self, i: int) -> int:
-    """Returns the parent's index of the node at index `i`.
-    If `i = 0`, then -1 is returned, because the root has no parent.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    return -1 if i == 0 else (i - 1) // 2
-
-
-
- -
- - -
-
-

def push_down(

self, i)

-
- - - - -

Classical so-called heapify operation for heaps.

-
- -
-
def push_down(self, i: int) -> None:
-    """Classical so-called heapify operation for heaps."""
-    raise NotImplementedError()
-
-
-
- -
- - -
-
-

def push_up(

self, i)

-
- - - - -

Classical reverse-heapify operation for heaps.

-
- -
-
def push_up(self, i: int) -> None:
-    """Classical reverse-heapify operation for heaps."""
-    raise NotImplementedError()
-
-
-
- -
- - -
-
-

def replace(

self, i, x)

-
- - - - -

Replaces the HeapNode object at index i with x.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-
- -
-
def replace(self, i: int, x) -> HeapNode:
-    """Replaces the `HeapNode` object at index `i` with `x`.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`."""
-    raise NotImplementedError()
-
-
-
- -
- - -
-
-

def right_index(

self, i)

-
- - - - -

Returns the right child's index of the node at index i, -if it exists, otherwise this function returns -1.

-

If i is not a valid index, an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def right_index(self, i: int) -> int:
-    """Returns the right child's index of the node at index `i`,
-    if it exists, otherwise this function returns -1.
-    If `i` is not a valid index, an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if not self.is_good_index(i):
-        raise IndexError("i is not a valid index.")
-    right = i * 2 + 2
-    return right if self.is_good_index(right) else -1
-
-
-
- -
- - -
-
-

def search(

self, x)

-
- - - - -

Searches for x in this heap, -and, if present, returns its index, otherwise returns -1.

-

x can either be a key or a HeapNode object. -If it's a key, an HeapNode is first created, -whose key and value are equal to x.

-

Time Complexity: O(n).

-
- -
-
def search(self, x) -> int:
-    """Searches for `x` in this heap,
-    and, if present, returns its index, otherwise returns -1.
-    `x` can either be a key or a `HeapNode` object.
-    If it's a key, an `HeapNode` is first created,
-    whose key and value are equal to `x`.
-    **Time Complexity:** O(n)."""
-    if x is None:
-        raise ValueError("x cannot be None.")
-    if not isinstance(x, HeapNode):
-        x = HeapNode(x)
-    for i, node in enumerate(self.heap):
-        if node == x:
-            return i
-    return -1
-
-
-
- -
- - -
-
-

def search_by_value(

self, val)

-
- - - - -

Returns the index of the HeapNode object with value=val. --1 is returned if no such a HeapNode object exists.

-

If val and the values in this heap are not comparable, -the behaviour of this method is undefined.

-

By construction, HeapNode objects can't be initialized with None values, -but that field could also be set manually after creation.

-

Time Complexity: O(n).

-
- -
-
def search_by_value(self, val) -> int:
-    """Returns the index of the `HeapNode` object with `value=val`.
-    -1 is returned if no such a `HeapNode` object exists.
-    If `val` and the values in this heap are not comparable,
-    the behaviour of this method is undefined.
-    By construction, HeapNode objects can't be initialized with None values,
-    but that field could also be set manually after creation.
-    **Time Complexity:** O(n)."""
-    for i, node in enumerate(self.heap):
-        if node.value == val:
-            return i
-    return -1
-
-
-
- -
- - -
-
-

def size(

self)

-
- - - - -

Returns the size of this heaps.

-

Time Complexity: O(1).

-
- -
-
def size(self) -> int:
-    """Returns the size of this heaps.
-    **Time Complexity:** O(1)."""
-    return len(self.heap)
-
-
-
- -
- - -
-
-

def swap(

self, i, j)

-
- - - - -

Swaps elements at indexes i and j, -if they are valid indexes, -otherwise an IndexError is raised.

-

Time Complexity: O(1).

-
- -
-
def swap(self, i: int, j: int) -> None:
-    """Swaps elements at indexes `i` and `j`,
-    if they are valid indexes,
-    otherwise an `IndexError` is raised.
-    **Time Complexity:** O(1)."""
-    if self.is_good_index(i) and self.is_good_index(j):
-        self.heap[i], self.heap[j] = self.heap[j], self.heap[i]
-    else:
-        raise IndexError("i or j are not valid indexes.")
-
-
-
- -
- -

Instance variables

-
-

var heap

- - - - -
-
- -
-
-
- -
-

class HeapNode

- - -

All elements of heap objects are represented with objects of the class HeapNode.

-
- -
-
class HeapNode:
-    """All elements of heap objects are represented with objects of the class HeapNode."""
-
-    def __init__(self, key, value=None):
-        """`key` is the priority used to heapify the heap,
-        and it must be a non-None comparable value.
-        `value` can be used for example for the name of the `HeapNode` object."""
-        if key is None:
-            raise ValueError("key cannot be None.")
-        self.key = key
-        self.value = value if value is not None else self.key
-
-    def __eq__(self, o):
-        return self.key == o.key and self.value == o.value
-
-    def __ne__(self, o):
-        return not self.__eq__(o)
-
-    def __le__(self, o):
-        return self.key <= o.key
-
-    def __ge__(self, o):
-        return self.key >= o.key
-
-    def __lt__(self, o):
-        return not self.__ge__(o)
-
-    def __gt__(self, o):
-        return not self.__le__(o)
-
-    def __str__(self):
-        return str(self.key)
-
-    def __repr__(self):
-        return str(self.value) + " -> " + str(self.key)
-
-
-
- - -
-

Ancestors (in MRO)

- -

Static methods

- -
-
-

def __init__(

self, key, value=None)

-
- - - - -

key is the priority used to heapify the heap, -and it must be a non-None comparable value. -value can be used for example for the name of the HeapNode object.

-
- -
-
def __init__(self, key, value=None):
-    """`key` is the priority used to heapify the heap,
-    and it must be a non-None comparable value.
-    `value` can be used for example for the name of the `HeapNode` object."""
-    if key is None:
-        raise ValueError("key cannot be None.")
-    self.key = key
-    self.value = value if value is not None else self.key
-
-
-
- -
- -

Instance variables

-
-

var key

- - - - -
-
- -
-
-

var value

- - - - -
-
- -
-
-
- -
- -
-
- -
- - diff --git a/docs/ands/ds/index.html b/docs/ands/ds/index.html deleted file mode 100644 index 86bf3591..00000000 --- a/docs/ands/ds/index.html +++ /dev/null @@ -1,1235 +0,0 @@ - - - - - - ands.ds API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands.ds module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.ds.BST

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 01/07/2015

-

Updated: 28/08/2016

-

Description

-

Coding Conventions

-

In general, if a variable name has more than one word, -those words are separated by _ (underscores). -Functions' names should roughly describe what the function does. -Names of functi...

- -
-
-

ands.ds.DSForests

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 21/02/2016

-

Updated: 03/01/2016

-

Description

-

A disjoint-set (forests) or union-find data structure is a data structure which keeps track of a set of elements -partitioned into disjoint (non-overlapping, i.e. their intersection is the empty set) sets. -...

- -
-
-

ands.ds.Graph

- - -

Author: Nelson Brochado

-

Creation: July, 2015

-

Last update: 01/09/16

-

Graph data structure using adjacency list representation.

-

You can represent a undirected graph by adding -each endpoint (node) of an edge to the adjacency list of the other endpoint. -For example, suppose we have node A and B. -To ...

- -
-
-

ands.ds.HashTable

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 01/06/2015

-

Updated: 21/02/2016

-

Description

-

Hash table that re-sizes if no more slot is available. -The process of re-sizing doubles the current capacity of the hash table each time (for now). -It uses [linear probing](https://en.wikipedia.org/wiki/Li...

- -
-
-

ands.ds.MaxHeap

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 15/02/2016

-

Updated: 05/02/2017

-

Description

-

Mirror-class to the MinHeap class. -For more info, see the introductory doc-strings of the file MinHeap.py.

-

References

-
    -
  • [https://en.wikipedia.org/wiki/Binary_heap](https://en.wikipedia....
  • -
- -
-
-

ands.ds.MinHeap

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 01/07/2015

-

Updated: 05/02/2017

-

Description

-

A binary min-heap is a data structure similar to a binary tree, -where the parent nodes are smaller or equal to their children.

-

In addition to the previous constraint, a binary min-heap is a complete binar...

- -
-
-

ands.ds.MinMaxHeap

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 18/02/2016

-

Updated: 29/12/2016

-

Description

-

Min-Max Heap is a heap that supports find-min and find-max operations in constant time. -Moreover, both remove-min and remove-max are supported in logarithmic time. -It's therefore an useful data structure t...

- -
-
-

ands.ds.MinPriorityQueue

- - -

Author: Nelson Brochado

-

Created: 24/01/2017

- -
-
-

ands.ds.Queue

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 02/07/2015

-

Updated: 04/02/2017

-

Description

-

Basic queue, which is FIFO (first-in-first-out) data structure. -It's implemented using a deque, because a deque supports better the dequeue operation than lists.

-

References

-
    -
  • [https://docs.python.org...
  • -
- -
-
-

ands.ds.RBT

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 01/08/2015

-

Updated: 28/08/2016

-

Description

-

Red-black Tree Property

-
    -
  1. -

    Every node is either red or black.

    -
  2. -
  3. -

    The root is black.

    -
  4. -
  5. -

    Every NIL or leaf node is black.

    -
  6. -
  7. -

    If a node is red, then both its children are black, -in other words, there c...

    -
  8. -
- -
-
-

ands.ds.Stack

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 05/07/2015

-

Updated: 04/02/2017

-

Description

-

A stack is one of the most simple and, at the same time, useful abstract data types in computer science.

-

An abstract data type (or, in short, ADT) is a logical description or specification -of a certain wa...

- -
-
-

ands.ds.TST

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 05/09/2015

-

Updated: 03/02/2017

-

Description

-

Ternary-search tries (or trees) combine the time efficiency of other tries -with the space efficiency of binary-search trees.

-

An advantage compared to hash maps is that ternary search tries support sorting...

- -
-
-

ands.ds.heap

- - -

Meta info

-

Author: Nelson Brochado

-

Created: 01/07/2015

-

Updated: 05/02/2017

-

Description

-

This module contains currently the classes HeapNode, which is a class to represent nodes of heaps, -the class BinaryHeap and a function which returns a pretty string representation of a heap passed as p...

- -
-
- -
-
- -
- - diff --git a/docs/ands/index.html b/docs/ands/index.html deleted file mode 100644 index a0a34f6e..00000000 --- a/docs/ands/index.html +++ /dev/null @@ -1,1045 +0,0 @@ - - - - - - ands API documentation - - - - - - - - - - - - - - -Top - -
- - - - -
- - - - - - -
-

ands module

- - - -
-
#!/usr/bin/env python3
-# -*- coding: utf-8 -*-
-
-# File to allow this directory to be treated as a python package.
-
-# From Python 3.3+ this way of constructing subpackages
-# should no more be necessary, but for some reason,
-# at the moment of this writing, pdoc does not seem to work
-# as expected if I omit these __init__.py files.
-
-
- -
- -
- - - -

Sub-modules

-
-

ands.algorithms

- - - -
-
-

ands.ds

- - - -
-
- -
-
- -
- - From f0f80f78188918d2c99950eee69bfb94fb9dd373 Mon Sep 17 00:00:00 2001 From: nelson-brochado Date: Sun, 5 Feb 2017 03:23:30 +0100 Subject: [PATCH 3/3] Renamed heap.py to Heap.py since it caused problems... --- ands/ds/MaxHeap.py | 2 +- ands/ds/MinHeap.py | 2 +- ands/ds/MinMaxHeap.py | 2 +- tests/ds/test_BinaryHeap.py | 2 +- tests/ds/test_HeapNode.py | 4 ++-- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/ands/ds/MaxHeap.py b/ands/ds/MaxHeap.py index 72452717..c52fc271 100644 --- a/ands/ds/MaxHeap.py +++ b/ands/ds/MaxHeap.py @@ -23,7 +23,7 @@ """ -from ands.ds.heap import BinaryHeap, HeapNode +from ands.ds.Heap import BinaryHeap, HeapNode __all__ = ["MaxHeap", "is_max_heap"] diff --git a/ands/ds/MinHeap.py b/ands/ds/MinHeap.py index 6f3fc852..0ec524b6 100755 --- a/ands/ds/MinHeap.py +++ b/ands/ds/MinHeap.py @@ -40,7 +40,7 @@ - Chapter 13 of [Introduction to Algorithms (3rd ed.)](https://mitpress.mit.edu/books/introduction-algorithms) by CLRS """ -from ands.ds.heap import BinaryHeap, HeapNode +from ands.ds.Heap import BinaryHeap, HeapNode __all__ = ["MinHeap", "is_min_heap"] diff --git a/ands/ds/MinMaxHeap.py b/ands/ds/MinMaxHeap.py index 88a9fbf0..6e760a14 100644 --- a/ands/ds/MinMaxHeap.py +++ b/ands/ds/MinMaxHeap.py @@ -67,7 +67,7 @@ - [http://www.diku.dk/forskning/performance-engineering/Jesper/heaplab/heapsurvey_html/node11.html](http://www.diku.dk/forskning/performance-engineering/Jesper/heaplab/heapsurvey_html/node11.html) """ -from ands.ds.heap import BinaryHeap, HeapNode +from ands.ds.Heap import BinaryHeap, HeapNode class MinMaxHeap(BinaryHeap): diff --git a/tests/ds/test_BinaryHeap.py b/tests/ds/test_BinaryHeap.py index e183efaa..06ff98b5 100755 --- a/tests/ds/test_BinaryHeap.py +++ b/tests/ds/test_BinaryHeap.py @@ -15,7 +15,7 @@ import unittest -from ands.ds.heap import BinaryHeap +from ands.ds.Heap import BinaryHeap class TestBinaryHeap(unittest.TestCase): diff --git a/tests/ds/test_HeapNode.py b/tests/ds/test_HeapNode.py index 4fbd2c87..de9e3161 100644 --- a/tests/ds/test_HeapNode.py +++ b/tests/ds/test_HeapNode.py @@ -11,13 +11,13 @@ # Description -Tests for the HeapNode class in heap.py. +Tests for the HeapNode class in Heap.py. """ import unittest -from ands.ds.heap import HeapNode +from ands.ds.Heap import HeapNode class TestHeapNode(unittest.TestCase):