├── 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. Apple Stocks
9 |
- 2. Product of All Other Numbers
10 |
- 3. Highest Product of 3
11 |
- 4. Merging Meeting Times
12 |
- 5. Making Change
13 |
- 6. Rectangular Love
14 |
- 7. Temperature Tracker
15 |
- 8. Balanced Binary Tree
16 |
- 9. Binary Search Tree Checker
17 |
- 10. 2nd Largest Item in a Binary Search Tree
18 |
- 11. MillionGazillion
19 |
- 12. Find in Ordered Set
20 |
- 13. Find Rotation Point
21 |
- 14. Inflight Entertainment
22 |
- 15. Compute nth Fibonacci Number
23 |
- 16. The Cake Thief
24 |
- 17. JavaScript Scope
25 |
- 18. What's Wrong with This JavaScript?
26 |
- 19. Queue Two Stacks
27 |
- 20. Largest Stack
28 |
- 21. The Stolen Breakfast Drone
29 |
- 22. Delete Node
30 |
- 23. Does This Linked List Have A Cycle?
31 |
- 24. Reverse A Linked List
32 |
- 25. Kth to Last Node in a Singly-Linked List
33 |
- 26. Reverse String in Place
34 |
- 27. Reverse Words
35 |
- 28. Parenthesis Matching
36 |
- 29. Bracket Validator
37 |
- 30. Permutation Palindrome
38 |
- 31. Recursive String Permutations
39 |
- 32. Top Scores
40 |
- 33. Which Appears Twice
41 |
- 34. Word Cloud Data
42 |
- 35. In-Place Shuffle
43 |
- 36. Single Riffle Shuffle
44 |
- 37. Simulate 5-sided die
45 |
- 38. Simulate 7-sided die
46 |
- 39. Two Egg Problem
47 |
- 40. Find Repeat, Space Edition
48 |
- 41. Find Repeat, Space Edition BEAST MODE
49 |
- 42. Find Duplicate Files
50 |
- 43. Merge Sorted Arrays
51 |
- 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 |
--------------------------------------------------------------------------------