├── .gitignore ├── README.md ├── __init__.py ├── bonus ├── ant.png ├── graphs.png ├── max_perf_tree.py ├── problem_hard_15_wrong.py └── spiral_copy.py ├── chapter_1 ├── __init__.py ├── p1_1.py ├── p1_2.py ├── p1_3.py ├── p1_4.py ├── p1_5.py ├── p1_6.py ├── p1_7.py ├── p1_8.py ├── p1_9.py └── readme.md ├── chapter_10 ├── p10_10.py ├── p10_11.py ├── p10_2.py ├── p10_3.py ├── p10_4.py ├── p10_5.py ├── p10_8.py └── p10_9.py ├── chapter_16 ├── p16_1.py ├── p16_10.py ├── p16_11.py ├── p16_13.py ├── p16_14.py ├── p16_15.py ├── p16_16.py ├── p16_17.py ├── p16_18.py ├── p16_19.py ├── p16_2.py ├── p16_20.py ├── p16_21.py ├── p16_22.py ├── p16_23.py ├── p16_24.py ├── p16_25.py ├── p16_26.py ├── p16_3.py ├── p16_4.py ├── p16_5.py ├── p16_6.py ├── p16_7.py ├── p16_8.py └── p16_9.py ├── chapter_17 ├── p17_1.py ├── p17_10.py ├── p17_11.py ├── p17_12.py ├── p17_13.py ├── p17_14.py ├── p17_15.py ├── p17_16.py ├── p17_17.py ├── p17_18.py ├── p17_19.py ├── p17_2.py ├── p17_20.py ├── p17_21.py ├── p17_22.py ├── p17_23.py ├── p17_24.py ├── p17_25.py ├── p17_26.py ├── p17_3.py ├── p17_4.py ├── p17_5.py ├── p17_6.py ├── p17_7.py ├── p17_8.py ├── p17_9.py └── readme.md ├── chapter_2 ├── __init__.py ├── linked_list_utils.py ├── p2_1.py ├── p2_2.py ├── p2_3.py ├── p2_4.py ├── p2_5.py ├── p2_6.py ├── p2_7.py ├── p2_8.py └── readme.md ├── chapter_3 ├── datastructs.py ├── p3_2.py ├── p3_3.py ├── p3_4.py ├── p3_5.py └── p3_6.py ├── chapter_4 ├── p4_1.py ├── p4_10.py ├── p4_11.py ├── p4_12.py ├── p4_2.py ├── p4_3.py ├── p4_4.py ├── p4_5.py ├── p4_6.py ├── p4_7.py ├── p4_8.py └── p4_9.py ├── chapter_5 ├── p5_1.py ├── p5_2.py ├── p5_3.py ├── p5_4.py ├── p5_5.py ├── p5_6.py ├── p5_7.py └── p5_8.py ├── chapter_6 ├── p6_1.py ├── p6_10.py ├── p6_4.py ├── p6_5.py ├── p6_7.py └── p6_9.py ├── chapter_8 ├── p8_1.py ├── p8_10.py ├── p8_11.py ├── p8_12.py ├── p8_13.py ├── p8_14.py ├── p8_2.py ├── p8_3.py ├── p8_4.py ├── p8_5.py ├── p8_6.py ├── p8_7.py ├── p8_8.py └── p8_9.py ├── setup.py └── utils ├── __init__.py ├── binutils.py ├── datasets.py ├── graphs.py ├── lorem_ipsum.txt └── treeviz.py /.gitignore: -------------------------------------------------------------------------------- 1 | venv/ 2 | graph_out/ 3 | .vscode/ 4 | settings.json 5 | 6 | */__pycache__/* 7 | .DS_Store 8 | Leet.egg-*/ 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Cracking the Coding Interview (6th edition) Python Solutions 2 | 3 | > Existing Python solutions can be found [officially here](https://github.com/careercup/CtCI-6th-Edition-Python/tree/e6bc732588601d0a98e5b1bc44d83644b910978d) and [user-created here](https://github.com/w-hat/ctci-solutions) 4 | 5 | ## Current progress 6 | | Nr. | Chapter | Done | Total | Ommited | Notes | 7 | |:---: |:----------------------: |:----: |:-----: |:-------: | :-------: | 8 | | 1 | [Array and String](chapter_1) | 9 | 9 | | | | 9 | | 2 | [Linked Lists](chapter_2) | 8 | 8 | | | 10 | | 3 | [Stacks and Queues](chapter_3) | 6 | 6 | 1 | | 11 | | 4 | [Trees and Graphs](chapter_4) | 12 | 12 | | | 12 | | 5 | [Bit Manipulation](chapter_5) | 8 | 8 | | | 13 | | 6 | [Math and Logic Puzzles](chapter_6) | 10 | 10 | 4 | | 14 | | 7 | [OO Design](chapter_7) | 0 | 12 | | | 15 | | 8 | [Recursion and DP](chapter_8) | 14 | 14 | | | 16 | | 9 | [System Design](chapter_9) | 0 | 8 | | See [the System Design Primer](https://github.com/donnemartin/system-design-primer) | 17 | | 10 | [Sorting and Searching](chapter_10) | 9 | 11 | 1 | | 18 | | 11 | [Testing](chapter_11) | 4 | 6 | 4 | | 19 | | 12 | [C/C++](chapter_12) | 11 | 11 | 11 | | 20 | | 13 | [Java](chapter_13) | 0 | 8 | | | 21 | | 14 | [Databases](chapter_14) | 0 | 7 | | | 22 | | 15 | [Threads and Locks](chapter_15) | 0 | 7 | | | 23 | | 16 | [Moderate](chapter_16) | 26 | 26 | 1 | | 24 | | 17 | [Hard](chapter_17) | 26 | 26 | | | 25 | 26 | 27 | ## Running the solutions 28 | 29 | For convenience, the project has a `setup.py` that specifies a package, for local path resolving. 30 | This can be installed locally in the python (virtual)environment by using: 31 | ```bash 32 | pip install -e . 33 | ``` 34 | 35 | Problems can be run by running the file, most should have a main method with some sample input, e.g. : 36 | ```bash 37 | python p16_20.py 38 | ``` 39 | 40 | ## Some highlights 41 | 42 | ### Langton's ant (Chapter 16, Problem 22) 43 | 44 | ![Result pattern](bonus/ant.png) 45 | 46 | An interesting example of emergent patterns from simple rules. Raise the number of iterations in the code to see more of the "highway" form. 47 | For more information, check the [wikipedia link](https://en.wikipedia.org/wiki/Langton%27s_ant). 48 | 49 | ### Tree visualising in VS Code 50 | 51 | ![Example tree](bonus/graphs.png) 52 | 53 | This requires graphviz to be installed locally and the extension added to VS Code. 54 | The one used in the screenshot above can be found [here](https://marketplace.visualstudio.com/items?itemName=tintinweb.graphviz-interactive-preview). 55 | 56 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StBogdan/CTCI_python/3a606f14e57e263b9a6c468da2990d502824a3ef/__init__.py -------------------------------------------------------------------------------- /bonus/ant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StBogdan/CTCI_python/3a606f14e57e263b9a6c468da2990d502824a3ef/bonus/ant.png -------------------------------------------------------------------------------- /bonus/graphs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StBogdan/CTCI_python/3a606f14e57e263b9a6c468da2990d502824a3ef/bonus/graphs.png -------------------------------------------------------------------------------- /bonus/max_perf_tree.py: -------------------------------------------------------------------------------- 1 | from utils.graphs import BiNode, ltbt 2 | 3 | 4 | def maximum_perfect_tree(tree_root: BiNode): 5 | max_now = 0 6 | 7 | def max_perf_tree_depth(root: BiNode) -> int: 8 | nonlocal max_now 9 | if not root: 10 | return 0 11 | 12 | l_max = max_perf_tree_depth(root.left) 13 | r_max = max_perf_tree_depth(root.right) 14 | val_now = 1 + min(l_max, r_max) 15 | 16 | if val_now > max_now: 17 | max_now = val_now 18 | 19 | return val_now 20 | 21 | max_perf_tree_depth(tree_root) 22 | return 2**max_now -1 23 | 24 | 25 | if __name__ == "__main__": 26 | tree_arr = [1] +\ 27 | [2, 3] + \ 28 | [None, 4] + [5, 6] +\ 29 | [None, None] + [None, None] + [7, 8] + [9, 10] 30 | [None] * 14 + [11] 31 | example_root = ltbt(tree_arr) 32 | print(f"Max tree is {maximum_perfect_tree(example_root)}") 33 | -------------------------------------------------------------------------------- /bonus/problem_hard_15_wrong.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from typing import List 3 | 4 | # Wrong, but fun application of tries 5 | 6 | def trie(): 7 | return defaultdict(trie) 8 | 9 | 10 | def build_trie_from_words(words: List[str]) -> dict: 11 | word_trie = trie() 12 | 13 | for word in words: 14 | current_pointer = word_trie 15 | for char in word: 16 | current_pointer = current_pointer[char] 17 | 18 | current_pointer["end"] = True 19 | 20 | return word_trie 21 | 22 | 23 | def get_longest_gestaltwort(words: List[str]): 24 | word_trie = build_trie_from_words(words) 25 | 26 | c_max_len = 0 27 | c_max_word = 0 28 | 29 | for word in words: 30 | if is_composite(word, word_trie): 31 | if len(word) > c_max_len: 32 | c_max_len = len(word) 33 | c_max_word = word 34 | return c_max_word 35 | 36 | 37 | def is_composite(word: str, word_trie: dict) -> bool: 38 | possible_split_pointers = [] 39 | 40 | trie_pointer = word_trie 41 | for c in word: 42 | new_trie_pointer = trie_pointer[c] 43 | 44 | i = 0 45 | while i < len(possible_split_pointers): 46 | trie_pointer_subword = possible_split_pointers[i] 47 | if c not in trie_pointer_subword: 48 | possible_split_pointers.pop(i) 49 | else: 50 | possible_split_pointers[i] = trie_pointer_subword[c] 51 | i += 1 52 | 53 | if "end" in new_trie_pointer: 54 | possible_split_pointers.append(word_trie) 55 | 56 | # print(f"Char: {c} of {word}, current trie elems: {list(trie_pointer[c])}," 57 | # f"\tpossible split pointers {[x.keys() for x in possible_split_pointers]}") 58 | trie_pointer = new_trie_pointer 59 | 60 | return any("end" in x for x in possible_split_pointers) 61 | 62 | 63 | """ 64 | for each word: 65 | check if partition exists 66 | for each char: 67 | if end of word in trie 68 | push to beginning 69 | add to possible split pointers 70 | 71 | for each of possible split pointes: 72 | advance to next letter if possible 73 | if not remove them from possible split pointers 74 | 75 | for possible pointers: 76 | if any is at the end of a word, there is a valid partition 77 | record length against max 78 | """ 79 | 80 | 81 | def sys_word_test(): 82 | with open("/usr/share/dict/words", "r") as system_words_file: 83 | big_list = list(map(lambda word: word.lower(), 84 | system_words_file.read().split("\n"))) 85 | 86 | print(f"Have a total of {len(big_list)} words") 87 | print(f"The longest is: {sorted(big_list, key = len)[-1]}") 88 | 89 | print( 90 | f"For the words, the longest is {get_longest_gestaltwort(big_list[1:])}") 91 | 92 | 93 | if __name__ == "__main__": 94 | words = ["cat", "banana", "dog", "nana", 95 | "walk", "walker", "dogwalker", "catdognana"] 96 | print(f"For the words, the longest is {get_longest_gestaltwort(words)}") 97 | -------------------------------------------------------------------------------- /bonus/spiral_copy.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StBogdan/CTCI_python/3a606f14e57e263b9a6c468da2990d502824a3ef/bonus/spiral_copy.py -------------------------------------------------------------------------------- /chapter_1/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StBogdan/CTCI_python/3a606f14e57e263b9a6c468da2990d502824a3ef/chapter_1/__init__.py -------------------------------------------------------------------------------- /chapter_1/p1_1.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | # Method: Character map 4 | # Time: O(n) 5 | # Space: O(n) 6 | 7 | # Follow-up 8 | # Method: Sort and compare 9 | # Time: O(n*log(n)) 10 | # Space: O(n) 11 | 12 | 13 | def all_uniqs(input_str: str): 14 | ctr = Counter(input_str) 15 | 16 | for k, v in ctr.items(): 17 | if v > 1: 18 | return False 19 | return True 20 | 21 | 22 | def all_uniqs_no_datastruct(x: str): 23 | sorted_x = sorted(x) 24 | n = len(x) 25 | for i in range(1, n): 26 | if sorted_x[i-1] == sorted_x[i]: 27 | return False 28 | return True 29 | 30 | 31 | if __name__ == "__main__": 32 | 33 | inputs = ["", "aaasodkc", "abbccdcss", "aaa", "b", "bdes"] 34 | 35 | for x in inputs: 36 | print( 37 | f"Input {x} result: {all_uniqs(x)} same as {all_uniqs_no_datastruct(x)}") 38 | -------------------------------------------------------------------------------- /chapter_1/p1_2.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | 3 | # Method: Sort and compare 4 | # Time: O(n*log(n)) 5 | # Space: O(n) 6 | 7 | 8 | def check_perm(a: str, b: str): 9 | sa = sorted(a) 10 | sb = sorted(b) 11 | 12 | return sb == sa 13 | 14 | 15 | def check_perm_ctr(a: str, b: str): 16 | ca = Counter(a) 17 | cb = Counter(b) 18 | 19 | for k, v in ca.items(): 20 | if k not in cb: 21 | return False 22 | elif v != cb[k]: 23 | return False 24 | 25 | if any(k not in ca for k in cb): 26 | return False 27 | 28 | return True 29 | 30 | 31 | if __name__ == "__main__": 32 | inputs = [("aa", "ac"), ("", ""), ("saodk", "dkoas"), ("cab", "baac")] 33 | 34 | for a, b in inputs: 35 | print( 36 | f"Bot input {a} and {b}, result {check_perm(a,b)}" 37 | f" == {check_perm_ctr(a,b)}") 38 | -------------------------------------------------------------------------------- /chapter_1/p1_3.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | # Method: Count spaces, move into place from the end 4 | # Time: O(n) 5 | # Space: O(1) 6 | 7 | 8 | def urlize(a: List[chr], true_len): 9 | ei = true_len - 1 10 | 11 | nr_spaces = 0 12 | for i in range(true_len): 13 | if a[i] == " ": 14 | nr_spaces += 1 15 | 16 | ei = true_len - 1 - (nr_spaces//3) * 2 17 | # print(f"Turns out the string starts at {ei}") 18 | end_i = true_len-1 19 | while ei >= 0: 20 | # print(f"Not at {ei} and outputloc {end_i}") 21 | if a[ei] != " ": 22 | a[end_i] = a[ei] 23 | else: 24 | a[end_i] = "0" 25 | a[end_i-1] = "2" 26 | a[end_i-2] = "%" 27 | end_i -= 2 28 | end_i -= 1 29 | ei -= 1 30 | return "".join(a) 31 | 32 | 33 | def get_prob_input(str_w_spaces: str) -> str: 34 | """Add the necessary buffer at the end, for a normal str 35 | """ 36 | spaces = str_w_spaces.count(" ") 37 | return str_w_spaces + " " * (spaces * 2), len(str_w_spaces) + spaces*2 38 | 39 | 40 | if __name__ == "__main__": 41 | example1 = "Taco cat is evil" 42 | a, fn = get_prob_input(example1) 43 | print(f"Input is |{a}|, result |{urlize(list(a), fn)}| ") 44 | 45 | a, fn = get_prob_input(" a") 46 | print(f"Input is |{a}|, result |{urlize(list(a), fn)}| ") 47 | 48 | a, fn = get_prob_input(" a c a c") 49 | print(f"Input is |{a}|, result |{urlize(list(a), fn)}| ") 50 | -------------------------------------------------------------------------------- /chapter_1/p1_4.py: -------------------------------------------------------------------------------- 1 | import string 2 | from collections import Counter 3 | 4 | # Method: Map and check for odds 5 | # Time: O(n) 6 | # Space: O(n) 7 | 8 | # Method: Character bit-map, check set bits 9 | # Time: O(n) 10 | # Space: O(1) (limited by alphabet) 11 | 12 | 13 | def is_palindrom_perm(word: str) -> bool: 14 | clean_w = pre_process(word) 15 | ctr = Counter(clean_w) 16 | odd_c = sum(x % 2 for x in ctr.values()) 17 | return not odd_c > 1 18 | 19 | 20 | def is_palindrome_perm_better(word: str) -> bool: 21 | bs = {c: 0b0 for c in string.ascii_lowercase} 22 | for c in pre_process(word): 23 | bs[c] ^= 0b1 # Flip by xor with one 24 | return not sum(x for x in bs.values()) > 1 25 | 26 | 27 | def pre_process(x: str) -> str: 28 | # x.translate(string.maketrans("","",string.punctuation)) 29 | letter_iter = filter(lambda c: c.isalpha(), x.strip()) 30 | return "".join(map(lambda x: x.lower(), letter_iter)) 31 | 32 | 33 | if __name__ == "__main__": 34 | print(pre_process("a...!>!>!a<<> {is_palindrom_perm(ex)} \ 38 | same as {is_palindrome_perm_better(ex)}") 39 | 40 | ex1 = "A man, a plan, a canal, panama" 41 | printex(ex1) 42 | ex2 = "This is not a plaindrome" 43 | printex(ex2) 44 | ex3 = "aaaaccbbb" 45 | printex(ex3) 46 | -------------------------------------------------------------------------------- /chapter_1/p1_5.py: -------------------------------------------------------------------------------- 1 | # Method: Char by char check, count differences 2 | # Time: O(min(n,m)), where n,m the lenghts of the input strings 3 | # Space: O(1) 4 | 5 | 6 | def is_edit(a: str, b: str) -> bool: 7 | la = len(a) 8 | lb = len(b) 9 | 10 | if la == lb: 11 | return check_subst(a, b) 12 | else: 13 | # Make a shorter 14 | if la > lb: 15 | a, b = b, a 16 | return check_edit(a, b) 17 | 18 | 19 | def check_edit(a: str, b: str) -> bool: 20 | la = len(a) 21 | lb = len(b) 22 | i = 0 23 | j = 0 24 | mismatches = 0 25 | 26 | while i < la and j < lb: 27 | if a[i] == b[j]: 28 | i += 1 29 | else: 30 | mismatches += 1 31 | j += 1 32 | 33 | if mismatches > 1: 34 | return False 35 | 36 | return abs(i-j) <= 1 37 | 38 | 39 | def check_subst(a, b) -> bool: 40 | mistmatches = 0 41 | 42 | for i in range(len(a)): 43 | if a[i] != b[i]: 44 | mistmatches += 1 45 | if mistmatches > 1: 46 | return False 47 | 48 | return True 49 | 50 | 51 | if __name__ == "__main__": 52 | examples = [("pale", "ple"), ("pales", "pale"), 53 | ("pale", "bale"), ("pale", "bake"), ("babaqw", "babaw")] 54 | for a, b in examples: 55 | print(f"For {a} and {b} checking: {is_edit(a,b)}") 56 | -------------------------------------------------------------------------------- /chapter_1/p1_6.py: -------------------------------------------------------------------------------- 1 | # Method: Build new, counting occurences 2 | # Time: O(n) 3 | # Space: O(n) 4 | 5 | 6 | def basic_compr(input_str: str) -> str: 7 | len_compres = 0 8 | len_orig = len(input_str) 9 | res = [] 10 | 11 | prev = None 12 | prev_count = 0 13 | for c in input_str: 14 | if c != prev: 15 | if prev: 16 | res.append(prev) 17 | res.append(str(prev_count)) 18 | len_compres += 1 + len(str(prev_count)) 19 | if len_compres > len_orig: 20 | return input_str 21 | prev = c 22 | prev_count = 1 23 | else: 24 | prev_count += 1 25 | 26 | res.append(prev) 27 | res.append(str(prev_count)) 28 | 29 | len_compres += 1 + len(str(prev_count)) 30 | 31 | return input_str if len_compres > len_orig else "".join(res) 32 | 33 | 34 | if __name__ == "__main__": 35 | exs = ["aaaabbbb", "aaaaaaa", "abcdef", "abcdeeeeeeeeeee"] 36 | 37 | for ex in exs: 38 | print(f"String {ex} compresses in {basic_compr(ex)}") 39 | -------------------------------------------------------------------------------- /chapter_1/p1_7.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | # Method: Recursive edge-walking 4 | # Time: O(n*m), where n,m the dimensions of the matrix 5 | # Space: O(1) 6 | 7 | 8 | def rotate(arr: List[List[int]]) -> List[List[int]]: 9 | rot_help(arr, 0, len(arr[0])-1, 0, len(arr)-1) 10 | return arr 11 | 12 | 13 | def rot_help(arr, xl, xh, yl, yh): 14 | if xl >= xh and yl >= yh: 15 | print(f"Shorting 0 or 1 wide at {xl} to {xh}") 16 | return 17 | else: 18 | rot_help(arr, xl+1, xh-1, yl+1, yh-1) 19 | 20 | side = xh-xl 21 | for i in range(side): 22 | print(f"{i} of {side} side, w/ x:{xl}-{xh} y:{yl}-{yh}") 23 | temp = arr[yl][xl+i] # Top left segment 24 | arr[yl][xl+i] = arr[yh-i][xl] # Trasition 1 25 | arr[yh-i][xl] = arr[yh][xh-i] # Transition 2 26 | arr[yh][xh-i] = arr[yl+i][xh] # Transition 3 27 | arr[yl+i][xh] = temp # Top right end 28 | 29 | 30 | if __name__ == "__main__": 31 | ex1 = [[1, 2, 3], 32 | [8, 0, 4], 33 | [7, 6, 5]] 34 | rotate(ex1) 35 | for line in ex1: 36 | print(line) 37 | 38 | ex2 = [[1, 2], [3, 4]] 39 | rotate(ex2) 40 | for line in ex2: 41 | print(line) 42 | -------------------------------------------------------------------------------- /chapter_1/p1_8.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | # Method: Individual cell checks, with handling of first line and col 4 | # Time: O(n*m) 5 | # Space: O(1) 6 | 7 | 8 | def zero_m(matric: List[List[int]]): 9 | rows, cols = len(matric), len(matric[0]) 10 | fcol0 = False 11 | frow0 = False 12 | 13 | for r in range(rows): 14 | if matric[r][0] == 0: 15 | fcol0 = True 16 | break 17 | 18 | for c in range(cols): 19 | if matric[0][c] == 0: 20 | frow0 = True 21 | break 22 | 23 | for r in range(rows): 24 | for c in range(cols): 25 | if matric[r][c] == 0: 26 | matric[r][0] = 0 27 | matric[0][c] = 0 28 | 29 | print(f"Gonna zero now, first row {frow0} and first col {fcol0}") 30 | mrint(matric) 31 | 32 | for r in range(1, rows): 33 | if matric[r][0] == 0: 34 | for c in range(cols): 35 | matric[r][c] = 0 36 | 37 | for c in range(1, cols): 38 | if matric[0][c] == 0: 39 | for r in range(rows): 40 | matric[r][c] = 0 41 | 42 | if fcol0: 43 | for r in range(rows): 44 | matric[r][0] = 0 45 | 46 | if frow0: 47 | for c in range(cols): 48 | matric[0][c] = 0 49 | 50 | return matric 51 | 52 | 53 | def mrint(matrix: List[List[int]]): 54 | for line in matrix: 55 | print(line) 56 | 57 | 58 | if __name__ == "__main__": 59 | ex1 = [[1, 2, 3], 60 | [8, 0, 4], 61 | [7, 6, 5]] 62 | 63 | mrint(zero_m(ex1)) 64 | print("-"*50) 65 | ex2 = [[0, 1, 2], 66 | [4, 5, 6]] 67 | 68 | mrint(zero_m(ex2)) 69 | -------------------------------------------------------------------------------- /chapter_1/p1_9.py: -------------------------------------------------------------------------------- 1 | # Method: Attach to itself 2 | # Time: O(n) (create new string, potentially substring check) 3 | # Space: O(n) (store the new big string) 4 | 5 | 6 | def checkrot(s1: str, s2: str): 7 | s22 = s2 + s2 8 | return isSubstr(s1, s22) and len(s1) == len(s2) 9 | 10 | 11 | def isSubstr(s1: str, s2: str) -> bool: 12 | return s1 in s2 13 | 14 | 15 | if __name__ == "__main__": 16 | ex1 = ("abc", "cab") 17 | ex2 = ("panama", "ampaan") 18 | ex3 = ("ab", "abc") 19 | 20 | for a, b in (ex1, ex2, ex3): 21 | print(f"Is {a} rot of {b} ? {checkrot(a,b)}") 22 | -------------------------------------------------------------------------------- /chapter_1/readme.md: -------------------------------------------------------------------------------- 1 | # Chapter 1: Arrays and Strings 2 | 3 | The complexity table is for a quick lookup, more details in the actual file (e.g. explaining the symbols used) 4 | 5 | | Nr. | Title | Solution | Time | Space | Notes | 6 | |:---: |:-----: |:--------: |:----: |:-----: |:-----: | 7 | | 1.1 | Is unique | [Python](./p1_1.py) | O(n) | O(n) | | 8 | | 1.2 | Check Permutation | [Python](./p1_2.py) | O(n*log(n)) | O(n) | | 9 | | 1.3 | URLify | [Python](./p1_3.py) | O(n) | O(1) | | 10 | | 1.4 | Palindrome Permutation | [Python](./p1_4py) | O(n) | O(1) | | 11 | | 1.5 | One Away | [Python](./p1_5.py) | O(min(n,m)) | O(1) | | 12 | | 1.6 | String Compression | [Python](./p1_6.py) | O(n) | O(n) | | 13 | | 1.7 | Rotate Matrix | [Python](./p1_7.py) | O(n*m) | O(1) | | 14 | | 1.8 | Zero Matrix | [Python](./p1_8.py) | O(n*m) | O(1) | | 15 | | 1.9 | String Rotation | [Python](./p1_9.py) | O(n) | O(n) | | 16 | -------------------------------------------------------------------------------- /chapter_10/p10_10.py: -------------------------------------------------------------------------------- 1 | from utils.graphs import BiNode 2 | from utils.treeviz import viz_tree 3 | 4 | 5 | class SizedNode(BiNode): 6 | def __init__(self, val: int): 7 | super().__init__(val) 8 | self.size = 1 9 | 10 | def insert(self, x: int): 11 | if x > self.val: 12 | if self.right: 13 | self.right.insert(x) 14 | else: 15 | self.right = SizedNode(x) 16 | else: 17 | if self.left: 18 | self.left.insert(x) 19 | else: 20 | self.left = SizedNode(x) 21 | self.size += 1 22 | 23 | 24 | class NumberTracker: 25 | 26 | def __init__(self): 27 | self.root = None 28 | 29 | def track(self, x: int): 30 | if not self.root: 31 | self.root = SizedNode(x) 32 | else: 33 | self.root.insert(x) 34 | 35 | def getRankOfNumber(self, x: int) -> int: 36 | return self._rank_helper(self.root, x) 37 | 38 | @staticmethod 39 | def _rank_helper(node: SizedNode, val: int): 40 | if not node: 41 | return 0 42 | size_left = node.left.size if node.left else 0 43 | size_right = node.right.size if node.right else 0 44 | 45 | if val == node.val: 46 | return size_left 47 | elif val < node.val: 48 | return NumberTracker._rank_helper(node.left, val) 49 | else: 50 | # This subtree, minus the elements in the right one (which we will fill in) 51 | return node.size - size_right + NumberTracker._rank_helper(node.right, val) 52 | 53 | 54 | if __name__ == "__main__": 55 | stream = [5, 1, 4, 4, 5, 9, 7, 13, 3] 56 | nt = NumberTracker() 57 | for x in stream: 58 | nt.track(x) 59 | 60 | viz_tree(nt.root) 61 | target_nums = [1, 3, 4, 5, 14] 62 | for num in target_nums: 63 | print(f"Rank of number {num} is {nt.getRankOfNumber(num)}") 64 | -------------------------------------------------------------------------------- /chapter_10/p10_11.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | def peaks_and_valeys(arr: List[int]) -> List[int]: 4 | for i in range(len(arr)-2): 5 | # print(f"looking at {arr[i:i+3]}") 6 | if arr[i] >= arr[i+1]: 7 | if arr[i+2] < arr[i+1]: 8 | arr[i+1], arr[i+2] = arr[i+2], arr[i+1] 9 | else: 10 | if arr[i+1] < arr[i+2]: 11 | arr[i+1], arr[i+2] = arr[i+2], arr[i+1] 12 | return arr 13 | 14 | if __name__ == "__main__": 15 | exs = [[1,2,4,5,6,7], [7,6,5,4,3,2,1], [9,10,4,8,7]] 16 | for ex in exs: 17 | print(f"Peaked arr {ex}\tis\t{peaks_and_valeys(ex)}") -------------------------------------------------------------------------------- /chapter_10/p10_2.py: -------------------------------------------------------------------------------- 1 | from collections import Counter, defaultdict 2 | from typing import List 3 | 4 | 5 | def group_anag(arr: List[str]): 6 | buckets = defaultdict(list) 7 | res = [] 8 | 9 | for a in arr: 10 | elems_a = Counter(a) 11 | id_a = tuple(sorted(elems_a.items())) 12 | buckets[id_a].append(a) 13 | 14 | for id_str, ang_list in buckets.items(): 15 | # print(ang_list, end=" --- ") 16 | res.extend(ang_list) 17 | # print() 18 | return res 19 | 20 | 21 | if __name__ == "__main__": 22 | example1 = ["aabb", "cabt", "baab", "tbac", "bbaa", "tbba"] 23 | example2 = ["cccc", "ddddd", "avca", "vcaa", "wwqwq", "aacv"] 24 | print(group_anag(example1)) 25 | print(group_anag(example2)) 26 | -------------------------------------------------------------------------------- /chapter_10/p10_3.py: -------------------------------------------------------------------------------- 1 | def search_rot_arr(arr_rotated, num): 2 | n = len(arr_rotated) 3 | 4 | if not arr_rotated: 5 | return -1 6 | elif n == 1: 7 | return 0 if arr_rotated[0] == num else -1 8 | elif arr_rotated[0] < arr_rotated[-1]: 9 | return binary_search(arr_rotated, 0, n-1, num) 10 | 11 | pivot_index = find_pivot_index(arr_rotated) 12 | res_index = -1 13 | 14 | print(f"Found pivot index: {pivot_index}") 15 | 16 | search_start, search_end = \ 17 | (pivot_index+1, n-1) if num <= arr_rotated[-1] else (0, pivot_index) 18 | return binary_search(arr_rotated, search_start, search_end, num) 19 | 20 | 21 | def binary_search(arr, s, e, target): 22 | low = s 23 | high = e 24 | while low <= high: 25 | mid = (low + high) // 2 26 | 27 | if arr[mid] == target: 28 | return mid 29 | elif arr[mid] > target: 30 | high = mid-1 31 | else: 32 | low = mid+1 33 | 34 | return -1 35 | 36 | 37 | def find_pivot_index(arr): 38 | """Addresable index of the last element before pivot""" 39 | low = 0 40 | high = len(arr)-1 41 | 42 | while low <= high: 43 | mid = (low+high) // 2 # Divide and floor 44 | 45 | if mid == len(arr): 46 | return 0 47 | 48 | if arr[mid+1] < arr[mid]: 49 | return mid 50 | 51 | if arr[mid] > arr[-1]: 52 | low = mid+1 53 | else: 54 | high = mid - 1 55 | 56 | return 0 57 | 58 | 59 | if __name__ == "__main__": 60 | exs = [([2], 2), 61 | ([1, 2], 2), 62 | ([0, 1, 2, 3, 4, 5], 1), 63 | ([9, 12, 17, 2, 4, 5], 17), 64 | ([9, 12, 17, 2, 4, 5, 6], 4)] 65 | for arr, target in exs: 66 | print( 67 | f"Looking for {target} in {arr}, found location ? {search_rot_arr(arr, target)}") 68 | -------------------------------------------------------------------------------- /chapter_10/p10_4.py: -------------------------------------------------------------------------------- 1 | class Listy: 2 | def __init__(self, arr): 3 | self.arr = arr 4 | 5 | def elemAt(self, index: int): 6 | return self.arr[index] if index < len(self.arr) else -1 7 | 8 | def search_unsized_list(arr: Listy, target: int) -> int: 9 | # Lenght is not known 10 | ind = 0 11 | low = 0 12 | while arr.elemAt(ind) != -1 and arr.elemAt(ind) <= target: 13 | ce = arr.elemAt(ind) 14 | low = ind 15 | if not ind: 16 | ind = 1 17 | else: 18 | ind *= 2 19 | 20 | high = ind 21 | while low <= high: 22 | # print(f"Looking at low and high {low} and {high}") 23 | mid = (low + high) // 2 24 | ce = arr.elemAt(mid) 25 | if ce == target: 26 | return mid 27 | elif ce == -1 or ce > target: 28 | high = mid - 1 29 | else: 30 | low = mid + 1 31 | return -1 32 | 33 | 34 | if __name__ == "__main__": 35 | exs = [([1, 2, 3, 4, 5, 6, 7, 8], 10), 36 | ([12, 13, 14, 15], 15), 37 | (list(range(62)), 61)] 38 | for arr, target in exs: 39 | listy_arr = Listy(arr) 40 | print( 41 | f"Looking for {target} in {arr}, found location ?" 42 | f" {search_unsized_list(listy_arr, target)}") 43 | -------------------------------------------------------------------------------- /chapter_10/p10_5.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def sparse_search(warr: List[str], target): 5 | low = 0 6 | high = len(warr) - 1 7 | while low <= high: 8 | mid = (low + high) // 2 9 | 10 | # Go right until limit or non-empty 11 | while mid < high and not warr[mid]: 12 | mid += 1 13 | 14 | if warr[mid] == target: 15 | return mid 16 | elif (not warr[mid]) or warr[mid] > target: 17 | # In case that right side is empty, look left 18 | high = mid-1 19 | else: 20 | low = mid+1 21 | return -1 22 | 23 | 24 | if __name__ == "__main__": 25 | test_arr = ['b', '', '', 'car', '', '', 'tar', '', '', '', '', 'zar'] 26 | exs = [(test_arr, 'zar'), 27 | (test_arr, 'car'), 28 | (test_arr, "potato"), 29 | (test_arr, 'b'), 30 | ([''], "potato"), 31 | (['', 'aaa', ''], 'aaa'), 32 | (['aaa'], 'aaa')] 33 | for arr, target in exs: 34 | print(f"Position of {target}\tis {sparse_search(arr,target)}\tin {arr}") 35 | -------------------------------------------------------------------------------- /chapter_10/p10_8.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def find_duplicates(arr: List[int]) -> None: 5 | WORD_SIZE = 32 # Bytes 6 | bit_memory = [0b0] * 1000 # Assume 32 bit integers 7 | 8 | for x in arr: 9 | byte_target = x // WORD_SIZE 10 | inbyte_target = x % WORD_SIZE 11 | is_set = (bit_memory[byte_target] >> inbyte_target) & 0b1 12 | 13 | if is_set: 14 | print(x) 15 | else: 16 | bit_memory[byte_target] |= (0b1 << inbyte_target) 17 | 18 | 19 | if __name__ == "__main__": 20 | arr = [31999, 1, 2, 7, 4, 5, 7, 30, 31999, 2, 2, 2, 1] 21 | find_duplicates(arr) 22 | -------------------------------------------------------------------------------- /chapter_10/p10_9.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional, Tuple 2 | import typing 3 | 4 | 5 | # Method: Search space reduction 6 | # Time: O(m + n), where m, n dimensions of input matrix 7 | # Space: O(1) 8 | 9 | def get_at(arr: List[List[int]], point: Tuple[int, int]) -> int: 10 | px, py = point 11 | return arr[px][py] 12 | 13 | 14 | def matrix_search(arr: List[List[int]], target: int) -> Optional[tuple]: 15 | rows = len(arr) 16 | cols = len(arr[0]) 17 | 18 | row_now = rows-1 19 | col_now = 0 20 | while 0 < row_now and col_now < cols: 21 | elem_now = get_at(arr, (row_now, col_now)) 22 | # print(f"Looking at {row_now:3}:{col_now:3}, of elem {elem_now}") 23 | 24 | if elem_now == target: 25 | return row_now, col_now 26 | elif elem_now < target: 27 | col_now += 1 28 | else: 29 | row_now -= 1 30 | 31 | 32 | # The following functions were used in the old version (looking in quadrants) 33 | # An example of the Master theorem for figuring out complexity 34 | 35 | 36 | def matrix_bin_search_quadrants(arr: List[List[int]], t: int) -> Optional[tuple]: 37 | n = len(arr)-1 38 | m = len(arr[0])-1 39 | return mbs(arr, 0, n, 0, m, t) 40 | 41 | 42 | def check_square(matrix, x_low, x_high, y_low, y_high, target) -> Optional[tuple]: 43 | checking_positions = [(x, y) for x in (x_low, x_high) 44 | for y in (y_low, y_high)] 45 | for poz in checking_positions: 46 | if get_at(matrix, poz) == target: 47 | return poz 48 | 49 | 50 | def mbs(matrix, 51 | x_low, x_high, 52 | y_low, y_high, 53 | target): 54 | print( 55 | f"Looking within [x:{x_low}, y: {y_low}]\t-> [x:{x_high}, y:{y_high}]") 56 | 57 | if x_low > x_high or y_low > y_high: 58 | return None 59 | 60 | x_mid = (x_low + x_high) // 2 61 | y_mid = (y_low + y_high) // 2 62 | 63 | up_point = (x_low, y_mid) 64 | left_point = (x_mid, y_low) 65 | 66 | mid_point = (x_mid, y_mid) 67 | 68 | if target == get_at(matrix, up_point): 69 | return up_point 70 | elif target == get_at(matrix, left_point): 71 | return left_point 72 | elif target == get_at(matrix, mid_point): 73 | return mid_point 74 | 75 | # Lower right 76 | if target > get_at(matrix, mid_point): 77 | # print(f"Going low right with new mid-mid-point {mmp}") 78 | if mid_point[0] == x_low and mid_point[1] == y_low: 79 | return check_square(matrix, x_low, x_high, y_low, y_high, target) 80 | 81 | return mbs(matrix, 82 | mid_point[0], x_high, 83 | mid_point[1], y_high, 84 | target) 85 | 86 | # Top right and lower left 87 | if target > get_at(matrix, up_point): 88 | print("Going top left") 89 | r1 = mbs(matrix, x_low, mid_point[0]-1, 90 | up_point[1], y_high, target) 91 | if r1: 92 | return r1 93 | if target > get_at(matrix, left_point): 94 | print("Going bot right") 95 | r2 = mbs(matrix, left_point[0], x_high, 96 | y_low, mid_point[1]-1, target) 97 | 98 | if r2: 99 | return r2 100 | 101 | # Top left 102 | # print("Going top left") 103 | if not (target > get_at(matrix, up_point) or target > get_at(matrix, left_point)): 104 | return mbs(matrix, x_low, mid_point[0]-1, y_low, mid_point[1]-1, target) 105 | 106 | 107 | if __name__ == "__main__": 108 | example1 = [[0, 5, 6, 7, 8], 109 | [1, 6, 7, 8, 9], 110 | [2, 10, 15, 12, 21], 111 | [3, 11, 16, 21, 22], 112 | [4, 12, 20, 70, 90]] 113 | 114 | print(matrix_bin_search_quadrants(example1, 21), matrix_search(example1, 21)) 115 | -------------------------------------------------------------------------------- /chapter_16/p16_1.py: -------------------------------------------------------------------------------- 1 | def swap_in_place(a, b): 2 | b = a ^ b # After this "b" = a^b "a" = a 3 | a = b ^ a # After this "b" = a^b "a" = a^b^a = b 4 | b = b ^ a # After this "b" = a^b^b=a "a" =b 5 | # Or just do a,b = b,a 6 | return a, b 7 | 8 | 9 | if __name__ == "__main__": 10 | a, b = -2, 3 11 | print(f"Swapping {a} and {b} is {swap_in_place(a,b)}") 12 | -------------------------------------------------------------------------------- /chapter_16/p16_10.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, List 2 | 3 | 4 | def living_people(ppl: List[Tuple[int, int]]) -> int: 5 | year_now = 0 6 | max_pop = 0 7 | max_pop_year = 0 8 | 9 | births, deaths = zip(*ppl) 10 | births = list(births) 11 | deaths = list(deaths) 12 | 13 | births.sort() 14 | deaths.sort() 15 | 16 | in_i = 0 17 | out_i = 0 18 | now_pop = 0 19 | 20 | while in_i < len(births): 21 | year_now = births[in_i] 22 | 23 | # All deaths up to this year (excl) 24 | while out_i < len(deaths) and deaths[out_i] < year_now: 25 | max_pop -= 1 26 | out_i += 1 27 | 28 | now_pop += 1 29 | if now_pop > max_pop: 30 | max_pop = now_pop 31 | max_pop_year = births[in_i] 32 | in_i += 1 33 | 34 | return max_pop_year 35 | 36 | 37 | if __name__ == "__main__": 38 | ex = [(1900, 2000), (1910, 1920), (1944, 1944), (1944, 1944), 39 | (1934, 1944)] 40 | ex2 = [(12, 15), (20, 90), (10, 98), (1, 72), (10, 98), 41 | (23, 82), (13, 98), (90, 98), (83, 99), (75, 94)] 42 | 43 | print(f"Most living ppl in the list are in year {living_people(ex2)}") 44 | -------------------------------------------------------------------------------- /chapter_16/p16_11.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def diving_board_lengths(k: int, longer: int, shorter: int) -> List[int]: 5 | len_now = shorter * k 6 | ans = [len_now] 7 | 8 | if shorter == longer: 9 | return ans 10 | 11 | for i in range(k): 12 | len_now += longer - shorter 13 | ans.append(len_now) 14 | 15 | return ans 16 | 17 | 18 | if __name__ == "__main__": 19 | exs = [[10, 10, 5], [10, 10, 10], [10, 1, 2]] 20 | for k, longer, shorter in exs: 21 | print(f"Ways to measure {k} planks" 22 | f" from {longer} and {shorter}" 23 | f" are: {diving_board_lengths(k,longer,shorter)} ") 24 | -------------------------------------------------------------------------------- /chapter_16/p16_13.py: -------------------------------------------------------------------------------- 1 | from collections import namedtuple 2 | from typing import List, Tuple 3 | 4 | epsilon = 0.000001 5 | 6 | Point = namedtuple("Point", "x y") 7 | 8 | 9 | class Square: 10 | 11 | def __init__(self, topleft: Point, topright: Point, botleft: Point, botright: Point): 12 | self.top_left = topleft 13 | self.top_right = topright 14 | self.bot_left = botleft 15 | self.bot_right = botright 16 | 17 | def get_middle(self): 18 | return Point( 19 | (self.top_left.x + self.top_right.x) / 2, 20 | (self.top_left.y + self.bot_left.y) / 2) 21 | 22 | def __str__(self): 23 | return f"Square({self.top_left} to {self.bot_right})" 24 | 25 | 26 | def get_line_from_points(p1: Point, p2: Point): 27 | is_vertical = False 28 | slope = 0 29 | if p1 == p2: 30 | raise Exception("Cannot get one line for 2 of the same point") 31 | 32 | if p1.x != p2.x: 33 | slope = (p1.y - p2.y) / (p1.x - p2.x) 34 | y_intercept = p1.y - slope * p1.x 35 | else: 36 | is_vertical = True 37 | y_intercept = p1.x # Not acting as y_intercept 38 | 39 | return (slope, y_intercept, is_vertical) 40 | 41 | 42 | def get_line_intersection(line1, line2): 43 | l1_slope, l1_offset, l1_vert = line1 44 | l2_slope, l2_offset, l2_vert = line2 45 | 46 | if (not (l1_vert or l2_vert)): 47 | if l1_slope == l2_slope and l2_offset != l1_offset: 48 | return None 49 | 50 | intersect_x = (l2_offset - l1_offset) / (l1_slope - l2_slope) 51 | intersect_y = intersect_x * l1_slope + l1_offset 52 | 53 | return Point(intersect_x, intersect_y) 54 | elif l1_vert and l2_vert: 55 | if l1_offset != l2_offset: 56 | print("Got two verticals, but not at the same place") 57 | return None 58 | else: 59 | return (l1_offset, 0) # Good as any 60 | else: 61 | vline_x = l1_offset if l1_vert else l2_offset 62 | other_slope, other_offset, _ = line2 if l1_vert else line1 63 | 64 | isect_y = vline_x * other_slope + other_offset 65 | 66 | return Point(vline_x, isect_y) 67 | 68 | 69 | def point_in_segment(p: Point, segment: Tuple[Point, Point]) -> bool: 70 | x_points = sorted([segment[0].x, segment[1].x]) 71 | y_points = sorted([segment[0].y, segment[1].y]) 72 | 73 | return x_points[0] - epsilon <= p.x <= x_points[1] + epsilon\ 74 | and y_points[0] - epsilon <= p.y <= y_points[1] + epsilon 75 | 76 | 77 | def get_intesection_points(sq: Square, mid_line: tuple) -> List[Point]: 78 | slope, offset, vertical = mid_line 79 | 80 | sides = ((sq.top_left, sq.top_right), (sq.top_left, sq.bot_left), 81 | (sq.bot_right, sq.bot_left), (sq.bot_right, sq.top_right)) 82 | intercepts = [] 83 | for sq_p1, sq_p2 in sides: 84 | side_line = get_line_from_points(sq_p1, sq_p2) 85 | intercept = get_line_intersection(side_line, mid_line) 86 | # print(f"Got possible intercept {intercept}") 87 | if point_in_segment(intercept, (sq_p1, sq_p2)): 88 | intercepts.append(intercept) 89 | 90 | return intercepts 91 | 92 | 93 | def find_square_halving_line_points(s1: Square, s2: Square): 94 | # Returns the points at which the line splitting both squares into half, 95 | # Intersects the squares 96 | mid1 = s1.get_middle() 97 | mid2 = s2.get_middle() 98 | 99 | if mid1 == mid2: 100 | raise Exception("The two squares have the same middle point") 101 | 102 | mid_line = get_line_from_points(mid1, mid2) 103 | 104 | icept_points = get_intesection_points( 105 | s1, mid_line) + get_intesection_points(s2, mid_line) 106 | # print(icept_points) 107 | return max(icept_points), min(icept_points) 108 | 109 | 110 | if __name__ == "__main__": 111 | exs = [ 112 | (Square(Point(2, 5), Point(6, 5), Point(2, 1), Point(6, 1)), 113 | Square(Point(7, 8), Point(9, 8), Point(7, 6), Point(9, 6))), 114 | 115 | (Square(Point(2, 5), Point(6, 5), Point(2, 1), Point(6, 1)), 116 | Square(Point(5, 3), Point(6, 3), Point(5, 2), Point(6, 2))), 117 | ] 118 | 119 | for sq1, sq2 in exs: 120 | print(f"For the squares {sq1} and {sq2}," 121 | f" the mid line has intersections {find_square_halving_line_points(sq1, sq2)}" 122 | f" same as {find_square_halving_line_points(sq2, sq1)}") 123 | print("-"*50) 124 | -------------------------------------------------------------------------------- /chapter_16/p16_14.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import List, Tuple 3 | from collections import defaultdict, namedtuple 4 | import math 5 | 6 | Point = namedtuple("Point", "x y") 7 | 8 | 9 | def get_line_eq(p1, p2): 10 | is_vertical = False 11 | if p1.x == p2.x and p1.y == p2.y: 12 | raise Exception("Can't get a line for one point") 13 | 14 | y_diff = p1.y - p2.y 15 | x_diff = p1.x - p2.x 16 | slope = None 17 | 18 | if y_diff == 0: # Horiz line 19 | slope = 0 20 | offset = p1.y 21 | elif x_diff == 0: 22 | is_vertical = True 23 | offset = p1.x 24 | else: 25 | slope = x_diff / y_diff 26 | offset = p1.y - slope * p1.x 27 | 28 | return slope, offset, is_vertical 29 | 30 | 31 | def get_most_popular_line(lines: dict, epsilon: float) -> Tuple[tuple, int]: 32 | lines_list = list(lines.keys()) 33 | lines_len = len(lines_list) 34 | 35 | # Check every pair of lines 36 | # #for differences within the epsilon threshold 37 | for i in range(lines_len-1): 38 | for j in range(i+1, lines_len): 39 | l1_slope, l1_offset, l1_vertical = lines_list[i] 40 | l2_slope, l2_offset, l2_vertical = lines_list[j] 41 | 42 | offset_is_close = math.isclose( 43 | l1_offset, l2_offset, abs_tol=epsilon) 44 | 45 | if l1_vertical != l2_vertical: 46 | continue 47 | elif l1_vertical == l2_vertical and l2_vertical: 48 | if offset_is_close: 49 | lines[lines_list[i]] += lines[lines_list[j]] 50 | else: 51 | if math.isclose(l1_slope, l2_slope, abs_tol=epsilon) and \ 52 | offset_is_close: 53 | lines[lines_list[i]] += lines[lines_list[j]] 54 | return max(lines.items(), key=lambda x: x[1]) 55 | 56 | 57 | def find_line_with_most_points(points: List[Point]) -> tuple: 58 | line_occ_dict = defaultdict(int) 59 | epsilon = 0.0001 60 | 61 | for i in range(len(points)-1): 62 | for j in range(i+1, len(points)): 63 | if points[i] == points[j]: 64 | continue 65 | 66 | line_now = get_line_eq(points[i], points[j]) 67 | line_occ_dict[line_now] += 1 68 | 69 | return get_most_popular_line(line_occ_dict, epsilon) 70 | 71 | 72 | if __name__ == "__main__": 73 | exs = [ 74 | [Point(1, 1), Point(4, 1), Point(8, 3), 75 | Point(3, 3), Point(1, 5), Point(2, 2), Point(4, 7)], 76 | [Point(10, 15), Point(10, 16), 77 | Point(10, 17), Point(11, 15), Point(12, 12)], 78 | ] 79 | 80 | for ex in exs: 81 | line, point_count = find_line_with_most_points(ex) 82 | print(f"Line with max points is {line}, with {point_count} points") 83 | -------------------------------------------------------------------------------- /chapter_16/p16_15.py: -------------------------------------------------------------------------------- 1 | 2 | def master_mind_check(answer: str, guess: str): 3 | hits = 0 4 | pseudo_hits = 0 5 | 6 | unhit_colours_ans = set() 7 | unhit_colours_guess = set() 8 | for i in range(len(answer)): 9 | if answer[i] == guess[i]: 10 | hits += 1 11 | else: 12 | unhit_colours_ans.add(answer[i]) 13 | unhit_colours_guess.add(guess[i]) 14 | 15 | pseudo_hits = len(unhit_colours_ans & unhit_colours_guess) 16 | return hits, pseudo_hits 17 | 18 | 19 | if __name__ == "__main__": 20 | exs = [ 21 | ("RGBY", "GGRR"), 22 | ("RGBY", "RBGY"), 23 | ("RRYY", "RYGY") 24 | ] 25 | for ans, guess in exs: 26 | print( 27 | f"For ans {ans}, guess {guess} has hits, pseudos: {master_mind_check(ans,guess)}") 28 | -------------------------------------------------------------------------------- /chapter_16/p16_16.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple 2 | 3 | 4 | def get_left_sorted(arr): 5 | # Find left side, returns first index after 6 | left_max, end_left = arr[0], 0 7 | 8 | while end_left < len(arr) and arr[end_left] >= left_max: 9 | left_max = arr[end_left] 10 | end_left += 1 11 | # print(f"End left is {end_left}, {arr[:end_left+1]}") 12 | return end_left 13 | 14 | 15 | def get_right_sorted(arr): 16 | # Find right side 17 | start_right = len(arr)-1 18 | right_min = arr[-1] 19 | while start_right > 0 and arr[start_right] <= right_min: 20 | right_min = arr[start_right] 21 | start_right -= 1 22 | # print(f"Start right is {start_right} , {arr[start_right:]}") 23 | return start_right 24 | 25 | 26 | def find_sub_sort(arr: List[int]) -> Tuple[int, int]: 27 | # arr is: left | middle | right 28 | n = len(arr) 29 | 30 | end_left = get_left_sorted(arr) 31 | start_right = get_right_sorted(arr) 32 | 33 | if end_left == n: # Array already sorted 34 | return (0, 0) 35 | 36 | # Calc middle 37 | mid_min = float("inf") 38 | mid_max = -float("inf") 39 | 40 | for i in range(end_left, start_right+1): 41 | mid_min = min(mid_min, arr[i]) 42 | mid_max = max(mid_max, arr[i]) 43 | # print( 44 | # f"For the mid section {arr[end_left:start_right+1]}," 45 | # f" min {mid_min}, max {mid_max}") 46 | 47 | # Expand middle leftwards 48 | while end_left > 0 and arr[end_left-1] > mid_min: 49 | end_left -= 1 50 | # print(f"Expanded left now to: {end_left}") 51 | 52 | # Expand middle rightwards 53 | while start_right < n-1 and arr[start_right+1] < mid_max: 54 | start_right += 1 55 | # print(f"Expanded right now to: {start_right}") 56 | 57 | return (end_left, start_right) 58 | 59 | 60 | if __name__ == "__main__": 61 | exs = [ 62 | [1, 2, 4, 7, 10, 11, 7, 12, 6, 7, 16, 18, 19], 63 | [1, 2, 3, 4, 5], 64 | [7, 12, 3, 4, 5, 6, 0] 65 | ] 66 | for arr in exs: 67 | print( 68 | f"To sort {arr}, we need to sort sub arr between {find_sub_sort(arr)} ") 69 | -------------------------------------------------------------------------------- /chapter_16/p16_17.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def largest_sum_cseq(arr: List[int]): 5 | max_now = 0 6 | sum_now = 0 7 | 8 | for x in arr: 9 | sum_now += x 10 | if sum_now > max_now: 11 | max_now = sum_now 12 | 13 | if sum_now < 0: 14 | sum_now = 0 15 | 16 | return max_now 17 | 18 | 19 | if __name__ == "__main__": 20 | exs = [([2, -8, 3, -2, 4, -10], 5), 21 | ([1, 2, 3, 4, 5, 6], 21), 22 | ([-10, 100, -5, -5, -5, -5, -5, 1, 2, 3, 4, 25, -50], 110)] 23 | 24 | for arr, target in exs: 25 | print(f"Largest sum in {arr} is {largest_sum_cseq(arr)}," 26 | f" expected {target}") 27 | -------------------------------------------------------------------------------- /chapter_16/p16_18.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def get_posib_combo(pattern_str: str, n: int) -> List[tuple]: 5 | ca = pattern_str.count('a') 6 | cb = pattern_str.count('b') 7 | 8 | if cb == 0: 9 | if n % ca != 0: 10 | return [] 11 | else: 12 | return [(n//ca,0)] 13 | 14 | posibs = [] 15 | for i in range(0, n//ca + 1): 16 | if (n-(ca*i)) % cb == 0: 17 | posibs.append((i, (n - ca*i) // cb)) 18 | return posibs 19 | 20 | 21 | def check_posib(text, pattern, ca, cb): 22 | # print(f"Checking pos {ca} and {cb}") 23 | a = text[:ca] 24 | b = None 25 | 26 | p_i = 1 27 | t_i = ca 28 | while p_i < len(pattern) and t_i < len(text): 29 | if pattern[p_i] == 'a': 30 | if text[t_i: t_i + ca] != a: 31 | # print(f"Failed at check {text[t_i: t_i + ca]} agains a: {a}") 32 | return False 33 | else: 34 | t_i += ca 35 | else: 36 | if not b: 37 | b = text[t_i: t_i + cb] 38 | t_i += cb 39 | else: 40 | if text[t_i: t_i + cb] != b: 41 | # print(f"Failed at check {text[t_i: t_i + cb]} agains b: {b}") 42 | return False 43 | else: 44 | t_i += cb 45 | p_i += 1 46 | # print(f"Ending todays check with {p_i} of {len(pattern)} and {t_i} of {len(text)}") 47 | return p_i == len(pattern) and t_i == len(text) 48 | 49 | 50 | def flip_pattern(p): 51 | if p[0] == 'b': 52 | return p.translate(str.maketrans("ab", "ba", "")) 53 | else: 54 | return p 55 | 56 | 57 | def check_pattern_match(text: str, pattern: str) -> bool: 58 | pattern = flip_pattern(pattern) 59 | # print(f"Flipped pattern is : {pattern}") 60 | posibs = get_posib_combo(pattern, len(text)) 61 | # print(f"Looking at combos pattern is : {posibs}") 62 | 63 | return any(check_posib(text, pattern, ca, cb) for ca, cb in posibs) 64 | 65 | 66 | if __name__ == "__main__": 67 | exs = [("catcatgocatgo", "aabab"), 68 | ("catcatgocatgo", "bbaba"), 69 | ("catcatgocatgo", "b"), 70 | ("catcatgjocatgo", "aa"), 71 | ("catcatgocatgo", "ba"), 72 | ("catcat", "aaa"), 73 | ("catcat", "bb"),("a","bb")] 74 | for text, pattern in exs: 75 | print(f"'{text}' fits patterns '{pattern}'' ? {check_pattern_match(text, pattern)}") 76 | print("-"*50) 77 | -------------------------------------------------------------------------------- /chapter_16/p16_19.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from typing import List 4 | 5 | 6 | def traverse_pond(matrix: List[List[int]], poz: tuple) -> int: 7 | x, y = poz 8 | n = len(matrix) 9 | m = len(matrix[0]) 10 | 11 | pond_size = 1 12 | 13 | matrix[x][y] = -1 14 | 15 | # Look in all directions 16 | for dx in (-1, 0, 1): 17 | for dy in (-1, 0, 1): 18 | new_x = x + dx 19 | new_y = y + dy 20 | 21 | # In in bounds, water and not visited, go there 22 | if 0 <= new_x < n and 0 <= new_y < m \ 23 | and matrix[new_x][new_y] == 0: 24 | pond_size += traverse_pond(matrix, (new_x, new_y)) 25 | 26 | return pond_size 27 | 28 | 29 | def find_pond_sizes(wet_map: List[List[int]]): 30 | pond_sizes = [] 31 | for i in range(len(wet_map)): 32 | for j in range(len(wet_map[0])): 33 | if wet_map[i][j] == 0: 34 | pond_sizes.append(traverse_pond(wet_map, (i, j))) 35 | return pond_sizes 36 | 37 | 38 | if __name__ == "__main__": 39 | ex_pond = [ 40 | [0, 2, 1, 0], 41 | [0, 1, 0, 1], 42 | [1, 1, 0, 1], 43 | [0, 1, 0, 1] 44 | ] 45 | 46 | print(f"In my map, pond sizes are: {find_pond_sizes(ex_pond)}") 47 | -------------------------------------------------------------------------------- /chapter_16/p16_2.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from typing import List 3 | import string 4 | 5 | 6 | def word_freq_setup(words: List[str]): 7 | ctr = defaultdict(lambda: 0) 8 | for word in words: 9 | proc_word = word.strip().lower().translate( 10 | str.maketrans('', '', string.punctuation)) 11 | if not proc_word: 12 | continue 13 | ctr[proc_word] += 1 14 | return ctr 15 | 16 | 17 | def lookup_word(word: str, words_counts: dict) -> int: 18 | proc_word = word.strip() \ 19 | .lower() \ 20 | .translate(str.maketrans('', '', string.punctuation)) 21 | if proc_word in words_counts: 22 | return words_counts[proc_word] 23 | else: 24 | raise Exception(f"Word \"{proc_word}\" not found") 25 | 26 | 27 | if __name__ == "__main__": 28 | big_text = """The missile knows where it is at all times. 29 | It knows this because it knows where it isn't. 30 | By subtracting where it is from where it isn't, or where it isn't from where it is (whichever is greater), it obtains a difference, or deviation. 31 | The guidance subsystem uses deviations to generate corrective commands to drive the missile from a position where it is to a position where it isn't, and arriving at a position where it wasn't, it now is. 32 | Consequently, the position where it is, is now the position that it wasn't, and it follows that the position that it was, is now the position that it isn't. 33 | In the event that the position that it is in is not the position that it wasn't, the system has acquired a variation, the variation being the difference between where the missile is, and where it wasn't. 34 | If variation is considered to be a significant factor, it too may be corrected by the GEA. However, the missile must also know where it was. 35 | The missile guidance computer scenario works as follows. 36 | Because a variation has modified some of the information the missile has obtained, it is not sure just where it is. 37 | However, it is sure where it isn't, within reason, and it knows where it was. 38 | It now subtracts where it should be from where it wasn't, or vice-versa, and by differentiating this from the algebraic sum of where it shouldn't be, and where it was, it is able to obtain the deviation and its variation, which is called error." 39 | """ 40 | lookup_list = "missile", "it", "It", "was", "wasn't", "the", "tHE" 41 | 42 | word_dict = word_freq_setup(big_text.split(" ")) 43 | for word in lookup_list: 44 | try: 45 | print(f"Found word: {word} {lookup_word(word, word_dict)}") 46 | except Exception as e: 47 | print(f"Didn't find {word} in dict") 48 | -------------------------------------------------------------------------------- /chapter_16/p16_20.py: -------------------------------------------------------------------------------- 1 | from typing import List, Iterable 2 | from collections import defaultdict 3 | import utils.datasets 4 | 5 | 6 | T9_MAP = { 7 | 0: set(), 8 | 1: set(), 9 | 2: {'a', 'b', 'c'}, 10 | 3: {'d', 'e', 'f'}, 11 | 4: {'g', 'h', 'i'}, 12 | 5: {'j', 'k', 'l'}, 13 | 6: {'m', 'n', 'o'}, 14 | 7: {'p', 'q', 'r', 's'}, 15 | 8: {'t', 'u', 'v'}, 16 | 9: {'w', 'x', 'y', 'z'} 17 | } 18 | 19 | 20 | def rec_def_dict(): 21 | return defaultdict(rec_def_dict) 22 | 23 | 24 | def create_trie_from_words(words: Iterable[str]): 25 | trie_root = rec_def_dict() 26 | for word in words: 27 | trie_pointer = trie_root 28 | for char in word: 29 | trie_pointer = trie_pointer[char] 30 | 31 | trie_pointer["end"] = word 32 | 33 | return trie_root 34 | 35 | 36 | def t9_trav_helper(digits: List[int], offset: int, pointer: dict) -> List[str]: 37 | if offset == len(digits): 38 | if "end" in pointer: 39 | return [pointer["end"]] 40 | else: 41 | letters = T9_MAP[digits[offset]] 42 | words = [] 43 | 44 | for c in letters: 45 | if c in pointer: 46 | new_pointer = pointer[c] 47 | new_words = t9_trav_helper(digits, offset + 1, new_pointer) 48 | if new_words: 49 | words += new_words 50 | 51 | return words 52 | 53 | 54 | def get_t9_words(input_digits: str, words: List[str]) -> List[str]: 55 | trie_root = create_trie_from_words(words) 56 | digit_list = list(map(int, input_digits)) 57 | return t9_trav_helper(digit_list, 0, trie_root) 58 | 59 | 60 | if __name__ == "__main__": 61 | sys_word_list = list(map(lambda x: x.lower(), 62 | utils.datasets.get_system_word_list())) 63 | exs = [ 64 | "8733", 65 | "2222", 66 | "798466", 67 | "32573" 68 | ] 69 | for ex in exs: 70 | print(f"For input {ex}, possible words are: " 71 | f"{get_t9_words(ex, sys_word_list)}") 72 | -------------------------------------------------------------------------------- /chapter_16/p16_21.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def sum_swap(arr1: List[int], arr2: List[int]) -> tuple: 5 | if len(arr1) < len(arr2): 6 | return sum_swap(arr2, arr1) 7 | 8 | posibs_set = set(arr2) 9 | target_change = abs(sum(arr1) - sum(arr2)) 10 | 11 | if target_change % 2 == 1: 12 | return None 13 | target_change //= 2 14 | 15 | for x in arr1: 16 | if x-target_change in posibs_set: 17 | return (x, x-target_change) 18 | elif x+target_change in posibs_set: 19 | return (x, x + target_change) 20 | 21 | return None 22 | 23 | 24 | if __name__ == "__main__": 25 | exs = [ 26 | ([4, 1, 2, 1, 1, 2], [3, 6, 3, 3]), 27 | ([1, 2, 3, 4, 5], [1, 2, 3, 4, 4]) 28 | ] 29 | 30 | for arr1, arr2 in exs: 31 | print(f"Swap {sum_swap(arr1, arr2)} to make {arr1} have same sum ar {arr2}") 32 | -------------------------------------------------------------------------------- /chapter_16/p16_22.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | import time 3 | 4 | DIRS = [(0, 1), (-1, 0), (0, -1), (1, 0)] 5 | 6 | 7 | def update_bounds(elem, bounds): 8 | if elem > bounds[1]: 9 | bounds[1] = elem 10 | elif elem < bounds[0]: 11 | bounds[0] = elem 12 | 13 | 14 | def print_k_moves(k: int): 15 | matrix = defaultdict(lambda: 0) 16 | 17 | dir_index = 0 18 | row = 0 19 | col = 0 20 | row_bounds = [0, 0] 21 | col_bounds = [0, 0] 22 | 23 | for i in range(k): 24 | square_now = matrix[(row, col)] 25 | 26 | if square_now: 27 | matrix[(row, col)] = 0 28 | dir_index -= 1 29 | else: 30 | matrix[(row, col)] = 1 31 | dir_index += 1 32 | 33 | dir_index %= len(DIRS) 34 | 35 | drow, dcol = DIRS[dir_index] 36 | row += drow 37 | col += dcol 38 | 39 | update_bounds(row, row_bounds) 40 | update_bounds(col, col_bounds) 41 | 42 | for i in range(row_bounds[0], row_bounds[1]+1): 43 | for j in range(col_bounds[0], col_bounds[1]+1): 44 | print("░" if not matrix[(i, j)] else "█", end="") 45 | print() 46 | 47 | 48 | if __name__ == "__main__": 49 | # for i in range(11005): 50 | # print_k_moves() 51 | # print("-"*50) 52 | 53 | print_k_moves(12_000) 54 | -------------------------------------------------------------------------------- /chapter_16/p16_23.py: -------------------------------------------------------------------------------- 1 | import random 2 | from collections import defaultdict 3 | 4 | STATISTICAL_SIGNIF_THRESHOLD = 10**6 5 | 6 | 7 | def rand5() -> int: 8 | return random.randint(0, 4) 9 | 10 | 11 | def rand7() -> int: 12 | res = rand5()*5 + rand5() # Get res in range 0-24 13 | while res >= 21: # Reroll until in 7-divisible range 14 | res = rand5()*5 + rand5() 15 | 16 | return res % 7 17 | 18 | 19 | if __name__ == "__main__": 20 | results = defaultdict(lambda: 0) 21 | for _ in range(STATISTICAL_SIGNIF_THRESHOLD): 22 | results[rand7()] += 1 23 | 24 | for x in sorted(results.keys()): 25 | print(f"Value {x} found times: {results[x]}") 26 | -------------------------------------------------------------------------------- /chapter_16/p16_24.py: -------------------------------------------------------------------------------- 1 | def old_2sum(arr, target): 2 | seen = {} 3 | ans = [] 4 | for i, x in enumerate(arr): 5 | if target - x in seen: 6 | ans.append((arr[i], arr[seen[target-x]])) 7 | else: 8 | seen[x] = i 9 | 10 | return ans 11 | 12 | 13 | if __name__ == "__main__": 14 | exs = [([4, 6, 10, 15, 16], 21), 15 | ([12, 6, 7, 1, 19, 3, 0, 4, 40], 7), 16 | ([4, 4, 4, 2, 2, 1, 1, 1, 1, 3], 4)] 17 | for arr, x in exs: 18 | print(f"Pairs for targeet {x} in {arr} are {old_2sum(arr,x)}") 19 | -------------------------------------------------------------------------------- /chapter_16/p16_25.py: -------------------------------------------------------------------------------- 1 | class LRUCache: 2 | def __init__(self, max_size): 3 | super().__init__() 4 | self.latest = None 5 | self.oldest = None 6 | self.max_size = max_size 7 | self._dict = {} 8 | 9 | def add(self, key, val) -> None: 10 | if key in self._dict: 11 | self._dict[key].val = val 12 | self._bump_node(self._dict[key]) 13 | elif len(self._dict) == self.max_size: 14 | self.evict_lru() 15 | 16 | new_node = CacheNode(key, val) 17 | if self.latest: 18 | self.latest.right = new_node 19 | else: 20 | self.oldest = new_node 21 | 22 | new_node.left = self.latest 23 | self._dict[key] = new_node 24 | self.latest = new_node 25 | 26 | # print(f"Self.latest now is {self.latest}, to it's left {self.latest.left}") 27 | 28 | def get(self, key): 29 | if key not in self._dict: 30 | raise Exception(f"Key {key} not in cache") 31 | 32 | val = self._dict[key].val 33 | self._bump_node(self._dict[key]) 34 | return val 35 | 36 | def evict_lru(self): 37 | if not self.oldest: 38 | raise Exception("Cannot evict, cache should be empty") 39 | 40 | oldest_nodes = self.oldest 41 | # print(f"[Evicting] oldest, which is {oldest_nodes}, on it's right {oldest_nodes.right}") 42 | 43 | if oldest_nodes.right: 44 | # print(f"[Evicting] On right is {oldest_nodes.right}") 45 | self.oldest = oldest_nodes.right 46 | oldest_nodes.right.left = None 47 | else: 48 | self.oldest = None 49 | self.latest = None 50 | 51 | del self._dict[oldest_nodes.key] 52 | 53 | def _bump_node(self, node): 54 | # print(f"Bumping node {node}, w/ LEFT: {node.left} and RIGHT {node.right}") 55 | # print(f"Bumping node in dict: {self._dict[node.left.key] if node.left else 'Nevermind'}") 56 | 57 | if node != self.latest: 58 | if node.left: 59 | node.left.right = node.right 60 | else: # You are the oldest 61 | self.oldest = node.right 62 | 63 | # Add to the latest nodes, become latest 64 | self.latest.right = node 65 | node.left = self.latest 66 | self.latest = node 67 | 68 | def __str__(self): 69 | return f"LRUCache w/ items: {list(self._dict.items())}, oldest: {self.oldest} , newest: {self.latest}" 70 | 71 | 72 | class CacheNode: 73 | 74 | def __init__(self, key, val): 75 | self.key = key 76 | self.val = val 77 | self.right = None 78 | self.left = None 79 | 80 | def __str__(self): 81 | return f"CN(k:{self.key}, v:{self.val})" 82 | 83 | def __repr__(self): 84 | return self.__str__() 85 | 86 | 87 | if __name__ == "__main__": 88 | lrc = LRUCache(4) 89 | for i in range(10): 90 | lrc.add(i, i) 91 | assert lrc.get(i) == i 92 | print(lrc) 93 | 94 | print("-"*50) 95 | lrc.get(6) 96 | lrc.get(8) 97 | print(lrc) 98 | print("-"*50) 99 | 100 | for i in range(10, 16): 101 | lrc.add(i, i) 102 | print(lrc) 103 | -------------------------------------------------------------------------------- /chapter_16/p16_26.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def eval_operation(ops_string: str) -> float: 5 | tokens = tokenise(ops_string) 6 | 7 | reduced_tokens = [] 8 | i = 0 9 | while i < len(tokens): 10 | if tokens[i] == "/": 11 | reduced_tokens[-1] /= tokens[i+1] 12 | i += 2 13 | elif tokens[i] == "*": 14 | reduced_tokens[-1] *= tokens[i+1] 15 | i += 2 16 | else: 17 | reduced_tokens.append(tokens[i]) 18 | i += 1 19 | 20 | final_result = reduced_tokens[0] 21 | i = 1 22 | # print(tokens) 23 | # print(reduced_tokens) 24 | while i < len(reduced_tokens): 25 | if reduced_tokens[i] == "+": 26 | final_result += reduced_tokens[i+1] 27 | elif reduced_tokens[i] == "-": 28 | final_result -= reduced_tokens[i+1] 29 | i += 2 30 | 31 | return final_result 32 | 33 | 34 | def tokenise(raw: str) -> list: 35 | tokens = [] 36 | cnum = None # Only have positive integers 37 | for c in raw: 38 | if c.isdigit(): 39 | if cnum: 40 | cnum *= 10 41 | cnum += int(c) 42 | else: 43 | cnum = int(c) 44 | elif c in "-+/*": 45 | if cnum: 46 | tokens.append(cnum) 47 | cnum = None 48 | tokens.append(c) 49 | else: 50 | raise Exception("Unknown charater is string: ", c) 51 | 52 | if cnum: 53 | tokens.append(cnum) 54 | 55 | return tokens 56 | 57 | 58 | if __name__ == "__main__": 59 | exs = [ 60 | "234-134+50/25*5*2+3", 61 | ] 62 | 63 | for ex in exs: 64 | print(f"Result of operation {ex} is {eval_operation(ex)}") 65 | -------------------------------------------------------------------------------- /chapter_16/p16_3.py: -------------------------------------------------------------------------------- 1 | 2 | from collections import namedtuple 3 | from typing import Tuple, Optional, List 4 | 5 | Point = namedtuple("Point", "x y") 6 | 7 | 8 | def get_line_eq(p1, p2): 9 | is_vertical = False 10 | slope =0 11 | if p1 == p2: 12 | raise Exception("Can't get a line eq for one point") 13 | 14 | if p1.x != p2.x: 15 | slope = (p1.y - p2.y) / (p1.x - p2.x) 16 | offset = p1.y - slope*p1.x 17 | else: # Horiz line 18 | is_vertical = True 19 | offset = p1.x 20 | 21 | return (slope, offset, is_vertical) 22 | 23 | 24 | def get_line_intersection(line_1, line_2): 25 | l1_slope, l1_offset, l1_vert = line_1 26 | l2_slope, l2_offset, l2_vert = line_2 27 | 28 | if (not (l1_vert or l2_vert)): 29 | if l1_slope == l2_slope and l2_offset != l1_offset: 30 | return None 31 | 32 | intersect_x = (l2_offset - l1_offset) / (l1_slope - l2_slope) 33 | intersect_y = intersect_x * l1_slope + l1_offset 34 | 35 | return Point(intersect_x, intersect_y) 36 | else: 37 | if l1_vert and l2_vert: 38 | if l1_offset != l2_offset: 39 | print("Got two verticals, but not at the same place") 40 | return None 41 | else: 42 | return (l1_offset, 0) # Good as any 43 | else: 44 | vert_line_offset = l1_offset if l1_vert else l2_offset 45 | norm_line_slope, norm_line_offset = l2_slope, l2_offset if l1_vert else l1_slope, l1_offset 46 | 47 | isect_x = vert_line_offset 48 | isect_y = isect_x * norm_line_slope + norm_line_offset 49 | return Point(isect_x, isect_y) 50 | 51 | 52 | def point_in_segment(p: Point, segment: Tuple[Point, Point]) -> bool: 53 | x_points = sorted([segment[0].x, segment[1].x]) 54 | y_points = sorted([segment[0].y, segment[1].y]) 55 | return x_points[0] <= p.x <= x_points[1]\ 56 | and y_points[0] <= p.y <= y_points[1] 57 | 58 | 59 | def check_line_overlap(s1, s2): 60 | if point_in_segment(s1[0], s2): 61 | return s1[0] 62 | elif point_in_segment(s1[1], s2): 63 | return s1[1] 64 | elif point_in_segment(s2[0], s1): 65 | return s2[0] 66 | elif point_in_segment(s2[1], s1): 67 | return s2[1] 68 | else: 69 | return None 70 | 71 | 72 | def get_segment_insect(segment1: Tuple[Point, Point], 73 | segment2: Tuple[Point, Point]) -> Optional[Point]: 74 | line_1 = get_line_eq(*segment1) 75 | line_2 = get_line_eq(*segment2) 76 | 77 | print(f"See {line_1} and {line_2}") 78 | 79 | if not line_1 or not line_2: 80 | print(f"Failed to determine a line from {line_1} and {line_2}") 81 | elif line_1 == line_2 or \ 82 | ((line_1[2] and line_2[2]) and line_1[1] == line_2[1]): 83 | print(f"Got the same line twice, see {line_1} and {line_2}") 84 | return check_line_overlap(segment1, segment2) 85 | 86 | line_intersect = get_line_intersection(line_1, line_2) 87 | 88 | if line_intersect: 89 | if point_in_segment(line_intersect, segment1) \ 90 | and point_in_segment(line_intersect, segment2): 91 | return line_intersect 92 | else: 93 | print(f"Point {line_intersect} not in both lines") 94 | 95 | 96 | if __name__ == "__main__": 97 | exs = [ 98 | ((Point(10, 10), Point(20, 20)), (Point(20, 10), Point(10, 20))), 99 | ((Point(10, 10), Point(10, 20)), (Point(10, 15), Point(10, 25))), 100 | ((Point(10, 10), Point(20, 10)), (Point(15, 10), Point(25, 10))), 101 | ((Point(10, 10), Point(10, 20)), (Point(15, 10), Point(15, 20))), 102 | ] 103 | 104 | for seg1, seg2 in exs: 105 | print(f"Meeting point of segments {seg1} and {seg2} " 106 | f"is {get_segment_insect(seg1,seg2)} ") 107 | print("-"*50) 108 | -------------------------------------------------------------------------------- /chapter_16/p16_4.py: -------------------------------------------------------------------------------- 1 | from typing import List, Optional 2 | from enum import Enum 3 | 4 | 5 | class Dir(Enum): 6 | LEFT = (0, -1) 7 | RIGHT = (0, 1) 8 | UP = (-1, 0) 9 | DOWN = (1, 0) 10 | UPRIGHT = (-1, 1) 11 | UPLEFT = (-1, -1) 12 | DOWNRIGHT = (1, 1) 13 | DOWNLEFT = (1, -1) 14 | 15 | 16 | def check_in_dir(m: List[List[chr]], 17 | start_poz: tuple, 18 | direction: Dir) -> Optional[chr]: 19 | sr, sc = start_poz 20 | dr, dc = direction.value 21 | winner_sign = m[sr][sc] 22 | # print(f"Checking from {start_poz}, going in dir {dr} {dc}, {winner_sign}") 23 | 24 | # If not complete on this direction, no winner 25 | if not winner_sign: 26 | return None 27 | 28 | row = sr 29 | col = sc 30 | while 0 <= row < len(m) and 0 <= col < len(m[0]): 31 | elem_now = m[row][col] 32 | if elem_now != winner_sign: 33 | return None 34 | row += dr 35 | col += dc 36 | 37 | return winner_sign 38 | 39 | 40 | def check_xo(board: List[List[chr]]): 41 | rows = len(board) 42 | cols = len(board[0]) 43 | 44 | row_winners = [check_in_dir(board, (row, 0), Dir.RIGHT) 45 | for row in range(rows)] 46 | 47 | col_winners = [check_in_dir(board, (0, col), Dir.DOWN) 48 | for col in range(cols)] 49 | 50 | diag_winners = [check_in_dir(board, (0, 0), Dir.DOWNRIGHT), 51 | check_in_dir(board, (0, cols-1), Dir.DOWNLEFT)] 52 | 53 | # print(f"Row winners {row_winners}, " 54 | # f"col {col_winners} and diags: {diag_winners}") 55 | 56 | possible_winners = set(x for x in row_winners + 57 | col_winners + diag_winners if x) 58 | if len(possible_winners) > 1: 59 | raise Exception("Contested board detected") 60 | else: 61 | return list(possible_winners)[0] 62 | 63 | 64 | if __name__ == "__main__": 65 | board = [ 66 | ['x', 'o', 'o'], 67 | ['o', 'o', 'o'], 68 | ['x', 'o', 'x'] 69 | ] 70 | winrar = check_xo(board) 71 | print(f"Winner of the board is {winrar}") 72 | -------------------------------------------------------------------------------- /chapter_16/p16_5.py: -------------------------------------------------------------------------------- 1 | def zeroes_in_fact(n: int): 2 | five_pow = 1 3 | nr_z = 0 4 | while (5**five_pow) <= n: 5 | nr_z += (n // 5 ** five_pow) 6 | five_pow += 1 7 | return nr_z 8 | 9 | 10 | if __name__ == "__main__": 11 | exs = [1, 10, 34, 55, 642, 1000] 12 | for ex in exs: 13 | print(f"Number of zeroes in {ex}! is {zeroes_in_fact(ex)}") 14 | -------------------------------------------------------------------------------- /chapter_16/p16_6.py: -------------------------------------------------------------------------------- 1 | def binary_search_around(arr, target): 2 | low = 0 3 | high = len(arr)-1 4 | 5 | mid = -1 6 | while low <= high: 7 | mid = (low + high) // 2 8 | if arr[mid] == target: 9 | return (mid, mid) 10 | elif arr[mid] > target: 11 | high = mid - 1 12 | else: 13 | low = mid+1 14 | 15 | lower = None 16 | higher = None 17 | if arr[mid] > target: 18 | higher = mid 19 | if mid > 0: 20 | lower = mid-1 21 | else: 22 | lower = mid 23 | if lower < len(arr)-1: 24 | higher = mid+1 25 | 26 | return (lower, higher) 27 | 28 | 29 | def smallest_diff(arr1, arr2) -> int: 30 | cmin_diff = float("inf") 31 | if len(arr1) > len(arr2): 32 | arr1, arr2 = arr2, arr1 33 | 34 | sorted_arr = sorted(arr1) 35 | for x in arr2: 36 | smaller, bigger = binary_search_around(sorted_arr, x) 37 | print(f"For x {x} got around elems: {smaller} and {bigger}") 38 | 39 | diff_with_bigger = sorted_arr[bigger] - x \ 40 | if bigger is not None else float('inf') 41 | diff_with_smaller = x - sorted_arr[smaller] \ 42 | if smaller is not None else float('inf') 43 | 44 | diff_now = min(diff_with_bigger, diff_with_smaller) 45 | if diff_now < cmin_diff: 46 | cmin_diff = diff_now 47 | 48 | return cmin_diff 49 | 50 | 51 | if __name__ == "__main__": 52 | exs = [ 53 | ([1], [23, 127, 235, 19, 8]) 54 | ] 55 | 56 | for arr1, arr2 in exs: 57 | print(f"Minimum is {smallest_diff(arr1,arr2)}" 58 | f" for the arrays {arr1} and {arr2}") 59 | -------------------------------------------------------------------------------- /chapter_16/p16_7.py: -------------------------------------------------------------------------------- 1 | 2 | def sign(a: int): 3 | return (a >> 32) & 0b1 4 | 5 | 6 | def max_unconditional(a, b): 7 | diff = a - b 8 | 9 | both_negate = (sign(a)) & (sign(b)) 10 | sign_flag = sign(diff) ^ (not both_negate) 11 | # print(f"Are both {a} and {b} negative {both_negate:b},\t" 12 | # f"with signs {sign(a)} and {sign(b)}\t" 13 | # f"Sign flag is {sign_flag}") 14 | return ((not sign_flag) * a) | ((sign_flag) * b) 15 | 16 | 17 | if __name__ == "__main__": 18 | exs = [ 19 | (10, 20), 20 | (20, 10), 21 | (-10, -20), 22 | (20, -10), 23 | (-20, -10), 24 | (0, 10), 25 | (-20, 0), 26 | ] 27 | 28 | for a, b in exs: 29 | print(f"Max of {a} and {b} is:\t" 30 | f"{max_unconditional(a,b)} == {max(a,b)}") 31 | -------------------------------------------------------------------------------- /chapter_16/p16_8.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | english_spellings = { 4 | 0: "zero", 5 | 1: "one", 6 | 2: "two", 7 | 3: "three", 8 | 4: "four", 9 | 5: "five", 10 | 6: "six", 11 | 7: "seven", 12 | 8: "eight", 13 | 9: "nine", 14 | 10: "ten", 15 | 11: "eleven", 16 | 12: "twelve", 17 | 13: "thirteen", 18 | 14: "fourteen", 19 | 15: "fifteen", 20 | 16: "sixteen", 21 | 17: "seventeen", 22 | 18: "eighteen", 23 | 19: "nineteen", 24 | 20: "twenty", 25 | 30: "thirty", 26 | 40: "fourty", 27 | 50: "fifty", 28 | 60: "sixty", 29 | 70: "seventy", 30 | 80: "eighty", 31 | 90: "ninety", 32 | } 33 | 34 | group_separators = ["", "thousand", "million", "billion", "trillion"] 35 | 36 | 37 | def two_digits_to_str(digit_list_rev: List[int]) -> str: 38 | x = digit_list_rev[1] * 10 + digit_list_rev[0] 39 | if 19 < x: 40 | ans = english_spellings[digit_list_rev[1]*10] 41 | if digit_list_rev[0]: 42 | ans += "-" + english_spellings[digit_list_rev[0]] 43 | return ans 44 | elif x < 10: 45 | return english_spellings[x % 10] 46 | else: 47 | return english_spellings[x] 48 | 49 | 50 | def triple_to_string(triple_list_rev: List[int]) -> str: 51 | if not triple_list_rev or all(x == 0 for x in triple_list_rev): 52 | return "" 53 | 54 | # Add padding if necessary 55 | if len(triple_list_rev) < 3: 56 | triple_list_rev = triple_list_rev + [0] * (3 - len(triple_list_rev)) 57 | 58 | res = [] 59 | if triple_list_rev[2]: 60 | res.append(f"{english_spellings[triple_list_rev[2]]} hundred") 61 | 62 | if any(triple_list_rev[:2]): # There are non-zero digits 63 | res.append(two_digits_to_str(triple_list_rev[:2])) 64 | 65 | return " ".join(res) 66 | 67 | def get_digit_list(x:int) -> List[int]: 68 | digits =[] 69 | while x > 0: 70 | digits.append(x % 10) 71 | x //= 10 72 | return digits 73 | 74 | 75 | def int_to_eng(x: int): 76 | if x < 10: 77 | return english_spellings[x] 78 | 79 | x_digits = get_digit_list(x) 80 | 81 | if len(x_digits) == 4: 82 | return " ".join([two_digits_to_str(x_digits[2:]), two_digits_to_str(x_digits[:2])]) 83 | 84 | end_str_parts = [] 85 | triplets = [x_digits[i: i+3] for i in range(0, len(x_digits), 3)] 86 | 87 | for i, tripl in enumerate(triplets): 88 | tripl_str = triple_to_string(tripl) 89 | if tripl_str: 90 | end_str_parts.append( 91 | f"{triple_to_string(tripl)} {group_separators[i]}") 92 | 93 | return " ".join(end_str_parts[::-1]) 94 | 95 | 96 | if __name__ == "__main__": 97 | exs = [ 98 | 1, 99 | 0, 100 | 10, 101 | 20, 102 | 4959, 103 | 2020, 104 | 1945, 105 | 123_456_000_789_055, 106 | 100_020_003, 107 | 101_880, 108 | 1_000_234 109 | ] 110 | for x in exs: 111 | print(f"În engleză {x} este {int_to_eng(x)}") 112 | -------------------------------------------------------------------------------- /chapter_16/p16_9.py: -------------------------------------------------------------------------------- 1 | def substract(a: int, b: int) -> int: 2 | return a + flip_sign(b) 3 | 4 | 5 | def multiply(a: int, b: int) -> int: 6 | c_sum = 0 7 | negative_other = b < 0 8 | 9 | if negative_other: 10 | b = flip_sign(b) 11 | 12 | for _ in range(b): 13 | c_sum += a 14 | 15 | return c_sum if not negative_other else flip_sign(c_sum) 16 | 17 | 18 | def divide(a: int, b: int) -> int: 19 | if b == 0: 20 | raise Exception("Cannot do zero division") 21 | 22 | is_a_neg = a < 0 23 | is_b_neg = b < 0 24 | 25 | if is_a_neg: 26 | a = flip_sign(a) 27 | if is_b_neg: 28 | b = flip_sign(b) 29 | 30 | cprod = 0 31 | x = -1 32 | while cprod < a: 33 | x += 1 34 | cprod += b 35 | 36 | return x if not is_a_neg ^ is_b_neg else flip_sign(x) 37 | 38 | 39 | def flip_sign(a: int): 40 | diff_driver = -1 if a > 0 else 1 41 | new_a = 0 42 | while a: 43 | a += diff_driver 44 | new_a += diff_driver 45 | 46 | return new_a 47 | 48 | 49 | if __name__ == "__main__": 50 | exs = [ 51 | (10, 3), 52 | (10, -3), 53 | (-10, 3), 54 | (-10, -3), 55 | (-3, 12) 56 | ] 57 | 58 | for a, b in exs: 59 | print(f" {a} / {b} = {divide(a,b)}, {a} * {b} = {multiply(a,b)}") 60 | -------------------------------------------------------------------------------- /chapter_17/p17_1.py: -------------------------------------------------------------------------------- 1 | 2 | # Method: Bitwise addition, recurse with carry 3 | # Time: O(log(n)) 4 | # Space: O(log(n)) 5 | 6 | def add_overfill(left, res, ctr, carry): 7 | while left: 8 | # print(f"Diggin into {left:b}, resutl now {res:b}") 9 | res_bit = left & 1 10 | 11 | if carry: 12 | carry = res_bit & carry 13 | res_bit ^= 0b1 14 | else: 15 | carry = res_bit & carry 16 | 17 | res ^= raise_to_counter(res_bit, ctr) 18 | 19 | ctr <<= 1 20 | ctr ^= 1 21 | 22 | left >>= 1 23 | return res, ctr, carry 24 | 25 | 26 | def raise_to_counter(a_bit, ctr): 27 | ctemp = ctr 28 | while ctemp: 29 | a_bit <<= 1 30 | ctemp >>= 1 31 | return a_bit 32 | 33 | 34 | def add_no_alg(a, b): 35 | 36 | res = 0b0 37 | ctr = 0b0 38 | carry = 0b0 39 | 40 | while a and b: 41 | bit_b = b & 1 42 | bit_a = a & 1 43 | res_b = bit_a ^ bit_b 44 | # print(f"Adding {bit_a:b} & {bit_b:b} => {res_b:b}, incoming carry: {carry:b}") 45 | 46 | if carry: 47 | if bit_a & bit_b: 48 | carry = 0b1 49 | else: 50 | carry &= res_b 51 | res_b ^= 0b1 52 | # print(f"Added carry, now result {res_b:b}, further carry {carry:b}") 53 | else: 54 | carry = bit_a & bit_b 55 | # print(f"Carry set to {carry}") 56 | 57 | res ^= raise_to_counter(res_b, ctr) 58 | # print(f"Res_b is {res_b:b}, carry {carry:b}, counter is {ctr:b}") 59 | 60 | ctr <<= 1 61 | ctr ^= 1 62 | 63 | a >>= 1 64 | b >>= 1 65 | # print( f"Sumnow for {a:b}and {b:b},\tres: {res:b} ctr\t{ctr:b}\t new bit {res_b:b}") 66 | 67 | res, ctr, carry = add_overfill(a, res, ctr, carry) 68 | res, ctr, carry = add_overfill(b, res, ctr, carry) 69 | 70 | if carry: 71 | # print(f"Carry at the end, to {res:b}") 72 | res ^= raise_to_counter(0b1, ctr) 73 | 74 | return res 75 | 76 | 77 | def add_no_alg_better(a, b): 78 | if b == 0: 79 | return a 80 | 81 | just_sum = a ^ b 82 | carry = (a & b) << 1 83 | 84 | return add_no_alg_better(just_sum, carry) 85 | 86 | 87 | if __name__ == "__main__": 88 | exs = [(x, y) for x in range(1000) for y in range(1000)] 89 | # exs = [(0b1011, 0b1011)] 90 | fails = 0 91 | for a, b in exs: 92 | if add_no_alg(a, b) != a + b or add_no_alg_better(a, b) != a + b: 93 | print( 94 | f"Result\t{add_no_alg(a,b):b}," 95 | f"same as\t{(a+b):b} ? {add_no_alg(a,b) == a+b}, for inputs {a:b} and {b:b}" 96 | ) 97 | fails += 1 98 | print("*" * 50) 99 | else: 100 | # print(f"Good for {a} and {b}") 101 | pass 102 | -------------------------------------------------------------------------------- /chapter_17/p17_10.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | # Method: If there is a majority element, it's going to have enough buget to displace all others 4 | # Time: O(n) 5 | # Space: O(1) 6 | 7 | 8 | def majority_elem(arr): 9 | candidate = maj_elem(arr) 10 | return candidate if is_majority(candidate, arr) else -1 11 | 12 | 13 | def is_majority(x: int, arr: List[int]) -> bool: 14 | count = 0 15 | for elem in arr: 16 | count += elem == x 17 | print(f"Count is {count}") 18 | return count > len(arr) / 2 19 | 20 | 21 | def maj_elem(arr: List[int]) -> int: 22 | elem_now = arr[1] 23 | count = 1 24 | for i in range(1, len(arr)): 25 | x = arr[i] 26 | if x == elem_now: 27 | count += 1 28 | else: 29 | count -= 1 30 | 31 | if count < 1: 32 | elem_now = x 33 | count = 1 34 | return elem_now 35 | 36 | 37 | if __name__ == "__main__": 38 | posib = [4, 4, 4, 4, 5, 5, 5, 5, 5] 39 | print(majority_elem(posib)) 40 | -------------------------------------------------------------------------------- /chapter_17/p17_11.py: -------------------------------------------------------------------------------- 1 | import re 2 | from typing import List 3 | from collections import defaultdict 4 | import os 5 | 6 | # Method: Index occurences, run through 2 sorted occurence lists 7 | # Time: O(w*o + max(o)) 8 | # Space: O(w*o) 9 | # Notes: o = nr of occurences of a word, max(o) = **the** most frequent word 10 | 11 | # Idea: Reverse index 12 | # create mapping from 13 | # key: word , 14 | # value: sorted set of occurences 15 | # (count words in book) 16 | 17 | # when looking for shortest distance between 2 words, 18 | # run 2 pointers in the sorted arr, 19 | # find min diff 20 | 21 | 22 | def build_reverse_index(words: List[str]): 23 | rev_index = defaultdict(list) 24 | 25 | for index, word in enumerate(words): 26 | rev_index[word].append(index) 27 | 28 | return dict(rev_index) 29 | 30 | 31 | def get_min_interword_distance(w1: str, w2: str, rev_index: dict): 32 | occ_w1 = rev_index[w1] 33 | occ_w2 = rev_index[w2] 34 | if not occ_w1 or not occ_w2: 35 | raise Exception("Not all words present") 36 | 37 | min_sum = float("inf") 38 | p1 = 0 39 | p2 = 0 40 | while p1 < len(occ_w1) and p2 < len(occ_w2): 41 | diff_now = abs(occ_w1[p1] - occ_w2[p2]) 42 | if diff_now < min_sum: 43 | min_sum = diff_now 44 | 45 | if occ_w1[p1] > occ_w2[p2]: 46 | p2 += 1 47 | else: 48 | p1 += 1 49 | 50 | return min_sum 51 | 52 | 53 | if __name__ == "__main__": 54 | with open(os.path.join("utils", "lorem_ipsum.txt")) as words_source: 55 | all_non_empty_words = filter(bool, re.split(" |\n|\.", words_source.read())) 56 | all_text = list(map(lambda x: x.lower(), all_non_empty_words)) 57 | 58 | reverse_index = build_reverse_index(all_text) 59 | exs = [ 60 | ("ultrices", "bibendum"), 61 | ("hendrerit", "nulla"), 62 | ] 63 | 64 | for w1, w2 in exs: 65 | print( 66 | f"Shortest distance b/t {w1} and {w2}" 67 | f" at {get_min_interword_distance(w1,w2, reverse_index)}" 68 | ) 69 | -------------------------------------------------------------------------------- /chapter_17/p17_12.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple 2 | 3 | # Method: Recursive call, returning left and right edges of result linked list 4 | # Time: O(n) 5 | # Space: O(d) 6 | # Where d = depth of BST 7 | 8 | 9 | class BiNode: 10 | def __init__(self, val): 11 | self.val = val 12 | self.node1 = None 13 | self.node2 = None 14 | 15 | 16 | def flatten_binary_tree(root: BiNode): 17 | left_side, _ = flat_helper(root) 18 | return left_side 19 | 20 | 21 | def flat_helper(root: BiNode) -> Tuple[BiNode, BiNode]: 22 | left_edge = root 23 | right_edge = root 24 | 25 | if root.node1: 26 | left_edge_left, left_edge_right = flat_helper(root.node1) 27 | 28 | root.node1 = left_edge_right 29 | left_edge_right.node2 = root 30 | 31 | left_edge = left_edge_left 32 | 33 | if root.node2: 34 | right_edge_left, right_edge_right = flat_helper(root.node2) 35 | 36 | root.node2 = right_edge_left 37 | right_edge_left.node1 = root 38 | 39 | right_edge = right_edge_right 40 | 41 | return (left_edge, right_edge) 42 | 43 | 44 | if __name__ == "__main__": 45 | root = BiNode(20) 46 | root.node1 = BiNode(10) 47 | root.node1.node1 = BiNode(5) 48 | root.node2 = BiNode(25) 49 | root.node2.node1 = BiNode(22) 50 | root.node2.node1.node2 = BiNode(23) 51 | root.node2.node1.node1 = BiNode(21) 52 | 53 | list_start = flatten_binary_tree(root) 54 | 55 | list_elem_now = list_start 56 | while list_elem_now.node2: 57 | print(list_elem_now.val) 58 | list_elem_now = list_elem_now.node2 59 | -------------------------------------------------------------------------------- /chapter_17/p17_13.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from functools import lru_cache 3 | 4 | # Method: DFS, prefix and best-of-rest with lookup against dict 5 | # Time: O(n^2) 6 | # Space: O(n) 7 | 8 | 9 | def add_spaces(alltext: str, words: List[str]): 10 | word_set = set(words) 11 | space_arr, invalids = add_spaces_helper(alltext, 0, word_set, {}) 12 | 13 | # print(f"Managed to do it with {invalids} characters unknown, splits {space_arr}") 14 | i = 0 15 | spaced_text = [] 16 | for i in range(len(alltext)): 17 | spaced_text.append(alltext[i]) 18 | if i in space_arr: 19 | spaced_text.append(" ") 20 | 21 | return "".join(spaced_text) 22 | 23 | 24 | def add_spaces_helper( 25 | text: str, 26 | start_poz: int, 27 | word_set: set, 28 | memo: dict, 29 | ): 30 | if start_poz in memo: 31 | return memo[start_poz] 32 | 33 | n = len(text) 34 | if start_poz == n: 35 | return [], 0 36 | 37 | best_invals = n 38 | best_words = [] 39 | 40 | left_side = "" 41 | for i in range(start_poz, n): 42 | left_side += text[i] 43 | 44 | invals = len(left_side) if left_side not in word_set else 0 45 | 46 | if invals < best_invals: 47 | 48 | rest_words, rest_invals_count = add_spaces_helper( 49 | text, i + 1, word_set, memo 50 | ) 51 | 52 | if rest_invals_count + invals < best_invals: 53 | current_splits = [start_poz - 1, i] if invals == 0 else [] 54 | best_invals = rest_invals_count + invals 55 | best_words = rest_words + current_splits 56 | 57 | # print(f"At start {start_poz} Got best words {best_words}, and invals {best_invals}") 58 | memo[start_poz] = (best_words, best_invals) 59 | 60 | return best_words, best_invals 61 | 62 | 63 | if __name__ == "__main__": 64 | exs = ["helplinustechtipsineedtorebootmypc", "linusz", "torebootmy"] 65 | 66 | # words = utils.datasets.get_system_word_list() 67 | words = ["help", "tech", "tips", "boot", "my", "reboot", "need", "to", "linus"] 68 | for ex in exs: 69 | print(f"The way to split '{ex}' is: '{add_spaces(ex, words)}'") 70 | -------------------------------------------------------------------------------- /chapter_17/p17_14.py: -------------------------------------------------------------------------------- 1 | from copy import copy 2 | from typing import List 3 | import heapq 4 | 5 | # Method: Minheap 6 | # Time: O(n * log(k)) 7 | # Space: O(k) 8 | 9 | 10 | def smallest_k_elem(arr: List[int], k: int) -> List[int]: 11 | heap = [] 12 | 13 | for i in range(k): 14 | heapq.heappush(heap, -arr[i]) 15 | 16 | for i in range(k + 1, len(arr)): 17 | if arr[i] < -heap[0]: 18 | heapq.heappop(heap) 19 | heapq.heappush(heap, -arr[i]) 20 | 21 | return [-x for x in heap] 22 | 23 | 24 | if __name__ == "__main__": 25 | exs = [([1, 2, 3, 4, 5, 6], 4), ([1, 2, 2, 2, 1, 1, 0], 3)] 26 | 27 | for ex, k in exs: 28 | print(f"{k} smalles in {ex} are {smallest_k_elem(ex,k)}") 29 | -------------------------------------------------------------------------------- /chapter_17/p17_15.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | # Method: See if any split is buildable (left + right) 4 | # Time: O(w*(lw^2)) 5 | # Space: O(w*(lw^2)) 6 | # Note: w = nr of words lw: length of a word 7 | # Low confidence on both estimates 8 | 9 | 10 | def get_longest_gestaltwort(words: List[str]): 11 | known_words = {word: True for word in words} 12 | 13 | words_by_len = sorted(known_words, key=len, reverse=True) 14 | 15 | for word in words_by_len: 16 | if can_build_word(word, True, known_words): 17 | return word 18 | 19 | 20 | def can_build_word(word, is_given_word, known_words): 21 | if word in known_words and is_given_word: 22 | return known_words[word] 23 | 24 | for i in range(1, len(word)): 25 | left = word[:i] 26 | right = word[i:] 27 | if ( 28 | left in known_words 29 | and known_words[left] 30 | and can_build_word(right, False, known_words) 31 | ): 32 | known_words[word] = True 33 | return known_words[word] 34 | 35 | known_words[word] = False 36 | return known_words[word] 37 | 38 | 39 | def sys_word_test(): 40 | with open("/usr/share/dict/words", "r") as system_words_file: 41 | big_list = list( 42 | map(lambda word: word.lower(), system_words_file.read().split("\n")) 43 | ) 44 | 45 | print(f"Have a total of {len(big_list)} words") 46 | print(f"The longest is: {sorted(big_list, key = len)[-1]}") 47 | 48 | print(f"For the words, the longest is: {get_longest_gestaltwort(big_list[1:])}") 49 | 50 | 51 | if __name__ == "__main__": 52 | words = [ 53 | "cat", 54 | "banana", 55 | "dog", 56 | "nana", 57 | "walk", 58 | "walker", 59 | "dogwalker", 60 | "catdognana", 61 | ] 62 | print(f"For the words, the longest is: {get_longest_gestaltwort(words)}") 63 | -------------------------------------------------------------------------------- /chapter_17/p17_16.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | # Method: Bottom-up DP, max of times considering gap 4 | # Time: O(n) 5 | # Space: O(n) 6 | 7 | 8 | def masseuse_sched(appointments: List[int]): 9 | n = len(appointments) 10 | 11 | dp = [0 for _ in range(n + 3)] 12 | cmax = 0 13 | 14 | for i in range(n - 1, -1, -1): 15 | dp[i] = appointments[i] + max(dp[i + 2], dp[i + 3]) 16 | if dp[i] > cmax: 17 | cmax = dp[i] 18 | 19 | return max(dp[0], dp[1]) 20 | 21 | 22 | if __name__ == "__main__": 23 | exs = [[30, 15, 60, 75, 45, 15, 15, 45], [150, 30, 45, 60, 75, 150]] 24 | 25 | for ex in exs: 26 | print(f"Max mins is {masseuse_sched(ex)} for bookings {ex}") 27 | -------------------------------------------------------------------------------- /chapter_17/p17_17.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from typing import List 3 | 4 | # Method: Create a suffix tree, lookup each word against it 5 | # Time: O(b^2 + w * b) 6 | # Space: O(b^2) 7 | # Note: w = words 8 | 9 | 10 | def find_in_string(bigstr: str, words: List[str]): 11 | sufix_trie = create_suffix_trie(bigstr) 12 | res_dict = {} 13 | 14 | for word in words: 15 | pozs = find_starts(sufix_trie, word) 16 | res_dict[word] = pozs 17 | 18 | return res_dict 19 | 20 | 21 | def find_starts(trie: dict, word: str) -> List[int]: 22 | trie_pointer = trie 23 | for c in word: 24 | if c not in trie_pointer: 25 | return [] 26 | else: 27 | trie_pointer = trie_pointer[c] 28 | 29 | return [end_index - len(word) + 1 for end_index in trie_pointer["index"]] 30 | 31 | 32 | def build_trie_from_words(words: List[str]) -> dict: 33 | word_trie = trie() 34 | 35 | for word in words: 36 | add_word_to_trie(word) 37 | 38 | return word_trie 39 | 40 | 41 | def trie(): 42 | return defaultdict(trie) 43 | 44 | 45 | def create_suffix_trie(word): 46 | sufs_trie = trie() 47 | suffix_now = word 48 | start_index = 0 49 | 50 | while suffix_now: 51 | add_word_to_trie(sufs_trie, suffix_now, start_index) 52 | suffix_now = suffix_now[1:] 53 | start_index += 1 54 | 55 | return sufs_trie 56 | 57 | 58 | def add_word_to_trie(trie: dict, word: str, start_index=0) -> None: 59 | current_pointer = trie 60 | index_of_chr = start_index 61 | for char in word: 62 | current_pointer = current_pointer[char] 63 | if "index" in current_pointer: 64 | current_pointer["index"].append(index_of_chr) 65 | else: 66 | current_pointer["index"] = [index_of_chr] 67 | index_of_chr += 1 68 | 69 | current_pointer["end"] = True 70 | 71 | 72 | if __name__ == "__main__": 73 | exs = [("mississippi", ["i", "iss", "ppi", "miss", "issi", "ipi"])] 74 | for bigword, targets in exs: 75 | print(f"{[x for x in range(len(bigword))]}") 76 | print(" " + ", ".join(bigword)) 77 | print(f"Found targets at {find_in_string(bigword, targets)}") 78 | -------------------------------------------------------------------------------- /chapter_17/p17_18.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from collections import defaultdict 3 | 4 | # Method: Two pointer, keep track of elements 5 | # Time: O(l) 6 | # Space: O(s) 7 | 8 | 9 | def longest_superseq(longer: List[int], shorter: List[int]) -> List[int]: 10 | short_set = set(shorter) 11 | count_now = defaultdict(int) 12 | 13 | start = 0 14 | number_uniq = 0 15 | 16 | min_seq_now = float("inf") 17 | min_seq_indexes = (-1, -1) 18 | 19 | for i in range(len(longer)): 20 | x = longer[i] 21 | if x in short_set: 22 | if count_now[x] == 0: 23 | number_uniq += 1 24 | count_now[x] += 1 25 | 26 | while number_uniq == len(shorter) and start < i: 27 | current_subseq_len = i - start + 1 28 | if current_subseq_len < min_seq_now: 29 | min_seq_now = current_subseq_len 30 | min_seq_indexes = (start, i) 31 | 32 | if longer[start] in short_set: 33 | count_now[longer[start]] -= 1 34 | 35 | if count_now[longer[start]] == 0: 36 | number_uniq -= 1 37 | 38 | start += 1 39 | 40 | return list(min_seq_indexes) 41 | 42 | 43 | if __name__ == "__main__": 44 | exs = [ 45 | ([1, 5, 9], [7, 5, 9, 0, 2, 1, 3, 5, 7, 9, 1, 1, 5, 8, 8, 9, 7]), 46 | ([1, 2, 3], [1, 1, 2, 2, 2, 3, 3, 9, 9, 2, 1, 1, 3]), 47 | ] 48 | 49 | for shorter, longer in exs: 50 | print( 51 | f"Between {longest_superseq(longer,shorter)}, " 52 | f"you will find {shorter} in {longer}" 53 | ) 54 | -------------------------------------------------------------------------------- /chapter_17/p17_19.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | import math 3 | import copy 4 | import random 5 | 6 | # Method: Use math 7 | # Time: O(n) 8 | # Space: O(1) 9 | # Note: For 2 numbers, we can get a + b and a^2 + b^2 10 | # (a + b) ^ 2 = a^2 + 2ab + b^2 11 | 12 | TRIALS = 10 13 | 14 | 15 | def find_two_missing(arr: List[int], N: int) -> List[int]: 16 | # print(f"Array of len {len(arr)} is {arr}") 17 | all_sum = sum(range(1, N + 1)) 18 | all_quad_sum = sum(i ** 2 for i in range(1, N + 1)) 19 | 20 | real_sum = sum(arr) 21 | real_quad_sum = sum(x ** 2 for x in arr) 22 | 23 | delta_sum = all_sum - real_sum 24 | delta_quad_sum = all_quad_sum - real_quad_sum 25 | 26 | b = int((delta_sum + math.sqrt(2 * delta_quad_sum - delta_sum ** 2)) / 2) 27 | a = int((delta_sum ** 2 - delta_quad_sum) / (2 * b)) 28 | 29 | return [a, b] 30 | 31 | 32 | def get_indexes_to_remove(N: int) -> tuple: 33 | a = random.randint(0, N - 1) 34 | b = random.randint(0, N - 1) 35 | while b == a: 36 | b = random.randint(0, N - 1) 37 | 38 | return (a, b) 39 | 40 | 41 | if __name__ == "__main__": 42 | N = 100 43 | all_arr = list(range(1, N + 1)) 44 | 45 | for i in range(TRIALS): 46 | test_arr = copy.copy(all_arr) 47 | i1, i2 = get_indexes_to_remove(N) 48 | 49 | a, b = test_arr[i1], test_arr[i2] 50 | 51 | test_arr = [test_arr[i] for i in range(len(test_arr)) if i not in {i1, i2}] 52 | 53 | print(f"Missing from arr are {find_two_missing(test_arr,N)}, expected {a}, {b}") 54 | -------------------------------------------------------------------------------- /chapter_17/p17_2.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # Method: For evety position, switch a card with a random previous point 4 | # Time: O(n) 5 | # Space: O(n) 6 | # Notes: Tehnically, n is 52, also ignoring randomness generation 7 | 8 | 9 | def shuffle_deck(): 10 | deck = [0 for _ in range(52)] 11 | for i in range(1, 53): 12 | place = random.randint(0, i - 1) 13 | deck[i - 1], deck[place] = deck[place], i 14 | return deck 15 | 16 | 17 | if __name__ == "__main__": 18 | print(shuffle_deck()) 19 | -------------------------------------------------------------------------------- /chapter_17/p17_20.py: -------------------------------------------------------------------------------- 1 | import heapq as hq 2 | 3 | 4 | class MedianHolder: 5 | 6 | def __init__(self): 7 | self.left_heap = [] # Max (on top) heap 8 | self.right_heap = [] # Min (on top) heap 9 | self.size = 0 10 | 11 | def add_elem(self, x: int): 12 | 13 | if self.left_heap and x > -self.left_heap[0]: 14 | hq.heappush(self.right_heap, x) 15 | else: 16 | hq.heappush(self.left_heap, -x) 17 | 18 | if len(self.left_heap) < len(self.right_heap): # Too much on right 19 | hq.heappush(self.left_heap, - hq.heappop(self.right_heap)) 20 | elif len(self.left_heap) > len(self.right_heap) + 1: # Too much on left 21 | hq.heappush(self.right_heap, -hq.heappop(self.left_heap)) 22 | 23 | self.size += 1 24 | 25 | def get_median(self): 26 | if self.size == 0: 27 | raise Exception("No elems") 28 | if self.size % 2 == 0: 29 | return (-self.left_heap[0] + self.right_heap[0]) / 2 30 | else: 31 | return -self.left_heap[0] 32 | 33 | 34 | if __name__ == "__main__": 35 | mh = MedianHolder() 36 | for i in range(25): 37 | mh.add_elem(i) 38 | print(f"At elem {i}\t, Median is now {mh.get_median()} ") 39 | -------------------------------------------------------------------------------- /chapter_17/p17_21.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def get_lake_volumes(arr: List[int]) -> int: 5 | n = len(arr) 6 | left_bigger = [0]*n 7 | right_bigger = [0]*n 8 | 9 | bigger_left_now = 0 10 | bigger_right_now = 0 11 | for i in range(n): 12 | elem = arr[i] 13 | if elem > bigger_left_now: 14 | bigger_left_now = elem 15 | else: 16 | left_bigger[i] = bigger_left_now 17 | 18 | j = n-1-i 19 | elem_end = arr[j] 20 | 21 | if elem_end > bigger_right_now: 22 | bigger_right_now = elem_end 23 | else: 24 | right_bigger[j] = bigger_right_now 25 | 26 | water_vol = sum( 27 | max(min(left_bigger[i], right_bigger[i]) - arr[i], 0) 28 | for i in range(n)) 29 | # for i in range(n): 30 | # if left_bigger[i] and right_bigger[i]: 31 | # water_vol += min(left_bigger[i], right_bigger[i]) - arr[i] 32 | 33 | return water_vol 34 | 35 | 36 | if __name__ == "__main__": 37 | exs = [ 38 | [0, 0, 4, 0, 0, 6, 0, 0, 3, 0, 5, 0, 1, 0, 0, 0], 39 | [1, 3, 2, 4, 1, 3, 1, 4, 5], 40 | ] 41 | for arr in exs: 42 | print(f"Water in {arr} is {get_lake_volumes(arr)}") 43 | -------------------------------------------------------------------------------- /chapter_17/p17_22.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from collections import deque 3 | import utils.datasets 4 | 5 | 6 | def wildcard_match(w1: str, w2: str, wildcard_index: int) -> bool: 7 | for i in range(len(w1)): 8 | if i == wildcard_index: 9 | continue 10 | elif w1[i] != w2[i]: 11 | return False 12 | return True 13 | 14 | 15 | def get_one_related_words(word: str, word_set: set, used_words: set): 16 | related_words = set() 17 | available_words = word_set - used_words 18 | for i in range(len(word)): 19 | related_words.update( 20 | filter(lambda new_word: wildcard_match(word, new_word, i), 21 | available_words)) 22 | 23 | return related_words 24 | 25 | 26 | def word_transf(w1: str, w2: str, words: List[str]) -> List[str]: 27 | if len(w1) != len(w2): 28 | return [] 29 | 30 | word_set = set(word for word in words if len(word) == len(w1)) 31 | queue = deque() 32 | queue.append((w1, [])) 33 | seen = {w1} 34 | 35 | while queue: 36 | elem_now, path_to_here = queue.popleft() 37 | seen.add(elem_now) 38 | 39 | if elem_now == w2: 40 | return path_to_here + [w2] 41 | 42 | children = get_one_related_words(elem_now, word_set, seen) 43 | for child_word in children: 44 | if child_word not in seen: 45 | seen.add(child_word) 46 | queue.append((child_word, path_to_here + [elem_now])) 47 | 48 | return [] 49 | 50 | 51 | if __name__ == "__main__": 52 | all_words = utils.datasets.get_system_word_list() 53 | exs = [ 54 | ("damp", "like"), 55 | ("dank", "gene") 56 | ] 57 | for a, b in exs: 58 | print(f"Way from '{a}' to '{b}' is via {'->'.join(word_transf(a,b, all_words))}") 59 | -------------------------------------------------------------------------------- /chapter_17/p17_23.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple 2 | 3 | 4 | def precomp_down_left_squares(m: List[List[int]]) \ 5 | -> Tuple[List[List[int]], List[List[int]]]: 6 | rows = len(m) 7 | cols = len(m[0]) 8 | 9 | down_sq = [[0 for _ in range(cols+1)] for _ in range(rows+1)] 10 | right_sq = [[0 for _ in range(cols+1)] for _ in range(rows+1)] 11 | 12 | for row in range(rows-1, -1, -1): 13 | for col in range(cols-1, -1, -1): 14 | down_sq[row][col] = 0 if m[row][col] == 0 else 1 + \ 15 | down_sq[row+1][col] 16 | right_sq[row][col] = 0 if m[row][col] == 0 else 1 + \ 17 | right_sq[row][col+1] 18 | 19 | return down_sq, right_sq 20 | 21 | 22 | def check_square(coords: tuple, sq_size: int, 23 | down_sq: List[List[int]], 24 | right_sq: List[List[int]]) -> bool: 25 | """ Check that the square has squares for all the sides, as required by the the size 26 | l r 27 | # # # # top 28 | # # 29 | # # 30 | # # # # bot 31 | <-----> sq_size 32 | """ 33 | top_left_col, top_left_row = coords 34 | bot_left_col, bot_left_row = top_left_col, top_left_row + sq_size-1 35 | top_right_col, top_right_row = top_left_col + sq_size - 1, top_left_row 36 | 37 | if down_sq[top_left_row][top_left_col] < sq_size \ 38 | or right_sq[top_left_row][top_left_col] < sq_size: 39 | return False 40 | 41 | if down_sq[top_right_row][top_right_col] < sq_size \ 42 | or right_sq[bot_left_row][bot_left_col] < sq_size: 43 | return False 44 | 45 | return True 46 | 47 | 48 | def max_square_outline(matrix: List[List[int]]) -> List[tuple]: 49 | # Return the 4 coordonates, zero-indexed 50 | n = len(matrix) 51 | down_sq, right_sq = precomp_down_left_squares(matrix) 52 | 53 | for square_size in range(n, 0, -1): 54 | for row in range(0, n-square_size+1): 55 | for col in range(0, n-square_size+1): 56 | if check_square((row, col), square_size, down_sq, right_sq): 57 | return [ 58 | (row, col), 59 | (row+square_size - 1, col + square_size - 1) 60 | ] 61 | 62 | return [] 63 | 64 | 65 | if __name__ == "__main__": 66 | matrix = [ 67 | [0, 1, 1, 1, 1], 68 | [1, 0, 1, 0, 0], 69 | [1, 1, 1, 1, 0], 70 | [1, 0, 1, 1, 1]] 71 | 72 | print(f"Max square in test matrix is {max_square_outline(matrix)}") 73 | -------------------------------------------------------------------------------- /chapter_17/p17_24.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import List 3 | from collections import namedtuple 4 | 5 | ArrLargestSum = namedtuple("ArrLargestSum", "sum indexes") 6 | 7 | 8 | def largest_submatrix_sum(m: List[List[int]]) -> None: 9 | 10 | rows = len(m) 11 | cols = len(m[0]) 12 | 13 | max_now = -float("inf") 14 | row_max_now = (0, 0) 15 | col_max_now = (0, 0) 16 | 17 | for i in range(rows): 18 | col_sums = [0] * cols 19 | for j in range(i, rows): 20 | for col in range(cols): 21 | col_sums[col] += m[j][col] 22 | 23 | max_sum, (start_col, end_col) = get_largest_sum(col_sums) 24 | 25 | # print(f"Rows {i} to {j}, max is between cols {start_col} and {end_col} as {max_sum}") 26 | 27 | if max_sum > max_now: 28 | max_now = max_sum 29 | row_max_now = (i, j) 30 | col_max_now = (start_col, end_col) 31 | 32 | for i in range(row_max_now[0], row_max_now[1]+1): 33 | print() 34 | for j in range(col_max_now[0], col_max_now[1]+1): 35 | print(f"{m[i][j]:3}", end=" ") 36 | 37 | 38 | def precomp_matrix_sums(m: List[List[int]]) -> List[List[int]]: 39 | rows = len(m) 40 | cols = len(m[0]) 41 | # Add padding 42 | sum_m = [[0 for _ in range(cols+1)] for _ in range(rows+1)] 43 | for row in range(1, rows+1): 44 | for col in range(1, cols+1): 45 | elem_here = m[row-1][col-1] 46 | sum_m[row][col] = sum_m[row-1][col] + \ 47 | sum_m[row][col-1] - sum_m[row-1][col-1] + elem_here 48 | 49 | for line in sum_m: 50 | print(line) 51 | 52 | print("-"*50) 53 | 54 | trimmed_arr = [x[1:] for x in sum_m[1:]] 55 | for line in trimmed_arr: 56 | print(line) 57 | 58 | return trimmed_arr 59 | 60 | 61 | def get_largest_sum(arr: List[int]): 62 | csum = 0 63 | cstart = 0 64 | 65 | max_sum = -float("inf") 66 | max_inds = (0, 0) 67 | 68 | for i in range(len(arr)): 69 | elem = arr[i] 70 | csum += elem 71 | 72 | if csum > max_sum: 73 | max_sum = csum 74 | max_inds = (cstart, i) 75 | 76 | if csum < 0: 77 | csum = 0 78 | cstart = i+1 # It at least going to be the next 79 | 80 | return ArrLargestSum(max_sum, max_inds) 81 | 82 | 83 | if __name__ == "__main__": 84 | test_matrix = [ 85 | [1, 2, 3, 4, 5], 86 | [2, 3, -5000, 5, 6], 87 | [10, 20, 30, 40, 50], 88 | ] 89 | 90 | test_matrix_2 = [[1, -2, -1, 4], 91 | [1, -1, 1, 1], 92 | [0, -1, -1, 1], 93 | [0, 0, 1, 1]] 94 | 95 | largest_submatrix_sum(test_matrix_2) 96 | -------------------------------------------------------------------------------- /chapter_17/p17_25.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import List, Tuple 3 | from copy import copy 4 | import utils.datasets 5 | from collections import defaultdict 6 | 7 | 8 | # For each word size, starting with the largest 9 | # Get list of words of that lenght 10 | # Try all combos 11 | # Place one 12 | # For each letter, have a big trie traversal pointer 13 | # Check that there is at least one word with those letters (of the current to add, and the previous letters) 14 | # Also see which ones end (If all, add as candidate) 15 | # Add another 16 | 17 | 18 | def word_square_helper(all_words_trie:dict, 19 | words_of_len: set, 20 | used_words: List[str], 21 | current_trie_pointers: List[dict]): 22 | 23 | current_best = None 24 | for word in words_of_len - set(used_words): 25 | stack_score, new_trie_pointers = get_stack_score(all_words_trie, current_trie_pointers, word) 26 | 27 | if stack_score == -1: 28 | continue 29 | 30 | # print(f"For words used {used_words} and {word:20}, got stack score: {stack_score}") 31 | 32 | if stack_score == len(word): 33 | # print(f"Got a good one at {used_words} and {word}") 34 | current_best = used_words + [word] 35 | 36 | best_of_rest = word_square_helper(all_words_trie, words_of_len, used_words + [word], new_trie_pointers) 37 | if best_of_rest: 38 | current_best = best_of_rest 39 | 40 | return current_best 41 | 42 | def get_stack_score(big_trie, trie_pointer_list, new_word) -> Tuple[int, list]: 43 | new_trie_pointers = copy(trie_pointer_list) 44 | words_that_end_here = 0 45 | for i in range(len(new_word)): 46 | trie_ptr = new_trie_pointers[i] 47 | char_now = new_word[i] 48 | 49 | if char_now not in trie_ptr: 50 | return -1, None 51 | 52 | new_trie_pointers[i]= trie_ptr[char_now] 53 | if "end" in new_trie_pointers[i]: 54 | words_that_end_here+=1 55 | 56 | return words_that_end_here, new_trie_pointers 57 | 58 | def get_largest_word_matrix(word_list: List[str]) -> List[str]: 59 | words_by_len = defaultdict(set) 60 | 61 | max_word_len = len(max(word_list, key=len)) 62 | for word in word_list: 63 | words_by_len[len(word)].add(word) 64 | 65 | big_word_trie = utils.datasets.build_trie_from_words(word_list) 66 | 67 | best_total_chars = 0 68 | best_total_square = None 69 | 70 | for i in range(max_word_len, 0, -1): 71 | print(f"Checking words of len {i}, about {len(words_by_len[i])} of them") 72 | best_of_len = word_square_helper(big_word_trie, words_by_len[i], [], [big_word_trie]*i) 73 | total_chars = len(best_of_len) * i if best_of_len else 0 74 | 75 | if total_chars > best_total_chars: 76 | best_total_chars = total_chars 77 | best_total_square = best_of_len 78 | 79 | return best_total_square 80 | 81 | 82 | if __name__ == "__main__": 83 | big_word_list = utils.datasets.get_system_word_list() 84 | big_word_list = list(filter(lambda x: 6 > len(x) > 2, big_word_list)) 85 | 86 | result = get_largest_word_matrix(big_word_list) 87 | for word in result: 88 | print("".join(c + " " for c in word) + f" in worlist? {word in big_word_list}") 89 | 90 | for i in range(len(result[0])): 91 | colword= "".join(result[j][i] for j in range(len(result))) 92 | 93 | print(f"Colword {colword} in wordlist ? {colword in big_word_list}") 94 | 95 | 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | -------------------------------------------------------------------------------- /chapter_17/p17_26.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple 2 | from collections import defaultdict 3 | 4 | 5 | def sparse_similarity(docs_raw: List[Tuple[int, List[int]]]) -> None: 6 | doc_dict = dict(docs_raw) 7 | rev_index = defaultdict(set) 8 | 9 | for doc_id, doc_words in doc_dict.items(): 10 | for word in doc_words: 11 | rev_index[word].add(doc_id) 12 | 13 | intersect_of_pairs = defaultdict(int) 14 | 15 | for doc_id, doc_words in doc_dict.items(): 16 | for word in doc_words: 17 | for other_doc in rev_index[word]: 18 | # We assume an ordering of the doc ids, 19 | # as to prevent duplicate counting 20 | if other_doc <= doc_id: 21 | continue 22 | 23 | intersect_of_pairs[(doc_id, other_doc)] += 1 24 | 25 | print(f"ID1, ID2:\tSIMILARITY") 26 | for (doc1, doc2), in_common in intersect_of_pairs.items(): 27 | union_size = len(doc_dict[doc1]) + len(doc_dict[doc2]) - in_common 28 | docs_simil = in_common / union_size 29 | 30 | print(f"{doc1:3}, {doc2:3}:\t{docs_simil}") 31 | 32 | 33 | if __name__ == "__main__": 34 | ex = [ 35 | (13, [14, 15, 100, 9, 3]), 36 | (16, [32, 1, 9, 3, 5]), 37 | (19, [15, 29, 2, 6, 8, 7]), 38 | (24, [7, 10]) 39 | ] 40 | 41 | sparse_similarity(ex) 42 | -------------------------------------------------------------------------------- /chapter_17/p17_3.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | # Method: Reservoir sampling 4 | # Time: O(n) 5 | # Space: O(m) 6 | 7 | 8 | def get_subset(arr, m): 9 | if m >= len(arr): 10 | raise Exception("Subset cannot be larger than array") 11 | subset = arr[:m] 12 | for i in range(m, len(arr)): 13 | place = random.randint(0, i) 14 | if place < m: 15 | subset[place] = arr[i] 16 | return subset 17 | 18 | 19 | if __name__ == "__main__": 20 | arr = list(range(10)) 21 | for i in range(5): 22 | print(get_subset(arr, 5)) 23 | -------------------------------------------------------------------------------- /chapter_17/p17_4.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | # Method: Count expected evens and odds, tehnically O(log_2(n^2)) 4 | # Time: O(n) 5 | # Space: O(n) 6 | 7 | 8 | def get_bit(a: int, bit_nr: int) -> int: 9 | shifted_a = a >> (bit_nr) 10 | return shifted_a & 0b1 11 | 12 | 13 | def find_mssing(arr: List[int], n: int) -> int: 14 | return find_missing_helper(arr, list(range(len(arr))), 0, n) 15 | 16 | 17 | def find_missing_helper( 18 | arr: List[int], list_indexes: List[int], bit_offset: int, n: int 19 | ) -> int: 20 | if n == 0: 21 | return 0 22 | 23 | odds = [] 24 | evens = [] 25 | for i in list_indexes: 26 | bit_now = get_bit(arr[i], bit_offset) 27 | if bit_now: 28 | odds.append(i) 29 | else: 30 | evens.append(i) 31 | 32 | expected_odds = 0 33 | expected_evens = 0 34 | for i in range(n + 1): 35 | if i & 0b1: 36 | expected_odds += 1 37 | else: 38 | expected_evens += 1 39 | 40 | if len(evens) < expected_evens: 41 | bit_now = 0 42 | rest = find_missing_helper(arr, evens, bit_offset + 1, n >> 1) 43 | else: 44 | bit_now = 1 45 | rest = find_missing_helper(arr, odds, bit_offset + 1, n >> 1) 46 | 47 | # print(f"Bit now is {bit_now}, rest {rest}," 48 | # f" evens: {evens} (expected {expected_evens})," 49 | # f" odds: {odds} (expected {expected_odds})") 50 | return (rest << 1) | bit_now 51 | 52 | 53 | if __name__ == "__main__": 54 | exs = [ 55 | ([0, 1, 2], 3), 56 | ([1, 2, 3, 4, 5, 6, 7, 8], 8), 57 | ([0, 1, 2, 3, 5], 5), 58 | ([1, 2, 3, 4, 5, 6, 7, 8, 0], 9), 59 | ] 60 | 61 | for arr, n in exs: 62 | print(f"In arr {arr}, with limit {n}, missing is {find_mssing(arr,n)}") 63 | -------------------------------------------------------------------------------- /chapter_17/p17_5.py: -------------------------------------------------------------------------------- 1 | # Method: Store seen letter/number balances (the first location) 2 | # Time: O(n) 3 | # Space: O(n) 4 | 5 | 6 | def longest_subarr_number_letter_equal(arr: str) -> int: 7 | more_letters_now = 0 8 | seen_offsets = {0: -1} 9 | max_arr_size = 0 10 | max_arr_indexes = (-1, -1) 11 | 12 | for i in range(len(arr)): 13 | x = arr[i] 14 | 15 | if x.isdigit(): 16 | more_letters_now -= 1 17 | else: 18 | more_letters_now += 1 19 | 20 | # print(f"Offsets: {seen_offsets}, letters more now: {more_letters_now}, elem now {x}") 21 | 22 | if more_letters_now in seen_offsets: 23 | max_arr_now = i - seen_offsets[more_letters_now] 24 | if max_arr_now > max_arr_size: 25 | max_arr_size = max_arr_now 26 | max_arr_indexes = (seen_offsets[more_letters_now] + 1, i) 27 | else: 28 | seen_offsets[more_letters_now] = i 29 | 30 | return max_arr_size, max_arr_indexes 31 | 32 | 33 | if __name__ == "__main__": 34 | exs = ["aaa2222aa22aa222aaaa", "aaa222aa", "e4ee2a2a2a", "z0123456789a"] 35 | for ex_arr in exs: 36 | max_arr, (start, end) = longest_subarr_number_letter_equal(ex_arr) 37 | print( 38 | f"Max arr with eq is of len {max_arr}, " 39 | f"namely: {ex_arr[start:end+1]}, from {ex_arr}" 40 | ) 41 | -------------------------------------------------------------------------------- /chapter_17/p17_6.py: -------------------------------------------------------------------------------- 1 | # Method: Math counting digit by digit 2 | # Time: O(log(n)) 3 | # Space: O(1) 4 | 5 | # Go digit by digit (keep dividing by higher powers of 10) 6 | # At each stage, count *100 occurances in int div 7 | # For ind mod, -> case 1, over 2: count all two's of the remainder 8 | # -> case 2, on 2: count all two's of the remainder of this level 9 | # -> case 3: below 2: no twos 10 | # 25 11 | # 9 12 | 13 | 14 | def count_of_digit(x: int, digit=2): 15 | pow_10_now = 1 16 | digit_count = 0 17 | while x // pow_10_now > 0: 18 | # Left in the rest of the number (more significant digits) 19 | pow_10_next = pow_10_now * 10 20 | full_units = x // pow_10_next 21 | digit_count += (pow_10_now) * (full_units) 22 | 23 | remainder = x % pow_10_next 24 | rem_first_digit = remainder // pow_10_now 25 | 26 | if rem_first_digit > digit: 27 | digit_count += pow_10_now 28 | elif rem_first_digit == digit: 29 | digit_count += remainder % pow_10_now + 1 30 | 31 | # print( 32 | # f"Cpow {cpow_10}, count_twos {count_digit}, full_units {full_units}, " 33 | # f"remainder {remainder}, first digit {rem_first_digit}" 34 | # ) 35 | 36 | pow_10_now *= 10 37 | return digit_count 38 | 39 | 40 | def count_of_two(x: int): 41 | return count_of_digit(x, 2) 42 | 43 | 44 | if __name__ == "__main__": 45 | for i in range(0, 1000, 50): 46 | print(f"For {i}, two's to it are {count_of_two(i)}") 47 | -------------------------------------------------------------------------------- /chapter_17/p17_7.py: -------------------------------------------------------------------------------- 1 | from typing import Tuple, List 2 | from utils.graphs import Vertex 3 | 4 | 5 | # Method: Build graph, DFS 6 | # Time: O(V + E) 7 | # Space: O(V + E) 8 | # Notes: V = vertexes, nr of names 9 | # E = edges, nr of name equivalencies 10 | 11 | 12 | def get_truename_occurences( 13 | names_list: List[tuple], name_pairs: List[tuple] 14 | ) -> List[tuple]: 15 | name_dict = {name: Vertex((name, occ)) for name, occ in names_list} 16 | 17 | # Build graph 18 | for name1, name2 in name_pairs: 19 | if name1 not in name_dict or name2 not in name_dict: 20 | continue 21 | name_dict[name1].edges.add(name_dict[name2]) 22 | name_dict[name2].edges.add(name_dict[name1]) 23 | 24 | seen_nodes = set() 25 | name_totals = {} 26 | 27 | # DFS on the other names 28 | for name, name_vertex in name_dict.items(): 29 | if name in seen_nodes: 30 | continue 31 | name_totals[name] = traverse_connected_names(name_vertex, seen_nodes) 32 | 33 | return list(name_totals.items()) 34 | 35 | 36 | def traverse_connected_names(vertex: Vertex, seen: set) -> int: 37 | name, occ = vertex.val 38 | if name in seen: 39 | return 0 40 | 41 | seen.add(name) 42 | extra_sum = sum(traverse_connected_names(child, seen) for child in vertex.edges) 43 | return occ + extra_sum 44 | 45 | 46 | if __name__ == "__main__": 47 | ex_names = [ 48 | ("John", 15), 49 | ("Jon", 12), 50 | ("Chris", 13), 51 | ("Kris", 4), 52 | ("Christopher", 19), 53 | ] 54 | ex_pairs = [ 55 | ("John", "Jon"), 56 | ("John", "Johny"), 57 | ("Chris", "Kris"), 58 | ("Chris", "Christopher"), 59 | ] 60 | 61 | print(f"True counts are {get_truename_occurences(ex_names, ex_pairs)}") 62 | -------------------------------------------------------------------------------- /chapter_17/p17_8.py: -------------------------------------------------------------------------------- 1 | from typing import List, Tuple 2 | 3 | 4 | # Method: DP, prev tower possiblity construction 5 | # Time: O(n^2) 6 | # Space: O(2^n) 7 | # Not sure on space, every possible stacking ? 8 | 9 | 10 | def can_add(bot: tuple, top: tuple) -> bool: 11 | return bot[0] > top[0] and bot[1] > top[1] 12 | 13 | 14 | def get_circus_tower(arr: List[tuple]) -> List[tuple]: 15 | seqs = [] # Start poz -> list of heights 16 | sorted_arr = sorted(arr, reverse=True) 17 | 18 | # For every performer 19 | # (and every seq from prev performers, 20 | # see if you can add them) 21 | for i in range(len(arr)): 22 | for good_seq in filter(lambda seq: can_add(seq[-1], sorted_arr[i]), seqs): 23 | good_seq.append(sorted_arr[i]) 24 | 25 | # for j in range(len(seqs)): 26 | # if can_add(seqs[j][-1], sorted_arr[i]): 27 | # seqs[j].append(sorted_arr[i]) 28 | seqs.append([sorted_arr[i]]) 29 | return max(seqs, key=len) 30 | 31 | 32 | if __name__ == "__main__": 33 | exs = [ 34 | [(65, 100), (70, 150), (56, 90), (75, 190), (60, 95), (68, 110)], 35 | ] 36 | 37 | for perf_dim_arr in exs: 38 | print( 39 | f"Max tower is {get_circus_tower(perf_dim_arr)}" 40 | f" for performers {perf_dim_arr}" 41 | ) 42 | -------------------------------------------------------------------------------- /chapter_17/p17_9.py: -------------------------------------------------------------------------------- 1 | from collections import deque 2 | 3 | 4 | # Method: Continous queue building for multiples with 3, 5, 7 5 | # Time: O(k) 6 | # Space: O(k) 7 | 8 | 9 | def analyse_elem(x: int): 10 | divs = (3, 5, 7) 11 | div_occ = [0] * len(divs) 12 | for i in range(len(divs)): 13 | div = divs[i] 14 | temp_x = x 15 | while temp_x % div == 0: 16 | div_occ[i] += 1 17 | temp_x //= div 18 | 19 | return div_occ, sum(div_occ) 20 | 21 | 22 | def get_kth_357(k: int): 23 | queues = {3: deque(), 5: deque(), 7: deque()} 24 | 25 | queues[3].append(3) 26 | queues[5].append(5) 27 | queues[7].append(7) 28 | 29 | elem_now = 0 30 | for i in range(k): 31 | min_q_key = min(queues, key=lambda x: queues[x][0]) 32 | elem_now = queues[min_q_key].popleft() 33 | # print(f"Series elem {i}\t{elem_now:20}, {analyse_elem(elem_now)}") 34 | 35 | for x in queues: 36 | if x >= min_q_key: 37 | # If min_q_key * some_sufix used, 38 | # all q_key where q_key < min_q_keys 39 | # will have already been used 40 | queues[x].append(elem_now * x) 41 | 42 | return elem_now 43 | 44 | 45 | if __name__ == "__main__": 46 | k = 100 47 | print(get_kth_357(k)) 48 | -------------------------------------------------------------------------------- /chapter_17/readme.md: -------------------------------------------------------------------------------- 1 | # Chapter 17: Hard 2 | 3 | 4 | | Nr. | Title | Solution | Time | Space | Notes | 5 | |:---: |:-----: |:--------: |:----: |:-----: |:-----: | 6 | | 17.1 | Add without plus | [Python](./17_1.py) | O(log(n)) | O(log(n) | | 7 | | 17.2 | Shuffle | [Python](./17_2.py) | O(n) | O(n) | | 8 | | 17.3 | Random Set | [Python](./17_3.py) | O(n) | O(m) | | 9 | | 17.4 | Missing Number | [Python](./17_4.py) | O(n) | O(n) | | 10 | | 17.5 | Letters and Numbers | [Python](./17_5.py) | O(n) | O(n) | | 11 | | 17.6 | Count of 2s | [Python](./17_6.py) | O(log(n)) | O(1) | | 12 | | 17.7 | Baby Names | [Python](./17_7.py) | O(V+E)| O(V+E) | | 13 | | 17.8 | Circus Tower | [Python](./17_8.py) | O(n^2) | O(2^n) | | 14 | | 17.9 | Kth Multiple | [Python](./17_9.py) | O(k)| O(k) | Ok | 15 | | 17.10 | Majority Element | [Python](./17_10.py) | O(n) | O(1) | | 16 | | 17.11 | Word Distance | [Python](./17_11.py) | O(w\*o + max(o))| O(w\*o) | | 17 | | 17.12 | BiNode | [Python](./17_12.py) | O(n) | O(d) | | 18 | | 17.13 | Re-space | [Python](./17_13.py) | O(n^2) | O(n) | | 19 | | 17.14 | Smallest K | [Python](./17_14.py) | O(n * log(k))| O(k) | | 20 | | 17.15 | Longest Word | [Python](./17_15.py) | O(w*(lw^2))| O(w*(lw^2)) | | 21 | | 17.16 | The Masseuse | [Python](./17_16.py) | O(n) | O(n) | | 22 | | 17.17 | Multi-search | [Python](./17_17.py) | O(n) | O(n) | | 23 | | 17.18 | Shortest Subsequence | [Python](./17_18.py) | O(b^2 + w * b)| O(b^2) | | 24 | | 17.19 | Missing Two | [Python](./17_19.py) | O(n) | O(1) | | 25 | | 17.20 | Continuous Median | [Python](./17_20.py) | | | | 26 | | 17.21 | Volume of Histogram | [Python](./17_21.py) | | | | 27 | | 17.22 | Word Transformer | [Python](./17_22.py) | | | | 28 | | 17.23 | Max Black Square | [Python](./17_23.py) | | | | 29 | | 17.24 | Max Submatrix | [Python](./17_24.py) | | | | 30 | | 17.25 | Word Rectangle | [Python](./17_25.py) | | | | 31 | | 17.26 | Sparse Similarity | [Python](./17_26.py) | | | | -------------------------------------------------------------------------------- /chapter_2/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StBogdan/CTCI_python/3a606f14e57e263b9a6c468da2990d502824a3ef/chapter_2/__init__.py -------------------------------------------------------------------------------- /chapter_2/linked_list_utils.py: -------------------------------------------------------------------------------- 1 | class Node: 2 | val: int 3 | next = None 4 | 5 | def __init__(self, val, next_node=None): 6 | self.val = val 7 | self. next = next_node 8 | 9 | def __str__(self): 10 | return f"({self.val}) -> {self.next}" 11 | 12 | 13 | def link_list_of_list(arr) -> Node: 14 | head = Node(arr[0]) 15 | now_elem = head 16 | for x in arr[1:]: 17 | now_elem.next = Node(x) 18 | now_elem = now_elem.next 19 | return head 20 | 21 | 22 | def llol(arr): 23 | """Shorthand for (make) linked-list of list 24 | """ 25 | return link_list_of_list(arr) 26 | 27 | 28 | def lllen(head: Node) -> int: 29 | return len_of_ll(head) 30 | 31 | 32 | def len_of_ll(head: Node) -> int: 33 | count = 0 34 | elem_now = head 35 | while elem_now: 36 | count += 1 37 | elem_now = elem_now.next 38 | 39 | return count 40 | 41 | 42 | if __name__ == "__main__": 43 | ex1 = [1, 2, 3] 44 | print(link_list_of_list(ex1)) 45 | -------------------------------------------------------------------------------- /chapter_2/p2_1.py: -------------------------------------------------------------------------------- 1 | from chapter_2.linked_list_utils import Node, llol 2 | 3 | # Method: Use a set 4 | # Time: O(n) 5 | # Space: O(n) 6 | 7 | # Method (no buffer): Pick one, try to find in rest of linked list 8 | # Time: O(n^2) 9 | # Space: O(1) 10 | 11 | 12 | def remove_dups_ll(head: Node): 13 | seen = set() 14 | prev = None 15 | while head: 16 | if head.val in seen: 17 | prev.next = head.next 18 | else: 19 | seen.add(head.val) 20 | prev = head 21 | head = head.next 22 | 23 | 24 | def remove_dups_nobuf(head: Node): 25 | rc = head 26 | while rc: 27 | target = rc.val 28 | prev = rc 29 | rest_ptr = rc.next 30 | while rest_ptr: 31 | if rest_ptr.val == target: 32 | prev.next = rest_ptr.next 33 | else: 34 | prev = rest_ptr 35 | rest_ptr = rest_ptr.next 36 | rc = rc.next 37 | 38 | 39 | if __name__ == "__main__": 40 | exs = [[1, 2, 3, 4, 5], 41 | [11, 2, 3, 4, 5, 6, 11], 42 | [11, 2, 3, 4, 5, 6, 11, 11, 11, 23], 43 | [0], 44 | [0, 0], 45 | [1, 2, 1, 2, 1, 2, 1, 2, 1, 2, 2, 3, 1]] 46 | 47 | for ex in exs: 48 | r1 = llol(ex) 49 | r2 = llol(ex) 50 | remove_dups_nobuf(r1) 51 | remove_dups_nobuf(r2) 52 | print(f"For ex {ex} for ans 1: {r1}") 53 | print(f"For ex {ex} for ans 2: {r2}") 54 | print("-"*50) 55 | -------------------------------------------------------------------------------- /chapter_2/p2_2.py: -------------------------------------------------------------------------------- 1 | from chapter_2.linked_list_utils import * 2 | 3 | # Method: Leader and follower pointer, at k distance 4 | # Time: O(n) 5 | # Space: O(1) 6 | 7 | 8 | def k_to_last(k: int, head: Node): 9 | leader_ptr = head 10 | follower_ptr = head 11 | distance = -1 12 | 13 | while leader_ptr: 14 | print(f"Leader is {leader_ptr} " 15 | f"and follower {follower_ptr}," 16 | f" at distance {distance}") 17 | distance += 1 18 | if distance > k: 19 | follower_ptr = follower_ptr.next 20 | leader_ptr = leader_ptr.next 21 | 22 | if distance < k: 23 | raise Exception(f"Input too short for distance {distance}") 24 | 25 | return follower_ptr.val 26 | 27 | 28 | if __name__ == "__main__": 29 | ex1 = link_list_of_list([1, 2, 3, 4, 5, 6, 7]) 30 | d = 5 31 | print(ex1) 32 | print(k_to_last(d, ex1)) 33 | -------------------------------------------------------------------------------- /chapter_2/p2_3.py: -------------------------------------------------------------------------------- 1 | from chapter_2.linked_list_utils import llol, Node 2 | 3 | # Method: Identity theft 4 | # Time: O(1) 5 | # Space: O(1) 6 | 7 | 8 | def remove_mid(mid_node: Node): 9 | victim = mid_node.next 10 | if not victim: 11 | raise Exception("No following nodes") 12 | 13 | # Take the value, and point to the next (can be none) 14 | mid_node.val = victim.val 15 | mid_node.next = victim.next 16 | victim.next = None 17 | 18 | 19 | if __name__ == "__main__": 20 | exs = [([1, 2, 3, 4, 5], 3), 21 | ([1, 1, 1, 1], 1), 22 | ([1, 2, 4, 5, 6, 7], 2), 23 | ([1, 2, 3], 3)] 24 | 25 | for arr, elem in exs: 26 | head = llol(arr) 27 | in_node = head 28 | while in_node.val != elem: 29 | in_node = in_node.next 30 | 31 | try: 32 | remove_mid(in_node) 33 | print(f"For input {arr} to remove {elem} got result {head}") 34 | except Exception as e: 35 | print(f"For input {arr} to remove {elem} got error: {e}") 36 | print("-"*50) 37 | -------------------------------------------------------------------------------- /chapter_2/p2_4.py: -------------------------------------------------------------------------------- 1 | from chapter_2.linked_list_utils import llol, Node 2 | 3 | # Method: Construct 2 other lists 4 | # Time: O(n) 5 | # Space: O(1) 6 | 7 | 8 | def part_around(x: int, head: Node): 9 | low_head = None 10 | high_head = None 11 | low_root = None 12 | high_root = None 13 | 14 | elem_now = head 15 | while elem_now: 16 | elem_next = elem_now.next 17 | if elem_now.val >= x: 18 | if not high_root: 19 | high_head = elem_now 20 | high_root = elem_now 21 | else: 22 | high_head.next = elem_now 23 | high_head = elem_now 24 | else: 25 | if not low_head: 26 | low_head = elem_now 27 | low_root = elem_now 28 | else: 29 | low_head.next = elem_now 30 | low_head = elem_now 31 | elem_now = elem_next 32 | 33 | if low_root: 34 | low_head.next = high_root # Connect the parts 35 | high_head.next = None 36 | return low_root 37 | else: 38 | return high_root # No low elems 39 | 40 | 41 | if __name__ == "__main__": 42 | exs = [([1, 2, 3, 4, 5, 6, 7], 4), 43 | ([10, 4, 7, 3, 1, 200, 40, 50, 3], 4), 44 | ([30, 40, 50], 30), 45 | ([1, 2, 5, 3, 2, 200], 3) 46 | ] 47 | 48 | for arr, x in exs: 49 | head = llol(arr) 50 | print( 51 | f"For\t{head} around partition {x},\ngot\t{part_around(x, head)}") 52 | print("-"*50) 53 | -------------------------------------------------------------------------------- /chapter_2/p2_5.py: -------------------------------------------------------------------------------- 1 | from chapter_2.linked_list_utils import Node, lllen, llol 2 | 3 | # Backward oder (head at least significant) 4 | # Method: Digit by digit, with carry 5 | # Time: O(n) 6 | # Space: O(n) (Constant if you overwrite one of the inputs) 7 | 8 | # Forward order 9 | # Method: Pad to len, recurse down 10 | # Time: O(n) 11 | # Space: O(n) 12 | 13 | 14 | def sum_ll(tl1: Node, tl2: Node): 15 | nl = None 16 | nlh = None 17 | carry_flag = 0 18 | 19 | if lllen(tl1) < lllen(tl2): 20 | tl1, tl2 = tl2, tl1 21 | 22 | while tl1 and tl2: 23 | dsum = tl1.val + tl2.val + carry_flag 24 | 25 | carry_flag = dsum // 10 26 | dsum %= 10 27 | 28 | if not nl: 29 | nl = Node(dsum) 30 | nlh = nl 31 | else: 32 | nl.next = Node(dsum) 33 | nl = nl.next 34 | 35 | tl1 = tl1.next 36 | tl2 = tl2.next 37 | 38 | while tl1: # The longer one 39 | cval = tl1.val + carry_flag 40 | carry_flag = cval // 10 41 | cval %= 10 42 | 43 | nl.next = Node(cval) 44 | nl = nl.next 45 | 46 | tl1 = tl1.next 47 | 48 | if tl1 is None and carry_flag: 49 | nl.next = Node(1) 50 | 51 | return nlh 52 | 53 | 54 | # The follow up, linked list is in reverse order 55 | def pad_to_len(head, to_pad): 56 | for _ in range(to_pad): 57 | n = Node(0) 58 | n.next = head 59 | head = n 60 | return head 61 | 62 | 63 | def sum_ll_reverse(tl1, tl2): 64 | len1 = lllen(tl1) 65 | len2 = lllen(tl2) 66 | 67 | if len1 > len2: 68 | tl2 = pad_to_len(tl2, len1-len2) 69 | elif len2 != len1: 70 | tl1 = pad_to_len(tl1, len2-len1) 71 | 72 | res, carry = sum_ll_rec(tl1, tl2) 73 | if carry: 74 | n = Node(1) 75 | n.next = res 76 | return n 77 | else: 78 | return res 79 | 80 | 81 | def sum_ll_rec(tl1, tl2): 82 | if tl1 is None and tl2 is None: 83 | return None, 0 84 | rest, carry = sum_ll_rec(tl1.next, tl2.next) 85 | 86 | cval = tl1.val + tl2.val + carry 87 | head = Node(cval % 10) 88 | 89 | head.next = rest 90 | return head, cval//10 91 | 92 | 93 | if __name__ == "__main__": 94 | exs = [([1, 2, 3, 4, 5], [1, 2, 3]), 95 | ([9, 9, 9, 9], [2, 1]), 96 | ([9], [1])] 97 | 98 | for l1, l2 in exs: 99 | print(f"Backward result of sum of\t {l1} and {l2} is {sum_ll(llol(l1), llol(l2))}" 100 | f" same as {sum_ll(llol(l2), llol(l1))}") 101 | 102 | ll1r = llol(list(reversed(l1))) 103 | ll2r = llol(list(reversed(l2))) 104 | 105 | print(f"Forward result of sum of\t {l1} and {l2} is {sum_ll_reverse(llol(l1), llol(l2))}" 106 | f" same as {sum_ll_reverse(llol(l2), llol(l1))}") 107 | print("-"*50) 108 | 109 | 110 | -------------------------------------------------------------------------------- /chapter_2/p2_6.py: -------------------------------------------------------------------------------- 1 | from chapter_2.linked_list_utils import Node, llol 2 | 3 | # Method: Slow/Fast pointer, push to stack second half and check 4 | # Time: O(n) 5 | # Space: O(n) 6 | 7 | 8 | def check_ll_palindrome(head: Node): 9 | slow_len = -1 10 | fast_len = -1 11 | 12 | if not head: 13 | return True 14 | 15 | slow_ptr = head 16 | fast_ptr = head 17 | stack = [] 18 | while fast_ptr: 19 | slow_len += 1 20 | fast_len += 2 21 | stack.append(slow_ptr.val) 22 | 23 | slow_ptr = slow_ptr.next 24 | fast_ptr = fast_ptr.next 25 | 26 | if fast_ptr: 27 | fast_ptr = fast_ptr.next 28 | if not fast_ptr: 29 | fast_len += 1 30 | 31 | if fast_len > 0 and fast_len % 2: 32 | stack.pop() 33 | 34 | print(f"Stack is {stack}, slp is {slow_ptr}") 35 | 36 | while slow_ptr: 37 | if slow_ptr.val != stack.pop(): 38 | return False 39 | slow_ptr = slow_ptr.next 40 | 41 | return not stack 42 | 43 | 44 | if __name__ == "__main__": 45 | 46 | exs = [ 47 | [], 48 | [4], 49 | [1, 2, 2], 50 | [1, 2, 3, 4], 51 | [1, 1, 2, 1], 52 | [1, 1, 1], 53 | [1, 1, 1, 1], 54 | [1, 2, 0, 2, 1], 55 | ] 56 | 57 | for ex in exs: 58 | h_ex = llol(ex) if ex else None 59 | print(f"Result for {ex} is {check_ll_palindrome(h_ex)}") 60 | print("-"*50) 61 | -------------------------------------------------------------------------------- /chapter_2/p2_7.py: -------------------------------------------------------------------------------- 1 | from chapter_2.linked_list_utils import Node, link_list_of_list, lllen, llol 2 | 3 | # Method: Find total lenght, walk together until meet 4 | # Time: O(n) 5 | # Space: O(1) 6 | 7 | 8 | def get_intersecting_node(h1: Node, h2: Node): 9 | len_ll1 = lllen(h1) 10 | len_ll2 = lllen(h2) 11 | 12 | if get_last_of_ll(h1) != get_last_of_ll(h2): 13 | return None 14 | 15 | if len_ll1 > len_ll2: 16 | short_ll = h2 17 | longer_ll = h1 18 | else: 19 | short_ll = h1 20 | longer_ll = h2 21 | 22 | longer_ll = adjust(longer_ll, abs(len_ll1 - len_ll2)) 23 | while longer_ll != short_ll: 24 | longer_ll = longer_ll.next 25 | short_ll = short_ll.next 26 | return longer_ll.val 27 | 28 | 29 | def adjust(head: Node, jumps): 30 | h = head 31 | for _ in range(jumps): 32 | h = h.next 33 | return h 34 | 35 | 36 | def get_last_of_ll(head_in: Node): 37 | h = head_in 38 | while h.next: 39 | h = h.next 40 | return h 41 | 42 | 43 | if __name__ == "__main__": 44 | exs = [([1, 2, 3, 4, 5, 6], [10, 20, 30], 4), 45 | ([1, 2, 3, 4, 5], [11, 22, 33, 44], 4), 46 | ([1, 2], [32, 43, 54, 65, 76], 1)] 47 | for a1, a2, con_point in exs: 48 | ll1 = llol(a1) 49 | ll2 = llol(a2) 50 | 51 | last_of_2 = get_last_of_ll(ll2) 52 | last_of_2.next = adjust(ll1, con_point) 53 | 54 | print(f"For lists {a1} and {a2} joined at {con_point}, \ 55 | intersect node is : {get_intersecting_node(ll1,ll2)}") 56 | -------------------------------------------------------------------------------- /chapter_2/p2_8.py: -------------------------------------------------------------------------------- 1 | from chapter_2.linked_list_utils import Node 2 | 3 | # Method: Fast/Slow pointer to meeting, restart pointer at beginning 4 | # Time: O(n) 5 | # Space: O(1) 6 | 7 | def get_first_loop_elem(h: Node): 8 | if not h.next: 9 | return None 10 | 11 | slp = h.next 12 | fstp = h.next.next 13 | 14 | while fstp and slp != fstp: 15 | slp = slp.next 16 | fstp = fstp.next 17 | 18 | if not fstp: 19 | return None 20 | 21 | fstp = fstp.next 22 | print(f"Slow pointer at {slp.val}") 23 | 24 | if not fstp: 25 | return None 26 | 27 | slp2 = h 28 | 29 | while slp2 != slp: 30 | slp2 = slp2.next 31 | slp = slp.next 32 | 33 | return slp.val 34 | 35 | 36 | if __name__ == "__main__": 37 | 38 | nodes = [Node(1)] 39 | for x in range(1, 10): 40 | nodes.append(Node(x)) 41 | nodes[-2].next = nodes[-1] 42 | 43 | print(f"Got linked list {nodes[0]}") 44 | print( 45 | f"Does linked list have loop starting at ? \ 46 | {get_first_loop_elem(nodes[0])}") 47 | nodes[-1].next = nodes[8] 48 | 49 | print(f"Does linked list have loop starting at ? \ 50 | {get_first_loop_elem(nodes[0])}") 51 | -------------------------------------------------------------------------------- /chapter_2/readme.md: -------------------------------------------------------------------------------- 1 | # Chapter 2: Linked Lists 2 | 3 | The complexity table is for a quick lookup, more details in the actual file (e.g. explaining the symbols used) 4 | 5 | | Nr. | Title | Solution | Time | Space | Notes | 6 | |:---: |:-----: |:--------: |:----: |:-----: |:-----: | 7 | | 2.1 | Remove dups | [Python](./p2_1.py) | O(n^2) | O(1) | | 8 | | 2.2 | Return Kth to last | [Python](./p2_2.py) | O(n)) | O(1) | | 9 | | 2.3 | Delete Middle Node | [Python](./p2_3.py) | O(1) | O(1) | | 10 | | 2.4 | Partition | [Python](./p2_4py) | O(n) | O(1) | | 11 | | 2.5 | Sum Lists | [Python](./p2_5.py) | O(n) | O(n) | | 12 | | 2.6 | Palindrome | [Python](./p2_6.py) | O(n) | O(n) | | 13 | | 2.7 | Intersection | [Python](./p2_7.py) | O(n) | O(1) | | 14 | | 2.8 | Loop Detection | [Python](./p2_8.py) | O(n) | O(1) | | 15 | -------------------------------------------------------------------------------- /chapter_3/datastructs.py: -------------------------------------------------------------------------------- 1 | from chapter_2.linked_list_utils import Node 2 | 3 | 4 | class Stack: 5 | top: Node = None 6 | size: int = 0 7 | 8 | def push(self, elem): 9 | self.size += 1 10 | if not self.top: 11 | self.top = Node(elem) 12 | else: 13 | new_node = Node(elem) 14 | new_node.next = self.top 15 | self.top = new_node 16 | 17 | def pop(self): 18 | if not self.top: 19 | raise Exception("Cannot pop from an empty stack") 20 | 21 | self.size -= 1 22 | 23 | ret_elem = self.top.val 24 | self.top = self.top.next 25 | return ret_elem 26 | 27 | def peek(self): 28 | return self.top.val 29 | 30 | def isEmpty(self): 31 | return self.top is None 32 | 33 | def __str__(self): 34 | return self.top.__str__() 35 | 36 | 37 | class Queue: 38 | top: Node = None 39 | bot: Node = None 40 | size: int = 0 41 | 42 | def push(self, elem): 43 | self.size += 1 44 | if not self.top: 45 | self.top = Node(elem) 46 | self.bot = self.top 47 | else: 48 | new_node = Node(elem) 49 | self.top.next = new_node 50 | self.top = new_node 51 | 52 | def pop(self): 53 | if not self.bot: 54 | raise Exception("Cannot pop from an empty queue") 55 | 56 | self.size -= 1 57 | 58 | ret_elem = self.bot.val 59 | self.bot = self.bot.next 60 | if self.bot is None: 61 | self.top = None 62 | 63 | return ret_elem 64 | 65 | def peek(self): 66 | return self.bot.val 67 | 68 | def isEmpty(self): 69 | return self.bot is None 70 | 71 | def __str__(self): 72 | return self.bot.__str__() 73 | 74 | 75 | def test_stack(): 76 | s1 = Stack() 77 | for i in range(10): 78 | s1.push(i) 79 | 80 | print(s1) 81 | for i in range(10): 82 | print(s1.pop()) 83 | 84 | try: 85 | s1.pop() 86 | except Exception as e: 87 | print(f"Oh no, got {e}") 88 | 89 | 90 | def test_queue(): 91 | q1 = Queue() 92 | for i in range(5): 93 | q1.push(i) 94 | 95 | print(f"Is empty ? {q1.isEmpty()} ---> {q1}") 96 | 97 | for i in range(5): 98 | print(q1.pop()) 99 | 100 | try: 101 | q1.pop() 102 | except Exception as e: 103 | print(f"Oh no, got {e}, but queue has elem ? {q1.isEmpty()}") 104 | 105 | for i in range(5): 106 | q1.push(i) 107 | 108 | print(q1.pop()) 109 | 110 | 111 | if __name__ == '__main__': 112 | test_stack() 113 | test_queue() 114 | -------------------------------------------------------------------------------- /chapter_3/p3_2.py: -------------------------------------------------------------------------------- 1 | from chapter_3.datastructs import Stack 2 | 3 | MAX_INT = 2**32-1 4 | 5 | 6 | class MinStack: 7 | def __init__(self): 8 | self.stack = Stack() 9 | 10 | def pop(self): 11 | return self.stack.pop()[0] 12 | 13 | def min(self): 14 | if self.stack.isEmpty(): 15 | raise Exception("Empty stack has no minimum") 16 | 17 | _, min_elem = self.stack.peek() 18 | return min_elem 19 | 20 | def push(self, elem): 21 | current_min = self.min() if not self.stack.isEmpty() else MAX_INT 22 | 23 | nmin = min(elem, current_min) 24 | self.stack.push((elem, nmin)) 25 | 26 | 27 | if __name__ == "__main__": 28 | min_stack = MinStack() 29 | for i in range(10): 30 | min_stack.push(10-i) 31 | 32 | print(min_stack) 33 | for i in range(9): 34 | print( 35 | f"Got from stack elem {min_stack.pop()}, minimum is now {min_stack.min()}") 36 | -------------------------------------------------------------------------------- /chapter_3/p3_3.py: -------------------------------------------------------------------------------- 1 | from chapter_2.linked_list_utils import Node 2 | from typing import List 3 | from chapter_3.datastructs import Stack 4 | 5 | 6 | class SetOfStacks: 7 | STACK_CAP = 10 8 | 9 | def __init__(self): 10 | self.stacks: List[Stack] = [] # Stack vector 11 | self.size = 0 12 | self.top = None 13 | 14 | def pop(self): 15 | if self.size == 0: 16 | raise Exception("Empty Stack") 17 | 18 | if self.stacks[-1].size % self.STACK_CAP == 1: # One till we go down 19 | ret_val = self.stacks[-1].pop() 20 | 21 | self.stacks.pop() 22 | 23 | # Backpedal until non-empty stack 24 | i = 1 25 | while self.stacks and self.stacks[-i].isEmpty(): 26 | i += 1 27 | self.stacks.pop() 28 | 29 | else: 30 | ret_val = self.stacks[-1].pop() 31 | self.size -= 1 32 | return ret_val 33 | 34 | def pop_at(self, index: int): 35 | if index > len(self.stacks): 36 | raise Exception("Invalid stack index") 37 | 38 | stack_target: Stack = self.stacks[index] 39 | 40 | if not stack_target.isEmpty(): 41 | ret_val = stack_target.pop() 42 | self.size -= 1 43 | else: # That stack empty 44 | raise Exception(f"Empty sub stack for {index}") 45 | return ret_val 46 | 47 | def push(self, elem: int): 48 | if not self.stacks: 49 | self.stacks.append(Stack()) 50 | self.stacks[0].push(elem) 51 | else: 52 | last_stack = self.stacks[-1] 53 | if last_stack.size % self.STACK_CAP == 0: 54 | self.stacks.append(Stack()) 55 | self.stacks[-1].push(elem) 56 | else: 57 | last_stack.push(elem) 58 | 59 | self.size += 1 60 | 61 | def __str__(self): 62 | return "\n".join( 63 | [f"Stackset with {len(self.stacks)} stacks, size {self.size}, as follows:"] 64 | + list(map(str, self.stacks)) 65 | + ["*" * 50]) 66 | 67 | 68 | if __name__ == "__main__": 69 | sos = SetOfStacks() 70 | # Initial build 71 | for i in range(100): 72 | sos.push(i) 73 | print(sos) 74 | 75 | # Pop one from each, but from 5 more 76 | for i in range(10): 77 | if i == 5: 78 | for j in range(5): 79 | sos.pop_at(i) 80 | print(f"From stack {i}, got elem {sos.pop_at(i)}") 81 | 82 | # Push 2 more elements 83 | sos.push(99) 84 | sos.push(100) 85 | print(sos) 86 | -------------------------------------------------------------------------------- /chapter_3/p3_4.py: -------------------------------------------------------------------------------- 1 | from chapter_3.datastructs import Stack 2 | 3 | class MyQueue: 4 | s_in = Stack() 5 | s_out = Stack() 6 | 7 | def push(self, elem: int): 8 | self.s_in.push(elem) 9 | 10 | def pop(self): 11 | if self.s_out.isEmpty(): 12 | self._pour(self.s_in, self.s_out) 13 | return self.s_out.pop() 14 | 15 | def isEmpty(self): 16 | return self.s_in.isEmpty() and self.s_out.isEmpty() 17 | 18 | def peek(self): 19 | if self.s_out.isEmpty(): 20 | self._pour(self.s_in, self.s_out) 21 | self.s_out.peek() 22 | 23 | def size(self): 24 | return self.s_in.size + self.s_out.size 25 | 26 | @staticmethod 27 | def _pour(s_from:Stack, s_to:Stack): 28 | while not s_from.isEmpty(): 29 | s_to.push(s_from.pop()) 30 | 31 | def __str__(self): 32 | return f"Ha! I'm actually 2 stacks: {self.s_in} and {self.s_out}" 33 | 34 | 35 | def test_myQueue(): 36 | myq = MyQueue() 37 | for i in range(5): 38 | myq.push(i) 39 | 40 | print(f"Is empty ? {myq.isEmpty()} ---> {myq}") 41 | 42 | for i in range(5): 43 | print(myq.pop()) 44 | 45 | try: 46 | myq.pop() 47 | except Exception as e: 48 | print(f"Oh no, got {e}, but queue has elem ? {not myq.isEmpty()}") 49 | 50 | for i in range(10,15): 51 | myq.push(i) 52 | 53 | print(myq.pop()) 54 | 55 | for i in range(15,20): 56 | myq.push(i) 57 | print(myq) 58 | 59 | if __name__ == '__main__': 60 | test_myQueue() 61 | -------------------------------------------------------------------------------- /chapter_3/p3_5.py: -------------------------------------------------------------------------------- 1 | from chapter_3.datastructs import Stack 2 | 3 | def sort_stack_w_stack(s1: Stack) -> None: 4 | s2 = Stack() 5 | s2.push(s1.pop()) 6 | 7 | while not s1.isEmpty(): 8 | elem = s1.pop() 9 | # print(f"Looking at elem {elem}") 10 | nr_str = 0 11 | while not s2.isEmpty() and s2.peek() > elem: 12 | s1.push(s2.pop()) 13 | nr_str += 1 14 | 15 | s2.push(elem) 16 | for _ in range(nr_str): 17 | s2.push(s1.pop()) 18 | 19 | while not s2.isEmpty(): 20 | s1.push(s2.pop()) 21 | 22 | if __name__ == '__main__': 23 | s1 = Stack() 24 | for elem in [1,50,20,10,-40,20,-30]: 25 | s1.push(elem) 26 | 27 | print(f"Before sort {s1}") 28 | sort_stack_w_stack(s1) 29 | print(f"After sort {s1}") 30 | 31 | -------------------------------------------------------------------------------- /chapter_3/p3_6.py: -------------------------------------------------------------------------------- 1 | from chapter_3.datastructs import Queue 2 | 3 | 4 | class Animal: 5 | def __init__(self, name: str): 6 | self.arrival_time = None 7 | self.name = name 8 | 9 | def __str__(self): 10 | return f"Animal: {self.name}" 11 | 12 | 13 | class Dog(Animal): 14 | def __init__(self, *args, **kwargs): 15 | super().__init__(*args, **kwargs) 16 | 17 | 18 | class Cat(Animal): 19 | def __init__(self, *args, **kwargs): 20 | super().__init__(*args, **kwargs) 21 | 22 | 23 | class AnimalShelter: 24 | def __init__(self): 25 | self.dog_q = Queue() 26 | self.cat_q = Queue() 27 | self.order_seq = 0 28 | 29 | def enqueue(self, animal: Animal): 30 | self.order_seq += 1 31 | animal.arrival_time = self.order_seq 32 | 33 | if isinstance(animal, Dog): 34 | self.dog_q.push(animal) 35 | elif isinstance(animal, Cat): 36 | self.cat_q.push(animal) 37 | 38 | def dequeueAny(self): 39 | if self.dog_q.isEmpty() and self.cat_q.isEmpty(): 40 | raise Exception("No animal to adopt") 41 | elif self.dog_q.isEmpty(): 42 | return self.cat_q.pop() 43 | elif self.cat_q.isEmpty(): 44 | return self.dog_q.pop() 45 | 46 | earliest_cat = self.cat_q.peek().arrival_time 47 | earliest_dog = self.dog_q.peek().arrival_time 48 | 49 | if earliest_cat > earliest_dog: 50 | return self.dog_q.pop() 51 | else: 52 | return self.cat_q.pop() 53 | 54 | def dequeueDog(self): 55 | return self.dog_q.pop() 56 | 57 | def dequeueCat(self): 58 | return self.cat_q.pop() 59 | 60 | 61 | if __name__ == "__main__": 62 | animals = [Dog("Dave"), Cat("Catto"), Dog("Dave2"), Cat("Catto2")] 63 | shelter = AnimalShelter() 64 | for a in animals: 65 | shelter.enqueue(a) 66 | 67 | print(f"A dog is {shelter.dequeueDog()}") 68 | print(f"Earliest is {shelter.dequeueAny()}") 69 | print(f"A cat is {shelter.dequeueCat()}") 70 | -------------------------------------------------------------------------------- /chapter_4/p4_1.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from utils.graphs import Ditex 3 | from collections import deque 4 | 5 | 6 | def populate_graph(edges: List[tuple]) -> dict: 7 | graph = {} 8 | for a, b in edges: 9 | if a not in graph: 10 | graph[a] = Ditex(a) 11 | if b not in graph: 12 | graph[b] = Ditex(b) 13 | 14 | graph[a].out_edges.add(b) 15 | graph[b].in_edges.add(a) 16 | return graph 17 | 18 | 19 | def route_digraph(edges: List[tuple], s: int, e: int): 20 | graph = populate_graph(edges) 21 | start_dq = deque() 22 | end_dq = deque() 23 | 24 | seen_start = set([s]) 25 | seen_end = set([e]) 26 | 27 | start_dq.append(s) 28 | end_dq.append(e) 29 | 30 | round_robin_flag = True 31 | 32 | while start_dq and end_dq: 33 | if round_robin_flag: # From start to end 34 | ce = graph[start_dq.popleft()] 35 | seen_start.add(ce.val) 36 | 37 | if ce.val in seen_end: 38 | return True 39 | for child in ce.out_edges: 40 | if child not in seen_start: 41 | start_dq.append(child) 42 | else: # From end toward start 43 | ce = graph[end_dq.popleft()] 44 | seen_end.add(ce.val) 45 | 46 | if ce.val in seen_start: 47 | return True 48 | 49 | for parent in ce.in_edges: 50 | if parent not in seen_end: 51 | end_dq.append(parent) 52 | 53 | round_robin_flag != round_robin_flag 54 | return False 55 | 56 | 57 | if __name__ == "__main__": 58 | edges = [(1, 11), (1, 12), (1, 14), (12, 23), 59 | (23, 2), (2, 21), (2, 22), (24, 2)] 60 | targets = (1, 2), (11, 24), (1, 22), (1, 11) 61 | for s, e in targets: 62 | print( 63 | f"Is there a route from {s} to {e} ? {route_digraph(edges, s,e)}") 64 | -------------------------------------------------------------------------------- /chapter_4/p4_10.py: -------------------------------------------------------------------------------- 1 | from utils.graphs import ltbt, BiNode 2 | 3 | 4 | def check_same_tree(root1: BiNode, root2: BiNode) -> bool: 5 | if (not root1) and (not root2): 6 | return True 7 | elif (not root1) or (not root2): 8 | return False 9 | else: 10 | if root1.val != root2.val: 11 | return False 12 | else: 13 | return check_same_tree(root1.left, root2.left) \ 14 | and check_same_tree(root1.right, root2.right) 15 | 16 | 17 | def check_sub_tree(t1: BiNode, t2: BiNode) -> bool: 18 | if not t1: 19 | return False 20 | if t1.val == t2.val: 21 | return check_same_tree(t1, t2) 22 | else: 23 | return check_sub_tree(t1.left, t2) or check_sub_tree(t1.right, t2) 24 | 25 | 26 | if __name__ == "__main__": 27 | tree1_list = [0, 10, 15, 20, 30, 25, 35] + [None] * 5 + [45] 28 | tree2_list = [10, 20, 30] 29 | 30 | t1 = ltbt(tree1_list) 31 | t2 = ltbt(tree2_list) 32 | 33 | print(f"Is t2 a subtree of t1 ? {check_sub_tree(t1,t2)}") 34 | print(f"Is t1 a subtree of t2 ? {check_sub_tree(t2,t1)}") 35 | -------------------------------------------------------------------------------- /chapter_4/p4_11.py: -------------------------------------------------------------------------------- 1 | from utils.graphs import BiNode 2 | from utils.treeviz import viz_tree 3 | import random 4 | 5 | class SizedNode(BiNode): 6 | def __init__(self, val: int): 7 | super().__init__(val) 8 | self.size = 1 9 | 10 | def insert(self, x: int): 11 | if x > self.val: 12 | if self.right: 13 | self.right.insert(x) 14 | else: 15 | self.right = SizedNode(x) 16 | else: 17 | if self.left: 18 | self.left.insert(x) 19 | else: 20 | self.left = SizedNode(x) 21 | self.size += 1 22 | 23 | 24 | class BinarySearchTree: 25 | 26 | def __init__(self): 27 | self.root = None 28 | 29 | def add(self, x: int): 30 | if not self.root: 31 | self.root = SizedNode(x) 32 | else: 33 | self.root.insert(x) 34 | 35 | def getRandomNuber(self) -> int: 36 | target = random.randint(1, self.root.size) 37 | print(f"Target is {target}") 38 | return self._get_inorder_number(self.root, target) 39 | 40 | def _get_inorder_number(self, node: SizedNode, target:int) -> int: 41 | right_size = node.right.size if node.right else 0 42 | # print(f"Node is {node.val}\tsize is {node.size},\tright is {right_size}") 43 | node_traversal_poz = node.size - right_size 44 | # print(f"At poz {node_traversal_poz},\ttarget is target {target}") 45 | if node_traversal_poz == target: 46 | return node.val 47 | elif node_traversal_poz < target: 48 | return self._get_inorder_number(node.right, target - node_traversal_poz) 49 | else: 50 | return self._get_inorder_number(node.left, target) 51 | 52 | 53 | if __name__ == "__main__": 54 | stream = [5, 1, 4, 14, 15, 9, 7, 13, 3] 55 | rbst = BinarySearchTree() 56 | for x in stream: 57 | rbst.add(x) 58 | 59 | viz_tree(rbst.root) 60 | print(rbst.root.size) 61 | print(f"Getting a random node: {rbst.getRandomNuber()}") 62 | -------------------------------------------------------------------------------- /chapter_4/p4_12.py: -------------------------------------------------------------------------------- 1 | from utils.graphs import BiNode, ltbt 2 | from utils.treeviz import viz_tree 3 | from collections import defaultdict 4 | 5 | 6 | def paths_w_sum(root: BiNode, target: int) -> int: 7 | total_paths = 0 8 | 9 | def find_paths_down(r, t, sofarsum, prev): 10 | nonlocal total_paths 11 | if not r: 12 | return 13 | 14 | sum_now = prev + r.val 15 | if sum_now - t in sofarsum: 16 | total_paths += sofarsum[sum_now-t] 17 | 18 | sofarsum[sum_now] += 1 19 | find_paths_down(r.left, t, sofarsum, sum_now) 20 | find_paths_down(r.right, t, sofarsum, sum_now) 21 | sofarsum[sum_now] -= 1 22 | 23 | find_paths_down(root, target, defaultdict(lambda: 0), 0) 24 | return total_paths 25 | 26 | 27 | if __name__ == "__main__": 28 | ex_tree = [10, 5, -3, 3, 2, 11, None, 3, -2, 1] 29 | root = ltbt(ex_tree) 30 | viz_tree(root) 31 | target = 8 32 | print(f"Paths to {target} are {paths_w_sum(root,target)}") 33 | -------------------------------------------------------------------------------- /chapter_4/p4_2.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | from utils.graphs import BiNode 3 | from utils.treeviz import * 4 | # CTCI 4.2 5 | 6 | # Random links 7 | # https://orth.uk 8 | # https://foodprint.orth.uk for software engineering project 9 | 10 | # https://devwebcl.blogspot.com/2016/12/big-o-comparison.html 11 | # https://awwapp.com/b/umyxkgethk506/ 12 | 13 | 14 | def main(): 15 | # ordered_list = [1, 2, 3, 6] 16 | ordered_list = [1, 2, 3, 6, 10, 12, 15, 16] 17 | print(ordered_list) 18 | result = create_binary_from_sorted_arr(ordered_list) 19 | viz_tree(result) 20 | print(result) 21 | 22 | 23 | def create_binary_from_sorted_arr(arr: List[int]) -> BiNode: 24 | if (len(arr) == 0): 25 | return None 26 | if (len(arr) == 1): 27 | return BiNode(arr[0], None, None) 28 | 29 | # pick out middle number 30 | middle_index = len(arr) // 2 31 | 32 | # recursively call 33 | left = create_binary_from_sorted_arr(arr[0:middle_index]) 34 | right = create_binary_from_sorted_arr(arr[middle_index + 1:]) 35 | return BiNode(arr[middle_index], left, right) 36 | 37 | 38 | if __name__ == "__main__": 39 | main() 40 | -------------------------------------------------------------------------------- /chapter_4/p4_3.py: -------------------------------------------------------------------------------- 1 | from chapter_2.linked_list_utils import Node 2 | from utils.graphs import BiNode, ltbt 3 | 4 | 5 | def tree2LinkedList(root: BiNode): 6 | q = [] 7 | ans = [] 8 | 9 | q.append((root, 0)) 10 | last_elem = None 11 | last_seen_level = 1 12 | 13 | while q: 14 | ce, lvl = q.pop(0) 15 | 16 | if lvl != last_seen_level: 17 | ans.append(Node(ce.val)) 18 | last_seen_level = lvl 19 | last_elem = ans[lvl] 20 | else: 21 | last_elem.next = Node(ce.val) 22 | last_elem = last_elem.next 23 | 24 | for child in (ce.left, ce.right): 25 | if child is not None: 26 | q.append((child, lvl+1)) 27 | 28 | return ans 29 | 30 | if __name__ == "__main__": 31 | extree = ltbt(list(range(10))) 32 | resp = tree2LinkedList(extree) 33 | 34 | for ll in resp: 35 | print(ll) 36 | -------------------------------------------------------------------------------- /chapter_4/p4_4.py: -------------------------------------------------------------------------------- 1 | from utils.graphs import BiNode, ltbt 2 | from utils.treeviz import viz_tree 3 | 4 | 5 | def check_balanced(root: BiNode): 6 | return check_bal_rec(root) > 0 7 | 8 | 9 | def check_bal_rec(node: BiNode): 10 | if not node: 11 | return 0 12 | 13 | depth_right = check_bal_rec(node.right) 14 | depth_left = check_bal_rec(node.left) 15 | if depth_right < 0 or depth_left < 0: 16 | return -1 17 | elif abs(depth_right-depth_left) > 1: 18 | return -1 19 | else: 20 | return max(depth_right, depth_left) + 1 21 | 22 | 23 | if __name__ == "__main__": 24 | extree = ltbt([0, 11, 12, 21, 222, 55, None, 31, 21, None, 42]) 25 | viz_tree(extree) 26 | resp = check_balanced(extree) 27 | print(f"Is tree balanced {resp}") 28 | -------------------------------------------------------------------------------- /chapter_4/p4_5.py: -------------------------------------------------------------------------------- 1 | from utils.graphs import ltbt, BiNode 2 | from utils.treeviz import viz_tree 3 | 4 | 5 | def check_bst(root: BiNode): 6 | print(f"Checking bst, got result of ranging: {get_tree_range(root)}") 7 | return not get_tree_range(root)[0] is False 8 | 9 | 10 | def get_tree_range(n: BiNode) -> tuple: 11 | if n is None: 12 | return (None, None) 13 | else: 14 | range_l = get_tree_range(n.left) 15 | range_r = get_tree_range(n.right) 16 | 17 | if range_l[0] is False or range_r[0] is False: 18 | return (False, False) 19 | 20 | if check_bst_condition(n.val, range_l[1], range_r[0]): 21 | range_min = min(range_l[0], n.val) \ 22 | if range_l[0] is not None else n.val 23 | range_max = max(range_r[1], n.val) \ 24 | if range_r[1] is not None else n.val 25 | return (range_min, range_max) 26 | else: 27 | return (False, False) 28 | 29 | 30 | def check_bst_condition(val, max_left, min_right): 31 | return (not max_left or max_left <= val) \ 32 | and (not min_right or val < min_right) 33 | 34 | 35 | if __name__ == "__main__": 36 | extree = ltbt([100, 50, 150, 25, 99, None, 300] + [None] * 6 + [175 , 301]) 37 | print(extree) 38 | viz_tree(extree) 39 | resp = check_bst(extree) 40 | print(f"Is tree a binary search tree ? {resp}") 41 | -------------------------------------------------------------------------------- /chapter_4/p4_6.py: -------------------------------------------------------------------------------- 1 | from utils.graphs import BiPaNode, get_node_by_val, ltbt 2 | from utils.treeviz import viz_tree 3 | from typing import Optional 4 | 5 | 6 | def next_in_order(node: BiPaNode) -> Optional[BiPaNode]: 7 | if node.right: # Case 1, there's right subtree 8 | ans = node.right 9 | while ans.left: 10 | ans = ans.left 11 | return ans 12 | 13 | # Case 2, go up until not left child 14 | p = node.parent 15 | if p.left == node: 16 | return p 17 | else: # Case 3, current is right child 18 | while p.parent: 19 | if (p.parent.right == p): 20 | p = p.parent 21 | else: 22 | return p.parent 23 | 24 | return None 25 | 26 | 27 | if __name__ == "__main__": 28 | root = ltbt(range(7)) 29 | viz_tree(root) 30 | exs = [0, 1, 2, 3, 4, 5, 6] 31 | for ex in exs: 32 | next_node = next_in_order(get_node_by_val(root, ex)) 33 | next_val = next_node.val if next_node else -1 34 | print( 35 | f"For node {ex}, next val is {next_val}") 36 | -------------------------------------------------------------------------------- /chapter_4/p4_7.py: -------------------------------------------------------------------------------- 1 | 2 | 3 | from utils.graphs import Ditex 4 | from collections import OrderedDict, defaultdict 5 | from typing import List 6 | 7 | # TODO Redo as topological sort 8 | 9 | 10 | def build_nodes(projects: List[chr], deps: List[tuple]): 11 | nodes = {x: Ditex(x) for x in projects} 12 | 13 | for (a, b) in deps: 14 | # print(f"Looking at tuple {a} and {b}") 15 | nodes[b].out_edges.add(nodes[a]) 16 | nodes[a].in_edges.add(nodes[b]) 17 | 18 | sn = OrderedDict() 19 | seen_count = len(sn)-1 20 | while seen_count != len(sn): # Check progress 21 | seen_count = len(sn) 22 | 23 | for key, node in nodes.items(): 24 | if not {edge.val for edge in node.out_edges} - sn.keys(): 25 | sn[node.val] = 1 26 | down_build(node, sn) 27 | else: 28 | print(f"node {key} is waiting on {[x.val for x in node.out_edges]}, " 29 | f" seen so far {list(sn.keys())}") 30 | 31 | if len(sn) == len(projects): 32 | return list(sn.keys()) 33 | else: 34 | raise Exception("No valid build") 35 | 36 | 37 | def down_build(node: Ditex, seen: dict): 38 | for nodes_child in node.in_edges: 39 | if not nodes_child.out_edges - seen.keys(): 40 | print( 41 | f"Adding {nodes_child.val} as out_edges are \ 42 | {[x.val for x in nodes_child.out_edges]},\t \ 43 | compared to seen {list(seen.keys())}") 44 | seen[nodes_child.val] = 1 45 | down_build(nodes_child, seen) 46 | 47 | 48 | if __name__ == "__main__": 49 | # projects = ['a', 'b', 'c', 'd', 'e', 'f'] 50 | # deps = [('a', 'd'), ('f', 'b'), ('b', 'd'), ('f', 'a'), ('d', 'c')] 51 | 52 | projects = ['a', 'b', 'c'] 53 | deps = [('c', 'a')] 54 | build_order = build_nodes(projects, deps) 55 | print(f"Build order is {build_order}") 56 | -------------------------------------------------------------------------------- /chapter_4/p4_8.py: -------------------------------------------------------------------------------- 1 | from utils.graphs import BiNode, ltbt 2 | from typing import Tuple, Optional 3 | from utils.treeviz import viz_tree 4 | 5 | # No parent, method 1 with covers 6 | 7 | 8 | def first_common_ancestor(r: BiNode, e1: int, e2: int): 9 | res_1 = com_anc_covers(r, e1, e2) 10 | res_2 = com_anc_helper(r, e1, e2) 11 | 12 | print(f"Common anc, method 1 : {res_1}, " 13 | f"method 2: {res_2[0].val if res_2[1] else 'Not found'}") 14 | return res_2[0].val if res_2[1] else None 15 | 16 | 17 | def com_anc_covers(n: BiNode, 18 | e1: int, 19 | e2: int) -> Optional[int]: 20 | if not n: 21 | return None 22 | 23 | # If both are covered by the left/right subtree, 24 | # Look for solution there 25 | rc1 = covers(n.right, e1) 26 | rc2 = covers(n.right, e2) 27 | if rc1 and rc2: 28 | return com_anc_covers(n.right, e1, e2) 29 | 30 | lc1 = covers(n.left, e1) 31 | lc2 = covers(n.left, e2) 32 | if lc1 and lc2: 33 | return com_anc_covers(n.left, e1, e2) 34 | 35 | # If current is one of them, and the other in a subtree 36 | if (n.val == e1 and (lc2 or rc2)) or (n.val == e2 and (lc1 or rc1)): 37 | return n.val 38 | 39 | # If e1 and e2 covered by different subtrees, this is the place 40 | if (rc1 ^ rc2) and (lc1 ^ lc2): 41 | return n.val 42 | 43 | 44 | def covers(root: BiNode, elem: int): 45 | if not root: 46 | return False 47 | if root.val == elem: 48 | return True 49 | else: 50 | return covers(root.left, elem) or covers(root.right, elem) 51 | 52 | # No parent, method 2 53 | # Send up the result of current search 54 | 55 | 56 | def com_anc_helper(root: BiNode, 57 | e1: int, 58 | e2: int) -> Tuple[Optional[BiNode], Optional[bool]]: 59 | if not root: 60 | return (None, None) 61 | 62 | # If the ancestor is in the left (or right)subtree, 63 | # return that 64 | 65 | left_res, left_has_anc = com_anc_helper(root.left, e1, e2) 66 | if left_res and left_has_anc: 67 | return (left_res, left_has_anc) 68 | 69 | right_res, right_has_anc = com_anc_helper(root.right, e1, e2) 70 | if right_res and right_has_anc: 71 | return (right_res, right_has_anc) 72 | 73 | # If both nodes found in the left and right trees 74 | # But not ancestors, this is the ancestor 75 | if left_res and right_res: 76 | return (root, True) 77 | else: 78 | if (root.val == e1 or root.val == e2): 79 | # Current is one of them and the other in subtrees 80 | is_anc = (left_res or right_res) 81 | return (root, is_anc) 82 | else: 83 | # Send up if one of target nodes found 84 | return (left_res if left_res else right_res, False) 85 | 86 | 87 | if __name__ == "__main__": 88 | ex = [2, 10, 30, 55, 15, None, None, 3, 37, None, 17] 89 | root = ltbt(ex) 90 | viz_tree(root) 91 | 92 | first_common_ancestor(root, 17, 37) 93 | first_common_ancestor(root, 17, 37) 94 | first_common_ancestor(root, 55, 3) 95 | first_common_ancestor(root, 3, 55) 96 | first_common_ancestor(root, 2, 9999) -------------------------------------------------------------------------------- /chapter_4/p4_9.py: -------------------------------------------------------------------------------- 1 | # BST Sequences 2 | 3 | 4 | def rec_weave(l1, l2, atm_ans): 5 | lol = [] 6 | if l1: 7 | rest_of_lists = rec_weave(l1[1:], l2, atm_ans + [l1[0]]) 8 | lol.extend(rest_of_lists) 9 | else: 10 | return [atm_ans + l2] 11 | 12 | if l2: 13 | rest_of_lists_2 = rec_weave(l1, l2[1:], atm_ans + [l2[0]]) 14 | lol.extend(rest_of_lists_2) 15 | else: 16 | return [atm_ans + l1] 17 | 18 | return lol 19 | 20 | # [1,2] 21 | # [3,4] 22 | # Result: [1,2,3,4], [3,4,1,2], and others 23 | 24 | 25 | def weave(l1, l2): 26 | return rec_weave(l1, l2, []) 27 | # l1 [1] 28 | # l2 [2] 29 | # [2,1 ], [1,2] 30 | 31 | 32 | if __name__ == "__main__": 33 | print("Result") 34 | # print(weave([1], [2])) 35 | print(weave([1], [3, 5, 4])) 36 | -------------------------------------------------------------------------------- /chapter_5/p5_1.py: -------------------------------------------------------------------------------- 1 | from utils.binutils import blen 2 | 3 | 4 | def binary_insert(n: int, m: int, i: int, j: int) -> int: 5 | len_n = blen(n) 6 | len_m = blen(m) 7 | 8 | mask = 0b1 << (len_n - j - 1) 9 | mask -= 1 10 | # print(f"Mask is\t{mask:b}") 11 | 12 | mask <<= j + 1 13 | # print(f"Mask is\t{mask:b}") 14 | 15 | mask += (0b1 << i+1) - 1 16 | # print(f"Mask is\t{mask:b}") 17 | # print(f"N is\t{n:b}") 18 | n &= mask 19 | print(f"N is\t{n:b}") 20 | 21 | j_mask = m << i 22 | # print(f"Jmask is\t{j_mask:b}") 23 | n |= j_mask 24 | return n 25 | 26 | 27 | if __name__ == "__main__": 28 | n = 0b11110000001 29 | m = 0b1111 30 | i = 2 31 | j = 6 32 | 33 | print(f"Result of insertion is: {bin(binary_insert(n,m,i,j))}") 34 | -------------------------------------------------------------------------------- /chapter_5/p5_2.py: -------------------------------------------------------------------------------- 1 | 2 | # TODO Figure out how to make this work in Python 3 | # This is the intuition of the CTCI solution 4 | 5 | def float_bin_rep(a: float) -> str: 6 | max_len = 32 7 | ans = [] 8 | while a > 0: 9 | print(f"A now is {a}") 10 | if len(ans) >= max_len: 11 | raise Exception("Float too long to show") 12 | a *= 2 13 | if a >= 1: 14 | ans.append("1") 15 | a -= 1 16 | else: 17 | ans.append("0") 18 | return "".join(["0."] + ans) 19 | 20 | 21 | if __name__ == "__main__": 22 | exs = [0.72, 0.20, 0.3253, 0.53405, 0.1111113] 23 | for ex in exs[:1]: 24 | x_int = int(str(ex)[2:]) 25 | try: 26 | print(f"Binary of the fract part of {ex} is {float_bin_rep(ex)}, expected {x_int:b}") 27 | except Exception as e: 28 | print(f"Issue with showing {ex}, expected {x_int:b}") -------------------------------------------------------------------------------- /chapter_5/p5_3.py: -------------------------------------------------------------------------------- 1 | def longest_seq_by_flip(x: int) -> int: 2 | max_join = -1 3 | seq_prev = 0 4 | seq_now = 0 5 | 6 | zeros_in_str = False 7 | prev_was_zero = False 8 | joinable_gap = False 9 | 10 | while x: 11 | bit_now = x & 0b1 12 | x >>= 1 13 | if bit_now: 14 | prev_was_zero = False 15 | seq_now += 1 16 | else: 17 | zeros_in_str = True 18 | if joinable_gap and seq_now != 0: 19 | if seq_now + seq_prev + 1 > max_join: 20 | max_join = seq_now + seq_prev + 1 21 | 22 | if seq_now > 0: 23 | # If there is a seq and a 0, check if len(that)+1 is largest 24 | if seq_now + 1 > max_join: 25 | max_join = seq_now+1 26 | # Update for next seq 27 | seq_prev = seq_now 28 | seq_now = 0 29 | 30 | joinable_gap = not prev_was_zero 31 | prev_was_zero = True 32 | 33 | # print(f"At bit {bit_now},\tseq_now: {seq_now},\tseq_prev: {seq_prev},\tjoin_gap {joinable_gap},\tprev_was_zero {prev_was_zero}") 34 | 35 | if joinable_gap: 36 | if seq_now + seq_prev + 1 > max_join: 37 | max_join = seq_now + seq_prev + 1 38 | elif seq_now + zeros_in_str > max_join: 39 | # Cover the case of all ones, or one seq with trailing zeros 40 | max_join = seq_now + zeros_in_str 41 | 42 | return max_join 43 | 44 | 45 | if __name__ == "__main__": 46 | exs = [(0b111, 3), (0b101, 3), (0b111001, 4), (0b1010101, 3), 47 | (0b1011, 4), (0b101100, 4), (0b110, 3), (0b100111001, 4)] 48 | for ex, target in exs: 49 | print( 50 | f"Biggest seq for {ex:b} is {longest_seq_by_flip(ex)} == {target}") 51 | -------------------------------------------------------------------------------- /chapter_5/p5_4.py: -------------------------------------------------------------------------------- 1 | import utils.binutils as binutils 2 | 3 | # TODO Handle the last bit correctly 4 | 5 | 6 | def next_bigger(x: int) -> int: 7 | fugee_ones = x & 0b1 8 | temp = x >> 1 9 | flip_mask = binutils.ones(binutils.blen(x)) 10 | bit_flip = 0b1 << 1 11 | flip_mask <<= 1 12 | 13 | # Find first non-trailing 0 14 | while (temp & 0b1 or not fugee_ones) and temp: 15 | bit_now = temp & 0b1 16 | fugee_ones += bit_now 17 | flip_mask <<= 1 18 | bit_flip <<= 1 19 | 20 | temp >>= 1 21 | # print(f"Looking at temp {temp:b}, bit_flip {bit_flip:b}") 22 | 23 | fugee_ones -= 1 24 | # print(f"Flip mask:\t{flip_mask:b}") 25 | # print(f"Bit flip:\t{bit_flip:b}") 26 | # print(f"Refugees:\t {fugee_ones}") 27 | 28 | # print(f"Original x:\t{x:b}") 29 | x &= flip_mask 30 | # print(f"Clearend x:\t{x:b}") 31 | x |= bit_flip # Set target to 1 32 | # print(f"Bitfliped x:\t{x:b}") 33 | 34 | # Refugees to the right 35 | ref_mask = (0b1 << (fugee_ones)) - 1 36 | # print(f"Refugee mask is {ref_mask:b}") 37 | x |= ref_mask 38 | # print(f"Refmasked x:\t{x:b}\n" + "-" *50) 39 | return x 40 | 41 | # 11_0_11111 42 | # 11_1_00000 43 | # 11_1_01111 44 | 45 | 46 | def next_smaller(x: int) -> int: 47 | fugee_zeros = x & 0b1 48 | temp = x >> 1 49 | flip_mask = binutils.ones(binutils.blen(x)) 50 | bit_flip = 0b1 << 1 51 | flip_mask <<= 1 52 | trailing_ones = True 53 | window_size = 0 54 | 55 | # Find first non-trailing 1 56 | while ((not temp & 0b1) or trailing_ones) and temp: 57 | bit_now = temp & 0b1 58 | 59 | fugee_zeros += not bit_now 60 | window_size += 1 61 | 62 | if not bit_now: 63 | trailing_ones = False 64 | 65 | flip_mask <<= 1 66 | bit_flip <<= 1 67 | 68 | temp >>= 1 69 | # print(f"Will look at temp {temp:b}, bit_flip {bit_flip:b}. {trailing_ones}") 70 | 71 | # print(f"Flip mask:\t{flip_mask:b}") 72 | # print(f"Bit flip:\t{bit_flip:b}") 73 | # print(f"Refugees:\t {fugee_zeros}") 74 | # print() 75 | # print(f"Original x:\t{x:b}") 76 | x &= flip_mask 77 | # print(f"Clearend x:\t{x:b}") 78 | 79 | flip_mask = binutils.ones(binutils.blen(x)) 80 | flip_mask ^= bit_flip 81 | # print(f"Bitflip mask:\t{flip_mask:b}") 82 | 83 | x &= ~bit_flip # 111_0_111, unset target bit 84 | # print(f"Bitfliped x:\t{x:b}") 85 | 86 | # Refugees to the left (make highest with available) 87 | ref_mask = (0b1 << (window_size - fugee_zeros + 1)) - 1 88 | ref_mask <<= fugee_zeros 89 | # print(f"Refugee mask is {ref_mask:b}") 90 | x |= ref_mask 91 | # print(f"Refmasked x:\t{x:b}\n" + "-" * 50) 92 | 93 | return x 94 | 95 | 96 | if __name__ == "__main__": 97 | exs = [0b10011001, 0b10, 0b111000] 98 | for x in exs: 99 | print(f"For {x:b} < {next_bigger(x):b}\tand\t{x:b} > {next_smaller(x):b}") 100 | -------------------------------------------------------------------------------- /chapter_5/p5_5.py: -------------------------------------------------------------------------------- 1 | def is_power_of_two(n: int) -> bool: 2 | return (n & (n-1)) == 0 3 | 4 | 5 | if __name__ == "__main__": 6 | exs = [0, 2, 4, 10, 23, 1, 64, 1024] 7 | for x in exs: 8 | print(f"Is {x} a power of two ? {is_power_of_two(x)}") 9 | -------------------------------------------------------------------------------- /chapter_5/p5_6.py: -------------------------------------------------------------------------------- 1 | def flips_to_convert(a: int, b: int) -> int: 2 | flip_c = 0 3 | while a and b: 4 | flip_c += (a & 0b1) ^ (b & 0b1) 5 | a >>= 1 6 | b >>= 1 7 | 8 | while a: 9 | flip_c += a & 0b1 10 | a >>= 1 11 | while b: 12 | flip_c += b & 0b1 13 | b >>= 1 14 | 15 | return flip_c 16 | 17 | 18 | if __name__ == "__main__": 19 | exs = [(0b1000010101, 0b1111), (0b11101, 0b01111), 20 | (0b1, 0b101), (0b101, 0b101)] 21 | for a, b in exs: 22 | print(f"Converting {a:b} into {b:b} flips {flips_to_convert(a,b)}") 23 | -------------------------------------------------------------------------------- /chapter_5/p5_7.py: -------------------------------------------------------------------------------- 1 | def pairwise_swap(a: int) -> int: 2 | select_mask = 0xAAAAAAAA # 16 A's 3 | odd_bits = a & select_mask 4 | even_bits = (a << 1) & select_mask 5 | return (odd_bits >> 1) | (even_bits) 6 | 7 | 8 | if __name__ == "__main__": 9 | exs = [0b1010, 0b1111, 0b100, 0b110, 0b100101] 10 | for x in exs: 11 | print(f"Swapping {x:10b} --> {pairwise_swap(x):10b}") 12 | -------------------------------------------------------------------------------- /chapter_5/p5_8.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def draw_line(screen: List[int], 5 | width_bits: int, 6 | x1: int, 7 | x2: int, 8 | y: int) -> None: 9 | # print(f"Putting line\t{x1} to {x2} on line {y}") 10 | w = width_bits // 8 11 | h = len(screen)//w 12 | 13 | start_index = w*y + x1//8 14 | end_index = w*y + x2//8 15 | 16 | # print(f"Indexes are\t{start_index} to {end_index}") 17 | 18 | start_mask = (0b1 << (8 - (x1 % 8))) - 1 19 | # print(f"Start mask is\t{start_mask:08b}") 20 | 21 | if start_index != end_index: 22 | screen[start_index] |= start_mask 23 | 24 | for i in range(start_index+1, end_index): 25 | screen[i] |= 0xFF 26 | 27 | end_mask = (0xFF << (8 - (x2 % 8))) % 256 28 | # print(f"End mask is\t{end_mask:08b}") 29 | 30 | screen[end_index] |= end_mask 31 | else: 32 | start_mask &= (0xFF) << (8 - x2 % 8) 33 | # print(f"Same mask is {start_mask:08b}") 34 | screen[start_index] |= start_mask 35 | 36 | 37 | def print_screen(screen: List[int], width_bits: int): 38 | width = width_bits//8 39 | for i in range(len(screen)//width): 40 | for j in range(width): 41 | # print(f"{i} -- {j}") 42 | print(f"{screen[i*width+j]:08b}", end=" ") 43 | print() 44 | 45 | 46 | if __name__ == "__main__": 47 | width_bits = 5 * 8 48 | h = 5 49 | screen = [0 for i in range(width_bits//8) for j in range(h)] 50 | # print_screen(screen, width_bits) 51 | for line_index in range(h): 52 | start_i = line_index + 1 53 | end_i = start_i*5 54 | draw_line(screen, width_bits, start_i, end_i, line_index) 55 | 56 | print_screen(screen, width_bits) 57 | -------------------------------------------------------------------------------- /chapter_6/p6_1.py: -------------------------------------------------------------------------------- 1 | import random 2 | import math 3 | from typing import List 4 | 5 | class Bottle: 6 | def __init__(self, pill_weight): 7 | self.pill = pill_weight 8 | 9 | def populate_pills(bottle_num:int ) -> List[Bottle]: 10 | special_bottle = random.randint(0,bottle_num) 11 | print(f"Special bottle will be {special_bottle+1} (1 indexed)") 12 | return [Bottle(1.0 if i != special_bottle else 1.1) for i in range(bottle_num)] 13 | 14 | def detect_special_bottle(bottles:List[Bottle]) -> int: 15 | expected_sum =0 16 | pill_pile = 0 17 | for i in range(len(bottles)): 18 | pill_pile += (i+1)* bottles[i].pill * 10 19 | expected_sum += (i+1) * 10 20 | 21 | outstanding_pill = pill_pile - expected_sum 22 | # print(outstanding_pill) 23 | return math.ceil(outstanding_pill) 24 | 25 | if __name__ == "__main__": 26 | number_of_bottles =20 27 | bottles = populate_pills(number_of_bottles) 28 | print(f"Special bottle is {detect_special_bottle(bottles)}") 29 | -------------------------------------------------------------------------------- /chapter_6/p6_10.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | import random 3 | 4 | BOTTLES_NUMBER = 100 5 | TEST_STRIP_NUMBER = 10 6 | MAGIC_INFECTED_BOTTLE_NUM = random.randint(0, BOTTLES_NUMBER) 7 | 8 | 9 | class TestStrip: 10 | 11 | def __init__(self): 12 | self.samples = set() 13 | self.positive = False 14 | 15 | def add_sample(self, sample_id: int): 16 | self.samples.add(sample_id) 17 | 18 | 19 | def test_strip(strip: TestStrip): 20 | if MAGIC_INFECTED_BOTTLE_NUM in strip.samples: 21 | strip.positive = True 22 | 23 | 24 | def detect_poison_bottle(bottles: List[int], tests: List[TestStrip]) -> int: 25 | if len(bottles) > len(tests) ** 2: 26 | raise Exception("Cannot test in one pass") 27 | 28 | for i, bottle_id in enumerate(bottles): 29 | # Get the 10bit representation of i 30 | # use set bits to figure out which test to add_sample on 31 | for test_num, bit_mask_indicator in enumerate(map(int,f"{i:010b}")): 32 | if bit_mask_indicator: 33 | tests[test_num].add_sample(bottle_id) 34 | 35 | for strip in tests: 36 | test_strip(strip) 37 | 38 | # Reconstruct number from positive tests: 39 | # Pythonist one-liner 40 | # int("".join(map(str,[int(strip.positive) for strip in tests])),2) 41 | 42 | # Strong C-style for loop 43 | bottle_index = 1 if tests[0].positive else 0 44 | for strip in tests[1:]: 45 | bottle_index<<=1 46 | if strip.positive: 47 | bottle_index+=1 48 | 49 | return bottle_index 50 | 51 | 52 | if __name__ == "__main__": 53 | soda_bottles = list(range(0, BOTTLES_NUMBER)) 54 | test_strips = [TestStrip() for _ in range(TEST_STRIP_NUMBER)] 55 | print( 56 | f"Poison bottle is {detect_poison_bottle(soda_bottles,test_strips)}," 57 | f" actual {MAGIC_INFECTED_BOTTLE_NUM}") 58 | -------------------------------------------------------------------------------- /chapter_6/p6_4.py: -------------------------------------------------------------------------------- 1 | 2 | def ants_collision_chance(num_vertex: int): 3 | # Ants not going the same way => 2 options (either left of right) 4 | # Ants are equally likely to go either way 5 | good_cases =2 6 | all_cases = 2**num_vertex 7 | 8 | return (all_cases - good_cases)/ all_cases 9 | 10 | 11 | if __name__ == "__main__": 12 | for i in range(3,10): 13 | print(f"Chance of ant collision on {i}-vertex polygon" 14 | f" is {ants_collision_chance(i):10}, compared with {1- (1/2)**(i-1):10}") -------------------------------------------------------------------------------- /chapter_6/p6_5.py: -------------------------------------------------------------------------------- 1 | class Jug: 2 | 3 | def __init__(self, capacity_liters): 4 | super().__init__() 5 | self.capacity_liters = capacity_liters 6 | self.current_fill = 0 7 | 8 | def empty(self): 9 | self.current_fill = 0 10 | 11 | def fill(self): 12 | self.current_fill = self.capacity_liters 13 | 14 | def fill_from(self, other_jug): 15 | if self.capacity_liters - self.current_fill > other_jug.current_fill: 16 | # Pour the whole other jug 17 | self.current_fill += other_jug.current_fill 18 | other_jug.current_fill = 0 19 | else: 20 | # Just top off the current 21 | other_jug.current_fill -= self.capacity_liters - self.current_fill 22 | self.current_fill = self.capacity_liters 23 | 24 | 25 | if __name__ == "__main__": 26 | jug_3l = Jug(3) 27 | jug_5l = Jug(5) 28 | 29 | # Create a fill of 2L in small one 30 | jug_5l.fill() 31 | jug_3l.fill_from(jug_5l) 32 | jug_3l.empty() 33 | 34 | # Fill 5 in the big one, top off the small one with 1L 35 | jug_3l.fill_from(jug_5l) 36 | jug_5l.fill() 37 | jug_3l.fill_from(jug_5l) 38 | 39 | print(f"Now in the big jug, we have {jug_5l.current_fill}") 40 | 41 | # Progress on water quantities in the jugs 42 | # 5 - 0 43 | # 2 - 3 44 | # 2 - 0 45 | # 0 - 2 46 | # 5 - 2 47 | # 4 - 0 48 | -------------------------------------------------------------------------------- /chapter_6/p6_7.py: -------------------------------------------------------------------------------- 1 | import random 2 | 3 | def sim_children(pop_size: int): 4 | boys = 0 5 | girls = 0 6 | while pop_size: 7 | more_children_couples =0 8 | for i in range(pop_size): 9 | has_girl = bool(random.getrandbits(1)) 10 | if not has_girl: 11 | boys += 1 12 | more_children_couples += 1 13 | else: 14 | girls += 1 15 | pop_size = more_children_couples 16 | 17 | return boys, girls 18 | 19 | 20 | if __name__ == "__main__": 21 | for i in range(1, 8): 22 | pop_boys, pop_girls = sim_children(10**i) 23 | print(f"For pop size {10**i:10}\t, ratio {pop_boys/pop_girls:10}\t, got boys {pop_boys}\t girls {pop_girls}") 24 | -------------------------------------------------------------------------------- /chapter_6/p6_9.py: -------------------------------------------------------------------------------- 1 | def shift_lockers(n:int): 2 | locker_status = [False] *n 3 | for i in range(n): 4 | for j in range(i, n, i+1): 5 | locker_status[j] = not locker_status[j] 6 | 7 | # print(locker_status) 8 | for i in range(n): 9 | if locker_status[i]: 10 | print(f"Locker {i+1} still open") 11 | 12 | if __name__ == "__main__": 13 | shift_lockers(100) -------------------------------------------------------------------------------- /chapter_8/p8_1.py: -------------------------------------------------------------------------------- 1 | def triple_step(n: int) -> int: 2 | 3 | dp = [1, 1, 2] 4 | 5 | if n < 3: 6 | return dp[n] 7 | 8 | for i in range(3, n+1): 9 | new_elem = sum(dp) 10 | dp[0] = dp[1] 11 | dp[1] = dp[2] 12 | dp[2] = new_elem 13 | 14 | return dp[2] 15 | 16 | 17 | if __name__ == "__main__": 18 | for i in range(20): 19 | print(f"Going to {i} step, ways to do this: {triple_step(i)}") 20 | -------------------------------------------------------------------------------- /chapter_8/p8_10.py: -------------------------------------------------------------------------------- 1 | 2 | from typing import List 3 | 4 | DIRS = [(0, 1), (0, -1), (1, 0), (-1, 0)] 5 | 6 | 7 | def paint_fill(img: List[List[int]], point: tuple, color: int) -> None: 8 | fill_rec(img, point, gp(img, point), color) 9 | 10 | 11 | def gp(matrix, point): 12 | x, y = point 13 | return matrix[x][y] 14 | 15 | 16 | def fill_rec(img, point, from_c, to_c): 17 | if gp(img, point) == from_c: 18 | x, y = point 19 | img[x][y] = to_c 20 | for xd, yd in DIRS: 21 | nx, ny = xd + x, yd + y 22 | 23 | if 0 <= nx and nx < len(img) and 0 <= ny and ny < len(img[0]): 24 | fill_rec(img, (nx, ny), from_c, to_c) 25 | 26 | 27 | if __name__ == "__main__": 28 | ex = [[1, 1, 1, 1, 1, 1, 1, 1, 1], 29 | [1, 1, 1, 3, 3, 3, 1, 1, 1], 30 | [1, 1, 1, 2, 1, 2, 1, 1, 1], 31 | [1, 1, 1, 2, 1, 2, 1, 1, 1], 32 | [1, 1, 1, 2, 1, 2, 1, 1, 1]] 33 | 34 | loc3 = (1, 3) 35 | paint_fill(ex, loc3, 2) 36 | 37 | for line in ex: 38 | print(line) 39 | -------------------------------------------------------------------------------- /chapter_8/p8_11.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def repr_w_coints(cents: int) -> int: 5 | coins = [25, 10, 5, 1] 6 | return coin_repr(cents, 0, coins) 7 | 8 | 9 | def coin_repr(n: int, start_ci: int, coins: List[int]) -> int: 10 | ways = 0 11 | if n < 0: 12 | return 0 13 | if n == 0: 14 | return 1 15 | 16 | for i in range(start_ci, len(coins)): 17 | ways += coin_repr(n-coins[i], i, coins) 18 | 19 | return ways 20 | 21 | 22 | if __name__ == '__main__': 23 | exs = [1, 3, 4, 10, 20, 30, 40, 50, 60] 24 | for ex in exs: 25 | print(f"Way to repr {ex} is {repr_w_coints(ex)}") 26 | -------------------------------------------------------------------------------- /chapter_8/p8_12.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def queens_8(): 5 | current_queens = [] 6 | ways = 0 7 | for i in range(8): 8 | ways += rec_queen_place(1, [(i, 0)]) 9 | 10 | return ways 11 | 12 | 13 | def rec_queen_place(col: int, queen_list): 14 | # print(f"Current queens {queen_list}") 15 | if col >= 8: 16 | display_queen_placement(queen_list) 17 | return 1 18 | 19 | possible_rows = set(range(8)) 20 | for (qr, qc) in queen_list: 21 | possible_rows.remove(qr) 22 | 23 | ways = 0 24 | for row in possible_rows: 25 | if valid_w_queens((row, col), queen_list): 26 | ways += rec_queen_place(col+1, queen_list + [(row, col)]) 27 | return ways 28 | 29 | 30 | def valid_w_queens(poz, queens): 31 | diag_desc = poz[0]-poz[1] 32 | diag_asc = poz[0] + poz[1] 33 | return not any(qi-qj == diag_desc or qi+qj == diag_asc 34 | for (qi, qj) in queens) 35 | 36 | 37 | def display_queen_placement(ql: List): 38 | board = [['_' for _ in range(8)] for _ in range(8)] 39 | for qi, qj in ql: 40 | board[qi][qj] = "Q" 41 | 42 | for line in board: 43 | print(line) 44 | print("-"*50) 45 | 46 | if __name__ == "__main__": 47 | print(f"Got ways to put 8 queens: {queens_8()}") 48 | -------------------------------------------------------------------------------- /chapter_8/p8_13.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | from collections import namedtuple 3 | from typing import List 4 | 5 | Box = namedtuple("Box", "w l h") 6 | 7 | 8 | def can_stack(bot: Box, top: Box): 9 | return bot.w > top.w and bot.l > top.l and bot.h > top.h 10 | 11 | 12 | def max_box_stack(boxes: List[Box]): 13 | # Need to be immutable for the cache 14 | sort_boxes = tuple(sorted(boxes, key=lambda box: -box.h)) 15 | # print(f"Sorted boxes are {sort_boxes}") 16 | 17 | box_stack_sizes = [stack_boxes(sort_boxes, i) 18 | for i in range(len(sort_boxes))] 19 | return max(box_stack_sizes) 20 | 21 | 22 | @lru_cache(None) 23 | def stack_boxes(boxes: List[Box], box_index: int) -> int: 24 | height_of_boxpile = 0 25 | box_now = boxes[box_index] 26 | 27 | max_h_added = 0 28 | 29 | for i in range(box_index+1, len(boxes)): 30 | h_added = 0 31 | if can_stack(box_now, boxes[i]): 32 | h_added = stack_boxes(boxes, i) 33 | max_h_added = max(max_h_added, h_added) 34 | 35 | height_of_boxpile = box_now.h + max_h_added 36 | return height_of_boxpile 37 | 38 | 39 | if __name__ == "__main__": 40 | boxes = [ 41 | Box(20, 20, 20), 42 | Box(10, 10, 10), 43 | Box(5, 5, 5), 44 | Box(30, 5, 30), 45 | Box(15, 15, 15)] 46 | print(f"Maximum height for boxes is {max_box_stack(boxes)}") 47 | -------------------------------------------------------------------------------- /chapter_8/p8_14.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache, partial 2 | from typing import List 3 | 4 | 5 | @lru_cache(None) 6 | def count_eval(form: str, result: bool): 7 | if len(form) == 1: 8 | literal = int(form) 9 | return 1 if bool(literal) == result else 0 10 | 11 | ways = 0 12 | for i in range(0, len(form)-1, 2): 13 | current_op = form[i+1] 14 | left_side = partial(count_eval, form[:i+1]) 15 | right_side = partial(count_eval, form[i+2:]) 16 | 17 | if current_op == "&": 18 | if result == True: 19 | ways += left_side(True) * right_side(True) 20 | else: 21 | ways += left_side(False) * right_side(True) \ 22 | + left_side(True) * right_side(False) \ 23 | + left_side(False) * right_side(False) 24 | elif current_op == "^": 25 | if result == False: 26 | ways += left_side(False) * right_side(False) \ 27 | + left_side(True) * right_side(True) 28 | else: 29 | ways += left_side(True) * right_side(False) \ 30 | + left_side(False) * right_side(True) 31 | elif current_op == "|": 32 | if result == False: 33 | ways += left_side(False) * right_side(False) 34 | else: 35 | ways += left_side(True) * right_side(True) \ 36 | + left_side(False) * right_side(True) \ 37 | + left_side(True) * right_side(False) 38 | 39 | # print(f"Looking to package {form} for {result}, ways for this {ways}") 40 | return ways 41 | 42 | if __name__ == "__main__": 43 | exs = [ 44 | ("1^0", True), 45 | ("1^0|0|1", False), 46 | ("0&0&0&1^1|0", True) 47 | ] 48 | 49 | for formula, target in exs: 50 | print(f"Ways to partition {formula} for {target} are {count_eval(formula, target)}") -------------------------------------------------------------------------------- /chapter_8/p8_2.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def robot_br(m: List[List[int]], r, c) -> List: 5 | rm = [[None for _ in range(c)] for _ in range(r)] 6 | 7 | def up_left_rec(i, j, dir): 8 | if 0 > i or j < 0: 9 | return 10 | 11 | if rm[i][j] is None: 12 | rm[i][j] = dir 13 | up_left_rec(i-1, j, "u") 14 | up_left_rec(i, j-1, "l") 15 | 16 | for i in range(r): 17 | for j in range(c): 18 | if m[i][j]: 19 | rm[i][j] = "b" 20 | up_left_rec(r-1, c-1, "e") 21 | 22 | if not rm[0][0]: 23 | return [] 24 | 25 | path = [(0, 0)] 26 | i, j = 0, 0 27 | while rm[i][j] != "e": 28 | dir = rm[i][j] 29 | if dir == "u": 30 | i += 1 31 | elif dir == "l": 32 | j += 1 33 | 34 | path.append((i, j)) 35 | for line in rm: 36 | print(line) 37 | 38 | return path 39 | 40 | 41 | if __name__ == "__main__": 42 | ex1 = [[0, 1, 0, 0, 0], 43 | [0, 1, 0, 0, 0], 44 | [0, 0, 0, 0, 0], 45 | [0, 1, 1, 1, 0], 46 | [0, 1, 0, 0, 0], 47 | ] 48 | 49 | r, c = len(ex1), len(ex1[0]) 50 | print(f"For the input ,result path is {robot_br(ex1, r, c)}") 51 | -------------------------------------------------------------------------------- /chapter_8/p8_3.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def find_magic_index(arr: List[int]) -> int: 5 | low = 0 6 | high = len(arr)-1 7 | while low <= high: 8 | mid = (low + high) // 2 9 | if mid == arr[mid]: 10 | return mid 11 | elif mid > arr[mid]: 12 | low = mid + 1 13 | else: 14 | high = mid - 1 15 | 16 | return -1 17 | 18 | 19 | def find_magic_index_dups(arr: List[int]) -> int: 20 | return fmidh(arr, 0, len(arr)-1) 21 | 22 | 23 | def fmidh(arr, s, e) -> int: 24 | if s > e: 25 | return -1 26 | 27 | mid = (s+e) // 2 28 | elem = arr[mid] 29 | 30 | if elem == mid: 31 | return mid 32 | 33 | lr = fmidh(arr, s, min(mid - 1, arr[mid])) 34 | if lr > 0: 35 | return lr 36 | 37 | rr = fmidh(arr, max(mid+1, arr[mid]), e) 38 | if rr > 0: 39 | return rr 40 | 41 | return -1 42 | 43 | 44 | if __name__ == "__main__": 45 | ex1 = [-1, 1, 5, 6, 7, 8, 9, 10] 46 | ex_dups = [-10, -5, 2, 2, 2, 3, 4, 5, 9, 12, 13] 47 | 48 | print(f"A good index is {find_magic_index(ex1)}" 49 | f" same as {find_magic_index_dups(ex1)}, in {ex1}") 50 | print(f"With dups index: {find_magic_index_dups(ex_dups)} in {ex_dups}") 51 | -------------------------------------------------------------------------------- /chapter_8/p8_4.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def gen_subsets(set_elems: List[int], offset: int): 5 | if offset == len(set_elems) - 1: 6 | return [[set_elems[offset]], []] 7 | 8 | wo_elem = gen_subsets(set_elems, offset+1) 9 | w_elem = [[set_elems[offset]] + 10 | subset for subset in gen_subsets(set_elems, offset+1)] 11 | return w_elem + wo_elem 12 | 13 | 14 | if __name__ == "__main__": 15 | exs = [ 16 | [1, 2, 3], 17 | [1], 18 | [1, 2, 3, 4] 19 | ] 20 | 21 | for arr in exs: 22 | subsets = gen_subsets(arr, 0) 23 | print(f"Subsets of {arr} are size {len(subsets)}: {subsets}") 24 | -------------------------------------------------------------------------------- /chapter_8/p8_5.py: -------------------------------------------------------------------------------- 1 | from functools import lru_cache 2 | 3 | 4 | def rec_mult(a: int, b: int) -> int: 5 | if not a or not b: 6 | return 0 7 | sm, bg = (a, b) if a < b else (b, a) 8 | return rmh(sm, bg) 9 | 10 | 11 | @lru_cache(None) 12 | def rmh(sm, bg): 13 | if sm == 1: 14 | return bg 15 | 16 | prod = rmh(sm//2, bg) 17 | prod += prod 18 | if sm % 2: 19 | prod += bg 20 | 21 | return prod 22 | 23 | 24 | if __name__ == "__main__": 25 | exs = [(0, 1), (1, 0), (3, 3), (9, 9), (50, 50), (5, 7)] 26 | for a, b in exs: 27 | print(f"{a} times {b} is {a*b} same as {rec_mult(a,b)}") 28 | -------------------------------------------------------------------------------- /chapter_8/p8_6.py: -------------------------------------------------------------------------------- 1 | def hanoi_towers(n): 2 | stacks = [[], [], []] 3 | stacks[0] = list(range(n, 0, -1)) 4 | 5 | print(f"Got start stacks\t{stacks}") 6 | move_stack(n, 0, 2, stacks) 7 | display_stacks(stacks) 8 | 9 | 10 | def move_disk(source_ind, target_ind, stacks): 11 | stacks[target_ind].append(stacks[source_ind].pop()) 12 | 13 | 14 | def display_stacks(stacks): 15 | for i, stack in enumerate(stacks): 16 | print(f"{i} -> {stack}") 17 | print("-"*50) 18 | 19 | 20 | def move_stack(elem_bot, src, target, stacks): 21 | display_stacks(stacks) 22 | temp_stack = 3 - src - target 23 | top_src = stacks[src][-1] 24 | if top_src == elem_bot: 25 | move_disk(src, target, stacks) 26 | else: 27 | move_stack(elem_bot-1, src, temp_stack, stacks) 28 | move_disk(src, target, stacks) 29 | move_stack(elem_bot-1, temp_stack, target, stacks) 30 | 31 | 32 | if __name__ == "__main__": 33 | hanoi_towers(4) 34 | -------------------------------------------------------------------------------- /chapter_8/p8_7.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | def perm_uniq(inp: str): 4 | return rec_perm("", inp) 5 | 6 | 7 | def rec_perm(prefix, ltr_pool): 8 | if not ltr_pool: 9 | return [prefix] 10 | 11 | perms = [] 12 | for i, ltr in enumerate(ltr_pool): 13 | nprefix = prefix + ltr 14 | sub_perm = rec_perm(nprefix, ltr_pool[:i] + ltr_pool[i+1:]) 15 | perms += sub_perm 16 | 17 | return perms 18 | 19 | 20 | if __name__ == "__main__": 21 | exs = ["a", "", "abc", "abcd", "abcdef"] 22 | 23 | for ex in exs: 24 | permutations = perm_uniq(ex) 25 | print( 26 | f"Perms {len(ex)} of nr {len(permutations)}" 27 | f" same as predicted {math.factorial(len(ex))}" 28 | f" \t{ex} are {permutations}") 29 | -------------------------------------------------------------------------------- /chapter_8/p8_8.py: -------------------------------------------------------------------------------- 1 | from collections import Counter 2 | from typing import List 3 | import math 4 | 5 | 6 | def perm_dups(words: str): 7 | count = Counter(words) 8 | res_list_list = perm_w_count(count) 9 | return ["".join(res) for res in res_list_list] 10 | 11 | 12 | def perm_w_count(counter: dict) -> List[List]: 13 | res = [] 14 | has_to_place = False 15 | 16 | for k, v in counter.items(): 17 | if v > 0: 18 | has_to_place = True 19 | call_dict = counter.copy() 20 | # print(f"Call dict is {call_dict}") 21 | call_dict[k] -= 1 22 | res_subcall = perm_w_count(call_dict) 23 | res.extend([[k] + rest for rest in res_subcall]) 24 | 25 | if not has_to_place: 26 | return [[""]] 27 | else: 28 | return res 29 | 30 | 31 | if __name__ == "__main__": 32 | exs = ["a", "", "abc", "abcd", "aaab", "bbaa", "calalc", "aqa"] 33 | 34 | for ex in exs: 35 | permutations = perm_dups(ex) 36 | print( 37 | f"Perms input len: {len(ex)} of computed: {len(permutations)}" 38 | f" \t{ex} are {permutations}") 39 | print("-"*50) 40 | -------------------------------------------------------------------------------- /chapter_8/p8_9.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | def rec_prn_builder(left2p, to_close): 5 | if left2p == 0: 6 | return [[]] 7 | 8 | res = [] 9 | if to_close > 0: 10 | sub_res = rec_prn_builder(left2p - 1, to_close-1) 11 | res.extend([[")"] + sub_r for sub_r in sub_res]) 12 | 13 | if to_close < left2p: 14 | sub_res = rec_prn_builder(left2p-1, to_close+1) 15 | 16 | res.extend([["("] + sub_r for sub_r in sub_res]) 17 | 18 | return res 19 | 20 | 21 | def pair_builder(pairs: int) -> List[str]: 22 | res_lists = rec_prn_builder(pairs*2, 0) 23 | return ["".join(res_list) for res_list in res_lists] 24 | 25 | 26 | if __name__ == "__main__": 27 | print(pair_builder(1)) 28 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name='Leet', 5 | version='0.0.1', 6 | packages=find_packages(), 7 | ) 8 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StBogdan/CTCI_python/3a606f14e57e263b9a6c468da2990d502824a3ef/utils/__init__.py -------------------------------------------------------------------------------- /utils/binutils.py: -------------------------------------------------------------------------------- 1 | # Binary utilities 2 | 3 | def blen(a: int): 4 | return len(bin(a)) - 2 5 | 6 | 7 | def ones(number: int): 8 | return (0b1 << (number)) - 1 9 | 10 | 11 | if __name__ == "__main__": 12 | print(f"{ones(5):b}") 13 | -------------------------------------------------------------------------------- /utils/datasets.py: -------------------------------------------------------------------------------- 1 | from collections import defaultdict 2 | from typing import List 3 | 4 | def get_system_word_list(file_path="/usr/share/dict/words"): 5 | with open(file_path, "r") as system_words_file: 6 | big_list = list(map(lambda word: word.lower(), 7 | system_words_file.read().split("\n"))) 8 | return big_list 9 | 10 | def trie(): 11 | return defaultdict(trie) 12 | 13 | 14 | def build_trie_from_words(words: List[str]) -> dict: 15 | word_trie = trie() 16 | 17 | for word in words: 18 | current_pointer = word_trie 19 | for char in word: 20 | current_pointer = current_pointer[char] 21 | 22 | current_pointer["end"] = True 23 | 24 | return word_trie 25 | -------------------------------------------------------------------------------- /utils/graphs.py: -------------------------------------------------------------------------------- 1 | from typing import List 2 | 3 | 4 | class Vertex: 5 | 6 | def __init__(self, val): 7 | self.val = val 8 | self.edges = set() 9 | 10 | def __str__(self): 11 | return f"Vertex({self.val}) with edges {len(self.edges)}" 12 | 13 | 14 | class Ditex(Vertex): # Directed vertex 15 | 16 | def __init__(self, val): 17 | self.val = val 18 | self.in_edges = set() 19 | self.out_edges = set() 20 | 21 | @property 22 | def edges(self): 23 | return self.in_edges | self.out_edges 24 | 25 | def __str__(self): 26 | return f"Ditex({self.val})(in:{len(self.in_edges)})(out:{len(self.out_edges)})" 27 | 28 | 29 | class BiNode: 30 | def __str__(self): 31 | return f"({self.left if self.left else '_' }/{self.val}\\{self.right if self.right else '_'})" 32 | 33 | def __init__(self, val, left=None, right=None): 34 | self.val = val 35 | self.left = left 36 | self.right = right 37 | 38 | 39 | class BiPaNode(BiNode): 40 | 41 | def __init__(self, val, left=None, right=None): 42 | super().__init__(val, left=left, right=right) 43 | self.parent = None 44 | 45 | 46 | def ltbt(arr: List[int]) -> BiNode: 47 | return list_to_binary_tree(arr) 48 | 49 | 50 | def list_to_binary_tree(arr: List[int]) -> BiNode: 51 | nodes = [BiPaNode(arr[0])] 52 | for i in range(1, len(arr)): 53 | parent = nodes[(i - 1)//2] 54 | 55 | if not parent and arr[i]: 56 | raise Exception(f"Cannot have children without parents to attach to," 57 | f" for {arr[i]} on input {arr}") 58 | 59 | if not arr[i]: 60 | nodes.append(None) 61 | else: 62 | node_now = BiPaNode(arr[i]) 63 | node_now.parent = parent 64 | 65 | nodes.append(node_now) 66 | if (i-1) % 2: 67 | parent.right = node_now 68 | else: 69 | parent.left = node_now 70 | 71 | return nodes[0] 72 | 73 | 74 | def get_node_by_val(root: BiNode, target): 75 | if not root: 76 | return None 77 | if root.val == target: 78 | return root 79 | else: 80 | return get_node_by_val(root.left, target) \ 81 | or get_node_by_val(root.right, target) 82 | -------------------------------------------------------------------------------- /utils/lorem_ipsum.txt: -------------------------------------------------------------------------------- 1 | Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc diam metus, venenatis ut nibh eget, sollicitudin dictum neque. In vehicula erat at libero vehicula, ac pharetra erat dictum. Vestibulum eget elit quis ipsum convallis consectetur. Pellentesque sit amet elit ut augue euismod dapibus. Vivamus sed viverra nunc. Etiam facilisis nunc mauris, interdum posuere velit euismod at. Curabitur scelerisque posuere sollicitudin. Cras nulla magna, malesuada eget vehicula rutrum, pellentesque vitae arcu. Ut pharetra pharetra faucibus. Nunc libero turpis, feugiat sit amet purus sit amet, convallis elementum risus. In sed lobortis orci. Suspendisse facilisis leo feugiat, dignissim est sed, euismod tellus. Ut ullamcorper mi quis dapibus ornare. Integer nec felis nisl. Aliquam ultrices volutpat egestas. 2 | 3 | Phasellus feugiat volutpat dolor, ac vulputate sem scelerisque at. Donec ullamcorper eros odio, vel hendrerit libero venenatis accumsan. Curabitur eros ipsum, fringilla a nisi at, mattis luctus sapien. Nullam dignissim dapibus turpis, vitae aliquet nulla tincidunt non. Fusce varius, lorem non elementum bibendum, tellus urna dignissim turpis, sed gravida mauris dolor sed elit. In sollicitudin, sapien id efficitur dictum, ex sapien tristique arcu, quis dapibus orci felis feugiat sem. Sed gravida egestas velit id porta. Vivamus facilisis tincidunt vulputate. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut lacinia rhoncus sem a semper. Integer molestie magna ligula, at interdum lorem consectetur lobortis. Integer lacinia porttitor varius. Donec interdum mauris gravida dignissim vestibulum. 4 | 5 | Praesent euismod pulvinar odio, vitae feugiat elit viverra volutpat. Nulla arcu ante, cursus in augue at, posuere auctor elit. Proin eu justo ipsum. Suspendisse porta fringilla odio, nec euismod leo dapibus eget. Fusce ullamcorper justo tortor, nec gravida risus euismod vel. Sed ultrices ante ac neque posuere porta. Nunc cursus erat in blandit eleifend. Donec vestibulum felis quis scelerisque posuere. Integer vulputate aliquam metus, eu gravida ligula hendrerit non. Aenean nunc tellus, gravida eget tellus non, sodales pellentesque dui. Donec pellentesque sed elit quis cursus. Integer sed tellus sollicitudin, cursus lorem a, pretium mauris. 6 | 7 | Cras nec maximus lacus. Pellentesque accumsan ante et dui viverra, et dignissim ligula semper. Fusce interdum commodo massa. Sed at enim eleifend, ultrices dui quis, porta turpis. Aenean in sollicitudin leo, sed vehicula nunc. Quisque orci lectus, fringilla non euismod a, fringilla in augue. Vestibulum id gravida ex. Donec suscipit id nunc sed sodales. Sed placerat eros ac diam gravida tempus. 8 | 9 | Nullam iaculis lectus a metus viverra, sed mattis lorem pretium. Vestibulum hendrerit sapien vitae posuere sagittis. Fusce at ipsum interdum, ultrices nibh vel, lacinia diam. Suspendisse scelerisque, arcu ac sodales egestas, est urna condimentum mi, ac molestie velit ligula non arcu. Etiam in luctus enim. Morbi eget justo at elit bibendum mattis. Etiam ac quam et sapien ultrices commodo. Donec ipsum enim, tempus nec nulla a, dapibus maximus ipsum. Etiam non imperdiet augue. Phasellus cursus ex quis lectus ultricies, at faucibus dolor aliquam. Duis in malesuada massa. 10 | 11 | Integer sodales lorem sit amet lacus bibendum pretium. Donec at vulputate eros. Cras vulputate arcu eu tortor commodo iaculis. Praesent tristique nibh vel purus vehicula pharetra. Cras tristique orci nec mattis dictum. Proin iaculis congue felis, laoreet efficitur eros consequat sed. Fusce molestie, lacus quis aliquet fringilla, mauris elit dignissim ligula, nec vehicula velit mi et sem. Integer efficitur, augue at egestas hendrerit, odio nisl vehicula enim, non feugiat nisi ex et est. Donec consequat, ante quis laoreet semper, nibh leo iaculis arcu, vitae finibus velit est sit amet odio. Suspendisse ultrices elementum commodo. Nullam sit amet nisl erat. Phasellus facilisis augue sed diam rutrum, posuere pulvinar mi maximus. Duis mollis nec risus nec ornare. Suspendisse tempor dignissim sapien, sed blandit ligula pulvinar ut. 12 | 13 | Sed tristique nisl in tellus bibendum ultrices. Maecenas vel placerat nisl, vel viverra lorem. Aenean tempus neque quis mattis pharetra. Sed ornare eleifend eros, ac volutpat massa. Nunc in nibh fringilla, suscipit diam vel, lacinia ipsum. Pellentesque pellentesque erat sed consequat porta. Nunc bibendum enim eget est eleifend finibus. Pellentesque enim risus, pulvinar vitae dapibus vel, dictum ut arcu. 14 | 15 | Phasellus finibus magna et augue condimentum elementum. Aenean aliquet pulvinar libero quis pretium. Curabitur eget felis non neque pretium sagittis ut in urna. Donec accumsan justo nec est lobortis, in consectetur lorem elementum. Vestibulum sit amet ullamcorper lorem. Nullam massa ante, posuere sit amet sodales sit amet, feugiat finibus libero. Aliquam semper enim a sagittis gravida. 16 | 17 | Pellentesque aliquam luctus magna nec ultrices. Vestibulum quis enim sed dolor vulputate accumsan. Proin blandit eros sem, vitae pulvinar ante viverra non. Integer facilisis lorem augue, nec fringilla mauris pulvinar sit amet. Proin a metus eu eros sodales accumsan id in lacus. Nam lobortis erat a vestibulum iaculis. Integer sodales nulla non turpis ultrices, nec efficitur urna ultricies. Fusce ut tellus eget turpis varius iaculis eu porta elit. Duis at erat lorem. Quisque posuere diam nulla, at rutrum dui scelerisque et. Etiam luctus, quam vitae ornare facilisis, ante nibh volutpat augue, sodales pellentesque nulla dolor in justo. 18 | 19 | Phasellus mattis dui a dolor rhoncus, sit amet venenatis magna accumsan. In id vehicula nibh. In et volutpat neque. Proin pretium, dui in pretium aliquet, ante urna tincidunt purus, quis posuere neque odio eget dolor. Phasellus ut diam suscipit, pellentesque neque nec, laoreet justo. Nulla pharetra nisi ligula, a ultricies enim iaculis vitae. Praesent nisl libero, rhoncus at orci non, luctus pulvinar mauris. Mauris sed feugiat mi. Praesent venenatis posuere nunc sed laoreet. Integer posuere sem a diam sodales, vitae ullamcorper ligula ornare. Duis eget arcu vitae metus dapibus rutrum vitae a orci. Etiam ornare felis non ex aliquam, vel mollis tortor pharetra. Duis sed tortor maximus, pulvinar nibh eget, lobortis orci. Interdum et malesuada fames ac ante ipsum primis in faucibus. Duis quis massa faucibus, laoreet est at, posuere elit. Mauris ullamcorper, orci ut iaculis faucibus, nunc ex pellentesque risus, eu interdum mi lectus ultricies purus. 20 | 21 | Etiam viverra fringilla dignissim. Quisque ac iaculis velit. Fusce at faucibus felis. Sed faucibus et magna lacinia imperdiet. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Ut lacinia nunc et sem interdum tempus. Morbi non neque ligula. Pellentesque eget imperdiet massa, eget imperdiet eros. 22 | 23 | Pellentesque vitae velit libero. Etiam finibus volutpat lacus quis iaculis. Fusce vitae auctor magna. Donec pellentesque, nisi at iaculis cursus, lorem ipsum congue massa, non vehicula dui nisl non diam. Quisque tempor lacus bibendum orci vestibulum, vel suscipit nisi euismod. Pellentesque vitae tortor nibh. Fusce a nisi facilisis, lobortis justo quis, luctus augue. Nullam fringilla magna tempus tincidunt bibendum. Sed facilisis elit augue, ac finibus metus malesuada quis. Proin sollicitudin augue in orci maximus, eget varius lacus tincidunt. Curabitur ut velit arcu. Integer aliquam sodales venenatis. Suspendisse at ligula nec ante iaculis varius eget sed libero. Donec elementum porta turpis, nec ornare ipsum interdum vitae. Maecenas a lorem risus. 24 | 25 | Phasellus et mauris a tortor viverra scelerisque a non nulla. Fusce suscipit velit sit amet neque facilisis condimentum. Nunc sit amet nunc feugiat, pretium quam id, laoreet purus. Nunc id nunc tellus. Vivamus congue, felis nec commodo tristique, massa dui pulvinar mauris, dignissim rhoncus eros sem at enim. Curabitur tincidunt varius felis ut egestas. Vestibulum sed posuere felis. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Morbi dictum ultricies dignissim. Nullam ut condimentum tortor. Donec ut risus ut risus auctor molestie finibus vel eros. Proin sagittis pulvinar leo, eu molestie justo viverra sit amet. Curabitur interdum, sem consectetur cursus scelerisque, justo lorem dignissim dui, vitae semper ipsum mi quis arcu. Maecenas ut magna non risus posuere aliquet quis vitae urna. Nulla feugiat viverra nunc, a finibus felis dapibus eu. Aliquam sed auctor purus, sit amet eleifend ipsum. 26 | 27 | In sollicitudin nibh ut est maximus cursus. Aenean vestibulum a felis eget blandit. Nam eu tellus eleifend, imperdiet quam ut, rhoncus diam. Nullam odio libero, commodo nec dictum vel, dignissim a erat. Morbi dignissim convallis nunc a rhoncus. Nullam ultrices eu velit a ultricies. Phasellus sollicitudin libero at egestas condimentum. Nam congue turpis ante. Cras ac semper lectus, nec pretium velit. Sed nec nibh massa. Phasellus lectus ex, auctor id mattis eu, efficitur id nisi. Suspendisse bibendum pellentesque orci ac bibendum. Quisque venenatis porta elit. Donec sit amet velit eget nibh vehicula ullamcorper. Aenean dignissim ut nulla ut vehicula. 28 | 29 | Suspendisse potenti. Proin sed leo eget felis ultricies hendrerit. Aenean eget rutrum lorem. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Cras ultrices sollicitudin aliquet. Sed consectetur blandit congue. Sed semper sollicitudin metus, eget lobortis nunc vulputate sit amet. Morbi vehicula viverra ipsum, et aliquam nisl auctor ut. Ut vel imperdiet enim. Pellentesque porta justo et mi auctor aliquet. Aliquam nisi sapien, condimentum et mi a, dictum aliquet nunc. Cras id ante nulla. Integer imperdiet dui ac erat aliquet, a convallis enim tempus. Aliquam ullamcorper nibh nulla, quis ullamcorper magna consequat vel. Donec at libero ut massa blandit tincidunt vel suscipit nisl. 30 | 31 | Sed sed rhoncus nisl. Nunc et maximus lectus. Phasellus lobortis hendrerit dui sit amet maximus. Aenean viverra purus eros, a luctus tellus dapibus non. Aenean urna dui, imperdiet at nunc sit amet, sagittis elementum lectus. Nam ac justo sapien. Sed lacinia egestas dolor, ac varius nisl varius eget. Cras vehicula odio sit amet velit rhoncus commodo. Quisque ut orci in tortor ullamcorper egestas. Vestibulum scelerisque luctus lacus, et consectetur libero consequat et. Ut venenatis nisl id convallis ullamcorper. Nulla sed nibh nunc. Sed pulvinar massa eget tortor mollis, ut ornare turpis maximus. Proin ultricies mattis mi, vitae sodales tellus finibus quis. 32 | 33 | Donec congue neque et urna laoreet, euismod vestibulum enim laoreet. Vivamus in mollis nisl. Pellentesque fermentum viverra mi, quis mollis nulla volutpat ut. Cras vitae vestibulum dui. Phasellus hendrerit risus turpis, pretium rutrum sem semper vitae. Suspendisse ac nisi massa. Fusce at est vel mi congue consequat eu nec metus. Duis ac ex tristique, porttitor magna nec, tristique enim. Ut eget dolor et risus efficitur dignissim sodales vel nisi. Donec tempus, enim eu cursus vestibulum, velit lectus fermentum erat, vitae mattis mauris neque a felis. In hac habitasse platea dictumst. Nullam tincidunt nunc sit amet malesuada finibus. Proin congue elementum lobortis. Suspendisse volutpat nisl et blandit porttitor. Curabitur nunc ex, euismod eget tortor nec, tincidunt maximus libero. Nullam feugiat eget ex non varius. 34 | 35 | Sed non porttitor arcu. Vestibulum egestas, est at consequat ultricies, ante lorem lobortis sem, sed euismod ante urna nec quam. Maecenas placerat eros diam, id ultricies ligula sodales nec. Vestibulum ut tempor nibh, sed tincidunt ante. Donec in ligula elementum, gravida nisi gravida, elementum erat. Maecenas gravida, nisl quis vulputate pulvinar, lorem sem eleifend felis, eu molestie erat lectus id mauris. Suspendisse convallis, magna at sollicitudin sollicitudin, nulla eros faucibus mauris, ut mattis metus nibh vitae ipsum. Mauris varius ligula vel diam vestibulum suscipit. Vestibulum ultrices lacus vitae neque dictum, eget tincidunt dui auctor. 36 | 37 | Phasellus imperdiet arcu massa, lobortis vehicula libero varius id. Nunc vel sem dolor. Suspendisse hendrerit justo nec ipsum sodales, vitae pulvinar nibh gravida. Quisque fermentum lobortis enim ut congue. Aenean maximus sit amet metus sed elementum. Nam et nisl vitae risus lobortis suscipit. Vestibulum gravida odio a lorem tristique convallis. Sed aliquet, purus quis pretium convallis, dui nisi interdum velit, sit amet viverra lacus orci vitae erat. Curabitur molestie sapien eget risus consequat, sit amet posuere felis condimentum. Etiam neque risus, euismod eu consequat id, aliquet id purus. Donec in quam eget ipsum dictum cursus vel at odio. Aenean posuere molestie erat vel convallis. 38 | 39 | Nullam in rhoncus neque. Aenean efficitur magna vel quam porttitor aliquet. Cras quis dolor nibh. Donec cursus, augue quis cursus rhoncus, lorem sapien efficitur augue, eu aliquet enim purus sit amet lacus. Aenean ac rutrum arcu. Etiam id tortor sit amet nisi ornare congue. Aliquam vitae nunc vitae enim porttitor suscipit id vel nunc. Donec fermentum consectetur dolor, non bibendum eros. -------------------------------------------------------------------------------- /utils/treeviz.py: -------------------------------------------------------------------------------- 1 | from graphviz import Digraph 2 | from utils.graphs import BiNode, ltbt 3 | 4 | 5 | def viz_tree(root: BiNode): 6 | graf = Digraph() 7 | 8 | q = [root] 9 | 10 | node_id, node_val = get_node_for_graph(root) 11 | graf.node(node_id, node_val) 12 | 13 | while q: 14 | now_node = q.pop(0) 15 | now_node_id, _ = get_node_for_graph(now_node) 16 | 17 | for node in (now_node.left, now_node.right): 18 | if node is not None: 19 | node_id, node_val = get_node_for_graph(node) 20 | graf.node(node_id, node_val) 21 | graf.edge(now_node_id, node_id) 22 | q.append(node) 23 | 24 | graf.render(filename='graph_out/result.dot') 25 | 26 | 27 | def get_node_for_graph(node): 28 | node_id = str(id(node)) 29 | node_val = str(node.val) 30 | return node_id, node_val 31 | 32 | 33 | if __name__ == "__main__": 34 | test_arr = list(range(10)) 35 | root = ltbt(test_arr) 36 | 37 | print(root) 38 | 39 | viz_tree(root) 40 | --------------------------------------------------------------------------------