├── README.md ├── 43-merge-sorted-arrays.py ├── 33-which-appears-twice.py ├── 26-reverse-string-in-place.py ├── 22-delete-node.py ├── 31-recursive-string-permutations.py ├── 37-simulate-5-sided-die.py ├── 24-reverse-linked-list.py ├── 38-simulate-7-sided-die.py ├── 28-matching-parens.py ├── 30-permutation-palindrome.py ├── 14-inflight-entertainment.py ├── list-trees.py ├── 15-nth-fibonacci.py ├── 27-reverse-words.py ├── 19-queue-two-stacks.py ├── 1-stocks.py ├── 4-merging-ranges.py ├── 20-largest-stack.py ├── 25-kth-to-last-node-in-singly-linked-list.py ├── 23-linked-list-cycles.py ├── 29-bracket-validator.py ├── 7-temperature-tracker.py ├── q.html ├── 21-find-unique-int-among-duplicates.py ├── 34-word-cloud.py ├── 41-find-duplicate-optimize-for-space-beast-mode.py ├── 3-highest-product-of-3.py ├── 35-shuffle.py ├── 2-product-of-other-numbers.py ├── 13-find-rotation-point.py ├── 32-top-scores.py ├── 10-second-largest-item-in-bst.py ├── 6-rectangular-love.py ├── 9-bst-checker.py ├── 5-making-change.py ├── 36-single-riffle-check.py ├── 8-balanced-binary-tree.py ├── 16-cake-thief.py └── 11-compress-url-list.py /README.md: -------------------------------------------------------------------------------- 1 | This is my take on [interview cake's questions](https://www.interviewcake.com/table-of-contents). 2 | 3 | I like most of cake's questions, and the format. They are good review material in most cases, and at other times introduce me to topics I had missed. 4 | 5 | As usual with interview questions there are inevitably the questions where you either see the solution (or have seen the solution before) or will just never get what the interviewer is asking for. 6 | 7 | And often too, with these, I am indeed amazed at the notion that many of these can be answered 8 | 9 | + on a whiteboard 10 | + in 20 minutes or less 11 | + by people with no basic whiteboarding, on the fly, coding experience 12 | 13 | If I can do a couple of these in a day, by myself, I'm pretty good with that. 14 | 15 | So here are some code snippets to look at, play with, evaluate. 16 | 17 | The code was tested in Python 2, though it was written such that it will probably work as is in Python 3. 18 | 19 | The files here can be run by typing: 20 | 21 | python filename 22 | 23 | at which point unit tests will kick in, and sort of verify they work. For details though, after 24 | 25 | python filename 26 | 27 | you should type 28 | 29 | more|vi|emacs filename 30 | 31 | to see the details 32 | 33 | 34 | A few of the interview cake questions are indeed interview questions and not amenable to coding solutions. This repo contains only the coding. 35 | -------------------------------------------------------------------------------- /43-merge-sorted-arrays.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/merge-sorted-arrays 9 | # 10 | 11 | # In order to win the prize for most cookies sold, my friend Alice and I 12 | # are going to merge our Girl Scout Cookies orders and enter as one 13 | # unit. 14 | 15 | # Each order is represented by an "order id" (an integer). 16 | 17 | # We have our lists of orders sorted numerically already, in 18 | # lists. Write a function to merge our lists of orders into one sorted 19 | # list. 20 | 21 | # For example: 22 | 23 | my_list = [3, 4, 6, 10, 11, 15] 24 | alices_list = [1, 5, 8, 12, 14, 19] 25 | 26 | # print merge_lists(my_list, alices_list) 27 | 28 | # 29 | ###################################################################### 30 | 31 | # Now my turn... 32 | 33 | 34 | def merge_lists(listi, listj): 35 | """merge these two sorted lists""" 36 | 37 | sortd = [] 38 | i = 0 39 | j = 0 40 | leni = len(listi) 41 | lenj = len(listj) 42 | while i < leni or j < lenj: 43 | ith = listi[i] if i < leni else float('inf') 44 | jth = listj[j] if j < lenj else float('inf') 45 | if ith < jth: 46 | i += 1 47 | sortd.append(ith) 48 | else: 49 | j += 1 50 | sortd.append(jth) 51 | return sortd 52 | 53 | # Now Test 54 | 55 | 56 | class TestMergeSortedArrays(unittest.TestCase): 57 | 58 | def test_given(self): 59 | print(merge_lists(my_list, alices_list)) 60 | 61 | 62 | if __name__ == "__main__": 63 | # unittest.main() 64 | suite = unittest.TestLoader().loadTestsFromTestCase(TestMergeSortedArrays) 65 | unittest.TextTestRunner(verbosity=2).run(suite) 66 | -------------------------------------------------------------------------------- /33-which-appears-twice.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/which-appears-twice 9 | # 10 | 11 | # I have a list where every number in the range 1...n appears once 12 | # except for one number which appears twice. 13 | 14 | # Write a function for finding the number that appears twice. 15 | 16 | # 17 | ###################################################################### 18 | 19 | # Now my turn 20 | 21 | # 1. first try, not seen, 1/2 a binary search until I realized list of 22 | # ints is not necessarily sorted. 23 | # 2. 2nd try, in my head, can I xor them? Nah. 24 | 25 | # 3. 3rd try, I recall the gaussian formula to add n consecutive 26 | # integers, and then realize I can quickly determine this number 27 | # as well as sum my entire list 28 | # and the difference will be the duplicated number 29 | 30 | 31 | def which_twice(ints): 32 | """find the number in ints that appears twice""" 33 | 34 | n = len(ints) 35 | # n ints, so the list is 36 | # 1..(n-1) + some duplicated number 37 | # = ((n-1) * (1 + n - 1) / 2) 38 | # = ((n-1) * (n) / 2) 39 | # = ((n-1) * (n / 2.0) 40 | gauss = int((n - 1) * (n / 2.0)) 41 | total = sum(ints) 42 | dupe = total - gauss 43 | return dupe 44 | 45 | 46 | class TestTwice(unittest.TestCase): 47 | 48 | def test_examples(self): 49 | """test some examples""" 50 | tests = [ 51 | [1, [1, 1, 2, 3]], 52 | [2, [1, 2, 2, 3]], 53 | [2, [1, 2, 2, 3]], 54 | [2, [1, 2, 2, 3]], 55 | [2, [1, 2, 2, 3]], 56 | [3, [3, 1, 2, 3]], 57 | ] 58 | 59 | for soln, ints in tests: 60 | print("") 61 | print("%s <- %s" % (soln, ints)) 62 | ans = which_twice(ints) 63 | print("%s <= %s" % (ans, ints)) 64 | self.assertEqual(soln, ans) 65 | 66 | 67 | if __name__ == "__main__": 68 | # unittest.main() 69 | suite = unittest.TestLoader().loadTestsFromTestCase(TestTwice) 70 | unittest.TextTestRunner(verbosity=2).run(suite) 71 | -------------------------------------------------------------------------------- /26-reverse-string-in-place.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/reverse-string-in-place 9 | # 10 | 11 | # Write a function to reverse a string in-place(*). 12 | 13 | # (*) Since strings in Python are immutable, first convert the string into a 14 | # list of characters, do the in-place reversal on that list, and re-join 15 | # that list into a string before returning it. This isn't technically 16 | # "in-place" and the list of characters will cost O(n)O(n) additional 17 | # space, but it's a reasonable way to stay within the spirit of the 18 | # challenge. If you're comfortable coding in a language with mutable 19 | # strings, that'd be even better! 20 | 21 | # 22 | ###################################################################### 23 | 24 | # Now my turn 25 | 26 | 27 | def reverse_bytes_in_place(l): 28 | """reverse the list in place""" 29 | 30 | n = len(l) 31 | for i in range(n / 2): 32 | (l[i], l[n - i - 1]) = (l[n - i - 1], l[i]) 33 | 34 | # I like my solution that uses these in place, no need for a 35 | # temp val swap but at hacker news, someone says the "correct" 36 | # way is, and I think they are right, to use extended slices 37 | # as: 38 | 39 | # return l[::-1] 40 | 41 | return l 42 | 43 | 44 | def string_reverse(string): 45 | """reverse a string 'in place' in place""" 46 | 47 | # raise TypeError # (actual correct Python answer) 48 | 49 | b = bytearray(string) 50 | b = reverse_bytes_in_place(b) 51 | 52 | return str(b) 53 | 54 | 55 | # Now Test 56 | 57 | 58 | class TestReverseStringInPlace(unittest.TestCase): 59 | 60 | def test_string_reverse_in_place(self): 61 | """test string reversal""" 62 | self.assertEqual("", string_reverse("")) 63 | self.assertEqual("e", string_reverse("e")) 64 | self.assertEqual("abcd", string_reverse("dcba")) 65 | self.assertEqual("abcde", string_reverse("edcba")) 66 | self.assertEqual("jihgfedcba", string_reverse("abcdefghij")) 67 | 68 | if __name__ == "__main__": 69 | # unittest.main() 70 | suite = unittest.TestLoader().loadTestsFromTestCase(TestReverseStringInPlace) 71 | unittest.TextTestRunner(verbosity=2).run(suite) 72 | -------------------------------------------------------------------------------- /22-delete-node.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | import operator 6 | 7 | ###################################################################### 8 | # this problem is from 9 | # https://www.interviewcake.com/question/python/delete-node 10 | # 11 | 12 | # Delete a node from a singly-linked list , given only a variable 13 | # pointing to that node. 14 | 15 | # The input could, for example, be the variable b below: 16 | 17 | 18 | class LinkedListNode: 19 | 20 | def __init__(self, value): 21 | self.value = value 22 | self.next = None 23 | 24 | 25 | def example(): 26 | a = LinkedListNode('A') 27 | b = LinkedListNode('B') 28 | c = LinkedListNode('C') 29 | 30 | a.next = b 31 | b.next = c 32 | 33 | delete_node(b) 34 | 35 | 36 | # 37 | ###################################################################### 38 | 39 | # Now my turn 40 | 41 | # obvious attack: overwrite and shift up 42 | 43 | # what this really does is overwriting our current node with data from 44 | # the following node. That next node will be garbage collected if 45 | # nothing else points to it. This seemingly fails when you are handed 46 | # the last node. Sure you can mark it's data as invalid, but still the 47 | # prior node's next will still point to this ghost of a node. 48 | 49 | def delete_node(node): 50 | """deletes a node from the middle of a linked list""" 51 | 52 | ptr = node.next 53 | if ptr: 54 | node.next = ptr.next 55 | node.value = ptr.value 56 | 57 | else: 58 | # tail end node, what to do? 59 | # seemingly only thing to do is raise an Exception 60 | raise ValueError("Bad input! Can't delete tail node.") 61 | 62 | # Now Test 63 | 64 | 65 | class TestDeleteNode(unittest.TestCase): 66 | 67 | def setUp(self): 68 | self.a = LinkedListNode('A') 69 | self.b = LinkedListNode('B') 70 | self.c = LinkedListNode('C') 71 | self.a.next = self.b 72 | self.b.next = self.c 73 | 74 | def test_DeleteNode(self): 75 | """delete a node from middle of list""" 76 | delete_node(self.b) 77 | self.assertEqual('C', self.a.next.value) 78 | self.assertEqual(self.b, self.a.next) 79 | 80 | def test_DeleteTailNode(self): 81 | """delete a tail end and raise an Exception""" 82 | self.assertRaises(ValueError, delete_node, self.c) 83 | 84 | 85 | if __name__ == "__main__": 86 | # unittest.main() 87 | suite = unittest.TestLoader().loadTestsFromTestCase(TestDeleteNode) 88 | unittest.TextTestRunner(verbosity=2).run(suite) 89 | -------------------------------------------------------------------------------- /31-recursive-string-permutations.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/recursive-string-permutations 9 | # 10 | 11 | # Write a recursive function for generating all permutations of an input 12 | # string. Return them as a set. 13 | 14 | # Don't worry about time or space complexity -- if we wanted efficiency 15 | # we'd write an iterative version. 16 | 17 | # To start, assume every character in the input string is unique. 18 | 19 | # Your function can have loops -- it just needs to also be recursive. 20 | 21 | # 22 | ###################################################################### 23 | 24 | # Now my turn 25 | 26 | 27 | def permute(string): 28 | """returns a list of all permutations of the input string""" 29 | 30 | if len(string) <= 1: 31 | return set([string]) 32 | 33 | all_permutations = [] 34 | 35 | for i in range(len(string)): 36 | first = string[i] 37 | rest = string[:i] + string[i + 1:] 38 | permutations = permute(rest) 39 | for permutation in permutations: 40 | all_permutations.append(first + permutation) 41 | 42 | return set(all_permutations) 43 | 44 | # And now the tests 45 | 46 | 47 | class TestStringPermutations(unittest.TestCase): 48 | 49 | def test_s(self): 50 | """some simple tests""" 51 | tests = [ 52 | ["", [""]], 53 | ["a", ["a"]], 54 | ["ab", ["ab", "ba"]], 55 | ["abc", ["abc", "acb", "bac", "bca", "cab", "cba"]], 56 | ["abcd", 57 | [ 58 | 'abcd', 'abdc', 'acbd', 'acdb', 'adbc', 'adcb', 59 | 'bacd', 'badc', 'bcad', 'bcda', 'bdac', 'bdca', 60 | 'cabd', 'cadb', 'cbad', 'cbda', 'cdab', 'cdba', 61 | 'dabc', 'dacb', 'dbac', 'dbca', 'dcab', 'dcba']] 62 | ] 63 | 64 | for string, soln in tests: 65 | soln_set = set(soln) 66 | permutation_set = permute(string) 67 | print("\npermutation of '%s' is %s" % 68 | (string, permutation_set), end="") 69 | self.assertEqual(soln_set, permutation_set, 70 | "permutations of '%s' should be %s but are %s" % 71 | (string, soln_set, permutation_set)) 72 | print("") 73 | 74 | 75 | if __name__ == "__main__": 76 | # unittest.main() 77 | suite = unittest.TestLoader().loadTestsFromTestCase(TestStringPermutations) 78 | unittest.TextTestRunner(verbosity=2).run(suite) 79 | -------------------------------------------------------------------------------- /37-simulate-5-sided-die.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import random 5 | import unittest 6 | 7 | ###################################################################### 8 | # this problem is from 9 | # https://www.interviewcake.com/question/python/simulate-5-sided-die 10 | # 11 | 12 | # You have a function rand7() that generates a random integer from 1 to 13 | # 7. Use it to write a function rand5() that generates a random integer 14 | # from 1 to 5. 15 | 16 | # rand7() returns each integer with equal probability. rand5() must also 17 | # return each integer with equal probability. 18 | 19 | 20 | ###################################################################### 21 | 22 | # now my turn 23 | 24 | def rand7(): 25 | """return a random integer from 1 to 7""" 26 | return random.randrange(1, 8) 27 | 28 | # first try: 29 | 30 | 31 | def rand5(): 32 | """return a random integer from 1 to 5""" 33 | while True: 34 | r = rand7() 35 | if r < 6: 36 | return r 37 | 38 | 39 | def rand5natural(): 40 | """return a random integer from 1 to 5""" 41 | return random.randrange(1, 6) 42 | 43 | 44 | # now test 45 | 46 | 47 | class Test5SidedDie(unittest.TestCase): 48 | 49 | def test_rand5(self): 50 | """test the uniformity of the solution, by computing std deviation 51 | after many trials, showing it converges to a small number""" 52 | for fn in [rand5, rand5natural]: 53 | print("\n%s" % fn.__name__) 54 | for trials in [100, 1000, 10000, 100000]: 55 | freq = [0] * 5 56 | for i in range(trials): 57 | r = fn() 58 | i = r - 1 59 | freq[i] += 1 60 | 61 | total = sum(freq) 62 | mean = total / (5.0 * trials) 63 | averages = [freq[j] / float(trials) for j in range(5)] 64 | diffs = [(averages[j] - mean) for j in range(5)] 65 | sq_diffs = [diffs[j] ** 2 for j in range(5)] 66 | variance = sum(sq_diffs) / 5.0 67 | stddev = variance**0.5 68 | 69 | print("\n%s trials" % trials) 70 | print("5 sided die rolls %s" % freq) 71 | print("averages %s" % averages) 72 | print("mean %5.4s variance %5.4s stddev %8.6s" % 73 | (mean, variance, stddev)) 74 | 75 | self.assertAlmostEqual(stddev, 0, delta=0.1) 76 | 77 | 78 | if __name__ == "__main__": 79 | # unittest.main() 80 | suite = unittest.TestLoader().loadTestsFromTestCase(Test5SidedDie) 81 | unittest.TextTestRunner(verbosity=2).run(suite) 82 | -------------------------------------------------------------------------------- /24-reverse-linked-list.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/reverse-linked-list 9 | # 10 | 11 | # Hooray! It's opposite day. Linked lists go the opposite way today. 12 | # Write a function for reversing a linked list. Do it in-place. 13 | 14 | # Your function will have one input: the head of the list. 15 | # Your function should return the new head of the list. 16 | 17 | # Here's a sample linked list node class: 18 | 19 | 20 | class LinkedListNode: 21 | 22 | def __init__(self, value): 23 | self.value = value 24 | self.next = None 25 | 26 | # 27 | ###################################################################### 28 | 29 | # Now my turn 30 | 31 | 32 | def reverse_linked_list(head): 33 | """reverse a singly linked list in place""" 34 | if head: 35 | onebehind = None 36 | twobehind = None 37 | while head.next: 38 | twobehind = onebehind 39 | onebehind = head 40 | head = head.next 41 | onebehind.next = twobehind 42 | head.next = onebehind 43 | return head 44 | 45 | 46 | def ll2list(head): 47 | """converts our linked list to a python list""" 48 | if head: 49 | list = [head.value] 50 | while head.next: 51 | head = head.next 52 | list.append(head.value) 53 | return list 54 | else: 55 | return None 56 | 57 | # Now Test 58 | 59 | 60 | class TestReverseLinkedList(unittest.TestCase): 61 | 62 | def setUp(self): 63 | self.a = LinkedListNode('A') 64 | self.b = LinkedListNode('B') 65 | self.c = LinkedListNode('C') 66 | self.d = LinkedListNode('D') 67 | 68 | self.a.next = self.b 69 | self.b.next = self.c 70 | self.c.next = self.d 71 | 72 | def test_0empty(self): 73 | """No linked list just None""" 74 | head = reverse_linked_list(None) 75 | self.assertEquals(None, head) 76 | 77 | def test_1node(self): 78 | """a linked list of one element""" 79 | self.a.next = None 80 | head = reverse_linked_list(self.a) 81 | self.assertEqual(self.a, head) 82 | 83 | def test_4banger(self): 84 | """no cycles""" 85 | 86 | l1 = ll2list(self.a) 87 | head = reverse_linked_list(self.a) 88 | l2 = ll2list(head) 89 | l2.reverse() 90 | self.assertEquals(l1, l2) 91 | 92 | if __name__ == "__main__": 93 | # unittest.main() 94 | suite = unittest.TestLoader().loadTestsFromTestCase(TestReverseLinkedList) 95 | unittest.TextTestRunner(verbosity=2).run(suite) 96 | -------------------------------------------------------------------------------- /38-simulate-7-sided-die.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import random 5 | import unittest 6 | 7 | ###################################################################### 8 | # this problem is from 9 | # https://www.interviewcake.com/question/python/simulate-7-sided-die 10 | # 11 | 12 | # You have a function rand5() that generates a random integer from 1 to 13 | # 5. Use it to write a function rand7() that generates a random integer 14 | # from 1 to 7. 15 | 16 | # rand5() returns each integer with equal probability. rand7() must also 17 | # return each integer with equal probability. 18 | 19 | ###################################################################### 20 | 21 | # now my turn 22 | 23 | 24 | def rand5(): 25 | """return a random integer from 1 to 5""" 26 | return random.randrange(1, 6) 27 | 28 | # first try: 29 | 30 | 31 | # implementation of the canonical rejection sampling algorithm (found 32 | # by googling) 33 | 34 | def rand7(): 35 | """return a random integer from 1 to 7""" 36 | while True: 37 | r1 = 5 * (rand5() - 1) 38 | r2 = rand5() 39 | r = r1 + r2 40 | if r <= 21: 41 | return r % 7 + 1 42 | 43 | 44 | def rand7natural(): 45 | """return a random integer from 1 to 7""" 46 | return random.randrange(1, 8) 47 | 48 | 49 | # now test 50 | 51 | 52 | class Test5SidedDie(unittest.TestCase): 53 | 54 | def test_rand5(self): 55 | """test the uniformity of the solution, by computing std deviation 56 | after many trials, showing it converges to a small number""" 57 | for fn in [rand7, rand7natural]: 58 | print("\n%s" % fn.__name__) 59 | for trials in [100, 1000, 10000, 100000]: 60 | freq = [0] * 7 61 | for i in range(trials): 62 | r = fn() 63 | i = r - 1 64 | freq[i] += 1 65 | 66 | total = sum(freq) 67 | mean = total / (7.0 * trials) 68 | averages = [freq[j] / float(trials) for j in range(7)] 69 | diffs = [(averages[j] - mean) for j in range(7)] 70 | sq_diffs = [diffs[j] ** 2 for j in range(7)] 71 | variance = sum(sq_diffs) / 7.0 72 | stddev = variance**0.5 73 | 74 | print("\n%s trials" % trials) 75 | print("7 sided die rolls %s" % freq) 76 | print("averages %s" % averages) 77 | print("mean %5.4s variance %5.4s stddev %8.6s" % 78 | (mean, variance, stddev)) 79 | 80 | self.assertAlmostEqual(stddev, 0, delta=0.05) 81 | 82 | 83 | if __name__ == "__main__": 84 | # unittest.main() 85 | suite = unittest.TestLoader().loadTestsFromTestCase(Test5SidedDie) 86 | unittest.TextTestRunner(verbosity=2).run(suite) 87 | -------------------------------------------------------------------------------- /28-matching-parens.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/matching-parens 9 | # 10 | 11 | # I like parentheticals (a lot). "Sometimes (when I nest them (my 12 | # parentheticals) too much (like this (and this))) they get confusing." 13 | 14 | # Write a function that, given a sentence like the one above, along with 15 | # the position of an opening parenthesis, finds the corresponding 16 | # closing parenthesis. 17 | 18 | # Example: if the example string above is input with the number 10 19 | # (position of the first parenthesis), the output should be 79 (position 20 | # of the last parenthesis). 21 | 22 | # 23 | ###################################################################### 24 | 25 | # Now my turn 26 | 27 | # I literally do this in my head all the time, ... 28 | # (because years of editing (loving) LISP!) 29 | 30 | 31 | def find_close_paren(string, start): 32 | """find the closing paren, return None if not matched""" 33 | 34 | if string[start] != "(": 35 | raise Exception 36 | 37 | # keep a count of ( and ) 38 | # incr each ( 39 | # decr each ) 40 | # stop when count is 0 41 | 42 | i = start 43 | end = len(string) 44 | count = 0 45 | while i < end: 46 | ch = string[i] 47 | if ch == "(": 48 | count += 1 49 | elif ch == ")": 50 | count -= 1 51 | if count == 0: 52 | return i 53 | i += 1 54 | return None 55 | 56 | # And now the tests 57 | 58 | 59 | class TestMatchingParens(unittest.TestCase): 60 | 61 | def test_0givenexample(self): 62 | """test the given example""" 63 | input = "Sometimes (when I nest them (my parentheticals) too " \ 64 | "much (like this (and this))) they get confusing." 65 | 66 | self.assertEqual(79, find_close_paren(input, 10)) 67 | 68 | def test_1myfairlady(self): 69 | """the rain (in spain) (falls ...""" 70 | 71 | s = "the rain (in spain) (falls (ma(i)nly) ((i))n the plain!)" 72 | 73 | parens = [[9, 18], 74 | [20, 55], 75 | [27, 36], 76 | [30, 32]] 77 | 78 | for start, end in parens: 79 | self.assertEqual(end, find_close_paren(s, start)) 80 | 81 | def test_2lispfib(self): 82 | """test a small lisp routine""" 83 | input = "(defun fib (n) (cond ((<= n 1) 1) (t (+ (fib (- n 1)) (fib (- n 2))))))" 84 | self.assertEqual(71, find_close_paren(input, 0)) 85 | self.assertEqual(13, find_close_paren(input, 11)) 86 | self.assertEqual(70, find_close_paren(input, 16)) 87 | 88 | 89 | if __name__ == "__main__": 90 | # unittest.main() 91 | suite = unittest.TestLoader().loadTestsFromTestCase(TestMatchingParens) 92 | unittest.TextTestRunner(verbosity=2).run(suite) 93 | -------------------------------------------------------------------------------- /30-permutation-palindrome.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/permutation-palindrome 9 | # 10 | 11 | # Write an efficient function that checks whether any permutation of an 12 | # input string is a palindrome 13 | 14 | # Examples: 15 | 16 | # "civic" should return True 17 | # "ivicc" should return True 18 | # "civil" should return False 19 | # "livci" should return False 20 | 21 | # 22 | ###################################################################### 23 | 24 | # Now my turn 25 | 26 | # so at first it seems like we need an efficent way to come up with 27 | # all permutations of an input string. But I think if we consider what 28 | # we know about palindromes... 29 | 30 | # A palindrome will have either have every letter in it occurring a 31 | # multiple of 2 times, or will have one letter appearing an odd number 32 | # of times, and the rest appearing a multiple of 2 times. 33 | 34 | # so if we just maintain a count of each letter's number of 35 | # occurrences and test at the end that there are only zero or one odd 36 | # letters then we should know if we have a palindrome possibility. 37 | 38 | # we will assume case sensitivity 39 | # we not strip out any non alphabetical characters 40 | 41 | 42 | def could_be_a_palindrome(string): 43 | """returns True if any permutation of the string might be a palindrome""" 44 | 45 | # we will assume case sensitivity 46 | # we not strip out any non alphabetical characters 47 | 48 | freq = {} 49 | 50 | for ch in string: 51 | if ch in freq: 52 | del freq[ch] 53 | else: 54 | freq[ch] = True 55 | 56 | return len(freq) <= 1 57 | 58 | # And now the tests 59 | 60 | 61 | class TestPermutationPalindrome(unittest.TestCase): 62 | 63 | def test_0givenexample(self): 64 | """test the given example""" 65 | 66 | tests = [ 67 | [True, "civic"], 68 | [True, "ivicc"], 69 | [False, "civil"], 70 | [False, "livci"]] 71 | 72 | for valid, string in tests: 73 | self.assertEqual(valid, could_be_a_palindrome(string), 74 | "%s should be %s" % (string, valid)) 75 | 76 | def test_1simpletons(self): 77 | """some more simple tests""" 78 | tests = [ 79 | [False, "canal"], 80 | [True, "a man a plan a canal panama"], 81 | [True, "amanaplanacanalpanama"], 82 | [False, "panama"], 83 | [True, "kayak"], 84 | [True, "madam"], 85 | [True, "racecar"], 86 | [True, "tattarrattat"], 87 | [False, "tattarrattatxes"]] 88 | 89 | for valid, string in tests: 90 | self.assertEqual(valid, could_be_a_palindrome(string), 91 | "%s should be %s" % (string, valid)) 92 | 93 | 94 | if __name__ == "__main__": 95 | # unittest.main() 96 | suite = unittest.TestLoader().loadTestsFromTestCase(TestPermutationPalindrome) 97 | unittest.TextTestRunner(verbosity=2).run(suite) 98 | -------------------------------------------------------------------------------- /14-inflight-entertainment.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/inflight-entertainment 9 | # 10 | # 11 | # You've built an in-flight entertainment system with on-demand movie 12 | # streaming. 13 | # 14 | # Users on longer flights like to start a second movie right when their 15 | # first one ends, but they complain that the plane usually lands before 16 | # they can see the ending. So you're building a feature for choosing two 17 | # movies whose total runtimes will equal the exact flight length. 18 | # 19 | # Write a function that takes an integer flight_length (in minutes) and 20 | # a list of integers movie_lengths (in minutes) and returns a boolean 21 | # indicating whether there are two numbers in movie_lengths whose sum 22 | # equals flight_length. 23 | # 24 | # When building your function: 25 | # 26 | # + Assume your users will watch exactly two movies 27 | # + Don't make your users watch the same movie twice 28 | # + Optimize for runtime over memory 29 | ###################################################################### 30 | 31 | # Now my turn 32 | 33 | 34 | def matching_movies(flight_length, movie_lengths): 35 | """return true if two movies sum up to a flight length duration""" 36 | 37 | lengths = {} # a 1:many map of lengths to their movies 38 | for length in movie_lengths: 39 | # put each length of a movie into a hash table as we scan over 40 | # all the movies (by length) see if the complement movie is 41 | # already in lengths. by the time we reach the end of the 42 | # movies by lengths every movie has been checked for its 43 | # complement and since we check before insertion, no one has 44 | # to watch the same movie twice. 45 | 46 | complement = flight_length - length 47 | if complement in lengths: 48 | return True 49 | 50 | lengths[length] = True 51 | 52 | return False 53 | 54 | 55 | # Now let's test 56 | 57 | 58 | class TestMatchingMovies(unittest.TestCase): 59 | 60 | def setUp(self): 61 | self.movies = range(100, 120) 62 | 63 | def test_1no_matches(self): 64 | self.assertFalse(matching_movies(256, self.movies)) 65 | 66 | def test_2one_match(self): 67 | self.movies[5] = 256 - self.movies[10] 68 | self.assertTrue(matching_movies(256, self.movies)) 69 | 70 | def test_2theother_match(self): 71 | self.movies[10] = 256 - self.movies[5] 72 | self.assertTrue(matching_movies(256, self.movies)) 73 | # self.assertRaises(EarlyMatch, matching_movies, 256, self.movies) 74 | 75 | def test_one_match_but_same_movie(self): 76 | self.movies[5] = 128 77 | self.assertFalse(matching_movies(256, self.movies)) 78 | 79 | def test_two_matches(self): 80 | self.movies[5] = 128 81 | self.movies[15] = 128 82 | self.assertTrue(matching_movies(256, self.movies)) 83 | 84 | if __name__ == "__main__": 85 | # unittest.main() 86 | suite = unittest.TestLoader().loadTestsFromTestCase(TestMatchingMovies) 87 | unittest.TextTestRunner(verbosity=2).run(suite) 88 | -------------------------------------------------------------------------------- /list-trees.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | # A tree can be expressed in Python has nested lists. 4 | # Walk such a tree in BFS 5 | # Then walk the tree in DFS, pre-order, in-order, post-order 6 | 7 | import unittest 8 | 9 | 10 | def _printNode(node, **kwargs): 11 | print(node) 12 | 13 | 14 | def breadth_first_walk(tree, fn=None, **kwargs): 15 | """ 16 | http://algorithms.tutorialhorizon.com/breadth-first-searchtraversal-in-a-binary-tree 17 | """ 18 | if fn is None: 19 | fn = _printNode 20 | q = [tree] 21 | while len(q) > 0: 22 | node = q.pop() 23 | if type(node) is not list: 24 | node = [node, None, None] 25 | node.extend([None, None, None]) 26 | node = node[0:3] 27 | (value, left, right) = node 28 | if value: 29 | fn(value, **kwargs) 30 | if left: 31 | q.insert(0, left) 32 | if right: 33 | q.insert(0, right) 34 | 35 | 36 | def depth_first_walk(tree, depth=0, fn=None, **kwargs): 37 | if fn is None: 38 | fn = _printNode 39 | 40 | # this nonsense just helps with short hand "trees" 41 | # of forms 1, or [1] changing it into [1, None, None] 42 | # then truncating 43 | if type(tree) is not list: 44 | tree = [tree, None, None] 45 | tree.extend([None, None, None]) 46 | tree = tree[0:3] 47 | 48 | (value, left, right) = tree 49 | if value: 50 | fn(value, **kwargs) 51 | if left: 52 | depth_first_walk(left, depth + 1, fn, **kwargs) 53 | if right: 54 | depth_first_walk(right, depth + 1, fn, **kwargs) 55 | 56 | 57 | class TestListTrees(unittest.TestCase): 58 | 59 | cases = [] 60 | 61 | def setUp(self): 62 | Tree1Level1 = [1] 63 | Tree1Level2 = [1, None, None] 64 | Tree2Level1 = [1, 2, None] 65 | Tree2Level2 = [1, 2, 3] 66 | Tree2Level3 = [1, [2, None, None], None] 67 | Tree3Level1 = [1, ['2l', '3ll', '3lr'], None] 68 | Tree4Level1 = [1, ['2l', ['3ll', '4lll', '4llr'], '3lr'], 69 | ['2r', ['3rl', '4rll', '4rlr'], '3rr']] 70 | Tree9Level0 = [5, [10, 20, 25], [15, 30, 35]] 71 | TestListTrees.cases = [ 72 | Tree1Level1, 73 | Tree1Level2, 74 | Tree2Level1, 75 | Tree2Level2, 76 | Tree2Level3, 77 | Tree3Level1, 78 | Tree4Level1, 79 | Tree9Level0 80 | ] 81 | print("setup: %s cases" % len(TestListTrees.cases)) 82 | 83 | def test_0bf(self): 84 | print("test_0bf: breadth_first_walk") 85 | n = 0 86 | for case in TestListTrees.cases: 87 | n += 1 88 | print("\ncase %s: %s" % (n, case)) 89 | breadth_first_walk(case) 90 | self.assertTrue(True) 91 | 92 | def test_0df(self): 93 | print("test_0df: depth_first_walk") 94 | n = 0 95 | for case in TestListTrees.cases: 96 | n += 1 97 | print("\ncase %s: %s" % (n, case)) 98 | depth_first_walk(case) 99 | self.assertTrue(True) 100 | 101 | 102 | if __name__ == "__main__": 103 | unittest.main() 104 | -------------------------------------------------------------------------------- /15-nth-fibonacci.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/nth-fibonacci 9 | # 10 | 11 | # Write a function fib() that a takes an integer n and returns the nth fibonacci number. 12 | # Say our fibonacci series is 0-indexed and starts with 0. So: 13 | 14 | # fib(0) # => 0 15 | # fib(1) # => 1 16 | # fib(2) # => 1 17 | # fib(3) # => 2 18 | # fib(4) # => 3 19 | # ... 20 | 21 | 22 | ###################################################################### 23 | 24 | # Now my turn 25 | 26 | # the classic fibonacci is: 27 | 28 | def fibonacci(n): 29 | """return nth fibonacci""" 30 | if n <= 0: 31 | return 0 32 | if n == 1: 33 | return 1 34 | 35 | return fibonacci(n - 1) + fibonacci(n - 2) 36 | 37 | # which fibonacci is o(2^n) 38 | 39 | # but the work can be reduced via memoization, so memoize 40 | fibs = {0: 0, 1: 1} 41 | 42 | 43 | def memo_fibonacci(n): 44 | """return nth fibonacci""" 45 | 46 | if n <= 0: 47 | return 0 48 | 49 | if n in fibs: 50 | return fibs[n] 51 | 52 | fibs[n] = memo_fibonacci(n - 1) + memo_fibonacci(n - 2) 53 | return fibs[n] 54 | 55 | # With memoization, fib(n) should be o(n) in time 56 | 57 | # but as interviewcake notes, it is also o(n) in space (the stack as 58 | # well as the memo hash table 59 | 60 | # instead of using recursion, count up, greedily 61 | 62 | 63 | def iter_fibonacci(n): 64 | if n <= 0: 65 | return 0 66 | if n == 1: 67 | return 1 68 | 69 | fibn2 = 0 70 | fibn1 = 1 71 | for i in range(2, n + 1): 72 | fib = fibn1 + fibn2 73 | fibn2 = fibn1 74 | fibn1 = fib 75 | 76 | return fib 77 | 78 | # Now let's test 79 | 80 | 81 | class TestFibonacci(unittest.TestCase): 82 | 83 | def test_classic_fib(self): 84 | """test classic fibonacci""" 85 | solns = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 86 | 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765] 87 | 88 | n = 0 89 | for soln in solns: 90 | self.assertEqual(solns[n], fibonacci(n)) 91 | n += 1 92 | 93 | def test_memoized_fib(self): 94 | """test memoized fibonacci""" 95 | solns = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 96 | 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765] 97 | 98 | n = 0 99 | for soln in solns: 100 | self.assertEqual(solns[n], memo_fibonacci(n)) 101 | n += 1 102 | 103 | def test_iterative_fib(self): 104 | """test iterative fibonacci""" 105 | solns = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 106 | 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765] 107 | 108 | n = 0 109 | for soln in solns: 110 | self.assertEqual(solns[n], iter_fibonacci(n)) 111 | n += 1 112 | 113 | if __name__ == "__main__": 114 | # unittest.main() 115 | suite = unittest.TestLoader().loadTestsFromTestCase(TestFibonacci) 116 | unittest.TextTestRunner(verbosity=2).run(suite) 117 | -------------------------------------------------------------------------------- /27-reverse-words.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/reverse-words 9 | # 10 | 11 | # You're working on a secret team solving coded transmissions. 12 | 13 | # Your team is scrambling to decipher a recent message, worried it's a 14 | # plot to break into a major European National Cake Vault. The message 15 | # has been mostly deciphered, but all the words are backwards! Your 16 | # colleagues have handed off the last step to you. 17 | 18 | # Write a function reverse_words() that takes a string message and 19 | # reverses the order of the words in-place 20 | 21 | # For example: 22 | 23 | # message = 'find you will pain only go you recordings security the into if' 24 | # reverse_words(message) 25 | # returns: 'if into the security recordings you go only pain will you find' 26 | 27 | # When writing your function, assume the message contains only letters 28 | # and spaces, and all words are separated by one space. 29 | 30 | # 31 | ###################################################################### 32 | 33 | # Now my turn 34 | 35 | 36 | def reverse_wordssimple(message): 37 | """reverse the message but not 'in place'""" 38 | 39 | # split the message by spaces 40 | msg = message.split(" ") 41 | 42 | return " ".join(msg[::-1]) # new trick I learned to reverse a list 43 | 44 | 45 | # sigh this is a class gotcha question 46 | # if you see the trick you get it, if you don't you never will 47 | def reverse_words(message): 48 | """reverse the message 'in place' in a mutable byte array""" 49 | 50 | b = bytearray(message) 51 | 52 | # reverse the entire bytearray in place 53 | reverse_bytes(b, 0, len(b)) 54 | 55 | # now walk through the byte array finding words and the reverse 56 | # each word in place 57 | 58 | start = 0 59 | space = b.find(" ") 60 | while space != -1: 61 | reverse_bytes(b, start, space - start) 62 | 63 | start = space + 1 64 | space = b.find(" ", start) 65 | else: 66 | # reverse the unreversed remainder 67 | reverse_bytes(b, start, len(b) - start) 68 | 69 | return str(b) 70 | 71 | 72 | def reverse_bytes(b, start, length): 73 | """reverses the bytes in array b, starting at start, for length bytes""" 74 | for i in range(length / 2): 75 | begin = start + i 76 | finish = start + (length - 1) - i 77 | (b[begin], b[finish]) = (b[finish], b[begin]) 78 | return None 79 | # Now Test 80 | 81 | 82 | class TestReverseWords(unittest.TestCase): 83 | 84 | def test_reversewords(self): 85 | """test word reversal""" 86 | 87 | for test in ["", 88 | "a", 89 | "abc def", 90 | "abc def ", 91 | "the quick brown fox", 92 | 'find you will pain only go you recordings security the into if']: 93 | 94 | soln = reverse_wordssimple(test) 95 | self.assertEqual(soln, reverse_words(test)) 96 | 97 | 98 | if __name__ == "__main__": 99 | # # unittest.main() 100 | suite = unittest.TestLoader().loadTestsFromTestCase(TestReverseWords) 101 | unittest.TextTestRunner(verbosity=2).run(suite) 102 | -------------------------------------------------------------------------------- /19-queue-two-stacks.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/queue-two-stacks 9 | # 10 | # Implement a queue with two stacks 11 | 12 | # Optimize for the time cost of m function calls on your queue. These 13 | # can be any mix of enqueue and dequeue calls. 14 | 15 | # Assume you already have a stack implementation and it gives O(1)O(1) 16 | # time push and pop. 17 | 18 | # 19 | ###################################################################### 20 | 21 | # Now my turn 22 | 23 | 24 | class Stack(): 25 | 26 | def __init__(self): 27 | self.stk = [] 28 | 29 | def pop(self): 30 | """raises IndexError if you pop when it's empty""" 31 | return self.stk.pop() 32 | 33 | def push(self, elt): 34 | self.stk.append(elt) 35 | 36 | def is_empty(self): 37 | return len(self.stk) == 0 38 | 39 | def peek(self): 40 | if not self.stk.is_empty(): 41 | return self.stk[-1] 42 | 43 | 44 | class Queue(): 45 | 46 | def __init__(self): 47 | self.q = Stack() # the primary queue 48 | self.b = Stack() # the reverse, opposite q (a joke: q vs b) 49 | self.front = None 50 | 51 | def is_empty(self): 52 | return self.q.is_empty() 53 | 54 | def peek(self): 55 | if self.q.is_empty(): 56 | return None 57 | else: 58 | return self.front 59 | 60 | def enqueue(self, elt): 61 | self.front = elt 62 | self.q.push(elt) 63 | 64 | def dequeue(self): 65 | """raises IndexError if you dequeue from an empty queue""" 66 | while not self.q.is_empty() > 0: 67 | elt = self.q.pop() 68 | self.b.push(elt) 69 | val = self.b.pop() 70 | elt = None 71 | while not self.b.is_empty() > 0: 72 | elt = self.b.pop() 73 | self.q.push(elt) 74 | self.front = elt 75 | return val 76 | 77 | 78 | # Now let's test 79 | 80 | 81 | class TestQueueTwoStacks(unittest.TestCase): 82 | 83 | def setUp(self): 84 | self.q = Queue() 85 | 86 | def test_queuedequue(self): 87 | """queue up 5 integers, check they are in there, dequeue them, check for emptiness, perform other blackbox and whitebox tests""" 88 | self.assertTrue(self.q.is_empty()) 89 | self.assertTrue(self.q.q.is_empty()) 90 | self.assertTrue(self.q.b.is_empty()) 91 | 92 | l = range(5) 93 | for i in l: 94 | self.q.enqueue(i) 95 | 96 | self.assertEqual(4, self.q.peek()) 97 | self.assertEqual(l, self.q.q.stk) 98 | 99 | s = [] 100 | l.reverse() 101 | for i in l: 102 | elt = self.q.dequeue() 103 | s.append(elt) 104 | 105 | self.assertTrue(self.q.is_empty()) 106 | self.assertTrue(self.q.q.is_empty()) 107 | self.assertTrue(self.q.b.is_empty()) 108 | 109 | l.reverse() 110 | self.assertEqual(s, l) 111 | self.assertEqual([], self.q.b.stk) 112 | self.assertEqual([], self.q.q.stk) 113 | 114 | if __name__ == "__main__": 115 | # unittest.main() 116 | suite = unittest.TestLoader().loadTestsFromTestCase(TestQueueTwoStacks) 117 | unittest.TextTestRunner(verbosity=2).run(suite) 118 | -------------------------------------------------------------------------------- /1-stocks.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/stock-price 9 | 10 | # Writing programming interview questions hasn't made me rich. Maybe 11 | # trading Apple stocks will. 12 | 13 | # Suppose we could access yesterday's stock prices as a list, where: 14 | 15 | # + The indices are the time in minutes past trade opening time, which 16 | # was 9:30am local time. 17 | # + The values are the price in dollars of Apple stock at that time. 18 | 19 | # So if the stock cost $500 at 10:30am, stock_prices_yesterday[60] = 20 | # 500. 21 | 22 | # Write an efficient function that takes stock_prices_yesterday and 23 | # returns the best profit I could have made from 1 purchase and 1 sale 24 | # of 1 Apple stock yesterday. 25 | 26 | # For example: 27 | 28 | # stock_prices_yesterday = [10, 7, 5, 8, 11, 9] 29 | 30 | # get_max_profit(stock_prices_yesterday) 31 | # returns 6 (buying for $5 and selling for $11) 32 | 33 | # No "shorting" - you must buy before you sell. You may not buy and sell 34 | # in the same time step(at least 1 minute must pass). 35 | 36 | ###################################################################### 37 | 38 | # this solution is pretty much what is on the site as I just followed 39 | # it along as a "free question" to help determine if these examples 40 | # were interesting. 41 | 42 | 43 | def get_max_profit(stock_prices_yesterday): 44 | 45 | if len(stock_prices_yesterday) < 2: 46 | raise IndexError('Getting a profit requires at least 2 prices') 47 | 48 | # min_price = stock_prices_yesterday[0] 49 | # max_profit = 0 50 | 51 | min_price = stock_prices_yesterday[0] 52 | max_profit = stock_prices_yesterday[1] - stock_prices_yesterday[0] 53 | 54 | print("") 55 | for current_price in stock_prices_yesterday: 56 | 57 | # ensure min_price is the lowest price we've seen so far 58 | min_price = min(min_price, current_price) 59 | 60 | # see what our profit would be if we bought at the 61 | # min price and sold at the current price 62 | potential_profit = current_price - min_price 63 | 64 | # update max_profit if we can do better 65 | max_profit = max(max_profit, potential_profit) 66 | 67 | print(" cur_price %s min_price %s pot_profit %s max_profit %s" % 68 | (current_price, min_price, potential_profit, max_profit)) 69 | 70 | return max_profit 71 | 72 | # Now let's test 73 | 74 | 75 | class TestStockPrice(unittest.TestCase): 76 | 77 | def test_given_obvious_edge_case(self): 78 | stock_prices_yesterday = [10, 10, 10, 10] 79 | max_profit = get_max_profit(stock_prices_yesterday) 80 | self.assertEqual(0, max_profit) 81 | 82 | def test_given_example(self): 83 | """test the example given at interviewcake""" 84 | stock_prices_yesterday = [10, 7, 5, 8, 11, 9] 85 | max_profit = get_max_profit(stock_prices_yesterday) 86 | self.assertEqual(6, max_profit) 87 | 88 | def test_given_example(self): 89 | """test a day where there are no good deals""" 90 | stock_prices_yesterday = [10, 7, 5, 4, 2, 0] 91 | max_profit = get_max_profit(stock_prices_yesterday) 92 | self.assertEqual(0, max_profit) 93 | 94 | 95 | if __name__ == "__main__": 96 | # unittest.main() 97 | suite = unittest.TestLoader().loadTestsFromTestCase(TestStockPrice) 98 | unittest.TextTestRunner(verbosity=2).run(suite) 99 | -------------------------------------------------------------------------------- /4-merging-ranges.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | from operator import itemgetter 5 | import unittest 6 | 7 | ###################################################################### 8 | # this problem is from 9 | # https://www.interviewcake.com/question/highest-product-of-3 10 | # interviewcake 4 11 | # https://www.interviewcake.com/question/python/merging-ranges 12 | 13 | # Your company built an in-house calendar tool called HiCal. You want to 14 | # add a feature to see the times in a day when everyone is available. 15 | # 16 | # To do this, you'll need to know when any team is having a meeting. In 17 | # HiCal, a meeting is stored as tuples of integers (start_time, 18 | # end_time). These integers represent the number of 30-minute blocks 19 | # past 9:00am. 20 | # 21 | # For example: 22 | # 23 | # (2, 3) # meeting from 10:00 - 10:30 am 24 | # (6, 9) # meeting from 12:00 - 1:30 pm 25 | # 26 | # Write a function merge_ranges() that takes a list of meeting time 27 | # ranges and returns a list of condensed ranges. 28 | # 29 | # For example, given: 30 | # 31 | test1 = [(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)] 32 | # 33 | # your function would return: 34 | 35 | soln1 = [(0, 1), (3, 8), (9, 12)] 36 | 37 | # Do not assume the meetings are in order. The meeting times are coming 38 | # from multiple teams. 39 | # 40 | # Write a solution that's efficient even when we can't put a nice upper 41 | # bound on the numbers representing our time ranges. Here we've 42 | # simplified our times down to the number of 30-minute slots past 9:00 43 | # am. But we want the function to work even for very large numbers, like 44 | # Unix timestamps. In any case, the spirit of the challenge is to merge 45 | # meetings where start_time and end_time don't have an upper bound. 46 | 47 | ###################################################################### 48 | 49 | # Now my turn 50 | 51 | # I seem to recall seeing this before solved by a first pass sort of 52 | # the ranges then a second pass merging ranges using a push down list 53 | # to push and pop new ranges, so let's try that... 54 | 55 | 56 | def merge_ranges(meetings): 57 | if len(meetings) <= 1: 58 | return meetings 59 | 60 | meetings = sorted(meetings, key=itemgetter(0)) 61 | pdl = [meetings[0]] 62 | meetings = meetings[1:] 63 | for (nstart, nend) in meetings: 64 | (start, end) = pdl[-1] 65 | if nstart <= end: 66 | if end <= nend: 67 | end = nend 68 | pdl[-1] = (start, end) 69 | else: 70 | pdl.append((nstart, nend)) 71 | return pdl 72 | 73 | 74 | class TestMergingRanges(unittest.TestCase): 75 | 76 | def test_givenexample(self): 77 | """test any examples from the problem""" 78 | print("") 79 | for soln, test in [ 80 | [[(0, 1), (3, 8), (9, 12)], 81 | [(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)]]]: 82 | print("merge_ranges %s" % test) 83 | print(" is %s (should be %s)" % (merge_ranges(test), soln)) 84 | self.assertEqual(soln, merge_ranges(test)) 85 | 86 | def test_others(self): 87 | """test other trials""" 88 | print("") 89 | for soln, test in [ 90 | [[(-4, 8), (9, 12)], 91 | [(3, 5), (-4, 8), (10, 12), (9, 10)]], 92 | [[(0, 3)], 93 | [(0, 3), (1, 2)]]]: 94 | print("merge_ranges %s" % test) 95 | print(" is %s (should be %s)" % (merge_ranges(test), soln)) 96 | self.assertEqual(soln, merge_ranges(test)) 97 | 98 | 99 | if __name__ == "__main__": 100 | # # unittest.main() 101 | suite = unittest.TestLoader().loadTestsFromTestCase(TestMergingRanges) 102 | unittest.TextTestRunner(verbosity=2).run(suite) 103 | -------------------------------------------------------------------------------- /20-largest-stack.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/largest-stack 9 | # 10 | 11 | # You want to be able to access the largest element in a stack. 12 | 13 | # You've already implemented this Stack class. 14 | 15 | 16 | class Stack: 17 | 18 | # initialize an empty list 19 | def __init__(self): 20 | self.items = [] 21 | 22 | # push a new item to the last index 23 | def push(self, item): 24 | self.items.append(item) 25 | 26 | # remove the last item 27 | def pop(self): 28 | # if the stack is empty, return None 29 | # (it would also be reasonable to throw an exception) 30 | if not self.items: 31 | return None 32 | return self.items.pop() 33 | 34 | # see what the last item is 35 | def peek(self): 36 | if not self.items: 37 | return None 38 | return self.items[-1] 39 | 40 | # Use your Stack class to implement a new class MaxStack with a function 41 | # get_max() that returns the largest element in the stack. get_max() 42 | # should not remove the item. 43 | 44 | # Your stacks will contain only integers. 45 | 46 | # 47 | ###################################################################### 48 | 49 | # Now my turn 50 | 51 | 52 | class MaxStack(Stack): 53 | 54 | def get_max_bad_bad_bad(self): 55 | """simple, but works by peeking into super's internal data""" 56 | # a more sophisticated design might keep track of the max 57 | # value separately and not requiring spying into the super's 58 | # abstraction. 59 | 60 | # that would be better oop, less likely to break in the 61 | # future, but likely slower to run, harder to implement and 62 | # test, 63 | 64 | # it's easy to keep track of the max separately by writing our 65 | # own push and pop that reference super, IF all values in the 66 | # stack are unique. otherwise duplicate entries of the max 67 | # value makes the actual max value harder to maintain 68 | 69 | if self.peek(): 70 | return max(self.items) 71 | 72 | 73 | # Now let's test 74 | 75 | 76 | class TestMaxStack(unittest.TestCase): 77 | 78 | def test_MaxStack0Empty(self): 79 | """test an empty stack""" 80 | ms = MaxStack() 81 | self.assertEqual(None, ms.get_max_bad_bad_bad()) 82 | 83 | def test_MaxStack1Filled(self): 84 | """test a stack with a bunch of nums""" 85 | ms = MaxStack() 86 | for i in range(4): 87 | ms.push(i) 88 | ms.push(100) 89 | for i in range(5, 10): 90 | ms.push(i) 91 | 92 | self.assertEqual(100, ms.get_max_bad_bad_bad()) 93 | 94 | def test_MaxStack2MultipleMaxes(self): 95 | """test a stack with a multiple occurrences of the same max num""" 96 | ms = MaxStack() 97 | for i in range(4): 98 | ms.push(i) 99 | ms.push(100) 100 | for i in range(5, 10): 101 | ms.push(i) 102 | ms.push(110) 103 | ms.push(100) 104 | 105 | self.assertEqual(110, ms.get_max_bad_bad_bad()) 106 | ms.pop() 107 | self.assertEqual(110, ms.get_max_bad_bad_bad()) 108 | ms.pop() 109 | self.assertEqual(100, ms.get_max_bad_bad_bad()) 110 | ms.pop() 111 | ms.pop() 112 | ms.pop() 113 | ms.pop() 114 | ms.pop() 115 | self.assertEqual(100, ms.get_max_bad_bad_bad()) 116 | ms.pop() 117 | self.assertEqual(3, ms.get_max_bad_bad_bad()) 118 | 119 | if __name__ == "__main__": 120 | # unittest.main() 121 | suite = unittest.TestLoader().loadTestsFromTestCase(TestMaxStack) 122 | unittest.TextTestRunner(verbosity=2).run(suite) 123 | -------------------------------------------------------------------------------- /25-kth-to-last-node-in-singly-linked-list.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/kth-to-last-node-in-singly-linked-list 9 | # 10 | 11 | # You have a linked list and want to find the kth to last node 12 | 13 | # Write a function kth_to_last_node() that takes an integer k and the 14 | # head_node of a singly linked list, and returns the kth to last node 15 | # in the list. 16 | 17 | # For example: 18 | 19 | 20 | class LinkedListNode: 21 | 22 | def __init__(self, value): 23 | self.value = value 24 | self.next = None 25 | 26 | # a = LinkedListNode("Angel Food") 27 | # b = LinkedListNode("Bundt") 28 | # c = LinkedListNode("Cheese") 29 | # d = LinkedListNode("Devil's Food") 30 | # e = LinkedListNode("Eccles") 31 | 32 | # a.next = b 33 | # b.next = c 34 | # c.next = d 35 | # d.next = e 36 | 37 | # kth_to_last_node(2, a) 38 | 39 | 40 | # 41 | ###################################################################### 42 | 43 | # Now my turn 44 | 45 | 46 | def kth_to_last_node(k, head): 47 | """return the kth to last node. 0th and 1th to last return the last""" 48 | 49 | # English is funny, because "second to the last" means "next to" 50 | # and there is no such thing as "first to the last" 51 | 52 | # so kth to the last has no meaning unless k > 1 53 | # since there is no meaning to it, may as well return head 54 | 55 | # in general then, to find the kth from last 56 | # let n be k - 1 57 | # then find the nth element from the last 58 | 59 | # do this with a ptr that walks n elements behind an advancing ptr 60 | 61 | i = 0 62 | kth = head 63 | while(head and head.next): 64 | head = head.next 65 | if i == k - 1: 66 | kth = kth.next 67 | else: 68 | i += 1 69 | return kth 70 | 71 | # Now Test 72 | 73 | 74 | class TestKthToLast(unittest.TestCase): 75 | 76 | def test_givenexample(self): 77 | """test the given example""" 78 | 79 | a = LinkedListNode("Angel Food") 80 | b = LinkedListNode("Bundt") 81 | c = LinkedListNode("Cheese") 82 | d = LinkedListNode("Devil's Food") 83 | e = LinkedListNode("Eccles") 84 | 85 | a.next = b 86 | b.next = c 87 | c.next = d 88 | d.next = e 89 | 90 | self.assertEqual("Devil's Food", kth_to_last_node(2, a).value) 91 | 92 | def test_KthToLast(self): 93 | """test the kth to last nodes for all ks""" 94 | 95 | # English is funny, because "second to the last" means "next to" 96 | # and there is no such thing as "first to the last" 97 | 98 | print("") 99 | for i in range(5): 100 | self.llist = LinkedListNode(0) 101 | 102 | last = self.llist 103 | for j in range(1, i + 1): 104 | last.next = LinkedListNode(j) 105 | last = last.next 106 | 107 | print("\n%s" % ll2list(self.llist)) 108 | 109 | nl = "" 110 | for k in range(2, i + 2): 111 | value = kth_to_last_node(k, self.llist).value 112 | if k == i + 1: 113 | nl = "\n" 114 | print("kth %s->%s " % (k, value), end=nl) 115 | self.assertEqual(i - (k - 1), value) 116 | 117 | 118 | def ll2list(head): 119 | """converts our linked list to a python list""" 120 | if head: 121 | list = [head.value] 122 | while head.next: 123 | head = head.next 124 | list.append(head.value) 125 | return list 126 | else: 127 | return None 128 | 129 | if __name__ == "__main__": 130 | # unittest.main() 131 | suite = unittest.TestLoader().loadTestsFromTestCase(TestKthToLast) 132 | unittest.TextTestRunner(verbosity=2).run(suite) 133 | -------------------------------------------------------------------------------- /23-linked-list-cycles.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/linked-list-cycles 9 | # 10 | 11 | # You have a singly-linked list and want to check if it contains a cycle. 12 | 13 | # A singly-linked list is built with nodes, where each node has: 14 | 15 | # node.next -- the next node in the list. 16 | 17 | # node.value -- the data held in the node. For example, if our linked 18 | # list stores people in line at the movies, node.value might be the 19 | # person's name. 20 | 21 | # For example: 22 | 23 | 24 | class LinkedListNode: 25 | 26 | def __init__(self, value): 27 | self.value = value 28 | self.next = None 29 | 30 | # A cycle occurs when a node's next points back to a previous node in 31 | # the list. The linked list is no longer linear with a beginning and 32 | # end -- instead, it cycles through a loop of nodes. 33 | 34 | # Write a function contains_cycle() that takes the first node in a 35 | # singly-linked list and returns a boolean indicating whether the list 36 | # contains a cycle. 37 | 38 | 39 | # 40 | ###################################################################### 41 | 42 | # Now my turn 43 | 44 | 45 | def contains_cycle(node): 46 | """detect a cycle in a singly linked list""" 47 | 48 | # I seem to recall some sort of algorithm which uses two ptrs, one 49 | # advances quickly, the other advances slowly. If there is no 50 | # cycle, it comes to an end in n time. If there is a cycle, 51 | # eventually the fast ptr catches up to the slow ptr and we found a 52 | # cycle, again proportional to n time. 53 | 54 | slow = node 55 | while node.next and node.next.next: 56 | # print("%s: %s->%s" % (n, node.value, node.next)) 57 | slow = slow.next 58 | node = node.next.next 59 | if node is slow: 60 | return True 61 | return False 62 | 63 | # Now Test 64 | 65 | 66 | class TestDetectCycle(unittest.TestCase): 67 | 68 | def setUp(self): 69 | self.a = LinkedListNode('A') 70 | self.b = LinkedListNode('B') 71 | self.c = LinkedListNode('C') 72 | self.d = LinkedListNode('D') 73 | self.e = LinkedListNode('E') 74 | self.f = LinkedListNode('F') 75 | self.g = LinkedListNode('G') 76 | self.h = LinkedListNode('H') 77 | self.i = LinkedListNode('I') 78 | self.j = LinkedListNode('J') 79 | 80 | self.a.next = self.b 81 | self.b.next = self.c 82 | self.c.next = self.d 83 | self.d.next = self.e 84 | self.e.next = self.f 85 | self.f.next = self.g 86 | self.g.next = self.h 87 | self.h.next = self.i 88 | self.i.next = self.j 89 | 90 | def test_detect0(self): 91 | """no cycles""" 92 | # print("") 93 | # for node in [self.a, 94 | # self.b, 95 | # self.c, 96 | # self.d, 97 | # self.e, 98 | # self.f, 99 | # self.g, 100 | # self.h, 101 | # self.i, 102 | # self.j]: 103 | # print("%s -> %s" % (node.value, node.next)) 104 | 105 | self.assertFalse(contains_cycle(self.a)) 106 | 107 | def test_detect1acycle(self): 108 | """a cycle""" 109 | self.j.next = self.a 110 | 111 | self.assertTrue(contains_cycle(self.a)) 112 | 113 | def test_detect1alatecycle(self): 114 | """a cycle""" 115 | self.j.next = self.h 116 | 117 | self.assertTrue(contains_cycle(self.a)) 118 | 119 | def test_detect1asmallcycle(self): 120 | """a cycle""" 121 | self.j.next = self.j 122 | 123 | self.assertTrue(contains_cycle(self.a)) 124 | 125 | if __name__ == "__main__": 126 | # unittest.main() 127 | suite = unittest.TestLoader().loadTestsFromTestCase(TestDetectCycle) 128 | unittest.TextTestRunner(verbosity=2).run(suite) 129 | -------------------------------------------------------------------------------- /29-bracket-validator.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/bracket-validator 9 | # 10 | 11 | # You're working with an intern that keeps coming to you with JavaScript 12 | # code that won't run because the braces, brackets, and parentheses are 13 | # off. To save you both some time, you decide to write a 14 | # braces/brackets/parentheses validator. 15 | 16 | # Let's say: 17 | 18 | # + '(', '{', '[' are called "openers." 19 | # + ')', '}', ']' are called "closers." 20 | 21 | # Write an efficient function that tells us whether or not an input 22 | # string's openers and closers are properly nested. 23 | 24 | # Examples: 25 | 26 | # + "{ [ ] ( ) }" should return True 27 | # + "{ [ ( ] ) }" should return False 28 | # + "{ [ }" should return False 29 | 30 | # 31 | ###################################################################### 32 | 33 | # Now my turn 34 | 35 | # So this is very similar to 28-matching-parens.py, but instead of just 36 | # counting the parens, this time whenever we find an opener, we'll push 37 | # it onto a stack and when we find a closer, we'll pop the stack and see 38 | # if what was popped was matches the closer we just found. 39 | 40 | 41 | def has_valid_brackets(string): 42 | """returns True if a strings brackets are properly nested""" 43 | 44 | brackets = [] 45 | bracket_map = { 46 | '(': 0, 47 | '{': 0, 48 | '[': 0, 49 | ')': '(', 50 | '}': '{', 51 | ']': '[', 52 | } 53 | for ch in string: 54 | if ch in bracket_map: 55 | brack = bracket_map[ch] 56 | if brack == 0: 57 | brackets.append(ch) 58 | else: 59 | if len(brackets) <= 0: 60 | return False 61 | top = brackets.pop() 62 | if brack != top: 63 | return False 64 | if len(brackets) == 0: 65 | return True 66 | 67 | return False 68 | 69 | # And now the tests 70 | 71 | 72 | class TestValidBrackets(unittest.TestCase): 73 | 74 | def test_0givenexample(self): 75 | """test the given example""" 76 | 77 | tests = [ 78 | [True, "{ [ ] ( ) }"], 79 | [False, "{ [ ( ] ) }"], 80 | [False, "{ [ }"]] 81 | 82 | for valid, string in tests: 83 | self.assertEqual(valid, has_valid_brackets(string), 84 | "%s should be %s" % (string, valid)) 85 | 86 | def test_1myfairlady(self): 87 | """the rain (in spain) (falls ...""" 88 | s = "the rain (in spain) (falls (ma(i)nly) ((i))n the plain!)" 89 | self.assertEqual(True, has_valid_brackets(s)) 90 | 91 | def test_2simpletons(self): 92 | """some more simple tests""" 93 | tests = [ 94 | [True, 95 | """{ 96 | "id": 1, 97 | "name": "A green door", 98 | "price": 12.50, 99 | "tags": ["home", "green"] 100 | }"""], 101 | [True, 102 | """{ 103 | "$schema": "http://json-schema.org/draft-04/schema#", 104 | "properties": { 105 | "id": { 106 | "description": "The unique identifier for a product", 107 | "type": "integer" 108 | } 109 | }, 110 | "required": ["id"] 111 | }"""], 112 | [False, 113 | """[("sensor","string", {"Sensor name"}), 114 | ("timestamp","string", } "Time"), 115 | ("value","string", "Sensor value")]""" 116 | ]] 117 | 118 | for valid, string in tests: 119 | self.assertEqual(valid, has_valid_brackets(string), 120 | "%s should be %s" % (string, valid)) 121 | 122 | 123 | if __name__ == "__main__": 124 | # unittest.main() 125 | suite = unittest.TestLoader().loadTestsFromTestCase(TestValidBrackets) 126 | unittest.TextTestRunner(verbosity=2).run(suite) 127 | -------------------------------------------------------------------------------- /7-temperature-tracker.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | ###################################################################### 4 | # this problem is from 5 | # from https://www.interviewcake.com/question/python/temperature-tracker 6 | 7 | # You decide to test if your oddly-mathematical heating company is 8 | # fulfilling its All-Time Max, Min, Mean and Mode Temperature 9 | # Guarantee. 10 | # 11 | # Write a class TempTracker with these methods: 12 | # 13 | # + insert() - records a new temperature 14 | # + get_max() - returns the highest temp we've seen so far 15 | # + get_min() - returns the lowest temp we've seen so far 16 | # + get_mean() - returns the mean of all temps we've seen so far 17 | # + get_mode() - returns a mode of all temps we've seen so far 18 | # 19 | # Optimize for space and time. Favor speeding up the getter functions 20 | # (get_max(), get_min(), get_mean(), and get_mode()) over speeding up 21 | # the insert() function. 22 | # 23 | # get_mean() should return a float, but the rest of the getter functions 24 | # can return integers. Temperatures will all be inserted as 25 | # integers. We'll record our temperatures in Fahrenheit, so we can 26 | # assume they'll all be in the range 0..110 27 | # 28 | # If there is more than one mode, return any of the modes. 29 | ###################################################################### 30 | 31 | # Now my turn 32 | 33 | 34 | import unittest 35 | 36 | 37 | class TempTracker: 38 | """Temperature Tracker""" 39 | 40 | def __init__(self): 41 | self.temps = [0] * 111 42 | self.num_temps = 0 43 | self.min = 111 44 | self.max = -1 45 | self.total = 0 46 | self.mean = None 47 | self.max_freq = 0 48 | self.mode = None 49 | 50 | def insert(self, temp): 51 | if temp < 0 or temp > 110: 52 | raise Exception 53 | self.temps[temp] += 1 54 | self.num_temps += 1 55 | if temp < self.min: 56 | self.min = temp 57 | if temp > self.max: 58 | self.max = temp 59 | self.total += temp 60 | self.mean = self.total / float(self.num_temps) 61 | if self.temps[temp] > self.max_freq: 62 | self.max_freq = self.temps[temp] 63 | self.mode = temp 64 | 65 | def get_max(self): 66 | max = self.max 67 | if max == -1: 68 | max = None 69 | return max 70 | 71 | def get_min(self): 72 | min = self.min 73 | if min == 111: 74 | min = None 75 | return min 76 | 77 | def get_mean(self): 78 | return self.mean 79 | 80 | def get_mode(self): 81 | return self.mode 82 | 83 | 84 | class TestTempTracker(unittest.TestCase): 85 | 86 | def _test_tracker(self, temps, min, max, mean, modes): 87 | tracker = TempTracker() 88 | for temp in temps: 89 | tracker.insert(temp) 90 | print("") 91 | print("Test: temps = %s" % temps) 92 | print(" min %s max %s" % (tracker.get_min(), tracker.get_max())) 93 | self.assertTrue(tracker.get_min() == min) 94 | self.assertTrue(tracker.get_max() == max) 95 | print(" mean %s mode %s" % (tracker.get_mean(), tracker.get_mode())) 96 | self.assertTrue(tracker.get_mean() == mean) 97 | self.assertTrue(tracker.get_mode() in modes) 98 | 99 | def test_null(self): 100 | self._test_tracker([], None, None, None, [None]) 101 | 102 | def test_0(self): 103 | self._test_tracker([0], 0, 0, 0, [0]) 104 | 105 | def test_01(self): 106 | self._test_tracker([0, 1], 0, 1, 0.5, [0, 1]) 107 | 108 | def test_011(self): 109 | self._test_tracker([0, 1, 1], 0, 1, 2 / 3.0, [1]) 110 | 111 | def test_0112(self): 112 | self._test_tracker([0, 1, 1, 2], 0, 2, 4 / 4.0, [1]) 113 | 114 | def test_0111225(self): 115 | self._test_tracker([0, 1, 1, 2, 2, 5], 0, 5, 11 / 6.0, [1, 2]) 116 | 117 | def test_011122555(self): 118 | self._test_tracker([0, 1, 1, 2, 2, 5, 5, 5], 0, 5, 21 / 8.0, [5]) 119 | 120 | def test_extremes(self): 121 | tracker = TempTracker() 122 | self.assertRaises(Exception, tracker.insert, -1) 123 | self.assertRaises(Exception, tracker.insert, 111) 124 | 125 | 126 | if __name__ == "__main__": 127 | # unittest.main() 128 | suite = unittest.TestLoader().loadTestsFromTestCase(TestTempTracker) 129 | unittest.TextTestRunner(verbosity=2).run(suite) 130 | -------------------------------------------------------------------------------- /q.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | interview cake 4 | 5 | 6 | Interview Cake 7 |
    8 |
  1. 1. Apple Stocks 9 |
  2. 2. Product of All Other Numbers 10 |
  3. 3. Highest Product of 3 11 |
  4. 4. Merging Meeting Times 12 |
  5. 5. Making Change 13 |
  6. 6. Rectangular Love 14 |
  7. 7. Temperature Tracker 15 |
  8. 8. Balanced Binary Tree 16 |
  9. 9. Binary Search Tree Checker 17 |
  10. 10. 2nd Largest Item in a Binary Search Tree 18 |
  11. 11. MillionGazillion 19 |
  12. 12. Find in Ordered Set 20 |
  13. 13. Find Rotation Point 21 |
  14. 14. Inflight Entertainment 22 |
  15. 15. Compute nth Fibonacci Number 23 |
  16. 16. The Cake Thief 24 |
  17. 17. JavaScript Scope 25 |
  18. 18. What's Wrong with This JavaScript? 26 |
  19. 19. Queue Two Stacks 27 |
  20. 20. Largest Stack 28 |
  21. 21. The Stolen Breakfast Drone 29 |
  22. 22. Delete Node 30 |
  23. 23. Does This Linked List Have A Cycle? 31 |
  24. 24. Reverse A Linked List 32 |
  25. 25. Kth to Last Node in a Singly-Linked List 33 |
  26. 26. Reverse String in Place 34 |
  27. 27. Reverse Words 35 |
  28. 28. Parenthesis Matching 36 |
  29. 29. Bracket Validator 37 |
  30. 30. Permutation Palindrome 38 |
  31. 31. Recursive String Permutations 39 |
  32. 32. Top Scores 40 |
  33. 33. Which Appears Twice 41 |
  34. 34. Word Cloud Data 42 |
  35. 35. In-Place Shuffle 43 |
  36. 36. Single Riffle Shuffle 44 |
  37. 37. Simulate 5-sided die 45 |
  38. 38. Simulate 7-sided die 46 |
  39. 39. Two Egg Problem 47 |
  40. 40. Find Repeat, Space Edition 48 |
  41. 41. Find Repeat, Space Edition BEAST MODE 49 |
  42. 42. Find Duplicate Files 50 |
  43. 43. Merge Sorted Arrays 51 |
  44. 44. URL Shortener 52 |
53 | 54 | 55 | -------------------------------------------------------------------------------- /21-find-unique-int-among-duplicates.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | import operator 6 | 7 | ###################################################################### 8 | # this problem is from 9 | # https://www.interviewcake.com/question/python/find-unique-int-among-duplicates 10 | # 11 | 12 | # Your company delivers breakfast via autonomous quadcopter drones. And 13 | # something mysterious has happened. 14 | 15 | # Each breakfast delivery is assigned a unique ID, a positive 16 | # integer. When one of the company's 100 drones takes off with a 17 | # delivery, the delivery's ID is added to a list, 18 | # delivery_id_confirmations. When the drone comes back and lands, the ID 19 | # is again added to the same list. 20 | 21 | # After breakfast this morning there were only 99 drones on the 22 | # tarmac. One of the drones never made it back from a delivery. We 23 | # suspect a secret agent from Amazon placed an order and stole one of 24 | # our patented drones. To track them down, we need to find their 25 | # delivery ID. 26 | 27 | # Given the list of IDs, which contains many duplicate integers and one 28 | # unique integer, find the unique integer. 29 | 30 | # The IDs are not guaranteed to be sorted or sequential. Orders aren't 31 | # always fulfilled in the order they were received, and some deliveries 32 | # get cancelled before takeoff. 33 | 34 | # 35 | ###################################################################### 36 | 37 | # Now my turn 38 | 39 | # My first thoughts are to use two hash tables. One collects unique 40 | # ids, the other collects dup ids. Since hash insertion, deletion, 41 | # lookup is a constant, this should be O(n) where n is length of id 42 | # list. 43 | 44 | 45 | def find_unique_int(ids): 46 | """find one unique id in a list of random, possibly duplicated, integers""" 47 | uids = {} 48 | dups = {} 49 | for id in ids: 50 | if id not in dups: 51 | if id in uids: 52 | del uids[id] 53 | dups[id] = True 54 | else: 55 | uids[id] = True 56 | for key in uids.keys(): 57 | return key 58 | return None 59 | 60 | # interviewcake first uses a solution that uses one hash table (but 61 | # requires an n and n-1 scans), then suggests that given the problem 62 | # statement: these are ints, and the list containts duplicates, where 63 | # *duplicate* is a strict double entry, not a multiple entry, then we 64 | # don't need a hash table, but can use a single integer and an 65 | # XOR. Wow. Okay, that is true, but um, what a crazy arbitrary sort of 66 | # problem 67 | 68 | 69 | def find_unique_intxor(ids): 70 | """find one unique id in a list of random, possibly duplicated, integers""" 71 | unique = reduce(operator.xor, ids) 72 | return unique 73 | 74 | # So this is a clever implementation, but comparing it to the prior 75 | # find duplicates, it is much more brittle and works ONLY for this one 76 | # specific problem where there is ONE unique and the rest are 77 | # duplicate entries where duplicate means precisely TWO and never TWO 78 | # OR MORE. 79 | 80 | # Now Test 81 | 82 | 83 | class TestFindUniqueInt(unittest.TestCase): 84 | 85 | def test_FindUniqueInt0Empty(self): 86 | self.assertEqual(None, find_unique_int([])) 87 | 88 | def test_FindUniqueInt0One(self): 89 | self.assertEqual(1, find_unique_int([1])) 90 | 91 | def test_FindUniqueInt0Two(self): 92 | self.assertEqual(None, find_unique_int([1, 1])) 93 | 94 | def test_FindUniqueInt1Many(self): 95 | self.assertEqual(1, find_unique_int( 96 | [1, 2, 3, 4, 4, 3, 2, 5, 6, 6, 5, 100, 101, 101, 100])) 97 | 98 | def test_FindUniqueIntXOR0EmptyRaisesError(self): 99 | self.assertRaises(TypeError, find_unique_intxor, []) 100 | 101 | def test_FindUniqueIntXOR0One(self): 102 | self.assertEqual(1, find_unique_intxor([1])) 103 | 104 | def test_FindUniqueIntXOR0Two(self): 105 | self.assertEqual(0, find_unique_intxor([1, 1])) 106 | 107 | def test_FindUniqueIntXOR1JustRight(self): 108 | self.assertEqual(1, find_unique_intxor( 109 | [1, 2, 3, 4, 4, 3, 2, 5, 6, 6, 5, 100, 101, 101, 100])) 110 | 111 | def test_FindUniqueIntXOR2Many(self): 112 | """too many uniques means the wrong answer is returned""" 113 | self.assertEqual(103, find_unique_intxor( 114 | [1, 2, 3, 4, 4, 3, 2, 5, 6, 6, 5, 100, 101, 101, 100, 102])) 115 | 116 | 117 | if __name__ == "__main__": 118 | # unittest.main() 119 | suite = unittest.TestLoader().loadTestsFromTestCase(TestFindUniqueInt) 120 | unittest.TextTestRunner(verbosity=2).run(suite) 121 | -------------------------------------------------------------------------------- /34-word-cloud.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import string 5 | import re 6 | import unittest 7 | 8 | ###################################################################### 9 | # this problem is from 10 | # https://www.interviewcake.com/question/python/word-cloud 11 | # 12 | 13 | # You want to build a word cloud, an infographic where the size of a 14 | # word corresponds to how often it appears in the body of text. 15 | 16 | # To do this, you'll need data. Write code that takes a long string and 17 | # builds its word cloud data in a dictionary, where the keys are 18 | # words and the values are the number of times the words occurred. 19 | 20 | # Think about capitalized words. For example, look at these sentences: 21 | 22 | # 'After beating the eggs, Dana read the next step:' 23 | # 'Add milk and eggs, then add flour and sugar.' 24 | 25 | # What do we want to do with "After", "Dana", and "add"? In this 26 | # example, your final dictionary should include one "Add" or "add" with 27 | # a value of 22. Make reasonable (not necessarily perfect) decisions 28 | # about cases like "After" and "Dana". 29 | 30 | # Assume the input will only contain words and standard punctuation. 31 | 32 | # 33 | ###################################################################### 34 | 35 | # Now my turn 36 | 37 | # ignore all punctuation except sentence enders . ! and ? 38 | 39 | # lowercase any word that starts a sentence IFF it is also in the 40 | # corpus of words as a lowercase word. 41 | 42 | # How? 43 | 44 | # split corpus into sentences (strings ending with a . ? or !) 45 | # strip sentences into words 46 | # push all words into a case sensitive, word frequency counting, dict 47 | # scan the dict 48 | # if a cap word is in the dict as cap and downcase then downcase 49 | # return dict 50 | 51 | # we do make two passes through the input stream, but if this were a 52 | # real problem, I'd use a lexing and parsing library to implement a 53 | # real world's problem's requirements. 54 | 55 | 56 | def word_cloud(input): 57 | """map string of words into a dict of word frequencies""" 58 | 59 | sentence_enders = r"\.|!|\?" 60 | sentences = re.split(sentence_enders, input) 61 | 62 | freq = {} 63 | for sentence in sentences: 64 | words = re.split(r"[^a-zA-Z0-9-]+", sentence) 65 | for word in words: 66 | count = freq.get(word, 0) 67 | freq[word] = count + 1 68 | 69 | def is_cap(word): 70 | ch = word[0:1] 71 | return ch in string.uppercase 72 | 73 | for word, count in freq.items(): 74 | if is_cap(word) and word.lower() in freq: 75 | count = freq[word] 76 | freq[word.lower()] += count 77 | del freq[word] 78 | 79 | return freq 80 | 81 | 82 | class TestWordCloud(unittest.TestCase): 83 | 84 | def test_examples(self): 85 | """test the given example""" 86 | test = 'After beating the eggs, Dana read the next step:' + \ 87 | 'Add milk and eggs, then add flour and sugar-free diet coke.' 88 | soln = { 89 | 'After': 1, 90 | 'Dana': 1, 91 | 'add': 2, 92 | 'and': 2, 93 | 'beating': 1, 94 | 'coke': 1, 95 | 'diet': 1, 96 | 'eggs': 2, 97 | 'flour': 1, 98 | 'milk': 1, 99 | 'next': 1, 100 | 'read': 1, 101 | 'step': 1, 102 | 'sugar-free': 1, 103 | 'the': 2, 104 | 'then': 1, 105 | } 106 | 107 | cloud = word_cloud(test) 108 | self.assertDictEqual(soln, cloud) 109 | 110 | def test_more_examples(self): 111 | "test some additional examples" 112 | 113 | tests = [ 114 | ["We came, we saw, we conquered...then we ate Bill's " 115 | "(Mille-Feuille) cake." 116 | "The bill came to five dollars.", 117 | { 118 | 'Mille-Feuille': 1, 119 | 'The': 1, 120 | 'ate': 1, 121 | 'bill': 2, 122 | 'cake': 1, 123 | 'came': 2, 124 | 'conquered': 1, 125 | 'dollars': 1, 126 | 'five': 1, 127 | 's': 1, 128 | 'saw': 1, 129 | 'then': 1, 130 | 'to': 1, 131 | 'we': 4 132 | } 133 | ] 134 | ] 135 | 136 | for test, soln in tests: 137 | cloud = word_cloud(test) 138 | self.assertDictEqual(soln, cloud) 139 | 140 | 141 | if __name__ == "__main__": 142 | # unittest.main() 143 | suite = unittest.TestLoader().loadTestsFromTestCase(TestWordCloud) 144 | unittest.TextTestRunner(verbosity=2).run(suite) 145 | -------------------------------------------------------------------------------- /41-find-duplicate-optimize-for-space-beast-mode.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import random 5 | import unittest 6 | 7 | ###################################################################### 8 | # this problem is from 9 | # https://www.interviewcake.com/question/python/find-duplicate-optimize-for-space-beast-mode 10 | # 11 | 12 | # Find a duplicate, Space Edition BEAST MODE 13 | 14 | # We have a list of integers, where: 15 | 16 | # + The integers are in the range 1..n 17 | # + The list has a length of n+1 18 | 19 | # It follows that our list has at least one integer which appears at 20 | # least twice. But it may have several duplicates, and each duplicate 21 | # may appear more than twice. 22 | 23 | # Write a function which finds an integer that appears more than once in 24 | # our list. (If there are multiple duplicates, you only need to find one 25 | # of them.) 26 | 27 | # We're going to run this function on our new, super-hip Macbook Pro 28 | # With Retina Display. Thing is, the damn thing came with the RAM 29 | # soldered right to the motherboard, so we can't upgrade our RAM. So we 30 | # need to optimize for space! 31 | 32 | # Gotchas 33 | 34 | # We can do this in O(1) space. 35 | # We can do this in less than O(n^2) time, while keeping O(1) space. 36 | # We can do this in O(n log n) time and O(1) space. 37 | # We can do this without destroying the input. 38 | 39 | # 40 | ###################################################################### 41 | 42 | # Now my turn well almost... 43 | 44 | ###################################################################### 45 | # Interviewcake goes on to says 46 | # https://www.interviewcake.com/question/python/find-duplicate-optimize-for-space-beast-mode 47 | 48 | # We can find a duplicate integer in O(n) time while keeping our space 49 | # cost at O(1) 50 | 51 | # This is a tricky one to derive (unless you have a strong background in 52 | # graph theory), so we'll get you started: 53 | 54 | # Imagine each item in the list as a node in a linked list. In any 55 | # linked list , each node has a value and a "next" pointer. In this 56 | # case: 57 | 58 | # The value is the integer from the list. 59 | 60 | # The "next" pointer points to the value-eth node in the list (numbered 61 | # starting from 1). For example, if our value was 3, the "next" node 62 | # would be the third node. 63 | 64 | # list [2, 3, 1, 3] 65 | # position 1 2 3 4 66 | # linked list 2->3->1<-3 67 | ###################################################################### 68 | 69 | # Now my turn... 70 | 71 | # so the hint from interviewcake, the bonus "beast" mode make me think 72 | # of how one would find a cycle in a linked list, and we did that 73 | # earlier in interviewcake problem 23, linked-list-cycles. 74 | 75 | # and we did that by starting at a tail, and using "floyd's algorithm". 76 | # and we could do that here as well, if we only had a tail... 77 | 78 | # but as explained here: 79 | # http://aperiodic.net/phil/archives/Geekery/find-duplicate-elements.html 80 | 81 | # we do know a tail... 82 | 83 | # since the numbers are: 84 | # + The integers are in the range 1..n 85 | # + The list has a length of n+1 86 | 87 | # then we know nothing in the list from 1..n can point to list[n+1], 88 | # so we can start floyd walking at n+1, follow the graph till we hit 89 | # the cycle, and determine the start of the cycle (according to floyd) 90 | # and that will be a duplicate. 91 | 92 | 93 | def find_duplicate(list): 94 | """find a duplicate of 1..n in a list n+1 elements long""" 95 | 96 | n = len(list) 97 | i = n 98 | j = n 99 | while True: 100 | print("looking for cycle: i %s j %s" % (i, j)) 101 | i = list[i - 1] # tortoise 102 | j = list[j - 1] # hare 103 | j = list[j - 1] # hare 104 | if i == j: 105 | print("cycle found at %s" % i) 106 | break 107 | 108 | # we found a cycle 109 | # now restart j 110 | # and loop until j meets i again 111 | # and that's the start of the cycle (or the dup) 112 | 113 | j = n 114 | while True: 115 | print("looking for dup: i %s j %s" % (i, j)) 116 | i = list[i - 1] 117 | j = list[j - 1] 118 | if i == j: 119 | print("dup found at %s" % i) 120 | break 121 | 122 | print("dup is %s" % i) 123 | return i 124 | 125 | 126 | # Now Test 127 | 128 | 129 | class TestFindDuplicate(unittest.TestCase): 130 | 131 | def test_given(self): 132 | print("") 133 | dup = find_duplicate([2, 3, 1, 3]) 134 | self.assertEqual(3, dup) 135 | 136 | def test_more(self): 137 | print("") 138 | for i in range(20): 139 | n = random.randrange(4, 7) 140 | l = range(1, n + 1) 141 | dup = random.randrange(1, n + 1) 142 | l.append(dup) 143 | random.shuffle(l) 144 | print("l is %s" % l) 145 | found_dup = find_duplicate(l) 146 | self.assertEqual(dup, found_dup) 147 | 148 | if __name__ == "__main__": 149 | # unittest.main() 150 | suite = unittest.TestLoader().loadTestsFromTestCase(TestFindDuplicate) 151 | unittest.TextTestRunner(verbosity=2).run(suite) 152 | -------------------------------------------------------------------------------- /3-highest-product-of-3.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/highest-product-of-3 9 | 10 | # Given a list_of_ints, find the highest_product you can get from three of 11 | # the integers. 12 | 13 | # The input list_of_ints will always have at least three integers. 14 | 15 | 16 | def highest_product0(list_of_ints): 17 | """two neg nums may be bigger than three pos nums. do both products. return max""" 18 | 19 | # I think this is the correct approach for this problem, it reduces 20 | # logic complexity by trying both of the possible answers, determining 21 | # the actual max in its last comparison 22 | 23 | # it is presumably o(n log n) with one sort that could go to o(n^2) 24 | # there is an o(n) sol'n but logic is more complex, and requires many many 25 | # more mults 26 | 27 | list_of_ints.sort() 28 | 29 | min0 = list_of_ints[0] 30 | min1 = list_of_ints[1] 31 | 32 | max0 = list_of_ints[-1] 33 | max1 = list_of_ints[-2] 34 | max2 = list_of_ints[-3] 35 | 36 | poss1 = min0 * min1 * max0 37 | poss2 = max0 * max1 * max2 38 | 39 | return poss1 if poss1 > poss2 else poss2 # such an ugly ternary conditional 40 | 41 | 42 | def highest_productordern(list_of_ints): 43 | """a greedy order n approach taken off web (with a critical bug fix!)""" 44 | # from 45 | # https://knaidu.gitbooks.io/problem-solving/content/primitive_types/highest_product_of_3.html 46 | 47 | # Maintain the following values as we traverse the array 48 | # lowest_number 49 | # highest_number 50 | # lowest_product_of_2 51 | # highest_product_of_2 52 | # highest_product_of_3 53 | 54 | low = min(list_of_ints[0], list_of_ints[1]) 55 | high = max(list_of_ints[0], list_of_ints[1]) 56 | 57 | low_prod2 = high_prod2 = list_of_ints[0] * list_of_ints[1] 58 | high_prod3 = high_prod2 * list_of_ints[2] 59 | 60 | i = 2 61 | while i < len(list_of_ints): 62 | curr = list_of_ints[i] 63 | high_prod3 = max( 64 | high_prod2 * curr, 65 | low_prod2 * curr, 66 | high_prod3) 67 | 68 | low_prod2 = min(low * curr, low_prod2) 69 | high_prod2 = max(high * curr, high_prod2) 70 | 71 | high = max(high, curr) 72 | low = min(low, curr) 73 | i += 1 # heh, knaidu web book never incrs i, and so oo loop 74 | 75 | return high_prod3 76 | 77 | 78 | # first approach, correct and better than brute force, but way too 79 | # many comparisons (ie complex logic) 80 | def highest_product1(list_of_ints): 81 | """return the highest product, use logic to figure out which three operands to use""" 82 | 83 | # this has two sorts, both are of sublists 84 | # this works, is correct, but logic is a bit complex 85 | # probably simpler mainteance with highest_product0 86 | 87 | neg = [] 88 | pos = [] 89 | for int in list_of_ints: 90 | if int < 0: 91 | neg.append(int) 92 | else: 93 | pos.append(int) 94 | neg.sort() 95 | pos.sort() 96 | 97 | if len(pos) == 0: 98 | # no pos ints, so highest number are three "small" negs 99 | return(neg[-3] * neg[-2] * neg[-1]) 100 | elif len(pos) == 1: 101 | # 1 pos int, so return that * prod of two "large" negs 102 | return(neg[0] * neg[1] * pos[-1]) 103 | elif len(neg) >= 2: 104 | maxneg = neg[0] * neg[1] 105 | if maxneg > pos[-3] * pos[-2]: 106 | return maxneg * pos[-1] 107 | elif len(pos) == 2: 108 | return neg[-1] * pos[-2] * pos[-1] 109 | else: 110 | return pos[-3] * pos[-2] * pos[-1] 111 | 112 | 113 | class TestHighestProduct(unittest.TestCase): 114 | 115 | def test_givenexample(self): 116 | """test the example from the problem""" 117 | for soln, test in [ 118 | [300, [-10, -10, 1, 3, 2]], 119 | ]: 120 | self.assertEqual(soln, highest_product0(test)) 121 | 122 | def test_givenexampleordern(self): 123 | """test the example from the problem using order n soln""" 124 | for soln, test in [ 125 | [300, [-10, -10, 1, 3, 2]], 126 | ]: 127 | self.assertEqual(soln, highest_productordern(test)) 128 | 129 | def test_highestproduct(self): 130 | """test all 3 highest_product strategies, make sure they are equal to each other""" 131 | print("") 132 | for t in [ 133 | [1, 2, 3], 134 | [-1, 0, 1], 135 | [-3, -4, 2], 136 | [0, 1, 2, 3, 4, 5], 137 | [-1, 0, 1, 2, 3, 4], 138 | [-3, -4, 0, 1, 2, 3, 4], 139 | [1, 2, 3, -3, -4, 0, 4], 140 | [-1, -2, -3, 0, 1, 2, 30, 1, 2, -4, -5, 3], 141 | [-10, -10, 1, 3, 2]]: 142 | print("highest product of %s is %s" % (t, highest_product0(t))) 143 | self.assertEqual(highest_productordern(t), highest_product0(t)) 144 | self.assertEqual(highest_product0(t), highest_product1(t)) 145 | 146 | if __name__ == "__main__": 147 | # # unittest.main() 148 | suite = unittest.TestLoader().loadTestsFromTestCase(TestHighestProduct) 149 | unittest.TextTestRunner(verbosity=2).run(suite) 150 | -------------------------------------------------------------------------------- /35-shuffle.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import random 5 | import unittest 6 | 7 | ###################################################################### 8 | # this problem is from 9 | # https://www.interviewcake.com/question/python/shuffle 10 | # 11 | 12 | # Write a function for doing an in-place shuffle of a list. 13 | 14 | # The shuffle must be "uniform," meaning each item in the original list 15 | # must have the same probability of ending up in each spot in the final 16 | # list. 17 | 18 | # Assume that you have a function get_random(floor, ceiling) for getting 19 | # a random integer that is >= floor and <= ceiling. 20 | # 21 | ###################################################################### 22 | 23 | # Now my turn 24 | 25 | # I'll implement the canonical shuffle in place algorithm then test it 26 | # to make sure it fits the statistical definition of a uniform random 27 | # shuffle 28 | 29 | random.seed() 30 | 31 | 32 | def get_random(floor, ceiling): 33 | """return a random number in the closed interval (floor, ceiling)""" 34 | return random.randint(floor, ceiling) # randint does this already 35 | 36 | 37 | # this is interviewcake's non-uniform naive shuffle 38 | # stripped of comments, it differs from the in_place_uniform_shuffle 39 | # by 1 character. (A critical 1 character difference!) 40 | 41 | # using statistics in the unit tests, it can be seen that the naive 42 | # shuffle never converges on a uniform shuffling, empirically, the 43 | # variance can at times be seen to increase even with increasing 44 | # repetitions. 45 | 46 | def naive_shuffle(the_list): 47 | # for each index in the list 48 | 49 | n = len(the_list) 50 | 51 | for i in xrange(0, n - 1): 52 | 53 | # grab a random other index 54 | j = get_random(0, n - 1) 55 | 56 | # and swap the values 57 | the_list[i], the_list[j] = the_list[j], the_list[i] 58 | 59 | 60 | # the canonical shuffle in place algorithm 61 | 62 | 63 | def in_place_uniform_shuffle(the_list): 64 | """in place shuffle of a the_list""" 65 | 66 | n = len(the_list) 67 | 68 | # sweep each position in turn marching up the the_list 69 | # (do not bother with the nth element, which would swap with itself) 70 | for i in xrange(0, n - 1): 71 | # at each position of the_list put an element chosen from 72 | # remainder of the the_list 73 | j = get_random(i, n - 1) 74 | # print("%s <--> %s" % (i, j)) 75 | the_list[i], the_list[j] = the_list[j], the_list[i] 76 | 77 | 78 | class TestShuffle(unittest.TestCase): 79 | 80 | def test_demo(self): 81 | """demonstrate random shuffles""" 82 | for i in xrange(4, 7): 83 | for shuffle in [in_place_uniform_shuffle]: 84 | print("") 85 | print(shuffle.__name__) 86 | the_list = [element for element in xrange(i)] 87 | print("the_list was %s" % the_list) 88 | shuffle(the_list) 89 | print(" now %s" % the_list) 90 | print("") 91 | self.assertTrue(True) 92 | 93 | def test_uniformity(self): 94 | """test to make sure this is a uniform shuffle""" 95 | 96 | # if this is a uniform shuffle, then after some bignum 97 | # repetitions, we should see every element appear in every 98 | # position of the the_list, uniformly. 99 | 100 | # the average of 0..9 is 4.5 101 | # if the elements are uniformly shuffled, over bignum repetitions 102 | # at every position in the the_list, the average of all elements 103 | # shuffled into that position should be 4.5 104 | 105 | for shuffle in [naive_shuffle, in_place_uniform_shuffle]: 106 | 107 | print("\n\n%s\n" % shuffle.__name__) 108 | 109 | n = 10 110 | for trials in [1, 5, 10, 100, 1000, 10000]: 111 | count = [0 for i in range(n)] 112 | for trial in range(trials): 113 | the_list = [i for i in range(n)] 114 | shuffle(the_list) 115 | for i in range(n): 116 | count[i] += the_list[i] 117 | 118 | print("%s shuffles" % trials) 119 | print(" counts % s" % count) 120 | average = [count[i] / float(trials) for i in range(n)] 121 | print(" averages %s" % average) 122 | 123 | total = sum(average) 124 | mean = total / float(n) 125 | sq_diffs = [(average[i] - mean) ** 2 for i in range(n)] 126 | variance = sum(sq_diffs) / n 127 | stddev = variance**0.5 128 | 129 | print(" mean %s variance %.6s standard deviation %.6s" 130 | % (mean, variance, stddev)) 131 | 132 | # leaving the above loop, the last trial was of 10000 shuffles 133 | # using the in place uniform shuffle 134 | 135 | # the test of uniformity has shuffled the the_list 10000 times 136 | # and computes the averages of all the elements shuffled into 137 | # each position, and ensures standard deviation of the 138 | # averages is close to zero 139 | 140 | self.assertAlmostEqual(stddev, 0, delta=0.1) 141 | 142 | 143 | if __name__ == "__main__": 144 | # unittest.main() 145 | suite = unittest.TestLoader().loadTestsFromTestCase(TestShuffle) 146 | unittest.TextTestRunner(verbosity=2).run(suite) 147 | -------------------------------------------------------------------------------- /2-product-of-other-numbers.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/product-of-other-numbers 9 | 10 | # You have a list of integers, and for each index you want to find the 11 | # product of every integer except the integer at that index. 12 | 13 | # Write a function get_products_of_all_ints_except_at_index() that takes 14 | # a list of integers and returns a list of the products. 15 | 16 | # For example, given: 17 | # [1, 7, 3, 4] 18 | # your function would return: 19 | # [84, 12, 28, 21] 20 | # by calculating: 21 | # [7*3*4, 1*3*4, 1*7*4, 1*7*3] 22 | 23 | # Do not use division in your solution. 24 | 25 | # 26 | ###################################################################### 27 | 28 | # Now my turn 29 | 30 | 31 | # obvious solution THAT USES DIVISION 32 | def get_products_of_all_ints_except_at_indexDIVISION(l): 33 | """uses n mults and n divides""" 34 | if len(l) == 0: 35 | return [] 36 | 37 | if len(l) == 1: 38 | return [1] 39 | 40 | prod = 1 41 | for n in l: 42 | prod *= n 43 | 44 | prods = [] 45 | for i in range(len(l)): 46 | if l[i] != 0: 47 | prods.append(int(prod / l[i])) 48 | else: 49 | prods.append(int(prod)) 50 | 51 | return prods 52 | 53 | # okay, so no division... 54 | 55 | 56 | def get_products_of_all_ints_except_at_indexn2(l): 57 | """uses n squared mults, no divides, ie brute force""" 58 | if len(l) == 0: 59 | return [] 60 | 61 | if len(l) == 1: 62 | return [1] 63 | 64 | prods = [1] * len(l) 65 | n = len(prods) 66 | 67 | for i in range(n): 68 | for j in range(i): 69 | prods[j] = prods[j] * l[i] 70 | for j in range(i + 1, n): 71 | prods[j] = prods[j] * l[i] 72 | 73 | return prods 74 | 75 | # interview cake suggests "greedy" and shows a pattern of accumulating 76 | # products from the left and the right and asks us to generalize... 77 | 78 | # so the pattern and generalization seems to suggests scanning left 79 | # across the list of factors accumulating the product as we go and 80 | # progressively assigning product i as product list element i + 1 81 | 82 | # then scanning right across the list of factors accumulating a new 83 | # product from the right then multiplying that new product by the 84 | # existing prior product of products list element. 85 | 86 | # in this way, each products list element i becomes the prod(all 87 | # factors(j) except for i) 88 | 89 | 90 | def get_products_of_all_ints_except_at_index(l): 91 | """greedy: n space approx 4n mults""" 92 | if len(l) < 1: 93 | return [] 94 | 95 | prods = [1] * len(l) 96 | n = len(prods) 97 | 98 | left = 1 99 | for i in range(0, n): 100 | prods[i] = prods[i] * left 101 | left = left * l[i] 102 | 103 | # fun with zero based left marching range indices! 104 | right = 1 105 | for i in range(n - 1, -1, -1): 106 | prods[i] = prods[i] * right 107 | right = right * l[i] 108 | 109 | return prods 110 | 111 | # all in all pretty clever 112 | # I would bomb this terribly if asked for it on the fly in an 113 | # interview. but yeah, if you ask me, this question is inappropriate 114 | # for an interview, unless the goal is to make people sweat OR to 115 | # filter for people who have seen this problem before 116 | 117 | 118 | class TestGetProductsExcept(unittest.TestCase): 119 | 120 | def test_givenexample(self): 121 | """test the given example""" 122 | example = [1, 7, 3, 4] 123 | soln = [84, 12, 28, 21] 124 | self.assertEqual( 125 | soln, 126 | get_products_of_all_ints_except_at_indexDIVISION(example)) 127 | 128 | self.assertEqual( 129 | soln, 130 | get_products_of_all_ints_except_at_indexn2(example)) 131 | 132 | self.assertEqual( 133 | soln, 134 | get_products_of_all_ints_except_at_index(example)) 135 | 136 | def test_0sandEmpties(self): 137 | """test edge cases""" 138 | self.assertEqual( 139 | [], get_products_of_all_ints_except_at_indexDIVISION([])) 140 | self.assertEqual( 141 | [1], get_products_of_all_ints_except_at_indexDIVISION([5])) 142 | self.assertEqual( 143 | [1], get_products_of_all_ints_except_at_indexDIVISION([0])) 144 | self.assertEqual( 145 | [0, 0], get_products_of_all_ints_except_at_indexDIVISION([0, 0])) 146 | self.assertEqual( 147 | [0, 0, 0], 148 | get_products_of_all_ints_except_at_indexDIVISION([0, 0, 0])) 149 | 150 | self.assertEqual( 151 | [], get_products_of_all_ints_except_at_index([])) 152 | self.assertEqual( 153 | [1], get_products_of_all_ints_except_at_index([5])) 154 | self.assertEqual( 155 | [1], get_products_of_all_ints_except_at_index([0])) 156 | self.assertEqual( 157 | [0, 0], get_products_of_all_ints_except_at_index([0, 0])) 158 | self.assertEqual( 159 | [0, 0, 0], get_products_of_all_ints_except_at_index([0, 0, 0])) 160 | 161 | 162 | if __name__ == "__main__": 163 | # unittest.main() 164 | suite = unittest.TestLoader().loadTestsFromTestCase(TestGetProductsExcept) 165 | unittest.TextTestRunner(verbosity=2).run(suite) 166 | -------------------------------------------------------------------------------- /13-find-rotation-point.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/find-rotation-point 9 | # 10 | 11 | # I want to learn some big words so people think I'm smart. 12 | 13 | # I opened up a dictionary to a page in the middle and started flipping 14 | # through, looking for words I didn't know. I put each word I didn't know 15 | # at increasing indices in a huge list I created in memory. When I reached 16 | # the end of the dictionary, I started from the beginning and did the same 17 | # thing until I reached the page I started at. 18 | 19 | # Now I have a list of words that are mostly alphabetical, except they 20 | # start somewhere in the middle of the alphabet, reach the end, and then 21 | # start from the beginning of the alphabet. In other words, this is an 22 | # alphabetically ordered list that has been "rotated." For example: 23 | 24 | words = [ 25 | 'pto', 26 | 'ret', 27 | 'sup', 28 | 'und', 29 | 'xen', 30 | 'asy', # <-- rotates here! 31 | 'bab', 32 | 'ban', 33 | 'eng', 34 | 'kar', 35 | 'oth', 36 | ] 37 | 38 | # Write a function for finding the index of the "rotation point," 39 | # which is where I started working from the beginning of the 40 | # dictionary. This list is huge (there are lots of words I don't know) so 41 | # we want to be efficient here. 42 | 43 | ###################################################################### 44 | 45 | # Now my turn 46 | 47 | 48 | def find_rotation_point(words): 49 | """return the index of the rotation point of a sorted word list""" 50 | 51 | # basically a modified binary search 52 | index = 0 53 | n = len(words) 54 | if n > 1: 55 | left = 0 56 | right = n - 1 57 | 58 | while (right - left) > 1: 59 | if words[left] > words[right]: 60 | # the pivot is somewhere within this interval 61 | # but where? 62 | # split the interval in half 63 | # check each half for the pivot 64 | # use the half that contains the pivot 65 | nexttry = int((left + right) / 2.0 + 0.5) 66 | if words[nexttry] > words[right]: 67 | left = nexttry 68 | else: 69 | right = nexttry 70 | else: 71 | # Boy: Do not try and find the pivot. That's 72 | # impossible. Instead only try to realize the truth. 73 | # Neo: What truth? 74 | # Boy: There is no pivot. 75 | return left 76 | 77 | if words[left] <= words[right]: 78 | index = left 79 | else: 80 | index = right 81 | 82 | return index 83 | 84 | # Now let's test 85 | 86 | 87 | class TestFindRotationPoint(unittest.TestCase): 88 | 89 | @staticmethod 90 | def right_rotate(l, n): 91 | """a helper function: rotate right a list l right by n positions, return a new list""" 92 | if n < 0: 93 | raise ValueError( 94 | "Right rotations only please, negative drehungen sind verboten" 95 | ", move along, move along.") 96 | n = n % len(l) 97 | if n == 0 or len(l) == 0: 98 | return l[:] 99 | return l[-n:] + l[:len(l) - n] 100 | 101 | def test_00rotate(self): 102 | """test our helper list rotation function""" 103 | self.assertRaises(ValueError, self.right_rotate, [1, 2, 3, 4], -1) 104 | tests = [ 105 | [0, [1, 2, 3, 4]], 106 | [1, [4, 1, 2, 3]], 107 | [2, [3, 4, 1, 2]], 108 | [3, [2, 3, 4, 1]], 109 | [4, [1, 2, 3, 4]], 110 | [7, [2, 3, 4, 1]], 111 | ] 112 | for (positions, soln) in tests: 113 | case = [1, 2, 3, 4] 114 | result = self.right_rotate(case, positions) 115 | msg = "right_rotate({}, {}) should be {} was {}".format( 116 | case, positions, soln, result) 117 | self.assertEquals(soln, result, msg) 118 | 119 | def test_0words(self): 120 | """test the case in the problem statement""" 121 | n = find_rotation_point(words) 122 | self.assertEqual(5, n) 123 | 124 | def test_1words(self): 125 | """test the words in the problem statement at all rotations""" 126 | words.sort() 127 | for i in range(0, len(words)): 128 | wordlist = self.right_rotate(words, i) 129 | n = find_rotation_point(wordlist) 130 | msg = "rotation point {} should have been {} [{}]".format( 131 | n, i, wordlist) 132 | self.assertEquals(n, i, msg) 133 | 134 | def test_ultrawords(self): 135 | """test all sizes of substrings of the words in the problem statement at all rotations""" 136 | words.sort() 137 | for j in range(0, len(words)): 138 | wordlist = words[0:j] 139 | for i in range(0, len(wordlist)): 140 | rotated_list = self.right_rotate(wordlist, i) 141 | n = find_rotation_point(rotated_list) 142 | msg = "[{}, {}] rp {} should've been {} [{}]".format( 143 | j, i, n, i, rotated_list) 144 | self.assertEquals(n, i, msg) 145 | 146 | if __name__ == "__main__": 147 | # unittest.main() 148 | suite = unittest.TestLoader().loadTestsFromTestCase(TestFindRotationPoint) 149 | unittest.TextTestRunner(verbosity=2).run(suite) 150 | -------------------------------------------------------------------------------- /32-top-scores.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/top-scores 9 | # 10 | 11 | # You created a game that is more popular than Angry Birds. 12 | 13 | # You rank players in the game from highest to lowest score. So far 14 | # you're using an algorithm that sorts in O(n log(n)) time, but 15 | # players are complaining that their rankings aren't updated fast 16 | # enough. You need a faster sorting algorithm. 17 | 18 | # Write a function that takes: 19 | 20 | # + a list of unsorted_scores 21 | # + the highest_possible_score in the game 22 | 23 | # and returns a sorted list of scores in less than O(n log(n)) time. 24 | 25 | # For example: 26 | 27 | # unsorted_scores = [37, 89, 41, 65, 91, 53] 28 | # HIGHEST_POSSIBLE_SCORE = 100 29 | 30 | # sort_scores(unsorted_scores, HIGHEST_POSSIBLE_SCORE) 31 | # # returns [37, 41, 53, 65, 89, 91] 32 | 33 | # We're defining n as the number of unsorted_scores because we're 34 | # expecting the number of players to keep climbing. 35 | 36 | # And we'll treat highest_possible_score as a constant instead of 37 | # factoring it into our big O time and space costs, because the highest 38 | # possible score isn't going to change. Even if we do redesign the game 39 | # a little, the scores will stay around the same order of magnitude. 40 | 41 | 42 | # 43 | ###################################################################### 44 | 45 | # Now my turn 46 | 47 | # after some googling, this seems to call for a count sort which is 48 | # o(n) if you know the highest value 49 | # this is a translation of the wiki pseudo code at 50 | # https://en.wikipedia.org/wiki/Counting_sort#The_algorithm 51 | # which is basically python into python and into the specific 52 | # requirements of this problem. 53 | 54 | # note: I try this twice with sort_scores and sort_scores2, where 55 | # sort_scores2 is a cleaned up and optimized version of 56 | # sort_scores. Both work, but sort_scores2 is more understandable, 57 | # uses less space, and should basically have the same runtime and 58 | # complexity 59 | 60 | def sort_scores(scores, high): 61 | """sorts a list of scores. high is the highest possible""" 62 | 63 | print("") 64 | 65 | # variables: 66 | # scores -- the array of items to be sorted; 67 | # high -- a number such that all keys are in the range 0..high - 1 68 | # count -- an array of numbers, with indexes 0..k-1, initially all zero 69 | # n -- length of scores array 70 | # sorted_scores -- an array of items, with indexes 0..n-1 71 | # x -- an individual input item, used within the algorithm 72 | # total, oldCount, i -- numbers used within the algorithm 73 | 74 | count = [0] * (high + 1) 75 | 76 | # calculate the histogram of scores: 77 | for x in scores: 78 | count[x] += 1 79 | 80 | print("%s scores" % ((len(scores)))) 81 | print(scores) 82 | print("%s counts" % ((len(count) + 1))) 83 | print(count) 84 | 85 | # calculate the starting index for each key: 86 | 87 | total = 0 88 | for i in range(high + 1): # i = 0, 1, ... k-1 89 | oldCount = count[i] 90 | count[i] = total 91 | total += oldCount 92 | 93 | print(count) 94 | 95 | # copy to output array, preserving order of scores with equal keys: 96 | sorted_scores = [0] * (len(scores)) 97 | for x in scores: 98 | index = count[x] 99 | sorted_scores[index] = x 100 | count[x] += 1 101 | 102 | return sorted_scores 103 | 104 | 105 | # so count sort is a pretty interesting sorting algorithm, but it would 106 | # seem to be appropriate only when the high value is itself a 107 | # relatively low number as we have to alloc an array of that size. 108 | 109 | # it's apparently NOT the interview cake soln as this soln is 110 | # o(highest score) in size and interview cake insists it can be done 111 | # in o(n) space. 112 | 113 | # let's rethink this. 114 | 115 | # there is a lot of empty space in the count array, maybe we can turn 116 | # that into a dict where keys of the dict are the scores 117 | 118 | def sort_scores2(scores, high): 119 | """sorts a list of scores. high is the highest possible""" 120 | 121 | count = {} 122 | 123 | # calculate the histogram of scores: 124 | for x in scores: 125 | score = count.get(x, 0) 126 | count[x] = score + 1 127 | 128 | # transcribe the histogram into the sorted scores 129 | sorts = [] 130 | for i in range(high + 1): # i = 0, 1, ... k-1 131 | if i in count: 132 | eyes = [i] * count[i] 133 | sorts.extend(eyes) 134 | 135 | print("scores %s sorted is %s" % (scores, sorts)) 136 | return sorts 137 | 138 | # Conclusion: the count combined with a has reduces the space of a 139 | # sparse set of scores immensely. I would say this sort is o(n + k) 140 | # where n is the number of input scores and k is the high score. 141 | 142 | # Conclusion2: sort_scores2 using the dict, seems more pythonic, and 143 | # also seems much easier to understand. In comparison, sort_scores is 144 | # very clunky, although the unmodified wiki algorithm it came with has 145 | # the machinery to sort more than just a list of scores. 146 | 147 | # And now the tests 148 | 149 | 150 | class TestTopScores(unittest.TestCase): 151 | 152 | def test_examples(self): 153 | """test some examples""" 154 | tests = [ 155 | [[3, 8, 5, 3, 2], 11], 156 | [[37, 89, 41, 65, 91, 53], 100], 157 | [[37, 89, 41, 65, 91, 53, 41], 100], 158 | [[37, 89, 41, 45, 45, 45, 45, 45, 45, 65, 91, 53, 37, 41, 65], 100], 159 | [[37, 89, 41, 65, 91, 53, 37, 41, 65], 100], 160 | ] 161 | 162 | for fn in (sort_scores, sort_scores2): 163 | 164 | print("") 165 | print(fn.__name__) 166 | 167 | for unsortd, high in tests: 168 | sortd = sorted(unsortd) 169 | result = fn(unsortd, high) 170 | self.assertEqual(result, sortd, 171 | "sorting %s should be %s was %s" % 172 | (unsortd, sortd, result)) 173 | 174 | print("") 175 | 176 | 177 | if __name__ == "__main__": 178 | # unittest.main() 179 | suite = unittest.TestLoader().loadTestsFromTestCase(TestTopScores) 180 | unittest.TextTestRunner(verbosity=2).run(suite) 181 | -------------------------------------------------------------------------------- /10-second-largest-item-in-bst.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | ###################################################################### 4 | # this problem is from 5 | # https://www.interviewcake.com/question/second-largest-item-in-bst 6 | 7 | # Write a function to find the 2nd largest element in a binary search tree 8 | 9 | # Here are two implementations: 10 | # 1: the "obvious" that sorts the tree and pulls out penultimate 11 | # 2: and one that exploits knowledge of structure of a binary search tree 12 | # to find the two possible locations the penultimate might be at 13 | ###################################################################### 14 | 15 | import unittest 16 | 17 | # Here's a sample binary tree node class: 18 | 19 | 20 | class BinaryTreeNode: 21 | 22 | def __init__(self, value): 23 | self.value = value 24 | self.left = None 25 | self.right = None 26 | 27 | def insert_left(self, value): 28 | self.left = value 29 | return self.left 30 | 31 | def insert_right(self, value): 32 | self.right = value 33 | return self.right 34 | 35 | # ------------------------------ 36 | 37 | def __str__(self): 38 | s = "<" + str(self.value) + ", " + str(self.left) + \ 39 | ", " + str(self.right) + ">" 40 | return s 41 | 42 | # sort a binary tree with in-order traversal 43 | def uniq_sort(self, nodes=None, depth=0): 44 | if nodes is None: 45 | nodes = [] 46 | depth = depth + 1 47 | if self.left: 48 | self.left.uniq_sort(nodes, depth) 49 | 50 | if len(nodes) > 0: 51 | if nodes[-1] != self.value: 52 | nodes.append(self.value) 53 | else: 54 | nodes.append(self.value) 55 | 56 | if self.right: 57 | self.right.uniq_sort(nodes, depth) 58 | return nodes 59 | 60 | # Find the 2nd largest element in a binary search tree 61 | # assumes it has been given a valid bst 62 | # this version requires a sort 63 | def second_largest(self): 64 | sort = self.uniq_sort() 65 | if len(sort) > 1: 66 | return sort[-2] 67 | else: 68 | return None 69 | 70 | # Find the 2nd largest element in a binary search tree 71 | # assumes it has been given a valid bst 72 | # this version avoids sorting the tree 73 | # by exploiting a bit of knowledge about binary search trees 74 | # and quite frankly is inspired by a google search of the question 75 | # this is one reason why "how would you do this..." interview 76 | # questions should be answered "I would think about and I would 77 | # google it". 78 | 79 | def second_largest2(self): 80 | """a sol'n that uses knowledge of binary trees to avoid sort""" 81 | n = 0 82 | pptr = self 83 | rptr = self.right 84 | # we want the second to last rightmost node 85 | # which we find with two following ptrs 86 | # when the first ptr falls off the tree 87 | # the ppptr points to the second to last node 88 | while rptr: 89 | n += 1 90 | ppptr = pptr 91 | pptr = rptr 92 | rptr = rptr.right 93 | if self.right: 94 | if n >= 1: 95 | return ppptr.value 96 | else: 97 | return None 98 | 99 | n = 0 100 | pptr = self 101 | rptr = self.left 102 | # now we want the last rightmost node 103 | # which we find with a following ptr 104 | # when the first ptr falls off the tree 105 | # the pptr points to the last node 106 | while rptr: 107 | n += 1 108 | pptr = rptr 109 | rptr = rptr.right 110 | if self.left: 111 | if n >= 1: 112 | return pptr.value 113 | else: 114 | return None 115 | return None 116 | 117 | # now test 118 | 119 | 120 | class TestBinaryTreeNodes(unittest.TestCase): 121 | 122 | def setUp(self): 123 | # [second_largest, list_tree] 124 | self.cases = [ 125 | [None, [1]], 126 | [2, [2, 1, 3]], 127 | [15, [12, [10, 8, 11], [15, 14, 16]]], 128 | [11, [12, [10, 8, 11]]], 129 | [10, [12, [10, 8]]] 130 | ] 131 | print("setup: %s cases" % len(self.cases)) 132 | 133 | def test_bst(self): 134 | caseno = 0 135 | for (soln, case) in self.cases: 136 | print("\ncase %2s: %s" % (caseno, case)) 137 | bt = list_tree_to_BinaryTree(case) 138 | 139 | # test the sorting soln 140 | second = bt.second_largest() 141 | print(" second_largest is %s" % second) 142 | self.assertEqual( 143 | soln, second, 144 | "expected soln {} is not {}".format(soln, second)) 145 | 146 | # now test the quicker soln 147 | second2 = bt.second_largest2() 148 | print(" second_largest2 is %s" % second2) 149 | self.assertEqual( 150 | soln, second2, 151 | "expected soln {} is not {}".format(soln, second2)) 152 | 153 | caseno += 1 154 | 155 | # a helper fn to convert from my preferred lisp list_tree format 156 | # to nodey binary trees 157 | 158 | # Coming from lisp, I like to see trees implemented in pure list 159 | # structures, [1 [2 21 22] [3 31 32]] if only because they are easy to 160 | # visualize and to create as tests. I refer to such trees here as a 161 | # list_tree 162 | 163 | # however, interviewcake prefers a more java/c traditional 164 | # [node, ptr_left, ptr_right] structure so I have added a few routines 165 | # that let me create testcases using lisplike trees and then 166 | # converting them to ptrful trees. 167 | 168 | 169 | def list_tree_to_BinaryTree(list_tree): 170 | 171 | # this just helps with short hand "trees" 172 | # of forms 1, or [1] changing it into [1, None, None] 173 | # then truncating 174 | if type(list_tree) is not list: 175 | list_tree = [list_tree, None, None] 176 | list_tree = list_tree + [None, None, None] 177 | list_tree = list_tree[0:3] 178 | 179 | (value, left, right) = list_tree 180 | 181 | node = BinaryTreeNode(value) 182 | 183 | if left: 184 | node.insert_left(list_tree_to_BinaryTree(left)) 185 | 186 | if right: 187 | node.insert_right(list_tree_to_BinaryTree(right)) 188 | 189 | return node 190 | 191 | 192 | if __name__ == "__main__": 193 | # unittest.main() 194 | suite = unittest.TestLoader().loadTestsFromTestCase(TestBinaryTreeNodes) 195 | unittest.TextTestRunner(verbosity=2).run(suite) 196 | -------------------------------------------------------------------------------- /6-rectangular-love.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | ###################################################################### 4 | # this problem is from 5 | # https://www.interviewcake.com/question/python/rectangular-love 6 | 7 | # A crack team of love scientists from OkEros (a hot new dating site) 8 | # have devised a way to represent dating profiles as rectangles on a 9 | # two-dimensional plane. 10 | 11 | # They need help writing an algorithm to find the intersection of two 12 | # users' love rectangles. They suspect finding that intersection is the 13 | # key to a matching algorithm so powerful it will cause an immediate 14 | # acquisition by Google or Facebook or Obama or something. 15 | 16 | # Write a function to find the rectangular intersection of two given 17 | # love rectangles. 18 | 19 | # As with the example above, love rectangles are always "straight" and 20 | # never "diagonal." More rigorously: each side is parallel with either 21 | # the x-axis or the y-axis. 22 | 23 | # They are defined as dictionaries like this: 24 | 25 | # my_rectangle = { 26 | 27 | # # coordinates of bottom-left corner 28 | # 'left_x': 1, 29 | # 'bottom_y': 5, 30 | 31 | # # width and height 32 | # 'width': 10, 33 | # 'height': 4, 34 | 35 | # } 36 | 37 | # Your output rectangle should use this format as well. 38 | 39 | ###################################################################### 40 | 41 | # Now my turn 42 | 43 | import unittest 44 | from random import randint 45 | 46 | 47 | def rectangular_intersection(rect1, rect2): 48 | """returns a dictionary containing the coordinates of the intersection of two rects""" 49 | 50 | # order r1 and r2 left to right according to left of rect1 and rect2 51 | 52 | (r1, r2) = (rect1, rect2) \ 53 | if rect1['left_x'] <= rect2['left_x'] else (rect2, rect1) 54 | 55 | if r1['left_x'] + r1['width'] <= r2['left_x']: 56 | return None # [r1] [r2] so no intersection 57 | 58 | left_x = max(r1['left_x'], r2['left_x']) 59 | right_x = min(r1['left_x'] + r1['width'], r2['left_x'] + r2['width']) 60 | width = right_x - left_x 61 | 62 | # order r1 and r2 bottom to top according to bottom of rect1 and rect2 63 | 64 | (r1, r2) = (rect1, rect2) \ 65 | if rect1['bottom_y'] <= rect2['bottom_y'] else (rect2, rect1) 66 | 67 | if r1['bottom_y'] + r1['height'] <= r2['bottom_y']: 68 | return None # r2 is entirely above r1, no intersection 69 | 70 | lower_y = max(r1['bottom_y'], r2['bottom_y']) 71 | upper_y = min(r1['bottom_y'] + r1['height'], r2['bottom_y'] + r2['height']) 72 | height = upper_y - lower_y 73 | 74 | intersection = { 75 | 'left_x': left_x, 76 | 'bottom_y': lower_y, 77 | 'width': width, 78 | 'height': height 79 | } 80 | 81 | return intersection 82 | 83 | 84 | def rectangular_intersection2(r1, r2): 85 | """returns a dictionary containing the coordinates of the intersection of two rects""" 86 | 87 | left_x = max(r1['left_x'], r2['left_x']) 88 | right_x = min(r1['left_x'] + r1['width'], r2['left_x'] + r2['width']) 89 | width = right_x - left_x 90 | 91 | lower_y = max(r1['bottom_y'], r2['bottom_y']) 92 | upper_y = min(r1['bottom_y'] + r1['height'], r2['bottom_y'] + r2['height']) 93 | height = upper_y - lower_y 94 | 95 | if width <= 0 or height <= 0: 96 | return None 97 | 98 | intersection = { 99 | 'left_x': left_x, 100 | 'bottom_y': lower_y, 101 | 'width': width, 102 | 'height': height 103 | } 104 | 105 | return intersection 106 | 107 | # now test 108 | 109 | 110 | def make_rectangle(l): 111 | (l, b, w, h) = l 112 | return { 113 | 'left_x': l, 114 | 'bottom_y': b, 115 | 'width': w, 116 | 'height': h, 117 | } 118 | 119 | 120 | class TestRectangleIntersection(unittest.TestCase): 121 | 122 | def test_0rectangles(self): 123 | """testing""" 124 | 125 | r1 = make_rectangle([1, 5, 10, 4]) 126 | tests = [ 127 | [make_rectangle([2, 6, 2, 2]), 128 | make_rectangle([2, 6, 2, 2])], 129 | [make_rectangle([2, 6, 9, 3]), 130 | make_rectangle([2, 6, 9, 3])], 131 | [make_rectangle([2, 6, 10, 4]), 132 | make_rectangle([2, 6, 9, 3])], 133 | [make_rectangle([10, 5, 10, 2]), 134 | make_rectangle([10, 5, 1, 2])], 135 | [make_rectangle([0, 0, 1, 1]), 136 | None], 137 | [make_rectangle([2, 0, 9, 2]), 138 | None], 139 | [make_rectangle([2, 10, 10, 4]), 140 | None] 141 | ] 142 | 143 | print("test cases") 144 | for (r2, answer) in tests: 145 | soln1 = rectangular_intersection(r1, r2) 146 | soln2 = rectangular_intersection2(r1, r2) 147 | 148 | print("r1 = %s" % r1) 149 | print("r2 = %s" % r2) 150 | print("answer = %s" % answer) 151 | print(" soln1 = %s" % soln1) 152 | print(" soln2 = %s" % soln2) 153 | 154 | if soln1 != answer: 155 | print("test case failed, soln1 is not answer") 156 | 157 | self.assertEqual( 158 | soln1, answer, "known solution does not match return") 159 | 160 | if soln1 != soln2: 161 | print("test case failed, soln1 is not soln2") 162 | 163 | self.assertEqual( 164 | soln2, answer, "known solution does not match return") 165 | 166 | def test_1random_rectangles(self): 167 | """testing a million random rectangles""" 168 | 169 | print("") 170 | print("random rects") 171 | 172 | r1 = make_rectangle([1, 1, 8, 8]) 173 | overlaps = 0 174 | for i in range(1000000): 175 | l = randint(-10, 20) 176 | b = randint(-10, 20) 177 | w = randint(1, 15) 178 | h = randint(1, 15) 179 | r2 = make_rectangle([l, b, w, h]) 180 | soln1 = rectangular_intersection(r1, r2) 181 | soln2 = rectangular_intersection2(r1, r2) 182 | if soln1 != soln2: 183 | print("trial %s" % i) 184 | print("soln1 != soln2!") 185 | print("r1 = %s" % r1) 186 | print("r2 = %s" % r2) 187 | print("soln1 = %s" % soln1) 188 | print("soln2 = %s" % soln2) 189 | 190 | self.assertEqual( 191 | soln1, soln2, "two returned solutions do not match each other") 192 | 193 | elif soln1: 194 | overlaps += 1 195 | # print("trial %s" % i) 196 | # print("r2 = %s" % r2) 197 | # print("soln1 = %s" % soln1) 198 | # print("soln2 = %s" % soln2) 199 | # print("") 200 | print("1,000,000 random rects: %s overlaps found" % overlaps) 201 | 202 | 203 | if __name__ == "__main__": 204 | # unittest.main() 205 | suite = unittest.TestLoader().loadTestsFromTestCase(TestRectangleIntersection) 206 | unittest.TextTestRunner(verbosity=2).run(suite) 207 | -------------------------------------------------------------------------------- /9-bst-checker.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | ###################################################################### 4 | # this problem is from 5 | # https://www.interviewcake.com/question/bst-checker 6 | 7 | # ------------------------------ 8 | 9 | # Write a function to see if a binary tree is a valid binary search tree 10 | 11 | # A binary tree is a tree where every node has two or fewer 12 | # children. The children are usually called left and right. 13 | 14 | # ----------- review ----------- 15 | # 16 | # A * perfect * binary tree has no gaps, and the leaf nodes are all at the 17 | # bottom of the tree. 18 | 19 | # Property 1: 20 | # the number of total nodes on each "level" doubles as we 21 | # move down the tree. 22 | 23 | # Property 2: 24 | # the number of nodes on the last level is equal to the sum 25 | # of the number of nodes on all other levels(plus 1). In other words, 26 | # about half of our nodes are on the last level. 27 | 28 | # Let's call the number of nodes n, and the height of the tree as h, 29 | # where h starts at 1. h can also be thought of as the "number of 30 | # levels." 31 | 32 | # If a perfect binary tree has height h, how many nodes does it have? 33 | 34 | # n = 2 ^ h - 1 35 | 36 | # Let's call the number of nodes n, and the height of the tree h. h can 37 | # also be thought of as the "number of levels." 38 | 39 | # If we had h, how could we calculate n? 40 | 41 | # n = 2 ^ h - 1 42 | # n + 1 = 2 ^ h 43 | # ln(n + 2) = h * ln(2) 44 | # h = ln(n + 2) / ln(2) 45 | 46 | # A binary search tree is a binary tree in which, for each node: 47 | 48 | # The node's value is greater than all values in the left subtree. 49 | 50 | # The node's value is less than all values in the right subtree. 51 | 52 | # BSTs are useful for quick lookups. If the tree is balanced, we can 53 | # search for a given value in the tree in O(\lg{n})O(lgn) time. 54 | 55 | ###################################################################### 56 | 57 | # Now my turn 58 | 59 | import unittest 60 | 61 | # ------------------------------ 62 | 63 | # Here's a sample binary tree node class: 64 | 65 | 66 | class BinaryTreeNode: 67 | 68 | def __init__(self, value): 69 | self.value = value 70 | self.left = None 71 | self.right = None 72 | 73 | def insert_left(self, value): 74 | self.left = value 75 | return self.left 76 | 77 | def insert_right(self, value): 78 | self.right = value 79 | return self.right 80 | 81 | # ------------------------------ 82 | 83 | def __str__(self): 84 | s = "<" + str(self.value) + ", " + str(self.left) + \ 85 | ", " + str(self.right) + ">" 86 | return s 87 | 88 | # A function to check a binary tree is a valid binary search tree 89 | 90 | # A binary search tree is a binary tree in which, for each node: 91 | # The node's value is greater than all values in the left subtree. 92 | # The node's value is less than all values in the right subtree. 93 | # The above two conditions imply a valid bst has unique elements 94 | 95 | def is_bst(self, depth=0): 96 | depth += 1 97 | 98 | inf = float('inf') 99 | lmin = rmin = inf 100 | lmax = rmax = -inf 101 | 102 | if self.left: 103 | (lvalid, lmin, lmax) = self.left.is_bst(depth) 104 | 105 | if not lvalid: 106 | return False, None, None 107 | if self.value <= lmax: 108 | print("{:{width}}left: {} is not > {}!".format( 109 | "", self.value, lmax, width=depth)) 110 | return False, None, None 111 | 112 | if self.right: 113 | (rvalid, rmin, rmax) = self.right.is_bst(depth) 114 | 115 | if not rvalid: 116 | return False, None, None 117 | if self.value >= rmin: 118 | print("{:{width}}right: {} is not < {}!".format( 119 | "", self.value, rmin, width=depth)) 120 | return False, None, None 121 | 122 | nmin = min([lmin, rmin, self.value]) 123 | nmax = max([lmax, rmax, self.value]) 124 | 125 | print("{:{width}}min:{} < value:{} < max:{}".format( 126 | "", lmax, self.value, rmin, width=depth)) 127 | return self.value, nmin, nmax 128 | 129 | # now test 130 | 131 | 132 | class TestBinaryTreeNodes(unittest.TestCase): 133 | 134 | def setUp(self): 135 | # [valid_bst, list_tree] 136 | self.cases = [ 137 | [True, [1]], 138 | [False, [1, 2, None]], 139 | [True, [2, 1, 3]], 140 | [False, [2, 1, 2]], 141 | [False, [2, 1, 1]], 142 | [False, [3, 2, 1]], 143 | [True, [12, [10, 8, 11], [15, 14, 16]]], 144 | [False, [12, [10, 8, 13], [15, 10, 16]]], 145 | [False, [12, [10, 8, 11], [11, 12, 16]]], 146 | [False, [12, [10, 8, 11], [13, 14, 16]]], 147 | ] 148 | print("setup: %s cases" % len(self.cases)) 149 | 150 | def test_bst(self): 151 | caseno = 0 152 | for (designed_bst, case) in self.cases: 153 | print("\ncase %2s: %s" % (caseno, case)) 154 | bt = list_tree_to_BinaryTree(case) 155 | (valid, min, max) = bt.is_bst() 156 | 157 | by_design = "" 158 | if not designed_bst and not valid: 159 | by_design = "(by design)" 160 | if designed_bst and not valid: 161 | by_design = "!!! ERROR!" 162 | if not designed_bst and valid: 163 | by_design = "!!! ERROR!" 164 | 165 | if valid: 166 | print("{:{width}} {} <= {} <= {} => valid bst {}" 167 | .format('', 168 | min, valid, max, 169 | by_design, width=6)) 170 | else: 171 | print("{:{width}} not a valid bst {}" 172 | .format('', 173 | by_design, width=6)) 174 | 175 | self.assertNotEqual(by_design, "!!! ERROR!") 176 | 177 | caseno += 1 178 | 179 | # Coming from lisp, I like to see trees implemented in pure list 180 | # structures, [1 [2 21 22] [3 31 32]] if only because they are easy to 181 | # visualize and to create as tests. I refer to such trees here as a 182 | # list_tree 183 | 184 | # however, interviewcake prefers a more java/c traditional 185 | # [node, ptr_left, ptr_right] structure so I have added a routine 186 | # that lets me create testcases using lisplike trees and then 187 | # converting them to ptrful trees. 188 | 189 | # a helper fn to convert from my preferred lisp list_tree format 190 | # to nodey binary trees 191 | 192 | 193 | def list_tree_to_BinaryTree(list_tree): 194 | 195 | # this just helps with short hand "trees" 196 | # of forms 1, or [1] changing it into [1, None, None] 197 | # then truncating 198 | if type(list_tree) is not list: 199 | list_tree = [list_tree, None, None] 200 | list_tree = list_tree + [None, None, None] 201 | list_tree = list_tree[0:3] 202 | 203 | (value, left, right) = list_tree 204 | 205 | node = BinaryTreeNode(value) 206 | 207 | if left: 208 | node.insert_left(list_tree_to_BinaryTree(left)) 209 | 210 | if right: 211 | node.insert_right(list_tree_to_BinaryTree(right)) 212 | 213 | return node 214 | 215 | 216 | if __name__ == "__main__": 217 | # unittest.main() 218 | suite = unittest.TestLoader().loadTestsFromTestCase(TestBinaryTreeNodes) 219 | unittest.TextTestRunner(verbosity=2).run(suite) 220 | -------------------------------------------------------------------------------- /5-making-change.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # interviewcake 5 Making Change 9 | # https://www.interviewcake.com/question/python/coin 10 | 11 | # Imagine you landed a new job as a cashier... 12 | # 13 | # Your quirky boss found out that you're a programmer and has a weird 14 | # request about something they've been wondering for a long time. 15 | # 16 | # Write a function that, given: 17 | # 18 | # 1. an amount of money 19 | # 2. a list of coin denominations 20 | # 21 | # computes the number of ways to make amount of money with coins of the 22 | # available denominations. 23 | # 24 | # Example: for amount=4 (4c) and denominations=[1,2,3] (1c, 2c and 3c), 25 | # your program would output 4 - the number of ways to make 26 | # 4c with those denominations: 27 | # 28 | # 1c, 1c, 1c, 1c 29 | # 1c, 1c, 2c 30 | # 1c, 3c 31 | # 2c, 2c 32 | 33 | # 34 | ###################################################################### 35 | 36 | # Now my turn 37 | 38 | ### 39 | # first try: recursion this is a recursive solution. while this 40 | # works, mostly, apparently this is a problem that can be solved with 41 | # a dynamic programming solution (if you can recognize it) 42 | # the change-making problem 43 | # (https://en.wikipedia.org/wiki/Change-making_problem) 44 | 45 | # but let's see first what recursion can do, then later, retry with 46 | # some hints from interviewcake 47 | 48 | 49 | def make_change1(amount, denominations): 50 | """return num ways to make change from denominations""" 51 | denominations.sort() 52 | denominations.reverse() 53 | 54 | # _make_change1 will return ALL the ways change can be made 55 | # this includes duplicates like (2, 1) being the same as (1, 2) 56 | patterns = _make_change1(amount, denominations) 57 | 58 | # so uniquify will eliminate the dupes 59 | uniques = uniquify(patterns) 60 | count = len(uniques) 61 | return count 62 | 63 | 64 | def _make_change1(amount, denominations, level=0): 65 | """recursive procedure to find all ways combinations of demoniations sum to amount""" 66 | patterns = [] 67 | for d in denominations: 68 | (quotient, remainder) = divmod(amount, d) 69 | if quotient > 0: 70 | if quotient == 1 and remainder == 0: 71 | patterns.append([d]) 72 | else: 73 | sub_patterns \ 74 | = _make_change1(amount - d, denominations, level + 1) 75 | for pattern in sub_patterns: 76 | pat = [d] 77 | pat.extend(pattern) 78 | patterns.append(pat) 79 | return patterns 80 | 81 | 82 | def uniquify(patterns): 83 | """remove duplicates from a set of change making patterns""" 84 | s = set() 85 | # patterns is a list of lists 86 | # each pattern is a decomposition of the summands of the amount in terms 87 | # of the denominations 88 | # we sort the decomposition so that (2, 1) becomes (1, 2) 89 | # stringify it 90 | # toss it into a python set (to eliminate any dups) 91 | # and return the set 92 | for p in patterns: 93 | p.sort() 94 | strp = str(p) 95 | s.add(strp) 96 | elts = list(s) 97 | elts.sort() 98 | return elts 99 | 100 | 101 | # trial 2, the dynamic solution canonically given as the soln to this problem 102 | # is much faster and use far less space than the recursive method 103 | # coming up with the dynamic solution on the other hand.... 104 | 105 | 106 | def make_change2(amount, denominations): 107 | """return num ways to make use change from denominations to add to amount (dynamic)""" 108 | 109 | ncointypes = len(denominations) 110 | 111 | # ways is an array, where from 0 cents to the target amount 112 | # ways holds a list of ways the given denominations can sum to the target 113 | # amount 114 | ways = [[0 for x in range(ncointypes)] for z in range(amount + 1)] 115 | 116 | # Using a dynamic solution, determine the ways for every postive 117 | # amount less than our target amount, building a table, a cache of 118 | # amounts that help us find the number of ways for the target 119 | # amount 120 | 121 | # for each amount from 0 up to our target amount 122 | for curr_amt in range(1, amount + 1): 123 | 124 | # find the number of ways of using all the coins to sum to the 125 | # current amount by looping over each coin 126 | 127 | for coin in range(ncointypes): 128 | 129 | # 1. find ways to get to the current amount minus our current coin 130 | 131 | sub_amount = curr_amt - denominations[coin] 132 | if sub_amount < 0: 133 | sub_amt_ways = 0 134 | elif sub_amount == 0: 135 | sub_amt_ways = 1 # 0 plus our current coin = curr amount 136 | else: 137 | sub_amt_ways = ways[sub_amount][coin] 138 | 139 | # 2. lookup the prior ways we found to get the current 140 | # amount with the prior coins 141 | # here's where the dynamicism occurs: 142 | 143 | old_ways = ways[curr_amt][coin - 1] 144 | 145 | # the total ways is the sum of both ways 146 | ways[curr_amt][coin] = sub_amt_ways + old_ways 147 | 148 | count = ways[amount][ncointypes - 1] 149 | 150 | return count 151 | 152 | 153 | # and now test 154 | 155 | 156 | class TestMakeChange(unittest.TestCase): 157 | 158 | def change(self, text, fn, amount, denominations): 159 | """a helper fn""" 160 | count = fn(amount, denominations) 161 | print("%s: %s ways to make change for %s using %s" % 162 | (text, count, amount, denominations)) 163 | return count 164 | 165 | def test_givenexample1(self): 166 | """test the given example""" 167 | amount = 4 168 | denominations = [1, 2, 3] 169 | count = self.change("dynamic", make_change2, amount, denominations) 170 | self.assertEqual(4, count) 171 | 172 | def test_21(self): 173 | """change for 1 cent""" 174 | amount = 1 175 | denominations = [1, 2, 3] 176 | count = self.change("dynamic", make_change2, amount, denominations) 177 | self.assertEqual(1, count) 178 | 179 | def test_22(self): 180 | """change for 2 cents""" 181 | amount = 2 182 | denominations = [1, 2, 3] 183 | count = self.change("dynamic", make_change2, amount, denominations) 184 | self.assertEqual(2, count) 185 | 186 | def test_23(self): 187 | """change for 3 cents""" 188 | amount = 3 189 | denominations = [1, 2, 3] 190 | count = self.change("dynamic", make_change2, amount, denominations) 191 | self.assertEqual(3, count) 192 | 193 | def test_24(self): 194 | """change for 4 cents""" 195 | amount = 4 196 | denominations = [1, 2, 3] 197 | count = self.change("dynamic", make_change2, amount, denominations) 198 | self.assertEqual(4, count) 199 | 200 | def test_more(self): 201 | """more tests that help determine if the recurse and dynamic approaches are both correct""" 202 | tests = [ 203 | [1, 1, [1]], 204 | [0, 1, [2]], 205 | [0, 1, [3]], 206 | [1, 2, [1]], 207 | [2, 2, [1, 2]], 208 | [2, 2, [1, 2, 3]], 209 | [0, 2, [3]], 210 | [4, 4, [1, 2, 3]], 211 | [5, 10, [2, 5, 3, 6]], 212 | [85, 23, [1, 3, 5, 7, 8, 11]]] 213 | for soln, amount, denoms in tests: 214 | count = self.change("dynamic", make_change2, amount, denoms) 215 | self.assertEqual(soln, count) 216 | for soln, amount, denoms in tests: 217 | count = self.change("recursive", make_change1, amount, denoms) 218 | self.assertEqual(soln, count) 219 | 220 | # conclusion, the dynamic solution is far far faster! 221 | 222 | if __name__ == "__main__": 223 | # unittest.main() 224 | suite = unittest.TestLoader().loadTestsFromTestCase(TestMakeChange) 225 | unittest.TextTestRunner(verbosity=2).run(suite) 226 | -------------------------------------------------------------------------------- /36-single-riffle-check.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import random 5 | import unittest 6 | 7 | ###################################################################### 8 | # this problem is from 9 | # https://www.interviewcake.com/question/python/single-rifle-check 10 | # 11 | 12 | # I figured out how to get rich: online poker. 13 | 14 | # I suspect the online poker game I'm playing shuffles cards by doing 15 | # a single "riffle." 16 | 17 | # To prove this, let's write a function to tell us if a full deck of 18 | # cards shuffled_deck is a single riffle of two other halves half1 and 19 | # half2. 20 | 21 | # We'll represent a stack of cards as a list of integers in the range 22 | # 1..52 (since there are 52 distinct cards in a deck). 23 | 24 | # Why do I care? A single riffle is not a completely random 25 | # shuffle. If I'm right, I can make more informed bets and get rich 26 | # and finally prove to my ex that I am not a "loser with an unhealthy 27 | # cake obsession" (even though it's too late now because she let me go 28 | # and she's never getting me back). 29 | 30 | ###################################################################### 31 | 32 | # now my turn 33 | 34 | # first try: 35 | 36 | # a singly riffled deck should have two sets of cards in it, 37 | # for some c in a deck of n cards 38 | # the first set of cards is 1..c 39 | # the second set is c+1..n 40 | # and in the deck the cards from each set are sequential, and interspersed. 41 | # so scan the deck looking for sequential cards in either set 42 | # if you find a non-sequential card, then the deck is not singly riffled. 43 | 44 | random.seed() 45 | ncards = 52 46 | 47 | 48 | def is_first_riffle(deck): 49 | h1 = 0 50 | h2 = 0 51 | 52 | for card in deck: 53 | if card == h1 + 1: 54 | h1 = card 55 | else: 56 | if h2 == 0: 57 | h2 = card - 1 58 | 59 | if card == h2 + 1: 60 | h2 = card 61 | else: 62 | return False 63 | return True 64 | 65 | # heh. is_first_riffle has little to do with the problem statement, 66 | # which I now realize is about singly riffling a cut, shuffled deck. 67 | 68 | # try again... 69 | 70 | # let's write a function to tell us if a full deck of cards 71 | # shuffled_deck is a single riffle of two other halves half1 and 72 | # half2. 73 | 74 | 75 | # second try, recursive solution: 76 | def is_single_riffle(half1, half2, shuffled_deck): 77 | """returns True if shuffled_deck may have come from half1 and half2""" 78 | # base case 79 | if len(shuffled_deck) == 0: 80 | return True 81 | 82 | # if the top of shuffled_deck is the same as the top of half1 83 | # (making sure first that we have a top card in half1) 84 | if len(half1) and half1[0] == shuffled_deck[0]: 85 | 86 | # take the top cards off half1 and shuffled_deck and recurse 87 | return is_single_riffle(half1[1:], half2, shuffled_deck[1:]) 88 | 89 | # if the top of shuffled_deck is the same as the top of half2 90 | elif len(half2) and half2[0] == shuffled_deck[0]: 91 | 92 | # take the top cards off half2 and shuffled_deck and recurse 93 | return is_single_riffle(half1, half2[1:], shuffled_deck[1:]) 94 | 95 | # top of shuffled_deck doesn't match top of half1 or half2 96 | # so we know it's not a single riffle 97 | else: 98 | return False 99 | 100 | # third try (non-recursive): 101 | 102 | 103 | def is_single_riffle2(half1, half2, shuffled_deck): 104 | h1 = 0 105 | h2 = 0 106 | for card in shuffled_deck: 107 | if h1 < len(half1) and card == half1[h1]: 108 | h1 += 1 109 | elif h2 < len(half2) and card == half2[h2]: 110 | h2 += 1 111 | else: 112 | return False 113 | return True 114 | 115 | 116 | # now test 117 | 118 | 119 | class TestSingleRiffleCheck(unittest.TestCase): 120 | 121 | # throw away test helpers 122 | 123 | def first_riffle(self, num_cards=ncards): 124 | """return a single riffled deck of cards""" 125 | split = random.randrange(2, ncards - 1) 126 | h1 = [] 127 | for i in range(1, split): 128 | h1.append(i) 129 | 130 | h2 = [] 131 | for i in range(split, ncards + 1): 132 | h2.append(i) 133 | 134 | return h1, h2, self.shuffle(h1, h2, 3) 135 | 136 | def shuffle(self, h1, h2, maxcards=3): 137 | """shuffles two decks, with at most, maxcards in a row from a single deck""" 138 | deck = [] 139 | maxcards += 1 140 | while((len(h1) > 0) or (len(h2) > 0)): 141 | 142 | h1cards = random.randrange(1, maxcards) 143 | h1cards = min(h1cards, len(h1)) 144 | h1slice = h1[0:h1cards] 145 | h1 = h1[h1cards:] 146 | 147 | h2cards = random.randrange(1, maxcards) 148 | h2cards = min(h2cards, len(h2)) 149 | h2slice = h2[0:h2cards] 150 | h2 = h2[h2cards:] 151 | 152 | deck.extend(h1slice) 153 | deck.extend(h2slice) 154 | 155 | return deck 156 | 157 | def test_1good_riffles(self): 158 | """test good riffles""" 159 | 160 | h1, h2, deck = self.first_riffle() 161 | print("") 162 | print("h1: %s" % h1) 163 | print("h2: %s" % h2) 164 | self.assertTrue(is_single_riffle(h1, h2, deck), 165 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 166 | self.assertTrue(is_single_riffle2(h1, h2, deck), 167 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 168 | 169 | deck = self.shuffle(h1, h2) 170 | print("deck: %s" % deck) 171 | self.assertTrue(is_single_riffle(h1, h2, deck), 172 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 173 | self.assertTrue(is_single_riffle2(h1, h2, deck), 174 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 175 | 176 | def test_2ngood_riffles(self): 177 | for i in range(100): 178 | h1, h2, deck = self.first_riffle() 179 | self.assertTrue(is_single_riffle(h1, h2, deck), 180 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 181 | self.assertTrue(is_single_riffle2(h1, h2, deck), 182 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 183 | 184 | deck = self.shuffle(h1, h2) 185 | self.assertTrue(is_single_riffle(h1, h2, deck), 186 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 187 | self.assertTrue(is_single_riffle2(h1, h2, deck), 188 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 189 | 190 | def test_3nbad_riffles(self): 191 | for i in range(100): 192 | h1, h2, deck = self.first_riffle() 193 | self.assertTrue(is_single_riffle(h1, h2, deck), 194 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 195 | self.assertTrue(is_single_riffle2(h1, h2, deck), 196 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 197 | 198 | deck = self.shuffle(h1, h2) 199 | self.assertTrue(is_single_riffle(h1, h2, deck), 200 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 201 | self.assertTrue(is_single_riffle2(h1, h2, deck), 202 | "\nh1 %s h2 %s\ndeck %s" % (h1, h2, deck)) 203 | 204 | h1index = int(len(h1) / 2) 205 | h2index = int(len(h2) / 2) 206 | h1[h1index], h2[h2index] = h2[h2index], h1[h1index] 207 | h1index -= 1 208 | h2index += 1 209 | h1[h1index], h2[h2index] = h2[h2index], h1[h1index] 210 | self.assertFalse(is_single_riffle( 211 | h1, h2, deck), "\nh1 %s\nh2 %s\ndeck: %s\nh1index %s\nh2index %s" 212 | % (h1, h2, deck, h1index, h2index)) 213 | self.assertFalse(is_single_riffle2( 214 | h1, h2, deck), "\nh1 %s\nh2 %s\ndeck: %s\nh1index %s\nh2index %s" 215 | % (h1, h2, deck, h1index, h2index)) 216 | 217 | 218 | if __name__ == "__main__": 219 | # unittest.main() 220 | suite = unittest.TestLoader().loadTestsFromTestCase(TestSingleRiffleCheck) 221 | for i in range(1000): 222 | print(i) 223 | unittest.TextTestRunner(verbosity=2).run(suite) 224 | -------------------------------------------------------------------------------- /8-balanced-binary-tree.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | ###################################################################### 4 | # this problem is from 5 | # https://www.interviewcake.com/question/python/balanced-binary-tree 6 | 7 | # Write a function to see if a binary tree is "superbalanced" (a new 8 | # tree property we just made up). 9 | 10 | # A tree is "superbalanced" if the difference between the depths of any 11 | # two leaf nodes is no greater than one. 12 | 13 | ###################################################################### 14 | 15 | # Now my turn 16 | 17 | import unittest 18 | 19 | # Here's a sample binary tree node class: 20 | 21 | 22 | class BinaryTreeNode: 23 | 24 | def __init__(self, value): 25 | self.value = value 26 | self.left = None 27 | self.right = None 28 | 29 | def insert_left(self, value): 30 | self.left = value 31 | return self.left 32 | 33 | def insert_right(self, value): 34 | self.right = value 35 | return self.right 36 | 37 | # ------------------------------ 38 | 39 | def __str__(self): 40 | s = "<" + str(self.value) + ", " + str(self.left) + \ 41 | ", " + str(self.right) + ">" 42 | return s 43 | 44 | # recursively walk the binary tree top down 45 | # note the depth at each descent 46 | # and keep a list of the various leaf node depths 47 | # as soon as imsuperbalance is discovered 48 | # return false 49 | def is_superbalanced(self, leaf_depths=None, depth=0): 50 | if leaf_depths is None: 51 | leaf_depths = [] 52 | depth = depth + 1 53 | 54 | # print("{:{width}}is_superbalanced: {} {} ({})" 55 | # .format(' ', depth, leaf_depths, self.value, width=depth)) 56 | 57 | # a leaf node has no children 58 | if self.left is None and self.right is None: 59 | 60 | # two ways we might now have an unbalanced tree: 61 | # 1) more than 2 different leaf depths 62 | # 2) Two leaf depths that are more than 1 apart 63 | 64 | if depth not in leaf_depths: 65 | # print("{} : {}".format(depth, self.value)) 66 | leaf_depths.append(depth) 67 | 68 | if len(leaf_depths) > 2: 69 | return False 70 | 71 | if (max(leaf_depths) - min(leaf_depths)) > 1: 72 | return False 73 | 74 | return True 75 | 76 | if self.left: 77 | check = self.left.is_superbalanced(leaf_depths, depth) 78 | if not check: 79 | return False 80 | 81 | if self.right: 82 | check = self.right.is_superbalanced(leaf_depths, depth) 83 | if not check: 84 | return False 85 | 86 | return True 87 | 88 | 89 | class TestBinaryTreeNodes(unittest.TestCase): 90 | 91 | def setUp(self): 92 | # [caseno, superbalanced, list_tree] 93 | self.cases = [ 94 | [0, True, [1]], 95 | [1, True, [1, None, None]], 96 | [2, True, [1, 2, None]], 97 | [3, True, [1, 2, 3]], 98 | [4, True, [1, [2, None, None], None]], 99 | [5, True, [1, 100 | ['2l', 101 | '3ll', 102 | '3lr'], 103 | None]], 104 | [6, True, [1, 105 | ['2l', ['3ll', '4lll', '4llr'], '3lr'], 106 | ['2r', ['3rl', '4rll', '4rlr'], '3rr']]], 107 | [7, True, [5, [10, 20, 25], [15, 30, 35]]], 108 | [8, False, [ 109 | 5, [10, 20, [25, 40, None]], [15, None, None]]], 110 | [9, False, [5, 111 | [10, None, None], 112 | [15, 113 | None, 114 | [20, 115 | [25, 116 | [30, 40]]]]]], 117 | [10, False, [5, 118 | 10, 119 | [15, None, [20, [25, [30, 40]]]]]], 120 | [11, True, [5, 121 | [10, [20, 40, 45], [25, 50, 55]], 122 | [15, [30, 60, 65], [35, 70, 75]]]], 123 | [12, True, [5, 124 | [10, 125 | [20, 126 | [40, 80, 90], 127 | [45, 95, 100]], 128 | [25, 129 | [50, 105, 110], 130 | [55, 115, 120]]], 131 | [15, 132 | [30, 133 | [60, 125, 130], 134 | [65, 135, 140]], 135 | [35, 136 | [70, 145, 150], 137 | [75, 155, 160]]]]], 138 | [13, False, [5, 139 | [10, 140 | [20, 141 | [40, 80, 90], 142 | [45, 95, 100]], 143 | 25], 144 | [15, 145 | [30, 146 | [60, 125, 130], 147 | [65, 135, 140]], 148 | [35, 149 | [70, 145, 150], 150 | [75, 155, 160]]]]], 151 | [14, False, [5, 152 | [10, 153 | [20, 154 | [40, 80], 155 | [45, 95, 100]], 156 | [25, 157 | [50], 158 | [55, 115, 120]]], 159 | [15, 160 | [30, 161 | [60, 125, [130, 165]], 162 | [65, 135, 140]], 163 | [35, 164 | [70, 145, 150], 165 | [75, 155, 160]]]]], 166 | [15, True, [5, 167 | [10, 168 | [20, 169 | [40, 80]]]]], 170 | ] 171 | print("setup: %s cases" % len(self.cases)) 172 | 173 | def test_4balance(self): 174 | for (caseno, designed_superbalanced, case) in self.cases: 175 | print("\ncase %2s: %s" % (caseno, case)) 176 | bt = list_tree_to_BinaryTree(case) 177 | computed_superbalanced = bt.is_superbalanced() 178 | by_design = "" 179 | if not designed_superbalanced and not computed_superbalanced: 180 | by_design = "(by design)" 181 | if designed_superbalanced and not computed_superbalanced: 182 | by_design = "!!! ERROR!" 183 | 184 | print("{:{width}}{}superbalanced {}" 185 | .format('', '' if computed_superbalanced else "not ", 186 | by_design, width=6)) 187 | 188 | if designed_superbalanced and not computed_superbalanced: 189 | self.fail() 190 | 191 | 192 | # Coming from lisp, I like to see trees implemented in pure list 193 | # structures, [1 [2 21 22] [3 31 32]] if only because they are easy to 194 | # visualize and to create as tests. I refer to such trees here as a 195 | # list_tree 196 | 197 | # however, interviewcake prefers a more java/c traditional 198 | # [node, ptr_left, ptr_right] structure so I have added a routine 199 | # that let me create testcases using lisplike trees and then 200 | # converting them to ptrful trees. 201 | 202 | # a helper fn to convert from my preferred lisp list_tree format 203 | # to nodey binary trees 204 | 205 | def list_tree_to_BinaryTree(list_tree): 206 | 207 | # this just helps with short hand "trees" 208 | # of forms 1, or [1] changing it into [1, None, None] 209 | # then truncating 210 | if type(list_tree) is not list: 211 | list_tree = [list_tree, None, None] 212 | list_tree = list_tree + [None, None, None] 213 | list_tree = list_tree[0:3] 214 | 215 | (value, left, right) = list_tree 216 | 217 | node = BinaryTreeNode(value) 218 | 219 | if left: 220 | node.insert_left(list_tree_to_BinaryTree(left)) 221 | 222 | if right: 223 | node.insert_right(list_tree_to_BinaryTree(right)) 224 | 225 | return node 226 | 227 | 228 | if __name__ == "__main__": 229 | unittest.main() 230 | -------------------------------------------------------------------------------- /16-cake-thief.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | from __future__ import print_function 4 | import unittest 5 | 6 | ###################################################################### 7 | # this problem is from 8 | # https://www.interviewcake.com/question/python/cake-thief 9 | # 10 | # 11 | # You are a renowned thief who has recently switched from stealing 12 | # precious metals to stealing cakes because of the insane profit 13 | # margins. You end up hitting the jackpot, breaking into the 14 | # largest privately owned stock of cakes -- the vault of the Queen of 15 | # England. 16 | # 17 | # While Queen Elizabeth has a limited number of types of cake, she has 18 | # an unlimited supply of each type. 19 | # 20 | # Each type of cake has a weight and a value, stored in a tuple with two 21 | # indices: 22 | # 23 | # 0. An integer representing the weight of the cake in kilograms 24 | # 1. An integer representing the monetary value of the cake in British pounds 25 | # 26 | # For example: 27 | # 28 | # # weighs 7 kilograms and has a value of 160 pounds 29 | # (7, 160) 30 | # 31 | # # weighs 3 kilograms and has a value of 90 pounds 32 | # (3, 90) 33 | # 34 | # You brought a duffel bag that can hold limited weight, and you want to 35 | # make off with the most valuable haul possible. 36 | # 37 | # Write a function max_duffel_bag_value() that takes a list of cake type 38 | # tuples and a weight capacity, and returns the maximum monetary value 39 | # the duffel bag can hold. 40 | # 41 | # For example: 42 | # 43 | # cake_tuples = [(7, 160), (3, 90), (2, 15)] 44 | # capacity = 20 45 | # 46 | # max_duffel_bag_value(cake_tuples, capacity) 47 | # # returns 555 (6 of the middle type of cake and 1 of the last type of cake) 48 | # 49 | # Weights and values may be any non-negative integer. Yes, it is weird 50 | # to think about cakes that weigh nothing or duffel bags that cannot 51 | # hold anything. But we are not just super mastermind criminals -- we are 52 | # also meticulous about keeping our algorithms flexible and 53 | # comprehensive. 54 | # 55 | ###################################################################### 56 | 57 | # Now my turn 58 | import sys 59 | 60 | 61 | # first try, define a "density" function of $cake / $pounds of cake 62 | # and try to fill the duffel based on these cake densities 63 | 64 | # this is the first strategy, and it's fast and close but suboptimal 65 | 66 | inf = float('inf') 67 | 68 | 69 | def dollar_density(cake): 70 | """density is value / capacity. If capacity is zero, density is inf""" 71 | weight, value = cake 72 | if value == 0: 73 | return 0 74 | elif weight == 0: 75 | return inf 76 | else: 77 | return cake[1] / float(cake[0]) 78 | 79 | 80 | def cake_compare(cake0, cake1): 81 | """compares two cakes by bucks per size""" 82 | density0 = dollar_density(cake0) 83 | density1 = dollar_density(cake1) 84 | if density0 < density1: 85 | return -1 86 | elif density0 == density1: 87 | return 0 88 | else: 89 | return 1 90 | 91 | 92 | def max_duffel_bag_value_density(cake_tuples, capacity): 93 | # sort the bag according to $ density 94 | # then add cakes to the duffel bag in order of decreasing "cake 95 | # dollar/capacity density" 96 | # interviewcake provides an example where this strategy fails 97 | # test_0oddsize shows this strategy fails because smaller cakes 98 | # might better fill the duffel bag with a higher value of cakes 99 | cake_inventory = sorted(cake_tuples, cake_compare, reverse=True) 100 | print(cake_inventory) 101 | 102 | total_value = 0 103 | remaining_space = capacity 104 | for cake in cake_inventory: 105 | weight, value = cake 106 | density = dollar_density(cake) 107 | if density == inf: 108 | return inf 109 | elif density > 0: 110 | ncake = remaining_space / weight 111 | if ncake > 0: 112 | print("%s %s %s" % (ncake, weight, value)) 113 | remaining_space -= ncake * weight 114 | total_value += ncake * value 115 | print("%s %s" % (total_value, remaining_space)) 116 | return total_value 117 | 118 | # so let's try this again 119 | 120 | # interviewcake suggests trying a greedy algorithm and building up 121 | # this is reasonable, but a bit contradictory as interviewcake originally 122 | # suggested that: 123 | 124 | # The brute force approach is to try every combination of cakes, but 125 | # that would take a really long time -- you'd surely be captured 126 | 127 | # using a greedy algorithm here is not the brutest brute force, but 128 | # it's fairly brute force as it does loop over various combinations 129 | # keeping track of maxium values 130 | 131 | # oh well, let's try a greedy approach 132 | 133 | 134 | def max_duffel_bag_value_greedy(cake_tuples, capacity): 135 | 136 | for weight, value in cake_tuples: 137 | if weight == 0 and value > 0: 138 | return float('inf') 139 | 140 | capacity_to_value_map = [0] * (capacity + 1) 141 | print("\ncake_tuples %s, capacity %s" % (cake_tuples, capacity)) 142 | 143 | for cap in range(0, capacity + 1): 144 | # print(" %s" % cap) 145 | possible_values = [0] 146 | for weight, value in cake_tuples: 147 | if weight <= cap: 148 | prev_cap = cap - weight 149 | prev_val = capacity_to_value_map[prev_cap] 150 | new_val = prev_val + value 151 | # print(" w %s v %s pc %s pv %s nv %s" % 152 | # (weight, value, prev_cap, prev_val, new_val)) 153 | possible_values.append(new_val) 154 | 155 | # print(" poss values %s" % possible_values) 156 | max_value = max(possible_values) 157 | if max_value == 0: 158 | if cap > 0: 159 | max_value = capacity_to_value_map[cap - 1] 160 | 161 | capacity_to_value_map[cap] = max_value 162 | 163 | # print(" capacity_to_value_map[%s] is %s" % 164 | # (cap, capacity_to_value_map[cap])) 165 | 166 | print(capacity_to_value_map[capacity]) 167 | return capacity_to_value_map[capacity] 168 | 169 | # hey it works! 170 | 171 | # and on further elucidation from interviewcake is 172 | # dynamic 173 | # bottom up 174 | # solution to the unbounded knapsack problem 175 | 176 | # Now let's test 177 | 178 | 179 | class TestDuffelPacker(unittest.TestCase): 180 | 181 | # # test "naive", "obvious", density packing 182 | 183 | # def test_00dollar_density_weightless_but_worthless(self): 184 | # self.assertEqual(dollar_density([0, 0]), 0) 185 | 186 | # def test_00dollar_density_weightless(self): 187 | # self.assertEqual(dollar_density([0, 1]), inf) 188 | 189 | # def test_01dollar_density_1typical(self): 190 | # self.assertEqual(dollar_density([5, 10]), 2.0) 191 | 192 | # def test_01dollar_density_2typical(self): 193 | # self.assertEqual(dollar_density([10, 5]), 0.5) 194 | 195 | # def test_0given_example0(self): 196 | # cake_tuples = [(7, 160), (3, 90), (2, 15)] 197 | # capacity = 20 198 | # self.assertEqual( 199 | # 555, max_duffel_bag_value_density(cake_tuples, capacity)) 200 | 201 | # def test_0given_example1(self): 202 | # cake_tuples = [(1, 30), (50, 200)] 203 | # capacity = 100 204 | # self.assertEqual( 205 | # 3000, max_duffel_bag_value_density(cake_tuples, capacity)) 206 | 207 | # def test_0given_example2(self): 208 | # cake_tuples = [(3, 40), (5, 70)] 209 | # capacity = 8 210 | # self.assertEqual( 211 | # 110, max_duffel_bag_value_density(cake_tuples, capacity)) 212 | 213 | # def test_0oddsize(self): 214 | # """this test shows where the naive, obvious solution fails""" 215 | # cake_tuples = [(3, 40), (5, 70)] 216 | # capacity = 9 217 | # self.assertNotEqual( 218 | # 120, max_duffel_bag_value_density(cake_tuples, capacity)) 219 | 220 | # test greedy 221 | 222 | def test_1_simple_example0(self): 223 | cake_tuples = [(1, 2), (3, 4)] 224 | capacity = 7 225 | self.assertEqual( 226 | 14, max_duffel_bag_value_greedy(cake_tuples, capacity)) 227 | 228 | def test_1given_example0(self): 229 | cake_tuples = [(7, 160), (3, 90), (2, 15)] 230 | capacity = 20 231 | self.assertEqual( 232 | 555, max_duffel_bag_value_greedy(cake_tuples, capacity)) 233 | 234 | def test_1given_example1(self): 235 | cake_tuples = [(1, 30), (50, 200)] 236 | capacity = 100 237 | self.assertEqual( 238 | 3000, max_duffel_bag_value_greedy(cake_tuples, capacity)) 239 | 240 | def test_1given_example2(self): 241 | cake_tuples = [(3, 40), (5, 70)] 242 | capacity = 8 243 | self.assertEqual( 244 | 110, max_duffel_bag_value_greedy(cake_tuples, capacity)) 245 | 246 | def test_1oddsize(self): 247 | cake_tuples = [(3, 40), (5, 70)] 248 | capacity = 9 249 | self.assertEqual( 250 | 120, max_duffel_bag_value_greedy(cake_tuples, capacity)) 251 | 252 | def test_1zerosizedduffed(self): 253 | """test a duffel that can hold nothing can hold nothing""" 254 | cake_tuples = [[0, 0], (1, 2), (3, 4)] 255 | capacity = 0 256 | self.assertEqual( 257 | 0, max_duffel_bag_value_greedy(cake_tuples, capacity)) 258 | 259 | def test_1valuelessweightlesscake(self): 260 | """test a valueless weightless cake is ignored""" 261 | cake_tuples = [[0, 0], (1, 2), (3, 4)] 262 | capacity = 7 263 | self.assertEqual( 264 | 14, max_duffel_bag_value_greedy(cake_tuples, capacity)) 265 | 266 | def test_2valueableweightlesscake(self): 267 | """test a valuable weightless cake returns inf""" 268 | cake_tuples = [[0, 5], (1, 2), (3, 4)] 269 | capacity = 7 270 | self.assertEqual( 271 | float('inf'), max_duffel_bag_value_greedy(cake_tuples, capacity)) 272 | 273 | 274 | if __name__ == "__main__": 275 | # unittest.main() 276 | suite = unittest.TestLoader().loadTestsFromTestCase(TestDuffelPacker) 277 | unittest.TextTestRunner(verbosity=2).run(suite) 278 | -------------------------------------------------------------------------------- /11-compress-url-list.py: -------------------------------------------------------------------------------- 1 | #!python 2 | 3 | ###################################################################### 4 | # this problem is from 5 | # https://www.interviewcake.com/question/python/compress-url-list? 6 | # 7 | # I'm making a search engine called MillionGazillion 8 | # 9 | # I wrote a crawler that visits web pages, stores a few keywords in a 10 | # database, and follows links to other web pages. I noticed that my 11 | # crawler was wasting a lot of time visiting the same pages over and 12 | # over, so I made a set, visited, where I'm storing URLs I've already 13 | # visited. Now the crawler only visits a URL if it hasn't already been 14 | # visited. 15 | # 16 | # Thing is, the crawler is running on my old desktop computer in my 17 | # parents' basement (where I totally don't live anymore), and it keeps 18 | # running out of memory because visited is getting so huge. 19 | # 20 | # How can I trim down the amount of space taken up by visited? 21 | ###################################################################### 22 | 23 | # So my naive quick and dirty take would be to hash it until it 24 | # becomes a problem then rethink. (Don't optimize too soon) 25 | 26 | # Then maybe compress the url ala bitly, using "readable" base 36 or base 27 | # 62 or unreadable base 256 (because this is for an internal db, 28 | # readability could be tossed) then hash it. 29 | 30 | # Maybe if it were a real problem try other known compression 31 | # algorithms, or heavens google for clues or speak to folks who have 32 | # experience. 33 | 34 | # Well, skipping ahead, interviewcake suggests storing this in a 35 | # 'trie', a data structure I know nothing of other than its name. 36 | 37 | # But I can google, and sometimes even understand what I google. 38 | 39 | # At any rate, interviewcake provides a sample implementation of a 40 | # trie using a large organized cluster of organized clusters of 41 | # dictionaries. 42 | 43 | ###################################################################### 44 | # Let's make visited a nested dictionary where each map has keys of just 45 | # one character. So we would store 'google.com' as 46 | # visited['g']['o']['o']['g']['l']['e']['.']['c']['o']['m']['*'] = True. 47 | # 48 | # The '*' at the end means 'this is the end of an entry'. Otherwise we 49 | # wouldn't know what parts of visited are real URLs and which parts are 50 | # just prefixes. In the example above, 'google.co' is a prefix that we 51 | # might think is a visited URL if we didn't have some way to mark 'this 52 | # is the end of an entry.' 53 | ###################################################################### 54 | 55 | # interviewake suggests this is not the be all and end all of trie 56 | # implementations, but it's what it's going with. 57 | 58 | # So I try it, and sure, it works pretty well, but wow, nested 59 | # directories seems to be a pretty convoluted way to store these urls 60 | # and I really have to wonder, how does that compare to my naive hash 61 | # table implementation? 62 | 63 | # And the answer seems to be, not well. MORE on that below, but the 64 | # take away is that while a trie structure may have many wonderful 65 | # uses, a trie structure made of hash tables is about 10x worse in 66 | # terms of space that just using a single hash table at least for 67 | # Python 2.7 68 | 69 | # what I took from this question: 70 | 71 | # + trie structure 72 | # + how to measure structure size in python 73 | # + a bit more sophistication using python unittest 74 | # + how to calculate the variance and std deviation of a stream of input 75 | 76 | 77 | # Conclusion: this question helps demonstrate the adage, don't 78 | # optimize prematurely, or in its strong form, "Premature optimization 79 | # is the root of all evil." -- Donald Knith. In this question, it's 80 | # pretty clear interviewcake optimized too soon. 81 | 82 | # so here we go 83 | 84 | from __future__ import print_function 85 | from sys import getsizeof, stderr 86 | from itertools import chain 87 | from collections import deque 88 | import unittest 89 | import random 90 | import string 91 | import math 92 | 93 | try: 94 | from reprlib import repr 95 | except ImportError: 96 | pass 97 | 98 | 99 | # 1. Interview Cake's Trie implementation: 100 | 101 | class Trie: 102 | 103 | def __init__(self): 104 | self.root_node = {} 105 | 106 | def check_present_and_add(self, word): 107 | 108 | current_node = self.root_node 109 | is_new_word = False 110 | 111 | # Work downwards through the trie, adding nodes 112 | # as needed, and keeping track of whether we add 113 | # any nodes. 114 | for char in word: 115 | if char not in current_node: 116 | is_new_word = True 117 | current_node[char] = {} 118 | current_node = current_node[char] 119 | 120 | # Explicitly mark the end of a word. 121 | # Otherwise, we might say a word is 122 | # present if it is a prefix of a different, 123 | # longer word that was added earlier. 124 | if "End Of Word" not in current_node: 125 | is_new_word = True 126 | current_node["End Of Word"] = {} 127 | 128 | return is_new_word 129 | 130 | # 15 lines. As usual I find Interviewcake's code elegant and simple, 131 | # and find I am yet again jealous of their coding talents. 132 | 133 | # Well, let's play with it, and test it. 134 | 135 | # First let's create a class to generate words. In the interviewcake 136 | # example, these words are urls, in which 137 | 138 | # 1. urls can form families, as in google.com, www.google.com, 139 | # www.google.com/about, etc. 140 | 141 | # 2. urls might be up to 2000 characters longer 142 | 143 | # to simulate urls, let's generate words of a random length. To 144 | # simulate url families, let's create one long random string, and then 145 | # allocate all the words out of that long random string. Url families 146 | # are when these strings start with the same index. 147 | 148 | 149 | class WordGenerator: 150 | 151 | def __init__(self, length=100000): 152 | self.charlen = length 153 | self.chars = string.ascii_lowercase + string.ascii_uppercase 154 | self.charlist = \ 155 | ''.join(random.choice(self.chars) for _ in range(length)) 156 | print("init: %s chars" % length) 157 | 158 | def gen(self, max_length=None): 159 | 160 | length = random.randrange(0, max_length) 161 | start = random.randint(0, self.charlen) 162 | end = start + length 163 | if end > self.charlen: 164 | end = self.charlen 165 | word = self.charlist[start:end] 166 | return word 167 | 168 | # Now let's test 169 | # Let's generate a large number of words 170 | # and store each word in a trie of hash tables and just one hash table 171 | # let's measure the storage required for the trie and for the hash table 172 | # and let's measure the mean and std. deviation of the words. 173 | 174 | # to measure the memory, we'll use a "recipe" from ActiveState 175 | # from https://code.activestate.com/recipes/577504/ 176 | # recursive version of getsizeof 177 | # 178 | 179 | 180 | class TestTrieVsHash(unittest.TestCase): 181 | 182 | def a_test(self, number_of_words=1000, max_word_size=20, text_size=10000): 183 | 184 | # Our Trie of hash tables 185 | self.t = Trie() 186 | 187 | # Our naive hash table 188 | self.h = {} 189 | 190 | sizes_t = [] 191 | sizes_h = [] 192 | 193 | sizes_t.append(total_size(self.t.root_node)) 194 | sizes_h.append(total_size(self.h)) 195 | 196 | self.words = WordGenerator(length=text_size) 197 | 198 | bucket_size = int(math.ceil(number_of_words / 10.0)) 199 | 200 | # https://en.wikipedia.org/wiki/Algorithms_for_calculating_variance#Online_algorithm 201 | n = 0 202 | mean = 0.0 203 | M2 = 0.0 204 | 205 | for i in range(number_of_words): 206 | 207 | # get a word 208 | word = self.words.gen(max_length=max_word_size) 209 | 210 | # store a word 211 | self.t.check_present_and_add(word) 212 | self.h[word] = True 213 | 214 | # keep a running sum of the variance of word length 215 | n += 1 216 | x = len(word) 217 | delta = x - mean 218 | mean += delta / n 219 | delta2 = x - mean 220 | M2 += delta * delta2 221 | 222 | # print("n %s len %s delta %s mean %s delta2 %s M2 %s" % 223 | # (n, x, delta, mean, delta2, M2)) 224 | 225 | if i % bucket_size == 0: 226 | sizes_t.append(total_size(self.t.root_node)) 227 | sizes_h.append(total_size(self.h)) 228 | 229 | # all done, print out a csv of the results 230 | print("\nwords, trie bytes, hash bytes") 231 | for i in range(len(sizes_t)): 232 | # print("%s words, trie %s bytes, hash %s" % 233 | # (i * bucket_size, sizes_t[i], sizes_h[i])) 234 | print("%s, %s, %s" % 235 | (i * bucket_size, sizes_t[i], sizes_h[i])) 236 | 237 | if n < 2: 238 | stddev = float('nan') 239 | else: 240 | variance = M2 / (n - 1) 241 | stddev = math.sqrt(variance) 242 | 243 | print("\n%s words, mean length = %s, std. deviation = %s" % 244 | (n, mean, stddev)) 245 | 246 | print("") 247 | 248 | def test_1small_words(self): 249 | self.a_test(max_word_size=20) 250 | 251 | def test_2big_words(self): 252 | self.a_test(max_word_size=200) 253 | 254 | def test_3small_corpus(self): 255 | self.a_test(text_size=1000, number_of_words=10000, max_word_size=20) 256 | 257 | def test_4lots_of_words(self): 258 | self.a_test(number_of_words=100000) 259 | 260 | # def test_0statistics(self): 261 | # self.a_test(text_size=100, number_of_words=2, max_word_size=20) 262 | # self.a_test(text_size=100, number_of_words=5, max_word_size=20) 263 | # self.a_test(text_size=100, number_of_words=10, max_word_size=20) 264 | # self.a_test(text_size=100, number_of_words=100, max_word_size=20) 265 | # self.a_test(text_size=1000, number_of_words=1000, max_word_size=20) 266 | # self.a_test(text_size=1000, number_of_words=1000, max_word_size=20) 267 | # self.a_test(text_size=1000, number_of_words=10000, max_word_size=20) 268 | 269 | # So each run of this script will produce different results, but one 270 | # such run looks like this: 271 | 272 | # $ py -2 11-compress-url-list.py 273 | # 274 | # words, trie bytes, hash bytes 275 | # 0, 140, 140 276 | # 1000, 31416, 387 277 | # 2000, 18017816, 174823 278 | # 3000, 35784028, 397537 279 | # 4000, 53562164, 548605 280 | # 5000, 71300648, 698388 281 | # 6000, 88432436, 845175 282 | # 7000, 105592728, 1289495 283 | # 8000, 122474404, 1437768 284 | # 9000, 139042184, 1584284 285 | # 10000, 156160852, 1733523 286 | # 287 | # 10000 words, mean length = 127.7944, std. deviation = 73.2895904276 288 | # . 289 | # -------------------------------------------------------------------- 290 | # Ran 1 test in 42.856s 291 | 292 | 293 | # from https://code.activestate.com/recipes/577504/ 294 | # recursive version of getsizeof 295 | 296 | def total_size(o, handlers={}, verbose=False): 297 | """ Returns the approximate memory footprint an object and all of its contents. 298 | 299 | Automatically finds the contents of the following builtin containers and 300 | their subclasses: tuple, list, deque, dict, set and frozenset. 301 | To search other containers, add handlers to iterate over their contents: 302 | 303 | handlers = {SomeContainerClass: iter, 304 | OtherContainerClass: OtherContainerClass.get_elements} 305 | 306 | """ 307 | dict_handler = lambda d: chain.from_iterable(d.items()) 308 | all_handlers = {tuple: iter, 309 | list: iter, 310 | deque: iter, 311 | dict: dict_handler, 312 | set: iter, 313 | frozenset: iter, 314 | } 315 | all_handlers.update(handlers) # user handlers take precedence 316 | seen = set() # track object id's already seen 317 | # estimate sizeof object without __sizeof__ 318 | default_size = getsizeof(0) 319 | 320 | def sizeof(o): 321 | if id(o) in seen: # do not double count the same object 322 | return 0 323 | seen.add(id(o)) 324 | s = getsizeof(o, default_size) 325 | 326 | if verbose: 327 | print(s, type(o), repr(o), file=stderr) 328 | 329 | for typ, handler in all_handlers.items(): 330 | if isinstance(o, typ): 331 | s += sum(map(sizeof, handler(o))) 332 | break 333 | return s 334 | 335 | return sizeof(o) 336 | 337 | 338 | if __name__ == "__main__": 339 | # unittest.main() 340 | suite = unittest.TestLoader().loadTestsFromTestCase(TestTrieVsHash) 341 | unittest.TextTestRunner(verbosity=2).run(suite) 342 | --------------------------------------------------------------------------------