diff options
Diffstat (limited to 'users/wpcarro/scratch')
245 files changed, 14710 insertions, 0 deletions
diff --git a/users/wpcarro/scratch/README.md b/users/wpcarro/scratch/README.md new file mode 100644 index 000000000000..8259ac70d9b2 --- /dev/null +++ b/users/wpcarro/scratch/README.md @@ -0,0 +1,6 @@ +# Scratch + +The purpose of the `scratch` directory is to host practice exercises. Practice +encompasses things like working on data structures and algorithms problems for +upcoming coding interviews or general aptitude as well as writing code snippets +to help me learn a new programming language or understand an unfamiliar concept. diff --git a/users/wpcarro/scratch/advent-of-code-2019/README.md b/users/wpcarro/scratch/advent-of-code-2019/README.md new file mode 100644 index 000000000000..e7c105a7f60f --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/README.md @@ -0,0 +1,4 @@ +# 2019 Advent of Code + +Here are my attempts at the 2019 Advent of Code challenge before my dedication +to the effort plummeted. diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_1.py b/users/wpcarro/scratch/advent-of-code-2019/day_1.py new file mode 100644 index 000000000000..bd4024e3ec7d --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_1.py @@ -0,0 +1,119 @@ +from math import floor + +xs = [ + 102473, + 84495, + 98490, + 68860, + 62204, + 72810, + 65185, + 145951, + 77892, + 108861, + 70764, + 67286, + 74002, + 80773, + 52442, + 131505, + 107162, + 126993, + 59784, + 64231, + 91564, + 68585, + 98735, + 69020, + 77332, + 60445, + 65826, + 111506, + 95431, + 146687, + 135119, + 86804, + 95915, + 85434, + 111303, + 148127, + 132921, + 136213, + 89004, + 143137, + 144853, + 143017, + 104386, + 100612, + 54760, + 63813, + 144191, + 84481, + 69718, + 84936, + 98621, + 124993, + 92736, + 60369, + 137284, + 101902, + 112726, + 51784, + 126496, + 85005, + 101661, + 137278, + 136637, + 90340, + 100209, + 53683, + 50222, + 132060, + 98797, + 139054, + 135638, + 100632, + 137849, + 125333, + 103981, + 76954, + 134352, + 74229, + 93402, + 62552, + 50286, + 57066, + 98439, + 120708, + 117827, + 107884, + 72837, + 148663, + 125645, + 61460, + 120555, + 142473, + 106668, + 58612, + 58576, + 143366, + 90058, + 121087, + 89546, + 126161, +] + + +def fuel_for_mass(x): + """Return the amount of fuel (in mass) required for a mass of X. The total + amount of fuel includes the amount of fuel required for the fuel itself, + since fuel also has a mass weights.""" + mass_fuel = floor(x / 3) - 2 + if mass_fuel < 0: + return 0 + else: + fuel_fuel = fuel_for_mass(mass_fuel) + return mass_fuel + fuel_fuel + + +print(sum(fuel_for_mass(x) for x in xs)) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_2.py b/users/wpcarro/scratch/advent-of-code-2019/day_2.py new file mode 100644 index 000000000000..77774c1bb5ad --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_2.py @@ -0,0 +1,32 @@ +from itertools import product + +x = [ + 1, 0, 0, 3, 1, 1, 2, 3, 1, 3, 4, 3, 1, 5, 0, 3, 2, 1, 10, 19, 1, 6, 19, 23, + 2, 23, 6, 27, 2, 6, 27, 31, 2, 13, 31, 35, 1, 10, 35, 39, 2, 39, 13, 43, 1, + 43, 13, 47, 1, 6, 47, 51, 1, 10, 51, 55, 2, 55, 6, 59, 1, 5, 59, 63, 2, 9, + 63, 67, 1, 6, 67, 71, 2, 9, 71, 75, 1, 6, 75, 79, 2, 79, 13, 83, 1, 83, 10, + 87, 1, 13, 87, 91, 1, 91, 10, 95, 2, 9, 95, 99, 1, 5, 99, 103, 2, 10, 103, + 107, 1, 107, 2, 111, 1, 111, 5, 0, 99, 2, 14, 0, 0 +] + + +def interpret(i, x): + op, a, b, out = x[i + 0], x[i + 1], x[i + 2], x[i + 3] + if op == 1: + x[out] = x[a] + x[b] + return interpret(i + 4, x) + elif op == 2: + x[out] = x[a] * x[b] + return interpret(i + 4, x) + elif op == 99: + return x + else: + raise Exception('Unsupported opcode: {}. {}, {}'.format(op, a, b)) + + +for a, b in product(range(100), range(100)): + y = x[:] + y[1] = a + y[2] = b + if interpret(0, y)[0] == 19690720: + print(100 * a + b) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_3.py b/users/wpcarro/scratch/advent-of-code-2019/day_3.py new file mode 100644 index 000000000000..6dd863528c1c --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_3.py @@ -0,0 +1,137 @@ +from math import floor +from heapq import heappush, heappop + +xs = [ + "R1009", "U993", "L383", "D725", "R163", "D312", "R339", "U650", "R558", + "U384", "R329", "D61", "L172", "D555", "R160", "D972", "L550", "D801", + "L965", "U818", "L123", "D530", "R176", "D353", "L25", "U694", "L339", + "U600", "L681", "D37", "R149", "D742", "R762", "U869", "R826", "U300", + "L949", "U978", "L303", "U361", "R136", "D343", "L909", "U551", "R745", + "U913", "L566", "D292", "R820", "U886", "R205", "D431", "L93", "D71", + "R577", "U872", "L705", "U510", "L698", "U963", "R607", "U527", "L669", + "D543", "R690", "U954", "L929", "D218", "R490", "U500", "L589", "D332", + "R949", "D538", "R696", "U659", "L188", "U468", "L939", "U833", "L445", + "D430", "R78", "D303", "R130", "D649", "R849", "D712", "L511", "U745", + "R51", "U973", "R799", "U829", "R605", "D771", "L837", "U204", "L414", + "D427", "R538", "U116", "R540", "D168", "R493", "U900", "L679", "U431", + "L521", "D500", "L428", "U332", "L954", "U717", "L853", "D339", "L88", + "U807", "L607", "D496", "L163", "U468", "L25", "U267", "L759", "D898", + "L591", "U445", "L469", "U531", "R596", "D486", "L728", "D677", "R350", + "D429", "R39", "U568", "R92", "D875", "L835", "D841", "R877", "U178", + "L221", "U88", "R592", "U692", "R455", "U693", "L419", "U90", "R609", + "U672", "L293", "U168", "R175", "D456", "R319", "D570", "R504", "D165", + "L232", "D624", "L604", "D68", "R807", "D59", "R320", "D281", "L371", + "U956", "L788", "D897", "L231", "D829", "R287", "D798", "L443", "U194", + "R513", "D925", "L232", "U225", "L919", "U563", "R448", "D889", "R661", + "U852", "L950", "D558", "L269", "U186", "L625", "U673", "L995", "U732", + "R435", "U849", "L413", "D690", "L158", "D234", "R361", "D458", "L271", + "U90", "L781", "U754", "R256", "U162", "L842", "U927", "L144", "D62", + "R928", "D238", "R473", "U97", "L745", "U303", "L487", "D349", "L520", + "D31", "L825", "U385", "L133", "D948", "L39", "U62", "R801", "D664", + "L333", "U134", "R692", "U385", "L658", "U202", "L279", "D374", "R489", + "D686", "L182", "U222", "R733", "U177", "R94", "D603", "L376", "U901", + "R216", "D851", "L155", "D214", "L460", "U758", "R121", "D746", "L180", + "U175", "L943", "U146", "L166", "D251", "L238", "U168", "L642", "D341", + "R281", "U182", "R539", "D416", "R553", "D67", "L748", "U272", "R257", + "D869", "L340", "U180", "R791", "U138", "L755", "D976", "R731", "U713", + "R602", "D284", "L258", "U176", "R509", "U46", "R935", "U576", "R96", + "U89", "L913", "U703", "R833" +] +ys = [ + "L1006", "D998", "R94", "D841", "R911", "D381", "R532", "U836", "L299", + "U237", "R781", "D597", "L399", "D800", "L775", "D405", "L485", "U636", + "R589", "D942", "L878", "D779", "L751", "U711", "L973", "U410", "L151", + "U15", "L685", "U417", "L106", "D648", "L105", "D461", "R448", "D743", + "L589", "D430", "R883", "U37", "R155", "U350", "L421", "U23", "R337", + "U816", "R384", "D671", "R615", "D410", "L910", "U914", "L579", "U385", + "R916", "U13", "R268", "D519", "R289", "U410", "L389", "D885", "L894", + "U734", "L474", "U707", "L72", "U155", "L237", "U760", "L127", "U806", + "L15", "U381", "L557", "D727", "L569", "U320", "L985", "D452", "L8", + "D884", "R356", "U732", "L672", "D458", "L485", "U402", "L238", "D30", + "R644", "U125", "R753", "U183", "L773", "U487", "R849", "U210", "L164", + "D808", "L595", "D668", "L340", "U785", "R313", "D72", "L76", "D263", + "R689", "U604", "R471", "U688", "R462", "D915", "R106", "D335", "R869", + "U499", "R190", "D916", "R468", "D882", "R56", "D858", "L143", "D741", + "L386", "U856", "R50", "U853", "R151", "D114", "L773", "U854", "L290", + "D344", "L23", "U796", "L531", "D932", "R314", "U960", "R643", "D303", + "L661", "D493", "L82", "D491", "L722", "U848", "L686", "U4", "L985", + "D509", "L135", "D452", "R500", "U105", "L326", "D101", "R222", "D944", + "L645", "D362", "L628", "U305", "L965", "U356", "L358", "D137", "R787", + "U728", "R967", "U404", "R18", "D928", "L695", "D965", "R281", "D597", + "L791", "U731", "R746", "U163", "L780", "U41", "L255", "U81", "L530", + "D964", "R921", "D297", "R475", "U663", "L226", "U623", "L984", "U943", + "L143", "U201", "R926", "U572", "R343", "U839", "R764", "U751", "R128", + "U939", "R987", "D108", "R474", "U599", "R412", "D248", "R125", "U797", + "L91", "D761", "L840", "U290", "L281", "U779", "R650", "D797", "R185", + "D320", "L25", "U378", "L696", "U332", "R75", "D620", "L213", "D667", + "R558", "U267", "L846", "U306", "R939", "D220", "R311", "U827", "R345", + "U534", "R56", "D679", "R48", "D845", "R898", "U8", "R862", "D960", "R753", + "U319", "L886", "D795", "R805", "D265", "R876", "U729", "R894", "D368", + "R858", "U744", "R506", "D327", "L903", "U919", "L721", "U507", "L463", + "U753", "R775", "D719", "R315", "U128", "R17", "D376", "R999", "D386", + "L259", "U181", "L162", "U605", "L265", "D430", "R35", "D968", "R207", + "U466", "R796", "D667", "R93", "U749", "L315", "D410", "R312", "U929", + "L923", "U260", "R638" +] + + +def to_coords(xs): + row, col = 0, 0 + coords = [] + for x in xs: + d, amt = x[0], int(x[1:]) + if d == 'U': + for i in range(1, amt + 1): + coords.append((row + i, col)) + row += amt + elif d == 'D': + for i in range(1, amt + 1): + coords.append((row - i, col)) + row -= amt + elif d == 'L': + for i in range(1, amt + 1): + coords.append((row, col - i)) + col -= amt + elif d == 'R': + for i in range(1, amt + 1): + coords.append((row, col + i)) + col += i + return coords + + +def contains(row, col, d): + if row not in d: + return False + return col in d[row] + + +def intersections(xs, ys): + d = {} + ints = set() + for row, col in to_coords(xs): + if row in d: + d[row].add(col) + else: + d[row] = {col} + for row, col in to_coords(ys): + if contains(row, col, d): + ints.add((row, col)) + return ints + + +def trace_to(coord, xs): + count = 0 + for coord_x in to_coords(xs): + count += 1 + if coord_x == coord: + return count + raise Exception("Intersection doesn't exist") + + +answer = [] +for coord in intersections(xs, ys): + x = trace_to(coord, xs) + y = trace_to(coord, ys) + heappush(answer, x + y) + +print(heappop(answer)) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_4.py b/users/wpcarro/scratch/advent-of-code-2019/day_4.py new file mode 100644 index 000000000000..adef73b452dc --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_4.py @@ -0,0 +1,35 @@ +import re + +start = 134792 +end = 675810 + + +def satisfies(x): + x = str(x) + result = False + double, not_decreasing = False, False + + # double and *only* double exists + for i in range(len(x) - 1): + # double and left-of-a is BOL or !x + # and right-of-b is EOL or !x + a, b = x[i], x[i + 1] + bol = i - 1 < 0 + eol = i + 2 >= len(x) + if a == b and (bol or x[i - 1] != a) and (eol or x[i + 2] != a): + double = True + break + + # not_decreasing + prev = int(x[0]) + for a in x[1:]: + a = int(a) + if prev > a: + return False + prev = a + not_decreasing = True + + return double and not_decreasing + + +print(len([x for x in range(start, end + 1) if satisfies(x)])) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_5.py b/users/wpcarro/scratch/advent-of-code-2019/day_5.py new file mode 100644 index 000000000000..3d82846e6126 --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_5.py @@ -0,0 +1,170 @@ +x = [ + 3, 225, 1, 225, 6, 6, 1100, 1, 238, 225, 104, 0, 1102, 31, 68, 225, 1001, + 13, 87, 224, 1001, 224, -118, 224, 4, 224, 102, 8, 223, 223, 1001, 224, 7, + 224, 1, 223, 224, 223, 1, 174, 110, 224, 1001, 224, -46, 224, 4, 224, 102, + 8, 223, 223, 101, 2, 224, 224, 1, 223, 224, 223, 1101, 13, 60, 224, 101, + -73, 224, 224, 4, 224, 102, 8, 223, 223, 101, 6, 224, 224, 1, 224, 223, + 223, 1101, 87, 72, 225, 101, 47, 84, 224, 101, -119, 224, 224, 4, 224, + 1002, 223, 8, 223, 1001, 224, 6, 224, 1, 223, 224, 223, 1101, 76, 31, 225, + 1102, 60, 43, 225, 1102, 45, 31, 225, 1102, 63, 9, 225, 2, 170, 122, 224, + 1001, 224, -486, 224, 4, 224, 102, 8, 223, 223, 101, 2, 224, 224, 1, 223, + 224, 223, 1102, 29, 17, 224, 101, -493, 224, 224, 4, 224, 102, 8, 223, 223, + 101, 1, 224, 224, 1, 223, 224, 223, 1102, 52, 54, 225, 1102, 27, 15, 225, + 102, 26, 113, 224, 1001, 224, -1560, 224, 4, 224, 102, 8, 223, 223, 101, 7, + 224, 224, 1, 223, 224, 223, 1002, 117, 81, 224, 101, -3645, 224, 224, 4, + 224, 1002, 223, 8, 223, 101, 6, 224, 224, 1, 223, 224, 223, 4, 223, 99, 0, + 0, 0, 677, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1105, 0, 99999, 1105, 227, 247, + 1105, 1, 99999, 1005, 227, 99999, 1005, 0, 256, 1105, 1, 99999, 1106, 227, + 99999, 1106, 0, 265, 1105, 1, 99999, 1006, 0, 99999, 1006, 227, 274, 1105, + 1, 99999, 1105, 1, 280, 1105, 1, 99999, 1, 225, 225, 225, 1101, 294, 0, 0, + 105, 1, 0, 1105, 1, 99999, 1106, 0, 300, 1105, 1, 99999, 1, 225, 225, 225, + 1101, 314, 0, 0, 106, 0, 0, 1105, 1, 99999, 8, 226, 677, 224, 102, 2, 223, + 223, 1005, 224, 329, 1001, 223, 1, 223, 1108, 677, 226, 224, 102, 2, 223, + 223, 1006, 224, 344, 101, 1, 223, 223, 108, 677, 226, 224, 102, 2, 223, + 223, 1006, 224, 359, 101, 1, 223, 223, 7, 677, 226, 224, 102, 2, 223, 223, + 1005, 224, 374, 101, 1, 223, 223, 1007, 226, 677, 224, 102, 2, 223, 223, + 1005, 224, 389, 101, 1, 223, 223, 8, 677, 677, 224, 102, 2, 223, 223, 1006, + 224, 404, 1001, 223, 1, 223, 1007, 677, 677, 224, 1002, 223, 2, 223, 1006, + 224, 419, 101, 1, 223, 223, 1108, 677, 677, 224, 1002, 223, 2, 223, 1005, + 224, 434, 1001, 223, 1, 223, 1107, 226, 677, 224, 102, 2, 223, 223, 1005, + 224, 449, 101, 1, 223, 223, 107, 226, 226, 224, 102, 2, 223, 223, 1006, + 224, 464, 101, 1, 223, 223, 1108, 226, 677, 224, 1002, 223, 2, 223, 1005, + 224, 479, 1001, 223, 1, 223, 7, 677, 677, 224, 102, 2, 223, 223, 1006, 224, + 494, 1001, 223, 1, 223, 1107, 677, 226, 224, 102, 2, 223, 223, 1005, 224, + 509, 101, 1, 223, 223, 107, 677, 677, 224, 1002, 223, 2, 223, 1006, 224, + 524, 101, 1, 223, 223, 1008, 677, 677, 224, 1002, 223, 2, 223, 1006, 224, + 539, 101, 1, 223, 223, 7, 226, 677, 224, 1002, 223, 2, 223, 1005, 224, 554, + 101, 1, 223, 223, 108, 226, 226, 224, 1002, 223, 2, 223, 1006, 224, 569, + 101, 1, 223, 223, 1008, 226, 677, 224, 102, 2, 223, 223, 1005, 224, 584, + 101, 1, 223, 223, 8, 677, 226, 224, 1002, 223, 2, 223, 1005, 224, 599, 101, + 1, 223, 223, 1007, 226, 226, 224, 1002, 223, 2, 223, 1005, 224, 614, 101, + 1, 223, 223, 1107, 226, 226, 224, 1002, 223, 2, 223, 1006, 224, 629, 101, + 1, 223, 223, 107, 677, 226, 224, 1002, 223, 2, 223, 1005, 224, 644, 1001, + 223, 1, 223, 1008, 226, 226, 224, 1002, 223, 2, 223, 1006, 224, 659, 101, + 1, 223, 223, 108, 677, 677, 224, 1002, 223, 2, 223, 1005, 224, 674, 1001, + 223, 1, 223, 4, 223, 99, 226 +] + +# Interpretter spec: +# Op-code width: 2 +# ABCDE +# A: Mode of 3rd parameter +# B: Mode of 2rd parameter +# C: Mode of 1st parameter +# DE: 2-digit op-code +# +# Not every op-code has the same arity. +# +# Parameter modes: +# - positional: index of memory. 0 +# - immediate: raw value. 1 +# Assert that you never attempt to write to an "immediate value" + +# Parameter modes +POS = '0' # positional parameter mode +VAL = '1' # immediate parameter mode + + +# Pasted from day-2.py +# interpretter :: Int -> [Int] -> [Int] -> IO () +def interpret(i, x, argv=[], outs=[]): + """Values in `argv` will be applied to any `input` fields.""" + # The widest op-code we'll see is 3 + 2 = 5 for either addition or + # multiplication since each of those is a 3-arity function with a two-digit + # op-code. + instruction = '{:05d}'.format(x[i]) + op = instruction[-2:] + + if op == '01': + a, b, out = x[i + 1], x[i + 2], x[i + 3] + mode_a, mode_b, mode_out = instruction[2], instruction[1], instruction[ + 0] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + assert mode_out == POS + x[out] = a + b + return interpret(i + 4, x, argv=argv, outs=outs) + elif op == '02': + a, b, out = x[i + 1], x[i + 2], x[i + 3] + mode_a, mode_b, mode_out = instruction[2], instruction[1], instruction[ + 0] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + assert mode_out == POS + x[out] = a * b + return interpret(i + 4, x, argv=argv, outs=outs) + # input + elif op == '03': + a = x[i + 1] + mode_a = instruction[2] + assert mode_a == POS + # What's the pythonic way to defensively get this value? + if len(argv) and argv[0] is not None: + x[a] = argv[0] + return interpret(i + 2, x, argv=argv[1:], outs=outs) + elif len(outs) and outs[-1] is not None: + x[a] = outs[-1] + return interpret(i + 2, x, argv=argv, outs=outs) + else: + # Here we want to block until the user applies input. This could be + # done easily with message passing for something similar. + x[a] = int(input('Enter: ')) + return interpret(i + 2, x, argv=argv) + # output + elif op == '04': + a = x[i + 1] + mode_a = instruction[2] + a = a if mode_a == VAL else x[a] + outs.append(a) + return interpret(i + 2, x, argv=argv, outs=outs) + # jump-if-true + elif op == '05': + a, b = x[i + 1], x[i + 2] + mode_a, mode_b = instruction[2], instruction[1] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + if a != 0: + return interpret(b, x, argv=argv, outs=outs) + else: + return interpret(i + 3, x, argv=argv, outs=outs) + # jump-if-false + elif op == '06': + a, b = x[i + 1], x[i + 2] + mode_a, mode_b = instruction[2], instruction[1] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + if a == 0: + return interpret(b, x, argv=argv, outs=outs) + else: + return interpret(i + 3, x, argv=argv, outs=outs) + pass + # less than + elif op == '07': + a, b, out = x[i + 1], x[i + 2], x[i + 3] + mode_a, mode_b, mode_out = instruction[2], instruction[1], instruction[ + 0] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + assert mode_out == POS + if a < b: + x[out] = 1 + else: + x[out] = 0 + return interpret(i + 4, x, argv=argv, outs=outs) + # equals + elif op == '08': + a, b, out = x[i + 1], x[i + 2], x[i + 3] + mode_a, mode_b, mode_out = instruction[2], instruction[1], instruction[ + 0] + a = a if mode_a == VAL else x[a] + b = b if mode_b == VAL else x[b] + assert mode_out == POS + if a == b: + x[out] = 1 + else: + x[out] = 0 + return interpret(i + 4, x, argv=argv, outs=outs) + elif op == '99': + return x[0] + else: + raise Exception('Unsupported opcode: {}.'.format(op)) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_6.py b/users/wpcarro/scratch/advent-of-code-2019/day_6.py new file mode 100644 index 000000000000..aba99b8239ff --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_6.py @@ -0,0 +1,155 @@ +from graphviz import Digraph + +data = """6WF)DRK 2PT)PSM H42)FN8 1XR)LQD HRK)9KL TD6)H8W 98Z)BJM RCQ)LVG +RWQ)Q7H 2PS)X94 NHB)25X PXC)W57 L8L)MVX CFK)D8K R1B)43T PDY)QKX FQK)82K JJ6)MQJ +FB6)6V1 R28)5MZ BN2)5HN 6BQ)JVC W57)22C MQJ)DL2 MTC)84R RH8)CRN Y27)3GN CKQ)31C +R7V)9BK ZDY)PDY X2Q)Y6S Q8B)SAN 1Z3)PVT R87)57R KCJ)44X PWQ)9CB HLC)VYW HFP)9XS +X33)MC3 RYS)R7R JRF)VHW 79R)FXZ YQQ)STV 8J6)JWX Q6D)RV6 LL9)B4D 6R1)T1Z VK9)42M +PQP)17N K6C)HMK GLY)N47 KDW)CDC DQ4)RY5 SND)FDR 7YF)1VN MDT)B3S D3F)98Z 5VH)MR7 +KNR)2L8 CJW)QDL FWY)14X SJD)79R COM)BXW T2B)FPB B2Q)BRJ Z21)HYC VHW)5XR WZ4)2JM +8HF)342 PYR)X9Y RKF)P43 S1S)9WT 2PB)BSB QF7)M9T HML)HMC 7J9)7Q6 8F1)29K DH1)NDM +1YC)PXC P32)HR7 PMX)7Y9 STV)SLW NYY)NF1 TG9)998 DMB)DLW XGL)1Z3 GK8)WCS YHR)HQC +9Q5)B6D R2T)CM5 6KC)J5G ZM9)L8L J8T)F89 3LN)YOU T2T)Z8F SCY)FKG 9W4)195 QLM)DD7 +4QY)JCB WKM)3JF 693)YM8 61M)B6Y DSP)X2M YZ5)DPL BC9)3B1 BDB)JTG 3TJ)TW1 W5M)SF6 +K4Q)X56 5HT)YHX YJG)DM5 68N)X2Q 2YP)DS5 BLK)MY3 6WV)VZ4 2JQ)ZT8 G93)V2W WN1)SBD +SS7)DY9 X56)8HP JY1)VS4 XQ6)L94 98Z)DMC V6S)NWT D9L)Y44 V6G)GVS JDW)FZW FJT)S38 +L2Z)VPL 7ZX)DKK X2M)8WM YVZ)XWS HMK)P87 47M)TD6 TDZ)21T 19R)95B GD9)Q1L 9QX)DFR +Y64)XGN CRG)6VY V3L)61D RJ4)C9Z XXG)P53 VJ8)QTF CPQ)2M9 JRN)8V1 KMH)K94 DLW)VQ4 +91W)2QQ G4B)RWQ 4P1)MKS K6G)DZ7 WCS)JR9 LXM)7RY 6ZB)K6G HMC)622 Z21)BLK Q6N)48V +66S)MK4 PDK)6WV Y6S)GY1 2L8)ZMG 42W)ZN6 6MS)8TZ JBY)STQ NSF)3ZM 5CV)X9N K4V)WFL +J6R)DT8 N3N)CX4 PTD)YXT F74)4T5 C51)3FW KRW)DS1 NWT)CKQ 195)6G6 HVQ)S18 Q7H)BKM +SKN)4D4 GK2)MLX MVX)TG9 YPK)RHQ Y9F)Z8W 42M)WNL 84R)6JP KNC)NHF FZW)PGM 3FW)HGX +DBK)FB6 45T)HLT L11)JVN HB5)K6C QH5)888 BTJ)J55 8BT)8ZS FR1)XGL S87)PS9 C4K)BN2 +N2Q)18C KTF)ZM9 TN2)B2Q DF3)CFK 9T3)TMR P29)3P1 P1W)7SQ 4D4)1DJ LML)ZJ3 Q4L)RKF +MW2)79T LVG)CPQ BDC)JH5 DNZ)232 998)GTM YGS)4WH GY1)C51 J55)QBT B8Z)34W FJ2)H42 +58J)326 T1Z)DCJ 1ZH)GLV 1YC)JG6 14K)22B RY5)QRY 7V2)2WT 4GQ)XHV ZJ3)TQ8 2G8)SN3 +FPB)HMN SC4)57D 5LQ)R2T LXM)R8Z JQ6)G4B WNL)GK2 42M)P75 LM3)YPK ZN6)753 PN4)835 +C4H)JY1 LR4)VD5 PSM)P1W VWL)C6C G2V)WBC 85M)R24 B1V)QW7 175)2PM Y1V)1ZH 34W)3MJ +WN7)TTB 3PV)CQD N7Y)9T3 223)8D4 RV6)LJ9 HFP)JRF VMT)DNB GJP)D3F J5G)KMS 7Q6)ZW2 +YCB)JBY XGN)MNL 888)DSP X61)Q6N WT5)X12 SDN)FD1 2QC)54W V98)964 T7S)YVZ MLX)9VZ +FR8)QH5 TVQ)2PS 2PV)FHY F4S)MPT 3J9)JNB J6M)GDC Q4C)MJN 9VZ)BZK P2P)B69 WBC)M1W +D97)HPF JKB)9L4 593)6YJ RMB)4Q5 QZB)38C H12)6R1 MKY)DDD HGX)CRG P53)WY7 22B)GMM +44X)2D8 DT8)L7H 3Y2)D3S FB8)68N 3BC)1XR 4XF)TVQ VPL)R7V Z4V)JSK B3S)FW5 49Z)YQQ +99V)D13 54Q)SS7 CYC)TXH PQ3)78W X4M)G9H WFL)M99 ZYY)3Y2 12Y)PSW W38)P29 H8W)JJ6 +P66)VPH GK2)45T H5F)FJT JDJ)SNV 14F)96Q JG6)TQ4 2L6)52Q SCY)CBJ 3GN)KNC KLM)XPR +DH1)QZB DMB)X7G DPL)7SX D97)N3N GNS)T95 53P)GW2 BHR)HNB YHX)XQV 2CR)Y1V C9D)Z7P +FN8)2PT 6LF)FCQ JNL)LQR SPV)YCB HGX)N83 VS4)8BT 5RH)FTX HYC)X2J 69V)J6S 9XS)PN4 +SD7)5Q3 2RN)82D QRY)FFY K2Y)3X2 79Z)S2Z YN2)Y64 JKB)MDT KJ8)NDH N57)5VH 3XK)1Q1 +SCH)FJ6 17N)GMP QR4)7V2 GLV)GLY NHF)ZDY QDL)S14 QF1)BMC ZLF)DHN 3JF)7TR MKS)GCY +964)91R 9L4)L5G RRX)6ZB CD7)73M 3X2)PGC HNB)S9Z L94)KLM 8MQ)SCR 18C)3TJ M4Y)BTJ +BC9)5YR TV5)SCY 2NX)8CC C9Z)MTC B69)3QP HR7)CHJ 8ZS)JRN 31C)TJW D43)4NH 93Q)X9X +T95)DNZ LQ5)BC9 9T5)S2C RP8)DH1 GCY)SD7 Y44)9B5 VG5)ZYY 7RY)V3L PWV)Q4L NF1)7YF +DRK)Y8V D13)GYG TW1)2PB ZVZ)2VV BRJ)V2V 9CB)Y7B MK4)9CJ TMR)6XS HWF)GK8 QTF)S1S +DFW)6LF N3S)WN1 N2Q)MSW CZ5)X61 FXZ)C4H SCQ)MF7 9LY)3LN 5MZ)PMX CN9)WF9 FHY)PR8 +S38)NWH M29)G5S 4NH)GZJ 5YR)54H CLX)MNY TJD)HQL RRZ)4GQ YHB)CZ5 P37)93Q YJG)3Q3 +95B)QMF CMQ)BLZ QD9)45M JSK)R28 YCW)CLX 8K3)JGB N8M)PQW P75)1HL XBS)T2T 22C)PVW +689)6MS FFY)RWX YHL)2G8 Y8V)4P1 Y7B)62Z YKJ)JDJ 1HL)5LQ PZ3)B1C 52Q)7HB 3Q2)ZV7 +YBF)Z4V J95)SDH NM6)YBF 8YN)J3M J6S)KNR PVT)N4X SDH)RFW RFW)7Y1 JCB)52B 3MJ)H58 +4QF)XHZ F62)DFW 7LJ)KDW JHL)C9D B4D)Q8B 342)YGS PFR)ZQT Z9K)TNS 8F8)WLB 94N)DMB +QBT)RYS 3VR)KRR 8D4)ST6 X9N)2PV 632)8K3 MX5)XNP 57D)Y27 18D)PQP D3F)RJ4 PLS)PBL +1JP)YDC 79V)BG2 S14)2NX 4Q5)NCQ FTX)555 2PM)KMH HQC)RMB 9Z9)BNZ XHV)Y94 7ZP)YHR +BNZ)49Z W6D)LX6 SLS)JL3 PVW)P9W Z1L)HB5 DS5)G2V Z9Q)RV8 DFR)LPJ 836)693 K94)VWL +HRG)836 J3V)593 52N)LPK 9KL)Y7M LX6)F7D JL3)511 L4G)D97 1RH)Y9F NJ2)LML GW2)9WV +8KZ)NRC XQV)G6D R8Z)QF7 326)HML R7R)8PM 622)YCW WQY)LGS NF1)FF3 5LQ)QF1 5XR)PTD +V2V)PFR 9T5)JQ6 CBQ)8KZ VZ4)HVQ TJW)DQT 9WT)5M6 CFK)YHL JR9)1JP Y1K)CF4 8WS)JPY +VYC)1D6 GKK)7J9 JTG)RRX 6V1)F74 1H5)QR4 SN3)NMG MF7)GQ1 RYK)SCH BNZ)9LY 1DJ)9LP +L6W)5BK FCQ)BFL DCJ)3RD MXD)8MQ RWX)1RH NBF)WKM K6C)WNH H58)L6W Y7B)BJH PGC)NBF +96Q)Q2W F7D)BSN 223)Z9K K94)VYC X9X)7M3 Q1M)3J9 QXF)XQ6 DD7)3Q2 Q1L)NHB 79T)LXQ +8TZ)M29 21T)Q4C B1C)NSF 8D8)FJ2 LJH)HGJ QS2)PS1 5KX)Z2L C6C)6BQ VQ2)2YP P87)N8M +ST5)L4G 8SP)W5M T4H)69V 9WF)GHS FF3)SND C5G)GKK VQ2)X4M P43)8J6 TD6)384 66V)CN9 +CX4)T9T NCQ)2JQ 29K)K8K RY5)K4Q GQ3)T4H FNH)P32 3BC)PRQ 5HN)4QY M1W)BGT 84R)ST5 +S45)CJW CK4)W7G SGX)19R S2C)7ZX DHN)W5Y 8D9)HM2 BSB)SPV D8K)DFV JHL)2L6 KYP)12Y +KDN)6X7 Y44)SQZ 6G6)SJD N7D)QGF Q84)8WJ F89)LL9 LYJ)2RN 25X)Q84 HM3)53P JNB)QD9 +SLW)1DQ 384)3BC PR8)NGV 49N)7ZP 65H)LHJ 6XS)S45 ZMG)FR1 X2M)Y86 QD3)QLM P4R)PQ3 +RTK)4M3 4YW)N7D R7V)M4M 73M)CBF DFV)64R Z7P)LMK HRG)Y1K 3ZM)BCZ WY7)QXP DMC)9Q5 +PSW)1H5 8CC)TV5 TTB)S88 BZK)K2Y T2B)CBQ HJB)Y19 DQW)KML Z8W)8ZL PBL)5TK 1D6)MX5 +3MJ)4YW MDT)HJB 62Z)X33 DZ7)BDC 9CJ)FRD 82D)KDN LK7)18D 9QQ)61M Y34)DZG J4T)6KC +971)QD3 511)GQ3 MJN)F62 RNM)NKG BGW)KJ8 DL2)1YH ZQT)RYZ 1YH)ZJ6 2WT)YYQ 7HB)DYQ +3BN)WQY 2M9)62D TSK)YR1 N7Y)VJ8 WZ4)FWT MNY)YN2 DYQ)RRZ 3RG)YT3 2SM)VK9 JH5)ZXH +GYG)K2M PKF)V6G JGB)S87 X94)N57 MSW)L2Z X4N)25G BLZ)4QF JPY)GD9 WLB)V6S KML)2SM +TXH)9X1 48V)KTR 8PM)WZ4 ZW2)967 PS9)3BN 4WH)9T5 8M1)R6V N7M)VWK S88)978 N4X)8KH +6VY)PLS NRC)874 QGF)QWJ NMG)J3V B8Z)WPF 45M)2QC KDW)VQ2 FZW)223 BXW)QXF FRD)PWV +8HP)4G7 KDN)YYL LHJ)SDN P6P)XMC W5Y)RYK HX8)KW3 Z2L)H12 WPF)T2B L7H)BGW MNL)17B +GHS)66V QKX)XWV FW5)W38 PDK)Y34 FKG)Q6D DQT)YJG 15G)79V 4VK)51Y BJH)LR4 48V)6GC +DM5)Y1F CM5)VG5 KB8)HRK 5HN)RCQ 6JP)SDQ LGH)NJ2 L94)N7Y 4Y2)ZLF 25G)C4K K8K)SLS +232)ZVZ GQ1)58J RV8)H5F 78W)565 YCF)8D9 DZG)99V N83)CKR TN2)ZCX NGV)8SP BSN)FTN +LPJ)94N 3Q3)Q1M JVX)971 54W)LGH 67Y)P66 R24)P37 3QP)QTY YHR)FLT GMP)NM6 NDH)632 +PWV)8D8 LMK)3PV ZWJ)KB8 967)4VK 3B1)WN7 XWS)5CV YR1)FNH 565)4PH 5BK)V98 W5Y)FR8 +PS1)HX8 38C)XXG XWV)1YC M4M)LQ5 S9Z)49N XMC)R1B YYL)VC9 GMM)SCQ LXQ)J95 51Y)RP8 +HLT)XBS 82K)B8Z NR5)7K3 K2M)67Y SF6)W6D CF4)85M MC3)LXM HMN)RNM BFL)4XF MT2)PM4 +VWK)JKB 3JF)ZTZ QWJ)9QQ KRR)TJD VYW)Z9Q CK4)QS2 8NQ)NR5 57R)BHR 8WM)YHB Y86)GNS +2Y2)Z21 X12)9QX LJ9)YKJ 3RD)8F1 7SQ)CK4 ZXH)3XK DDD)5KX ZCX)PYR GZJ)KXL KC5)52N +PM4)RYP 14X)ZWJ FJ6)175 17B)689 HQL)14F LQR)DBK LGS)4Y2 2QQ)SGR 2VV)8F8 J6S)LM3 +RTP)YZ5 XDD)14K VQ4)MT2 KMH)KYC CKR)RTP VD5)MRM CM5)KRW BG3)XDD PGM)J4T MY3)JVX +Z8F)WNP BKM)WT5 FLT)KTF N7D)8M1 Y19)CMQ HPF)WDL 65H)JJP 2MQ)66S 4Q5)54Q Q2W)ZL4 +QTY)659 MRM)9Z9 X2J)SC4 YWH)RB3 FTN)LYJ LMK)N7M SGX)15G KW3)FQK 3VV)JNL JWX)R8R +9Z3)9MB BMC)N3S W7G)Z1L SD7)MW2 376)RH8 NWT)JHL 7CD)N2Z KTR)HM3 1Q1)TDZ DY9)2CR +6YJ)14G FWT)JDW C2S)C5G SNV)J6M 5TK)YWH J3M)8HF HM2)GJP P9W)7CD 1VN)SGX KMS)RBK +64R)B1V 62D)3VV 61D)F4S XPR)SKN FJT)N3P 9WV)D43 TQ8)BDB 46H)K4V 8WJ)MXD NDM)9WF +8ZL)1QJ SCR)2MQ 7Y9)LJH VPH)MKY YDC)PDK 4G7)65H 2JM)NYY T9T)VMT 8M1)TSK G5S)X4N +6FH)KYP D98)DQW G6D)C2S 6X7)N2Q 1QJ)T7S ZL4)J8T 5BT)3VR 835)KCJ YM8)3RG Y7M)PWQ +54W)9W4 CBF)7LJ 4T5)8WS RHQ)HBK CQD)D98 HGJ)J6R JVC)79Z FD1)PKF VC9)5BT C4H)6WF +D3S)P6P MR7)BG3 R6V)DF3 9X1)NQ5 ZTZ)2Y2 8WM)HFP CDC)376 TQ4)M4Y 9MB)N1R HBK)DQ4 +1DQ)CYC WNP)DM8 CBJ)LK7 ZT8)FWY LQD)PNN 555)9Z3 TNS)D9L QMF)L11 FR8)5RH WF9)R87 +NKG)5HT L5G)91W N2Z)YV9 9B5)CD7 ZV7)8NQ ST6)74T ZJ6)CQV S18)47M 74T)8YN WNH)TN2 +874)46H 3VV)PZ3 Y1F)42W MPT)2LP FDR)HWF X7G)RTK 52B)P4R RYP)G93 NWH)YCF 7TR)FB8 +RWQ)6FH 8F8)HLC CRN)P2P B6D)KC5 PNN)HRG""".split() + +# COM is the root in this tree + + +# parent :: Vertex -> [Edge] -> Maybe(Vertex) +def parent(x, xs): + for a, b in xs: + if b == x: + return a + return None + + +# parents :: Vertex -> [Edge] -> [Vertex] +def parents(x, xs): + parents = [] + p = parent(x, xs) + while p: + parents.append(p) + p = parent(p, xs) + return parents + + +# alias Vertex :: String +# alias Edge :: (String, String) +# to_edge_list :: [String] -> [(String, String)] +def to_edge_list(xs): + """Returns a list of tuples where (A, B) represents a directed edge from + vertex A to vertex B.""" + return [(x[0:3], x[4:]) for x in xs] + + +# to_graphviz :: [Edge] -> String +def to_graphviz(xs): + d = Digraph() + for a, b in xs: + d.node(a, label=a) + d.edge(a, b) + return d.source + + +graph = to_edge_list(data) +you = parents('YOU', graph) +san = parents('SAN', graph) + +# Distance from YOU to shared point with SAN +yd = 1 +for i in range(len(you)): + if you[i] in san: + break + yd += 1 + +# Distance from SAN to shared point with YOU +sd = 1 +for i in range(len(san)): + if san[i] in you: + break + sd += 1 + +print('Number of orbital transfers required: {}'.format(yd - 1 + sd - 1)) diff --git a/users/wpcarro/scratch/advent-of-code-2019/day_7.py b/users/wpcarro/scratch/advent-of-code-2019/day_7.py new file mode 100644 index 000000000000..14597d5104e3 --- /dev/null +++ b/users/wpcarro/scratch/advent-of-code-2019/day_7.py @@ -0,0 +1,49 @@ +from day_5 import interpret +from itertools import permutations + +# TODO: I may need to re-write this in Elixir modelling each amplifier as a +# `Process` and `Process.send`ing each amplifier the signals. + +data = [ + 3, 8, 1001, 8, 10, 8, 105, 1, 0, 0, 21, 38, 59, 76, 89, 106, 187, 268, 349, + 430, 99999, 3, 9, 1002, 9, 3, 9, 101, 2, 9, 9, 1002, 9, 4, 9, 4, 9, 99, 3, + 9, 1001, 9, 5, 9, 1002, 9, 5, 9, 1001, 9, 2, 9, 1002, 9, 3, 9, 4, 9, 99, 3, + 9, 1001, 9, 4, 9, 102, 4, 9, 9, 1001, 9, 3, 9, 4, 9, 99, 3, 9, 101, 4, 9, + 9, 1002, 9, 5, 9, 4, 9, 99, 3, 9, 1002, 9, 3, 9, 101, 5, 9, 9, 1002, 9, 3, + 9, 4, 9, 99, 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, + 1002, 9, 2, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, 9, + 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 101, 1, 9, 9, 4, 9, 3, 9, 1001, 9, 1, 9, 4, + 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 99, 3, 9, 1002, 9, + 2, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 101, + 1, 9, 9, 4, 9, 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, + 101, 2, 9, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 102, 2, 9, 9, 4, 9, 3, + 9, 1001, 9, 2, 9, 4, 9, 99, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 1001, 9, 2, 9, + 4, 9, 3, 9, 101, 1, 9, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 101, 2, 9, + 9, 4, 9, 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, 3, 9, 102, 2, + 9, 9, 4, 9, 3, 9, 1001, 9, 1, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, 99, 3, 9, + 1001, 9, 2, 9, 4, 9, 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, + 3, 9, 102, 2, 9, 9, 4, 9, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, + 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 101, 1, 9, + 9, 4, 9, 3, 9, 101, 1, 9, 9, 4, 9, 99, 3, 9, 101, 2, 9, 9, 4, 9, 3, 9, 102, + 2, 9, 9, 4, 9, 3, 9, 1002, 9, 2, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, 3, 9, + 1001, 9, 2, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, 3, 9, 1001, 9, 1, 9, 4, 9, + 3, 9, 1001, 9, 2, 9, 4, 9, 3, 9, 1001, 9, 2, 9, 4, 9, 3, 9, 102, 2, 9, 9, + 4, 9, 99 +] + +data_a, data_b, data_c, data_d, data_e = data[:], data[:], data[:], data[:], data[:] + +# m = 0 +# for a, b, c, d, e in permutations(range(5, 10)): +# answer = None +# z = 0 +# while z is not None: +# print(a, b, c, d, e) +# print('---') +# v = interpret(0, data_a, argv=[a, z]) +# print(v) +# w = interpret(0, data_b, argv=[b, v]) +# x = interpret(0, data_c, argv=[c, w]) +# y = interpret(0, data_d, argv=[d, x]) +# z = interpret(0, data_e, argv=[e, y]) +# m = max(m, z) diff --git a/users/wpcarro/scratch/blockchain/default.nix b/users/wpcarro/scratch/blockchain/default.nix new file mode 100644 index 000000000000..c02f9a9c8108 --- /dev/null +++ b/users/wpcarro/scratch/blockchain/default.nix @@ -0,0 +1,14 @@ +{ pkgs, ... }: + +let + pypkgs = pkgs.python3Packages; +in +pkgs.python3Packages.buildPythonApplication { + pname = "main"; + src = ./.; + version = "0.0.1"; + propagatedBuildInputs = with pypkgs; [ + flask + requests + ]; +} diff --git a/users/wpcarro/scratch/blockchain/main.py b/users/wpcarro/scratch/blockchain/main.py new file mode 100644 index 000000000000..e7b627613389 --- /dev/null +++ b/users/wpcarro/scratch/blockchain/main.py @@ -0,0 +1,263 @@ +from flask import Flask, jsonify, request +from hashlib import sha256 +from datetime import datetime +from urllib.parse import urlparse + +import json +import requests +import uuid + +################################################################################ +# Helper Functions +################################################################################ + +def hash(x): + return sha256(x).hexdigest() + +def is_pow_valid(guess, prev_proof): + """ + Return true if the hash of `guess` + `prev_proof` has 4x leading zeros. + """ + return hash(str(guess + prev_proof).encode("utf8"))[:4] == "0000" + +################################################################################ +# Classes +################################################################################ + +class Node(object): + def __init__(self, host="0.0.0.0", port=8000): + self.app = Flask(__name__) + self.define_api() + self.identifier = str(uuid.uuid4()) + self.blockchain = Blockchain() + self.neighbors = set() + + def add_neighbors(self, urls=None): + for url in urls: + parsed = urlparse(url) + if not parsed.netloc: + raise ValueError("Must pass valid URLs for neighbors") + self.neighbors.add(parsed.netloc) + + def decode_chain(chain_json): + return Blockchain( + blocks=[ + Block( + index=block["index"], + ts=block["ts"], + transactions=[ + Transaction( + origin=tx["origin"], + target=tx["target"], + amount=tx["amount"]) + for tx in block["ts"] + ], + proof=block["proof"], + prev_hash=block["prev_hash"]) + for block in chain_json["blocks"] + ], + transactions=[ + Transaction( + origin=tx["origin"], + target=tx["target"], + amount=tx["amount"]) + for tx in chain_json["transactions"] + ]) + + def resolve_conflicts(self): + auth_chain, auth_length = self.blockchain, len(self.blockchain) + + for neighbor in self.neighbors: + res = requests.get(f"http://{neighbor}/chain") + if res.status_code == 200 and res.json()["length"] > auth_length: + decoded_chain = decode_chain(res.json()["chain"]) + if Blockchain.is_valid(decoded_chain): + auth_length = res.json()["length"] + auth_chain = decoded_chain + + self.blockchain = auth_chain + + def define_api(self): + def msg(x): + return jsonify({"message": x}) + + ############################################################################ + # / + ############################################################################ + + @self.app.route("/healthz", methods={"GET"}) + def healthz(): + return "ok" + + @self.app.route("/reset", methods={"GET"}) + def reset(): + self.blockchain = Blockchain() + return msg("Success") + + @self.app.route("/mine", methods={"GET"}) + def mine(): + # calculate POW + proof = self.blockchain.prove_work() + + # reward miner + self.blockchain.add_transaction( + origin="0", # zero signifies that this is a newly minted coin + target=self.identifier, + amount=1) + + # publish new block + self.blockchain.add_block(proof=proof) + return msg("Success") + + ############################################################################ + # /transactions + ############################################################################ + + @self.app.route("/transactions/new", methods={"POST"}) + def new_transaction(): + payload = request.get_json() + + self.blockchain.add_transaction( + origin=payload["origin"], + target=payload["target"], + amount=payload["amount"]) + return msg("Success") + + ############################################################################ + # /blocks + ############################################################################ + + @self.app.route("/chain", methods={"GET"}) + def view_blocks(): + return jsonify({ + "length": len(self.blockchain), + "chain": self.blockchain.dictify(), + }) + + ############################################################################ + # /nodes + ############################################################################ + @self.app.route("/node/neighbors", methods={"GET"}) + def view_neighbors(): + return jsonify({"neighbors": list(self.neighbors)}) + + @self.app.route("/node/register", methods={"POST"}) + def register_nodes(): + payload = request.get_json()["neighbors"] + payload = set(payload) if payload else set() + self.add_neighbors(payload) + return msg("Success") + + @self.app.route("/node/resolve", methods={"GET"}) + def resolve_nodes(): + self.resolve_conflicts() + return msg("Success") + + def run(self): + self.app.run(host="0.0.0.0", port=8000) + + +class Blockchain(object): + def __init__(self, blocks=None, transactions=None): + self.blocks = blocks or [] + self.transactions = transactions or [] + self.add_block() + + def __len__(self): + return len(self.blocks) + + def __iter__(self): + for block in self.blocks: + yield block + + def prove_work(self): + guess, prev_proof = 0, self.blocks[-1].proof or 0 + while not is_pow_valid(guess, prev_proof): + guess += 1 + return guess + + def add_block(self, prev_hash=None, proof=None): + b = Block( + index=len(self), + transactions=self.transactions, + prev_hash=self.blocks[-1].hash() if self.blocks else None, + proof=proof) + self.blocks.append(b) + return b + + def adopt_blocks(self, json_blocks): + pass + + def add_transaction(self, origin=None, target=None, amount=None): + tx = Transaction(origin=origin, target=target, amount=amount) + self.transactions.append(tx) + + @staticmethod + def is_valid(chain): + prev_block = next(chain) + + for block in chain: + if block.prev_hash != prev_block.hash() or not is_pow_valid(prev_block.proof, block.proof): + return False + prev_block = block + + return True + + def dictify(self): + return { + "blocks": [block.dictify() for block in self.blocks], + "transactions": [tx.dictify() for tx in self.transactions], + } + + +class Block(object): + def __init__(self, index=None, ts=None, transactions=None, proof=None, prev_hash=None): + self.index = index + self.ts = ts or str(datetime.now()) + self.transactions = transactions + self.proof = proof + self.prev_hash = prev_hash + + def hash(self): + return sha256(self.jsonify().encode()).hexdigest() + + def dictify(self): + return { + "index": self.index, + "ts": self.ts, + "transactions": [tx.dictify() for tx in self.transactions], + "proof": self.proof, + "prev_hash": self.prev_hash, + } + + def jsonify(self): + return json.dumps(self.dictify(), sort_keys=True) + +class Transaction(object): + def __init__(self, origin=None, target=None, amount=None): + if None in {origin, target, amount}: + raise ValueError("To create a Transaction, you must provide origin, target, and amount") + + self.origin = origin + self.target = target + self.amount = amount + + def dictify(self): + return { + "origin": self.origin, + "target": self.target, + "amount": self.amount, + } + + def jsonify(self): + return json.dumps(self.dictify(), sort_keys=True) + +################################################################################ +# Main +################################################################################ + +def run(): + Node(host="0.0.0.0", port=8000).run() + +if __name__ == "__main__": + run() diff --git a/users/wpcarro/scratch/blockchain/setup.py b/users/wpcarro/scratch/blockchain/setup.py new file mode 100644 index 000000000000..e5310565dbbd --- /dev/null +++ b/users/wpcarro/scratch/blockchain/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup + +setup( + name='main', + version='0.0.1', + py_modules=['main'], + entry_points={ + 'console_scripts': ['main = main:run'] + }, +) diff --git a/users/wpcarro/scratch/crack_the_coding_interview/11_1.py b/users/wpcarro/scratch/crack_the_coding_interview/11_1.py new file mode 100644 index 000000000000..ec7b65dae0c3 --- /dev/null +++ b/users/wpcarro/scratch/crack_the_coding_interview/11_1.py @@ -0,0 +1,40 @@ +# Implementation for a problem from "Crack the Coding Interview". +# +# Dependencies: +# - python 2.7.16 +# - entr 4.1 +# +# To run the tests, run: `python 11_1.py` +# For a tight development loop, run: `echo 11_1.py | entr python /_` +# +# Author: William Carroll <wpcarro@gmail.com> + +################################################################################ +# Implementation +################################################################################ +def insert_sorted(xs, ys): + """ + Merges `ys` into `xs` and ensures that the result is sorted. + + Assumptions: + - `xs` and `ys` are both sorted. + - `xs` has enough unused space to accommodate each element in `ys`. + """ + for y in ys: + xi = xs.index(None) - 1 + yi = xs.index(None) + xs[yi] = y + while xi != -1 and y < xs[xi]: + xs[xi], xs[yi] = xs[yi], xs[xi] + xi, yi = xi - 1, yi - 1 + return xs + +################################################################################ +# Tests +################################################################################ +assert insert_sorted([1, 3, 5, None, None], [2, 4]) == [1, 2, 3, 4, 5] +assert insert_sorted([None, None], [2, 4]) == [2, 4] +assert insert_sorted([None, None], [2, 4]) == [2, 4] +assert insert_sorted([1, 1, None, None], [0, 0]) == [0, 0, 1, 1] +assert insert_sorted([1, 1, None, None], [1, 1]) == [1, 1, 1, 1] +print('All tests pass!') diff --git a/users/wpcarro/scratch/crack_the_coding_interview/to_tree.hs b/users/wpcarro/scratch/crack_the_coding_interview/to_tree.hs new file mode 100644 index 000000000000..8496d88c0c0c --- /dev/null +++ b/users/wpcarro/scratch/crack_the_coding_interview/to_tree.hs @@ -0,0 +1,11 @@ +data Tree a = Node a [Tree a] deriving (Show) + +withRoot :: [a] -> [Tree a] +withRoot xs = xs |> toThing |> fmap buildTree + +buildTree :: (a, [a]) + + +toTree :: [a] -> Tree a +toTree [x] = Node x [] +toTree [x | xs] = Node x (toTree xs) diff --git a/users/wpcarro/scratch/cryptopals/.gitignore b/users/wpcarro/scratch/cryptopals/.gitignore new file mode 100644 index 000000000000..7aa03e126b87 --- /dev/null +++ b/users/wpcarro/scratch/cryptopals/.gitignore @@ -0,0 +1 @@ +alice.txt \ No newline at end of file diff --git a/users/wpcarro/scratch/cryptopals/README.md b/users/wpcarro/scratch/cryptopals/README.md new file mode 100644 index 000000000000..f4f5719f9f6b --- /dev/null +++ b/users/wpcarro/scratch/cryptopals/README.md @@ -0,0 +1,3 @@ +# cryptopals + +My solutions for some of the questions at https://cryptopals.com. diff --git a/users/wpcarro/scratch/cryptopals/set1/4.txt b/users/wpcarro/scratch/cryptopals/set1/4.txt new file mode 100644 index 000000000000..d172b6cff724 --- /dev/null +++ b/users/wpcarro/scratch/cryptopals/set1/4.txt @@ -0,0 +1,327 @@ +0e3647e8592d35514a081243582536ed3de6734059001e3f535ce6271032 +334b041de124f73c18011a50e608097ac308ecee501337ec3e100854201d +40e127f51c10031d0133590b1e490f3514e05a54143d08222c2a4071e351 +45440b171d5c1b21342e021c3a0eee7373215c4024f0eb733cf006e2040c +22015e420b07ef21164d5935e82338452f42282c1836e42536284c450de3 +043b452e0268e7eb005a080b360f0642e6e342005217ef04a42f3e43113d +581e0829214202063d70030845e5301f5a5212ed0818e22f120b211b171b +ea0b342957394717132307133f143a1357e9ed1f5023034147465c052616 +0c300b355c2051373a051851ee154a023723414c023a08171e1b4f17595e +550c3e13e80246320b0bec09362542243be42d1d5d060e203e1a0c66ef48 +e159464a582a6a0c50471310084f6b1703221d2e7a54502b2b205c433afa +ec58ea200e3005090e1725005739eda7342aed311001383fff7c58ef1f11 +01305424231c0d2c41f105057f74510d335440332f1038ec17275f5814e1 +05f12f380720ea2b19e24a07e53c142128354e2827f25a08fb401c3126a6 +0d17272f53063954163d050a541b1f1144305ae37d4932431b1f33140b1b +0b4f070f071fe92c200e1fa05e4b272e50201b5d493110e429482c100730 +100a3148080f227fe60a132f0c10174fe3f63d1a5d38eb414ca8e82f2b05 +0a19e83c58400a023b13234572e6e4272bf67434331631e63b5e0f00175c +54520c2ceb45530e0f78111d0b0707e01e4bf43b0606073854324421e6f9 +09e7585353ee4a34190de1354e481c373a1b2b0a136127383e271212191f +0f060d09fb4f2d5024022c5ff6463c390c2b5f1a5532071a31f33503fcea +371d39121605584f48217235ee1e0602445c162e4942254c071954321d29 +4a0900e63e5f161e15554045f3594c2a6a77e4e52711602beaf53ae53bed +29011616565d2a372a605bee39eced31183fe068185c3b445b391fe53232 +e4102337000303452a1e2f2b29493f54ed5a037b3e08311b625cfd005009 +2d560d4b0618203249312a310d5f541f295c3f0f25235c2b20037d1600f3 +2c245155e8253708391a7ceb0d05005c3e080f3f0f0e5a16583b111f4448 +493804044d262eec3759594f212d562420105d6a39e70a0f3957f347070c +e72d1d1f103807590f4339575e00381074485d2d580249f744052605e11d +e131570ae95307143a71131729552d001057a4540a1f425b190b572dee34 +2c1655342f02581c202b0a5c17a358291e1506f325550f05365e165c1c5f +e318164df80b043e5406296e5359271d152f552e155a43eda81f23231d1c +001de0413e174e18192c061e4b3d1b5626f90e3e1429544a20ee150d0c20 +32e902193219033c58191302441a5c1b584825ea140c290927aaea53e23c +3a36363a732e32ea3f0e430508204b332c382a19292d5b291122e123446a +1804115614031f5f571f2b143c5d3c1b257a4b37350f18445a3e08341c3d +21f2fb250b2e55151e77253a3f0e5f4b2030370a4155e720e73914e35a4a +510a55583a3c491221397c123a2b14a8305b3b09e71b241d0e51202e1a32 +1b51202f4917232b512a141d6812f03c455df05e5a1c2cee14390b3b593a +5f5731e5203116ee131a4a4b24112cef5d0822f035e6547d3a0014462f26 +0028fb522104f771501a555d3f581e30e9ec3e49e3e63123432f07794145 +1459f6312f000e5a1373e346e40f211e1b0b0e17000f391f170552150500 +7e301e18325717e3412e022f087be30e5641080151357714e0e0eee15e11 +533258e9360f513b083aa51d2824222f40200a470537ecec392d31070b38 +07e32c180dfa56496a461627542115132a4c284050495b23e2245b093159 +2d3c230a1e5a300f6c3e26ed0d1709434950fd6f1e121335054129e4e4ec +ef22fa2112311b11584ce43434f46f521a215433f9514fe33d313a3e0838 +34e7f336270c08010f2f544f0f1c1e235c0222644c2632efec061de2115f +121a42395d4c560d213b0c0a26a7e4f4382718153d5e511158a10b2c021e +e05d414dfa40222f0c382a03235f4d0d04372d4b7855105e26e44f2e0555 +7f3a4f1351f85b0344223e1177e14707190c0e311f4ca633f5f3e9352372 +01424d5d1a322a0d381717130e181d07240c2c19ecee750b1a37085d014c +16012c5de55a0314a8260e2759e439123ca0c81c321d454e4e0ee14f4c1d +0b1415512f38580e4e2a227def242643183c224f0ea146443403022fe9fd +43eb2b1078322a02192d5b5e0c360d584d0b5e2c13072912ee32f03f4155 +002a52553e08361b0be0074b573e201c164c093a5c0f0159333b59770d5b +38e63c1c5244301a5a01f26930321256143e1ae05e1120a9eaf20a192d58 +7d54140a152ef4035f09083ded531ee04df55848020656a1342e502649eb +0c211dfe101702015516341136252f3f06f73247133113f5642d083a3417 +015e3d51433f3c003e5e28030b1d413eee186824504b241e0f0d32373e2b +2d465040ec130c5c0e2704aa17010c40095207223669110f22f45ea155f7 +14552e2b341e5ce0195351066a23e3283e0ee935444b255a1c5c3cef7614 +372b453d5a357c05142be65b3c17f92d2b134853390a312bf92a531b513d +5658265f4c0ce4440a20322f591a413034292b312206a01be6453a512d21 +1c585c19f31f785324f8583d1ee02620342b10a236263f105011ee5b0e14 +0f522b550818591a752e5fea0e033322ee5e280a4a1b244f5a2b35341255 +39093c1ced331b264127173f1312e2455fa33b31012c1f4d073c553f5d5e +18f82d5d07e2430b3b3c1b5b49effb0313173f5d4a2e5c134555ff6b1d1a +550a20234202726341190311295254f4064205aa515ae0145a23071c4e18 +3f2047024e3ce4555a1b39fa145455012c3afb0f2d11134846182e3c575b +e3e456571937762828065443153b51152e262f09c937024405284f236432 +012f580c3536ec5c021574541d5c41123a4e661d5f0f5f344a083e3a5e4c +4216252d01eb0a2a4623621b48360d312c29f33e380650447617124b3e71 +54141e59323606390204e95f1206520e5c084510034d30171c5e744f335d +1e30061401600b342e171059526d1949431a3f412f56594c183711ea4837 +3131254f11e76f550e1e4d26f1391f44363b151c31281ff45259351da0e6 +5def250d0f3505385f22e9f4112633005d272d092e0138275851f943e90e +0939165718303b445210095c16390cf04f19450e06f4545c0a0c320e3e23 +1e0b0b1f573f3d0fe05d43090fa8482242300819313142325b1f4b19365b +0d3b2a5d271e463d2203765245065d5d684a051e5815265b52f3171d3004 +6af423303817a43324394af15a5c482e3b16f5a46f1e0b5c1201214b5fe4 +4030544f3f51151e436e04203a5e3b287ee303490a43fb3b28042f36504e +1a2d5a03fc0e2c04384046242e2b5e1548101825eb2f285f1a210f022141 +122355e90122281deeed3ba05636003826525d5551572d07030d4935201f +2a3c484a15410d3b16375d4665271b5c4ce7ee37083d3e512b45204f17f6 +03222801255c2c211a7aeb1e042b4e38e8f1293143203139fb202c325f2b +06542a28041956350e292bf3fe5c32133a2a171b3a3e4e4e3101381529e3 +4a5209ef24e5f3225e503b143d0e5747323fe7ee3d5b1b5110395619e65a +1fee0a3945563d2b5703701817584b5f5b54702522f5031b561929ea2d1e +e7271935100e3c31211b23113a3a5524e02241181a251d521ff52f3c5a76 +144a0efee02f0f5f1d353a1c112e1909234f032953ec591e0a58e55d2cf4 +efee0cf00d0955500210015311467543544708eb590d113d30443d080c1e +1a562c1f7e2b0030094f051c03e30f4d501a0fe22a2817edfc5e470c3843 +1c3df1135321a8e9241a5607f8305d571aa546001e3254555a11511924 +eb1d3f54ec0fea341a097c502ff1111524e24f5b553e49e8576b5b0e1e33 +72413e2f5329e332ec563b5e65185efefd2c3b4e5f0b5133246d214a401d +352a0ae632183d200a162e5346110552131514e0553e51003e220d47424b +1d005c58135f3c1b53300c3b49263928f55625454f3be259361ded1f0834 +2d2457524a1e1204255934174d442a1a7d130f350a123c4a075f5be73e30 +0c0518582d131f39575925e0231833370c482b270e183810415d5aec1900 +453b181df1572735380b0446097f00111f1425070b2e1958102ceb592928 +010a4a2d0b0926082d2f1525562d1d070a7a08152f5b4438a4150b132e20 +2b395d0d5d015d41335d21250de33e3d42152d3f557d1e44e4ee22255d2d +4a1b5c272d0d1c45072639362e402dee2853e51311262b17aa72eb390410 +e7015f0215352030574b4108e44d0e1a204418e62325ff7f34052f234b2d +1d563c13202346071d39e34055402b0b392c27f552222d3deb3843ee2c16 +29332a521f3c1b0811e33e1a25520e323e75e01c17473f55071226120d3d +210b35ee1a0a5335222e35033905170c4f3104eb032d425058367d5a2bf2 +1e553809415efb1c460f2f0ffafaec491e4d4e49510452e8245a366a4106 +e1f92cee0e10142514e7ec13155c412fe901092f1f0fa738280c5eee5e04 +3526291e0b2a5f486a3051041f4c16372f5402e6f70b31a03525190b161a +260e5e1f0c2e4d7528ef11552fefe247201e4752085c1da903563c162a4b +2a14ff2e3265e604075e523b24455c364a7f284f3a43051d52152f1119e8 +5f02e55a4b1300063640ef10151002565f0b0c010033a1cbef5d3634484a +1b121c585b495a5e033a09037f2d1754072c2d49084055172a3c220bed4f +1613400e1632435c0018482aa55b363d26290ae4405ded280f2b0c271536 +4011250ce02119464a1de43113170356342c272d1d3355555e5706245e0a +16272d5e545953002e10020875e223010719555410f91ce518420e382456 +0d4037320345f945241a1d090a545a310142442131464f4d10562ae4f05a +07ee4d4ae12e571e313c1636313134233e495459e548317708563c2c1b2f +e75803294b36565225552c3406304f0201e43323291b5e0e2159025c2f25 +5e63194411490c44494232237e1b323108573d3f391d1f3537e4165a2b35 +51000a3a264c503b5852072a5636f04f5cea58a42838f5fca876415c3521 +3c14130be511275932055a30aa2d03470c51060009f210543002585f5713 +10f0370c5823115200e5015d083e2f1a5df91d68065c1b03f0080855e529 +02ec00f1462d034123151ba6fc07eb3d5e54e85a3f3ee532fb41791a060b +0c29274232f93efb3d465544e45e491b042ced245100e3f05c14134c254b +5741235f051e080401a8013c065627e8ee5432205114243d54320e133f2d +4a4d181635411f5d084e31ed230c16506d5125415e060e4dcd0e5f3708e3 +2d531c3e22065a5eee07310c145305131800063e4a20094b2006ea131240 +e7335c1c4308160be6aa551a0f5a58243e0b10ee470047683c345e1c5b0c +5434505ee22a18110d20342e4b53062c4d79042a0a02422e225b2523e95a +3252212407115c07e15eee06391d0519e9271b641330011f383410281f0e +2cee2b355233292b595d1c69592f483b54584f7154fd4928560752e333a1 +17272b272f110df5e91c560a39104510240b5c4b0c1c570871e422351927 +c32550ec3f132c0c2458503ae5241d3c0d7911480a073826315620403615 +16e11c270d2b010650145de2290b0beb1e120a3a354b2104064f3b533c4e +505746313d4d2e3455290a281ee81d50007e1148252528025237715a342a +1c0a13163e404e40242142061d34185421160220fa031f7a423a08f2e01a +101d303802f51b0c08ef461259315b553823e622a12d565509e23c624139 +0a3d1309e4384c0eed383846545a035a41ee1771513b090a031e15f45159 +2d4944092a1965542507003b23195758403e175a0a450c5c38114de21141 +eb100fe63a031c4b35eb591845e428441c0d5b0037131f5c160a31243619 +c155ef0d19143e24392507a202581a25491b135c27571d5c5b35250f0bef +0e1d510556485e39557e044e2cf10457523016473f500b1e36370c17591c +7e5a19250a5e152b46f5130a094cef08e84704ef10197324464b0114017a +3b56f126390008343d3c400232ed201667211f0b1a1413080202530b08e2 +4912321b61c90a0cf6ef0a0a0c0f17fa62eb385e2616194526701aff5fe6 +2c57114b0400152d4f2aeb18ed41386c2e3a023a281d1a311eefe750ebab +3a4353282114593b3e36446d2c5e1e582e335337022930331f211604576a +295f3bfae9271ae8065a3b4417545c3e5b0df11a53351c78530915392d2e +074a122ee01b17131e4e124e2322a9560ce4120e37582b24e1036fe93f30 +3c08290121090ef72f25e4f220323444532d3fe71f34553c7b2726131009 +12e84a3308590357a719e74c4f2133690a20031a0b045af63551325b1219 +0e3d4fe03f56523cf40f29e4353455120e3a4f2f26f6a30a2b3e0c5b085a +57f3315c33e41c0f523426232d0651395c1525274e314d0219163b5f181f +53471622182739e9e25b473d74e1e7023d095a3134e62d1366563004120e +230a06431935391d5e0b5543223a3bed2b4358f555401e1b3b5c36470d11 +22100330e03b4812e6120f163b1ef6abebe6f602545ef9a459e33d334c2a +463405faa655563a43532cfe154bec32fe3345eb2c2700340811213e5006 +14241340112b2916017c270a0652732ee8121132385a6c020c040e2be15b +251119225c573b105d5c0a371c3d421ef23e22377fee334e0228561b2d15 +2e4c2e373b434b0d0b1b340c300e4b195614130ea03c234c292e14530c46 +0d2c3f08560ee32e5a5b6413355215384442563e69ec294a0eef561e3053 +193c100c0b24231c012273e10d2e12552723586120020b02e45632265e5f +2c175a11553d4b0b16025e2534180964245b125e5d6e595d1d2a0710580b +213a175ff30855e4001b305000263f5a5c3c5100163cee00114e3518f33a +10ed33e65b003012e7131e161d5e2e270b4645f358394118330f5a5b241b +33e80130f45708395457573406422a3b0d03e6e5053d0d2d151c083337a2 +551be2082b1563c4ec2247140400124d4b6508041b5a472256093aea1847 +7b5a4215415d544115415d5015455447414c155c46155f4058455c5b523f +0864eb4935144c501103a71851370719301bec57093a0929ea3f18060e55 +2d395e57143359e80efffb13330633ea19e323077b4814571e5a3de73a1f +52e73c1d53330846243c422d3e1b374b5209543903e3195c041c251b7c04 +2f3c2c28273a12520b482f18340d565d1fe84735474f4a012e1a13502523 +23340f39064e306a08194d544647522e1443041d5ee81f5a18415e34a45f +475a392637565757730a0c4a517b2821040e1709e028071558021f164c54 +100b2135190505264254005618f51152136125370eef27383e45350118ed +3947452914e0223f1d040943313c193f295b221e573e1b5723391d090d1f +2c33141859392b04155e3d4e393b322526ee3e581d1b3d6817374d0c085b +c2ea5821200f1b755b2d13130f04e26625ea3a5b1e37144d3e473c24030d +ee15025d2019f757305e3f010e2a453a205f1919391e1a04e86d1a350119 +1a5beb4946180fe0002a031a050b41e5164c58795021e1e45c59e2495c20 +1121394f1e381c3647005b7326250514272b55250a49183be5454ba518eb +1ee55936102a465d5004371f2e382f1d03144f170d2b0eed042ee341eb19 +ec1014ef3ff1272c3408220a41163708140b2e340e505c560c1e4cf82704 +274b341a454a27a0263408292e362c201c0401462049523b2d55e5132d54 +e259032c444b091e2e4920023f1a7ce40908255228e36f0f2424394b3c48 +34130cf8223f23084813e745e006531a1e464b005e0e1ee405413fe22b4e +4af201080c0928420c2d491f6e5121e451223b070dee54244b3efc470a0e +771c161f795df81c22101408465ae7ef0c0604733ee03a20560c1512f217 +2f3a142c4155073a200f04166c565634020a59ea04244ff7413c4bc10858 +240d4752e5fa5a4e1ce255505602e55d4c575e2b59f52b4e0c0a0b464019 +21341927f3380232396707232ae424ea123f5b371d4f65e2471dfbede611 +e10e1c3b1d4d28085c091f135b585709332c56134e4844552f45eb41172a +3f1b5a343f034832193b153c482f1705392f021f5f0953290c4c43312b36 +3810161aea7001fb5d502b285945255d4ef80131572d2c2e59730e2c3035 +4d59052e1f2242403d440a13263e1d2dea0612125e16033b180834030829 +022917180d07474c295f793e42274b0e1e16581036225c1211e41e04042f +ec2b41054f2a5f56065e5e0e1f56e13e0a702e1b2f2137020e363a2ae2a4 +53085a3b34e75a1caa2e5d031f261f5f044350312f37455d493f131f3746 +0c295f1724e90b001a4e015d27091a0b3256302c303d51a05956e6331531 +e42b315ce21f0def38144d20242845fa3f3b3b0ce8f4fb2d31ed1d54134b +2957023141335d35372813263b46581af6535a16404d0b4ff12a207648ec +e4421e301de25c43010c504e0f562f2018421ce137443b41134b5f542047 +0c5600294e085c1d3622292c480d261213e05c1334385108c145f3090612 +062d2e02267404241f4966e6e010052d3224e72856100b1d22f65a30e863 +324950394700e11a01201a0564525706f1013f353319076b4c0d015a2e24 +2a1be80e2013571522483b1e20321a4e03285d211a444d113924e8f41a1f +27193ae2302208e73010eaa1292001045737013e10e4745aed2c105b25fb +1b135d46eaef103e1d330a14337a2a4302441c1631ed07e7100c743a0e35 +1a0957115c293b1c0de853245b5b18e2e12d28421b3230245d7b4a55f355 +e7360e2b3846202a2926fa495e3302ed064d127a17343a1f11032b40e8f5 +06e8f90a3118381c5414157d1434050210363e30500511a00a3d56e10438 +30021931f7193e25a0540ef52658350929380974fb035b1a5d2c042959c7 +151b0c24052d0e56025404390e5a3909edec0d03070f040cff710825363e +2a2328120b2203320810134a0c0a0ef30b25460bec011c1e26e913575a51 +e12d0948ed3c511416151d1c54082b3e385d14f838510bec4e4b5f585321 +1559305c3a49192a010f04ec11001a3d5a5621e5535358353206521f013f +172c2c155a3a322009505c290516a2c4e4405a1e0a1e353b6e1a5a4e2f09 +552c34e2432b0df1132b130841000d4007232339a2092a593f142b0a0117 +0931432e452d3aea1d02587d3a3e56ed2a3050e2f9363df366331e421947 +0250094823545b20163f1d0a36a92228ed25564d1a304deae8035c32370d +4314380e264e2359e6a412504a424328e84434ff30236649353315344a00 +25e33540550d3c15135b0eed451cfd1812eaf2063f085d6e214d121c342f +37513b2d0a4e3e5211372a3a01334c5d51030c46463e3756290c0d0e1222 +132f175e4c4af1120138e1f2085a3804471f5824555d083de6123f533123 +0de11936062d3d2f12193e135f38ff5e1a531d1426523746004e2c063a27 +49241aee1802311611a50de9592009e936270108214a0c4213a01f09545f +02e14d2babee204a5c4337135821360d021b7831305963ee0737072f0deb +1512371119050c0c1142245a004f033650481830230a1925085c1a172726 +3be62f230a4b50526ec9345100252aa729eafa59221b3fa517304e500a15 +5e57f231333c3d0c470a47551733511031362a3bed0f334a3f3136104230 +eb24015d051a151f245905061a37ea273d2239fe02463a5e314d565f0457 +23025f415d290a594e3b5940313347a11c5e41531ff15a385a183829780a +51e0035f2deb3b163eabe8550e2e0414491f573b5419234a28183044e112 +1d54e8390b26585f3aef5f14206672240c4a5e5d31e01b4d406e351401fa +e555173e242c753b275d4ee50b2f26501402a71b1b5733ec19ee34284aed +2ee8f023401c09383b084d623ef324ee5a33065a6d5e365b092c5d0d4501 +3f4e024d4b161e144d5e3b140d1e2944465b491d265603a705373c231240 +544f0d4ea6091e00e62d3e130d4f005139f339001a3b480c221b730be75e +5f1f4f3e0a0dec3b5128e32960e42d0fee02275528154b10e65c36555a2e +ea3e311b5b0f5f220b1f1b2914f12111f41213e06232224df5ec0114470d +51203f1e01e5563851284013514a565e53125223052f47100e5011100201 +3f5bee2305217838582be55958a00245265b0308ec56525b5c114c2d5407 +e6e74818e53602160e45372029eb4de72754ec3f49290d2f5901014c0e7f +08e715e612380a5c1908285a1222073a023c562907384e4f470444483f34 +1110382b5225343ba6092133483e2d683e1e280227084a1e405e3a341513 +415f240f0c53e3f7196e2252fb0105347f345e531f535a344bf439220916 +5722e7f7fa2f4c2e057e2a025e2dec31413439aa12265f5a3458f81a4b15 +135839401856f337a72fec475a060de239a650163a55392a5b303f051415 +56090f18023a2b16e2364407050d48e1541408281d3aa3e84c5b264c1f33 +1725f9540aec5e10ed293e4e5a5a2d2125f053251a55395d1c2044022231 +292d523ff86a180620075f325e02566659f30423525a053a01f0087f4b3b +17fe493808f25309251e1325596ce32b42311e5d0c2f58652640582a4b17 +67381a5afb7128150a0043e45b173d2111155c49092d2635370a3a201826 +e62d021d36e03b205d5f1f295c094608342a412122583f3bfc34190be62c +393a055f59060d454a235326e844243a30285c14e316272524f4f0444f51 +352c3c5b2b5845244f55494940194721f80b120f07392b7c2c5a0508111e +2f1219430151e60f11150b101e295736361b1e053e4d08f83f230e2c383a +ef5b1d492610e834330f5cf3a2485d324f2822084f41111f582957191b19 +1e3e223704fe1d2e1f592753e5550f15170b231b4234e945301f5605a670 +300d322759ea0337015c662a0e073809543f2741104835512d0624551751 +373727ef1f41084d0b5c0c0137283b1337026aea1c5ae115064ffa183402 +09152b11e1233e5a0e302a521c5a33181e180026463744a82c024b4bf04e +1df61df1263fee59135c13400950153d3c5c59183b020b1d2d2c492f4968 +e2000c405a01ede30c4c082e2537443c120f38fc57c43651423e5c3beb1d +1922182420191b293e163d58020b005f454a0621051a38e80b090a463ee9 +39513f2d47042c0fe5134419ec48490f150f323a5ee7a7e0201e193a5e1b +2037200a2b1013567b35fb4a0f322c2f49435d091920521c302b413f5f35 +775d1a345b483b35a02a4c3e17ee3a3d5a5b57153613264f23041922432f +35125b3e0a1d2257eb002a26455e1a2f042e1545e92f0b3408032c4f3551 +2d4c392321300a18ed4f3e2c314d20500052aa3917e55d0d29500754282e +381b2e263758f63c474a1c23110c2d5f1c220412e91043580656080c0427 +081ce1e5350b6a3535f0e6592e5b543432340e38f008e0324102e45a3f25 +30040c181615362e4d1016160a4a5c006eeb1d2422355a3f1028ff192a07 +53f6354d4b5d121974245c14f0225713331f2e381810101428571725e432 +1a2c06372d5b1419742150042d25003c2650512834ef16e51d183f0f0508 +3d191107251100ee2e4125405a44174f061e0e1e5959e606530e06ed245e +3f592d47512dec5922500e460e1de7183b4c3c2e583942255a0c5d4d2305 +3438001e482a002d56113a1fe13bed542d3508e22f4e22221431121c1539 +ed445a5d28415073eb18022ef836274d573a48090f2a663058194901405d +215b143954fc313c1e28584b51e729ef31013b232bfb4c52e2322a2d4557 +5244102e1c3d304450ee01761924e62ff2173305e15809102b2125284dfc +171a3f010f3639056f2be71c2047581de32e05a20833e1221b0e25362459 +2958280de238084f5a1c292e005be71f3b311e1f415809383d3862260238 +361f56ecee120156375862eb3627185c2519545149e2e50b1f3b0c4e3352 +e6115f440634e4005d273611e41c5d383c3814537b3d23362b084024345b +10370656372e0236eb4f3303e216505f0e465228383729394faa2f205f34 +2e125b2f2c1d0f1f170e0c51331f0c06291610345c0603791f33253f0e0c +1c2b080526133aeb3e23571d4cfa1e48057a2a010a490a50391b09514f2e +59383ae11237e5450029162d2e1d3e09221a160e42ea06ea0ca7c7ecf4ea +3d3024f34d5c07464bea3b185e110d3a10395d3b2632343cf30ca2e6065a +262f111c0e15441a4825111b185f1e5756243206125f4603e97e79582d27 +2d5801ee2654113e2da00b58e9260d643c10423e1d1f42093b0d0f7d5102 +3649211f210456051e290f1b4c584d0749220c280b2a50531f262901503e +52053e3e152b5b2b4415580fec57ef5c08e5ed43cc2d2e5b40355d0d2017 +6d3917263f030c4b55f0025d501e57504a122729293c4c5819680d3001ed +1e313323324e5e177b171cf70c371541395c0e2b7726e42505483014362e +1910e4f7253f0a012057e03b1e3b4201362b224ff60e0b3a1d115b043957 +200c1e0b242e5e3b4755f61e3be05c040908f1234358e55562711d2efa0f +0737e0160b1d13132044080d2325f1f0ee2f00354f2106471131020a5d0b +3f21060de62c052a17576e2ce729242b3e3621300627f01e52580a480050 +1b381a11351f4f5d22040c3c4b3e7d263714e8e61a571d107a34260a4a51 +edf52314e111207c0b23eb482f441d211f306137152407040e08530a783e +3c054e2d4e2905275e640220f74f1a193f54e1ed5b4e2a290eab27a55147 +33522817335316ea2f3df957e25e02030601514f09f74c2fedee102d3114 +5d05231d03313826164156110c44e4111f4658005e115e300f413b430300 +380bf53a4331f74627492c133fe8eb3141ee39040def040c1a0ae914e3ed +5b00f0211f0a091e05582e22f05a5d262e0ce352251d25100b102b11e339 +36053935f051f959093252411e2d5af81f360c0fa15d0b373b1d26323b77 +501424184202206215e05944505c4817514540445b0207025de05b050932 +0a5a114515536f553a352c513f0b12f700345fa51d5efb28222676e559ea +561b0557403f5f534a574638411e2d3b3c133f79555c333215e6f5f9e7ec +6658f7210218110f00062752e305f21601442c5310162445ed4d175630f3 +0e2154253c4a22f02e1b0933351314071b521513235031250c18120024a1 +e03555453d1e31775f37331823164c341c09e310463438481019fb0b12fa +37eee654410e4007501f2c0e42faf50125075b2b46164f165a1003097f08 +2a5332145851553926523965582e5b2f530d5d1e292046344feaed461517 +583d2b06251f551d2f5451110911e6034147481a05166e1f241a5817015b +1f2d3f5c310c315402200010e24135592435f71b4640540a041012ee1b3f +5b2010060e2f5a4d045e0b36192f79181b0732183b4a261038340032f434 +3a5557340be6f5315c35112912393503320f54065f0e275a3b5853352008 +1c595d183539220eec123478535337110424f90a355af44c267be848173f +41053f5cef5f6f56e4f5410a5407281600200b2649460a2e3a3c38492a0c +4c071a57e9356ee415103c5c53e254063f2019340969e30a2e381d5b2555 +32042f46431d2c44607934ed180c1028136a5f2b26092e3b2c4e2930585a \ No newline at end of file diff --git a/users/wpcarro/scratch/cryptopals/set1/c1.py b/users/wpcarro/scratch/cryptopals/set1/c1.py new file mode 100644 index 000000000000..0dfd6bb6d059 --- /dev/null +++ b/users/wpcarro/scratch/cryptopals/set1/c1.py @@ -0,0 +1,19 @@ +from base64 import b64encode + +################################################################################ +# Challenge 1 +################################################################################ + +def hex_to_base64(x): + parsed = bytearray.fromhex(x) + print(parsed.decode()) # easter egg + return b64encode(parsed).decode() + +run_tests = False +if run_tests: + actual = hex_to_base64("49276d206b696c6c696e6720796f757220627261696e206c696b65206120706f69736f6e6f7573206d757368726f6f6d") + expect = "SSdtIGtpbGxpbmcgeW91ciBicmFpbiBsaWtlIGEgcG9pc29ub3VzIG11c2hyb29t" + + print(actual) + assert actual == expect + print("Success!") diff --git a/users/wpcarro/scratch/cryptopals/set1/c2.py b/users/wpcarro/scratch/cryptopals/set1/c2.py new file mode 100644 index 000000000000..badd60503d90 --- /dev/null +++ b/users/wpcarro/scratch/cryptopals/set1/c2.py @@ -0,0 +1,20 @@ +def fixed_xor(x, y, decode_hex=True, encode_hex=True): + if decode_hex: + x = bytearray.fromhex(x) + y = bytearray.fromhex(y) + + result = bytearray(len(x)) + + for i in range(len(x)): + result[i] = x[i] ^ y[i] + + return result.hex() if encode_hex else result + +run_tests = False +if run_tests: + actual = fixed_xor("1c0111001f010100061a024b53535009181c", "686974207468652062756c6c277320657965") + expect = "746865206b696420646f6e277420706c6179" + + print(actual) + assert actual == expect + print("Success!") diff --git a/users/wpcarro/scratch/cryptopals/set1/c3.py b/users/wpcarro/scratch/cryptopals/set1/c3.py new file mode 100644 index 000000000000..2d84026a7b7d --- /dev/null +++ b/users/wpcarro/scratch/cryptopals/set1/c3.py @@ -0,0 +1,50 @@ +from c2 import fixed_xor +from collections import Counter + +def frequency_table(): + with open('alice.txt', 'r') as f: + chars = {} + while True: + l = f.readline() + if not l: break + for c in l: + chars[c] = chars.get(c, 0) + 1 + result = {} + for c, n in chars.items(): + result[c] = n / len(chars) + return result + +def score(bs, freqs): + return sum(freqs.get(b, 0) for b in bs) + +def decode_cipher(x): + freqs = frequency_table() + + if not freqs: + raise Error("Cannot decode cipher without a populated frequency table") + + x = bytearray.fromhex(x) + num_bytes = len(x) + + mx, result, key = 0, None, None + for b in range(0, 1 << 8): + mask = bytearray(b.to_bytes(1, 'big') * num_bytes) + try: + y = fixed_xor(x, mask, decode_hex=False, encode_hex=False).decode('ascii') + except: + continue + test = score(y, freqs) + if test > mx: + result = y + mx = test + key = mask.decode('ascii') + return result + +run_tests = False +if run_tests: + print(decode_cipher("1b37373331363f78151b7f2b783431333d78397828372d363c78373e783a393b3736")) + +################################################################################ +# Answer +################################################################################ +"Cooking MC's like a pound of bacon" diff --git a/users/wpcarro/scratch/cryptopals/set1/c4.py b/users/wpcarro/scratch/cryptopals/set1/c4.py new file mode 100644 index 000000000000..c546419a3388 --- /dev/null +++ b/users/wpcarro/scratch/cryptopals/set1/c4.py @@ -0,0 +1,23 @@ +import c3 + +content = None +with open('4.txt', 'r') as f: + content = f.read().splitlines() +if not content: + raise Error("Need content to proceed") + +xs = [] +for line in content: + try: + x = c3.decode_cipher(line) + if x: xs.append(x) + except: + continue + +freqs = c3.frequency_table() +print(max(xs, key=lambda x: c3.score(x, freqs))) + +################################################################################ +# Answer +################################################################################ +"Now that the party is jumping" diff --git a/users/wpcarro/scratch/cryptopals/set1/c5.py b/users/wpcarro/scratch/cryptopals/set1/c5.py new file mode 100644 index 000000000000..a098dfe74afa --- /dev/null +++ b/users/wpcarro/scratch/cryptopals/set1/c5.py @@ -0,0 +1,16 @@ +def encrypt_repeating_key(x, key): + result = b"" + for i in range(len(x)): + b = ord(x[i]) ^ ord(key[i % len(key)]) + result += b.to_bytes(1, 'big') + return result.hex() + +cleartext = "Burning 'em, if you ain't quick and nimble\nI go crazy when I hear a cymbal" +expected = "0b3637272a2b2e63622c2e69692a23693a2a3c6324202d623d63343c2a26226324272765272a282b2f20430a652e2c652a3124333a653e2b2027630c692b20283165286326302e27282f" + +run_tests = False +if run_tests: + ciphertext = encrypt_repeating_key(cleartext, "ICE") + print(ciphertext) + assert ciphertext == expected + print("Success!") diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/array-traversals.py b/users/wpcarro/scratch/data_structures_and_algorithms/array-traversals.py new file mode 100644 index 000000000000..35cb4392812e --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/array-traversals.py @@ -0,0 +1,87 @@ +# This is practice for various types of list traversals that turn up. + +xs = range(10) +n = len(xs) + +print('---') +# pythonic left-to-right traversal +result = '' +for x in xs: + result += str(x) +print(result) + +print('---') +# left-to-right traversal +result = '' +for i in range(n): + result += str(xs[i]) +print(result) + +print('---') +# right-to-left traversal +result = '' +for i in range(n): + result += str(xs[n - 1 - i]) +print(result) + +print('---') +# 2x left-to-right traversal +result = '' +for i in range(2 * n): + result += str(xs[i % n]) +print(result) + +print('---') +# 2x right-to-left traversal +result = '' +for i in range(2 * n): + result += str(xs[(n - 1 - i) % n]) +print(result) + +################################################################################ +# Table traversals +################################################################################ + +table = [[row * 10 + i for i in range(10)] for row in range(3)] +row_ct = len(table) +col_ct = len(table[0]) + +print('---') +# 3x10 table traversal +result = '' +for row in table: + r = '' + for col in row: + r += '{:3d}'.format(col) + result += r + '\n' +print(result[0:-1]) + +print('---') +# 3x10 table traversal +result = '' +for row in range(row_ct): + r = '' + for col in range(col_ct): + r += '{:3d}'.format(table[row][col]) + result += r + '\n' +print(result[0:-1]) + +print('---') +# 3x10 table traversal (reverse) +result = '' +for row in range(row_ct): + r = '' + for col in range(col_ct): + r += '{:3d}'.format(table[row_ct - 1 - row][col_ct - 1 - col]) + result += r + '\n' +print(result) + +print('---') +# 3x10 column-row traversal +result = '' +for col in range(col_ct): + r = '' + for row in range(row_ct): + r += '{:3d}'.format(table[row][col]) + result += r + '\n' +print(result) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/balanced-binary-tree.py b/users/wpcarro/scratch/data_structures_and_algorithms/balanced-binary-tree.py new file mode 100644 index 000000000000..01fd965fd540 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/balanced-binary-tree.py @@ -0,0 +1,145 @@ +import unittest +from itertools import combinations + + +def balanced(xs): + """Return True if `xs` contains no two values that differ by more than + one.""" + if len(xs) == 0 or len(xs) == 1: + return True + if len(xs) == 2: + return math.abs(xs[0] - xs[1]) <= 1 + else: + pass + + +def is_leaf(node): + return node.left is None and node.right is None + + +def is_balanced(tree_root): + """Returns True if the difference between the depths of any two leaf nodes + does not exceed 1.""" + depths = set() + populate_depths(tree_root, 0, depths) + + # cartesian product - only the top half + for diff in set(abs(a - b) for a, b in combinations(depths, 2)): + if diff > 1: + return False + + return True + + +def populate_depths(node, depth, depths): + if is_leaf(node): + depths.add(depth) + else: + if node.left is not None: + populate_depths(node.left, depth + 1, depths) + if node.right is not None: + populate_depths(node.right, depth + 1, depths) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_full_tree(self): + tree = Test.BinaryTreeNode(5) + left = tree.insert_left(8) + right = tree.insert_right(6) + left.insert_left(1) + left.insert_right(2) + right.insert_left(3) + right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_leaves_at_the_same_depth(self): + tree = Test.BinaryTreeNode(3) + left = tree.insert_left(4) + right = tree.insert_right(2) + left.insert_left(1) + right.insert_right(9) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_one(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right.insert_right(7) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_two(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right_right = right.insert_right(7) + right_right.insert_right(8) + result = is_balanced(tree) + self.assertFalse(result) + + def test_three_leaves_total(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right.insert_left(8) + right.insert_right(5) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_subtrees_superbalanced(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right_left = right.insert_left(8) + right.insert_right(5) + right_left.insert_left(7) + result = is_balanced(tree) + self.assertFalse(result) + + def test_both_subtrees_superbalanced_two(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(2) + right = tree.insert_right(4) + left.insert_left(3) + left_right = left.insert_right(7) + left_right.insert_right(8) + right_right = right.insert_right(5) + right_right_right = right_right.insert_right(6) + right_right_right.insert_right(9) + result = is_balanced(tree) + self.assertFalse(result) + + def test_only_one_node(self): + tree = Test.BinaryTreeNode(1) + result = is_balanced(tree) + self.assertTrue(result) + + def test_linked_list_tree(self): + tree = Test.BinaryTreeNode(1) + right = tree.insert_right(2) + right_right = right.insert_right(3) + right_right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/bit-manipulation.py b/users/wpcarro/scratch/data_structures_and_algorithms/bit-manipulation.py new file mode 100644 index 000000000000..dc30bb508887 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/bit-manipulation.py @@ -0,0 +1,32 @@ +def test(x, i): + return x & (1 << i) != 0 + + +def set(x, i): + return x | (1 << i) + + +def clear(x, i): + return x & ~(1 << i) + + +def toggle(x, i): + if test(x, i): + return clear(x, i) + else: + return set(x, i) + + +def test_single(x): + if x == 0: + return False + else: + return x & (x - 1) == 0 + + +print(test(0b1010, 3)) +print('{0:b}'.format(set(0b1010, 1))) +print('{0:b}'.format(clear(0b1010, 1))) +print('{0:b}'.format(toggle(0b1010, 2))) +print(test_single(0b1010)) +print(test_single(0b1000)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/bracket-validator.py b/users/wpcarro/scratch/data_structures_and_algorithms/bracket-validator.py new file mode 100644 index 000000000000..a50f8b074e55 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/bracket-validator.py @@ -0,0 +1,63 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# is_valid :: String -> Boolean +def is_valid(xs): + s = [] + seeking = { + '}': '{', + ']': '[', + ')': '(', + } + openers = seeking.values() + closers = seeking.keys() + for c in xs: + if c in openers: + s.append(c) + elif c in closers: + if not s: + return False + elif s[-1] != seeking.get(c): + return False + else: + s.pop() + return len(s) == 0 + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_valid_short_code(self): + result = is_valid('()') + self.assertTrue(result) + + def test_valid_longer_code(self): + result = is_valid('([]{[]})[]{{}()}') + self.assertTrue(result) + + def test_interleaved_openers_and_closers(self): + result = is_valid('([)]') + self.assertFalse(result) + + def test_mismatched_opener_and_closer(self): + result = is_valid('([][]}') + self.assertFalse(result) + + def test_missing_closer(self): + result = is_valid('[[]()') + self.assertFalse(result) + + def test_extra_closer(self): + result = is_valid('[[]]())') + self.assertFalse(result) + + def test_empty_string(self): + result = is_valid('') + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/bst-checker.py b/users/wpcarro/scratch/data_structures_and_algorithms/bst-checker.py new file mode 100644 index 000000000000..689be97a8503 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/bst-checker.py @@ -0,0 +1,121 @@ +import unittest + + +################################################################################ +# Implementation +################################################################################ +# is_leaf :: Node(a) -> Boolean +def is_leaf(node): + return not node.left and not node.right + + +# is_binary_search_tree :: Node(Integer) -> Set(Int) -> Set(Int) -> Boolean +def is_binary_search_tree_a(node, la=set(), ra=set()): + """My first solution for this problem.""" + for x in la: + if not node.value < x: + return False + for x in ra: + if not node.value > x: + return False + if is_leaf(node): + return True + elif not node.left: + return is_binary_search_tree( + node.right, + la=la, + ra=ra ^ {node.value}, + ) + elif not node.right: + return is_binary_search_tree(node.left, la=la ^ {node.value}, ra=ra) + else: + return all([ + is_binary_search_tree(node.left, la=la ^ {node.value}, ra=ra), + is_binary_search_tree(node.right, la=la, ra=ra ^ {node.value}) + ]) + + +# is_binary_search_tree :: Node(Int) -> Maybe(Int) -> Maybe(Int) -> Boolean +def is_binary_search_tree(node, lb=None, ub=None): + if lb: + if node.value < lb: + return False + if ub: + if node.value > ub: + return False + if is_leaf(node): + return True + elif not node.right: + return is_binary_search_tree(node.left, lb=lb, ub=node.value) + elif not node.left: + return is_binary_search_tree(node.right, lb=node.value, ub=ub) + else: + return is_binary_search_tree( + node.left, lb=lb, ub=node.value) and is_binary_search_tree( + node.right, lb=node.value, ub=ub) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_valid_full_tree(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + right.insert_left(60) + right.insert_right(80) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + def test_both_subtrees_valid(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(80) + left.insert_left(20) + left.insert_right(60) + right.insert_left(70) + right.insert_right(90) + result = is_binary_search_tree(tree) + self.assertFalse(result) + + def test_descending_linked_list(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(40) + left_left = left.insert_left(30) + left_left_left = left_left.insert_left(20) + left_left_left.insert_left(10) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + def test_out_of_order_linked_list(self): + tree = Test.BinaryTreeNode(50) + right = tree.insert_right(70) + right_right = right.insert_right(60) + right_right.insert_right(80) + result = is_binary_search_tree(tree) + self.assertFalse(result) + + def test_one_node_tree(self): + tree = Test.BinaryTreeNode(50) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/cafe-order-checker.py b/users/wpcarro/scratch/data_structures_and_algorithms/cafe-order-checker.py new file mode 100644 index 000000000000..e34a2b136ab6 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/cafe-order-checker.py @@ -0,0 +1,91 @@ +import unittest + + +################################################################################ +# Implementation +################################################################################ +def is_first_come_first_served(to, di, xs): + # All the guards, assertions we should need. + if to == di == xs == []: + return True + elif to == di == []: + return False + elif to == []: + return di == xs + elif to == []: + return di == xs + elif di == []: + return to == xs + elif xs == []: + return False + elif len(xs) != (len(to) + len(di)): + return False + + fst, snd = to, di + + if xs[0] == to[0]: + fst, snd = to, di + elif xs[0] == di[0]: + fst, snd = di, to + else: + return False + + fst_done, snd_done = False, False + fi, si = 1, 0 + + for i in range(1, len(xs)): + # Short-circuit and avoid index-out-of-bounds without introducing overly + # defensive, sloppy code. + if fst_done: + return snd[si:] == xs[i:] + elif snd_done: + return fst[fi:] == xs[i:] + + if fst[fi] == xs[i]: + fi += 1 + elif snd[si] == xs[i]: + si += 1 + else: + return False + + fst_done, snd_done = fi == len(fst), si == len(snd) + + return True + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_both_registers_have_same_number_of_orders(self): + result = is_first_come_first_served([1, 4, 5], [2, 3, 6], + [1, 2, 3, 4, 5, 6]) + self.assertTrue(result) + + def test_registers_have_different_lengths(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], [1, 2, 6, 3, 5]) + self.assertFalse(result) + + def test_one_register_is_empty(self): + result = is_first_come_first_served([], [2, 3, 6], [2, 3, 6]) + self.assertTrue(result) + + def test_served_orders_is_missing_orders(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], [1, 6, 3, 5]) + self.assertFalse(result) + + def test_served_orders_has_extra_orders(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], + [1, 2, 3, 5, 6, 8]) + self.assertFalse(result) + + def test_one_register_has_extra_orders(self): + result = is_first_come_first_served([1, 9], [7, 8], [1, 7, 8]) + self.assertFalse(result) + + def test_one_register_has_unserved_orders(self): + result = is_first_come_first_served([55, 9], [7, 8], [1, 7, 8, 9]) + self.assertFalse(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/cake-thief.py b/users/wpcarro/scratch/data_structures_and_algorithms/cake-thief.py new file mode 100644 index 000000000000..9eddb34b2db3 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/cake-thief.py @@ -0,0 +1,71 @@ +import unittest +from math import floor + + +################################################################################ +# Solution +################################################################################ +def max_duffel_bag_value(xs, cap): + ct = (cap + 1) + maxes = [0] * ct + for c in range(cap + 1): + for w, v in xs: + if w == 0 and v > 0: + return float('inf') + if w == c: + maxes[c:] = [max(maxes[c], v)] * (ct - c) + elif w < c: + d = c - w + maxes[c:] = [max(maxes[c], v + maxes[d])] * (ct - c) + else: + continue + return maxes[cap] + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_one_cake(self): + actual = max_duffel_bag_value([(2, 1)], 9) + expected = 4 + self.assertEqual(actual, expected) + + def test_two_cakes(self): + actual = max_duffel_bag_value([(4, 4), (5, 5)], 9) + expected = 9 + self.assertEqual(actual, expected) + + def test_only_take_less_valuable_cake(self): + actual = max_duffel_bag_value([(4, 4), (5, 5)], 12) + expected = 12 + self.assertEqual(actual, expected) + + def test_lots_of_cakes(self): + actual = max_duffel_bag_value([(2, 3), (3, 6), (5, 1), (6, 1), (7, 1), + (8, 1)], 7) + expected = 12 + self.assertEqual(actual, expected) + + def test_value_to_weight_ratio_is_not_optimal(self): + actual = max_duffel_bag_value([(51, 52), (50, 50)], 100) + expected = 100 + self.assertEqual(actual, expected) + + def test_zero_capacity(self): + actual = max_duffel_bag_value([(1, 2)], 0) + expected = 0 + self.assertEqual(actual, expected) + + def test_cake_with_zero_value_and_weight(self): + actual = max_duffel_bag_value([(0, 0), (2, 1)], 7) + expected = 3 + self.assertEqual(actual, expected) + + def test_cake_with_non_zero_value_and_zero_weight(self): + actual = max_duffel_bag_value([(0, 5)], 5) + expected = float('inf') + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/coins.py b/users/wpcarro/scratch/data_structures_and_algorithms/coins.py new file mode 100644 index 000000000000..eb5754f98210 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/coins.py @@ -0,0 +1,57 @@ +import unittest +from math import floor + +################################################################################ +# Solution +################################################################################ + +# change_possibilities :: Int -> [Int] -> Int +def change_possibilities(n, xs): + combinations = [0] * (n + 1) + combinations[0] = 1 + + for x in xs: + for i in range(len(combinations)): + if i >= x: + combinations[i] += combinations[i - x] + + return combinations[n] + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + + def test_sample_input(self): + actual = change_possibilities(4, (1, 2, 3)) + expected = 4 + self.assertEqual(actual, expected) + + def test_one_way_to_make_zero_cents(self): + actual = change_possibilities(0, (1, 2)) + expected = 1 + self.assertEqual(actual, expected) + + def test_no_ways_if_no_coins(self): + actual = change_possibilities(1, ()) + expected = 0 + self.assertEqual(actual, expected) + + def test_big_coin_value(self): + actual = change_possibilities(5, (25, 50)) + expected = 0 + self.assertEqual(actual, expected) + + def test_big_target_amount(self): + actual = change_possibilities(50, (5, 10)) + expected = 6 + self.assertEqual(actual, expected) + + def test_change_for_one_dollar(self): + actual = change_possibilities(100, (1, 5, 10, 25, 50)) + expected = 292 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/conways-game-of-life.py b/users/wpcarro/scratch/data_structures_and_algorithms/conways-game-of-life.py new file mode 100644 index 000000000000..3836bcd0c653 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/conways-game-of-life.py @@ -0,0 +1,78 @@ +from itertools import product +from random import choice +from time import sleep +from os import system +from math import floor +from colorama import Back, Fore, Style + +################################################################################ +# Simulation of Conway's Game of Life. The goal here was to write this with a +# small amount of code as a proof-of-concept that could be run in the terminal. +# +# If you'd like to tinker with the rules, see the conditionals defined in the +# `advance/1` function. For other parameters, like the board size and refresh +# rate, refer to the while-loop defined at the bottom of this file. +################################################################################ + + +def init_board(n, init_alive_percentage): + """Initialize a board of size `n` by `n`. Supply a percentage, + `init_alive_percentage`, representing the number of cells in the board that + should be alive from the start.""" + alive_count = floor(n * init_alive_percentage) + distribution = [True] * alive_count + [False] * (n - alive_count) + return [[choice(distribution) for _ in range(n)] for _ in range(n)] + + +def neighbors(coord, board): + """Return the neighbors for a given `coord` on a `board`.""" + n = len(board) + row, col = coord + return [ + board[(row + row_d) % n][(col + col_d) % n] + for row_d, col_d in product([-1, 0, 1], [-1, 0, 1]) + if (row_d, col_d) != (0, 0) + ] + + +def advance(board): + """Advance the state of the `board` from T[n] to T[n+1].""" + n = len(board) + new_board = [[False for _ in range(n)] for _ in range(n)] + for row in range(n): + for col in range(n): + alive_count = len([x for x in neighbors((row, col), board) if x]) + # Loneliness + if alive_count == 0: + new_board[row][col] = False + # Status Quo + elif alive_count == 1: + new_board[row][col] = board[row][col] + # Cooperation + elif alive_count == 2: + new_board[row][col] = True + # Resource starvation + elif alive_count >= 3: + new_board[row][col] = False + return new_board + + +def print_board(board): + """Print the game `board` in a human-readable way.""" + result = '' + for row in board: + for col in row: + if col: + result += Back.GREEN + '1 ' + Style.RESET_ALL + else: + result += Back.RED + '0 ' + Style.RESET_ALL + result += '\n' + print(result) + + +board = init_board(100, 0.50) +while True: + system('clear') + print_board(board) + sleep(0.15) + board = advance(board) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/delete-node.py b/users/wpcarro/scratch/data_structures_and_algorithms/delete-node.py new file mode 100644 index 000000000000..7e431e224962 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/delete-node.py @@ -0,0 +1,60 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def delete_node(x): + if not x.next: + raise Exception('Cannot delete the last node in a linked list.') + else: + x.value = x.next.value + x.next = x.next.next + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def setUp(self): + self.fourth = Test.LinkedListNode(4) + self.third = Test.LinkedListNode(3, self.fourth) + self.second = Test.LinkedListNode(2, self.third) + self.first = Test.LinkedListNode(1, self.second) + + def test_node_at_beginning(self): + delete_node(self.first) + actual = self.first.get_values() + expected = [2, 3, 4] + self.assertEqual(actual, expected) + + def test_node_in_middle(self): + delete_node(self.second) + actual = self.first.get_values() + expected = [1, 3, 4] + self.assertEqual(actual, expected) + + def test_node_at_end(self): + with self.assertRaises(Exception): + delete_node(self.fourth) + + def test_one_node_in_list(self): + unique = Test.LinkedListNode(1) + with self.assertRaises(Exception): + delete_node(unique) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/dft.py b/users/wpcarro/scratch/data_structures_and_algorithms/dft.py new file mode 100644 index 000000000000..127d48c1864b --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/dft.py @@ -0,0 +1,65 @@ +from random import choice + + +class Node(object): + def __init__(self, value=None, left=None, right=None): + self.value = value + self.left = left + self.right = left + + +def p(node, indent=0): + print(indent * ' ' + '|-' + str(node.value)) + if node.left is not None: + p(node.left, indent=indent + 2) + if node.right is not None: + p(node.right, indent=indent + 2) + + +# read trees (i.e. traversing, parsing) +# write trees (i.e. generating, printing) +def random(d=0): + left = None + right = None + + if choice([True, False]): + left = random(d + 1) + + if choice([True, False]): + right = random(d + 1) + + return Node( + value=d, + left=left, + right=right, + ) + + +################################################################################ +# DFTs can be: +# - imperative (mutable) +# - functional (immutable) +# - iterative +# - recursive +################################################################################ + + +# Iterative +def traverse(node, f): + stack = [(node, 0)] + + while len(stack): + node, depth = stack.pop() + f(node, depth) + print(depth) + + if node.left is not None: + stack.append((node.left, depth + 1)) + if node.right is not None: + stack.append((node.right, depth + 1)) + + +print('----------------------------------------------------------------------') +for _ in range(10): + traverse(random(), lambda _, d: print(d)) +print() diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/dijkstra-shortest-path.py b/users/wpcarro/scratch/data_structures_and_algorithms/dijkstra-shortest-path.py new file mode 100644 index 000000000000..03907f604044 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/dijkstra-shortest-path.py @@ -0,0 +1,48 @@ +from collections import deque +from heapq import heappush, heappop +from fixtures import weighted_graph + + +def put(t, x, xs): + if t == 'stack': + return xs.append(x) + if t == 'queue': + return xs.append(x) + if t == 'priority': + return heappush(xs, x) + + +def pop(t, xs): + if t == 'stack': + return xs.pop() + if t == 'queue': + return xs.popleft() + if t == 'priority': + return heappop(xs) + + +# shortest_path :: Vertex -> Vertex -> Graph -> [Vertex] +def shortest_path(a, b, g): + """Returns the shortest path from vertex a to vertex b in graph g.""" + t = 'priority' + xs = [] + seen = set() + # Map(Weight, [Vertex]) + m = {} + + put(t, (0, [a], a), xs) + + while xs: + w0, path, v = pop(t, xs) + + seen.add(v) + if v == b: + m[w0] = path + for w1, x in g.get(v): + if x not in seen: + put(t, (w0 + w1, path + [x], x), xs) + + return m + + +print(shortest_path('a', 'f', graph_a)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space-beast.py b/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space-beast.py new file mode 100644 index 000000000000..93fdd9eed2d6 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space-beast.py @@ -0,0 +1,56 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def find_duplicate(xs): + self_ref_count = 0 + for i in range(len(xs)): + if xs[i] == i + 1: + self_ref_count += 1 + hops = len(xs) - 1 - self_ref_count + current = xs[-1] + while hops > 0: + current = xs[current - 1] + hops -= 1 + return current + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + # TODO: Debug why this fails. + def test_darren_from_interview_cake(self): + actual = find_duplicate([4, 1, 8, 3, 2, 7, 6, 5, 4]) + expected = 4 + self.assertEqual(actual, expected) + + def test_just_the_repeated_number(self): + actual = find_duplicate([1, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_short_list(self): + actual = find_duplicate([1, 2, 3, 2]) + expected = 2 + self.assertEqual(actual, expected) + + def test_last_cycle(self): + actual = find_duplicate([3, 4, 2, 3, 1, 5]) + expected = 3 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_duplicate([1, 2, 5, 5, 5, 5]) + expected = 5 + self.assertEqual(actual, expected) + + def test_long_list(self): + actual = find_duplicate([4, 1, 4, 8, 3, 2, 7, 6, 5]) + expected = 4 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space.py b/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space.py new file mode 100644 index 000000000000..e2739f0f6055 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/find-duplicate-optimize-for-space.py @@ -0,0 +1,61 @@ +from math import floor +import unittest + + +################################################################################ +# Solution +################################################################################ +def bounds(r): + ct = len(r) + if ct % 2 == 0: + h = int(ct / 2) + return ct, h + else: + h = floor(ct / 2) + return ct, h + + +def find_repeat(xs): + ct, h = bounds(xs) + rl = range(1, h + 1) + rr = range(h + 1, ct) + while True: + nl = len([None for x in xs if x in rl]) + nr = len([None for x in xs if x in rr]) + branch = rl if nl > nr else rr + if len(branch) == 1: + return branch[0] + ct, h = bounds(branch) + rl = range(branch[0], branch[0]) + rr = range(branch[0] + h, branch[-1] + 1) + raise Exception( + 'We could not find any duplicates in xs. Perhaps xs did not adhere to the usage contract.' + ) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_just_the_repeated_number(self): + actual = find_repeat([1, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_short_list(self): + actual = find_repeat([1, 2, 3, 2]) + expected = 2 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_repeat([1, 2, 5, 5, 5, 5]) + expected = 5 + self.assertEqual(actual, expected) + + def test_long_list(self): + actual = find_repeat([4, 1, 4, 8, 3, 2, 7, 6, 5]) + expected = 4 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/find-rotation-point.py b/users/wpcarro/scratch/data_structures_and_algorithms/find-rotation-point.py new file mode 100644 index 000000000000..2103a5b84f75 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/find-rotation-point.py @@ -0,0 +1,59 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def find_rotation_point(xs): + """Usage of `visited` here is a hack, but works for the test cases + (gulp).""" + i = 0 + j = round(len(xs) / 2) + result = None + visited = set() + while not result: + if i in visited: + i += 1 + if j in visited: + j -= 1 + visited.add(i) + visited.add(j) + if xs[j - 1] > xs[j]: + result = j + elif xs[i] < xs[j]: + i = j + j += round((len(xs) - j) / 2) + elif xs[i] >= xs[j]: + i = j + j -= round((j - i) / 2) + return result + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_small_list(self): + actual = find_rotation_point(['cape', 'cake']) + expected = 1 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_rotation_point( + ['grape', 'orange', 'plum', 'radish', 'apple']) + expected = 4 + self.assertEqual(actual, expected) + + def test_large_list(self): + actual = find_rotation_point([ + 'ptolemaic', 'retrograde', 'supplant', 'undulate', 'xenoepist', + 'asymptote', 'babka', 'banoffee', 'engender', 'karpatka', + 'othellolagkage' + ]) + expected = 5 + self.assertEqual(actual, expected) + + # Are we missing any edge cases? + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/find-unique-int-among-duplicates.py b/users/wpcarro/scratch/data_structures_and_algorithms/find-unique-int-among-duplicates.py new file mode 100644 index 000000000000..dfa5de42cc0b --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/find-unique-int-among-duplicates.py @@ -0,0 +1,45 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def find_unique_delivery_id(xs): + a = 0 + for x in xs: + a ^= x + return a + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_one_drone(self): + actual = find_unique_delivery_id([1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_unique_id_comes_first(self): + actual = find_unique_delivery_id([1, 2, 2]) + expected = 1 + self.assertEqual(actual, expected) + + def test_unique_id_comes_last(self): + actual = find_unique_delivery_id([3, 3, 2, 2, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_unique_id_in_middle(self): + actual = find_unique_delivery_id([3, 2, 1, 2, 3]) + expected = 1 + self.assertEqual(actual, expected) + + def test_many_drones(self): + actual = find_unique_delivery_id( + [2, 5, 4, 8, 6, 3, 1, 4, 2, 3, 6, 5, 1]) + expected = 8 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/fixtures.py b/users/wpcarro/scratch/data_structures_and_algorithms/fixtures.py new file mode 100644 index 000000000000..27689ca76d04 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/fixtures.py @@ -0,0 +1,110 @@ +# Using this module to store commonly used, but annoying to create, data +# structures for my test inputs. +# +# Use like: +# from fixtures import graph_a + +################################################################################ +# Constants +################################################################################ + +edge_list = [ + ('a', 'b'), + ('a', 'c'), + ('a', 'e'), + ('b', 'c'), + ('b', 'd'), + ('c', 'e'), + ('d', 'f'), + ('e', 'd'), + ('e', 'f'), +] + +unweighted_graph = { + 'a': {'b', 'c', 'e'}, + 'b': {'c', 'd'}, + 'c': {'e'}, + 'd': {'f'}, + 'e': {'d', 'f'}, + 'f': set(), +} + +adjacencies = { + 'a': { + 'a': False, + 'b': False + }, + 'a': [], + 'a': [], + 'a': [], + 'a': [], + 'a': [], + 'a': [], +} + +weighted_graph = { + 'a': {(4, 'b'), (2, 'c'), (4, 'e')}, + 'b': {(5, 'c'), (10, 'd')}, + 'c': {(3, 'e')}, + 'd': {(11, 'f')}, + 'e': {(4, 'd'), (5, 'f')}, + 'f': set(), +} + +# This is `weighted_graph` with each of its weighted edges "expanded". +expanded_weights_graph = { + 'a': ['b-1', 'c-1', 'e-1'], + 'b-1': ['b-2'], + 'b-2': ['b-3'], + 'b-3': ['b'], + 'c-1': ['c'], + 'e-1': ['e-2'], + 'e-2': ['e-3'], + 'e-3': ['e'], + # and so on... +} + +unweighted_digraph = { + '5': {'2', '0'}, + '4': {'0', '1'}, + '3': {'1'}, + '2': {'3'}, + '1': set(), + '0': set(), +} + +################################################################################ +# Functions +################################################################################ + + +def vertices(xs): + result = set() + for a, b in xs: + result.add(a) + result.add(b) + return result + + +def edges_to_neighbors(xs): + result = {v: set() for v in vertices(xs)} + for a, b in xs: + result[a].add(b) + return result + + +def neighbors_to_edges(xs): + result = [] + for k, ys in xs.items(): + for y in ys: + result.append((k, y)) + return result + + +def edges_to_adjacencies(xs): + return xs + + +# Skipping handling adjacencies because I cannot think of a reasonable use-case +# for it when the vertex labels are items other than integers. I can think of +# ways of handling this, but none excite me. diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/graph-coloring.py b/users/wpcarro/scratch/data_structures_and_algorithms/graph-coloring.py new file mode 100644 index 000000000000..bc7f7ceea562 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/graph-coloring.py @@ -0,0 +1,180 @@ +import unittest +from collections import deque + + +################################################################################ +# Solution +################################################################################ +class GraphNode: + def __init__(self, label): + self.label = label + self.neighbors = set() + self.color = None + + +# color_graph :: G(V, E) -> Set(Color) -> IO () +def color_graph(graph, colors): + q = deque() + seen = set() + q.append(graph[0]) + + while q: + node = q.popleft() + + illegal = {n.color for n in node.neighbors} + for x in colors: + if x not in illegal: + node.color = x + + seen.add(node) + + for x in node.neighbors: + if x not in seen: + q.append(x) + + # TODO: Is this the best way to traverse separate graphs? + for x in graph: + if x not in seen: + q.append(x) + + return 0 + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def setUp(self): + self.colors = frozenset([ + 'red', + 'green', + 'blue', + 'orange', + 'yellow', + 'white', + ]) + + def assertGraphColoring(self, graph, colors): + self.assertGraphHasColors(graph, colors) + self.assertGraphColorLimit(graph) + for node in graph: + self.assertNodeUniqueColor(node) + + def assertGraphHasColors(self, graph, colors): + for node in graph: + msg = 'Node %r color %r not in %r' % (node.label, node.color, + colors) + self.assertIn(node.color, colors, msg=msg) + + def assertGraphColorLimit(self, graph): + max_degree = 0 + colors_found = set() + for node in graph: + degree = len(node.neighbors) + max_degree = max(degree, max_degree) + colors_found.add(node.color) + max_colors = max_degree + 1 + used_colors = len(colors_found) + msg = 'Used %d colors and expected %d at most' % (used_colors, + max_colors) + self.assertLessEqual(used_colors, max_colors, msg=msg) + + def assertNodeUniqueColor(self, node): + for adjacent in node.neighbors: + msg = 'Adjacent nodes %r and %r have the same color %r' % ( + node.label, + adjacent.label, + node.color, + ) + self.assertNotEqual(node.color, adjacent.color, msg=msg) + + def test_line_graph(self): + node_a = GraphNode('a') + node_b = GraphNode('b') + node_c = GraphNode('c') + node_d = GraphNode('d') + + node_a.neighbors.add(node_b) + node_b.neighbors.add(node_a) + node_b.neighbors.add(node_c) + node_c.neighbors.add(node_b) + node_c.neighbors.add(node_d) + node_d.neighbors.add(node_c) + + graph = [node_a, node_b, node_c, node_d] + tampered_colors = list(self.colors) + color_graph(graph, tampered_colors) + self.assertGraphColoring(graph, self.colors) + + def test_separate_graph(self): + node_a = GraphNode('a') + node_b = GraphNode('b') + node_c = GraphNode('c') + node_d = GraphNode('d') + + node_a.neighbors.add(node_b) + node_b.neighbors.add(node_a) + node_c.neighbors.add(node_d) + node_d.neighbors.add(node_c) + + graph = [node_a, node_b, node_c, node_d] + tampered_colors = list(self.colors) + color_graph(graph, tampered_colors) + self.assertGraphColoring(graph, self.colors) + + def test_triangle_graph(self): + node_a = GraphNode('a') + node_b = GraphNode('b') + node_c = GraphNode('c') + + node_a.neighbors.add(node_b) + node_a.neighbors.add(node_c) + node_b.neighbors.add(node_a) + node_b.neighbors.add(node_c) + node_c.neighbors.add(node_a) + node_c.neighbors.add(node_b) + + graph = [node_a, node_b, node_c] + tampered_colors = list(self.colors) + color_graph(graph, tampered_colors) + self.assertGraphColoring(graph, self.colors) + + def test_envelope_graph(self): + node_a = GraphNode('a') + node_b = GraphNode('b') + node_c = GraphNode('c') + node_d = GraphNode('d') + node_e = GraphNode('e') + + node_a.neighbors.add(node_b) + node_a.neighbors.add(node_c) + node_b.neighbors.add(node_a) + node_b.neighbors.add(node_c) + node_b.neighbors.add(node_d) + node_b.neighbors.add(node_e) + node_c.neighbors.add(node_a) + node_c.neighbors.add(node_b) + node_c.neighbors.add(node_d) + node_c.neighbors.add(node_e) + node_d.neighbors.add(node_b) + node_d.neighbors.add(node_c) + node_d.neighbors.add(node_e) + node_e.neighbors.add(node_b) + node_e.neighbors.add(node_c) + node_e.neighbors.add(node_d) + + graph = [node_a, node_b, node_c, node_d, node_e] + tampered_colors = list(self.colors) + color_graph(graph, tampered_colors) + self.assertGraphColoring(graph, self.colors) + + def test_loop_graph(self): + node_a = GraphNode('a') + node_a.neighbors.add(node_a) + graph = [node_a] + tampered_colors = list(self.colors) + with self.assertRaises(Exception): + color_graph(graph, tampered_colors) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/graph-to-graphviz.py b/users/wpcarro/scratch/data_structures_and_algorithms/graph-to-graphviz.py new file mode 100644 index 000000000000..0e7e97a20ca7 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/graph-to-graphviz.py @@ -0,0 +1,39 @@ +from graphviz import Digraph +from collections import deque +from fixtures import weighted_graph + +# There are three ways to model a graph: +# 1. Edge list: [(Vertex, Vertex)] +# 2. Neighbors table: Map(Vertex, [Vertex]) +# 3. Adjacency matrix: [[Boolean]] +# +# The following graph is a neighbors table. + + +# to_graphviz :: Vertex -> Map(Vertex, [(Vertex, Weight)]) -> String +def to_graphviz(start, g): + """Compiles the graph into GraphViz.""" + d = Digraph() + q = deque() + seen = set() + + q.append(start) + + while q: + v = q.popleft() + if v in seen: + continue + d.node(v, label=v) + + for w, x in g[v]: + d.edge(v, x, label=str(w)) + q.append(x) + seen.add(v) + + return d.source + + +with open('/tmp/test.gv', 'w') as f: + src = to_graphviz('a', weighted_graph) + f.write(src) + print('/tmp/test.gv created!') diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/highest-product-of-3.py b/users/wpcarro/scratch/data_structures_and_algorithms/highest-product-of-3.py new file mode 100644 index 000000000000..889663e058da --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/highest-product-of-3.py @@ -0,0 +1,89 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# f :: [Int] -> Int +def highest_product_of_3(xs): + """Here we're greedily storing: + - current max + - largest product of two + - largest positive number + - second largest positive number + - largest negative number + """ + if len(xs) < 3: + raise Exception + + cm = None + ld = xs[0] * xs[1] + l2 = min(xs[0], xs[1]) + if xs[0] < 0 or xs[1] < 0: + ln = min(xs[0], xs[1]) + else: + ln = 1 + l = max(xs[0], xs[1]) + + for x in xs[2:]: + if not cm: + cm = max(x * ln * l, ld * x, x * l * l2) # beware + ld = max(ld, x * ln, x * l) + ln = min(ln, x) + l = max(l, x) + if x < l: + l2 = max(l2, x) + else: + cm = max(cm, x * ln * l, x * ld, x * l * l2) + ld = max(ld, x * ln, x * l) + ln = min(ln, x) + l = max(l, x) + if x < l: + l2 = max(l2, x) + + return cm + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_short_list(self): + actual = highest_product_of_3([1, 2, 3, 4]) + expected = 24 + self.assertEqual(actual, expected) + + def test_longer_list(self): + actual = highest_product_of_3([6, 1, 3, 5, 7, 8, 2]) + expected = 336 + self.assertEqual(actual, expected) + + def test_list_has_one_negative(self): + actual = highest_product_of_3([-5, 4, 8, 2, 3]) + expected = 96 + self.assertEqual(actual, expected) + + def test_list_has_two_negatives(self): + actual = highest_product_of_3([-10, 1, 3, 2, -10]) + expected = 300 + self.assertEqual(actual, expected) + + def test_list_is_all_negatives(self): + actual = highest_product_of_3([-5, -1, -3, -2]) + expected = -6 + self.assertEqual(actual, expected) + + def test_error_with_empty_list(self): + with self.assertRaises(Exception): + highest_product_of_3([]) + + def test_error_with_one_number(self): + with self.assertRaises(Exception): + highest_product_of_3([1]) + + def test_error_with_two_numbers(self): + with self.assertRaises(Exception): + highest_product_of_3([1, 1]) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/inflight-entertainment.py b/users/wpcarro/scratch/data_structures_and_algorithms/inflight-entertainment.py new file mode 100644 index 000000000000..6e17baef3709 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/inflight-entertainment.py @@ -0,0 +1,35 @@ +# possible :: Int -> [Int] -> Bool +def possible(flight_duration, film_durations): + seeking = set() + + for x in film_durations: + if x in seeking: + return True + else: + seeking.add(flight_duration - x) + + return False + + +should = [ + (10, [1, 9, 8, 8, 8]), + (10, [1, 9]), + (10, [1, 9, 5, 5, 6]), + (1, [0.5, 0.5]), + (1, [0.5, 0.5]), +] + +for a, b in should: + print("Testing: %s %s" % (a, b)) + assert possible(a, b) + +shouldnt = [ + (10, [1, 10, 1, 2, 1, 12]), + (1, [0.25, 0.25, 0.25, 0.25]), + (5, [1, 2, 2]), +] +for a, b in shouldnt: + print("Testing: %s %s" % (a, b)) + assert not possible(a, b) + +print("Tests pass") diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/knapsack-0-1.py b/users/wpcarro/scratch/data_structures_and_algorithms/knapsack-0-1.py new file mode 100644 index 000000000000..c72d19d4ed73 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/knapsack-0-1.py @@ -0,0 +1,38 @@ +import unittest +from math import floor + + +def knapify(xs, capacity=None): + assert capacity is not None + n = len(xs) + # For 0/1 Knapsack, we must use a table, since this will encode which values + # work for which items. This is cleaner than including a separate data + # structure to capture it. + maxes = [[0 for x in range(capacity + 1)] for x in range(n + 1)] + + # Build table maxes[][] in bottom up manner + for row in range(n + 1): + for col in range(capacity + 1): + if row == 0 or col == 0: + maxes[row][col] = 0 + elif xs[row - 1][0] <= col: + maxes[row][col] = max( + xs[row - 1][1] + maxes[row - 1][col - xs[row - 1][0]], + maxes[row - 1][col]) + else: + maxes[row][col] = maxes[row - 1][col] + + return maxes[-1][capacity] + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_one_cake(self): + actual = knapify([(3, 10), (2, 15), (7, 2), (12, 20)], capacity=12) + expected = None + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/kth-to-last.py b/users/wpcarro/scratch/data_structures_and_algorithms/kth-to-last.py new file mode 100644 index 000000000000..8291e54533d5 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/kth-to-last.py @@ -0,0 +1,82 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def length(x): + if not x: + return 0 + else: + count = 1 + while x: + x = x.next + count += 1 + return count + + +def kth_to_last_node(k, x): + hops = length(x) - 1 + dest = hops - k + + if k == 0: + raise Exception("Our God doesn't support this kind of behavior.") + + if dest < 0: + raise Exception('Value k to high for list.') + + while dest > 0: + x = x.next + dest -= 1 + + return x + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def setUp(self): + self.fourth = Test.LinkedListNode(4) + self.third = Test.LinkedListNode(3, self.fourth) + self.second = Test.LinkedListNode(2, self.third) + self.first = Test.LinkedListNode(1, self.second) + + def test_first_to_last_node(self): + actual = kth_to_last_node(1, self.first) + expected = self.fourth + self.assertEqual(actual, expected) + + def test_second_to_last_node(self): + actual = kth_to_last_node(2, self.first) + expected = self.third + self.assertEqual(actual, expected) + + def test_first_node(self): + actual = kth_to_last_node(4, self.first) + expected = self.first + self.assertEqual(actual, expected) + + def test_k_greater_than_linked_list_length(self): + with self.assertRaises(Exception): + kth_to_last_node(5, self.first) + + def test_k_is_zero(self): + with self.assertRaises(Exception): + kth_to_last_node(0, self.first) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/largest-stack.py b/users/wpcarro/scratch/data_structures_and_algorithms/largest-stack.py new file mode 100644 index 000000000000..aab9671eb6d3 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/largest-stack.py @@ -0,0 +1,107 @@ +import unittest + + +class Stack(object): + def __init__(self): + """Initialize an empty stack""" + self.items = [] + + def push(self, item): + """Push a new item onto the stack""" + self.items.append(item) + + def pop(self): + """Remove and return the last item""" + # If the stack is empty, return None + # (it would also be reasonable to throw an exception) + if not self.items: + return None + + return self.items.pop() + + def peek(self): + """Return the last item without removing it""" + if not self.items: + return None + return self.items[-1] + + +class MaxStack(object): + # Implement the push, pop, and get_max methods + def __init__(self): + self.m = Stack() + self.stack = Stack() + + def push(self, item): + if self.m.peek() is None: + self.m.push(item) + elif item >= self.m.peek(): + self.m.push(item) + self.stack.push(item) + + def pop(self): + x = self.stack.pop() + if x == self.m.peek(): + self.m.pop() + return x + + def get_max(self): + return self.m.peek() + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_stack_usage(self): + max_stack = MaxStack() + + max_stack.push(5) + + actual = max_stack.get_max() + expected = 5 + self.assertEqual(actual, expected) + + max_stack.push(4) + max_stack.push(7) + max_stack.push(7) + max_stack.push(8) + + actual = max_stack.get_max() + expected = 8 + self.assertEqual(actual, expected) + + actual = max_stack.pop() + expected = 8 + self.assertEqual(actual, expected) + + actual = max_stack.get_max() + expected = 7 + self.assertEqual(actual, expected) + + actual = max_stack.pop() + expected = 7 + self.assertEqual(actual, expected) + + actual = max_stack.get_max() + expected = 7 + self.assertEqual(actual, expected) + + actual = max_stack.pop() + expected = 7 + self.assertEqual(actual, expected) + + actual = max_stack.get_max() + expected = 5 + self.assertEqual(actual, expected) + + actual = max_stack.pop() + expected = 4 + self.assertEqual(actual, expected) + + actual = max_stack.get_max() + expected = 5 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/linked-list-cycles.py b/users/wpcarro/scratch/data_structures_and_algorithms/linked-list-cycles.py new file mode 100644 index 000000000000..75a4b993944c --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/linked-list-cycles.py @@ -0,0 +1,88 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def contains_cycle(x): + if not x: + return False + elif not x.next: + return False + + a = x + b = x.next + + while b.next: + if a == b: + return True + + a = a.next + b = b.next.next + + return False + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def test_linked_list_with_no_cycle(self): + fourth = Test.LinkedListNode(4) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + result = contains_cycle(first) + self.assertFalse(result) + + def test_cycle_loops_to_beginning(self): + fourth = Test.LinkedListNode(4) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + fourth.next = first + result = contains_cycle(first) + self.assertTrue(result) + + def test_cycle_loops_to_middle(self): + fifth = Test.LinkedListNode(5) + fourth = Test.LinkedListNode(4, fifth) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + fifth.next = third + result = contains_cycle(first) + self.assertTrue(result) + + def test_two_node_cycle_at_end(self): + fifth = Test.LinkedListNode(5) + fourth = Test.LinkedListNode(4, fifth) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + fifth.next = fourth + result = contains_cycle(first) + self.assertTrue(result) + + def test_empty_list(self): + result = contains_cycle(None) + self.assertFalse(result) + + def test_one_element_linked_list_no_cycle(self): + first = Test.LinkedListNode(1) + result = contains_cycle(first) + self.assertFalse(result) + + def test_one_element_linked_list_cycle(self): + first = Test.LinkedListNode(1) + first.next = first + result = contains_cycle(first) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/memo.py b/users/wpcarro/scratch/data_structures_and_algorithms/memo.py new file mode 100644 index 000000000000..44ea93e1bd49 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/memo.py @@ -0,0 +1,60 @@ +import time +import random +from heapq import heappush, heappop + + +class Memo(object): + def __init__(self, size=1): + """ + Create a key-value data-structure that will never exceed `size` + members. Memo evicts the least-recently-accessed elements from itself + before adding inserting new key-value pairs. + """ + if size <= 0: + raise Exception("We do not support an empty memo") + self.xs = {} + self.heap = [(0, None)] * size + + def contains(self, k): + """ + Return true if key `k` exists in the Memo. + """ + return k in self.xs + + def get(self, k): + """ + Return the memoized item at key `k`. + """ + # "touch" the element in the heap + return self.xs[k] + + def set(self, k, v): + """ + Memoize value `v` at key `k`. + """ + _, to_evict = heappop(self.heap) + if to_evict != None: + del self.xs[to_evict] + heappush(self.heap, (time.time(), k)) + self.xs[k] = v + + +memo = Memo(size=10) + + +def f(x): + """ + Compute some mysterious, expensive function. + """ + if memo.contains(x): + print("Hit.\t\tf({})".format(x)) + return memo.get(x) + else: + print("Computing...\tf({})".format(x)) + time.sleep(0.25) + res = random.randint(0, 10) + memo.set(x, res) + return res + + +[f(random.randint(0, 10)) for _ in range(10)] diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/merge-sort.py b/users/wpcarro/scratch/data_structures_and_algorithms/merge-sort.py new file mode 100644 index 000000000000..6dbe0fa0f3c3 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/merge-sort.py @@ -0,0 +1,28 @@ + + + +# merge :: [a] -> [a] -> [a] +# merge([], []): [] +# merge(xs, []): xs +# merge([], ys): ys +# merge(xs@[x|xs'], ys@[y|ys']) +# when y =< x: cons(y, merge(xs, ys')) +# when x < y: cons(x, merge(xs', ys)) +def merge(xs, ys): + if xs == [] and ys == []: + return [] + elif ys == []: + return xs + elif xs == []: + return ys + else: + x = xs[0] + y = ys[0] + + if y <= x: + return [y] + merge(xs, ys[1:]) + else: + return [x] + merge(xs[1:], ys) + +print(merge([3, 4, 6, 10, 11, 15], + [1, 5, 8, 12, 14, 19])) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/merging-ranges.py b/users/wpcarro/scratch/data_structures_and_algorithms/merging-ranges.py new file mode 100644 index 000000000000..4e3604d5bcca --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/merging-ranges.py @@ -0,0 +1,94 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# do_merge_ranges :: [(Int, Int)] -> [(Int, Int)] -> [(Int, Int)] +def do_merge_ranges(prev, xs): + if len(xs) == 0: + return prev + elif len(xs) == 1: + return prev + xs + else: + a1, a2 = xs[0] + b1, b2 = xs[1] + rest = xs[2:] + if b1 <= a2: + return do_merge_ranges(prev, [(a1, max(a2, b2))] + rest) + else: + return do_merge_ranges(prev + [(a1, a2)], [(b1, b2)] + rest) + + +# merge_ranges :: [(Int, Int)] -> [(Int, Int)] +def merge_ranges(xs): + xs = xs[:] + xs.sort() + return do_merge_ranges([], xs) + + +# merge_ranges_b :: [(Int, Int)] -> [(Int, Int)] +def merge_ranges_b(xs): + fi = 0 + ci = 1 + result = [] + xs = xs[:] + xs.sort() + while ci < len(xs): + while ci < len(xs) and xs[ci][0] <= xs[fi][1]: + xs[fi] = xs[fi][0], max(xs[ci][1], xs[fi][1]) + ci += 1 + result.append(xs[fi]) + fi = ci + ci += 1 + if fi < len(xs): + result.append(xs[fi]) + return result + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_meetings_overlap(self): + actual = merge_ranges([(1, 3), (2, 4)]) + expected = [(1, 4)] + self.assertEqual(actual, expected) + + def test_meetings_touch(self): + actual = merge_ranges([(5, 6), (6, 8)]) + expected = [(5, 8)] + self.assertEqual(actual, expected) + + def test_meeting_contains_other_meeting(self): + actual = merge_ranges([(1, 8), (2, 5)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_stay_separate(self): + actual = merge_ranges([(1, 3), (4, 8)]) + expected = [(1, 3), (4, 8)] + self.assertEqual(actual, expected) + + def test_multiple_merged_meetings(self): + actual = merge_ranges([(1, 4), (2, 5), (5, 8)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_not_sorted(self): + actual = merge_ranges([(5, 8), (1, 4), (6, 8)]) + expected = [(1, 4), (5, 8)] + self.assertEqual(actual, expected) + + def test_one_long_meeting_contains_smaller_meetings(self): + actual = merge_ranges([(1, 10), (2, 5), (6, 8), (9, 10), (10, 12)]) + expected = [(1, 12)] + self.assertEqual(actual, expected) + + def test_sample_input(self): + actual = merge_ranges([(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)]) + expected = [(0, 1), (3, 8), (9, 12)] + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.gv b/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.gv new file mode 100644 index 000000000000..1e67c3954f5f --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.gv @@ -0,0 +1,11 @@ +strict graph { + Min -- {William, Jayden, Omar} + William -- {Min, Noam} + Jayden -- {Min, Amelia, Ren, Noam} + Adam -- {Amelia, Miguel, Sofia, Lucas} + Ren -- {Jayden, Omar} + Amelia -- {Jayden, Adam, Miguel} + Miguel -- {Amelia, Adam, Liam, Nathan} + Noam -- {Nathan, Jayden, William} + Omar -- {Ren, Min, Scott} +} diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.py b/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.py new file mode 100644 index 000000000000..c9d7d9d74151 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/mesh-message.py @@ -0,0 +1,97 @@ +import unittest +from collections import deque + + +################################################################################ +# Solution +################################################################################ +# get_path :: G(V, E) -> V -> V -> Maybe([V]) +def get_path(g, src, dst): + q = deque() + result = None + seen = set() + q.append(([], src)) + + if src not in g or dst not in g: + raise Exception + + while q: + p, node = q.popleft() + + seen.add(node) + + if node == dst: + if not result: + result = p + [node] + elif len(p + [node]) < len(result): + result = p + [node] + else: + if node not in g: + raise Exception + for x in g.get(node): + if not x in seen: + q.append((p + [node], x)) + + return result + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def setUp(self): + self.graph = { + 'a': ['b', 'c', 'd'], + 'b': ['a', 'd'], + 'c': ['a', 'e'], + 'd': ['a', 'b'], + 'e': ['c'], + 'f': ['g'], + 'g': ['f'], + } + + def test_two_hop_path_1(self): + actual = get_path(self.graph, 'a', 'e') + expected = ['a', 'c', 'e'] + self.assertEqual(actual, expected) + + def test_two_hop_path_2(self): + actual = get_path(self.graph, 'd', 'c') + expected = ['d', 'a', 'c'] + self.assertEqual(actual, expected) + + def test_one_hop_path_1(self): + actual = get_path(self.graph, 'a', 'c') + expected = ['a', 'c'] + self.assertEqual(actual, expected) + + def test_one_hop_path_2(self): + actual = get_path(self.graph, 'f', 'g') + expected = ['f', 'g'] + self.assertEqual(actual, expected) + + def test_one_hop_path_3(self): + actual = get_path(self.graph, 'g', 'f') + expected = ['g', 'f'] + self.assertEqual(actual, expected) + + def test_zero_hop_path(self): + actual = get_path(self.graph, 'a', 'a') + expected = ['a'] + self.assertEqual(actual, expected) + + def test_no_path(self): + actual = get_path(self.graph, 'a', 'f') + expected = None + self.assertEqual(actual, expected) + + def test_start_node_not_present(self): + with self.assertRaises(Exception): + get_path(self.graph, 'h', 'a') + + def test_end_node_not_present(self): + with self.assertRaises(Exception): + get_path(self.graph, 'a', 'h') + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/norman.py b/users/wpcarro/scratch/data_structures_and_algorithms/norman.py new file mode 100644 index 000000000000..379ba92abba8 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/norman.py @@ -0,0 +1,78 @@ + + + +# Write a function with the following type signature:L +# equal? :: String -> String -> Bool +# +# Determine equality between two inputs with backspace characters encoded as +# "<". + +################################################################################ +# Solution 1 +################################################################################ + +# from collections import deque + +# def equal(a, b): +# sa = deque() +# sb = deque() + +# for c in a: +# if c == '<': +# sa.pop() +# else: +# sa.append(c) + +# for c in b: +# if c == '<': +# sb.pop() +# else: +# sb.append(c) + +# return sa == sb + +################################################################################ +# Solution 2 +################################################################################ + +def handle_dels(num_dels, i, xs): + if i < 0: + return -1 + + while xs[i] == '<': + num_dels += 1 + i -= 1 + + while num_dels > 0 and xs[i] != '<': + num_dels -= 1 + i -= 1 + + if xs[i] == '<': + return handle_dels(num_dels, i, xs) + else: + return i + +def update_index(i, xs): + # TODO: Indexing into non-available parts of a string. + if xs[i] != '<' and xs[i - 1] != '<': + return i - 1 + + elif xs[i - 1] == '<': + return handle_dels(0, i - 1, xs) + +def equal(a, b): + ia = len(a) - 1 + ib = len(b) - 1 + + while ia >= 0 and ib >= 0: + if a[ia] != b[ib]: + return False + ia = update_index(ia, a) + ib = update_index(ib, b) + + if ia != 0: + return update_index(ia, a) <= -1 + if ib != 0: + return update_index(ib, b) <= -1 + + return True diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/nth-fibonacci.py b/users/wpcarro/scratch/data_structures_and_algorithms/nth-fibonacci.py new file mode 100644 index 000000000000..cdb2846ea338 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/nth-fibonacci.py @@ -0,0 +1,59 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +def fib(n): + """This should be accomplishable in O(1) space.""" + if n in {0, 1}: + return n + a = 0 # i = 0 + b = 1 # i = 1 + for x in range(2, n + 1): + result = a + b + a = b + b = result + return result + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_zeroth_fibonacci(self): + actual = fib(0) + expected = 0 + self.assertEqual(actual, expected) + + def test_first_fibonacci(self): + actual = fib(1) + expected = 1 + self.assertEqual(actual, expected) + + def test_second_fibonacci(self): + actual = fib(2) + expected = 1 + self.assertEqual(actual, expected) + + def test_third_fibonacci(self): + actual = fib(3) + expected = 2 + self.assertEqual(actual, expected) + + def test_fifth_fibonacci(self): + actual = fib(5) + expected = 5 + self.assertEqual(actual, expected) + + def test_tenth_fibonacci(self): + actual = fib(10) + expected = 55 + self.assertEqual(actual, expected) + + def test_negative_fibonacci(self): + with self.assertRaises(Exception): + fib(-1) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/optimal-stopping.py b/users/wpcarro/scratch/data_structures_and_algorithms/optimal-stopping.py new file mode 100644 index 000000000000..af13239941d0 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/optimal-stopping.py @@ -0,0 +1,49 @@ +from random import choice +from math import floor + +# Applying Chapter 1 from "Algorithms to Live By", which describes optimal +# stopping problems. Technically this simulation is invalid because the +# `candidates` function takes a lower bound and an upper bound, which allows us +# to know the cardinal number of an individual candidates. The "look then leap" +# algorithm is ideal for no-information games - i.e. games when upper and lower +# bounds aren't known. The `look_then_leap/1` function is ignorant of this +# information, so it behaves as if in a no-information game. Strangely enough, +# this algorithm will pick the best candidate 37% of the time. +# +# Chapter 1 describes two algorithms: +# 1. Look-then-leap: ordinal numbers - i.e. no-information games. Look-then-leap +# finds the best candidate 37% of the time. +# 2. Threshold: cardinal numbers - i.e. where upper and lower bounds are +# known. The Threshold algorithm finds the best candidate ~55% of the time. +# +# All of this and more can be studied as "optimal stopping theory". This applies +# to finding a spouse, parking a car, picking an apartment in a city, and more. + + +# candidates :: Int -> Int -> Int -> [Int] +def candidates(lb, ub, ct): + xs = list(range(lb, ub + 1)) + return [choice(xs) for _ in range(ct)] + + +# look_then_leap :: [Integer] -> Integer +def look_then_leap(candidates): + best = candidates[0] + seen_ct = 1 + ignore_ct = floor(len(candidates) * 0.37) + for x in candidates[1:]: + if ignore_ct > 0: + ignore_ct -= 1 + best = max(best, x) + else: + if x > best: + print('Choosing the {} candidate.'.format(seen_ct)) + return x + seen_ct += 1 + print('You may have waited too long.') + return candidates[-1] + + +candidates = candidates(1, 100, 100) +print(candidates) +print(look_then_leap(candidates)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/perm-tree.py b/users/wpcarro/scratch/data_structures_and_algorithms/perm-tree.py new file mode 100644 index 000000000000..0eb389c26bb9 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/perm-tree.py @@ -0,0 +1,83 @@ +import unittest + + +################################################################################ +# Answer +################################################################################ +class Node(object): + def __init__(self, value, children=set()): + self.value = value + self.children = children + + +# treeify :: Char -> Set(Char) -> Node(Char) +def treeify(x, xs): + return Node(x, [treeify(c, xs - {c}) for c in xs]) + + +# dft :: Node(Char) -> [String] +def dft(node): + result = [] + s = [] + + s.append(('', node)) + + while s: + p, n = s.pop() + p += str(n.value) + + if not n.children: + result.append(p) + else: + for c in n.children: + s.append((p, c)) + + return result + + +# main :: String -> Set(String) +def get_permutations(xs): + if xs == '': + return set(['']) + + ys = set(xs) + trees = [] + + for y in ys: + trees.append(treeify(y, ys - {y})) + + result = set() + + for t in trees: + for d in dft(t): + result.add(d) + + return result + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_empty_string(self): + actual = get_permutations('') + expected = set(['']) + self.assertEqual(actual, expected) + + def test_one_character_string(self): + actual = get_permutations('a') + expected = set(['a']) + self.assertEqual(actual, expected) + + def test_two_character_string(self): + actual = get_permutations('ab') + expected = set(['ab', 'ba']) + self.assertEqual(actual, expected) + + def test_three_character_string(self): + actual = get_permutations('abc') + expected = set(['abc', 'acb', 'bac', 'bca', 'cab', 'cba']) + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/permutation-palindrome.py b/users/wpcarro/scratch/data_structures_and_algorithms/permutation-palindrome.py new file mode 100644 index 000000000000..0a2136a408f2 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/permutation-palindrome.py @@ -0,0 +1,49 @@ +from collections import Counter +import unittest + + +################################################################################ +# Impl +################################################################################ +# palindromifiable :: String -> Boolean +def has_palindrome_permutation(x): + bag = Counter(x) + odd_entries_ct = 0 + + for _, y in bag.items(): + if y % 2 != 0: + odd_entries_ct += 1 + + return odd_entries_ct in {0, 1} + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_permutation_with_odd_number_of_chars(self): + result = has_palindrome_permutation('aabcbcd') + self.assertTrue(result) + + def test_permutation_with_even_number_of_chars(self): + result = has_palindrome_permutation('aabccbdd') + self.assertTrue(result) + + def test_no_permutation_with_odd_number_of_chars(self): + result = has_palindrome_permutation('aabcd') + self.assertFalse(result) + + def test_no_permutation_with_even_number_of_chars(self): + result = has_palindrome_permutation('aabbcd') + self.assertFalse(result) + + def test_empty_string(self): + result = has_palindrome_permutation('') + self.assertTrue(result) + + def test_one_character_string(self): + result = has_palindrome_permutation('a') + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/permutations.py b/users/wpcarro/scratch/data_structures_and_algorithms/permutations.py new file mode 100644 index 000000000000..fc2c1ef7eebc --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/permutations.py @@ -0,0 +1,55 @@ +class Node(object): + # ctor :: a -> [a] -> Node(a) + def __init__(self, value, children=[]): + self.value = value + self.children = children + + +# is_leaf :: Node(a) -> Boolean +def is_leaf(node): + return len(node.children) == 0 + + +# enumerate :: Node(a) -> Set(List(a)) +def enumerate(node): + current = [] + result = [] + q = [] + + q.append(node) + + while q: + x = q.pop() + print(x.value) + + for c in x.children: + q.append(c) + + current.append(x.value) + print(current) + + if is_leaf(x): + result.append(current) + print("Reseting current") + current = [] + + return result + + +node = Node('root', [ + Node('a', [ + Node('b', [Node('c')]), + Node('c', [Node('b')]), + ]), + Node('b', [ + Node('a', [Node('c')]), + Node('c', [Node('a')]), + ]), + Node('c', [ + Node('a', [Node('b')]), + Node('b', [Node('a')]), + ]) +]) + +print('----------') +print(enumerate(node)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/plot.py b/users/wpcarro/scratch/data_structures_and_algorithms/plot.py new file mode 100644 index 000000000000..5601891a0d9b --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/plot.py @@ -0,0 +1,9 @@ +import numpy as np +import matplotlib.pyplot as plt + +rng = np.random.RandomState(10) # deterministic random data +a = np.hstack((rng.normal(size=1000), rng.normal(loc=5, scale=2, size=1000))) +_ = plt.hist(a, bins='auto') # arguments are passed to np.histogram +plt.title("Histogram with 'auto' bins") +Text(0.5, 1.0, "Histogram with 'auto' bins") +plt.show() diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/product-of-other-numbers.py b/users/wpcarro/scratch/data_structures_and_algorithms/product-of-other-numbers.py new file mode 100644 index 000000000000..d05e82d42b02 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/product-of-other-numbers.py @@ -0,0 +1,68 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# f :: [Int] -> [Int] +def get_products_of_all_ints_except_at_index(xs): + if len(xs) in {0, 1}: + raise Exception + + ct = len(xs) + lefts = [1] * ct + rights = [1] * ct + result = [] + + for i in range(1, ct): + lefts[i] = lefts[i - 1] * xs[i - 1] + for i in range(ct - 2, -1, -1): + rights[i] = rights[i + 1] * xs[i + 1] + + return [lefts[i] * rights[i] for i in range(ct)] + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_small_list(self): + actual = get_products_of_all_ints_except_at_index([1, 2, 3]) + expected = [6, 3, 2] + self.assertEqual(actual, expected) + + def test_longer_list(self): + actual = get_products_of_all_ints_except_at_index([8, 2, 4, 3, 1, 5]) + expected = [120, 480, 240, 320, 960, 192] + self.assertEqual(actual, expected) + + def test_list_has_one_zero(self): + actual = get_products_of_all_ints_except_at_index([6, 2, 0, 3]) + expected = [0, 0, 36, 0] + self.assertEqual(actual, expected) + + def test_list_has_two_zeros(self): + actual = get_products_of_all_ints_except_at_index([4, 0, 9, 1, 0]) + expected = [0, 0, 0, 0, 0] + self.assertEqual(actual, expected) + + def test_one_negative_number(self): + actual = get_products_of_all_ints_except_at_index([-3, 8, 4]) + expected = [32, -12, -24] + self.assertEqual(actual, expected) + + def test_all_negative_numbers(self): + actual = get_products_of_all_ints_except_at_index([-7, -1, -4, -2]) + expected = [-8, -56, -14, -28] + self.assertEqual(actual, expected) + + def test_error_with_empty_list(self): + with self.assertRaises(Exception): + get_products_of_all_ints_except_at_index([]) + + def test_error_with_one_number(self): + with self.assertRaises(Exception): + get_products_of_all_ints_except_at_index([1]) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/queue-two-stacks.py b/users/wpcarro/scratch/data_structures_and_algorithms/queue-two-stacks.py new file mode 100644 index 000000000000..63da08ebf79a --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/queue-two-stacks.py @@ -0,0 +1,66 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +class QueueTwoStacks(object): + def __init__(self): + self.a = [] + self.b = [] + + def enqueue(self, x): + self.a.append(x) + + def dequeue(self): + if self.b: + return self.b.pop() + else: + while self.a: + self.b.append(self.a.pop()) + return self.dequeue() + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_basic_queue_operations(self): + queue = QueueTwoStacks() + queue.enqueue(1) + queue.enqueue(2) + queue.enqueue(3) + actual = queue.dequeue() + expected = 1 + self.assertEqual(actual, expected) + actual = queue.dequeue() + expected = 2 + self.assertEqual(actual, expected) + queue.enqueue(4) + actual = queue.dequeue() + expected = 3 + self.assertEqual(actual, expected) + actual = queue.dequeue() + expected = 4 + self.assertEqual(actual, expected) + + def test_error_when_dequeue_from_new_queue(self): + queue = QueueTwoStacks() + with self.assertRaises(Exception): + queue.dequeue() + + def test_error_when_dequeue_from_empty_queue(self): + queue = QueueTwoStacks() + queue.enqueue(1) + queue.enqueue(2) + actual = queue.dequeue() + expected = 1 + self.assertEqual(actual, expected) + actual = queue.dequeue() + expected = 2 + self.assertEqual(actual, expected) + with self.assertRaises(Exception): + queue.dequeue() + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/rectangular-love.py b/users/wpcarro/scratch/data_structures_and_algorithms/rectangular-love.py new file mode 100644 index 000000000000..47c0f53979c6 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/rectangular-love.py @@ -0,0 +1,246 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# bottom :: Rectangle -> Int +def bottom(x): + return x.get('bottom_y') + + +# top :: Rectangle -> Int +def top(x): + return bottom(x) + x.get('height') + + +# left :: Rectangle -> Int +def left(x): + return x.get('left_x') + + +# right :: Rectangle -> Int +def right(x): + return left(x) + x.get('width') + + +# sort_highest :: Rectangle -> Rectangle -> (Rectangle, Rectangle) +def sort_highest(x, y): + if top(x) >= top(y): + return x, y + else: + return y, x + + +# sort_leftmost :: Rectangle -> Rectangle -> (Rectangle, Rectangle) +def sort_leftmost(x, y): + if left(x) <= left(y): + return x, y + else: + return y, x + + +# rectify :: Int -> Int -> Int -> Int -> Rectify +def rectify(top=None, bottom=None, left=None, right=None): + assert top >= bottom + assert left <= right + return { + 'left_x': left, + 'bottom_y': bottom, + 'width': right - left, + 'height': top - bottom, + } + + +# empty_rect :: Rectangle +def empty_rect(): + return { + 'left_x': None, + 'bottom_y': None, + 'width': None, + 'height': None, + } + + +# find_rectangular_overlap :: Rectangle -> Rectangle -> Maybe(Rectangle) +def find_rectangular_overlap(x, y): + ha, hb = sort_highest(x, y) + la, lb = sort_leftmost(x, y) + + if bottom(hb) <= top(hb) <= bottom(ha) <= top(ha): + return empty_rect() + + if left(la) <= right(la) <= left(lb) <= right(lb): + return empty_rect() + + # We should have an intersection here. + verts = [bottom(ha), top(ha), bottom(hb), top(hb)] + verts.sort() + horzs = [left(la), right(la), left(lb), right(lb)] + horzs.sort() + return rectify(top=verts[2], + bottom=verts[1], + left=horzs[1], + right=horzs[2]) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_overlap_along_both_axes(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 1, + 'width': 6, + 'height': 3, + } + rect2 = { + 'left_x': 5, + 'bottom_y': 2, + 'width': 3, + 'height': 6, + } + expected = { + 'left_x': 5, + 'bottom_y': 2, + 'width': 2, + 'height': 2, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_one_rectangle_inside_another(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 1, + 'width': 6, + 'height': 6, + } + rect2 = { + 'left_x': 3, + 'bottom_y': 3, + 'width': 2, + 'height': 2, + } + expected = { + 'left_x': 3, + 'bottom_y': 3, + 'width': 2, + 'height': 2, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_both_rectangles_the_same(self): + rect1 = { + 'left_x': 2, + 'bottom_y': 2, + 'width': 4, + 'height': 4, + } + rect2 = { + 'left_x': 2, + 'bottom_y': 2, + 'width': 4, + 'height': 4, + } + expected = { + 'left_x': 2, + 'bottom_y': 2, + 'width': 4, + 'height': 4, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_touch_on_horizontal_edge(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 2, + 'width': 3, + 'height': 4, + } + rect2 = { + 'left_x': 2, + 'bottom_y': 6, + 'width': 2, + 'height': 2, + } + expected = { + 'left_x': None, + 'bottom_y': None, + 'width': None, + 'height': None, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_touch_on_vertical_edge(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 2, + 'width': 3, + 'height': 4, + } + rect2 = { + 'left_x': 4, + 'bottom_y': 3, + 'width': 2, + 'height': 2, + } + expected = { + 'left_x': None, + 'bottom_y': None, + 'width': None, + 'height': None, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_touch_at_a_corner(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 1, + 'width': 2, + 'height': 2, + } + rect2 = { + 'left_x': 3, + 'bottom_y': 3, + 'width': 2, + 'height': 2, + } + expected = { + 'left_x': None, + 'bottom_y': None, + 'width': None, + 'height': None, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + def test_no_overlap(self): + rect1 = { + 'left_x': 1, + 'bottom_y': 1, + 'width': 2, + 'height': 2, + } + rect2 = { + 'left_x': 4, + 'bottom_y': 6, + 'width': 3, + 'height': 6, + } + expected = { + 'left_x': None, + 'bottom_y': None, + 'width': None, + 'height': None, + } + actual = find_rectangular_overlap(rect1, rect2) + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/recursive-string-permutations.py b/users/wpcarro/scratch/data_structures_and_algorithms/recursive-string-permutations.py new file mode 100644 index 000000000000..70461ddf5dac --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/recursive-string-permutations.py @@ -0,0 +1,37 @@ +import unittest + + +################################################################################ +# Implementation +################################################################################ +# get_permutations :: String -> Set(String) +def get_permutations(string): + pass + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_empty_string(self): + actual = get_permutations('') + expected = set(['']) + self.assertEqual(actual, expected) + + def test_one_character_string(self): + actual = get_permutations('a') + expected = set(['a']) + self.assertEqual(actual, expected) + + def test_two_character_string(self): + actual = get_permutations('ab') + expected = set(['ab', 'ba']) + self.assertEqual(actual, expected) + + def test_three_character_string(self): + actual = get_permutations('abc') + expected = set(['abc', 'acb', 'bac', 'bca', 'cab', 'cba']) + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/reverse-linked-list.py b/users/wpcarro/scratch/data_structures_and_algorithms/reverse-linked-list.py new file mode 100644 index 000000000000..b7396b20ce3f --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/reverse-linked-list.py @@ -0,0 +1,79 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# reverse :: List(a) -> List(a) +def reverse(node): + curr = node + prev = None + while curr: + nxt = curr.next + curr.next = prev + prev = curr + curr = nxt + # Make sure to understand the spec! Debugging takes time. Rewriting takes + # time. + return prev + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def test_short_linked_list(self): + second = Test.LinkedListNode(2) + first = Test.LinkedListNode(1, second) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [2, 1] + self.assertEqual(actual, expected) + + def test_long_linked_list(self): + sixth = Test.LinkedListNode(6) + fifth = Test.LinkedListNode(5, sixth) + fourth = Test.LinkedListNode(4, fifth) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [6, 5, 4, 3, 2, 1] + self.assertEqual(actual, expected) + + def test_one_element_linked_list(self): + first = Test.LinkedListNode(1) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [1] + self.assertEqual(actual, expected) + + def test_empty_linked_list(self): + result = reverse(None) + self.assertIsNone(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/reverse-words.py b/users/wpcarro/scratch/data_structures_and_algorithms/reverse-words.py new file mode 100644 index 000000000000..5df12ebabdc7 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/reverse-words.py @@ -0,0 +1,181 @@ +from collections import deque +import unittest + +################################################################################ +# Solution +################################################################################ + + +def rev(xs, i, j): + """Reverse xs in place from [i, j]""" + while i < j: + xs[i], xs[j] = xs[j], xs[i] + i += 1 + j -= 1 + + +def rotate(xs, n, i=None, j=None): + """Mutably rotates list, xs, n times. Positive n values rotate right while + negative n values rotate left. Rotate within window [i, j].""" + i = i or 0 + j = j or len(xs) - 1 + ct = j - i + + if n < 0: + n = abs(n) + p = i + n - 1 + rev(xs, i, p) + rev(xs, p + 1, j) + rev(xs, i, j) + else: + p = j - (n - 1) + rev(xs, p, j) + rev(xs, i, p - 1) + rev(xs, i, j) + return xs + + +def rev_words(xs, i, j): + if j + 1 == len(xs): + return 0 + + while j + 1 < len(xs): + while j + 1 < len(xs) and xs[j + 1] != ' ': + j += 1 + + rev(xs, i, j) + j += 2 + i = j + + return 0 + + +def reverse_words(xs): + # first reverse everything + rev(xs, 0, len(xs) - 1) + return rev_words(xs, 0, 0) + + +def reverse_words_bak(xs, i=None, j=None): + i = i or 0 + j = j or len(xs) - 1 + w0, w1 = [], [] + + if i >= j: + return 0 + + pi = i + while pi < len(xs) and xs[pi] != ' ': + w0.append(xs[pi]) + pi += 1 + + if pi == len(xs): + return 0 + + pj = j + while xs[pj] != ' ': + w1.append(xs[pj]) + pj -= 1 + + d = len(w0) - len(w1) + + rotate(xs, -1 * d, i, j) + + for k in range(len(w1)): + xs[i + k] = w1[len(w1) - 1 - k] + + for k in range(len(w0)): + xs[j - k] = w0[len(w0) - 1 - k] + + while i != j and xs[i] != ' ' and xs[j] != ' ': + i += 1 + j -= 1 + + if i == j: + return 0 + + elif xs[i] == ' ': + while j > 0 and xs[j] != ' ': + j -= 1 + if j == 0: + return 0 + elif xs[j] == ' ': + while i < len(xs) and xs[i] != ' ': + i += 1 + if i == len(xs): + return 0 + return reverse_words(xs, i + 1, j - 1) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_rev(self): + xs = [1, 2, 3, 4, 5] + rev(xs, 0, len(xs) - 1) + self.assertEqual(xs, [5, 4, 3, 2, 1]) + + def test_rotate(self): + ys = [1, 2, 3, 4, 5] + xs = ys[:] + self.assertEqual(rotate(xs, 1, 1, 3), [1, 4, 2, 3, 5]) + xs = ys[:] + self.assertEqual(rotate(xs, -1, 1, 3), [1, 3, 4, 2, 5]) + xs = ys[:] + self.assertEqual(rotate(xs, 1), [5, 1, 2, 3, 4]) + xs = ys[:] + self.assertEqual(rotate(xs, -1), [2, 3, 4, 5, 1]) + xs = ys[:] + self.assertEqual(rotate(xs, -2), [3, 4, 5, 1, 2]) + xs = ys[:] + self.assertEqual(rotate(xs, -5), [1, 2, 3, 4, 5]) + xs = ys[:] + self.assertEqual(rotate(xs, 5), [1, 2, 3, 4, 5]) + xs = ys[:] + self.assertEqual(rotate(xs, 3), [3, 4, 5, 1, 2]) + + def test_one_word(self): + message = list('vault') + reverse_words(message) + expected = list('vault') + self.assertEqual(message, expected) + + def test_two_words(self): + message = list('thief cake') + reverse_words(message) + expected = list('cake thief') + self.assertEqual(message, expected) + + def test_three_words(self): + message = list('one another get') + reverse_words(message) + expected = list('get another one') + self.assertEqual(message, expected) + + def test_multiple_words_same_length(self): + message = list('rat the ate cat the') + reverse_words(message) + expected = list('the cat ate the rat') + self.assertEqual(message, expected) + + def test_multiple_words_different_lengths(self): + message = list('at rat house') + reverse_words(message) + expected = list('house rat at') + self.assertEqual(message, expected) + + def test_multiple_words_different_lengths(self): + message = list('yummy is cake bundt chocolate') + reverse_words(message) + expected = list('chocolate bundt cake is yummy') + self.assertEqual(message, expected) + + def test_empty_string(self): + message = list('') + reverse_words(message) + expected = list('') + self.assertEqual(message, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/second-largest-item-bst.py b/users/wpcarro/scratch/data_structures_and_algorithms/second-largest-item-bst.py new file mode 100644 index 000000000000..bc167d975a7b --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/second-largest-item-bst.py @@ -0,0 +1,179 @@ +import unittest +from collections import deque + + +################################################################################ +# Implementation +################################################################################ +def is_leaf(node): + return node.left is None and node.right is None + + +def find_largest(node): + current = node + while current.right is not None: + current = current.right + return current.value + + +def find_second_largest(node): + history = deque() + current = node + + while current.right: + history.append(current) + current = current.right + + if current.left: + return find_largest(current.left) + elif history: + return history.pop().value + else: + raise TypeError + + +def find_second_largest_backup(node): + history = deque() + current = node + + # traverse -> largest + while current.right: + history.append(current) + current = current.right + + if current.left: + return find_largest(current.left) + elif history: + return history.pop().value + else: + raise ArgumentError + + +# Write a iterative version to avoid consuming memory with the call stack. +# Commenting out the recursive code for now. +def find_second_largest_backup(node): + if node.left is None and node.right is None: + raise ArgumentError + + elif node.right is None and is_leaf(node.left): + return node.left.value + + # recursion + # elif node.right is None: + # return find_largest(node.left) + + # iterative version + elif node.right is None: + current = node.left + while current.right is not None: + current = current.right + return current.value + + # recursion + # TODO: Remove recursion from here. + elif not is_leaf(node.right): + return find_second_largest(node.right) + + # could do an else here, but let's be more assertive. + elif is_leaf(node.right): + return node.value + + else: + raise ArgumentError + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_full_tree(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + right.insert_left(60) + right.insert_right(80) + actual = find_second_largest(tree) + expected = 70 + self.assertEqual(actual, expected) + + def test_largest_has_a_left_child(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + right.insert_left(60) + actual = find_second_largest(tree) + expected = 60 + self.assertEqual(actual, expected) + + def test_largest_has_a_left_subtree(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + right_left = right.insert_left(60) + right_left_left = right_left.insert_left(55) + right_left.insert_right(65) + right_left_left.insert_right(58) + actual = find_second_largest(tree) + expected = 65 + self.assertEqual(actual, expected) + + def test_second_largest_is_root_node(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + actual = find_second_largest(tree) + expected = 50 + self.assertEqual(actual, expected) + + def test_descending_linked_list(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(40) + left_left = left.insert_left(30) + left_left_left = left_left.insert_left(20) + left_left_left.insert_left(10) + actual = find_second_largest(tree) + expected = 40 + self.assertEqual(actual, expected) + + def test_ascending_linked_list(self): + tree = Test.BinaryTreeNode(50) + right = tree.insert_right(60) + right_right = right.insert_right(70) + right_right.insert_right(80) + actual = find_second_largest(tree) + expected = 70 + self.assertEqual(actual, expected) + + def test_error_when_tree_has_one_node(self): + tree = Test.BinaryTreeNode(50) + with self.assertRaises(Exception): + find_second_largest(tree) + + def test_error_when_tree_is_empty(self): + with self.assertRaises(Exception): + find_second_largest(None) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/shortest-path-inject-vertices.py b/users/wpcarro/scratch/data_structures_and_algorithms/shortest-path-inject-vertices.py new file mode 100644 index 000000000000..e08ea66b8f50 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/shortest-path-inject-vertices.py @@ -0,0 +1,94 @@ +from heapq import heappush, heappop +from collections import deque +from fixtures import weighted_graph, expanded_weights_graph + +# UnweightedGraph(a) :: Map(a, Set(a)) +# WeightedGraph(a) :: Map(a, Set(a)) + + +# shortest_path_dijkstra :: Vertex -> Vertex -> WeightedGraph(Vertex) +def shortest_path_dijkstra(a, b, g): + q = [] + seen = set() + + heappush(q, (0, a, [a])) + + while q: + w0, v0, path = heappop(q) + if v0 in seen: + continue + elif v0 == b: + return w0, path + for w1, v1 in g.get(v0): + heappush(q, (w0 + w1, v1, path + [v1])) + seen.add(v0) + return 'weighted', 'pizza' + + +# expand_edge :: Vertex -> (Weight, Vertex) -> Map(Vertex, [Vertex]) +def expand_edge(v0, wv): + w, v1 = wv + assert w > 1 + + result = {v0: ['{}-{}'.format(v1, 1)]} + for x in range(w - 2): + result['{}-{}'.format(v1, x + 1)] = ['{}-{}'.format(v1, x + 2)] + result['{}-{}'.format(v1, w - 1)] = [v1] + + return result + + +# expand_weights :: Vertex -> WeightedGraph(Vertex) -> UnweightedGraph(Vertex) +def expand_weights(v, g): + result = {} + q = deque() + seen = set() + + q.append(v) + while q: + v = d.popleft() + if v in seen: + continue + x = expand_edge(v, g.get) + for w, v1 in g.get(v): + if w > 1: + ws = expand_edge(v, (w, v1)) + result = {**result, **ws} + q.append(v) + pass + + +# shortest_path_inject :: Vertex -> Vertex -> WeightedGraph(Vertex) +def shortest_path_inject(a, b, g): + q = deque() + seen = set() + + q.append((a, [a])) + + while q: + v0, path = q.popleft() + if v0 == 'dummy': + continue + elif v0 in seen: + continue + elif v0 == b: + return len(path), path + for _, v1 in g.get(v0): + q.append((v1, path + [v1])) + seen.add(v0) + continue + + return None, None + + +print(expand_edge('a', (4, 'b'))) +print(expand_edge('a', (5, 'e'))) +assert expand_weights('a', weighted_graph) == expanded_weights_graph +# a = 'a' +# b = 'd' +# w, x = shortest_path_dijkstra(a, b, weighted_graph) +# w1, x1 = shortest_path_inject(a, b, weighted_graph) +# print("[dijkstra] Shortest path from {} to {} is {} with weight {}".format( +# a, b, x, w)) +# print("[injection] Shortest path from {} to {} is {} with weight {}".format( +# a, b, x1, w1)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/shuffle.py b/users/wpcarro/scratch/data_structures_and_algorithms/shuffle.py new file mode 100644 index 000000000000..bdfbad24263c --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/shuffle.py @@ -0,0 +1,34 @@ +import random + + +def get_random(floor, ceiling): + return random.randrange(floor, ceiling + 1) + + +# shuffle_in_place :: [a] -> IO () +def shuffle_in_place(xs): + """Fisher-Yates algorithm. Notice that shuffling here is the same as + selecting a random permutation of the input set, `xs`.""" + n = len(xs) - 1 + for i in range(len(xs)): + r = get_random(i, n) + xs[i], xs[r] = xs[r], xs[i] + return xs + + +# shuffle :: [a] -> [a] +def shuffle_not_in_place(xs): + result = [] + + while xs: + i = get_random(0, len(xs) - 1) + x = xs.pop(i) + result.append(x) + + return result + + +xs = [x for x in range(9)] +print(xs) +# print(shuffle_not_in_place(xs)) +print(shuffle_in_place(xs[:])) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/string-reverse.py b/users/wpcarro/scratch/data_structures_and_algorithms/string-reverse.py new file mode 100644 index 000000000000..8b4cdac1c271 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/string-reverse.py @@ -0,0 +1,22 @@ + +# swap :: Int -> Int -> [Char] -> IO () +def swap(ia, iz, xs): + # handle swap when ia == iz + assert ia <= iz + xs[ia], xs[iz] = xs[iz], xs[ia] + + +# reverse :: [Char] -> IO () +def reverse(xs): + ia = 0 + iz = len(xs) - 1 + + while ia <= iz: + swap(ia, iz, xs) + ia += 1 + iz -= 1 + +x = list("superduperpooper") +reverse(x) +print(x) +print("Tests pass") diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/temperature-tracker.py b/users/wpcarro/scratch/data_structures_and_algorithms/temperature-tracker.py new file mode 100644 index 000000000000..6b042182f01c --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/temperature-tracker.py @@ -0,0 +1,84 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +class TempTracker(object): + def __init__(self): + # min / max + self.min = None + self.max = None + # mean + self.sum = 0 + self.num = 0 + # mode + self.nums = [0] * 111 + self.mode_num = 0 + self.mode = None + + def insert(self, x): + # min / max + if not self.min or x < self.min: + self.min = x + if not self.max or x > self.max: + self.max = x + # mean + self.sum += x + self.num += 1 + # mode + self.nums[x] += 1 + if self.nums[x] >= self.mode_num: + self.mode_num = self.nums[x] + self.mode = x + + def get_max(self): + return self.max + + def get_min(self): + return self.min + + def get_mean(self): + return self.sum / self.num + + def get_mode(self): + return self.mode + + +# Tests + + +class Test(unittest.TestCase): + def test_tracker_usage(self): + tracker = TempTracker() + + tracker.insert(50) + msg = 'failed on first temp recorded' + self.assertEqual(tracker.get_max(), 50, msg='max ' + msg) + self.assertEqual(tracker.get_min(), 50, msg='min ' + msg) + self.assertEqual(tracker.get_mean(), 50.0, msg='mean ' + msg) + self.assertEqual(tracker.get_mode(), 50, msg='mode ' + msg) + + tracker.insert(80) + msg = 'failed on higher temp recorded' + self.assertEqual(tracker.get_max(), 80, msg='max ' + msg) + self.assertEqual(tracker.get_min(), 50, msg='min ' + msg) + self.assertEqual(tracker.get_mean(), 65.0, msg='mean ' + msg) + self.assertIn(tracker.get_mode(), [50, 80], msg='mode ' + msg) + + tracker.insert(80) + msg = 'failed on third temp recorded' + self.assertEqual(tracker.get_max(), 80, msg='max ' + msg) + self.assertEqual(tracker.get_min(), 50, msg='min ' + msg) + self.assertEqual(tracker.get_mean(), 70.0, msg='mean ' + msg) + self.assertEqual(tracker.get_mode(), 80, msg='mode ' + msg) + + tracker.insert(30) + msg = 'failed on lower temp recorded' + self.assertEqual(tracker.get_max(), 80, msg='max ' + msg) + self.assertEqual(tracker.get_min(), 30, msg='min ' + msg) + self.assertEqual(tracker.get_mean(), 60.0, msg='mean ' + msg) + self.assertEqual(tracker.get_mode(), 80, msg='mode ' + msg) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/test.txt b/users/wpcarro/scratch/data_structures_and_algorithms/test.txt new file mode 100644 index 000000000000..ce013625030b --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/test.txt @@ -0,0 +1 @@ +hello diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/top-scores.py b/users/wpcarro/scratch/data_structures_and_algorithms/top-scores.py new file mode 100644 index 000000000000..8e7b073dd8bd --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/top-scores.py @@ -0,0 +1,25 @@ +from collections import deque + +# list: +# array: +# vector: +# bit-{array,vector}: + + +def sort(xs, highest): + v = [0] * (highest + 1) + result = deque() + + for x in xs: + v[x] += 1 + + for i, x in enumerate(v): + if x > 0: + result.appendleft(i) + + return list(result) + + +assert sort([37, 89, 41, 100, 65, 91, 53], + 100) == [100, 91, 89, 65, 53, 41, 37] +print("Tests pass!") diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/topo-sort.py b/users/wpcarro/scratch/data_structures_and_algorithms/topo-sort.py new file mode 100644 index 000000000000..fe295b0279ff --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/topo-sort.py @@ -0,0 +1,31 @@ +from fixtures import unweighted_digraph +from collections import deque + +# vertices_no_in_edges :: UnweightedDigraph -> Set(Vertex) +def vertices_no_in_edges(g): + """Return the vertices in graph `g` with no in-edges.""" + result = set() + vertices = set(g.keys()) + for neighbors in g.values(): + result = result.union(neighbors) + return vertices ^ result + +# topo_sort :: UnweightedDigraph -> List(Vertex) +def topo_sort(g): + q = deque() + seen = set() + result = [] + for x in vertices_no_in_edges(g): + q.append(x) + while q: + vertex = q.popleft() + if vertex in seen: + continue + result.append(vertex) + neighbors = g.get(vertex) + for x in g.get(vertex): + q.append(x) + seen.add(vertex) + return result + +print(topo_sort(unweighted_digraph)) diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/trickling-water.py b/users/wpcarro/scratch/data_structures_and_algorithms/trickling-water.py new file mode 100644 index 000000000000..45621990ecf9 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/trickling-water.py @@ -0,0 +1,38 @@ +class Node(object): + def __init__(self, value, children=[]): + self.value = value + self.children = children + + +################################################################################ +# Solution +################################################################################ +def trip_time(node): + s = [] + result = 0 + s.append((node.value, node)) + while s: + p, node = s.pop() + if not node.children: + result = max(result, p) + for x in node.children: + s.append((p + x.value, x)) + return result + + +################################################################################ +# Tests +################################################################################ +tree = Node( + 0, + children=[ + Node(5, children=[Node(6)]), + Node(2, children=[ + Node(6), + Node(10), + ]), + Node(3, children=[Node(2, children=[Node(11)])]), + ]) + +assert trip_time(tree) == 16 +print("Tests pass!") diff --git a/users/wpcarro/scratch/data_structures_and_algorithms/which-appears-twice.py b/users/wpcarro/scratch/data_structures_and_algorithms/which-appears-twice.py new file mode 100644 index 000000000000..e9a4f0eb24d0 --- /dev/null +++ b/users/wpcarro/scratch/data_structures_and_algorithms/which-appears-twice.py @@ -0,0 +1,33 @@ +import unittest + + +################################################################################ +# Solution +################################################################################ +# find_repeat :: [Int] -> Int +def find_repeat(xs): + n = len(xs) - 1 + return sum(xs) - ((n**2 + n) / 2) + + +################################################################################ +# Tests +################################################################################ +class Test(unittest.TestCase): + def test_short_list(self): + actual = find_repeat([1, 2, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_repeat([4, 1, 3, 4, 2]) + expected = 4 + self.assertEqual(actual, expected) + + def test_long_list(self): + actual = find_repeat([1, 5, 9, 7, 2, 6, 3, 8, 2, 4]) + expected = 2 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/balanced-binary-tree.py b/users/wpcarro/scratch/deepmind/part_one/balanced-binary-tree.py new file mode 100644 index 000000000000..7fc174a2a9f3 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/balanced-binary-tree.py @@ -0,0 +1,123 @@ +import unittest +from collections import deque + + +def is_balanced(node): + q, seen, ds = deque(), set(), set() + q.append((0, node)) + while q: + d, node = q.popleft() + l, r = node.left, node.right + seen.add(node) + if not l and not r: + if d not in ds and len(ds) == 2: + return False + else: + ds.add(d) + if l and l not in seen: + q.append((d + 1, l)) + if r and r not in seen: + q.append((d + 1, r)) + return max(ds) - min(ds) <= 1 + + +# Tests +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_full_tree(self): + tree = Test.BinaryTreeNode(5) + left = tree.insert_left(8) + right = tree.insert_right(6) + left.insert_left(1) + left.insert_right(2) + right.insert_left(3) + right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_leaves_at_the_same_depth(self): + tree = Test.BinaryTreeNode(3) + left = tree.insert_left(4) + right = tree.insert_right(2) + left.insert_left(1) + right.insert_right(9) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_one(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right.insert_right(7) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_two(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right_right = right.insert_right(7) + right_right.insert_right(8) + result = is_balanced(tree) + self.assertFalse(result) + + def test_three_leaves_total(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right.insert_left(8) + right.insert_right(5) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_subtrees_superbalanced(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right_left = right.insert_left(8) + right.insert_right(5) + right_left.insert_left(7) + result = is_balanced(tree) + self.assertFalse(result) + + def test_both_subtrees_superbalanced_two(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(2) + right = tree.insert_right(4) + left.insert_left(3) + left_right = left.insert_right(7) + left_right.insert_right(8) + right_right = right.insert_right(5) + right_right_right = right_right.insert_right(6) + right_right_right.insert_right(9) + result = is_balanced(tree) + self.assertFalse(result) + + def test_only_one_node(self): + tree = Test.BinaryTreeNode(1) + result = is_balanced(tree) + self.assertTrue(result) + + def test_linked_list_tree(self): + tree = Test.BinaryTreeNode(1) + right = tree.insert_right(2) + right_right = right.insert_right(3) + right_right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/dijkstra.py b/users/wpcarro/scratch/deepmind/part_one/dijkstra.py new file mode 100644 index 000000000000..6975dbe4d1d6 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/dijkstra.py @@ -0,0 +1,26 @@ +# Doing a practice implementation of Dijkstra's algorithm: a priority-first +# search. +from heapq import heappush, heappop + + +class Node(object): + def __init__(self, value, children): + self.value = value + self.children = children + + +def shortest_path(a, b): + """Return the shortest path from `a` to `b`.""" + q = [] + seen = set() + heappush((a.value, a, [a]), q) + + while q: + d, node, path = heappop(q) + if node == b: + return path + seen.add(node) + for child in node.children: + if child not in seen: + heappush((d + child.value, child, path + [child]), q) + raise Exception("Path between nodes A and B does not exist.") diff --git a/users/wpcarro/scratch/deepmind/part_one/efficiency.org b/users/wpcarro/scratch/deepmind/part_one/efficiency.org new file mode 100644 index 000000000000..89a45c52ad8a --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/efficiency.org @@ -0,0 +1,6 @@ +* Sorting +** Merge: O(n*log(n)) +** Heap: O(n*log(n)) +** Insertion: O(n^2) +** Quick: O(n^2) +** Bubble: O(n^2) diff --git a/users/wpcarro/scratch/deepmind/part_one/find-rotation-point.py b/users/wpcarro/scratch/deepmind/part_one/find-rotation-point.py new file mode 100644 index 000000000000..5c21d5167ce9 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/find-rotation-point.py @@ -0,0 +1,55 @@ +import unittest +from math import floor + + +def midpoint(a, b): + return a + floor((b - a) / 2) + + +def do_find_rotation_point(a, b, xs): + i = midpoint(a, b) + count = b - a + 1 + + if count == 2: + if xs[a] > xs[b]: + return b + else: + return -1 + + if i in {a, b}: + return i + + if xs[a] < xs[i]: + return do_find_rotation_point(i, b, xs) + else: + return do_find_rotation_point(a, i, xs) + + +def find_rotation_point(xs): + return do_find_rotation_point(0, len(xs) - 1, xs) + + +# Tests +class Test(unittest.TestCase): + def test_small_list(self): + actual = find_rotation_point(['cape', 'cake']) + expected = 1 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_rotation_point( + ['grape', 'orange', 'plum', 'radish', 'apple']) + expected = 4 + self.assertEqual(actual, expected) + + def test_large_list(self): + actual = find_rotation_point([ + 'ptolemaic', 'retrograde', 'supplant', 'undulate', 'xenoepist', + 'asymptote', 'babka', 'banoffee', 'engender', 'karpatka', + 'othellolagkage' + ]) + expected = 5 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/inflight-entertainment.py b/users/wpcarro/scratch/deepmind/part_one/inflight-entertainment.py new file mode 100644 index 000000000000..2116b27b0b97 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/inflight-entertainment.py @@ -0,0 +1,51 @@ +import unittest + + +def can_two_movies_fill_flight(xs, t): + seeking = set() + for x in xs: + if x in seeking: + return True + else: + seeking.add(t - x) + return False + + +# Tests + + +class Test(unittest.TestCase): + def test_short_flight(self): + result = can_two_movies_fill_flight([2, 4], 1) + self.assertFalse(result) + + def test_long_flight(self): + result = can_two_movies_fill_flight([2, 4], 6) + self.assertTrue(result) + + def test_one_movie_half_flight_length(self): + result = can_two_movies_fill_flight([3, 8], 6) + self.assertFalse(result) + + def test_two_movies_half_flight_length(self): + result = can_two_movies_fill_flight([3, 8, 3], 6) + self.assertTrue(result) + + def test_lots_of_possible_pairs(self): + result = can_two_movies_fill_flight([1, 2, 3, 4, 5, 6], 7) + self.assertTrue(result) + + def test_not_using_first_movie(self): + result = can_two_movies_fill_flight([4, 3, 2], 5) + self.assertTrue(result) + + def test_only_one_movie(self): + result = can_two_movies_fill_flight([6], 6) + self.assertFalse(result) + + def test_no_movies(self): + result = can_two_movies_fill_flight([], 2) + self.assertFalse(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/kth-to-last.py b/users/wpcarro/scratch/deepmind/part_one/kth-to-last.py new file mode 100644 index 000000000000..5335e419f7ec --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/kth-to-last.py @@ -0,0 +1,64 @@ +import unittest + + +def kth_to_last_node(k, x): + a, b = x, x + + if k == 0: + raise Exception('Value of 0 for k is not supported') + + for _ in range(k - 1): + if not a.next: + raise Exception('Value of {} for k is too large'.format(k)) + a = a.next + + while a.next: + a, b = a.next, b.next + return b + + +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def setUp(self): + self.fourth = Test.LinkedListNode(4) + self.third = Test.LinkedListNode(3, self.fourth) + self.second = Test.LinkedListNode(2, self.third) + self.first = Test.LinkedListNode(1, self.second) + + def test_first_to_last_node(self): + actual = kth_to_last_node(1, self.first) + expected = self.fourth + self.assertEqual(actual, expected) + + def test_second_to_last_node(self): + actual = kth_to_last_node(2, self.first) + expected = self.third + self.assertEqual(actual, expected) + + def test_first_node(self): + actual = kth_to_last_node(4, self.first) + expected = self.first + self.assertEqual(actual, expected) + + def test_k_greater_than_linked_list_length(self): + with self.assertRaises(Exception): + kth_to_last_node(5, self.first) + + def test_k_is_zero(self): + with self.assertRaises(Exception): + kth_to_last_node(0, self.first) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/merging-ranges.py b/users/wpcarro/scratch/deepmind/part_one/merging-ranges.py new file mode 100644 index 000000000000..23b40793b8f1 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/merging-ranges.py @@ -0,0 +1,59 @@ +import unittest + + +def merge_ranges(xs): + xs.sort() + result = [xs[0]] + for curr in xs[1:]: + a, z = result[-1] + if z >= curr[0]: + result[-1] = (a, max(z, curr[1])) + else: + result.append(curr) + return result + + +# Tests +class Test(unittest.TestCase): + def test_meetings_overlap(self): + actual = merge_ranges([(1, 3), (2, 4)]) + expected = [(1, 4)] + self.assertEqual(actual, expected) + + def test_meetings_touch(self): + actual = merge_ranges([(5, 6), (6, 8)]) + expected = [(5, 8)] + self.assertEqual(actual, expected) + + def test_meeting_contains_other_meeting(self): + actual = merge_ranges([(1, 8), (2, 5)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_stay_separate(self): + actual = merge_ranges([(1, 3), (4, 8)]) + expected = [(1, 3), (4, 8)] + self.assertEqual(actual, expected) + + def test_multiple_merged_meetings(self): + actual = merge_ranges([(1, 4), (2, 5), (5, 8)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_not_sorted(self): + actual = merge_ranges([(5, 8), (1, 4), (6, 8)]) + expected = [(1, 4), (5, 8)] + self.assertEqual(actual, expected) + + def test_one_long_meeting_contains_smaller_meetings(self): + actual = merge_ranges([(1, 10), (2, 5), (6, 8), (9, 10), (10, 12)]) + expected = [(1, 12)] + self.assertEqual(actual, expected) + + def test_sample_input(self): + actual = merge_ranges([(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)]) + expected = [(0, 1), (3, 8), (9, 12)] + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/recursive-string-permutations.py b/users/wpcarro/scratch/deepmind/part_one/recursive-string-permutations.py new file mode 100644 index 000000000000..f50db2838707 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/recursive-string-permutations.py @@ -0,0 +1,56 @@ +import unittest +from itertools import permutations + + +class Node(object): + def __init__(self, x): + self.value = x + self.children = [] + + +def make_tree(c, xs): + root = Node(c) + for x in xs: + root.children.append(make_tree(x, xs - {x})) + return root + + +def get_permutations(xs): + xs = set(xs) + root = make_tree("", xs) + q, perms = [], set() + q.append(("", root)) + while q: + c, node = q.pop() + if not node.children: + perms.add(c) + else: + for child in node.children: + q.append((c + child.value, child)) + return perms + + +# Tests +class Test(unittest.TestCase): + def test_empty_string(self): + actual = get_permutations('') + expected = set(['']) + self.assertEqual(actual, expected) + + def test_one_character_string(self): + actual = get_permutations('a') + expected = set(['a']) + self.assertEqual(actual, expected) + + def test_two_character_string(self): + actual = get_permutations('ab') + expected = set(['ab', 'ba']) + self.assertEqual(actual, expected) + + def test_three_character_string(self): + actual = get_permutations('abc') + expected = set(['abc', 'acb', 'bac', 'bca', 'cab', 'cba']) + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/reverse-linked-list.py b/users/wpcarro/scratch/deepmind/part_one/reverse-linked-list.py new file mode 100644 index 000000000000..82fac171d5d1 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/reverse-linked-list.py @@ -0,0 +1,74 @@ +import unittest + + +def reverse(node): + prev = None + next = None + curr = node + + while curr: + next = curr.next + curr.next = prev + prev = curr + curr = next + + return prev + + +# Tests +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def test_short_linked_list(self): + second = Test.LinkedListNode(2) + first = Test.LinkedListNode(1, second) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [2, 1] + self.assertEqual(actual, expected) + + def test_long_linked_list(self): + sixth = Test.LinkedListNode(6) + fifth = Test.LinkedListNode(5, sixth) + fourth = Test.LinkedListNode(4, fifth) + third = Test.LinkedListNode(3, fourth) + second = Test.LinkedListNode(2, third) + first = Test.LinkedListNode(1, second) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [6, 5, 4, 3, 2, 1] + self.assertEqual(actual, expected) + + def test_one_element_linked_list(self): + first = Test.LinkedListNode(1) + + result = reverse(first) + self.assertIsNotNone(result) + + actual = result.get_values() + expected = [1] + self.assertEqual(actual, expected) + + def test_empty_linked_list(self): + result = reverse(None) + self.assertIsNone(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/stock-price.py b/users/wpcarro/scratch/deepmind/part_one/stock-price.py new file mode 100644 index 000000000000..7055b66af196 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/stock-price.py @@ -0,0 +1,51 @@ +def get_max_profit(xs): + best_profit = xs[1] - xs[0] + lowest_buy = xs[0] + + for x in xs[1:]: + best_profit = max(best_profit, x - lowest_buy) + lowest_buy = min(lowest_buy, x) + return best_profit + + +# Tests + +import unittest + + +class Test(unittest.TestCase): + def test_price_goes_up_then_down(self): + actual = get_max_profit([1, 5, 3, 2]) + expected = 4 + self.assertEqual(actual, expected) + + def test_price_goes_down_then_up(self): + actual = get_max_profit([7, 2, 8, 9]) + expected = 7 + self.assertEqual(actual, expected) + + def test_price_goes_up_all_day(self): + actual = get_max_profit([1, 6, 7, 9]) + expected = 8 + self.assertEqual(actual, expected) + + def test_price_goes_down_all_day(self): + actual = get_max_profit([9, 7, 4, 1]) + expected = -2 + self.assertEqual(actual, expected) + + def test_price_stays_the_same_all_day(self): + actual = get_max_profit([1, 1, 1, 1]) + expected = 0 + self.assertEqual(actual, expected) + + def test_error_with_empty_prices(self): + with self.assertRaises(Exception): + get_max_profit([]) + + def test_error_with_one_price(self): + with self.assertRaises(Exception): + get_max_profit([1]) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_one/which-appears-twice.py b/users/wpcarro/scratch/deepmind/part_one/which-appears-twice.py new file mode 100644 index 000000000000..c01379295d32 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_one/which-appears-twice.py @@ -0,0 +1,29 @@ +import unittest + + +def find_repeat(xs): + n = max(xs) + expected_sum = (n + 1) * n / 2 + actual_sum = sum(xs) + return actual_sum - expected_sum + + +# Tests +class Test(unittest.TestCase): + def test_short_list(self): + actual = find_repeat([1, 2, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_medium_list(self): + actual = find_repeat([4, 1, 3, 4, 2]) + expected = 4 + self.assertEqual(actual, expected) + + def test_long_list(self): + actual = find_repeat([1, 5, 9, 7, 2, 6, 3, 8, 2, 4]) + expected = 2 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/.envrc b/users/wpcarro/scratch/deepmind/part_two/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/scratch/deepmind/part_two/balanced-binary-tree.py b/users/wpcarro/scratch/deepmind/part_two/balanced-binary-tree.py new file mode 100644 index 000000000000..03de0350d898 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/balanced-binary-tree.py @@ -0,0 +1,126 @@ +import unittest +from collections import deque + + +# is_balanced :: Node(a) -> Bool +def is_balanced(node): + q = deque() + q.append((0, node)) + mn, mx = None, None + + while q: + depth, node = q.popleft() + # Current node is a leaf node + if not node.left and not node.right: + mx = depth if mx is None else max(mx, depth) + mn = depth if mn is None else min(mn, depth) + if mx - mn > 1: + return False + if node.left: + q.append((depth + 1, node.left)) + if node.right: + q.append((depth + 1, node.right)) + + return mx - mn <= 1 + + +# Tests +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_full_tree(self): + tree = Test.BinaryTreeNode(5) + left = tree.insert_left(8) + right = tree.insert_right(6) + left.insert_left(1) + left.insert_right(2) + right.insert_left(3) + right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_leaves_at_the_same_depth(self): + tree = Test.BinaryTreeNode(3) + left = tree.insert_left(4) + right = tree.insert_right(2) + left.insert_left(1) + right.insert_right(9) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_one(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right.insert_right(7) + result = is_balanced(tree) + self.assertTrue(result) + + def test_leaf_heights_differ_by_two(self): + tree = Test.BinaryTreeNode(6) + left = tree.insert_left(1) + right = tree.insert_right(0) + right_right = right.insert_right(7) + right_right.insert_right(8) + result = is_balanced(tree) + self.assertFalse(result) + + def test_three_leaves_total(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right.insert_left(8) + right.insert_right(5) + result = is_balanced(tree) + self.assertTrue(result) + + def test_both_subtrees_superbalanced(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(5) + right = tree.insert_right(9) + right_left = right.insert_left(8) + right.insert_right(5) + right_left.insert_left(7) + result = is_balanced(tree) + self.assertFalse(result) + + def test_both_subtrees_superbalanced_two(self): + tree = Test.BinaryTreeNode(1) + left = tree.insert_left(2) + right = tree.insert_right(4) + left.insert_left(3) + left_right = left.insert_right(7) + left_right.insert_right(8) + right_right = right.insert_right(5) + right_right_right = right_right.insert_right(6) + right_right_right.insert_right(9) + result = is_balanced(tree) + self.assertFalse(result) + + def test_only_one_node(self): + tree = Test.BinaryTreeNode(1) + result = is_balanced(tree) + self.assertTrue(result) + + def test_linked_list_tree(self): + tree = Test.BinaryTreeNode(1) + right = tree.insert_right(2) + right_right = right.insert_right(3) + right_right.insert_right(4) + result = is_balanced(tree) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/bst-checker.py b/users/wpcarro/scratch/deepmind/part_two/bst-checker.py new file mode 100644 index 000000000000..fd0374a9ce91 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/bst-checker.py @@ -0,0 +1,110 @@ +import unittest +from collections import deque + + +# While this function solves the problem, it uses O(n) space since we're storing +# all of the less-thans and greater-thans. +def is_binary_search_tree_first_attempt(root): + q = deque() + q.append((set(), set(), root)) + + while q: + lts, gts, node = q.popleft() + + if not all([node.value < lt for lt in lts]): + return False + if not all([node.value > gt for gt in gts]): + return False + + if node.left: + q.append((lts | {node.value}, gts, node.left)) + if node.right: + q.append((lts, gts | {node.value}, node.right)) + + return True + + +# While I did not originally solve this problem this way, when I learned that I +# could condense the space of my solution's runtime, I wrote this. +def is_binary_search_tree(root): + q = deque() + q.append((None, None, root)) + + while q: + lt, gt, node = q.popleft() + + if not lt is None and node.value >= lt: + return False + if not gt is None and node.value <= gt: + return False + + if node.left: + q.append((node.value, gt, node.left)) + if node.right: + q.append((lt, node.value, node.right)) + + return True + + +# Tests +class Test(unittest.TestCase): + class BinaryTreeNode(object): + def __init__(self, value): + self.value = value + self.left = None + self.right = None + + def insert_left(self, value): + self.left = Test.BinaryTreeNode(value) + return self.left + + def insert_right(self, value): + self.right = Test.BinaryTreeNode(value) + return self.right + + def test_valid_full_tree(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(70) + left.insert_left(10) + left.insert_right(40) + right.insert_left(60) + right.insert_right(80) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + def test_both_subtrees_valid(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(30) + right = tree.insert_right(80) + left.insert_left(20) + left.insert_right(60) + right.insert_left(70) + right.insert_right(90) + result = is_binary_search_tree(tree) + self.assertFalse(result) + + def test_descending_linked_list(self): + tree = Test.BinaryTreeNode(50) + left = tree.insert_left(40) + left_left = left.insert_left(30) + left_left_left = left_left.insert_left(20) + left_left_left.insert_left(10) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + def test_out_of_order_linked_list(self): + tree = Test.BinaryTreeNode(50) + right = tree.insert_right(70) + right_right = right.insert_right(60) + right_right.insert_right(80) + result = is_binary_search_tree(tree) + self.assertFalse(result) + + def test_one_node_tree(self): + tree = Test.BinaryTreeNode(50) + result = is_binary_search_tree(tree) + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/cafe-order-checker.py b/users/wpcarro/scratch/deepmind/part_two/cafe-order-checker.py new file mode 100644 index 000000000000..0e31214b830d --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/cafe-order-checker.py @@ -0,0 +1,64 @@ +import unittest + + +# Solution +def is_first_come_first_served(xs, ys, zs): + i, j = 0, 0 + for z in zs: + if i < len(xs) and z == xs[i]: + i += 1 + elif j < len(ys) and z == ys[j]: + j += 1 + else: + return False + return i == len(xs) and j == len(ys) + + +# Tests +class Test(unittest.TestCase): + def test_both_registers_have_same_number_of_orders(self): + result = is_first_come_first_served([1, 4, 5], [2, 3, 6], + [1, 2, 3, 4, 5, 6]) + self.assertTrue(result) + + def test_registers_have_different_lengths(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], [1, 2, 6, 3, 5]) + self.assertFalse(result) + + def test_one_register_is_empty(self): + result = is_first_come_first_served([], [2, 3, 6], [2, 3, 6]) + self.assertTrue(result) + + def test_served_orders_is_missing_orders(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], [1, 6, 3, 5]) + self.assertFalse(result) + + def test_served_orders_has_extra_orders(self): + result = is_first_come_first_served([1, 5], [2, 3, 6], + [1, 2, 3, 5, 6, 8]) + self.assertFalse(result) + + def test_one_register_has_extra_orders(self): + result = is_first_come_first_served([1, 9], [7, 8], [1, 7, 8]) + self.assertFalse(result) + + def test_one_register_has_unserved_orders(self): + result = is_first_come_first_served([55, 9], [7, 8], [1, 7, 8, 9]) + self.assertFalse(result) + + # Bonus + def test_handles_repeats(self): + actual = is_first_come_first_served([1, 2, 1], [3, 4, 5, 5], + [3, 4, 1, 5, 5, 2, 1]) + self.assertTrue(actual) + + def test_kitchen_didnt_serve(self): + actual = is_first_come_first_served([1, 2], [3, 4], [1, 3, 4]) + self.assertFalse(actual) + + def test_customer_didnt_pay(self): + actual = is_first_come_first_served([2], [3, 4], [1, 3, 4]) + self.assertFalse(actual) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/coin.ts b/users/wpcarro/scratch/deepmind/part_two/coin.ts new file mode 100644 index 000000000000..8aa8de8bb87a --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/coin.ts @@ -0,0 +1,102 @@ +// The denomination of a coin. +type Coin = number; + +// The amount of change remaining. +type Amount = number; + +// Mapping of Coin -> Int +type CoinBag = Map<Coin, number>; + +function createCoinBag(coins: Coin[]): CoinBag { + const result = new Map(); + + for (const coin of coins) { + result.set(coin, 0); + } + + return result; +} + +// This algorithm should work conceptual, but it does not actually +// work. JavaScript uses reference equality when constructing a Set<Map<A,B>>, +// so my result.size returns a higher number than I expect because it contains +// many duplicate entries. +// +// Conceptually, I'm not sure this solution is optimal either -- even after I +// can dedupe the entries in `result`. +function changePossibilities(amt: Amount, coins: Coin[]): number { + if (amt === 0) { + return 1; + } + const result: Set<CoinBag> = new Set(); + + const q: [Coin, Amount, CoinBag][] = []; + + for (const coin of coins) { + const bag = createCoinBag(coins); + bag.set(coin, 1); + q.push([coin, amt - coin, bag]); + } + + while (q.length > 0) { + const [coin, amt, bag] = q.shift(); + + console.log([coin, amt, bag]); + + if (amt === 0) { + result.add(bag); + } else if (amt < 0) { + continue; + } else { + for (const c of coins) { + const bagCopy = new Map(bag); + const value = bagCopy.get(c); + bagCopy.set(c, value + 1); + q.push([c, amt - c, bagCopy]); + } + } + } + console.log(result); + return result.size; +} + +// Tests +let desc = "sample input"; +let actual = changePossibilities(4, [1, 2, 3]); +let expected = 4; +assertEqual(actual, expected, desc); + +desc = "one way to make zero cents"; +actual = changePossibilities(0, [1, 2]); +expected = 1; +assertEqual(actual, expected, desc); + +desc = "no ways if no coins"; +actual = changePossibilities(1, []); +expected = 0; +assertEqual(actual, expected, desc); + +desc = "big coin value"; +actual = changePossibilities(5, [25, 50]); +expected = 0; +assertEqual(actual, expected, desc); + +desc = "big target amount"; +actual = changePossibilities(50, [5, 10]); +expected = 6; +assertEqual(actual, expected, desc); + +// I think InterviewCake designed this assertion to be computationally +// expensive. +desc = "change for one dollar"; +actual = changePossibilities(100, [1, 5, 10, 25, 50]); +expected = 292; +assertEqual(actual, expected, desc); + +function assertEqual(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/delete-node.py b/users/wpcarro/scratch/deepmind/part_two/delete-node.py new file mode 100644 index 000000000000..4ed02ec30832 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/delete-node.py @@ -0,0 +1,57 @@ +import unittest + + +def delete_node(node): + if node.next: + node.value = node.next.value + node.next = node.next.next + else: + raise Exception( + "We cannot delete the last node in a linked list using this function" + ) + + +# Tests +class Test(unittest.TestCase): + class LinkedListNode(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + + def get_values(self): + node = self + values = [] + while node is not None: + values.append(node.value) + node = node.next + return values + + def setUp(self): + self.fourth = Test.LinkedListNode(4) + self.third = Test.LinkedListNode(3, self.fourth) + self.second = Test.LinkedListNode(2, self.third) + self.first = Test.LinkedListNode(1, self.second) + + def test_node_at_beginning(self): + delete_node(self.first) + actual = self.first.get_values() + expected = [2, 3, 4] + self.assertEqual(actual, expected) + + def test_node_in_middle(self): + delete_node(self.second) + actual = self.first.get_values() + expected = [1, 3, 4] + self.assertEqual(actual, expected) + + def test_node_at_end(self): + with self.assertRaises(Exception): + delete_node(self.fourth) + + def test_one_node_in_list(self): + unique = Test.LinkedListNode(1) + with self.assertRaises(Exception): + delete_node(unique) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space-beast-mode.py b/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space-beast-mode.py new file mode 100644 index 000000000000..c9edc32c8856 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space-beast-mode.py @@ -0,0 +1,114 @@ +import unittest + + +################################################################################ +# InterviewCake's solution +################################################################################ +def cycle_len(xs, i): + """ + Returns the length of a cycle that contains no duplicate items. + """ + result = 1 + checkpt = i + current = xs[checkpt - 1] + + while current != checkpt: + current = xs[current - 1] + result += 1 + + return result + + +def theirs(xs): + """ + This is InterviewCake's solution. + """ + i = xs[-1] + for _ in range(len(xs) - 1): + i = xs[i - 1] + + cycle_length = cycle_len(xs, i) + + p0 = xs[-1] + p1 = xs[-1] + for _ in range(cycle_length): + p1 = xs[p1 - 1] + + while p0 != p1: + p0 = xs[p0 - 1] + p1 = xs[p1 - 1] + + print(p0, p1) + + return p0 + + +################################################################################ +# My solution +################################################################################ +def mine(xs): + """ + This is the solution that I came up with, which differs from InterviewCake's + solution. + """ + i = xs[-1] + offset = 1 if len(xs) % 2 == 0 else 2 + + for _ in range(len(xs) - offset): + i = xs[i - 1] + + return i + + +use_mine = True +find_duplicate = mine if use_mine else theirs + + +# Tests +class Test(unittest.TestCase): + def test_just_the_repeated_number(self): + # len(xs) even + actual = find_duplicate([1, 1]) + expected = 1 + self.assertEqual(actual, expected) + + def test_short_list(self): + # len(xs) even + actual = find_duplicate([1, 2, 3, 2]) + expected = 2 + self.assertEqual(actual, expected) + + def test_medium_list(self): + # len(xs) even + actual = find_duplicate([1, 2, 5, 5, 5, 5]) + expected = 5 + self.assertEqual(actual, expected) + + def test_long_list(self): + # len(xs) odd + actual = find_duplicate([4, 1, 4, 8, 3, 2, 7, 6, 5]) + expected = 4 + self.assertEqual(actual, expected) + + ############################################################################ + # Additional examples from InterviewCake.com + ############################################################################ + def test_example_a(self): + # len(xs) even + actual = find_duplicate([3, 4, 2, 3, 1, 5]) + expected = 3 + self.assertTrue(actual, expected) + + def test_example_b(self): + # len(xs) even + actual = find_duplicate([3, 1, 2, 2]) + expected = 2 + self.assertEqual(actual, expected) + + def test_example_c(self): + # len(xs) odd BUT multiple duplicates + actual = find_duplicate([4, 3, 1, 1, 4]) + self.assertTrue(actual in {1, 4}) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space.ts b/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space.ts new file mode 100644 index 000000000000..98f5bb144e76 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/find-duplicate-optimize-for-space.ts @@ -0,0 +1,70 @@ +function findRepeatBruteForce(xs: Array<number>): number { + // InterviewCake asks us to write a function that optimizes for space. Using + // brute force, we can write a function that returns an answer using constant + // (i.e. O(1)) space at the cost of a quadratic (i.e. O(n^2)) runtime. + // + // I did not think of this myself; InterviewCake's "Tell me more" hints + // did. Since I think this idea is clever, I wrote a solution from memory to + // help me internalize the solution. + for (let i = 0; i < xs.length; i += 1) { + let seeking = xs[i]; + for (let j = i + 1; j < xs.length; j += 1) { + if (xs[j] === seeking) { + return seeking; + } + } + } +} + +function findRepeatSort(xs: Array<number>): number { + // This version first sorts xs, which gives the function a time-complexity of + // O(n*log(n)), which is better than the quadratic complexity of the + // brute-force solution. The space requirement here is constant. + // + // Since we need to sort xs in-place to avoid paying a O(n) space cost for + // storing the newly sorted xs, we're mutating our input. InterviewCake + // advises us to not mutate our input. + xs.sort(); + let i = 0; + let j = 1; + for (; j < xs.length; ) { + if (xs[i] === xs[j]) { + return xs[i]; + } + i += 1; + j += 1; + } +} + +function findRepeat(xs: Array<number>): number { + return 0; +} + +// Tests +let desc = "just the repeated number"; +let actual = findRepeat([1, 1]); +let expected = 1; +assertEqual(actual, expected, desc); + +desc = "short array"; +actual = findRepeat([1, 2, 3, 2]); +expected = 2; +assertEqual(actual, expected, desc); + +desc = "medium array"; +actual = findRepeat([1, 2, 5, 5, 5, 5]); +expected = 5; +assertEqual(actual, expected, desc); + +desc = "long array"; +actual = findRepeat([4, 1, 4, 8, 3, 2, 7, 6, 5]); +expected = 4; +assertEqual(actual, expected, desc); + +function assertEqual(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/find-rotation-point.ts b/users/wpcarro/scratch/deepmind/part_two/find-rotation-point.ts new file mode 100644 index 000000000000..7bf1a484454d --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/find-rotation-point.ts @@ -0,0 +1,68 @@ +function findRotationPoint(xs: Array<string>): number { + // Find the rotation point in the vector. + let beg = 0; + let end = xs.length - 1; + + while (beg != end) { + let mid = beg + Math.floor((end - beg) / 2); + + if (beg === mid) { + return xs[beg] < xs[end] ? beg : end; + } + + if (xs[end] <= xs[mid]) { + beg = mid; + end = end; + } else { + beg = beg; + end = mid; + } + } + + return beg; +} + +// Tests +let desc; +let actual; +let expected; + +desc = "small array one"; +actual = findRotationPoint(["cape", "cake"]); +expected = 1; +assertEquals(actual, expected, desc); + +desc = "small array two"; +actual = findRotationPoint(["cake", "cape"]); +expected = 0; +assertEquals(actual, expected, desc); + +desc = "medium array"; +actual = findRotationPoint(["grape", "orange", "plum", "radish", "apple"]); +expected = 4; +assertEquals(actual, expected, desc); + +desc = "large array"; +actual = findRotationPoint([ + "ptolemaic", + "retrograde", + "supplant", + "undulate", + "xenoepist", + "asymptote", + "babka", + "banoffee", + "engender", + "karpatka", + "othellolagkage" +]); +expected = 5; +assertEquals(actual, expected, desc); + +function assertEquals(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/graph-coloring.ts b/users/wpcarro/scratch/deepmind/part_two/graph-coloring.ts new file mode 100644 index 000000000000..a0b6d5dbae53 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/graph-coloring.ts @@ -0,0 +1,232 @@ +type Color = string; + +interface GraphNode { + label: string; + neighbors: Set<GraphNode>; + color: string; +} + +class GraphNode { + constructor(label: string) { + this.label = label; + this.neighbors = new Set(); + this.color = null; + } +} + +interface Queue<A> { + xs: Array<A>; +} + +class Queue<A> { + constructor() { + this.xs = []; + } + isEmpty(): boolean { + return this.xs.length === 0; + } + enqueue(x: A): void { + this.xs.push(x); + } + dequeue(): A { + return this.xs.shift(); + } +} + +type Graph = Array<GraphNode>; + +// Return a set of all of the colors from the neighbor nodes of `node`. +function neighborColors(node: GraphNode): Set<Color> { + const result: Set<Color> = new Set(); + + for (const x of node.neighbors) { + if (typeof x.color === 'string') { + result.add(x.color); + } + } + + return result; +} + +// Returns the set difference between sets `xs`, and `ys`. +function setDifference<A>(xs: Set<A>, ys: Set<A>): Set<A> { + const result: Set<A> = new Set(); + + for (const x of xs) { + if (!ys.has(x)) { + result.add(x); + } + } + + return result; +} + +// Returns an element from the set, `xs`. +// Throwns an error if `xs` is an empty set. +function choose<A>(xs: Set<A>): A { + if (xs.size === 0) { + throw new Error('Cannot choose an element from an empty set.'); + } else { + return xs.values().next().value; + } +} + +// Returns true if `node` is present in `node.neighbors`. +function isCyclic(node: GraphNode): boolean { + for (const x of node.neighbors) { + if (x === node) { + return true; + } + } +} + +function colorGraph(graph: Graph, colors: Array<Color>): void { + const allColors = new Set(colors); + + for (const node of graph) { + if (isCyclic(node)) { + throw new Error('InterviewCake would like me to invalidate this'); + } + if (typeof node.color !== 'string') { + node.color = choose(setDifference(allColors, neighborColors(node))); + } + } +} + + +// Tests +const colors = ['red', 'green', 'blue', 'orange', 'yellow', 'white']; + +let graph = []; +{ + const nodeA = new GraphNode('A'); + const nodeB = new GraphNode('B'); + const nodeC = new GraphNode('C'); + const nodeD = new GraphNode('D'); + nodeA.neighbors.add(nodeB); + nodeB.neighbors.add(nodeA); + nodeB.neighbors.add(nodeC); + nodeC.neighbors.add(nodeB); + nodeC.neighbors.add(nodeD); + nodeD.neighbors.add(nodeC); + graph = [nodeA, nodeB, nodeC, nodeD]; +} +colorGraph(graph, colors); +assertEqual(validateGraphColoring(graph), true, 'line graph'); + +{ + const nodeA = new GraphNode('A'); + const nodeB = new GraphNode('B'); + const nodeC = new GraphNode('C'); + const nodeD = new GraphNode('D'); + nodeA.neighbors.add(nodeB); + nodeB.neighbors.add(nodeA); + nodeC.neighbors.add(nodeD); + nodeD.neighbors.add(nodeC); + graph = [nodeA, nodeB, nodeC, nodeD]; +} +colorGraph(graph, colors); +assertEqual(validateGraphColoring(graph), true, 'separate graph'); + +{ + const nodeA = new GraphNode('A'); + const nodeB = new GraphNode('B'); + const nodeC = new GraphNode('C'); + nodeA.neighbors.add(nodeB); + nodeA.neighbors.add(nodeC); + nodeB.neighbors.add(nodeA); + nodeB.neighbors.add(nodeC); + nodeC.neighbors.add(nodeA); + nodeC.neighbors.add(nodeB); + graph = [nodeA, nodeB, nodeC]; +} +colorGraph(graph, colors); +assertEqual(validateGraphColoring(graph), true, 'triangle graph'); + +{ + const nodeA = new GraphNode('A'); + const nodeB = new GraphNode('B'); + const nodeC = new GraphNode('C'); + const nodeD = new GraphNode('D'); + const nodeE = new GraphNode('E'); + nodeA.neighbors.add(nodeB); + nodeA.neighbors.add(nodeC); + nodeB.neighbors.add(nodeA); + nodeB.neighbors.add(nodeC); + nodeB.neighbors.add(nodeD); + nodeB.neighbors.add(nodeE); + nodeC.neighbors.add(nodeA); + nodeC.neighbors.add(nodeB); + nodeC.neighbors.add(nodeD); + nodeC.neighbors.add(nodeE); + nodeD.neighbors.add(nodeB); + nodeD.neighbors.add(nodeC); + nodeD.neighbors.add(nodeE); + nodeE.neighbors.add(nodeB); + nodeE.neighbors.add(nodeC); + nodeE.neighbors.add(nodeD); + graph = [nodeA, nodeB, nodeC, nodeD, nodeE]; +} +colorGraph(graph, colors); +assertEqual(validateGraphColoring(graph), true, 'envelope graph'); + +{ + const nodeA = new GraphNode('A'); + nodeA.neighbors.add(nodeA); + graph = [nodeA]; +} +assertThrows(() => { + colorGraph(graph, colors); +}, 'loop graph'); + +function validateGraphColoring(graph) { + + const maxDegree = Math.max(...graph.map(node => node.neighbors.size)); + + const colorsUsed = new Set(); + + graph.forEach(node => { + colorsUsed.add(node.color); + }); + + if (colorsUsed.has(null)) { + return false; + } + + if (colorsUsed.size > maxDegree + 1) { + return false; + } + + let badEdges = 0; + + graph.forEach(node => { + node.neighbors.forEach(neighbor => { + if (neighbor.color === node.color) { + badEdges += 1; + } + }); + }); + + if (badEdges > 0) { + return false; + } + + return true; +} + +function assertEqual(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} + +function assertThrows(func, desc) { + try { + func(); + console.log(`${desc} ... FAIL`); + } catch (e) { + console.log(`${desc} ... PASS`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/highest-product-of-3.py b/users/wpcarro/scratch/deepmind/part_two/highest-product-of-3.py new file mode 100644 index 000000000000..8ebb5cf29a4f --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/highest-product-of-3.py @@ -0,0 +1,81 @@ +import unittest +import sys +import trace + + +def highest_product_of_3(xs): + if len(xs) < 3: + raise Exception("List needs to contain at least three elements.") + hp3 = xs[0] * xs[1] * xs[2] + hp2 = xs[0] * xs[1] + lp2 = xs[0] * xs[1] + hn = max(xs[0], xs[1]) + ln = min(xs[0], xs[1]) + for x in xs[2:]: + hp3 = max(hp3, hp2 * x, lp2 * x) + hp2 = max(hp2, hn * x, ln * x) + lp2 = min(lp2, hn * x, ln * x) + hn = max(hn, x) + ln = min(ln, x) + return hp3 + + +# Tests +class Test(unittest.TestCase): + def test_short_list(self): + actual = highest_product_of_3([1, 2, 3, 4]) + expected = 24 + self.assertEqual(actual, expected) + + def test_longer_list(self): + actual = highest_product_of_3([6, 1, 3, 5, 7, 8, 2]) + expected = 336 + self.assertEqual(actual, expected) + + def test_list_has_one_negative(self): + actual = highest_product_of_3([-5, 4, 8, 2, 3]) + expected = 96 + self.assertEqual(actual, expected) + + def test_list_has_two_negatives(self): + actual = highest_product_of_3([-10, 1, 3, 2, -10]) + expected = 300 + self.assertEqual(actual, expected) + + def test_list_is_all_negatives(self): + actual = highest_product_of_3([-5, -1, -3, -2]) + expected = -6 + self.assertEqual(actual, expected) + + def test_error_with_empty_list(self): + with self.assertRaises(Exception): + highest_product_of_3([]) + + def test_error_with_one_number(self): + with self.assertRaises(Exception): + highest_product_of_3([1]) + + def test_error_with_two_numbers(self): + with self.assertRaises(Exception): + highest_product_of_3([1, 1]) + + def test_custom(self): + actual = highest_product_of_3([9, 5, 2, 1, 7, 3]) + expected = 9 * 7 * 5 + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) + + +def main(): + highest_product_of_3([-5, -1, -3, -2]) + + +tracer = trace.Trace(ignoredirs=[sys.prefix, sys.exec_prefix], + trace=0, + count=1) + +tracer.run('main()') +r = tracer.results() +r.write_results(show_missing=True, coverdir=".") diff --git a/users/wpcarro/scratch/deepmind/part_two/inflight-entertainment.ts b/users/wpcarro/scratch/deepmind/part_two/inflight-entertainment.ts new file mode 100644 index 000000000000..d6da1db3d313 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/inflight-entertainment.ts @@ -0,0 +1,85 @@ +function canTwoMoviesFillFlightBonus( + xs: Array<number>, + duration: number +): boolean { + // Returns true if two movies exist that can fill the flight duration +/- 20 + // minutes. + const seeking = {}; + + for (let x of xs) { + for (let i = 0; i < 40; i += 1) { + if (seeking[x + i + 1]) { + return true; + } + } + for (let i = 1; i <= 20; i += 1) { + seeking[duration - x - i] = true; + seeking[duration - x + i] = true; + } + } + + return false; +} + +function canTwoMoviesFillFlight(xs: Array<number>, duration: number): boolean { + const seeking = {}; + + for (let x of xs) { + if (seeking[x]) { + return true; + } else { + seeking[duration - x] = true; + } + } + + return false; +} + +// Tests +let desc = "short flight"; +let actual = canTwoMoviesFillFlight([2, 4], 1); +let expected = false; +assertEquals(actual, expected, desc); + +desc = "long flight"; +actual = canTwoMoviesFillFlight([2, 4], 6); +expected = true; +assertEquals(actual, expected, desc); + +desc = "one movie half flight length"; +actual = canTwoMoviesFillFlight([3, 8], 6); +expected = false; +assertEquals(actual, expected, desc); + +desc = "two movies half flight length"; +actual = canTwoMoviesFillFlight([3, 8, 3], 6); +expected = true; +assertEquals(actual, expected, desc); + +desc = "lots of possible pairs"; +actual = canTwoMoviesFillFlight([1, 2, 3, 4, 5, 6], 7); +expected = true; +assertEquals(actual, expected, desc); + +desc = "not using first movie"; +actual = canTwoMoviesFillFlight([4, 3, 2], 5); +expected = true; +assertEquals(actual, expected, desc); + +desc = "only one movie"; +actual = canTwoMoviesFillFlight([6], 6); +expected = false; +assertEquals(actual, expected, desc); + +desc = "no movies"; +actual = canTwoMoviesFillFlight([], 2); +expected = false; +assertEquals(actual, expected, desc); + +function assertEquals(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/merge-sorted-arrays.ts b/users/wpcarro/scratch/deepmind/part_two/merge-sorted-arrays.ts new file mode 100644 index 000000000000..2d478e0e37de --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/merge-sorted-arrays.ts @@ -0,0 +1,63 @@ +function mergeArrays(xs: Array<number>, ys: Array<number>): Array<number> { + let i = 0; + let j = 0; + const result = []; + + for (let q = 0; q < xs.length + ys.length; q += 1) { + if (i === xs.length) { + while (j < ys.length) { + result.push(ys[j]); + j += 1; + } + } else if (j === ys.length) { + while (i < xs.length) { + result.push(xs[i]); + i += 1; + } + } else if (xs[i] < ys[j]) { + result.push(xs[i]); + i += 1; + } else { + result.push(ys[j]); + j += 1; + } + } + + return result; +} + +// Tests +let desc = "both arrays are empty"; +let actual = mergeArrays([], []); +let expected = []; +assertDeepEqual(actual, expected, desc); + +desc = "first array is empty"; +actual = mergeArrays([], [1, 2, 3]); +expected = [1, 2, 3]; +assertDeepEqual(actual, expected, desc); + +desc = "second array is empty"; +actual = mergeArrays([5, 6, 7], []); +expected = [5, 6, 7]; +assertDeepEqual(actual, expected, desc); + +desc = "both arrays have some numbers"; +actual = mergeArrays([2, 4, 6], [1, 3, 7]); +expected = [1, 2, 3, 4, 6, 7]; +assertDeepEqual(actual, expected, desc); + +desc = "arrays are different lengths"; +actual = mergeArrays([2, 4, 6, 8], [1, 7]); +expected = [1, 2, 4, 6, 7, 8]; +assertDeepEqual(actual, expected, desc); + +function assertDeepEqual(a: Array<number>, b: Array<number>, desc: string) { + const aStr = JSON.stringify(a); + const bStr = JSON.stringify(b); + if (aStr !== bStr) { + console.log(`${desc} ... FAIL: ${aStr} != ${bStr}`); + } else { + console.log(`${desc} ... PASS`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/merging-ranges.py b/users/wpcarro/scratch/deepmind/part_two/merging-ranges.py new file mode 100644 index 000000000000..23d0813d1524 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/merging-ranges.py @@ -0,0 +1,115 @@ +import unittest +import timeit + + +# Solution that uses O(n) space to store the result. +def not_in_place(xs): + xs.sort() + result = [xs[0]] + for ca, cb in xs[1:]: + pa, pb = result[-1] + if ca <= pb: + result[-1] = (pa, max(pb, cb)) + else: + result.append((ca, cb)) + return result + + +# Solution that uses O(1) space to store the result. +def in_place(xs): + xs.sort() + i = 0 + j = i + 1 + while j < len(xs): + pa, pb = xs[i] + ca, cb = xs[j] + if ca <= pb: + xs[i] = (pa, max(pb, cb)) + del xs[j] + else: + i = j + j += 1 + return xs + + +def test_nip(): + inputs = [ + [(1, 3), (2, 4)], + [(5, 6), (6, 8)], + [(1, 8), (2, 5)], + [(1, 3), (4, 8)], + [(1, 4), (2, 5), (5, 8)], + [(5, 8), (1, 4), (6, 8)], + [(1, 10), (2, 5), (6, 8), (9, 10), (10, 12)], + [(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)], + ] + for x in inputs: + not_in_place(x) + + +def test_ip(): + inputs = [ + [(1, 3), (2, 4)], + [(5, 6), (6, 8)], + [(1, 8), (2, 5)], + [(1, 3), (4, 8)], + [(1, 4), (2, 5), (5, 8)], + [(5, 8), (1, 4), (6, 8)], + [(1, 10), (2, 5), (6, 8), (9, 10), (10, 12)], + [(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)], + ] + for x in inputs: + in_place(x) + + +merge_ranges = in_place + +setup = 'from __main__ import test_nip, test_ip' +print(timeit.timeit('test_nip()', number=10000, setup=setup)) +print(timeit.timeit('test_ip()', number=10000, setup=setup)) + + +# Tests +class Test(unittest.TestCase): + def test_meetings_overlap(self): + actual = merge_ranges([(1, 3), (2, 4)]) + expected = [(1, 4)] + self.assertEqual(actual, expected) + + def test_meetings_touch(self): + actual = merge_ranges([(5, 6), (6, 8)]) + expected = [(5, 8)] + self.assertEqual(actual, expected) + + def test_meeting_contains_other_meeting(self): + actual = merge_ranges([(1, 8), (2, 5)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_stay_separate(self): + actual = merge_ranges([(1, 3), (4, 8)]) + expected = [(1, 3), (4, 8)] + self.assertEqual(actual, expected) + + def test_multiple_merged_meetings(self): + actual = merge_ranges([(1, 4), (2, 5), (5, 8)]) + expected = [(1, 8)] + self.assertEqual(actual, expected) + + def test_meetings_not_sorted(self): + actual = merge_ranges([(5, 8), (1, 4), (6, 8)]) + expected = [(1, 4), (5, 8)] + self.assertEqual(actual, expected) + + def test_one_long_meeting_contains_smaller_meetings(self): + actual = merge_ranges([(1, 10), (2, 5), (6, 8), (9, 10), (10, 12)]) + expected = [(1, 12)] + self.assertEqual(actual, expected) + + def test_sample_input(self): + actual = merge_ranges([(0, 1), (3, 5), (4, 8), (10, 12), (9, 10)]) + expected = [(0, 1), (3, 8), (9, 12)] + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/mesh-message.py b/users/wpcarro/scratch/deepmind/part_two/mesh-message.py new file mode 100644 index 000000000000..a265296ab066 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/mesh-message.py @@ -0,0 +1,183 @@ +import unittest +from collections import deque +from heapq import heappush, heappop + + +################################################################################ +# InterviewCake.com +################################################################################ +# construct_path :: Map String String -> String -> String -> [String] +def construct_path(paths, beg, end): + """ + Reconstruct the path from `beg` to `end`. + """ + result = [] + current = end + + print(paths) + print(beg, end) + print('-----') + while current: + result.append(current) + current = paths[current] + + result.reverse() + return result + + +def get_path_ic(graph, beg, end): + """ + InterviewCake uses a dictionary and back-tracking to store and reconstruct + the path instead of storing the path as state on each node. + This reduces the memory costs. See get_path_bft for an example of this less + optimal solution. + """ + if beg not in graph: + raise Exception('Origin node absent from graph.') + + if end not in graph: + raise Exception('Destination node absent from graph.') + + q = deque() + q.append(beg) + paths = {beg: None} + + while q: + node = q.popleft() + + if node == end: + print(graph) + return construct_path(paths, beg, end) + + for x in graph[node]: + if x not in paths: + paths[x] = node + q.append(x) + + return None + + +################################################################################ +# Per-node state +################################################################################ +def get_path_bft(graph, beg, end): + """ + Here we find the shortest path from `beg` to `end` in `graph` by doing a BFT + from beg to end and storing the path state alongside each node in the queue. + """ + if beg not in graph: + raise Exception('Origin node absent from graph.') + + if end not in graph: + raise Exception('Destination node absent from graph.') + + q = deque() + seen = set() + q.append([beg]) + + while q: + path = q.popleft() + node = path[-1] + seen.add(node) + + if node == end: + return path + + for x in graph[node]: + if x not in seen: + q.append(path + [x]) + + +################################################################################ +# Dijkstra's Algorithm +################################################################################ +def get_path(graph, beg, end): + """ + Here we find the shortest path using Dijkstra's algorithm, which is my + favorite solution. + """ + if beg not in graph: + raise Exception( + 'The origin node, {}, is not present in the graph'.format(beg)) + + if end not in graph: + raise Exception( + 'The origin node, {}, is not present in the graph'.format(end)) + + q = [] + seen = set() + heappush(q, (1, [beg])) + + while q: + weight, path = heappop(q) + node = path[-1] + seen.add(node) + + if node == end: + return path + + for x in graph[node]: + if x not in seen: + heappush(q, (weight + 1, path + [x])) + + return None + + +# Tests +class Test(unittest.TestCase): + def setUp(self): + self.graph = { + 'a': ['b', 'c', 'd'], + 'b': ['a', 'd'], + 'c': ['a', 'e'], + 'd': ['b', 'a'], + 'e': ['c'], + 'f': ['g'], + 'g': ['f'], + } + + def test_two_hop_path_1(self): + actual = get_path(self.graph, 'a', 'e') + expected = ['a', 'c', 'e'] + self.assertEqual(actual, expected) + + def test_two_hop_path_2(self): + actual = get_path(self.graph, 'd', 'c') + expected = ['d', 'a', 'c'] + self.assertEqual(actual, expected) + + def test_one_hop_path_1(self): + actual = get_path(self.graph, 'a', 'c') + expected = ['a', 'c'] + self.assertEqual(actual, expected) + + def test_one_hop_path_2(self): + actual = get_path(self.graph, 'f', 'g') + expected = ['f', 'g'] + self.assertEqual(actual, expected) + + def test_one_hop_path_3(self): + actual = get_path(self.graph, 'g', 'f') + expected = ['g', 'f'] + self.assertEqual(actual, expected) + + def test_zero_hop_path(self): + actual = get_path(self.graph, 'a', 'a') + expected = ['a'] + self.assertEqual(actual, expected) + + def test_no_path(self): + actual = get_path(self.graph, 'a', 'f') + expected = None + self.assertEqual(actual, expected) + + def test_start_node_not_present(self): + with self.assertRaises(Exception): + get_path(self.graph, 'h', 'a') + + def test_end_node_not_present(self): + with self.assertRaises(Exception): + get_path(self.graph, 'a', 'h') + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/misc/matrix-traversals.py b/users/wpcarro/scratch/deepmind/part_two/misc/matrix-traversals.py new file mode 100644 index 000000000000..52354f990e11 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/misc/matrix-traversals.py @@ -0,0 +1,104 @@ +# Herein I'm practicing two-dimensional matrix traversals in all directions of +# which I can conceive: +# 0. T -> B; L -> R +# 1. T -> B; R -> L +# 2. B -> T; L -> R +# 3. B -> T; R -> L +# +# Commentary: +# When I think of matrices, I'm reminded of cartesian planes. I think of the +# cells as (X,Y) coordinates. This has been a pitfall for me because matrices +# are usually encoded in the opposite way. That is, to access a cell at the +# coordinates (X,Y) given a matrix M, you index M like this: M[Y][X]. To attempt +# to avoid this confusion, instead of saying X and Y, I will prefer saying +# "column" and "row". +# +# When traversing a matrix, you typically traverse vertically and then +# horizontally; in other words, the rows come first followed by the columns. As +# such, I'd like to refer to traversal orders as "top-to-bottom, left-to-right" +# rather than "left-to-right, top-to-bottom". +# +# These practices are all in an attempt to rewire my thinking. + +# This is a list of matrices where the index of a matrix corresponds to the +# order in which it should be traversed to produce the sequence: +# [1,2,3,4,5,6,7,8,9]. +boards = [[[1, 2, 3], [4, 5, 6], [7, 8, 9]], [[3, 2, 1], [6, 5, 4], [9, 8, 7]], + [[7, 8, 9], [4, 5, 6], [1, 2, 3]], [[9, 8, 7], [6, 5, 4], [3, 2, 1]]] + +# T -> B; L -> R +board = boards[0] +result = [] +for row in board: + for col in row: + result.append(col) +print(result) + +# T -> B; R -> L +board = boards[1] +result = [] +for row in board: + for col in reversed(row): + result.append(col) +print(result) + +# B -> T; L -> R +board = boards[2] +result = [] +for row in reversed(board): + for col in row: + result.append(col) +print(result) + +# B -> T; R -> L +board = boards[3] +result = [] +for row in reversed(board): + for col in reversed(row): + result.append(col) +print(result) + +################################################################################ +# Neighbors +################################################################################ + +import random + + +# Generate a matrix of size `rows` x `cols` where each cell contains an item +# randomly selected from `xs`. +def generate_board(rows, cols, xs): + result = [] + for _ in range(rows): + row = [] + for _ in range(cols): + row.append(random.choice(xs)) + result.append(row) + return result + + +# Print the `board` to the screen. +def print_board(board): + print('\n'.join([' '.join(row) for row in board])) + + +board = generate_board(4, 5, ['R', 'G', 'B']) +print_board(board) + + +# Return all of the cells horizontally and vertically accessible from a starting +# cell at `row`, `col` in `board`. +def neighbors(row, col, board): + result = {'top': [], 'bottom': [], 'left': [], 'right': []} + for i in range(row - 1, -1, -1): + result['top'].append(board[i][col]) + for i in range(row + 1, len(board)): + result['bottom'].append(board[i][col]) + for i in range(col - 1, -1, -1): + result['left'].append(board[row][i]) + for i in range(col + 1, len(board[0])): + result['right'].append(board[row][i]) + return result + + +print(neighbors(1, 2, board)) diff --git a/users/wpcarro/scratch/deepmind/part_two/nth-fibonacci.py b/users/wpcarro/scratch/deepmind/part_two/nth-fibonacci.py new file mode 100644 index 000000000000..14e176b62aab --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/nth-fibonacci.py @@ -0,0 +1,72 @@ +import unittest + + +# Compute the fibonacci using a bottom-up algorithm. +def fib(n): + if n < 0: + raise Error('Cannot call fibonacci with negative values') + cache = [0, 1] + for i in range(n): + cache[0], cache[1] = cache[1], cache[0] + cache[1] + return cache[0] + + +# Compute the fibonacci using memoization. +def fib_memoized(n): + cache = { + 0: 0, + 1: 1, + } + + def do_fib(n): + if n < 0: + raise Error('The fib function does not support negative inputs') + + if n in cache: + return cache[n] + + cache[n - 1] = do_fib(n - 1) + cache[n - 2] = do_fib(n - 2) + return cache[n - 1] + cache[n - 2] + + return do_fib(n) + + +# Tests +class Test(unittest.TestCase): + def test_zeroth_fibonacci(self): + actual = fib(0) + expected = 0 + self.assertEqual(actual, expected) + + def test_first_fibonacci(self): + actual = fib(1) + expected = 1 + self.assertEqual(actual, expected) + + def test_second_fibonacci(self): + actual = fib(2) + expected = 1 + self.assertEqual(actual, expected) + + def test_third_fibonacci(self): + actual = fib(3) + expected = 2 + self.assertEqual(actual, expected) + + def test_fifth_fibonacci(self): + actual = fib(5) + expected = 5 + self.assertEqual(actual, expected) + + def test_tenth_fibonacci(self): + actual = fib(10) + expected = 55 + self.assertEqual(actual, expected) + + def test_negative_fibonacci(self): + with self.assertRaises(Exception): + fib(-1) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/package-lock.json b/users/wpcarro/scratch/deepmind/part_two/package-lock.json new file mode 100644 index 000000000000..340aad9f5ce2 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/package-lock.json @@ -0,0 +1,79 @@ +{ + "name": "deepmind-part-two", + "version": "1.0.0", + "lockfileVersion": 1, + "requires": true, + "dependencies": { + "arg": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", + "integrity": "sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==", + "dev": true + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "make-error": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.5.tgz", + "integrity": "sha512-c3sIjNUow0+8swNwVpqoH4YCShKNFkMaw6oH1mNS2haDZQqkeZFlHS3dhoeEbKKmJB4vXpJucU6oH75aDYeE9g==", + "dev": true + }, + "prettier": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.0.2.tgz", + "integrity": "sha512-5xJQIPT8BraI7ZnaDwSbu5zLrB6vvi8hVV58yHQ+QK64qrY40dULy0HSRlQ2/2IdzeBpjhDkqdcFBnFeDEMVdg==", + "dev": true + }, + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + }, + "source-map-support": { + "version": "0.5.16", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.16.tgz", + "integrity": "sha512-efyLRJDr68D9hBBNIPWFjhpFzURh+KJykQwvMyW5UiZzYwoF6l4YMMDIJJEyFWxWCqfyxLzz6tSfUFR+kXXsVQ==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + } + }, + "ts-node": { + "version": "8.6.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-8.6.2.tgz", + "integrity": "sha512-4mZEbofxGqLL2RImpe3zMJukvEvcO1XP8bj8ozBPySdCUXEcU5cIRwR0aM3R+VoZq7iXc8N86NC0FspGRqP4gg==", + "dev": true, + "requires": { + "arg": "^4.1.0", + "diff": "^4.0.1", + "make-error": "^1.1.1", + "source-map-support": "^0.5.6", + "yn": "3.1.1" + } + }, + "typescript": { + "version": "3.7.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.7.5.tgz", + "integrity": "sha512-/P5lkRXkWHNAbcJIiHPfRoKqyd7bsyCma1hZNUGfn20qm64T6ZBlrzprymeu918H+mB/0rIg2gGK/BXkhhYgBw==", + "dev": true + }, + "yn": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yn/-/yn-3.1.1.tgz", + "integrity": "sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==", + "dev": true + } + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/package.json b/users/wpcarro/scratch/deepmind/part_two/package.json new file mode 100644 index 000000000000..1f10668ec861 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/package.json @@ -0,0 +1,16 @@ +{ + "name": "deepmind-part-two", + "version": "1.0.0", + "description": "Practicing coding interview questions", + "main": "index.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "author": "William Carroll", + "license": "MIT", + "devDependencies": { + "prettier": "^2.0.2", + "ts-node": "^8.6.2", + "typescript": "^3.7.5" + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/permutation-palindrome.py b/users/wpcarro/scratch/deepmind/part_two/permutation-palindrome.py new file mode 100644 index 000000000000..730b4bfdc873 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/permutation-palindrome.py @@ -0,0 +1,37 @@ +import unittest +from collections import Counter + + +def has_palindrome_permutation(xs): + vs = Counter(xs).values() + return len([v for v in vs if v % 2 == 1]) in {0, 1} + + +# Tests +class Test(unittest.TestCase): + def test_permutation_with_odd_number_of_chars(self): + result = has_palindrome_permutation('aabcbcd') + self.assertTrue(result) + + def test_permutation_with_even_number_of_chars(self): + result = has_palindrome_permutation('aabccbdd') + self.assertTrue(result) + + def test_no_permutation_with_odd_number_of_chars(self): + result = has_palindrome_permutation('aabcd') + self.assertFalse(result) + + def test_no_permutation_with_even_number_of_chars(self): + result = has_palindrome_permutation('aabbcd') + self.assertFalse(result) + + def test_empty_string(self): + result = has_palindrome_permutation('') + self.assertTrue(result) + + def test_one_character_string(self): + result = has_palindrome_permutation('a') + self.assertTrue(result) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/product-of-other-numbers.py b/users/wpcarro/scratch/deepmind/part_two/product-of-other-numbers.py new file mode 100644 index 000000000000..6f7858ff4e5b --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/product-of-other-numbers.py @@ -0,0 +1,68 @@ +import unittest + + +# get_products_of_all_ints_except_at_index :: [Int] -> [Int] +def get_products_of_all_ints_except_at_index(xs): + n = len(xs) + if n < 2: + raise Exception("Cannot computer without 2 or elements") + # lhs + befores = [None] * n + befores[0] = 1 + for i in range(1, n): + befores[i] = befores[i - 1] * xs[i - 1] + + # rhs + afters = [None] * n + afters[-1] = 1 + for i in range(n - 2, -1, -1): + afters[i] = afters[i + 1] * xs[i + 1] + + result = [None] * n + for i in range(n): + result[i] = befores[i] * afters[i] + return result + + +# Tests +class Test(unittest.TestCase): + def test_small_list(self): + actual = get_products_of_all_ints_except_at_index([1, 2, 3]) + expected = [6, 3, 2] + self.assertEqual(actual, expected) + + def test_longer_list(self): + actual = get_products_of_all_ints_except_at_index([8, 2, 4, 3, 1, 5]) + expected = [120, 480, 240, 320, 960, 192] + self.assertEqual(actual, expected) + + def test_list_has_one_zero(self): + actual = get_products_of_all_ints_except_at_index([6, 2, 0, 3]) + expected = [0, 0, 36, 0] + self.assertEqual(actual, expected) + + def test_list_has_two_zeros(self): + actual = get_products_of_all_ints_except_at_index([4, 0, 9, 1, 0]) + expected = [0, 0, 0, 0, 0] + self.assertEqual(actual, expected) + + def test_one_negative_number(self): + actual = get_products_of_all_ints_except_at_index([-3, 8, 4]) + expected = [32, -12, -24] + self.assertEqual(actual, expected) + + def test_all_negative_numbers(self): + actual = get_products_of_all_ints_except_at_index([-7, -1, -4, -2]) + expected = [-8, -56, -14, -28] + self.assertEqual(actual, expected) + + def test_error_with_empty_list(self): + with self.assertRaises(Exception): + get_products_of_all_ints_except_at_index([]) + + def test_error_with_one_number(self): + with self.assertRaises(Exception): + get_products_of_all_ints_except_at_index([1]) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/recursive-string-permutations.ts b/users/wpcarro/scratch/deepmind/part_two/recursive-string-permutations.ts new file mode 100644 index 000000000000..cb930d9ad648 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/recursive-string-permutations.ts @@ -0,0 +1,85 @@ +// Returns a new string comprised of every characters in `xs` except for the +// character at `i`. +function everyOtherChar(xs: string, i: number): string[] { + const result = []; + + for (let j = 0; j < xs.length; j += 1) { + if (i !== j) { + result.push(xs[j]); + } + } + + return [xs[i], result.join('')]; +} + +function getPermutations(xs: string): Set<string> { + if (xs === '') { + return new Set(['']); + } + + const result: Set<string> = new Set; + + for (let i = 0; i < xs.length; i += 1) { + const [char, rest] = everyOtherChar(xs, i); + const perms = getPermutations(rest); + + for (const perm of perms) { + result.add(char + perm); + } + } + + return result; +} + +// Tests +let desc = 'empty string'; +let input = ''; +let actual = getPermutations(input); +let expected = new Set(['']); +assert(isSetsEqual(actual, expected), desc); + +desc = 'one character string'; +input = 'a'; +actual = getPermutations(input); +expected = new Set(['a']); +assert(isSetsEqual(actual, expected), desc); + +desc = 'two character string'; +input = 'ab'; +actual = getPermutations(input); +expected = new Set(['ab', 'ba']); +assert(isSetsEqual(actual, expected), desc); + +desc = 'three character string'; +input = 'abc'; +actual = getPermutations(input); +expected = new Set(['abc', 'acb', 'bac', 'bca', 'cab', 'cba']); +assert(isSetsEqual(actual, expected), desc); + +desc = 'four character string'; +input = 'abca'; +actual = getPermutations(input); +expected = new Set([ + 'abca', 'abac', 'acba', 'acab', 'aabc', 'aacb', 'baca', 'baac', 'bcaa', + 'bcaa', 'baac', 'baca', 'caba', 'caab', 'cbaa', 'cbaa', 'caab', 'caba', + 'aabc', 'aacb', 'abac', 'abca', 'acab', 'acba' +]); +assert(isSetsEqual(actual, expected), desc); + +function isSetsEqual(as, bs) { + if (as.size !== bs.size) { + return false; + } + for (let a of as) { + if (!bs.has(a)) return false; + } + return true; +} + +function assert(condition, desc) { + if (condition) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/reverse-string-in-place.ts b/users/wpcarro/scratch/deepmind/part_two/reverse-string-in-place.ts new file mode 100644 index 000000000000..d714dfef997f --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/reverse-string-in-place.ts @@ -0,0 +1,13 @@ +// Reverse array of characters, `xs`, mutatively. +function reverse(xs: Array<string>) { + let i: number = 0; + let j: number = xs.length - 1; + + while (i < j) { + let tmp = xs[i]; + xs[i] = xs[j] + xs[j] = tmp + i += 1 + j -= 1 + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/reverse-words.py b/users/wpcarro/scratch/deepmind/part_two/reverse-words.py new file mode 100644 index 000000000000..033d11244ca7 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/reverse-words.py @@ -0,0 +1,74 @@ +import unittest + + +def reverse(xs, i, j): + """Reverse array of characters, xs, in-place.""" + while i < j: + xs[i], xs[j] = xs[j], xs[i] + i += 1 + j -= 1 + + +def reverse_words(xs): + punctuation = None + if len(xs) > 0 and xs[-1] in ".?!": + punctuation = xs.pop() + reverse(xs, 0, len(xs) - 1) + i = 0 + j = i + while j < len(xs): + while j < len(xs) and xs[j] != ' ': + j += 1 + reverse(xs, i, j - 1) + j += 1 + i = j + if punctuation: + xs.append(punctuation) + + +# Tests +class Test(unittest.TestCase): + def test_one_word(self): + message = list('vault') + reverse_words(message) + expected = list('vault') + self.assertEqual(message, expected) + + def test_two_words(self): + message = list('thief cake') + reverse_words(message) + expected = list('cake thief') + self.assertEqual(message, expected) + + def test_three_words(self): + message = list('one another get') + reverse_words(message) + expected = list('get another one') + self.assertEqual(message, expected) + + def test_multiple_words_same_length(self): + message = list('rat the ate cat the') + reverse_words(message) + expected = list('the cat ate the rat') + self.assertEqual(message, expected) + + def test_multiple_words_different_lengths(self): + message = list('yummy is cake bundt chocolate') + reverse_words(message) + expected = list('chocolate bundt cake is yummy') + self.assertEqual(message, expected) + + def test_empty_string(self): + message = list('') + reverse_words(message) + expected = list('') + self.assertEqual(message, expected) + + def test_bonus_support_punctuation(self): + message = list('yummy is cake bundt chocolate this!') + reverse_words(message) + expected = list('this chocolate bundt cake is yummy!') + self.assertEqual(message, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/second-largest-item-in-bst.ts b/users/wpcarro/scratch/deepmind/part_two/second-largest-item-in-bst.ts new file mode 100644 index 000000000000..4c5e57607d87 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/second-largest-item-in-bst.ts @@ -0,0 +1,219 @@ +/******************************************************************************* + * Setup + ******************************************************************************/ + +interface BinaryTreeNode { + value: number; + left: BinaryTreeNode; + right: BinaryTreeNode; +} + +class BinaryTreeNode { + constructor(value: number) { + this.value = value; + this.left = null; + this.right = null; + } + + insertLeft(value: number): BinaryTreeNode { + this.left = new BinaryTreeNode(value); + return this.left; + } + + insertRight(value: number): BinaryTreeNode { + this.right = new BinaryTreeNode(value); + return this.right; + } +} + +/******************************************************************************* + * First solution + ******************************************************************************/ + +/** + * I first solved this problem using O(n) space and O(n*log(n)) + * time. InterviewCake informs me that we can improve both the time and the + * space performance. + */ +function findSecondLargest_first(node: BinaryTreeNode): number { + const stack: Array<BinaryTreeNode> = []; + const xs: Array<number> = []; + stack.push(node); + + while (stack.length > 0) { + const node = stack.pop() + + xs.push(node.value); + + if (node.left) { + stack.push(node.left); + } + if (node.right) { + stack.push(node.right); + } + } + + xs.sort(); + + if (xs.length < 2) { + throw new Error('Cannot find the second largest element in a BST with fewer than two elements.'); + } else { + return xs[xs.length - 2]; + } +} + +/******************************************************************************* + * Second solution + ******************************************************************************/ + +/** + * My second solution accumulates a list of the values in the tree using an + * in-order traversal. This reduces the runtime costs from O(n*log(n)) from the + * previous solution to O(n). The memory cost is still O(n), which InterviewCake + * informs me can be reduced to O(1). + */ +function findSecondLargest_second(node: BinaryTreeNode): number { + const xs: Array<number> = accumulateInorder(node); + + if (xs.length < 2) { + throw new Error('Cannot find the second largest element in a BST with fewer than two elements.'); + } else { + return xs[xs.length - 2]; + } +} + +/** + * Returns an array containing the values of the tree, `node`, sorted in-order + * (i.e. from smallest-to-largest). + */ +function accumulateInorder(node: BinaryTreeNode): Array<number> { + let result = []; + + if (node.left) { + result = result.concat(accumulateInorder(node.left)); + } + result.push(node.value) + if (node.right) { + result = result.concat(accumulateInorder(node.right)); + } + + return result; +} + +/******************************************************************************* + * Third solution + ******************************************************************************/ + +/** + * Returns the largest number in a BST. + */ +function findLargest(node: BinaryTreeNode): number { + let curr: BinaryTreeNode = node; + + while (curr.right) { + curr = curr.right; + } + + return curr.value; +} + +/** + * Returns the second largest number in a BST + */ +function findSecondLargest(node: BinaryTreeNode): number { + let curr = node; + let parent = null; + + while (curr.right) { + parent = curr; + curr = curr.right + } + + if (curr.left) { + return findLargest(curr.left); + } + else { + return parent.value; + } +} + + +// Tests +let desc = 'full tree'; +let treeRoot = new BinaryTreeNode(50); +let leftNode = treeRoot.insertLeft(30); +leftNode.insertLeft(10); +leftNode.insertRight(40); +let rightNode = treeRoot.insertRight(70); +rightNode.insertLeft(60); +rightNode.insertRight(80); +assertEquals(findSecondLargest(treeRoot), 70, desc); + +desc = 'largest has a left child'; +treeRoot = new BinaryTreeNode(50); +leftNode = treeRoot.insertLeft(30); +leftNode.insertLeft(10); +leftNode.insertRight(40); +rightNode = treeRoot.insertRight(70); +rightNode.insertLeft(60); +assertEquals(findSecondLargest(treeRoot), 60, desc); + +desc = 'largest has a left subtree'; +treeRoot = new BinaryTreeNode(50); +leftNode = treeRoot.insertLeft(30); +leftNode.insertLeft(10); +leftNode.insertRight(40); +rightNode = treeRoot.insertRight(70); +leftNode = rightNode.insertLeft(60); +leftNode.insertRight(65); +leftNode = leftNode.insertLeft(55); +leftNode.insertRight(58); +assertEquals(findSecondLargest(treeRoot), 65, desc); + +desc = 'second largest is root node'; +treeRoot = new BinaryTreeNode(50); +leftNode = treeRoot.insertLeft(30); +leftNode.insertLeft(10); +leftNode.insertRight(40); +rightNode = treeRoot.insertRight(70); +assertEquals(findSecondLargest(treeRoot), 50, desc); + +desc = 'descending linked list'; +treeRoot = new BinaryTreeNode(50); +leftNode = treeRoot.insertLeft(40); +leftNode = leftNode.insertLeft(30); +leftNode = leftNode.insertLeft(20); +leftNode = leftNode.insertLeft(10); +assertEquals(findSecondLargest(treeRoot), 40, desc); + +desc = 'ascending linked list'; +treeRoot = new BinaryTreeNode(50); +rightNode = treeRoot.insertRight(60); +rightNode = rightNode.insertRight(70); +rightNode = rightNode.insertRight(80); +assertEquals(findSecondLargest(treeRoot), 70, desc); + +desc = 'one node tree'; +treeRoot = new BinaryTreeNode(50); +assertThrowsError(() => findSecondLargest(treeRoot), desc); + +desc = 'when tree is empty'; +treeRoot = null; +assertThrowsError(() => findSecondLargest(treeRoot), desc); + +function assertEquals(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`) + } +} + +function assertThrowsError(func, desc) { + try { + func(); + console.log(`${desc} ... FAIL`); + } catch (e) { + console.log(`${desc} ... PASS`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/shell.nix b/users/wpcarro/scratch/deepmind/part_two/shell.nix new file mode 100644 index 000000000000..f1b02c4d2ed5 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/shell.nix @@ -0,0 +1,10 @@ +{ pkgs, ... }: + +pkgs.mkShell { + buildInputs = with pkgs; [ + nodejs + python3 + go + goimports + ]; +} diff --git a/users/wpcarro/scratch/deepmind/part_two/shuffle.py b/users/wpcarro/scratch/deepmind/part_two/shuffle.py new file mode 100644 index 000000000000..fdc5a8bd80ab --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/shuffle.py @@ -0,0 +1,20 @@ +import random + + +def get_random(floor, ceiling): + return random.randrange(floor, ceiling + 1) + + +def shuffle(xs): + n = len(xs) + for i in range(n - 1): + j = get_random(i + 1, n - 1) + xs[i], xs[j] = xs[j], xs[i] + + +sample_list = [1, 2, 3, 4, 5] +print('Sample list:', sample_list) + +print('Shuffling sample list...') +shuffle(sample_list) +print(sample_list) diff --git a/users/wpcarro/scratch/deepmind/part_two/stock-price.py b/users/wpcarro/scratch/deepmind/part_two/stock-price.py new file mode 100644 index 000000000000..56a3c20ea05b --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/stock-price.py @@ -0,0 +1,54 @@ +import unittest + + +def get_max_profit(xs): + if len(xs) < 2: + raise Exception('Can only trade with two or more ticker values.') + lowest_buy = xs[0] + max_profit = None + for x in xs[1:]: + if not max_profit: + max_profit = x - lowest_buy + else: + max_profit = max(max_profit, x - lowest_buy) + lowest_buy = min(lowest_buy, x) + return max_profit + + +# Tests +class Test(unittest.TestCase): + def test_price_goes_up_then_down(self): + actual = get_max_profit([1, 5, 3, 2]) + expected = 4 + self.assertEqual(actual, expected) + + def test_price_goes_down_then_up(self): + actual = get_max_profit([7, 2, 8, 9]) + expected = 7 + self.assertEqual(actual, expected) + + def test_price_goes_up_all_day(self): + actual = get_max_profit([1, 6, 7, 9]) + expected = 8 + self.assertEqual(actual, expected) + + def test_price_goes_down_all_day(self): + actual = get_max_profit([9, 7, 4, 1]) + expected = -2 + self.assertEqual(actual, expected) + + def test_price_stays_the_same_all_day(self): + actual = get_max_profit([1, 1, 1, 1]) + expected = 0 + self.assertEqual(actual, expected) + + def test_error_with_empty_prices(self): + with self.assertRaises(Exception): + get_max_profit([]) + + def test_error_with_one_price(self): + with self.assertRaises(Exception): + get_max_profit([1]) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/todo.org b/users/wpcarro/scratch/deepmind/part_two/todo.org new file mode 100644 index 000000000000..9c76da754167 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/todo.org @@ -0,0 +1,77 @@ +* Array and string manipulation +** DONE Merging Meeting Times +** DONE Reverse String in Place +** DONE Reverse Words +** DONE Merge Sorted Arrays +** DONE Cafe Order Checker +* Hashing and hash tables +** DONE Inflight Entertainment +** DONE Permutation Palindrome +** DONE Word Cloud Data +** DONE Top Scores +* Greedy Algorithms +** DONE Apple Stocks +** DONE Highest Product of 3 +** DONE Product of All Other Numbers +** DONE Cafe Order Checker +** DONE In-Place Shuffle +* Sorting, searching, and logarithms +** DONE Find Rotation Point +** TODO Find Repeat, Space Edition +** DONE Top Scores +** DONE Merging Meeting Times +* Trees and graphs +** DONE Balanced Binary Tree +** DONE Binary Search Tree Checker +** DONE 2nd Largest Item in a Binary Search Tree +** DONE Graph Coloring +** DONE MeshMessage +** DONE Find Repeat, Space Edition BEAST MODE +* Dynamic programming and recursion +** DONE Recursive String Permutations +** DONE Compute nth Fibonacci Number +** TODO Making Change +** TODO The Cake Thief +** DONE Balanced Binary Tree +** DONE Binary Search Tree Checker +** DONE 2nd Largest Item in a Binary Search Tree +* Queues and stacks +** TODO Largest Stack +** TODO Implement A Queue With Two Stacks +** TODO Parenthesis Matching +** TODO Bracket Validator +* Linked lists +** DONE Delete Node +** TODO Does This Linked List Have A Cycle? +** TODO Reverse A Linked List +** TODO Kth to Last Node in a Singly-Linked List +** DONE Find Repeat, Space Edition BEAST MODE +* System design +** TODO URL Shortener +** TODO MillionGazillion +** TODO Find Duplicate Files +* General programming +** TODO Rectangular Love +** TODO Temperature Tracker +* Bit manipulation +** TODO Binary Numbers +** TODO The Stolen Breakfast Drone +* Combinatorics, probability, and other math +** TODO Which Appears Twice +** TODO Find in Ordered Set +** DONE In-Place Shuffle +** TODO Simulate 5-sided die +** TODO Simulate 7-sided die +** TODO Two Egg Problem +* JavaScript +** TODO JavaScript Scope +** TODO What's Wrong with This JavaScript? +* Coding interview tips +** TODO How The Coding Interview Works +** TODO General Coding Interview Advice +** TODO Impostor Syndrome +** TODO Why You Hit Dead Ends +** TODO Tips for Getting Unstuck +** TODO The 24 Hours Before Your Interview +** TODO Beating Behavioral Questions +** TODO Managing Your Interview Timeline diff --git a/users/wpcarro/scratch/deepmind/part_two/top-scores.py b/users/wpcarro/scratch/deepmind/part_two/top-scores.py new file mode 100644 index 000000000000..0ac349c1f880 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/top-scores.py @@ -0,0 +1,47 @@ +import unittest + + +def sort_scores(xs, highest_possible_score): + result = [] + buckets = [0] * highest_possible_score + + for x in xs: + buckets[x - 1] += 1 + + for i in range(highest_possible_score - 1, -1, -1): + if buckets[i] > 0: + for _ in range(buckets[i]): + result.append(i + 1) + + return result + + +# Tests +class Test(unittest.TestCase): + def test_no_scores(self): + actual = sort_scores([], 100) + expected = [] + self.assertEqual(actual, expected) + + def test_one_score(self): + actual = sort_scores([55], 100) + expected = [55] + self.assertEqual(actual, expected) + + def test_two_scores(self): + actual = sort_scores([30, 60], 100) + expected = [60, 30] + self.assertEqual(actual, expected) + + def test_many_scores(self): + actual = sort_scores([37, 89, 41, 65, 91, 53], 100) + expected = [91, 89, 65, 53, 41, 37] + self.assertEqual(actual, expected) + + def test_repeated_scores(self): + actual = sort_scores([20, 10, 30, 30, 10, 20], 100) + expected = [30, 30, 20, 20, 10, 10] + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/deepmind/part_two/top-scores.ts b/users/wpcarro/scratch/deepmind/part_two/top-scores.ts new file mode 100644 index 000000000000..79c10c883211 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/top-scores.ts @@ -0,0 +1,57 @@ +function sortScores(xs: Array<number>, highest: number): Array<number> { + const counts: Array<number> = []; + const result: Array<number> = []; + + // Initialize counts + for (let i = 0; i <= highest; i += 1) { + counts.push(0); + } + + for (let i = 0; i < xs.length; i += 1) { + counts[xs[i]] += 1; + } + + for (let i = highest; i >= 0; i -= 1) { + let count: number = counts[i]; + + for (let j = 0; j < count; j += 1) { + result.push(i); + } + } + + return result; +} + +// Tests +let desc = "no scores"; +let actual = sortScores([], 100); +let expected = []; +assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); + +desc = "one score"; +actual = sortScores([55], 100); +expected = [55]; +assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); + +desc = "two scores"; +actual = sortScores([30, 60], 100); +expected = [60, 30]; +assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); + +desc = "many scores"; +actual = sortScores([37, 89, 41, 65, 91, 53], 100); +expected = [91, 89, 65, 53, 41, 37]; +assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); + +desc = "repeated scores"; +actual = sortScores([20, 10, 30, 30, 10, 20], 100); +expected = [30, 30, 20, 20, 10, 10]; +assertEqual(JSON.stringify(actual), JSON.stringify(expected), desc); + +function assertEqual(a, b, desc) { + if (a === b) { + console.log(`${desc} ... PASS`); + } else { + console.log(`${desc} ... FAIL: ${a} != ${b}`); + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/tsconfig.json b/users/wpcarro/scratch/deepmind/part_two/tsconfig.json new file mode 100644 index 000000000000..9b6918ca37d8 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/tsconfig.json @@ -0,0 +1,7 @@ +{ + "compilerOptions": { + "downlevelIteration": true, + "target": "es5", + "lib": ["es6", "dom"] + } +} diff --git a/users/wpcarro/scratch/deepmind/part_two/word-cloud.py b/users/wpcarro/scratch/deepmind/part_two/word-cloud.py new file mode 100644 index 000000000000..36ace8405f71 --- /dev/null +++ b/users/wpcarro/scratch/deepmind/part_two/word-cloud.py @@ -0,0 +1,79 @@ +import unittest +import re +from collections import Counter + + +class WordCloudData(object): + def __init__(self, x): + x = x.replace('...', ' ').replace(' - ', ' ') + x = ''.join(c for c in x if c not in ',.!?;:') + self.words_to_counts = dict( + Counter(x.lower() for x in re.split(r'\s+', x))) + + +# Tests +class Test(unittest.TestCase): + def test_simple_sentence(self): + input = 'I like cake' + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = {'i': 1, 'like': 1, 'cake': 1} + self.assertEqual(actual, expected) + + def test_longer_sentence(self): + input = 'Chocolate cake for dinner and pound cake for dessert' + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = { + 'and': 1, + 'pound': 1, + 'for': 2, + 'dessert': 1, + 'chocolate': 1, + 'dinner': 1, + 'cake': 2, + } + self.assertEqual(actual, expected) + + def test_punctuation(self): + input = 'Strawberry short cake? Yum!' + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = {'cake': 1, 'strawberry': 1, 'short': 1, 'yum': 1} + self.assertEqual(actual, expected) + + def test_hyphenated_words(self): + input = 'Dessert - mille-feuille cake' + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = {'cake': 1, 'dessert': 1, 'mille-feuille': 1} + self.assertEqual(actual, expected) + + def test_ellipses_between_words(self): + input = 'Mmm...mmm...decisions...decisions' + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = {'mmm': 2, 'decisions': 2} + self.assertEqual(actual, expected) + + def test_apostrophes(self): + input = "Allie's Bakery: Sasha's Cakes" + + word_cloud = WordCloudData(input) + actual = word_cloud.words_to_counts + + expected = {"bakery": 1, "cakes": 1, "allie's": 1, "sasha's": 1} + self.assertEqual(actual, expected) + + +unittest.main(verbosity=2) diff --git a/users/wpcarro/scratch/facebook/anglocize-int.py b/users/wpcarro/scratch/facebook/anglocize-int.py new file mode 100644 index 000000000000..a828230d0851 --- /dev/null +++ b/users/wpcarro/scratch/facebook/anglocize-int.py @@ -0,0 +1,71 @@ +THOUSAND = int(1e3) +MILLION = int(1e6) +BILLION = int(1e9) +TRILLION = int(1e12) + +facts = { + 1: "One", + 2: "Two", + 3: "Three", + 4: "Four", + 5: "Five", + 6: "Six", + 7: "Seven", + 8: "Eight", + 9: "Nine", + 10: "Ten", + 11: "Eleven", + 12: "Twelve", + 13: "Thirteen", + 14: "Fourteen", + 15: "Fifteen", + 16: "Sixteen", + 17: "Seventeen", + 18: "Eighteen", + 19: "Nineteen", + 20: "Twenty", + 30: "Thirty", + 40: "Forty", + 50: "Fifty", + 60: "Sixty", + 70: "Seventy", + 80: "Eighty", + 90: "Ninety", + 100: "Hundred", + THOUSAND: "Thousand", + MILLION: "Million", + BILLION: "Billion", + TRILLION: "Trillion", +} + +def anglocize(x): + # ones + if x >= 0 and x < 10: + pass + + # tens + elif x < 100: + pass + + # hundreds + elif x < THOUSAND: + pass + + # thousands + elif x < MILLION: + pass + + # millions + elif x < BILLION: + pass + + # billion + elif x < TRILLION: + pass + + # trillion + else: + pass + +x = 1234 +assert anglocize(x) == "One Thousand, Two Hundred Thirty Four" diff --git a/users/wpcarro/scratch/facebook/balanced-binary-tree.py b/users/wpcarro/scratch/facebook/balanced-binary-tree.py new file mode 100644 index 000000000000..afa9706f97ba --- /dev/null +++ b/users/wpcarro/scratch/facebook/balanced-binary-tree.py @@ -0,0 +1,70 @@ +from collections import deque + +class Node(object): + # __init__ :: T(A) + def __init__(self, value=None, left=None, right=None): + self.value = value + self.left = left + self.right = right + + # insert_left :: T(A) -> A -> T(A) + def insert_left(self, value): + self.left = Node(value) + return self.left + + # insert_right :: T(A) -> A -> T(A) + def insert_right(self, value): + self.right = Node(value) + return self.right + + # is_superbalanced :: T(A) -> Bool + def is_superbalanced(self): + xs = deque() + min_depth, max_depth = float('inf'), float('-inf') + xs.append((self, 0)) + while xs: + x, d = xs.popleft() + # Only redefine the depths at leaf nodes + if not x.left and not x.right: + min_depth, max_depth = min(min_depth, d), max(max_depth, d) + if x.left: + xs.append((x.left, d + 1)) + if x.right: + xs.append((x.right, d + 1)) + return max_depth - min_depth <= 1 + + # __repr__ :: T(A) -> String + def __repr__(self): + result = '' + xs = deque() + xs.append((self, 0)) + while xs: + node, indent = xs.popleft() + result += '{i}{x}\n'.format(i=' ' * indent, x=node.value) + if node.left: + xs.append((node.left, indent + 2)) + if node.right: + xs.append((node.right, indent + 2)) + return result + +# from_array :: List(A) -> T(A) +def from_array(values): + xs = deque() + root = Node() + xs.append(root) + for value in values: + node = xs.popleft() + node.value = value + node.left = Node() + xs.append(node.left) + node.right = Node() + xs.append(node.right) + return root + +x = from_array([1, 1, 1, 1, 1, 1, 1]) +print(x) +print(x.is_superbalanced()) + +x = Node(1, Node(2), Node(3)) +print(x) +print(x.is_superbalanced()) diff --git a/users/wpcarro/scratch/facebook/breakfast-generator.py b/users/wpcarro/scratch/facebook/breakfast-generator.py new file mode 100644 index 000000000000..df9b5015ad3a --- /dev/null +++ b/users/wpcarro/scratch/facebook/breakfast-generator.py @@ -0,0 +1,112 @@ +# After being inspired by... +# craftinginterpreters.com/representing-code.html +# ...I'm implementing the breakfast generator that the author describes +# therein. + +import random +import string + +# Breakfast + +def breakfast(): + fn = random.choice([ + lambda: " ".join([protein(), "with", breakfast(), "on the side"]), + lambda: protein(), + lambda: bread(), + ]) + return fn() + +def protein(): + fn = random.choice([ + lambda: " ".join([qualifier(), "crispy", "bacon"]), + lambda: "sausage", + lambda: " ".join([cooking_method(), "sausage"]), + ]) + return fn() + +def qualifier(): + fn = random.choice([ + lambda: "really", + lambda: "super", + lambda: " ".join(["really", qualifier()]), + ]) + return fn() + +def cooking_method(): + return random.choice([ + "scrambled", + "poached", + "fried", + ]) + +def bread(): + return random.choice([ + "toast", + "biscuits", + "English muffin", + ]) + +print(breakfast()) + +# Expression Language + +# Because Python is a strictly evaluated language any functions that are +# mutually recursive won't terminate and will overflow our stack. Therefore, any +# non-terminals expressed in an alternative are wrapped in lambdas as thunks. + +def expression(): + fn = random.choice([ + lambda: literal(), + lambda: binary(), + ]) + return fn() + +def literal(): + return str(random.randint(0, 100)) + +def binary(): + return " ".join([expression(), operator(), expression()]) + +def operator(): + return random.choice(["+", "*"]) + +print(expression()) + +# Lox + +def lox_expression(): + fn = random.choice([ + lambda: lox_literal(), + lambda: lox_unary(), + lambda: lox_binary(), + lambda: lox_grouping(), + ]) + return fn() + +def lox_literal(): + fn = random.choice([ + lambda: str(random.randint(0, 100)), + lambda: lox_string(), + lambda: random.choice(["true", "false"]), + lambda: "nil", + ]) + return fn() + +def lox_string(): + return "\"{}\"".format( + "".join(random.choice(string.ascii_lowercase) + for _ in range(random.randint(0, 25)))) + +def lox_grouping(): + return "(" + lox_expression() + ")" + +def lox_unary(): + return random.choice(["-", "!"]) + lox_expression() + +def lox_binary(): + return lox_expression() + lox_operator() + lox_expression() + +def lox_operator(): + return random.choice(["==", "!=", "<", "<=", ">", ">=", "+", "-", "*", "/"]) + +print(lox_expression()) diff --git a/users/wpcarro/scratch/facebook/bst-checker.py b/users/wpcarro/scratch/facebook/bst-checker.py new file mode 100644 index 000000000000..7ef63a95315e --- /dev/null +++ b/users/wpcarro/scratch/facebook/bst-checker.py @@ -0,0 +1,49 @@ +from collections import deque + +class Node(object): + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + + def is_bst(self): + s = [] + s.append((float('-inf'), self, float('inf'))) + while s: + lo, node, hi = s.pop() + if lo <= node.value <= hi: + node.left and s.append((lo, node.left, node.value)) + node.right and s.append((node.value, node.right, hi)) + else: + return False + return True + + +x = Node( + 50, + Node( + 17, + Node( + 12, + Node(9), + Node(14), + ), + Node( + 23, + Node(19), + ), + ), + Node( + 72, + Node( + 54, + None, + Node(67) + ), + Node(76), + ), +) + + +assert x.is_bst() +print("Success!") diff --git a/users/wpcarro/scratch/facebook/cafe-order-checker.py b/users/wpcarro/scratch/facebook/cafe-order-checker.py new file mode 100644 index 000000000000..9d88a68069fd --- /dev/null +++ b/users/wpcarro/scratch/facebook/cafe-order-checker.py @@ -0,0 +1,19 @@ +def orders_are_sorted(take_out, dine_in, audit): + if len(take_out) + len(dine_in) != len(audit): + return False + + i, j = 0, 0 + for x in audit: + if i < len(take_out) and take_out[i] == x: + i += 1 + elif j < len(dine_in) and dine_in[j] == x: + j += 1 + else: + return False + return True + + +assert orders_are_sorted([1,3,5], [2,4,6], [1,2,4,3,6,5]) +assert not orders_are_sorted([1,3,5], [2,4,6], [1,2,4,5,6,3]) +assert orders_are_sorted([], [2,4,6], [2,4,6]) +print("Success!") diff --git a/users/wpcarro/scratch/facebook/cake_thief.py b/users/wpcarro/scratch/facebook/cake_thief.py new file mode 100644 index 000000000000..90a2add06658 --- /dev/null +++ b/users/wpcarro/scratch/facebook/cake_thief.py @@ -0,0 +1,61 @@ +from math import floor + +def print_table(table): + print('\n-- TABLE --') + for row in range(len(table)): + x = '' + for col in range(len(table[row])): + x += ' ' + str(table[row][col]) + print(x) + +def leftover(capacity, kg): + n = floor(capacity / kg) + return n, capacity - (n * kg) + +def init_table(num_rows, num_cols): + table = [] + for _ in range(num_rows): + row = [] + for _ in range(num_cols): + row.append(0) + table.append(row) + return table + +def get(table, row, col): + if row < 0 or col < 0: + return 0 + return table[row][col] + +def max_haul(items, capacity): + table = init_table(len(items), capacity) + + for row in range(len(table)): + for col in range(len(table[row])): + curr_capacity = col + 1 + kg, val = items[row] + # A + a = get(table, row - 1, col) + # B + n, lo = leftover(curr_capacity, kg) + b = (val * n) + get(table, row - 1, lo - 1) + # commit + if kg > curr_capacity: + table[row][col] = a + else: + print(n, lo) + table[row][col] = max([a, b]) + print_table(table) + return table[-1][-1] + +# There are multiple variants of this problem: +# 1. We're allowed to take multiple of each item. +# 2. We can only take one of each item. +# 3. We can only take a fixed amount of each item. + +items = [(7,160), (3,90), (2,15)] +capacity = 20 +result = max_haul(items, capacity) +expected = None +print("Result: {} == Expected: {}".format(result, expected)) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/camping-knapsack.py b/users/wpcarro/scratch/facebook/camping-knapsack.py new file mode 100644 index 000000000000..add59ed409cd --- /dev/null +++ b/users/wpcarro/scratch/facebook/camping-knapsack.py @@ -0,0 +1,46 @@ +from utils import get, init_table, print_table + +def max_haul(capacity, items, names): + table = init_table(rows=len(items), cols=capacity, default=0) + items_table = init_table(rows=len(items), cols=capacity, default=[]) + for row in range(len(table)): + for col in range(len(table[row])): + kg, value = items[row] + curr_capacity = col + 1 + + if kg > curr_capacity: + a = 0 + else: + a = value + get(table, row - 1, curr_capacity - kg - 1) + b = get(table, row - 1, col) + + if a > b: + rest = get(items_table, row - 1, curr_capacity - kg - 1) + knapsack = [names.get(items[row])] + if rest: + knapsack += rest + else: + knapsack = get(items_table, row - 1, col) + + table[row][col] = max([a, b]) + items_table[row][col] = knapsack + print_table(table) + return items_table[-1][-1] + +water = (3, 10) +book = (1, 3) +food = (2, 9) +jacket = (2, 5) +camera = (1, 6) +items = [water, book, food, jacket, camera] +result = max_haul(6, items, { + water: 'water', + book: 'book', + food: 'food', + jacket: 'jacket', + camera: 'camera', +}) +expected = ['camera', 'food', 'water'] +print(result, expected) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/coin.py b/users/wpcarro/scratch/facebook/coin.py new file mode 100644 index 000000000000..354e2dfb58b8 --- /dev/null +++ b/users/wpcarro/scratch/facebook/coin.py @@ -0,0 +1,50 @@ +def init_table(rows=0, cols=0, default=None): + table = [] + for _ in range(rows): + row = [] + for _ in range(cols): + row.append(default) + table.append(row) + return table + +def print_table(table): + result = '' + for row in range(len(table)): + x = '' + for col in range(len(table[row])): + x += str(table[row][col]) + ' ' + result += x + '\n' + print(result) + +def get(table, row, col): + if row < 0 or col < 0: + return 0 + else: + return table[row][col] + +def make_change(coins, amt): + table = init_table(rows=len(coins), cols=amt, default=0) + for row in range(len(table)): + for col in range(len(table[row])): + coin = coins[row] + curr_amt = col + 1 + pull_down = get(table, row - 1, col) + + if curr_amt < coin: + table[row][col] = pull_down + elif curr_amt == coin: + table[row][col] = pull_down + 1 + else: + leftover = get(table, row, curr_amt - coin - 1) + table[row][col] = pull_down + leftover + + print_table(table) + return table[-1][-1] + +# 1 2 3 4 +# 1 1 1 1 1 +# 2 1 1 2 2 +# 3 1 1 3 4 + +result = make_change([3,2,1], 4) +print(result) diff --git a/users/wpcarro/scratch/facebook/count-islands.py b/users/wpcarro/scratch/facebook/count-islands.py new file mode 100644 index 000000000000..b876319b2f7a --- /dev/null +++ b/users/wpcarro/scratch/facebook/count-islands.py @@ -0,0 +1,53 @@ +from collections import deque + +def maybe_queue(row, col, game, q, seen): + """ + Add coordinate, (`row`, `col`), to the queue, `q`, as long as it exists in + the map, `game`, and it is not already present in `seen`. + """ + if row >= 0 and row < len(game) and col >= 0 and col < len(game[0]): + if game[row][col] == 'L' and (row, col) not in seen: + q.append((row, col)) + seen.add((row, col)) + +def visit_island(row, col, game, seen): + """ + Starting at the coordinate, (`row`, `col`), in the map, `game`, visit all + surrounding tiles marked as land by adding them to the `seen` set. + """ + q = deque() + q.append((row, col)) + while q: + row, col = q.popleft() + maybe_queue(row - 1, col, game, q, seen) # UP + maybe_queue(row + 1, col, game, q, seen) # DOWN + maybe_queue(row, col - 1, game, q, seen) # LEFT + maybe_queue(row, col + 1, game, q, seen) # RIGHT + +def count_islands(game): + """ + Return the number of contiguous land tiles in the map, `game`. + """ + result = 0 + seen = set() + for row in range(len(game)): + for col in range(len(game[row])): + if game[row][col] == 'L' and (row, col) not in seen: + visit_island(row, col, game, seen) + result += 1 + return result + +################################################################################ +# Tests +################################################################################ + +game = [ + "LWLWWW", + "LLLWWW", + "WWWLLW", +] + +result = count_islands(game) +print(result) +assert result == 2 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/delete-node.py b/users/wpcarro/scratch/facebook/delete-node.py new file mode 100644 index 000000000000..4034449ef0cd --- /dev/null +++ b/users/wpcarro/scratch/facebook/delete-node.py @@ -0,0 +1,19 @@ +from linked_list import Node, from_list + +def delete(node): + if not node.next: + node.value = None + else: + node.value = node.next.value + node.next = node.next.next + +one = Node(1) +two = Node(2) +three = Node(3) + +one.next = two +two.next = three + +print(one) +delete(two) +print(one) diff --git a/users/wpcarro/scratch/facebook/dijkstras.py b/users/wpcarro/scratch/facebook/dijkstras.py new file mode 100644 index 000000000000..7031701994a7 --- /dev/null +++ b/users/wpcarro/scratch/facebook/dijkstras.py @@ -0,0 +1,38 @@ +from heapq import heappush, heappop +import random + +# Dijkstra's algorithm will traverse a directed graph with weighted edges. If +# the edges aren't weighted, we can pretend that each edges weighs 1. The +# algorithm will find the shortest path between points A and B. + +def dijkstra(a, b, graph): + h = [] + seen = set() + heappush(h, (0, a, [a], [])) + while h: + km, x, path, steps = heappop(h) + + if x == b: + for a, b, d in steps: + print("{} -> {} => {}".format(a, b, d)) + return path, km + + seen.add(x) + for c, dist in graph[x]: + if c not in seen: + heappush(h, (km + dist, c, path + [c], steps + [(x, c, dist)])) + return [], float('inf') + +graph = { + 1: [(3, 9), (2, 7), (6, 14)], + 2: [(1, 7), (3, 10), (4, 15)], + 3: [(1, 9), (6, 2), (4, 11), (2, 10)], + 4: [(5, 6), (2, 15), (3, 11)], + 5: [(4, 6), (6, 9)], + 6: [(5, 9), (3, 2), (1, 14)], +} + +beg = random.choice(list(graph.keys())) +end = random.choice(list(graph.keys())) +print("Searching for the shortest path from {} -> {}".format(beg, end)) +print(dijkstra(beg, end, graph)) diff --git a/users/wpcarro/scratch/facebook/edit-distance.py b/users/wpcarro/scratch/facebook/edit-distance.py new file mode 100644 index 000000000000..a5b744f30f27 --- /dev/null +++ b/users/wpcarro/scratch/facebook/edit-distance.py @@ -0,0 +1,47 @@ +def print_grid(grid): + result = [] + for row in grid: + result.append(" ".join(str(c) for c in row)) + return print("\n".join(result)) + +def edit_distance(a, b): + """ + Compute the "edit distance" to transform string `a` into string `b`. + """ + grid = [] + for row in range(len(a) + 1): + r = [] + for col in range(len(b) + 1): + r.append(0) + grid.append(r) + + # left-to-right + # populate grid[0][i] + for col in range(len(grid[0])): + grid[0][col] = col + + # top-to-bottom + # populate grid[i][0] + for row in range(len(grid)): + grid[row][0] = row + + for row in range(1, len(grid)): + for col in range(1, len(grid[row])): + # last characters are the same + if a[0:row][-1] == b[0:col][-1]: + grid[row][col] = grid[row - 1][col - 1] + else: + # substitution + s = 1 + grid[row - 1][col - 1] + # deletion + d = 1 + grid[row - 1][col] + # insertion + i = 1 + grid[row][col - 1] + grid[row][col] = min(s, d, i) + print_grid(grid) + return grid[-1][-1] + +result = edit_distance("pizza", "pisa") +print(result) +assert result == 2 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/evaluator.hs b/users/wpcarro/scratch/facebook/evaluator.hs new file mode 100644 index 000000000000..1ba46a754892 --- /dev/null +++ b/users/wpcarro/scratch/facebook/evaluator.hs @@ -0,0 +1,39 @@ +module Evaluator where + +data Token + = TokenInt Integer + | TokenAdd + | TokenMultiply + deriving (Eq, Show) + +newtype AST = AST [Token] + deriving (Eq, Show) + +tokens :: [Token] +tokens = + [ TokenInt 13 + , TokenAdd + , TokenInt 2 + , TokenMultiply + , TokenInt 4 + , TokenAdd + , TokenInt 7 + , TokenAdd + , TokenInt 3 + , TokenMultiply + , TokenInt 8 + ] + +-- expression -> addition ; +-- addition -> multiplication ( "+" multiplication )* ; +-- multiplication -> terminal ( "*" terminal )* ; +-- terminal -> NUMBER ; + +parseExpression :: [Token] -> ([Token], AST) +parseExpression tokens = do + lhs, rest = parseMultiplication tokens + +parseMulitplication :: [Token] -> ([Token], AST) + +main :: IO () +main = print $ parse tokens diff --git a/users/wpcarro/scratch/facebook/evaluator.py b/users/wpcarro/scratch/facebook/evaluator.py new file mode 100644 index 000000000000..14deb66a8f65 --- /dev/null +++ b/users/wpcarro/scratch/facebook/evaluator.py @@ -0,0 +1,234 @@ +# After stumbling through my first technical screen, I'm going to drill +# algorithms for implementing evaluators for a toy expression language: +# e.g. 2 + 13 * 3 + 5 * 2 +# +# As of now, I'm aware of a few algorithms for solving this: +# - DONE: Convert infix expression to Polish notation and evaluate the Polish +# notation. +# - DONE: Evaluate the tokens using two stacks and avoid converting it. +# - DONE: Create a tree of depth two to encode the operator precedence and +# evaluate that AST. +# - TODO: Convert the infix expression to a prefix expression +# - TODO: Write a recursive descent parser and evaluate the AST. + +operators = { + '*': 1, + '+': 0, +} + +def tokenize(xs): + result = [] + i = 0 + while i < len(xs): + current = xs[i] + if current == ' ': + i += 1 + continue + elif current in operators.keys(): + result.append(current) + i += 1 + else: + i += 1 + while i < len(xs) and xs[i] in {str(n) for n in range(10)}: + current += xs[i] + i += 1 + result.append(int(current)) + return result + +# Convert infix to postfix; evaluate postfix +# I believe this is known as the Shunting-Yards algorithm +def postfix(tokens): + result = [] + s = [] + for token in tokens: + if type(token) == int: + result.append(token) + else: + while s and operators[token] < operators[s[-1]]: + result.append(s.pop()) + s.append(token) + while s: + result.append(s.pop()) + return result + +def do_evaluate_with_polish_notation(tokens): + s = [] + for token in tokens: + if token == '*': + s.append(s.pop() * s.pop()) + elif token == '+': + s.append(s.pop() + s.pop()) + else: + s.append(token) + return s[-1] + +def evaluate_with_polish_notation(expr): + tokens = tokenize(expr) + print("Tokens: {}".format(tokens)) + pn = postfix(tokens) + print("Postfix: {}".format(pn)) + result = do_evaluate_with_polish_notation(pn) + print("Result: {}".format(result)) + return result + +# Evaluate Tokens + +def apply_operator(op, a, b): + if op == '*': + return a * b + elif op == '+': + return a + b + +def do_evaluate_tokens(tokens): + vals = [] + ops = [] + for token in tokens: + if type(token) == int: + vals.append(token) + elif token == '*': + ops.append(token) + elif token == '+': + while ops and operators[token] < operators[ops[-1]]: + vals.append(apply_operator(ops.pop(), vals.pop(), vals.pop())) + ops.append(token) + else: + raise Exception("Unexpected token: {}".format(token)) + while ops: + vals.append(apply_operator(ops.pop(), vals.pop(), vals.pop())) + return vals[-1] + +def evaluate_tokens(expr): + tokens = tokenize(expr) + print("Tokens: {}".format(tokens)) + result = do_evaluate_tokens(tokens) + print("Result: {}".format(result)) + return result + +# Ad Hoc Tree + +def parse(tokens): + result = [] + series = [] + for token in tokens: + if type(token) == int: + series.append(token) + elif token == '*': + continue + elif token == '+': + result.append(series) + series = [] + else: + raise Exception("Unexpected token: {}".format(token)) + result.append(series) + return result + +def product(xs): + result = 1 + for x in xs: + result *= x + return result + +def do_evaluate_ad_hoc_tree(ast): + return sum([product(xs) for xs in ast]) + +def evaluate_ad_hoc_tree(expr): + tokens = tokenize(expr) + print("Tokens: {}".format(tokens)) + ast = parse(tokens) + print("AST: {}".format(ast)) + result = do_evaluate_ad_hoc_tree(ast) + print("Result: {}".format(result)) + return result + +# Recursive Descent Parser + +# expression -> addition ; +# addition -> multiplication ( "+" multiplication )* ; +# multiplication -> terminal ( "*" terminal )* ; +# terminal -> NUMBER ; + +class Parser(object): + def __init__(self, tokens): + self.tokens = tokens + self.i = 0 + + # mutations + def advance(self): + self.i += 1 + + def consume(self): + result = self.curr() + self.advance() + return result + + # predicates + def match(self, x): + if self.curr() == x: + self.advance() + return True + return False + + def tokens_available(self): + return self.i < len(self.tokens) + + # getters + def prev(self): + return self.tokens[self.i - 1] + + def curr(self): + return self.tokens[self.i] if self.tokens_available() else None + + def next(self): + return self.tokens[self.i + 1] + +def parse_expression(tokens): + parser = Parser(tokens) + return parse_addition(parser) + +def parse_addition(parser): + result = parse_multiplication(parser) + while parser.match("+"): + op = parser.prev() + rhs = parse_multiplication(parser) + result = ["+", result, rhs] + return result + +def parse_multiplication(parser): + result = parse_terminal(parser) + while parser.match("*"): + op = parser.prev() + rhs = parse_terminal(parser) + result = ["*", result, rhs] + return result + +def parse_terminal(parser): + # If we reach here, the current token *must* be a number. + return parser.consume() + +def evaluate_ast(ast): + if type(ast) == int: + return ast + else: + op, lhs, rhs = ast[0], ast[1], ast[2] + return apply_operator(op, evaluate_ast(lhs), evaluate_ast(rhs)) + +def evaluate_recursive_descent(expr): + tokens = tokenize(expr) + print("Tokens: {}".format(tokens)) + ast = parse_expression(tokens) + print("AST: {}".format(ast)) + result = evaluate_ast(ast) + return result + +methods = { + 'Polish Notation': evaluate_with_polish_notation, + 'Evaluate Tokens': evaluate_tokens, + 'Ad Hoc Tree': evaluate_ad_hoc_tree, + 'Recursive Descent': evaluate_recursive_descent, +} + +for name, fn in methods.items(): + expr = "13 + 2 * 4 + 7 + 3 * 8" + print("Evaluating \"{}\" using the \"{}\" method...".format(expr, name)) + assert fn(expr) == eval(expr) + print("Success!") diff --git a/users/wpcarro/scratch/facebook/find-duplicate-beast-mode.py b/users/wpcarro/scratch/facebook/find-duplicate-beast-mode.py new file mode 100644 index 000000000000..e246415efd1f --- /dev/null +++ b/users/wpcarro/scratch/facebook/find-duplicate-beast-mode.py @@ -0,0 +1,57 @@ +def advance(position, xs): + """ + Return the next element in `xs` pointed to by the current `position`. + """ + return xs[position - 1] + +def find_duplicate(xs): + """ + Find the duplicate integer in the list, `xs`. + """ + beg = xs[-1] + a = beg + b = advance(a, xs) + # Find the first element of the cycle + cycle_beg = None + while a != b: + cycle_beg = a + a = advance(a, xs) + b = advance(b, xs) + b = advance(b, xs) + # The duplicate element is the element before the `cycle_beg` + a = beg + result = None + while a != cycle_beg: + result = a + a = advance(a, xs) + return result + +def find_duplicate(xs): + """ + This is the solution that InterviewCake.com suggests. + """ + # find length of the cycle + beg = xs[-1] + a = beg + for _ in range(len(xs)): + a = advance(a, xs) + element = a + a = advance(a, xs) + n = 1 + while a != element: + a = advance(a, xs) + n += 1 + # find the first element in the cycle + a, b = beg, beg + for _ in range(n): + b = advance(b, xs) + while a != b: + a = advance(a, xs) + b = advance(b, xs) + return a + +xs = [2, 3, 1, 3] +result = find_duplicate(xs) +print(result) +assert result == 3 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/find-duplicate-optimize-for-space.py b/users/wpcarro/scratch/facebook/find-duplicate-optimize-for-space.py new file mode 100644 index 000000000000..7c491aef604e --- /dev/null +++ b/users/wpcarro/scratch/facebook/find-duplicate-optimize-for-space.py @@ -0,0 +1,22 @@ +import random + +def find_duplicate(xs): + print(xs) + # entry point in our cycle is the duplicate + i = xs[0] + j = xs[xs[0]] + while i != j: + print(i, xs[i], j, xs[j]) + i = xs[i] + j = xs[xs[j]] + # detect cycle + j = 0 + while i != j: + i = xs[i] + j = xs[j] + return xs[i] + +n = random.randint(5, 10) +xs = [random.randint(0, n - 1) for _ in range(n)] +result = find_duplicate(xs) +print(xs, result) diff --git a/users/wpcarro/scratch/facebook/find-rotation-point.py b/users/wpcarro/scratch/facebook/find-rotation-point.py new file mode 100644 index 000000000000..3636be4d93b5 --- /dev/null +++ b/users/wpcarro/scratch/facebook/find-rotation-point.py @@ -0,0 +1,47 @@ +from math import floor + +def find_rotation(xs): + if xs[0] < xs[-1]: + return xs[0] + beg, end = 0, len(xs) - 1 + found = False + count = 10 + while not found and count >= 0: + i = beg + floor((end - beg) / 2) + if xs[beg] < xs[i]: + beg = i + i = beg + floor((end - beg) / 2) + elif xs[beg] > xs[i]: + end = i + found = xs[i - 1] > xs[i] + count -= 1 + return xs[i] + + +xs = [(['ptolemaic', + 'retrograde', + 'supplant', + 'undulate', + 'xenoepist', + 'zebra', + 'asymptote', + 'babka', + 'banoffee', + 'engender', + 'karpatka', + 'othellolagkage', + ], "asymptote"), + (['asymptote', + 'babka', + 'banoffee', + 'engender', + 'karpatka', + 'othellolagkage', + ], "asymptote"), + ] + +for x, expected in xs: + result = find_rotation(x) + print(x, result) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/find-unique-int-among-duplicates.py b/users/wpcarro/scratch/facebook/find-unique-int-among-duplicates.py new file mode 100644 index 000000000000..56032aa05c8c --- /dev/null +++ b/users/wpcarro/scratch/facebook/find-unique-int-among-duplicates.py @@ -0,0 +1,17 @@ +import random + +def find_duplicate(xs): + mini, maxi, acc = xs[0], xs[0], xs[0] + for i in range(1, len(xs)): + mini = min(mini, xs[i]) + maxi = max(maxi, xs[i]) + acc = acc ^ xs[i] + mask = mini + for i in range(mini + 1, maxi + 1): + mask = mask ^ i + return mask ^ acc + +xs = [5, 3, 4, 1, 5, 2] +print(xs) +result = find_duplicate(xs) +print(result) diff --git a/users/wpcarro/scratch/facebook/graph-coloring.py b/users/wpcarro/scratch/facebook/graph-coloring.py new file mode 100644 index 000000000000..e5b6d9c89332 --- /dev/null +++ b/users/wpcarro/scratch/facebook/graph-coloring.py @@ -0,0 +1,60 @@ +from collections import deque + +class Palette(object): + def __init__(self, n): + self.i = 0 + self.colors = list(range(n)) + + def get(self): + return self.colors[self.i] + + def advance(self): + self.i += 1 % len(self.colors) + +class GraphNode(object): + def __init__(self, label): + self.label = label + self.neighbors = set() + self.color = None + + def __repr__(self): + result = [] + xs = deque() + xs.append(self) + seen = set() + while xs: + node = xs.popleft() + result.append('{} ({})'.format(node.label, str(node.color))) + for c in node.neighbors: + if c.label not in seen: + xs.append(c) + seen.add(node.label) + return ', '.join(result) + +def color_graph(graph, d): + seen = set() + start = graph + xs = deque() + palette = Palette(d + 1) + xs.append((start, palette.get())) + while xs: + x, color = xs.popleft() + x.color = color + for c in x.neighbors: + if c.label not in seen: + palette.advance() + xs.append((c, palette.get())) + seen.add(x.label) + +a = GraphNode('a') +b = GraphNode('b') +c = GraphNode('c') + +a.neighbors.add(b) +b.neighbors.add(a) +b.neighbors.add(c) +c.neighbors.add(b) + +print(a) +color_graph(a, 3) +print(a) diff --git a/users/wpcarro/scratch/facebook/hard/binary-adder.py b/users/wpcarro/scratch/facebook/hard/binary-adder.py new file mode 100644 index 000000000000..f79a9f22b38b --- /dev/null +++ b/users/wpcarro/scratch/facebook/hard/binary-adder.py @@ -0,0 +1,22 @@ +import random + +def add(a, b): + """ + Return the sum of `a` and `b`. + """ + if b == 0: + return a + sum = a ^ b + carry = (a & b) << 1 + return add(sum, carry) + +################################################################################ +# Tests +################################################################################ + +for _ in range(10): + x, y = random.randint(0, 100), random.randint(0, 100) + print("{} + {} = {} == {}".format(x, y, x + y, add(x, y))) + assert add(x, y) == x + y + print("Pass!") +print("Success!") diff --git a/users/wpcarro/scratch/facebook/hard/fisher-yates.py b/users/wpcarro/scratch/facebook/hard/fisher-yates.py new file mode 100644 index 000000000000..200d1613ddcb --- /dev/null +++ b/users/wpcarro/scratch/facebook/hard/fisher-yates.py @@ -0,0 +1,7 @@ +import random + +def shuffle(xs): + n = len(xs) + for i in range(n): + j = random.randint(i, n - 1) + xs[i], xs[j] = xs[j], xs[i] diff --git a/users/wpcarro/scratch/facebook/hard/random-choice.py b/users/wpcarro/scratch/facebook/hard/random-choice.py new file mode 100644 index 000000000000..a5c6e4e6ee81 --- /dev/null +++ b/users/wpcarro/scratch/facebook/hard/random-choice.py @@ -0,0 +1,50 @@ +import random + +# This class of problems is known as "resevoir sampling". +def choose_a(m, xs): + """ + Randomly choose `m` elements from `xs`. + This algorithm runs in linear time with respect to the size of `xs`. + """ + result = [None] * m + for i in range(len(xs)): + j = random.randint(0, i) + if j < m: + result[j] = xs[i] + return result + +def choose_b(m, xs): + """ + This algorithm, which copies `xs`, which runs in linear time, and then + shuffles the copies, which also runs in linear time, achieves the same + result as `choose_a` and both run in linear time. + + `choose_a` is still preferable since it has a coefficient of one, while this + version has a coefficient of two because it copies + shuffles. + """ + ys = xs[:] + random.shuffle(ys) + return ys[:m] + +def choose_c(m, xs): + """ + This is one, possibly inefficient, way to randomly sample `m` elements from + `xs`. + """ + choices = set() + while len(choices) < m: + choices.add(random.randint(0, len(xs) - 1)) + return [xs[i] for i in choices] + +# ROYGBIV +xs = [ + 'red', + 'orange', + 'yellow', + 'green', + 'blue', + 'indigo', + 'violet', +] +print(choose_b(3, xs)) +print(choose_c(3, xs)) diff --git a/users/wpcarro/scratch/facebook/hard/suffix-tree.py b/users/wpcarro/scratch/facebook/hard/suffix-tree.py new file mode 100644 index 000000000000..782678fb822c --- /dev/null +++ b/users/wpcarro/scratch/facebook/hard/suffix-tree.py @@ -0,0 +1,93 @@ +import random +from collections import deque + +def exists(pattern, tree): + """ + Return true if `pattern` exists in `tree`. + """ + if len(pattern) == 0: + return True + if len(pattern) == 1: + for branch in tree: + if branch[0] == pattern[0]: + return True + return False + for branch in tree: + if branch[0] == pattern[0]: + return exists(pattern[1:], branch[1]) + return False + +# Branch :: (Char, [Branch]) +# SuffixTree :: [Branch] + +def suffix_tree(xs): + """ + Create a suffix tree from the input string, `xs`. + """ + root = [] + for i in range(len(xs)): + curr = xs[i:] + parent = root + for c1 in curr: + grafted = False + for c2, children in parent: + if c1 == c2: + grafted = True + parent = children + if grafted: + continue + else: + children = [] + child = (c1, children) + parent.append(child) + parent = children + return root + +def suffix_tree(x): + """ + Creates a suffix from the input string, `x`. This implementation uses a + stack. + """ + result = [None, []] + q = deque() + for i in range(len(x)): + q.append((result, x[i:])) + while q: + parent, x = q.popleft() + s = [] + s.append((parent, x)) + while s: + parent, x = s.pop() + if not x: + continue + c, rest = x[0], x[1:] + grafted = False + for child in parent[1]: + if c == child[0]: + s.append((child, rest)) + grafted = True + if not grafted: + child = [c, []] + parent[1].append(child) + s.append((child, rest)) + return result[1] + +################################################################################ +# Tests +################################################################################ + +x = random.choice(["burrito", "pizza", "guacamole"]) +tree = suffix_tree(x) +for branch in tree: + print(branch) + +for _ in range(3): + n = len(x) + i, j = random.randint(0, n), random.randint(0, n) + pattern = x[min(i, j):max(i, j)] + print("Checking \"{}\" for \"{}\" ...".format(x, pattern)) + print("Result: {}".format(exists(pattern, tree))) + pattern = random.choice(["foo", "bar", "baz"]) + print("Checking \"{}\" for \"{}\" ...".format(x, pattern)) + print("Result: {}".format(exists(pattern, tree))) + print() diff --git a/users/wpcarro/scratch/facebook/heap.py b/users/wpcarro/scratch/facebook/heap.py new file mode 100644 index 000000000000..0c0dce91b4dd --- /dev/null +++ b/users/wpcarro/scratch/facebook/heap.py @@ -0,0 +1,30 @@ +from math import floor + +class Heap(object): + def __init__(self): + self.xs = [None] + self.i = 1 + + def __repr__(self): + return "[{}]".format(", ".join(str(x) for x in self.xs[1:])) + + def insert(self, x): + if len(self.xs) == 1: + self.xs.append(x) + self.i += 1 + return + self.xs.append(x) + i = self.i + while i != 1 and self.xs[floor(i / 2)] > self.xs[i]: + self.xs[floor(i / 2)], self.xs[i] = self.xs[i], self.xs[floor(i / 2)] + i = floor(i / 2) + self.i += 1 + + def root(self): + return self.xs[1] + +xs = Heap() +print(xs) +for x in [12, 15, 14, 21, 1, 10]: + xs.insert(x) + print(xs) diff --git a/users/wpcarro/scratch/facebook/highest-product-of-3.py b/users/wpcarro/scratch/facebook/highest-product-of-3.py new file mode 100644 index 000000000000..c237b8e52e2d --- /dev/null +++ b/users/wpcarro/scratch/facebook/highest-product-of-3.py @@ -0,0 +1,20 @@ +def hi_product(xs): + lowest_one, highest_one = min(xs[0], xs[1]), max(xs[0], xs[1]) + lowest_two, highest_two = xs[0] * xs[1], xs[0] * xs[1] + highest = float('-inf') + for x in xs[2:]: + highest = max(highest, highest_two * x, lowest_two * x) + lowest_one = min(lowest_one, x) + highest_one = max(highest_one, x) + lowest_two = min(lowest_two, highest_one * x, lowest_one * x) + highest_two = max(highest_two, highest_one * x, lowest_one * x) + return highest + +xs = [([-10,-10,1,3,2], 300), + ([1,10,-5,1,-100], 5000)] + +for x, expected in xs: + result = hi_product(x) + print(x, result) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/infix-to-postfix.py b/users/wpcarro/scratch/facebook/infix-to-postfix.py new file mode 100644 index 000000000000..4c6d64494d95 --- /dev/null +++ b/users/wpcarro/scratch/facebook/infix-to-postfix.py @@ -0,0 +1,51 @@ +operators = { + '*': 1, + '+': 0, +} + +def tokenize(xs): + result = [] + i = 0 + while i < len(xs): + current = xs[i] + if current in operators.keys(): + result.append(current) + i += 1 + continue + else: + i += 1 + while i < len(xs) and xs[i] in {str(n) for n in range(10)}: + current += xs[i] + i += 1 + result.append(int(current)) + return result + +def postfix(xs): + result = [] + s = [] + for x in xs: + if x in operators.keys(): + while s and operators[s[-1]] >= operators[x]: + result.append(s.pop()) + s.append(x) + else: + result.append(x) + while s: + result.append(s.pop()) + return result + +def evaluate(xs): + s = [] + for x in xs: + print(s, x) + if x == '*': + s.append(s.pop() * s.pop()) + elif x == '+': + s.append(s.pop() + s.pop()) + else: + s.append(x) + print(s) + return s[-1] + + +print(evaluate(postfix(tokenize("12+3*10")))) diff --git a/users/wpcarro/scratch/facebook/inflight-entertainment.py b/users/wpcarro/scratch/facebook/inflight-entertainment.py new file mode 100644 index 000000000000..7ddea5350a4f --- /dev/null +++ b/users/wpcarro/scratch/facebook/inflight-entertainment.py @@ -0,0 +1,29 @@ +from random import choice +from utils import init_table + +def get(movie, seeking): + return any([movie in xs for xs in seeking.values()]) + +def set_complement(movie, seeking): + for duration, xs in seeking.items(): + seeking[duration].add(duration - movie) + +def choose_movies(tolerance, duration, movies): + seeking = {duration + i: set() for i in range(-1 * tolerance, tolerance + 1)} + for movie in movies: + if get(movie, seeking): + return movie, duration - movie + else: + set_complement(movie, seeking) + return None + +tolerance = 20 +duration = choice([1, 2, 3]) * choice([1, 2]) * choice([15, 30, 45]) +movies = [choice([1, 2, 3]) * choice([15, 30, 45]) for _ in range(10)] +print("Seeking two movies for a duration of [{}, {}] minutes".format(duration - tolerance, duration + tolerance)) +print(movies) +result = choose_movies(tolerance, duration, movies) +if result: + print("{} + {} = {}".format(result[0], result[1], duration)) +else: + print(":( We're sad because we couldn't find two movies for a {} minute flight".format(duration)) diff --git a/users/wpcarro/scratch/facebook/intersecting-linked-lists.py b/users/wpcarro/scratch/facebook/intersecting-linked-lists.py new file mode 100644 index 000000000000..80ac01dafd56 --- /dev/null +++ b/users/wpcarro/scratch/facebook/intersecting-linked-lists.py @@ -0,0 +1,34 @@ +class LinkedList(object): + def __init__(self, x): + self.val = x + self.next = None + + def __repr__(self): + if self.next: + return "{} -> {}".format(self.val, self.next) + return "{}".format(self.val) + +def find_intersection(a, b): + init_a, init_b = a, b + + while a != b: + a = a.next if a.next else init_b + b = b.next if b.next else init_a + + return a + +# make A... +e1 = LinkedList(5) +d1 = LinkedList(2); d1.next = e1 +c1 = LinkedList(3); c1.next = d1 # shared +b1 = LinkedList(1); b1.next = c1 # shared +a1 = LinkedList(4); a1.next = b1 # shared + +# make B... +c2 = LinkedList(1); c2.next = c1 +b2 = LinkedList(5); b2.next = c2 +a2 = LinkedList(6); a2.next = b2 + +print(a1) +print(a2) +print(find_intersection(a1, a2).val) diff --git a/users/wpcarro/scratch/facebook/interview-cake/bst-checker.py b/users/wpcarro/scratch/facebook/interview-cake/bst-checker.py new file mode 100644 index 000000000000..bbd52fa9c64c --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/bst-checker.py @@ -0,0 +1,14 @@ +def is_valid(node): + """ + Return True if `node` is a valid binary search tree. + """ + s = [] + s.append((float('-inf'), node, float('inf'))) + while s: + lo, node, hi = s.pop() + if lo <= node.value <= hi: + node.lhs and s.append((lo, node.lhs, node.value)) + node.rhs and s.append((node.value, node.rhs, hi)) + else: + return False + return True diff --git a/users/wpcarro/scratch/facebook/interview-cake/cafe-order-checker.py b/users/wpcarro/scratch/facebook/interview-cake/cafe-order-checker.py new file mode 100644 index 000000000000..688c340b987b --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/cafe-order-checker.py @@ -0,0 +1,34 @@ +def valid(take_out, dine_in, served): + # edge case + if len(take_out) + len(dine_in) != len(served): + return False + i = 0 + j = 0 + k = 0 + while i < len(take_out) and j < len(dine_in): + if take_out[i] == served[k]: + i += 1 + elif dine_in[j] == served[k]: + j += 1 + else: + return False + k += 1 + # take out + while i < len(take_out): + if take_out[i] != served[k]: + return False + i += 1 + # dine in + while j < len(dine_in): + if dine_in[j] != served[k]: + return False + j += 1 + return True + +take_out = [17, 8, 24] +dine_in = [12, 19, 2] +served = [17, 8, 12, 19, 24, 2] +result = valid(take_out, dine_in, served) +print(result) +assert result +print("Success!") diff --git a/users/wpcarro/scratch/facebook/interview-cake/linked-list-cycles.py b/users/wpcarro/scratch/facebook/interview-cake/linked-list-cycles.py new file mode 100644 index 000000000000..523ecd959daf --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/linked-list-cycles.py @@ -0,0 +1,70 @@ +def contains_cycle(node): + """ + Return True if the linked-list, `node`, contains a cycle. + """ + if not node: + return False + a = node + b = node.next + while a != b: + a = a.next + if b and b.next and b.next.next: + b = b.next.next + else: + return False + return True + +################################################################################ +# Bonus +################################################################################ + +def first_node_in_cycle(node): + """ + Given that the linked-list, `node`, contains a cycle, return the first + element of that cycle. + """ + # enter the cycle + a = node + b = node.next + while a != b: + a = a.next + b = b.next.next + + # get the length of the cycle + beg = a + a = a.next + n = 1 + while a != beg: + a = a.next + n += 1 + + # run b n-steps ahead of a + a = node + b = node + for _ in range(n): + b = b.next + + # where they intersect is the answer + while a != b: + a = a.next + b = b.next + return a + +################################################################################ +# Tests +################################################################################ + +class Node(object): + def __init__(self, value, next=None): + self.value = value + self.next = next + def __repr__(self): + return "Node({}) -> ...".format(self.value) + +d = Node('d') +c = Node('c', d) +b = Node('b', c) +a = Node('a', b) +d.next = b + +print(first_node_in_cycle(a)) diff --git a/users/wpcarro/scratch/facebook/interview-cake/merge-sorted-arrays.py b/users/wpcarro/scratch/facebook/interview-cake/merge-sorted-arrays.py new file mode 100644 index 000000000000..877bb218fdf5 --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/merge-sorted-arrays.py @@ -0,0 +1,30 @@ +def merge_sorted(xs, ys): + result = [] + i = 0 + j = 0 + while i < len(xs) and j < len(ys): + if xs[i] <= ys[j]: + result.append(xs[i]) + i += 1 + else: + result.append(ys[j]) + j += 1 + while i < len(xs): + result.append(xs[i]) + i += 1 + while j < len(xs): + result.append(ys[j]) + j += 1 + return result + +################################################################################ +# Tests +################################################################################ + +xs = [3, 4, 6, 10, 11, 15] +ys = [1, 5, 8, 12, 14, 19] +result = merge_sorted(xs, ys) +print(result) +assert len(result) == len(xs) + len(ys) +assert result == [1, 3, 4, 5, 6, 8, 10, 11, 12, 14, 15, 19] +print("Success!") diff --git a/users/wpcarro/scratch/facebook/interview-cake/nth-fibonacci.py b/users/wpcarro/scratch/facebook/interview-cake/nth-fibonacci.py new file mode 100644 index 000000000000..4629798cf711 --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/nth-fibonacci.py @@ -0,0 +1,6 @@ +def fib(n): + cache = (0, 1) + for _ in range(n): + a, b = cache + cache = (b, a + b) + return cache[0] diff --git a/users/wpcarro/scratch/facebook/interview-cake/permutation-palindrome.py b/users/wpcarro/scratch/facebook/interview-cake/permutation-palindrome.py new file mode 100644 index 000000000000..ced3b336e0d9 --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/permutation-palindrome.py @@ -0,0 +1,8 @@ +from collections import Counter + +def permutation_can_be_palindrome(x): + odd = 0 + for _, n in Counter(x): + if n % 0 != 0: + odd += 1 + return odd <= 1 diff --git a/users/wpcarro/scratch/facebook/interview-cake/queue-two-stacks.py b/users/wpcarro/scratch/facebook/interview-cake/queue-two-stacks.py new file mode 100644 index 000000000000..bfa465f98d7f --- /dev/null +++ b/users/wpcarro/scratch/facebook/interview-cake/queue-two-stacks.py @@ -0,0 +1,17 @@ +class Queue(object): + def __init__(self): + self.lhs = [] + self.rhs = [] + + def enqueue(self, x): + self.lhs.append(x) + + def dequeue(self): + if self.rhs: + return self.rhs.pop() + while self.lhs: + self.rhs.append(self.lhs.pop()) + if self.rhs: + return self.rhs.pop() + else: + raise Exception("Attempting to remove an item from an empty queue") diff --git a/users/wpcarro/scratch/facebook/knapsack-faq.py b/users/wpcarro/scratch/facebook/knapsack-faq.py new file mode 100644 index 000000000000..ae04f5eb96c0 --- /dev/null +++ b/users/wpcarro/scratch/facebook/knapsack-faq.py @@ -0,0 +1,42 @@ +from utils import get, init_table, print_table + +# This problem has a few variants: +# - limited supply of each item +# - unlimited supply of each item +# - fractional amounts of each item (e.g. rice) + +def max_haul(capacity, items): + min_kg = min([kg for _, kg in items]) + max_kg = max([kg for _, kg in items]) + + cols = int(max_kg / min_kg) + fr_col_index = lambda index: min_kg * index + min_kg + to_col_index = lambda capacity: int((capacity - min_kg) * cols / max_kg) + + table = init_table(rows=len(items), cols=cols, default=0) + for row in range(len(table)): + for col in range(len(table[row])): + curr_capacity = fr_col_index(col) + value, kg = items[row] + + if kg > curr_capacity: + a = 0 + else: + a = value + get(table, row - 1, to_col_index(curr_capacity - kg)) + + b = get(table, row - 1, col) + table[row][col] = max([a, b]) + print_table(table) + return table[-1][-1] + +guitar = (1500, 1) +stereo = (3000, 4) +laptop = (2000, 3) +necklace = (2000, 0.5) +items = [necklace, guitar, stereo, laptop] +capacity = 4 +result = max_haul(capacity, items) +expected = 4000 +print(result, expected) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/kth-to-last-node-in-singly-linked-list.py b/users/wpcarro/scratch/facebook/kth-to-last-node-in-singly-linked-list.py new file mode 100644 index 000000000000..dd258d924d10 --- /dev/null +++ b/users/wpcarro/scratch/facebook/kth-to-last-node-in-singly-linked-list.py @@ -0,0 +1,26 @@ +from linked_list import Node, from_list + +def kth_to_last_node(k, node): + one = node + two = node + for _ in range(k - 1): + if not one: + return None + one = one.next + while one.next: + one = one.next + two = two.next + return two.value + + +xs = from_list(["Angel Food", "Bundt", "Cheese", "Devil's Food", "Eccles"]) +result = kth_to_last_node(2, xs) +print(result) +assert result == "Devil's Food" +print("Success!") + +xs = from_list(["Angel Food", "Bundt"]) +result = kth_to_last_node(30, xs) +print(result) +assert result is None +print("Success!") diff --git a/users/wpcarro/scratch/facebook/language.py b/users/wpcarro/scratch/facebook/language.py new file mode 100644 index 000000000000..b57f469b49d2 --- /dev/null +++ b/users/wpcarro/scratch/facebook/language.py @@ -0,0 +1,70 @@ +import random + +# Write an evaluator for a small language: +# - operators: '+', '*' +# - operands: Integers +# +# E.g. evaluate("2+14*90+5*16") + +def tokenize(xs): + result = [] + i = 0 + while i < len(xs): + current = xs[i] + if current in {'*', '+'}: + result.append(current) + i += 1 + continue + elif current == ' ': + i += 1 + continue + else: + i += 1 + while i < len(xs) and xs[i] in {str(x) for x in range(10)}: + current += xs[i] + i += 1 + result.append(int(current)) + return result + +def ast(tokens): + result = [] + series = [] + for token in tokens: + if token == '+': + result.append(series) + series = [] + elif token == '*': + continue + else: + series.append(token) + if series: + result.append(series) + return result + +def product(xs): + result = 1 + for x in xs: + result *= x + return result + +def evaluate(x): + tokens = tokenize(x) + tree = ast(tokens) + return sum([product(xs) for xs in tree]) + +n = 7 +operands = [random.randint(0, 100) for _ in range(n)] +operators = [random.choice(['+','*']) for _ in range(n - 1)] +expr = [] +for i in range(n - 1): + expr.append(operands[i]) + expr.append(operators[i]) +expr.append(operands[-1]) + +expr = ' '.join([str(x) for x in expr]) +print("Expression: {}".format(expr)) +print("Tokens: {}".format(tokenize(expr))) +print("AST: {}".format(ast(tokenize(expr)))) +print("Answer: {}".format(evaluate(expr))) +assert evaluate(expr) == eval(expr) +print("Success!") diff --git a/users/wpcarro/scratch/facebook/language2.py b/users/wpcarro/scratch/facebook/language2.py new file mode 100644 index 000000000000..3aebd454833b --- /dev/null +++ b/users/wpcarro/scratch/facebook/language2.py @@ -0,0 +1,50 @@ + + + + + + + + + +def tokenize(xs): + result = [] + i = 0 + while i < len(xs): + curr = xs[i] + if curr in {'*','+'}: + result.append(curr) + i += 1 + continue + i += 1 + while i < len(xs) and xs[i] in {str(x) for x in range(10)}: + curr += xs[i] + i += 1 + result.append(int(curr)) + return result + +def parse(tokens): + result = [] + series = [] + for token in tokens: + if token == '*': + continue + elif token == '+': + result.append(series) + series = [] + else: + series.append(token) + if series: + result.append(series) + return result + +def product(xs): + result = 1 + for x in xs: + result *= x + return result + +def evaluate(tree): + return sum([product(xs) for xs in tree]) + +print(evaluate(parse(tokenize("2+30*8*9+10")))) diff --git a/users/wpcarro/scratch/facebook/largest-contiguous-sum.py b/users/wpcarro/scratch/facebook/largest-contiguous-sum.py new file mode 100644 index 000000000000..7761bf1c61df --- /dev/null +++ b/users/wpcarro/scratch/facebook/largest-contiguous-sum.py @@ -0,0 +1,15 @@ +def find_sum(xs): + result = float('-inf') + streak = 0 + for x in xs: + result = max(result, streak, x) + if streak + x <= 0: + streak = x + else: + streak += x + return result + + +x = [2,-8,3,-2,4,-10] +assert find_sum(x) == 5 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/largest-stack.py b/users/wpcarro/scratch/facebook/largest-stack.py new file mode 100644 index 000000000000..052db44153d4 --- /dev/null +++ b/users/wpcarro/scratch/facebook/largest-stack.py @@ -0,0 +1,49 @@ +from stack import Stack, from_list +from heapq import heapify, heappush, heappop +from random import shuffle + +class MaxStack(Stack): + def __init__(self): + self.max = Stack() + super().__init__() + + def __repr__(self): + return super().__repr__() + + def push(self, x): + super().push(x) + max = self.get_max() + if not max: + self.max.push(x) + else: + self.max.push(max if x < max else x) + + def pop(self): + self.max.pop() + return super().pop() + + def get_max(self): + return self.max.peek() + +xs = list(range(1, 11)) +shuffle(xs) +stack = MaxStack() +for x in xs: + stack.push(x) + +print(stack) +result = stack.get_max() +print(result) +assert result == 10 + +popped = stack.pop() +print("Popped: {}".format(popped)) +print(stack) +while popped != 10: + assert stack.get_max() == 10 + popped = stack.pop() + print("Popped: {}".format(popped)) + print(stack) + +assert stack.get_max() != 10 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/leetcode.org b/users/wpcarro/scratch/facebook/leetcode.org new file mode 100644 index 000000000000..6e915faf2913 --- /dev/null +++ b/users/wpcarro/scratch/facebook/leetcode.org @@ -0,0 +1,163 @@ +# This list is from: +# https://www.teamblind.com/post/New-Year-Gift---Curated-List-of-Top-100-LeetCode-Questions-to-Save-Your-Time-OaM1orEU +* Array +** DONE Two Sum + https://leetcode.com/problems/two-sum/ +** DONE Best Time to Buy and Sell Stock + https://leetcode.com/problems/best-time-to-buy-and-sell-stock/ +** DONE Contains Duplicate + https://leetcode.com/problems/contains-duplicate/ +** DONE Product of Array Except Self + https://leetcode.com/problems/product-of-array-except-self/ +** DONE Maximum Subarray + https://leetcode.com/problems/maximum-subarray/ +** DONE Maximum Product Subarray + https://leetcode.com/problems/maximum-product-subarray/ +** DONE Find Minimum in Rotated Sorted Array + https://leetcode.com/problems/find-minimum-in-rotated-sorted-array/ +** DONE Search in Rotated Sorted Array + https://leetcode.com/problems/search-in-rotated-sorted-array/ +** DONE 3Sum + https://leetcode.com/problems/3sum/ +** DONE Container With Most Water + https://leetcode.com/problems/container-with-most-water/ +* Binary +** DONE Sum of Two Integers + https://leetcode.com/problems/sum-of-two-integers/ +** DONE Number of 1 Bits + https://leetcode.com/problems/number-of-1-bits/ +** TODO Counting Bits + https://leetcode.com/problems/counting-bits/ +** DONE Missing Number + https://leetcode.com/problems/missing-number/ +** TODO Reverse Bits + https://leetcode.com/problems/reverse-bits/ +* Dynamic Programming +** DONE Climbing Stairs + https://leetcode.com/problems/climbing-stairs/ +** TODO Coin Change + https://leetcode.com/problems/coin-change/ +** TODO Longest Increasing Subsequence + https://leetcode.com/problems/longest-increasing-subsequence/ +** TODO Longest Common Subsequence +** DONE Word Break Problem + https://leetcode.com/problems/word-break/ +** TODO Combination Sum + https://leetcode.com/problems/combination-sum-iv/ +** TODO House Robber + https://leetcode.com/problems/house-robber/ +** TODO House Robber II + https://leetcode.com/problems/house-robber-ii/ +** TODO Decode Ways + https://leetcode.com/problems/decode-ways/ +** TODO Unique Paths + https://leetcode.com/problems/unique-paths/ +** TODO Jump Game + https://leetcode.com/problems/jump-game/ +* Graph +** DONE Clone Graph + https://leetcode.com/problems/clone-graph/ +** DONE Course Schedule + https://leetcode.com/problems/course-schedule/ +** TODO Pacific Atlantic Water Flow + https://leetcode.com/problems/pacific-atlantic-water-flow/ +** DONE Number of Islands + https://leetcode.com/problems/number-of-islands/ +** TODO Longest Consecutive Sequence + https://leetcode.com/problems/longest-consecutive-sequence/ +** TODO Alien Dictionary (Leetcode Premium) + https://leetcode.com/problems/alien-dictionary/ +** DONE Graph Valid Tree (Leetcode Premium) + https://leetcode.com/problems/graph-valid-tree/ +** DONE Number of Connected Components in an Undirected Graph (Leetcode Premium) + https://leetcode.com/problems/number-of-connected-components-in-an-undirected-graph/ +* Interval +** TODO Insert Interval + https://leetcode.com/problems/insert-interval/ +** DONE Merge Intervals + https://leetcode.com/problems/merge-intervals/ +** TODO No Overlapping Intervals + https://leetcode.com/problems/non-overlapping-intervals/ +** DONE Meeting Rooms (Leetcode Premium) + https://leetcode.com/problems/meeting-rooms/ +** TODO Meeting Rooms II (Leetcode Premium) + https://leetcode.com/problems/meeting-rooms-ii/ +* Linked List +** DONE Reverse a Linked List + https://leetcode.com/problems/reverse-linked-list/ +** DONE Detect Cycle in a Linked List + https://leetcode.com/problems/linked-list-cycle/ +** DONE Merge Two Sorted Lists + https://leetcode.com/problems/merge-two-sorted-lists/ +** DONE Merge K Sorted Lists + https://leetcode.com/problems/merge-k-sorted-lists/ +** DONE Remove Nth Node From End Of List + https://leetcode.com/problems/remove-nth-node-from-end-of-list/ +** DONE Reorder List + https://leetcode.com/problems/reorder-list/ +* Matrix +** DONE Set Matrix Zeroes + https://leetcode.com/problems/set-matrix-zeroes/ +** DONE Spiral Matrix + https://leetcode.com/problems/spiral-matrix/ +** TODO Rotate Image + https://leetcode.com/problems/rotate-image/ +** DONE Word Search + https://leetcode.com/problems/word-search/ +* String +** TODO Longest Substring Without Repeating Characters + https://leetcode.com/problems/longest-substring-without-repeating-characters/ +** TODO Longest Repeating Character Replacement + https://leetcode.com/problems/longest-repeating-character-replacement/ +** TODO Minimum Window Substring + https://leetcode.com/problems/minimum-window-substring/ +** DONE Valid Anagram + https://leetcode.com/problems/valid-anagram/ +** DONE Group Anagrams + https://leetcode.com/problems/group-anagrams/ +** DONE Valid Parentheses + https://leetcode.com/problems/valid-parentheses/ +** DONE Valid Palindrome + https://leetcode.com/problems/valid-palindrome/ +** TODO Longest Palindromic Substring + https://leetcode.com/problems/longest-palindromic-substring/ +** TODO Palindromic Substrings + https://leetcode.com/problems/palindromic-substrings/ +** DONE Encode and Decode Strings (Leetcode Premium) + https://leetcode.com/problems/encode-and-decode-strings/ +* Tree +** DONE Maximum Depth of Binary Tree + https://leetcode.com/problems/maximum-depth-of-binary-tree/ +** DONE Same Tree + https://leetcode.com/problems/same-tree/ +** DONE Invert/Flip Binary Tree + https://leetcode.com/problems/invert-binary-tree/ +** DONE Binary Tree Maximum Path Sum + https://leetcode.com/problems/binary-tree-maximum-path-sum/ +** DONE Binary Tree Level Order Traversal + https://leetcode.com/problems/binary-tree-level-order-traversal/ +** DONE Serialize and Deserialize Binary Tree + https://leetcode.com/problems/serialize-and-deserialize-binary-tree/ +** DONE Subtree of Another Tree + https://leetcode.com/problems/subtree-of-another-tree/ +** DONE Construct Binary Tree from Preorder and Inorder Traversal + https://leetcode.com/problems/construct-binary-tree-from-preorder-and-inorder-traversal/ +** DONE Validate Binary Search Tree + https://leetcode.com/problems/validate-binary-search-tree/ +** DONE Kth Smallest Element in a BST + https://leetcode.com/problems/kth-smallest-element-in-a-bst/ +** DONE Lowest Common Ancestor of BST + https://leetcode.com/problems/lowest-common-ancestor-of-a-binary-search-tree/ +** DONE Implement Trie (Prefix Tree) + https://leetcode.com/problems/implement-trie-prefix-tree/ +** DONE Add and Search Word + https://leetcode.com/problems/add-and-search-word-data-structure-design/ +** DONE Word Search II + https://leetcode.com/problems/word-search-ii/ +* Heap +** DONE Merge K Sorted Lists + https://leetcode.com/problems/merge-k-sorted-lists/ +** DONE Top K Frequent Elements + https://leetcode.com/problems/top-k-frequent-elements/ +** DONE Find Median from Data Stream + https://leetcode.com/problems/find-median-from-data-stream/ diff --git a/users/wpcarro/scratch/facebook/linked-list-cycles.py b/users/wpcarro/scratch/facebook/linked-list-cycles.py new file mode 100644 index 000000000000..56f54d497808 --- /dev/null +++ b/users/wpcarro/scratch/facebook/linked-list-cycles.py @@ -0,0 +1,26 @@ +import random + +from linked_list import Node + +def contains_cycle(node): + one = node + two = node + while two.next and two.next.next: + one = one.next + two = two.next.next + if one == two: + return True + return False + +xs = Node(1, Node(2, Node(3))) +assert not contains_cycle(xs) +print("Success!") + +a = Node(1) +b = Node(2) +c = Node(3) +a.next = b +b.next = c +c.next = random.choice([a, b, c]) +assert contains_cycle(a) +print("Success!") diff --git a/users/wpcarro/scratch/facebook/linked_list.py b/users/wpcarro/scratch/facebook/linked_list.py new file mode 100644 index 000000000000..1ae7061e8393 --- /dev/null +++ b/users/wpcarro/scratch/facebook/linked_list.py @@ -0,0 +1,22 @@ +class Node(object): + def __init__(self, value=None, next=None): + self.value = value + self.next = next + + def __repr__(self): + result = [] + node = self + while node: + result.append(str(node.value)) + node = node.next + return 'LinkedList({xs})'.format(xs=', '.join(result)) + +def from_list(xs): + head = Node(xs[0]) + node = head + for x in xs[1:]: + node.next = Node(x) + node = node.next + return head + +list = from_list(['A', 'B', 'C']) diff --git a/users/wpcarro/scratch/facebook/london-knapsack.py b/users/wpcarro/scratch/facebook/london-knapsack.py new file mode 100644 index 000000000000..a034fb49611d --- /dev/null +++ b/users/wpcarro/scratch/facebook/london-knapsack.py @@ -0,0 +1,42 @@ +from utils import get, init_table, print_table + +def optimal_itinerary(duration, items): + min_duration = min([duration for duration, _ in items]) + max_duration = max([duration for duration, _ in items]) + table = init_table(rows=len(items), cols=int(max_duration / min_duration), default=0) + to_index = lambda duration: int(duration / min_duration) - 1 + to_duration = lambda i: i * min_duration + min_duration + + for row in range(len(table)): + for col in range(len(table[row])): + curr_duration = to_duration(col) + duration, value = items[row] + if duration > curr_duration: + a = 0 + else: + a = value + get(table, row - 1, to_index(curr_duration - duration)) + b = get(table, row - 1, col) + table[row][col] = max([a, b]) + + print_table(table) + return table[-1][-1] + +# You're in London for two days, and you'd like to see the following +# attractions. How can you maximize your time spent in London? +westminster = (0.5, 7) +globe_theater = (0.5, 6) +national_gallery = (1, 9) +british_museum = (2, 9) +st_pauls_cathedral = (0.5, 8) +items = [ + westminster, + globe_theater, + national_gallery, + british_museum, + st_pauls_cathedral, +] +result = optimal_itinerary(2, items) +expected = 24 +print(result, expected) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/longest-common-substring.py b/users/wpcarro/scratch/facebook/longest-common-substring.py new file mode 100644 index 000000000000..8a838db45d1e --- /dev/null +++ b/users/wpcarro/scratch/facebook/longest-common-substring.py @@ -0,0 +1,20 @@ +from utils import get, init_table, print_table + +def longest_common_substring(a, b): + """ + Computes the length of the longest string that's present in both `a` and + `b`. + """ + table = init_table(rows=len(b), cols=len(a), default=0) + for row in range(len(table)): + for col in range(len(table[row])): + if b[row] == a[col]: + table[row][col] = 1 + get(table, row - 1, col - 1) + return max([max(row) for row in table]) + +dictionary = ["fish", "vista"] +result = [longest_common_substring("hish", x) for x in dictionary] +expected = [3, 2] +print(result, expected) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/merge-sorted-arrays.py b/users/wpcarro/scratch/facebook/merge-sorted-arrays.py new file mode 100644 index 000000000000..ae9377ad116b --- /dev/null +++ b/users/wpcarro/scratch/facebook/merge-sorted-arrays.py @@ -0,0 +1,44 @@ +def merge_sorted(xs, ys): + result = [] + i, j = 0, 0 + + while i < len(xs) and j < len(ys): + if xs[i] <= ys[j]: + result.append(xs[i]) + i += 1 + else: + result.append(ys[j]) + j += 1 + + while i < len(xs): + result.append(xs[i]) + i += 1 + + while j < len(ys): + result.append(ys[j]) + j += 1 + + return result + +# A +result = merge_sorted([3, 4, 6, 10, 11, 15], [1, 5, 8, 12, 14, 19]) +print(result) +assert result == [1, 3, 4, 5, 6, 8, 10, 11, 12, 14, 15, 19] + +# B +result = merge_sorted([], [1,2,3]) +print(result) +assert result == [1,2,3] + +# C +result = merge_sorted([1,2,3], []) +print(result) +assert result == [1,2,3] + +# D +result = merge_sorted([], []) +print(result) +assert result == [] + +# Wahoo! +print("Success!") diff --git a/users/wpcarro/scratch/facebook/merging-ranges.py b/users/wpcarro/scratch/facebook/merging-ranges.py new file mode 100644 index 000000000000..6da44572ee7e --- /dev/null +++ b/users/wpcarro/scratch/facebook/merging-ranges.py @@ -0,0 +1,23 @@ + +def merge(xs): + xs.sort() + result = xs[0:1] + for a, b in xs[1:]: + y, z = result[-1] + if a <= z: + result[-1] = (y, max(b, z)) + else: + result.append((a, b)) + return result + +inputs = [([(0,1),(3,5),(4,8),(10,12),(9,10)], [(0,1),(3,8),(9,12)]), + ([(1,2),(2,3)], [(1,3)]), + ([(1,5),(2,3)], [(1,5)]), + ([(1,10),(2,6),(3,5),(7,9)], [(1,10)]), + ] +for x, expected in inputs: + result = merge(x) + print(x) + print(result) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/mesh-message.py b/users/wpcarro/scratch/facebook/mesh-message.py new file mode 100644 index 000000000000..8438b059d843 --- /dev/null +++ b/users/wpcarro/scratch/facebook/mesh-message.py @@ -0,0 +1,40 @@ +from heapq import heappush, heappop +import random + +def shortest_path(a, b, graph): + seen = set() + h = [] + heappush(h, (0, a, [a])) + while h: + km, x, path = heappop(h) + if x == b: + return path + for c in graph[x]: + if c not in seen: + heappush(h, (km + 1, c, path + [c])) + raise Exception("We were unable to find a path from {} to {}".format(a, b)) + +graph = { + 'Min' : ['William', 'Jayden', 'Omar'], + 'William' : ['Min', 'Noam'], + 'Jayden' : ['Min', 'Amelia', 'Ren', 'Noam'], + 'Ren' : ['Jayden', 'Omar'], + 'Amelia' : ['Jayden', 'Adam', 'Miguel'], + 'Adam' : ['Amelia', 'Miguel', 'Sofia', 'Lucas'], + 'Miguel' : ['Amelia', 'Adam', 'Liam', 'Nathan'], + 'Noam' : ['Nathan', 'Jayden', 'William'], + 'Omar' : ['Ren', 'Min', 'Scott'], + 'Liam' : ['Ren'], + 'Nathan' : ['Noam'], + 'Scott' : [], +} + +result = shortest_path('Jayden', 'Adam', graph) +print(result) +assert result == ['Jayden', 'Amelia', 'Adam'] +print('Success!') + +beg = random.choice(list(graph.keys())) +end = random.choice(list(graph.keys())) +print("Attempting to find the shortest path between {} and {}".format(beg, end)) +print(shortest_path(beg, end, graph)) diff --git a/users/wpcarro/scratch/facebook/moderate/decompress-xml.py b/users/wpcarro/scratch/facebook/moderate/decompress-xml.py new file mode 100644 index 000000000000..b22983ed7aff --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/decompress-xml.py @@ -0,0 +1,98 @@ +import string +from parser import Parser + +mapping = { + 1: "family", + 2: "person", + 3: "firstName", + 4: "lastName", + 5: "state", +} + +def parse_int(i, xs): + result = "" + while i < len(xs) and xs[i] in string.digits: + result += xs[i] + i += 1 + return i, int(result) + +def parse_string(i, xs): + result = "" + while xs[i+1] not in string.digits: + result += xs[i] + i += 1 + return i, result + +def tokenize(xs): + result = [] + i = 0 + while i < len(xs): + if xs[i] in string.digits: + i, n = parse_int(i, xs) + result.append(n) + elif xs[i] in string.ascii_letters: + i, x = parse_string(i, xs) + result.append(x) + elif xs[i] == " ": + i += 1 + continue + return result + +def parse(xs): + parser = Parser(tokenize(xs)) + return parse_element(parser) + +# Element -> Tag Attribute* End Element* End ; +# Tag -> INTEGER ; +# Value -> STRING End ; +# Attribute -> Tag Value ; +# End -> 0 ; + +def parse_element(parser): + if type(parser.curr()) == str: + return parser.consume() + tag_id = parser.expect_predicate(lambda x: type(x) == int) + tag = mapping[tag_id] + attrs = parse_attrs(parser) + parser.expect([0]) + children = [] + while not parser.exhausted() and parser.curr() != 0: + children.append(parse_element(parser)) + parser.expect([0]) + return [tag, attrs, children] + +def parse_attrs(parser): + result = [] + while parser.curr() != 0: + tag_id = parser.expect_predicate(lambda x: type(x) == int) + tag = mapping[tag_id] + value = parser.consume() + result.append((tag, value)) + return result + +def stringify_xml(tree, indent=0): + if type(tree) == str: + return tree + result = "" + tag, attrs, children = tree + + str_attrs = [] + for k, v in attrs: + str_attrs.append("{}=\"{}\"".format(k, v)) + str_attrs = (" " if str_attrs else "") + " ".join(str_attrs) + + str_children = [] + for child in children: + str_children.append(" " * 2 * indent + stringify_xml(child, indent + 1)) + str_children = "\n".join(str_children) + + result += "{}<{}{}>\n{}{}\n{}</{}>".format( + " " * 2 * indent, tag, str_attrs, " " * 2 * indent, str_children, + " " * 2 * indent, tag) + return result + +x = "1 4 McDowell 5 CA 0 2 3 Gayle 0 Some Message 0 0" +print("Input: {}".format(x)) +print("Tokens: {}".format(tokenize(x))) +print("Parsed: {}".format(parse(x))) +print("{}".format(stringify_xml(parse(x)))) diff --git a/users/wpcarro/scratch/facebook/moderate/find-pairs-for-sum.py b/users/wpcarro/scratch/facebook/moderate/find-pairs-for-sum.py new file mode 100644 index 000000000000..69c2fc431296 --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/find-pairs-for-sum.py @@ -0,0 +1,19 @@ +import random + +def find_pairs(xs, n): + """ + Return all pairs of integers in `xs` that sum to `n`. + """ + seeking = set() + result = set() + for x in xs: + if x in seeking: + result.add((n - x, x)) + else: + seeking.add(n - x) + return result + +xs = [random.randint(1, 10) for _ in range(10)] +n = random.randint(1, 10) + random.randint(1, 10) +print("Seeking all pairs in {} for {}...".format(xs, n)) +print(find_pairs(xs, n)) diff --git a/users/wpcarro/scratch/facebook/moderate/parser.py b/users/wpcarro/scratch/facebook/moderate/parser.py new file mode 100644 index 000000000000..57dfb058c037 --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/parser.py @@ -0,0 +1,37 @@ +class Parser(object): + def __init__(self, tokens): + self.tokens = tokens + self.i = 0 + + def prev(self): + return self.tokens[self.i - 1] + + def curr(self): + return self.tokens[self.i] + + def next(self): + return self.tokens[self.i + 1] + + def consume(self): + if not self.exhausted(): + self.i += 1 + return self.prev() + + def match(self, xs): + if not self.exhausted() and self.curr() in xs: + self.consume() + return True + return False + + def expect(self, xs): + if not self.match(xs): + raise Exception("Expected token \"{}\" but received \"{}\"".format(xs, self.curr())) + return self.prev() + + def expect_predicate(self, predicate): + if predicate(self.curr()): + return self.consume() + raise Exception("Expected token \"{}\" to pass predicate, but it did not".format(self.curr())) + + def exhausted(self): + return self.i >= len(self.tokens) diff --git a/users/wpcarro/scratch/facebook/moderate/rand7.py b/users/wpcarro/scratch/facebook/moderate/rand7.py new file mode 100644 index 000000000000..ed3a7cea80e5 --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/rand7.py @@ -0,0 +1,25 @@ +# Define a function, rand7, that generates a random number [0,7), using only +# rand5, which generates a random number [0,5). + +import random +from collections import Counter + +# Returns [0,4] +def rand5(): + return random.randint(0,4) + +# Return [0,6] +def rand7_a(): + return sum(rand5() for _ in range(7)) % 7 + +# Return [0,6] +def rand7_b(): + x = 5 * rand5() + rand5() + if x < 21: + return x % 7 + return rand7_b() + +c = Counter([rand7_a() for _ in range(100000)]) +print(c) +c = Counter([rand7_b() for _ in range(100000)]) +print(c) diff --git a/users/wpcarro/scratch/facebook/moderate/tic-tac-toe-checker.py b/users/wpcarro/scratch/facebook/moderate/tic-tac-toe-checker.py new file mode 100644 index 000000000000..342c29be6bdb --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/tic-tac-toe-checker.py @@ -0,0 +1,99 @@ +import random + +def print_board(board): + result = [] + for row in range(len(board)): + r = [] + for col in range(len(board[row])): + cell = board[row][col] + if not cell: + r.append("-") + else: + r.append(cell) + result.append(" | ".join(r)) + print("\n---------\n".join(result)) + +def init_board(): + result = [] + for row in range(3): + r = [] + for col in range(3): + r.append(None) + result.append(r) + return result + +def check(board, player): + print_board(board) + print() + if player not in "XO": + raise Exception("Only checking the board for Xs or Os. You supplied {}".format(player)) + dn, ax, ddg, udg = "DOWN", "ACROSS", "DOWN_DIAGONAL", "UP_DIAGONAL" + ways = [ + [[dn, ax, ddg], [dn], [dn, udg]], + [[ax], [], []], + [[ax], [], []], + ] + for row in range(len(board)): + for col in range(len(board[row])): + if board[row][col] == player: + xs = ways[row][col] + for x in xs: + if x == dn: + if {player} == {board[row+1][col], board[row+2][col]}: + return True + if x == ax: + if {player} == {board[row][col+1], board[row][col+2]}: + return True + if x == ddg: + if {player} == {board[row+1][col+1], board[row+2][col+2]}: + return True + if x == udg: + if {player} == {board[row+1][col-1], board[row+2][col-2]}: + return True + return False + +def op(player): + return "X" if player == "O" else "O" + +dn_win = lambda p: [ + [op(p), p, None], + [op(p), p, None], + [None, p, None], +] + +ax_win = lambda p: [ + [p, p, p], + [op(p), op(p), None], + [None, None, None], +] + +ddg_win = lambda p: [ + [p, None, None], + [op(p), p, None], + [op(p), None, p], +] + +udg_win = lambda p: [ + [op(p), None, p], + [op(p), p, None], + [p, None, None], +] + +# Down +p = random.choice(["X", "O"]) +assert check(dn_win(p), p) == True +assert check(dn_win(p), op(p)) == False +# Across +p = random.choice(["X", "O"]) +assert check(ax_win(p), p) == True +assert check(ax_win(p), op(p)) == False +# Down Diagonally +p = random.choice(["X", "O"]) +assert check(ddg_win(p), p) == True +assert check(ddg_win(p), op(p)) == False +# Down Diagonally +p = random.choice(["X", "O"]) +assert check(udg_win(p), p) == True +assert check(udg_win(p), op(p)) == False +# Success +print("Tests pass!") diff --git a/users/wpcarro/scratch/facebook/moderate/unsorted-substring.py b/users/wpcarro/scratch/facebook/moderate/unsorted-substring.py new file mode 100644 index 000000000000..de7326b05822 --- /dev/null +++ b/users/wpcarro/scratch/facebook/moderate/unsorted-substring.py @@ -0,0 +1,67 @@ +# Write a function that accepts an array of integers and returns the indices for +# the starting and ending integers that, if their elements were sorted, the +# entire array would be sorted. + +################################################################################ +# First Attempt +################################################################################ + +def unsorted_substring(xs): + ys = xs[:]; ys.sort() + m = 0 + while xs[m] == ys[m]: + m += 1 + if m >= len(xs): + return -1, -1 + n = len(xs) - 1 + while xs[n] == ys[n]: + n -= 1 + return m, n + +################################################################################ +# Second Attempt +################################################################################ + +def unsorted_substring_2(xs): + beg = 1 + while xs[beg - 1] <= xs[beg]: + beg += 1 + if beg >= len(xs): + return -1, -1 + end = len(xs) - 2 + while xs[end + 1] >= xs[end]: + end -= 1 + + min_mid = xs[beg] + max_mid = xs[beg] + i = beg + 1 + while i <= end: + min_mid = min(min_mid, xs[i]) + max_mid = max(max_mid, xs[i]) + i += 1 + + # beg -= 1 until max(lhs) <= min(mid) + while beg - 1 >= 0 and xs[beg - 1] >= min_mid: + beg -= 1 + + # end += 1 while max(mid) <= min(rhs) + while end + 1 < len(xs) and max_mid >= xs[end + 1]: + end += 1 + return beg, end + +################################################################################ +# Tests +################################################################################ + +xs = [ + [1,2,4,7,10,11,7,12,6,7,16,18,19], + [1,2,3,4], + [4,3,2,1], + [1,3,2,4], + [2,1,3,4], +] + +for x in xs: + print("Testing: {}".format(x)) + print("1) {}".format(unsorted_substring(x))) + print("2) {}".format(unsorted_substring_2(x))) diff --git a/users/wpcarro/scratch/facebook/move-zeroes-to-end.py b/users/wpcarro/scratch/facebook/move-zeroes-to-end.py new file mode 100644 index 000000000000..1535b5a9faac --- /dev/null +++ b/users/wpcarro/scratch/facebook/move-zeroes-to-end.py @@ -0,0 +1,62 @@ +from collections import deque + +def move_zeroes_to_end_quadratic(xs): + """ + This solution is suboptimal. It runs in quadratic time, and it uses constant + space. + """ + i = 0 + while i < len(xs) - 1: + if xs[i] == 0: + j = i + 1 + while j < len(xs) and xs[j] == 0: + j += 1 + if j >= len(xs): + break + xs[i], xs[j] = xs[j], xs[i] + i += 1 + +def move_zeroes_to_end_linear(xs): + """ + This solution is clever. It runs in linear time proportionate to the number + of elements in `xs`, and has linear space proportionate to the number of + consecutive zeroes in `xs`. + """ + q = deque() + for i in range(len(xs)): + if xs[i] == 0: + q.append(i) + else: + if q: + j = q.popleft() + xs[i], xs[j] = xs[j], xs[i] + q.append(i) + +def move_zeroes_to_end_linear_constant_space(xs): + """ + This is the optimal solution. It runs in linear time and uses constant + space. + """ + i = 0 + for j in range(len(xs)): + if xs[j] != 0: + xs[i], xs[j] = xs[j], xs[i] + i += 1 + + +################################################################################ +# Tests +################################################################################ + +xss = [ + [1, 2, 0, 3, 4, 0, 0, 5, 0], + [0, 1, 2, 0, 3, 4], + [0, 0], +] + +f = move_zeroes_to_end_linear_constant_space + +for xs in xss: + print(xs) + f(xs) + print(xs) diff --git a/users/wpcarro/scratch/facebook/mst.py b/users/wpcarro/scratch/facebook/mst.py new file mode 100644 index 000000000000..81aa5cd48744 --- /dev/null +++ b/users/wpcarro/scratch/facebook/mst.py @@ -0,0 +1,71 @@ +from heapq import heappush, heappop +import random + +def to_vertex_list(graph): + result = {} + for a, b, kg in graph: + if a in result: + result[a].append((b, kg)) + else: + result[a] = [(b, kg)] + if b in result: + result[b].append((a, kg)) + else: + result[b] = [(a, kg)] + return result + +def mst(graph): + graph = to_vertex_list(graph) + beg = random.choice(list(graph.keys())) + h = [] + result = [] + seen = set() + for c, kg in graph[beg]: + heappush(h, (kg, beg, c)) + while h: + kg, beg, end = heappop(h) + # detect cycles + if end in seen: + continue + # use the edge + seen.add(beg) + seen.add(end) + result.append((beg, end)) + for c, kg in graph[end]: + heappush(h, (kg, end, c)) + return result + +graphs = [ + [ + ('A', 'B', 7), + ('A', 'D', 5), + ('B', 'D', 9), + ('E', 'D', 15), + ('F', 'D', 6), + ('F', 'G', 11), + ('F', 'E', 8), + ('G', 'E', 9), + ('C', 'E', 5), + ('B', 'E', 7), + ('B', 'C', 8), + ], + [ + ('A', 'B', 4), + ('A', 'C', 8), + ('B', 'C', 11), + ('B', 'E', 8), + ('C', 'D', 7), + ('C', 'F', 1), + ('D', 'E', 2), + ('D', 'F', 6), + ('E', 'G', 7), + ('E', 'H', 4), + ('F', 'H', 2), + ('G', 'H', 14), + ('G', 'I', 9), + ('H', 'I', 10), + ], +] + +for graph in graphs: + print(mst(graph)) diff --git a/users/wpcarro/scratch/facebook/n-queens.py b/users/wpcarro/scratch/facebook/n-queens.py new file mode 100644 index 000000000000..fc9326886cd8 --- /dev/null +++ b/users/wpcarro/scratch/facebook/n-queens.py @@ -0,0 +1,46 @@ +def print_board(board): + result = [] + for row in range(8): + r = [] + for col in range(8): + r.append("X" if col == board[row] else "-") + result.append(" ".join(r)) + print("\n".join(result)) + print() + +def can_place(board, row, col): + column_occupied = not any([board[i] == col for i in range(row)]) + + diagonals_clear = True + for r in range(row): + w = abs(col - board[r]) + h = abs(r - row) + if w == h: + diagonals_clear = False + break + + return all([column_occupied, diagonals_clear]) + +def init_board(): + board = [] + for row in range(8): + board.append(None) + return board + +def copy_board(board): + return board[:] + +def n_queens(): + do_n_queens(init_board(), 0, 0) + +def do_n_queens(board, row, col): + if row == 8: + print_board(board) + return + for i in range(col, 8): + if can_place(board, row, i): + copy = copy_board(board) + copy[row] = i + do_n_queens(copy, row + 1, 0) + +n_queens() diff --git a/users/wpcarro/scratch/facebook/nearby-words.py b/users/wpcarro/scratch/facebook/nearby-words.py new file mode 100644 index 000000000000..d2fc3cf5cfcc --- /dev/null +++ b/users/wpcarro/scratch/facebook/nearby-words.py @@ -0,0 +1,33 @@ +def nearby_chars(c): + keyboard = [ + "qwertyuiop", + "asdfghjkl", + "zxcvbnm", + ] + + for row in keyboard: + for i in range(len(row)): + if row[i] == c: + result = set() + if i + 1 < len(row): + result.add(row[i + 1]) + if i - 1 >= 0: + result.add(row[i - 1]) + return result + +def is_word(word): + words = { + "hello", + } + return word in words + +def nearby_words(x): + result = set() + for i in range(len(x)): + for c in nearby_chars(x[i]): + candidate = x[0:i] + c + x[i+1:] + if is_word(candidate): + result.add(candidate) + return result + +print(nearby_words('gello')) diff --git a/users/wpcarro/scratch/facebook/node.py b/users/wpcarro/scratch/facebook/node.py new file mode 100644 index 000000000000..4e24983af772 --- /dev/null +++ b/users/wpcarro/scratch/facebook/node.py @@ -0,0 +1,38 @@ +class Node(object): + def __init__(self, value, left=None, right=None): + self.value = value + self.left = left + self.right = right + + def insert_left(self, value): + self.left = Node(value) + return self.left + + def insert_right(self, value): + self.right = Node(value) + return self.right + +tree = Node( + 50, + Node( + 17, + Node( + 12, + Node(9), + Node(14), + ), + Node( + 23, + Node(19), + ), + ), + Node( + 72, + Node( + 54, + None, + Node(67) + ), + Node(76), + ), +) diff --git a/users/wpcarro/scratch/facebook/nth-fibonacci.py b/users/wpcarro/scratch/facebook/nth-fibonacci.py new file mode 100644 index 000000000000..f524067b3b44 --- /dev/null +++ b/users/wpcarro/scratch/facebook/nth-fibonacci.py @@ -0,0 +1,13 @@ +# 0, 1, 1, 2, 3, 5 +def fib(n): + if n < 0: + raise Exception("Need to supply an index that's >= 0. Not: {}".format(n)) + elif n in {0, 1}: + return n + state = [0, 1] + for i in range(1, n): + state[0], state[1] = state[1], state[0] + state[1] + return state[-1] + +for i in range(10): + print("fib({}) => {}".format(i, fib(i))) diff --git a/users/wpcarro/scratch/facebook/onsite.txt b/users/wpcarro/scratch/facebook/onsite.txt new file mode 100644 index 000000000000..b5242c4bd3a2 --- /dev/null +++ b/users/wpcarro/scratch/facebook/onsite.txt @@ -0,0 +1,22 @@ +** Behavior Interview ** +- Can I work in an unstructured environment? +- Do I have a growth mindset? +- How do I handle conflict? +- Am I empathic? +- Am I a self-starter? +- What is my communication style? +- Do I persevere? +- <forgot to write this one down> + +** Design Interview ** +- requirement gathering, problem exploring +- component analysis +- quantitative analysis +- trade-offs +- bottlenecks, weaknesses +- securing data (e.g. PII) + +Consider: +- pagination +- push/pull requests +- API design diff --git a/users/wpcarro/scratch/facebook/parsing/json.py b/users/wpcarro/scratch/facebook/parsing/json.py new file mode 100644 index 000000000000..3975e973fefa --- /dev/null +++ b/users/wpcarro/scratch/facebook/parsing/json.py @@ -0,0 +1,121 @@ +from parser import Parser + +# As an exercise to stress-test my understanding of recursive descent parsers, +# I'm attempting to write a JSON parser without referencing any existing BNF +# descriptions of JSON or existing JSON parser implementations. +# +# I'm only parsing a subset of JSON: enough to parse `sample`. Here is the BNF +# that I wrote to describe my expected input: +# +# expression -> object +# object -> '{' ( STRING ':' expression ) ( ',' STRING ':' expression )* '}' +# | array +# array -> '[' expression ( ',' expression )* ']' +# | literal +# literal -> STRING | INT + +def tokenize(xs): + """ + Return a list of tokens from the string input, `xs`. + """ + result = [] + i = 0 + while i < len(xs): + # single characters + if xs[i] in ",{}:[]": + result.append(xs[i]) + i += 1 + # strings + elif xs[i] == "\"": + curr = xs[i] + i += 1 + while xs[i] != "\"": + curr += xs[i] + i += 1 + curr += xs[i] + result.append(curr) + i += 1 + # integers + elif xs[i] in "0123456789": + curr = xs[i] + i += 1 + while xs[i] in "0123456789": + curr += xs[i] + i += 1 + result.append(int(curr)) + # whitespace + elif xs[i] in {" ", "\n"}: + i += 1 + return result + +def parse_json(x): + """ + Attempt to parse the string, `x`, into JSON. + """ + tokens = tokenize(x) + return parse_object(Parser(tokens)) + +def parse_object(parser): + if parser.match(['{']): + key = parse_string(parser) + parser.expect([':']) + value = parse_object(parser) + result = [(key, value)] + while parser.match([',']): + key = parse_string(parser) + parser.match([':']) + value = parse_object(parser) + result.append((key, value)) + return result + return parse_array(parser) + +def parse_array(parser): + if parser.match(['[']): + if parser.match([']']): + return [] + result = [parse_object(parser)] + while parser.match([',']): + result.append(parse_object(parser)) + parser.expect([']']) + return result + else: + return parse_literal(parser) + +def parse_string(parser): + if parser.curr().startswith("\""): + return parser.consume() + else: + raise Exception("Unexpected token: {}".format(parser.curr())) + +def parse_literal(parser): + return parser.consume() + +sample = """ +{ + "glossary": { + "title": "example glossary", + "GlossDiv": { + "title": "S", + "GlossList": { + "GlossEntry": { + "ID": "SGML", + "SortAs": "SGML", + "GlossTerm": "Standard Generalized Markup Language", + "Acronym": "SGML", + "Abbrev": "ISO 8879:1986", + "GlossDef": { + "para": "A meta-markup language, used to create markup languages such as DocBook.", + "GlossSeeAlso": [ + "GML", + "XML" + ] + }, + "GlossSee": "markup" + } + } + } + } +} +""" + +print(parse_json(sample)) diff --git a/users/wpcarro/scratch/facebook/parsing/parser.py b/users/wpcarro/scratch/facebook/parsing/parser.py new file mode 100644 index 000000000000..407bff61c980 --- /dev/null +++ b/users/wpcarro/scratch/facebook/parsing/parser.py @@ -0,0 +1,28 @@ +class Parser(object): + def __init__(self, tokens): + self.tokens = tokens + self.i = 0 + + def prev(self): + return self.tokens[self.i - 1] + + def curr(self): + return self.tokens[self.i] + + def consume(self): + if not self.exhausted(): + self.i += 1 + return self.prev() + + def match(self, xs): + if not self.exhausted() and self.curr() in xs: + self.consume() + return True + return False + + def expect(self, xs): + if not self.match(xs): + raise Exception("Expected token \"{}\" but received \"{}\"".format(xs, self.curr())) + + def exhausted(self): + return self.i >= len(self.tokens) diff --git a/users/wpcarro/scratch/facebook/parsing/regex.py b/users/wpcarro/scratch/facebook/parsing/regex.py new file mode 100644 index 000000000000..7fc2ef34e2ff --- /dev/null +++ b/users/wpcarro/scratch/facebook/parsing/regex.py @@ -0,0 +1,184 @@ +# Writing a small proof-of-concept... +# - lexer +# - parser +# - compiler +# ...for regex. +# +# BNF +# expression -> ( char_class | CHAR ) quantifier? ( "|" expression )* +# char_class -> "[" CHAR+ "]" +# quantifier -> "?" | "*" | "+" | "{" INT? "," INT? "}" +# +# Of the numerous things I do not support, here are a few items of which I'm +# aware: +# - alternatives: (a|b) +# - capture groups: (ab)cd + +from parser import Parser +import string + +################################################################################ +# Top-Level API +################################################################################ + +def tokenize(xs): + """ + Transform `xs` into a list of tokens. + + Also: expand shorthand symbols using the following table: + - ? -> {0,1} + - * -> {0,} + - + -> {1,} + """ + result = [] + i = 0 + shorthand = { + "?": ["{", 0, ",", 1, "}"], + "*": ["{", 0, ",", "}"], + "+": ["{", 1, ",", "}"], + } + while i < len(xs): + if xs[i] in shorthand: + for c in shorthand[xs[i]]: + result.append(c) + i += 1 + elif xs[i] == "{": + result.append(xs[i]) + i += 1 + curr = "" + while xs[i] in string.digits: + curr += xs[i] + i += 1 + result.append(int(curr)) + assert xs[i] == "," + result.append(",") + i += 1 + curr = "" + while xs[i] in string.digits: + curr += xs[i] + i += 1 + result.append(int(curr)) + else: + result.append(xs[i]) + i += 1 + return result + +def parse(expr): + """ + Tokenize `expr` and convert it into a parse-tree. + """ + tokens = tokenize(expr) + return parse_tokens(tokens) + +def compile(xs): + """ + Transform `xs`, a parse-tree representing a regex, into a function that + accepts a string, and returns the substring that the regex matches. + """ + def fn(input): + match = "" + i = 0 + for x in xs: + matches, q = x[1], x[2] + lo, hi = q[1], q[2] + for j in range(lo): + if i < len(input) and input[i] in matches: + match += input[i] + i += 1 + else: + print("Failed to match {} with {}".format(input[i], matches)) + return None + if hi == float('inf'): + while i < len(input) and input[i] in matches: + match += input[i] + i += 1 + else: + for j in range(hi - lo): + if i < len(input) and input[i] in matches: + match += input[i] + i += 1 + return match + return fn + +################################################################################ +# Helper Functions +################################################################################ + +def parse_tokens(tokens): + result = [] + parser = Parser(tokens) + while not parser.exhausted(): + result.append(parse_expression(parser)) + return result + +def parse_expression(parser): + if parser.curr() == "[": + return parse_character_class(parser) + else: + return parse_character(parser) + +def parse_character_class(parser): + parser.expect("[") + beg = parser.consume() + parser.expect("-") + end = parser.consume() + parser.expect("]") + if parser.curr() == "{": + q = parse_quantifier(parser) + return char_class(xs=expand_range(beg, end), q=q) + +def parse_quantifier(parser): + parser.expect("{") + if parser.match([","]): + end = parser.consume() + parser.expect("}") + return quantifier(beg=0, end=end) + else: + beg = parser.consume() + parser.expect(",") + if parser.match(["}"]): + return quantifier(beg=beg) + else: + end = parser.consume() + parser.expect("}") + return quantifier(beg=beg, end=end) + +def parse_character(parser): + c = parser.consume() + q = None + if parser.curr() == "{": + q = parse_quantifier(parser) + return char_class(xs={c}, q=q) + +def char_class(xs=set(), q=None): + if not q: + q = quantifier(beg=1, end=1) + return ["CHARACTER_CLASS", xs, q] + +def expand_range(beg, end): + # TODO: Implement this + return {string.printable[i] + for i in range(string.printable.index(beg), + string.printable.index(end) + 1)} + +def quantifier(beg=0, end=float('inf')): + return ['QUANTIFIER', beg, end] + +################################################################################ +# Tests +################################################################################ + +xs = [ + ("[a-c]*[0-9]{2,3}", ["dog"]), + ("ca+t?", ["cat", "caaaat", "ca", "dog"]), +] + +for re, inputs in xs: + print("Regex: {}".format(re)) + print("Tokens: {}".format(tokenize(re))) + print("Parsed: {}".format(parse(re))) + print("\nTESTS") + for input in inputs: + print("Attempting to match \"{}\"...".format(input)) + parser = compile(parse(re)) + print("Result: \"{}\"\n".format(parser(input))) diff --git a/users/wpcarro/scratch/facebook/permutation-palindrome.py b/users/wpcarro/scratch/facebook/permutation-palindrome.py new file mode 100644 index 000000000000..30603578ffb3 --- /dev/null +++ b/users/wpcarro/scratch/facebook/permutation-palindrome.py @@ -0,0 +1,17 @@ +from collections import Counter + +def is_palindrome(x): + return len([count for _, count in Counter(x).items() if count % 2 == 1]) <= 1 + + +xs = [("civic", True), + ("ivicc", True), + ("civil", False), + ("livci", False)] + +for x, expected in xs: + result = is_palindrome(x) + print(x) + print(result) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/polynomial-rolling-hash.py b/users/wpcarro/scratch/facebook/polynomial-rolling-hash.py new file mode 100644 index 000000000000..0c7b7cb5a0c4 --- /dev/null +++ b/users/wpcarro/scratch/facebook/polynomial-rolling-hash.py @@ -0,0 +1,72 @@ +def compute_hash(x): + """ + Compute a unique fingerprint for the string input, `x`, as an integer using + the following equation: + + x[0] * P^0 + x[1] * P^1 + ... x[n-1] * P^(n-1) % M + + P and M are constants where P represents the next available prime number + that's GTE the number of unique characters you'll be hashing. In the case of + all lowercase characters, of which there are 26, the next available prime + number is 31. + """ + p = 31 + m = int(10e9) + 9 # large prime number + power = 0 + result = 0 + for c in x: + result += ord(c) * p**power + power += 1 + return result % m + +class HashTable(object): + def __init__(self, size): + """ + Create a hash table with `size` buckets. + """ + buckets = [] + for _ in range(size): + buckets.append([]) + self.xs = buckets + self.compute_hash = lambda k: compute_hash(k) % size + + def __repr__(self): + result = [] + for bucket in self.xs: + for entry in bucket: + result.append(entry) + return "HashTable({})".format(",".join(str(x) for x in result)) + + def get(self, key): + """ + Attempt to retrieve value stored under `key`. + """ + h = self.compute_hash(key) + for k, v in self.xs[h]: + if k == key: + return v + return None + + def put(self, key, val): + """ + Set `key` to `val`; update value at `key` if it already exists. + """ + h = self.compute_hash(key) + for i in range(len(self.xs[h])): + # Update entry if the key exists... + if self.xs[h][i][0] == key: + self.xs[h][i] = (key, val) + return None + # ...create a new entry otherwise + self.xs[h].append((key, val)) + + def delete(self, key): + """ + Remove entry `key` from the hash table. + """ + h = self.compute_hash(key) + for i in range(len(self.xs[h])): + k, v = self.xs[h][i] + if k == key: + self.xs[h].remove((k, v)) + return diff --git a/users/wpcarro/scratch/facebook/product-of-all-other-numbers.py b/users/wpcarro/scratch/facebook/product-of-all-other-numbers.py new file mode 100644 index 000000000000..d381386b6257 --- /dev/null +++ b/users/wpcarro/scratch/facebook/product-of-all-other-numbers.py @@ -0,0 +1,33 @@ +from random import randint +from math import floor + +# loop {forwards, backwards, up, down} +# through a table of values, [[a]]. + +def product(xs): + n = len(xs) + lhs = [1] * (n + 1) + for i in range(1, n): + lhs[i] = lhs[i - 1] * xs[i - 1] + rhs = [1] * (n + 1) + for i in range(n - 1, 0, -1): + rhs[i] = rhs[i + 1] * xs[i] + result = [] + for i in range(n): + result.append(lhs[i] * rhs[i + 1]) + return result + +def computed_expected(xs): + product = 1 + for x in xs: + product *= x + return [floor(product / x) for x in xs] + +xs = [randint(1, 10) for _ in range(5)] +expected = computed_expected(xs) +result = product(xs) +print(xs, result, expected) +assert result == expected +print("Success!") + +print(product([2, 4, 3, 10, 5])) diff --git a/users/wpcarro/scratch/facebook/queue-two-stacks.py b/users/wpcarro/scratch/facebook/queue-two-stacks.py new file mode 100644 index 000000000000..a71abeb00552 --- /dev/null +++ b/users/wpcarro/scratch/facebook/queue-two-stacks.py @@ -0,0 +1,20 @@ +from stack import Stack + +class Queue(object): + def __init__(self): + self.lhs = Stack() + self.rhs = Stack() + + def enqueue(self, x): + self.rhs.push(x) + + def dequeue(self, x): + y = self.rhs.pop() + while y: + self.lhs.push(y) + y = self.rhs.pop() + result = self.lhs.pop() + y = self.lhs.pop() + while y: + self.rhs.push(y) + return result diff --git a/users/wpcarro/scratch/facebook/rabin-karp.py b/users/wpcarro/scratch/facebook/rabin-karp.py new file mode 100644 index 000000000000..53a47b278333 --- /dev/null +++ b/users/wpcarro/scratch/facebook/rabin-karp.py @@ -0,0 +1,27 @@ +def substring_exists(corpus, pattern): + """ + Return True if `pattern` appears in `corpus`. + + This function runs in O(m) time where n is equal to the length of + `corpus`. To improve the efficiency of this algorithm, use a hashing + function the reduces the number of collisions, which will consequently + reduce the number of string-to-string, linear comparisons. + """ + m, n = len(corpus), len(pattern) + a = sum(ord(c) for c in corpus[0:n]) + b = sum(ord(c) for c in pattern) + + # (clumsily) prevent an off-by-one error... + if a == b and corpus[0:n] == pattern: + return True + + for i in range(1, m - n): + # Update the hash of corpus by subtracting the hash of the character + # that is sliding out of view and adding the hash of the character that + # is sliding into view. + a = a - ord(corpus[i - 1]) + ord(corpus[i + n - 1]) + # Integer comparison in O(0) time followed by string comparison in O(m) + # time. + if a == b and corpus[i:i + n] == pattern: + return True + return False diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/magic-index.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/magic-index.py new file mode 100644 index 000000000000..03b2de015dfe --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/magic-index.py @@ -0,0 +1,33 @@ +from math import floor + +def find_magic_index_brute(xs): + for i in range(len(xs)): + if xs[i] == i: + return i + return -1 + +def mid(lo, hi): + return lo + floor((hi - lo) / 2) + +def find_magic_index(xs): + lo, hi = 0, len(xs) - 1 + return do_find_magic_index(xs, 0, len(xs) - 1) + +def do_find_magic_index(xs, lo, hi): + pass + +xss = [ + [], + [-1,0,2,4,5,6], + [1,1,1,1,1,5], + [-2,-2,-2,-2,4], + [1,2,3,4,5], +] + +for xs in xss: + print(xs) + a = find_magic_index_brute(xs) + b = find_magic_index(xs) + print(a, b) + assert a == b + print("Success!") diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/making-change.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/making-change.py new file mode 100644 index 000000000000..30c95a66c319 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/making-change.py @@ -0,0 +1,56 @@ +# Given an infinite supply of: +# - quarters +# - dimes +# - nickels +# - pennies +# Write a function to count the number of ways to make change of n. + +def get(table, row, col): + """ + Defensively get cell `row`, `col` from `table`. + """ + if row < 0 or row >= len(table): + return 0 + if col < 0 or col >= len(table[0]): + return 0 + return table[row][col] + +def print_table(table): + print('\n'.join([ + ','.join([str(col) for col in table[row]]) + for row in range(len(table))])) + +def init_table(rows=0, cols=0, default=0): + result = [] + for row in range(rows): + r = [] + for col in range(cols): + r.append(default) + result.append(r) + return result + +def make_change(n): + coins = [1, 5, 10, 25] + table = init_table(rows=len(coins), cols=n) + + for row in range(len(table)): + for col in range(len(table[row])): + curr_coin = coins[row] + curr_n = col + 1 + # a + a = get(table, row - 1, col) + # b + b = get(table, row, curr_n - curr_coin - 1) + # c + c = 1 if curr_coin <= curr_n else 0 + # commit + if curr_coin == curr_n: + table[row][col] = a + c + else: + table[row][col] = a + b * c + # debug + print_table(table) + print() + return table[-1][-1] + +print(make_change(7)) diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/paint-fill.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/paint-fill.py new file mode 100644 index 000000000000..e9e7f6a9c159 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/paint-fill.py @@ -0,0 +1,36 @@ +from collection import deque + +def fill(point, canvas, color): + if x not in canvas: + return + elif y not in canvas[x]: + return + + x, y = point + if canvas[y][x] == color: + return + canvas[y][x] = color + fill((x + 1, y), canvas, color) + fill((x - 1, y), canvas, color) + fill((x, y + 1), canvas, color) + fill((x, y - 1), canvas, color) + +def fill_bfs(point, canvas, color): + x, y = point + if x not in canvas: + return None + if y not in canvas[x]: + return None + xs = deque() + xs.append((x, y)) + while xs: + x, y = xs.popleft() + canvas[y][x] = color + for x2, y2 in [(x - 1, y), (x + 1, y), (x, y - 1), (x, y + 1)]: + if x2 not in canvas: + continue + elif y2 not in canvas[x2]: + continue + if canvas[y2][x2] != color: + xs.append((x2, y2)) + return None diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/parenthesize-bools.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/parenthesize-bools.py new file mode 100644 index 000000000000..f406d64e657f --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/parenthesize-bools.py @@ -0,0 +1,114 @@ +# BNF +# expression -> bool ( ( '|' | '&' | '^' ) bool )* +# bool -> '0' | '1' + +def tokenize(xs): + result = [] + for c in xs: + if c == '0': + result.append(0) + elif c == '1': + result.append(1) + elif c in "&|^": + result.append(c) + else: + raise Exception("Unexpected token, \"{}\"".format(c)) + return result + +class Parser(object): + def __init__(self, tokens): + self.tokens = tokens + self.i = 0 + + def prev(self): + return self.tokens[self.i - 1] + + def curr(self): + return self.tokens[self.i] + + def match(self, xs): + if self.exhausted(): + return False + if (self.curr() in xs): + self.consume() + return True + return False + + def consume(self): + result = self.curr() + self.i += 1 + return result + + def exhausted(self): + return self.i >= len(self.tokens) + +def recursive_descent(tokens): + parser = Parser(tokens) + return parse_expression(parser) + +def parse_expression(parser): + lhs = parse_bool(parser) + while parser.match(['|', '&', '^']): + op = parser.prev() + rhs = parse_expression(parser) + lhs = [op, lhs, rhs] + return lhs + +def parse_bool(parser): + if parser.curr() == 0: + parser.consume() + return False + elif parser.curr() == 1: + parser.consume() + return True + else: + raise Exception("Unexpected token: {}".format(parser.curr())) + +def f(expr, result): + tokens = tokenize(expr) + tree = recursive_descent(tokens) + return do_f(tree, result) + +def do_f(tree, result): + if type(tree) == bool: + if tree == result: + return 1 + else: + return 0 + + op, lhs, rhs = tree[0], tree[1], tree[2] + truth_tables = { + True: { + '|': [ + (True, True), + (True, False), + (False, True), + ], + '&': [ + (True, True), + ], + '^': [ + (True, False), + (False, True), + ], + }, + False: { + '|': [ + (False, False), + ], + '&': [ + (False, False), + (True, False), + (False, True), + ], + '^': [ + (True, True), + (False, False), + ], + } + } + + return sum([do_f(lhs, x) * do_f(rhs, y) for x, y in truth_tables[result][op]]) + +print(f("1^0|0|1", False)) +print(f("1|0|1|1", False)) diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/permutations.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/permutations.py new file mode 100644 index 000000000000..e23972d4186d --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/permutations.py @@ -0,0 +1,13 @@ +def char_and_rest(i, xs): + return xs[i], xs[:i] + xs[i+1:] + +# perms :: String -> [String] +def perms(xs): + if len(xs) == 1: + return [xs] + result = [] + for c, rest in [char_and_rest(i, xs) for i in range(len(xs))]: + result += [c + perm for perm in perms(rest)] + return result + +print(perms("cat")) diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/robot-grid-traversal.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/robot-grid-traversal.py new file mode 100644 index 000000000000..9ccc08526a99 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/robot-grid-traversal.py @@ -0,0 +1,28 @@ +import random + +def factorial(n): + result = 1 + for i in range(1, n + 1): + result *= i + return result + +def travel(a, b): + if a == b: + return 1 + + ax, ay = a + bx, by = b + if ax > bx or ay > by: + return 0 + + return sum([travel((ax + 1, ay), b), travel((ax, ay + 1), b)]) + +def travel_compute(a, b): + bx, by = b + return int(factorial(bx + by) / (factorial(bx) * factorial(by))) + +a = (0, 0) +b = (random.randint(1, 10), random.randint(1, 10)) +print("Travelling to {}, {}".format(b[0], b[1])) +print(travel(a, b)) +print(travel_compute(a, b)) diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/staircase.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/staircase.py new file mode 100644 index 000000000000..5eb4a8560674 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/staircase.py @@ -0,0 +1 @@ +# accidentally deleted my solution... TBI (again) diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/subsets.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/subsets.py new file mode 100644 index 000000000000..a6d26aa85055 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/subsets.py @@ -0,0 +1,41 @@ +# take-aways: +# - Use integers as lists of boolean values +# - Use 1 << n to compute 2^n where n = len(xs) + +def set_from_int(xs, n): + result = [] + for i in range(len(xs)): + if n & (1 << i) != 0: + result.append(xs[i]) + return result + +# subsets :: Set a -> List (Set a) +def subsets(xs): + n = len(xs) + return [set_from_int(xs, i) for i in range(1 << n)] + +# 0 1 2 +# 0 N Y Y +# 1 _ N Y +# 2 _ _ N + +# For my interview, be able to compute *permutations* and *combinations* + +# This differs from permutations because this is about finding combinations... +# +# bottom-up +# 0 => { } +# 1 => {3} {4} {3} +# 2 => {5,4} {5,3} {4,3} + +xs = [ + ([], [[]]), + ([5], [[], [5]]), + ([5,4], [[],[5],[4],[5,4]]), +] + +for x, expected in xs: + result = subsets(x) + print("subsets({}) => {} == {}".format(x, result, expected)) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/valid-parens.py b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/valid-parens.py new file mode 100644 index 000000000000..56f2c0b27456 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursion-and-dynamic-programming/valid-parens.py @@ -0,0 +1,50 @@ +def valid_parens(n): + if n == 0: + return [] + if n == 1: + return ["()"] + + result = set() + for x in valid_parens(n - 1): + result.add("({})".format(x)) + result.add("(){}".format(x)) + result.add("{}()".format(x)) + return result + +def valid_parens_efficient(n): + result = [] + curr = [''] * n**2 + do_valid_parens_efficient(result, curr, 0, n, n) + return result + +def do_valid_parens_efficient(result, curr, i, lhs, rhs): + if lhs == 0 and rhs == 0: + result.append(''.join(curr)) + else: + if lhs > 0: + curr[i] = '(' + do_valid_parens_efficient(result, curr, i + 1, lhs - 1, rhs) + if rhs > lhs: + curr[i] = ')' + do_valid_parens_efficient(result, curr, i + 1, lhs, rhs - 1) + +# Avoids recursion by using either a stack or a queue. I think this version is +# easier to understand. +def valid_parens_efficient_2(n): + result = [] + xs = [] + xs.append(('', n, n)) + while xs: + curr, lhs, rhs = xs.pop() + print(curr) + if lhs == 0 and rhs == 0: + result.append(''.join(curr)) + if lhs > 0: + xs.append((curr + '(', lhs - 1, rhs)) + if rhs > lhs: + xs.append((curr + ')', lhs, rhs - 1)) + return result + +# print(valid_parens(4)) +print(valid_parens_efficient(3)) +print(valid_parens_efficient_2(3)) diff --git a/users/wpcarro/scratch/facebook/recursive-string-permutations.py b/users/wpcarro/scratch/facebook/recursive-string-permutations.py new file mode 100644 index 000000000000..e4c61eff9f62 --- /dev/null +++ b/users/wpcarro/scratch/facebook/recursive-string-permutations.py @@ -0,0 +1,19 @@ +# permutations: no repeat characters + +def char_and_rest(i, xs): + return xs[i], xs[0:i] + xs[i + 1:] + +def permutations(xs): + if len(xs) == 1: + return [xs] + result = [] + for c, rest in [char_and_rest(i, xs) for i in range(len(xs))]: + result += [c + perm for perm in permutations(rest)] + return result + +expected = ["cat", "cta", "act", "atc", "tca", "tac"] +result = permutations("cat") +print(result, expected) +assert len(result) == len(expected) +assert result == expected +print("Success!") diff --git a/users/wpcarro/scratch/facebook/reverse-linked-list.py b/users/wpcarro/scratch/facebook/reverse-linked-list.py new file mode 100644 index 000000000000..820726733f9d --- /dev/null +++ b/users/wpcarro/scratch/facebook/reverse-linked-list.py @@ -0,0 +1,25 @@ +from linked_list import Node + +def reverse(node): + prev, curr, next = None, node, node.next + + while curr: + curr.next = prev + prev = curr + curr = next + next = curr.next if curr else None + return prev + +one = Node(1) +two = Node(2) +three = Node(3) +one.next = two +two.next = three + +print(one) +result = reverse(one) +print(result) +assert all([result == three, + three.next == two, + two.next == one]) +print("Success!") diff --git a/users/wpcarro/scratch/facebook/reverse-string-in-place.py b/users/wpcarro/scratch/facebook/reverse-string-in-place.py new file mode 100644 index 000000000000..72cd6c27a370 --- /dev/null +++ b/users/wpcarro/scratch/facebook/reverse-string-in-place.py @@ -0,0 +1,14 @@ +# reverse :: [Char] -> () +def reverse(xs): + i = 0 + j = len(xs) - 1 + while i < j: + xs[i], xs[j] = xs[j], xs[i] + i += 1 + j -= 1 + +xs = [list("testing"), list("a"), list("to")] +for x in xs: + print(x) + reverse(x) + print(x) diff --git a/users/wpcarro/scratch/facebook/reverse-words.py b/users/wpcarro/scratch/facebook/reverse-words.py new file mode 100644 index 000000000000..5a38b828a346 --- /dev/null +++ b/users/wpcarro/scratch/facebook/reverse-words.py @@ -0,0 +1,8 @@ +# reverse_word :: [Char] -> () +def reverse_words(x): + pass + +x = list("This is a test") +print(''.join(x)) +reverse_words(x) +print(''.join(result)) diff --git a/users/wpcarro/scratch/facebook/scratch.py b/users/wpcarro/scratch/facebook/scratch.py new file mode 100644 index 000000000000..e772d758473d --- /dev/null +++ b/users/wpcarro/scratch/facebook/scratch.py @@ -0,0 +1,94 @@ +# This is a scratch pad for randomly selected questions + +# def char_and_rest(i, xs): +# return xs[i], xs[:i] + xs[i+1:] + +# def perms(xs): +# if len(xs) == 1: +# return [xs] +# result = [] +# for i in range(len(xs)): +# c, rest = char_and_rest(i, xs) +# for perm in perms(rest): +# result.append(c + ''.join(perm)) +# return result + +# print(perms(list("woah"))) + +# def f(take_out, dine_in, served): +# j, k = 0, 0 +# for i in range(len(served)): +# if j < len(take_out) and served[i] == take_out[j]: +# j += 1 +# elif k < len(dine_in) and served[i] == dine_in[k]: +# k += 1 +# else: +# return False +# if j < len(take_out) or k < len(dine_in): +# return False +# return True + +# take_out = [17, 8, 24] +# dine_in = [12, 19, 2] +# served = [17, 8, 12, 19, 24, 2] +# print(f(take_out, dine_in, served)) + +# def match(a, b): +# if a == '{': +# return b == '}' +# if a == '[': +# return b == ']' +# if a == '(': +# return b == ')' +# return False + +# def f(xs): +# s = [] +# for c in xs: +# if c in {'{', '[', '('}: +# s.append(c) +# elif c in {'}', ']', ')'}: +# opener = s.pop() +# if not match(opener, c): +# return False +# return len(s) == 0 + +# assert f("{[]()}") +# assert f("{[(])}") == False +# assert f("{[}") == False +# print("Success!") + +# def valid_bst(node): +# lhs = max_bst_value(node.left) if node.left else float('-inf') +# rhs = min_bst_value(node.right) if node.right else float('inf') + +# return and([ +# lhs <= node.value, +# rhs > node.value, +# valid_bst(node.left), +# valid_bst(node.right), +# ]) + +import random +import math + +def shuffle(xs): + n = len(xs) + for i in range(n - 1): + j = random.randint(i + 1, n - 1) + xs[i], xs[j] = xs[j], xs[i] + return xs + +def as_card(i): + if i not in range(1, 53): + raise Exception("Not a card") + # 1 + suit = ['Hearts', 'Clubs', 'Diamonds', 'Spades'][math.floor((i - 1) / 13)] + n = ['Ace',2,3,4,5,6,7,8,9,10,'Jack','Queen','King'][(i - 1) % 13] + return '{} of {}'.format(n, suit) + +xs = list(range(1, 53)) +print(xs) +shuffle(xs) +for x in xs: + print(as_card(x)) diff --git a/users/wpcarro/scratch/facebook/second-largest-item-in-bst.py b/users/wpcarro/scratch/facebook/second-largest-item-in-bst.py new file mode 100644 index 000000000000..2815dec9ee60 --- /dev/null +++ b/users/wpcarro/scratch/facebook/second-largest-item-in-bst.py @@ -0,0 +1,22 @@ +from collections import deque +from node import Node, tree + +def find_largest(node): + while node.right: + node = node.right + return node.value + +def find_second_largest(node): + # parent of the rightmost, when rightmost is leaf + # max(rightmost.left) + prev = None + while node.right: + prev = node + node = node.right + if node.left: + return find_largest(node.left) + else: + return prev.value + +assert find_second_largest(tree) == 72 +print("Success!") diff --git a/users/wpcarro/scratch/facebook/shuffle.py b/users/wpcarro/scratch/facebook/shuffle.py new file mode 100644 index 000000000000..21a6a96c6072 --- /dev/null +++ b/users/wpcarro/scratch/facebook/shuffle.py @@ -0,0 +1,17 @@ +from random import randint + +def get_random(i, j): + return randint(i, j) + +def shuffle(xs): + for i in range(len(xs)): + j = get_random(i, len(xs) - 1) + xs[i], xs[j] = xs[j], xs[i] + +xs = list(range(1, 53)) +print(xs) +assert len(set(xs)) == 52 +shuffle(xs) +assert len(set(xs)) == 52 +print(xs) +print("Success!") diff --git a/users/wpcarro/scratch/facebook/stack.py b/users/wpcarro/scratch/facebook/stack.py new file mode 100644 index 000000000000..2a843e22166b --- /dev/null +++ b/users/wpcarro/scratch/facebook/stack.py @@ -0,0 +1,25 @@ +class Stack(object): + def __init__(self): + self.items = [] + + def __repr__(self): + return self.items.__repr__() + + def push(self, x): + self.items.append(x) + + def pop(self): + if not self.items: + return None + return self.items.pop() + + def peek(self): + if not self.items: + return None + return self.items[-1] + +def from_list(xs): + result = Stack() + for x in xs: + result.push(x) + return result diff --git a/users/wpcarro/scratch/facebook/stacking-boxes.py b/users/wpcarro/scratch/facebook/stacking-boxes.py new file mode 100644 index 000000000000..7a3304bc51b9 --- /dev/null +++ b/users/wpcarro/scratch/facebook/stacking-boxes.py @@ -0,0 +1,50 @@ +from random import randint + +class Box(object): + def __init__(self, w, h, d): + self.width = w + self.depth = d + self.height = h + + def __repr__(self): + return "{}x{}x{}".format(self.width, self.depth, self.height) + + def lt(self, b): + return all([ + self.width < b.width, + self.height < b.height, + self.depth < b.depth, + ]) + + def gt(self, b): + return all([ + self.width > b.width, + self.height > b.height, + self.depth > b.depth, + ]) + +def random_box(): + return Box( + randint(1, 10), + randint(1, 10), + randint(1, 10), + ) + +xs = [random_box() for _ in range(5)] + +def highest_stack(xs, cache={}): + if not xs: + return 0 + heights = [] + for i in range(len(xs)): + x, rest = xs[i], xs[0:i] + xs[i+1:] + if cache and x in cache: + height = cache[x] + else: + height = x.height + highest_stack([b for b in rest if x.gt(b)], cache) + cache[x] = height + heights += [height] + return max(heights) + +print(xs) +print(highest_stack(xs)) diff --git a/users/wpcarro/scratch/facebook/stock-price.py b/users/wpcarro/scratch/facebook/stock-price.py new file mode 100644 index 000000000000..8e42f8152311 --- /dev/null +++ b/users/wpcarro/scratch/facebook/stock-price.py @@ -0,0 +1,16 @@ +def max_profit(xs): + buy = xs[0] + profit = xs[1] - xs[0] + for price in xs[1:]: + profit = max(profit, price - buy) + buy = min(buy, price) + return profit + +xs = [([10,7,5,8,11,9], 6), + ([10,8,7,6,5], -1)] + +for x, expected in xs: + result = max_profit(x) + print(x, result) + assert result == expected + print("Success!") diff --git a/users/wpcarro/scratch/facebook/todo.org b/users/wpcarro/scratch/facebook/todo.org new file mode 100644 index 000000000000..6ac99267dbfa --- /dev/null +++ b/users/wpcarro/scratch/facebook/todo.org @@ -0,0 +1,60 @@ +* Array and string manipulation +** DONE Merging Meeting Times +** DONE Reverse String in Place +** TODO Reverse Words +** DONE Merge Sorted Arrays +** DONE Cafe Order Checker +* Hashing and hash tables +** DONE Inflight Entertainment +** DONE Permutation Palindrome +** DONE Word Cloud Data +** DONE Top Scores +* Greedy Algorithms +** DONE Apple Stocks +** DONE Highest Product of 3 +** DONE Product of All Other Numbers +** DONE Cafe Order Checker +** DONE In-Place Shuffle +* Sorting, searching, and logarithms +** DONE Find Rotation Point +** TODO Find Repeat, Space Edition +** DONE Top Scores +** DONE Merging Meeting Times +* Trees and graphs +** DONE Balanced Binary Tree +** DONE Binary Search Tree Checker +** DONE 2nd Largest Item in a Binary Search Tree +** DONE Graph Coloring +** DONE MeshMessage +** DONE Find Repeat, Space Edition BEAST MODE +* Dynamic programming and recursion +** DONE Recursive String Permutations +** DONE Compute nth Fibonacci Number +** DONE Making Change +** DONE The Cake Thief +** DONE Balanced Binary Tree +** DONE Binary Search Tree Checker +** DONE 2nd Largest Item in a Binary Search Tree +* Queues and stacks +** DONE Largest Stack +** DONE Implement A Queue With Two Stacks +** DONE Parenthesis Matching +** DONE Bracket Validator +* Linked lists +** DONE Delete Node +** DONE Does This Linked List Have A Cycle? +** DONE Reverse A Linked List +** DONE Kth to Last Node in a Singly-Linked List +** DONE Find Repeat, Space Edition BEAST MODE +* General programming +** TODO Rectangular Love +** TODO Temperature Tracker +* Bit manipulation +** DONE The Stolen Breakfast Drone +* Combinatorics, probability, and other math +** TODO Which Appears Twice +** TODO Find in Ordered Set +** TODO In-Place Shuffle +** TODO Simulate 5-sided die +** TODO Simulate 7-sided die +** TODO Two Egg Problem diff --git a/users/wpcarro/scratch/facebook/top-scores.py b/users/wpcarro/scratch/facebook/top-scores.py new file mode 100644 index 000000000000..c8a10ae5f181 --- /dev/null +++ b/users/wpcarro/scratch/facebook/top-scores.py @@ -0,0 +1,20 @@ +import random +from collections import deque + +def sorted(xs): + result = [0] * 100 + for x in xs: + result[x - 1] += 1 + + answer = deque() + for i in range(len(result)): + x = result[i] + for _ in range(x): + answer.appendleft(i + 1) + + return list(answer) + +scores = [random.choice(range(70, 100)) for _ in range(20)] +print(scores) +result = sorted(scores) +print(result) diff --git a/users/wpcarro/scratch/facebook/topo-sort.py b/users/wpcarro/scratch/facebook/topo-sort.py new file mode 100644 index 000000000000..874005a01932 --- /dev/null +++ b/users/wpcarro/scratch/facebook/topo-sort.py @@ -0,0 +1,61 @@ +import random +from heapq import heappush, heappop +from collections import deque + +# A topological sort returns the vertices of a graph sorted in an ascending +# order by the number of incoming edges each vertex has. +# +# A few algorithms for solving this exist, and at the time of this writing, I +# know none. I'm going to focus on two: +# 1. Kahn's +# 2. DFS (TODO) + +def count_in_edges(graph): + result = {k: 0 for k in graph.keys()} + for xs in graph.values(): + for x in xs: + result[x] += 1 + return result + +# Kahn's algorithm for returning a topological sorting of the vertices in +# `graph`. +def kahns_sort(graph): + result = [] + q = deque() + in_edges = count_in_edges(graph) + for x in [k for k, v in in_edges.items() if v == 0]: + q.append(x) + while q: + x = q.popleft() + result.append(x) + for c in graph[x]: + in_edges[c] -= 1 + if in_edges[c] == 0: + q.append(c) + return result + +graphs = [ + { + 0: [], + 1: [], + 2: [3], + 3: [1], + 4: [0, 1], + 5: [0, 2], + }, + { + 'A': ['C', 'D'], + 'B': ['D', 'E'], + 'C': [], + 'D': ['F', 'G'], + 'E': [], + 'F': [], + 'G': ['I'], + 'H': ['I'], + 'I': [], + } +] + +print("--- Kahn's --- ") +for graph in graphs: + print(kahns_sort(graph)) diff --git a/users/wpcarro/scratch/facebook/traversals.py b/users/wpcarro/scratch/facebook/traversals.py new file mode 100644 index 000000000000..e2565a32313d --- /dev/null +++ b/users/wpcarro/scratch/facebook/traversals.py @@ -0,0 +1,100 @@ +from math import floor + +# Lists +def cycle_backwards(times, xs): + n = len(xs) + for i in range(n * times): + print(xs[n - 1 - i % n]) + +def cycle_forwards(times, xs): + n = len(xs) + for i in range(n * times): + print(xs[i % n]) + +def backwards(xs): + n = len(xs) + for i in range(n): + print(xs[n - 1 - i]) + +def forwards(xs): + for i in range(len(xs)): + print(xs[i]) + +xs = [2, 5, 6, 9, 12] + +print("Forwards") +forwards(xs) +print("Backwards") +backwards(xs) +print("Cycle forwards") +cycle_forwards(2, xs) +print("Cycle backwards") +cycle_backwards(2, xs) + +# Tables +def tblr(table): + for row in range(len(table)): + for col in range(len(table[row])): + print(table[row][col]) + +def tbrl(table): + for row in range(len(table)): + n = len(table[row]) + for col in range(n): + print(table[row][n - 1 - col]) + +def btlr(table): + n = len(table) + for row in range(n): + for col in range(len(table[row])): + print(table[n - 1 - row][col]) + +def btrl(table): + rows = len(table) + for row in range(rows): + cols = len(table[row]) + for col in range(cols): + print(table[rows - 1 - row][cols - 1 - col]) + +def special(table): + rows = len(table) + cols = len(table[0]) + for col in range(cols): + for row in range(rows): + print(table[row][col]) + +def double_bonus(table): + rows = len(table) + cols = len(table[0]) + for i in range(rows): + row = i + for col in range(cols): + print(table[row][col % cols]) + row = (row + 1) % rows + +def free(table): + rows = len(table) + cols = len(table[0]) + d = rows * cols + for i in range(d): + row = floor((i % d) / cols) + col = i % cols + print(table[row][col]) + +table = [[1,2,3,4], + [5,6,7,8]] + +print("Top->Bottom, Left->Right") +tblr(table) +print("Top->Bottom, Right->Left") +tbrl(table) +print("Bottom->Top, Left->Right") +btlr(table) +print("Bottom->Top, Right->Left") +btrl(table) +print("Special") +special(table) +print("2x Bonus") +double_bonus(table) +print("Free") +free(table) diff --git a/users/wpcarro/scratch/facebook/utils.py b/users/wpcarro/scratch/facebook/utils.py new file mode 100644 index 000000000000..9a3e8a045e88 --- /dev/null +++ b/users/wpcarro/scratch/facebook/utils.py @@ -0,0 +1,19 @@ +def init_table(rows=0, cols=0, default=None): + table = [] + for row in range(rows): + x = [] + for col in range(cols): + x.append(default) + table.append(x) + return table + +def get(table, row, col, default=0): + if row < 0 or col < 0: + return default + return table[row][col] + +def print_table(table): + result = [] + for row in range(len(table)): + result.append(' '.join([str(cell) for cell in table[row]])) + print('\n'.join(result)) diff --git a/users/wpcarro/scratch/facebook/word-cloud.py b/users/wpcarro/scratch/facebook/word-cloud.py new file mode 100644 index 000000000000..88422e3631db --- /dev/null +++ b/users/wpcarro/scratch/facebook/word-cloud.py @@ -0,0 +1,32 @@ +def normalize(x): + noise = ".,;-" + for y in noise: + if x.endswith(y): + return normalize(x[0:-1]) + if x.startswith(y): + return normalize(x[1:]) + return x.lower() + +def word_cloud(xs): + result = dict() + + for x in xs.split(' '): + k = normalize(x) + if k in result: + result[k] += 1 + else: + result[k] = 1 + + return result + +result = word_cloud("This is just the beginning. The UK will lockdown again.") +assert result.get('this') == 1 +assert result.get('is') == 1 +assert result.get('just') == 1 +assert result.get('the') == 2 +assert result.get('beginning') == 1 +assert result.get('uk') == 1 +assert result.get('will') == 1 +assert result.get('lockdown') == 1 +assert result.get('again') == 1 +print("Success!") diff --git a/users/wpcarro/scratch/groceries/.envrc b/users/wpcarro/scratch/groceries/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/scratch/groceries/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/scratch/groceries/export.hs b/users/wpcarro/scratch/groceries/export.hs new file mode 100644 index 000000000000..ed43c9a3e887 --- /dev/null +++ b/users/wpcarro/scratch/groceries/export.hs @@ -0,0 +1,22 @@ +module Main where + +import qualified Data.List as L + +(|>) :: a -> (a -> b) -> b +x |> f = f x + +-- | Ignore items with zero quantity (i.e. "0x") and comments (i.e. "#") +isUndesirableOutput :: String -> Bool +isUndesirableOutput x = + (L.isPrefixOf "- 0x" x) || (L.isPrefixOf "#" x) + +-- | Run this to export the grocery list. +main :: IO () +main = do + content <- readFile "./list.org" + content + |> lines + |> filter (not . isUndesirableOutput) + |> unlines + |> putStrLn + pure () diff --git a/users/wpcarro/scratch/groceries/list.org b/users/wpcarro/scratch/groceries/list.org new file mode 100644 index 000000000000..a823b2a8ebc3 --- /dev/null +++ b/users/wpcarro/scratch/groceries/list.org @@ -0,0 +1,112 @@ +# The sections are sorted such that the first section is likely the first area +# in the grocery store you'll encounter. +# +# This version is written for Tesco Metro in London Bridge. +* Beer +- 0x beer (6x) +* Bread +- 0x GF bread +- 0x flour +- 0x GF flour +* Produce +- 0x brocoli +- 0x green beans +- 0x green asparagus +- 2x spinach greens +- 0x romaine lettuce head +- 0x tomatoes +- 0x zucchini +- 0x lemons +- 1x limes +- 0x large carrot +- 2x garlic +- 1x green onions +- 0x onions +- 0x avocado +- 0x basil plant +- 0x jalapeno +- 0x red pepper +- 0x green pepper +- 0x cherry tomatoes +- 0x potato +- 0x bag dry black beans +- 1x Scotch Bonnet pepper +* Spices +- 0x onion powder +- 0x garlic powder +- 0x chicken bouillon +- 0x oregano +- 0x red pepper flakes +- 0x basil plant +- 0x cilantro plant +* Meat +- 0x sausages +- 0x steak +- 0x chicken breasts +- 0x chicken legs +- 0x lamb +- 0x ground beef +* Frozen +- 0x Salmon +- 0x white fish +- 0x shrimp +- 0x bag green beans +- 1x bag peas +- 0x bag corn +* Dairy +- 1x unsalted butter +- 0x coconut milk +- 2x egg cartons (12x each) +- 2x sour cream +- 0x cheddar cheese +- 0x parmesan +- 0x gouda +- 0x random cheese +* Pasta +- 0x box of quinoa +- 0x box of rice +- 1x GF pasta +- 0x tortellini / ravioli +- 0x tomato sauce +- 0x tomato paste +- 0x can diced tomatoes +- 0x pesto +* Oil +- 0x olive oil +- 0x sesame oil +- 0x avocado oil +- 0x coconut oil +- 0x white wine vinegar +* Condiments +- 0x red Tabasco +- 0x green Tabasco +- 0x habanero Tabasco +- 0x BBQ sauce +- 0x french mustard +- 0x ketchup +- 0x oyster sauce +- 0x soy sauce +- 0x Srirachi sauce +* Nuts +- 0x almonds +- 0x walnuts +- 0x peanuts +- 0x cashews +- 0x Brazil nuts +- 0x mixed nuts +- 0x peanuts +- 2x peanut butter +* Sugar +- 0x Lindt chocolate +* Asian +- 0x red curry +- 0x green curry +- 0x coconut cream +* Wine +- 0x red wine +- 0x white wine +* Miscellaneous +- 0x coffee beans +- 0x tea +- 0x AA batteries +- 0x rubbing alcohol diff --git a/users/wpcarro/scratch/groceries/shell.nix b/users/wpcarro/scratch/groceries/shell.nix new file mode 100644 index 000000000000..0c6a298bf2b0 --- /dev/null +++ b/users/wpcarro/scratch/groceries/shell.nix @@ -0,0 +1,5 @@ +{ depot, ... }: + +depot.users.wpcarro.buildHaskell.shell { + deps = hpkgs: [ ]; +} diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/.envrc b/users/wpcarro/scratch/haskell-programming-from-first-principles/.envrc new file mode 100644 index 000000000000..a4a62da526d3 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/.envrc @@ -0,0 +1,2 @@ +source_up +use_nix diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/.ghci b/users/wpcarro/scratch/haskell-programming-from-first-principles/.ghci new file mode 100644 index 000000000000..12aab7f08e05 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/.ghci @@ -0,0 +1 @@ +:set prompt "> " diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/applicative.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/applicative.hs new file mode 100644 index 000000000000..8259606da374 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/applicative.hs @@ -0,0 +1,213 @@ +module ApplicativeScratch where + +import Data.Function ((&)) + +import Control.Applicative (liftA3) +import qualified Data.List as List +import qualified GHC.Base as Base + +-------------------------------------------------------------------------------- + +-- xs :: [(Integer, Integer)] +-- xs = zip [1..3] [4..6] + +-- added :: Maybe Integer +-- added = +-- (+3) <$> (lookup 3 xs) + +-------------------------------------------------------------------------------- + +-- y :: Maybe Integer +-- y = lookup 3 xs + +-- z :: Maybe Integer +-- z = lookup 2 xs + +-- tupled :: Maybe (Integer, Integer) +-- tupled = Base.liftA2 (,) y z + +-------------------------------------------------------------------------------- + +-- x :: Maybe Int +-- x = List.elemIndex 3 [1..5] + +-- y :: Maybe Int +-- y = List.elemIndex 4 [1..5] + +-- maxed :: Maybe Int +-- maxed = Base.liftA2 max x y + +-------------------------------------------------------------------------------- + +xs = [1..3] +ys = [4..6] + +x :: Maybe Integer +x = lookup 3 $ zip xs ys + +y :: Maybe Integer +y = lookup 2 $ zip xs ys + +summed :: Maybe Integer +summed = sum <$> Base.liftA2 (,) x y + +-------------------------------------------------------------------------------- + +newtype Identity a = Identity a deriving (Eq, Show) + +instance Functor Identity where + fmap f (Identity x) = Identity (f x) + +instance Applicative Identity where + pure = Identity + (Identity f) <*> (Identity x) = Identity (f x) + +-------------------------------------------------------------------------------- + +newtype Constant a b = + Constant { getConstant :: a } + deriving (Eq, Ord, Show) + +instance Functor (Constant a) where + fmap _ (Constant x) = Constant x + +instance Monoid a => Applicative (Constant a) where + pure _ = Constant mempty + (Constant x) <*> (Constant y) = Constant (x <> y) + +-------------------------------------------------------------------------------- + +one = const <$> Just "Hello" <*> Just "World" + +two :: Maybe (Integer, Integer, String, [Integer]) +two = (,,,) <$> (Just 90) + <*> (Just 10) + <*> (Just "Tierness") + <*> (Just [1..3]) + +-------------------------------------------------------------------------------- + +data List a = Nil | Cons a (List a) deriving (Eq, Show) + +instance Semigroup (List a) where + Nil <> xs = xs + xs <> Nil = xs + (Cons x xs) <> ys = Cons x (xs <> ys) + +instance Functor List where + fmap f Nil = Nil + fmap f (Cons x xs) = Cons (f x) (fmap f xs) + +instance Applicative List where + pure x = Cons x Nil + Nil <*> _ = Nil + _ <*> Nil = Nil + (Cons f fs) <*> xs = + (f <$> xs) <> (fs <*> xs) + +toList :: List a -> [a] +toList Nil = [] +toList (Cons x xs) = x : toList xs + +fromList :: [a] -> List a +fromList [] = Nil +fromList (x:xs) = Cons x (fromList xs) + +-------------------------------------------------------------------------------- + +newtype ZipList' a = + ZipList' [a] + deriving (Eq, Show) + +-- instance Eq a => EqProp (ZipList' a) where +-- (ZipList' lhs) =-= (ZipList' rhs) = +-- (take 1000 lhs) `eq` (take 1000 rhs) + +instance Functor ZipList' where + fmap f (ZipList' xs) = ZipList' $ fmap f xs + +instance Applicative ZipList' where + pure x = ZipList' (repeat x) + (ZipList' fs) <*> (ZipList' xs) = + ZipList' $ zipWith ($) fs xs + +-------------------------------------------------------------------------------- + +data Validation e a + = Failure e + | Success a + deriving (Eq, Show) + +instance Functor (Validation e) where + fmap f (Failure x) = Failure x + fmap f (Success x) = Success (f x) + +instance Monoid e => Applicative (Validation e) where + pure = undefined + (Success f) <*> (Success x) = Success (f x) + _ <*> (Failure x) = Failure x + (Failure x) <*> _ = Failure x + +data Error + = DivideByZero + | StackOverflow + deriving (Eq, Show) + +-------------------------------------------------------------------------------- + +stops :: String +stops = "pbtdkg" + +vowels :: String +vowels = "aeiou" + +combos :: [a] -> [b] -> [c] -> [(a, b, c)] +combos xs ys zs = + liftA3 (,,) xs ys zs + +-------------------------------------------------------------------------------- + +data Pair a = Pair a a deriving Show + +instance Functor Pair where + fmap f (Pair x y) = Pair (f x) (f y) + +instance Applicative Pair where + pure x = Pair x x + (Pair f g) <*> (Pair x y) = Pair (f x) (g x) + +p :: Pair Integer +p = Pair 1 2 + +-------------------------------------------------------------------------------- + +data Two a b = Two a b + +instance Functor (Two a) where + fmap f (Two x y) = Two x (f y) + +instance Monoid a => Applicative (Two a) where + pure x = Two mempty x + _ <*> _ = undefined + +-------------------------------------------------------------------------------- + +data Three a b c = Three a b c + +instance Functor (Three a b) where + fmap f (Three x y z) = Three x y (f z) + +instance (Monoid a, Monoid b) => Applicative (Three a b) where + pure x = Three mempty mempty x + (Three a b f) <*> (Three x y z) = Three (a <> x) (b <> y) (f z) + +-------------------------------------------------------------------------------- + +data Three' a b = Three' a b b + +instance Functor (Three' a) where + fmap f (Three' x y z) = Three' x (f y) (f z) + +instance Monoid a => Applicative (Three' a) where + pure x = Three' mempty x x + (Three' a f g) <*> (Three' x y z) = Three' (a <> x) (f y) (g z) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/basic-libraries.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/basic-libraries.hs new file mode 100644 index 000000000000..bb1f89987e29 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/basic-libraries.hs @@ -0,0 +1,60 @@ +module BasicLibrariesScratch where + +import Data.Function ((&)) + +-------------------------------------------------------------------------------- +newtype DList a = DL { unDL :: [a] -> [a] } + +instance (Show a) => Show (DList a) where + show (DL x) = "DL " ++ show (x []) + +-- | Create an empty difference list. +emptyDList :: DList a +emptyDList = DL $ \xs -> xs +{-# INLINE emptyDList #-} + +-- | Create a difference list with `x` as the only member. +singleton :: a -> DList a +singleton x = DL $ \xs -> x : xs +{-# INLINE singleton #-} + +-- | Convert the DList into a list. +toList :: DList a -> [a] +toList (DL unDL) = unDL mempty +{-# INLINE toList #-} + +-- | Add an element to the end of a DList. +infixr `snoc` +snoc :: a -> DList a -> DList a +snoc x (DL xs) = DL $ \ys -> xs (x : ys) +{-# INLINE snoc #-} + +-- | Add an element to the beginning of a DList. +infixr `cons` +cons :: a -> DList a -> DList a +cons x (DL xs) = DL $ \ys -> x : xs ys +{-# INLINE cons #-} + +-- | Combine two DLists together. +append :: DList a -> DList a -> DList a +append (DL xs) (DL ys) = DL $ \zs -> zs & ys & xs +{-# INLINE append #-} + +-------------------------------------------------------------------------------- +data Queue a = + Queue { one :: [a] + , two :: [a] + } deriving (Show, Eq) + +emptyQueue :: Queue a +emptyQueue = Queue mempty mempty + +enqueue :: a -> Queue a -> Queue a +enqueue x (Queue en de) = Queue (x:en) de + +dequeue :: Queue a -> Maybe (a, Queue a) +dequeue (Queue [] []) = Nothing +dequeue (Queue en []) = + let (d:de) = reverse en + in Just (d, Queue de []) +dequeue (Queue en (d:de)) = Just (d, Queue en de) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/composing-types.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/composing-types.hs new file mode 100644 index 000000000000..378cfb7ceae6 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/composing-types.hs @@ -0,0 +1,75 @@ +module ComposingTypesScratch where + +import Data.Function ((&)) +import Data.Bifunctor + +import qualified Data.Foldable as F + +-------------------------------------------------------------------------------- + +newtype Identity a = + Identity { getIdentity :: a } + deriving (Eq, Show) + +newtype Compose f g a = + Compose { getCompose :: f (g a) } + deriving (Eq, Show) + +-------------------------------------------------------------------------------- + +instance (Functor f, Functor g) => Functor (Compose f g) where + fmap f (Compose getCompose) = Compose $ (fmap . fmap) f getCompose + +instance (Applicative f, Applicative g) => Applicative (Compose f g) where + pure x = x & pure & pure & Compose + fgf <*> fga = undefined + +-------------------------------------------------------------------------------- + +instance (Foldable f, Foldable g) => Foldable (Compose f g) where + foldMap toMonoid x = undefined + +instance (Traversable f, Traversable g) => Traversable (Compose f g) where + traverse = undefined + +-------------------------------------------------------------------------------- + +data Deux a b = Deux a b deriving (Show, Eq) + +instance Bifunctor Deux where + bimap f g (Deux x y) = Deux (f x) (g y) + +data Const a b = Const a deriving (Show, Eq) + +instance Bifunctor Const where + bimap f _ (Const x) = Const (f x) + +data Drei a b c = Drei a b c deriving (Show, Eq) + +instance Bifunctor (Drei a) where + bimap f g (Drei x y z) = Drei x (f y) (g z) + +data SuperDrei a b c = SuperDrei a b deriving (Show, Eq) + +instance Bifunctor (SuperDrei a) where + bimap f g (SuperDrei x y) = SuperDrei x (f y) + +data SemiDrei a b c = SemiDrei a deriving (Show, Eq) + +instance Bifunctor (SemiDrei a) where + bimap _ _ (SemiDrei x) = SemiDrei x + +data Quadriceps a b c d = Quadzzz a b c d + +instance Bifunctor (Quadriceps a b) where + bimap f g (Quadzzz w x y z) = Quadzzz w x (f y) (g z) + +-- | Analogue for Either +data LeftRight a b + = Failure a + | Success b + deriving (Show, Eq) + +instance Bifunctor LeftRight where + bimap f _ (Failure x) = Failure (f x) + bimap _ g (Success y) = Success (g y) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/foldable.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/foldable.hs new file mode 100644 index 000000000000..5b59d9e9ba50 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/foldable.hs @@ -0,0 +1,107 @@ +module FoldableScratch where + +import Data.Function ((&)) + +-------------------------------------------------------------------------------- + +sum :: (Foldable t, Num a) => t a -> a +sum xs = + foldr (+) 0 xs + +product :: (Foldable t, Num a) => t a -> a +product xs = + foldr (*) 1 xs + +elem :: (Foldable t, Eq a) => a -> t a -> Bool +elem y xs = + foldr (\x acc -> if acc then acc else y == x) False xs + +minimum :: (Foldable t, Ord a) => t a -> Maybe a +minimum xs = + foldr (\x acc -> + case acc of + Nothing -> Just x + Just curr -> Just (min curr x)) Nothing xs + +maximum :: (Foldable t, Ord a) => t a -> Maybe a +maximum xs = + foldr (\x acc -> + case acc of + Nothing -> Nothing + Just curr -> Just (max curr x)) Nothing xs + +-- TODO: How could I use QuickCheck to see if Prelude.null and this null return +-- the same results for the same inputs? +null :: (Foldable t) => t a -> Bool +null xs = + foldr (\_ _ -> False) True xs + +length :: (Foldable t) => t a -> Int +length xs = + foldr (\_ acc -> acc + 1) 0 xs + +toList :: (Foldable t) => t a -> [a] +toList xs = + reverse $ foldr (\x acc -> x : acc) [] xs + +fold :: (Foldable t, Monoid m) => t m -> m +fold xs = + foldr mappend mempty xs + +foldMap :: (Foldable t, Monoid m) => (a -> m) -> t a -> m +foldMap f xs = + foldr (\x acc -> mappend (f x) acc) mempty xs + +-------------------------------------------------------------------------------- + +data List a = Nil | Cons a (List a) deriving (Eq, Show) + +instance Foldable List where + foldr f acc (Cons x rest) = foldr f (f x acc) rest + foldr f acc Nil = acc + +fromList :: [a] -> List a +fromList [] = Nil +fromList (x:rest) = Cons x (fromList rest) + +-------------------------------------------------------------------------------- + +data Constant a b = Constant b deriving (Eq, Show) + +-- TODO: Is this correct? +instance Foldable (Constant a) where + foldr f acc (Constant x) = f x acc + +-------------------------------------------------------------------------------- + +data Two a b = Two a b deriving (Eq, Show) + +instance Foldable (Two a) where + foldr f acc (Two x y) = f y acc + +-------------------------------------------------------------------------------- + +data Three a b c = Three a b c deriving (Eq, Show) + +instance Foldable (Three a b) where + foldr f acc (Three x y z) = f z acc + +-------------------------------------------------------------------------------- + +data Three' a b = Three' a b b deriving (Eq, Show) + +instance Foldable (Three' a) where + foldr f acc (Three' x y z) = acc & f z & f y + +-------------------------------------------------------------------------------- + +data Four' a b = Four' a b b b deriving (Eq, Show) + +instance Foldable (Four' a) where + foldr f acc (Four' w x y z) = acc & f z & f y & f x + +-------------------------------------------------------------------------------- + +filterF :: (Applicative f, Foldable t, Monoid (f a)) => (a -> Bool) -> t a -> f a +filterF pred xs = + foldr (\x acc -> if pred x then pure x `mappend` acc else acc) mempty xs diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/io.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/io.hs new file mode 100644 index 000000000000..1de8937fced4 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/io.hs @@ -0,0 +1,35 @@ +module IOScratch where + +import qualified System.Environment as SE +import qualified System.IO as SIO +-------------------------------------------------------------------------------- + +docs :: String +docs = "Pass -e to encrypt and -d to decrypt." + +encryptStdin :: IO () +encryptStdin = do + char <- SIO.hGetChar SIO.stdin + -- encrypt char + SIO.hPutStr SIO.stdout [char] + +decryptStdin :: IO () +decryptStdin = do + char <- SIO.hGetChar SIO.stdin + -- decrypt char + SIO.hPutStr SIO.stdout [char] + +main :: IO () +main = do + args <- SE.getArgs + case args of + [] -> + putStrLn $ "You did not pass enough arguments. " ++ docs + ["-e"] -> + encryptStdin + ["-d"] -> + decryptStdin + [x] -> + putStrLn $ "You passed an unsupported option: " ++ x ++ ". " ++ docs + _ -> + putStrLn $ "You passed too many arguments. " ++ docs diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/monad-transformers.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/monad-transformers.hs new file mode 100644 index 000000000000..3a780fc16c82 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/monad-transformers.hs @@ -0,0 +1,183 @@ +module MonadTransformersScratch where + +import Control.Monad +import qualified Control.Monad.Trans.Maybe as M +import qualified Control.Monad.Trans.Reader as R +import qualified Control.Monad.Trans.State as S +import Data.Function ((&)) +-------------------------------------------------------------------------------- + +newtype MaybeT m a = + MaybeT { runMaybeT :: m (Maybe a) } + +instance (Functor f) => Functor (MaybeT f) where + fmap f (MaybeT run) = + MaybeT $ (fmap . fmap) f run + +instance (Applicative m) => Applicative (MaybeT m) where + pure x = x & pure & pure & MaybeT + _ <*> _ = undefined + +instance (Monad m) => Monad (MaybeT m) where + return = pure + (MaybeT ma) >>= f = MaybeT $ do + maybeX <- ma + case maybeX of + Nothing -> pure Nothing + Just x -> x & f & runMaybeT + +-------------------------------------------------------------------------------- + +newtype EitherT e m a = + EitherT { runEitherT :: m (Either e a) } + +instance (Functor m) => Functor (EitherT e m) where + fmap f (EitherT mEither) = + EitherT $ (fmap . fmap) f mEither + +instance (Applicative m) => Applicative (EitherT e m) where + pure x = EitherT $ (pure . pure) x + EitherT mEitherF <*> EitherT mEitherX = + EitherT $ (fmap (<*>) mEitherF) <*> mEitherX + +instance (Monad m) => Monad (EitherT e m) where + return = pure + EitherT mEitherX >>= f = EitherT $ do + eitherX <- mEitherX + case eitherX of + Left x -> pure $ Left x + Right x -> runEitherT $ f x + +swapEither :: Either l r -> Either r l +swapEither (Left x) = Right x +swapEither (Right x) = Left x + +swapEitherT :: (Functor m) => EitherT e m a -> EitherT a m e +swapEitherT (EitherT mEitherX) = + EitherT $ fmap swapEither mEitherX + +eitherT :: Monad m => (a -> m c) -> (b -> m c) -> EitherT a m b -> m c +eitherT aToMC bToMC (EitherT mEitherX) = do + eitherX <- mEitherX + case eitherX of + Left x -> aToMC x + Right x -> bToMC x + +-------------------------------------------------------------------------------- + +newtype ReaderT r m a = ReaderT { runReaderT :: r -> m a } + +instance (Functor m) => Functor (ReaderT r m) where + fmap f (ReaderT rma) = + ReaderT $ (fmap . fmap) f rma + +instance (Applicative m) => Applicative (ReaderT r m) where + pure x = x & pure & pure & ReaderT + ReaderT f <*> ReaderT x = ReaderT $ fmap (<*>) f <*> x + +-- instance (Monad m) => Monad (ReaderT r m) where +-- return = pure +-- ReaderT rma >>= f = +-- ReaderT $ \r -> do +-- a <- rma r +-- runReaderT (f a) r +-- -------------------------------------------------------------------------------- + +rDec :: Num a => R.Reader a a +rDec = R.ReaderT $ \x -> pure $ x + 1 + +rShow :: Show a => R.Reader a String +rShow = R.ReaderT $ \x -> pure $ show x + +rPrintAndInc :: (Num a, Show a) => R.ReaderT a IO a +rPrintAndInc = R.ReaderT $ \x -> + putStrLn ("Hi: " ++ show x) >> pure (x + 1) + +sPrintIncAccum :: (Num a, Show a) => S.StateT a IO String +sPrintIncAccum = S.StateT $ \x -> do + putStrLn ("Hi: " ++ show x) + pure (show x, x + 1) + +-------------------------------------------------------------------------------- + +isValid :: String -> Bool +isValid v = '!' `elem` v + +maybeExcite :: M.MaybeT IO String +maybeExcite = M.MaybeT $ do + x <- getLine + putStrLn "" + case isValid x of + False -> pure Nothing + True -> pure $ Just x + +doExcite :: IO () +doExcite = do + putStr "Say something *exciting*: " + excite <- M.runMaybeT maybeExcite + case excite of + Nothing -> putStrLn "Gonna need some more excitement..." + Just x -> putStrLn "Now THAT'S exciting...nice!" + +-------------------------------------------------------------------------------- + +data Participant + = Man + | Machine + deriving (Show, Eq) + +newtype Hand = Hand (Integer, Integer) deriving (Show, Eq) + +newtype Score = Score (Integer, Integer) deriving (Show, Eq) + +getLineLn :: String -> IO String +getLineLn prompt = do + putStr prompt + x <- getLine + putStrLn "" + pure x + +promptGuess :: IO Hand +promptGuess = do + fingers <- getLineLn "How many fingers (0-5): " + guess <- getLineLn "Guess: " + pure $ Hand (read guess, read fingers) + +aiGuess :: IO Hand +aiGuess = pure $ Hand (2, 3) + +whoWon :: Hand -> Hand -> Maybe Participant +whoWon (Hand (guessA, fingersA)) (Hand (guessB, fingersB)) + | guessA == guessB && guessA == (fingersA + fingersB) = Nothing + | guessA == (fingersA + fingersB) = Just Man + | guessB == (fingersA + fingersB) = Just Machine + | otherwise = Nothing + +initScore :: Score +initScore = Score (0, 0) + +printScore :: Score -> IO () +printScore (Score (man, machine)) = + putStrLn $ "Man: " ++ show man ++ " Machine: " ++ show machine + +startMorra :: S.StateT Score IO () +startMorra = S.StateT $ \(Score (man, machine)) -> do + Hand (guessA, fingersA) <- promptGuess + Hand (guessB, fingersB) <- aiGuess + putStrLn $ "P: " ++ show fingersA ++ "," ++ show guessA + putStrLn $ "C: " ++ show fingersB ++ "," ++ show guessB + case whoWon (Hand (guessA, fingersA)) (Hand (guessB, fingersB)) of + Nothing -> do + putStrLn "Nobody won..." + printScore (Score (man, machine)) + pure ((), Score (man, machine)) + Just Man -> do + putStrLn "Man won!" + printScore (Score (man + 1, machine)) + pure ((), Score (man + 1, machine)) + Just Machine -> do + putStrLn "Oh no... Machine won..." + printScore (Score (man, machine + 1)) + pure ((), Score (man, machine + 1)) + +playMorra = S.runStateT (forever startMorra) initScore diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/monad.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/monad.hs new file mode 100644 index 000000000000..2f80b457b125 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/monad.hs @@ -0,0 +1,178 @@ +module MonadScratch where + +import Data.Function ((&)) +import Test.QuickCheck +import Test.QuickCheck.Checkers +import Control.Applicative (liftA2) +import qualified Control.Monad as Monad + +-------------------------------------------------------------------------------- + +bind :: Monad m => (a -> m b) -> m a -> m b +bind f x = Monad.join $ fmap f x + +-------------------------------------------------------------------------------- + +fTrigger :: Functor f => f (Int, String, [Int]) +fTrigger = undefined + +aTrigger :: Applicative a => a (Int, String, [Int]) +aTrigger = undefined + +mTrigger :: Monad m => m (Int, String, [Int]) +mTrigger = undefined + +-------------------------------------------------------------------------------- + +data Sum a b + = Fst a + | Snd b + deriving (Eq, Show) + +instance (Eq a, Eq b) => EqProp (Sum a b) where + (=-=) = eq + +instance (Arbitrary a, Arbitrary b) => Arbitrary (Sum a b) where + arbitrary = frequency [ (1, Fst <$> arbitrary) + , (1, Snd <$> arbitrary) + ] + +instance Functor (Sum a) where + fmap f (Fst x) = Fst x + fmap f (Snd x) = Snd (f x) + +instance Applicative (Sum a) where + pure x = Snd x + (Snd f) <*> (Snd x) = Snd (f x) + (Snd f) <*> (Fst x) = Fst x + (Fst x) <*> _ = Fst x + +instance Monad (Sum a) where + (Fst x) >>= _ = Fst x + (Snd x) >>= f = f x + +-------------------------------------------------------------------------------- + +data Nope a = NopeDotJpg deriving (Eq, Show) + +instance Arbitrary (Nope a) where + arbitrary = pure NopeDotJpg + +instance EqProp (Nope a) where + (=-=) = eq + +instance Functor Nope where + fmap f _ = NopeDotJpg + +instance Applicative Nope where + pure _ = NopeDotJpg + _ <*> _ = NopeDotJpg + +instance Monad Nope where + NopeDotJpg >>= f = NopeDotJpg + +-------------------------------------------------------------------------------- + +data BahEither b a + = PLeft a + | PRight b + deriving (Eq, Show) + +instance (Arbitrary b, Arbitrary a) => Arbitrary (BahEither b a) where + arbitrary = frequency [ (1, PLeft <$> arbitrary) + , (1, PRight <$> arbitrary) + ] + +instance (Eq a, Eq b) => EqProp (BahEither a b) where + (=-=) = eq + +instance Functor (BahEither b) where + fmap f (PLeft x) = PLeft (f x) + fmap _ (PRight x) = PRight x + +instance Applicative (BahEither b) where + pure = PLeft + (PRight x) <*> _ = PRight x + (PLeft f) <*> (PLeft x) = PLeft (f x) + _ <*> (PRight x) = PRight x + +instance Monad (BahEither b) where + (PRight x) >>= _ = PRight x + (PLeft x) >>= f = f x + +-------------------------------------------------------------------------------- + +newtype Identity a = Identity a + deriving (Eq, Ord, Show) + +instance Functor Identity where + fmap f (Identity x) = Identity (f x) + +instance Applicative Identity where + pure = Identity + (Identity f) <*> (Identity x) = Identity (f x) + +instance Monad Identity where + (Identity x) >>= f = f x + +-------------------------------------------------------------------------------- + +data List a + = Nil + | Cons a (List a) + deriving (Eq, Show) + +instance Arbitrary a => Arbitrary (List a) where + arbitrary = frequency [ (1, pure Nil) + , (1, Cons <$> arbitrary <*> arbitrary) + ] + +instance Eq a => EqProp (List a) where + (=-=) = eq + +fromList :: [a] -> List a +fromList [] = Nil +fromList (x:xs) = Cons x (fromList xs) + +instance Semigroup (List a) where + Nil <> xs = xs + xs <> Nil = xs + (Cons x xs) <> ys = + Cons x (xs <> ys) + +instance Functor List where + fmap f Nil = Nil + fmap f (Cons x xs) = Cons (f x) (fmap f xs) + +instance Applicative List where + pure x = Cons x Nil + Nil <*> _ = Nil + _ <*> Nil = Nil + (Cons f fs) <*> xs = + (f <$> xs) <> (fs <*> xs) + +instance Monad List where + Nil >>= _ = Nil + (Cons x xs) >>= f = (f x) <> (xs >>= f) + +-------------------------------------------------------------------------------- + +j :: Monad m => m (m a) -> m a +j = Monad.join + +l1 :: Monad m => (a -> b) -> m a -> m b +l1 = Monad.liftM + +l2 :: Monad m => (a -> b -> c) -> m a -> m b -> m c +l2 = Monad.liftM2 + +a :: Monad m => m a -> m (a -> b) -> m b +a = flip (<*>) + +meh :: Monad m => [a] -> (a -> m b) -> m [b] +meh xs f = flipType $ f <$> xs + +flipType :: Monad m => [m a] -> m [a] +flipType [] = pure mempty +flipType (m:ms) = + m >>= (\x -> (x:) <$> flipType ms) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/non-strictness.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/non-strictness.hs new file mode 100644 index 000000000000..42608fb0c961 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/non-strictness.hs @@ -0,0 +1,6 @@ +module NonStrictnessScratch where + +x = undefined +y = "blah" +main = do + print $ snd (x, x `seq` y) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/reader.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/reader.hs new file mode 100644 index 000000000000..7cb7b4a1bbc1 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/reader.hs @@ -0,0 +1,149 @@ +module Reader where + +import Data.Char +import Data.Function ((&)) +import Data.Functor ((<&>)) +import qualified Control.Applicative as A +import qualified Data.Maybe as MB + +cap :: String -> String +cap xs = xs <&> toUpper + +rev :: String -> String +rev = reverse + +compose :: String -> String +compose xs = xs & rev . cap + +fmapped :: String -> String +fmapped xs = xs & rev <$> cap + +tupled :: String -> (String, String) +tupled xs = A.liftA2 (,) cap rev $ xs + +tupled' :: String -> (String, String) +tupled' = do + capResult <- cap + revResult <- rev + pure (revResult, capResult) + +-------------------------------------------------------------------------------- + +newtype Reader r a = Reader { runReader :: r -> a } + +ask :: Reader a a +ask = Reader id + +-------------------------------------------------------------------------------- + +newtype HumanName = HumanName String + deriving (Eq, Show) + +newtype DogName = DogName String + deriving (Eq, Show) + +newtype Address = Address String + deriving (Eq, Show) + +data Person + = Person + { humanName :: HumanName + , dogName :: DogName + , address :: Address + } deriving (Eq, Show) + +data Dog + = Dog + { dogsName :: DogName + , dogsAddress :: Address + } deriving (Eq, Show) + +pers :: Person +pers = + Person (HumanName "Big Bird") + (DogName "Barkley") + (Address "Sesame Street") + +chris :: Person +chris = + Person (HumanName "Chris Allen") + (DogName "Papu") + (Address "Austin") + +getDog :: Person -> Dog +getDog p = + Dog (dogName p) (address p) + +getDogR :: Person -> Dog +getDogR = + A.liftA2 Dog dogName address + +-------------------------------------------------------------------------------- + +myLiftA2 :: Applicative f => (a -> b -> c) -> f a -> f b -> f c +myLiftA2 f x y = + f <$> x <*> y + +asks :: (r -> a) -> Reader r a +asks f = Reader f + +-------------------------------------------------------------------------------- + +instance Functor (Reader a) where + fmap f (Reader ab) = Reader $ f . ab + +instance Applicative (Reader a) where + pure x = Reader $ \_ -> x + (Reader rab) <*> (Reader ra) = Reader $ do + ab <- rab + fmap ab ra + +-------------------------------------------------------------------------------- + +instance Monad (Reader r) where + return = pure + -- (>>=) :: Reader r a -> (a -> Reader r b) -> Reader r b + (Reader x) >>= f = undefined + +-------------------------------------------------------------------------------- + +x = [1..3] +y = [4..6] +z = [7..9] + +xs :: Maybe Integer +xs = zip x y & lookup 3 + +ys :: Maybe Integer +ys = zip y z & lookup 6 + +zs :: Maybe Integer +zs = zip x y & lookup 4 + +z' :: Integer -> Maybe Integer +z' n = zip x y & lookup n + +x1 :: Maybe (Integer, Integer) +x1 = A.liftA2 (,) xs ys + +x2 :: Maybe (Integer, Integer) +x2 = A.liftA2 (,) ys zs + +x3 :: Integer -> (Maybe Integer, Maybe Integer) +x3 n = (z' n, z' n) + +summed :: Num a => (a, a) -> a +summed (x, y) = x + y + +bolt :: Integer -> Bool +bolt x = x > 3 && x < 8 + +main :: IO () +main = do + print $ sequenceA [Just 3, Just 2, Just 1] + print $ sequenceA [x, y] + print $ sequenceA [xs, ys] + print $ summed <$> ((,) <$> xs <*> ys) + print $ bolt 7 + print $ bolt <$> z + print $ sequenceA [(>3), (<8) ,even] 7 diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/shell.nix b/users/wpcarro/scratch/haskell-programming-from-first-principles/shell.nix new file mode 100644 index 000000000000..49dbe746d364 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/shell.nix @@ -0,0 +1,8 @@ +{ depot, ... }: + +depot.users.wpcarro.buildHaskell.shell { + deps = hpkgs: with hpkgs; [ + quickcheck-simple + checkers + ]; +} diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/state.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/state.hs new file mode 100644 index 000000000000..f63e0ecdf11c --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/state.hs @@ -0,0 +1,93 @@ +module StateScratch where + +-------------------------------------------------------------------------------- +import System.Random +-- import Control.Monad.Trans.State +import Data.Function ((&)) + +import qualified Control.Applicative as Ap +import qualified Control.Monad as M +-------------------------------------------------------------------------------- + +data Die + = DieOne + | DieTwo + | DieThree + | DieFour + | DieFive + | DieSix + deriving (Eq, Show) + +intToDie :: Integer -> Maybe Die +intToDie 1 = Just DieOne +intToDie 2 = Just DieTwo +intToDie 3 = Just DieThree +intToDie 4 = Just DieFour +intToDie 5 = Just DieFive +intToDie 6 = Just DieSix +intToDie _ = Nothing + +rollDie :: Moi StdGen Die +rollDie = do + (n, s) <- randomR (1, 6) + case intToDie n of + Just d -> pure (d, s) + Nothing -> pure (DieOne, s) + +rollsToGetN :: Integer -> StdGen -> [Die] +rollsToGetN n g = go 0 [] g + where + go sum result gen + | sum >= n = result + | otherwise = + let (dice, nextGen) = randomR (1, 6) gen + in case intToDie dice of + Nothing -> go (sum + dice) result nextGen + Just d -> go (sum + dice) (d : result) nextGen + +-------------------------------------------------------------------------------- + +newtype Moi s a = Moi { runMoi :: s -> (a, s) } + +instance Functor (Moi s) where + fmap f (Moi run) = + Moi $ \s -> let (x, t) = run s + in (f x, t) + +instance Applicative (Moi s) where + pure x = Moi $ \s -> (x, s) + (Moi f) <*> (Moi run) = + Moi $ \s -> let (g, t) = f s + (x, u) = run t + in (g x, u) + +instance Monad (Moi s) where + (Moi run1) >>= f = + Moi $ \s -> let (x, t) = run1 s + (Moi run2) = f x + in run2 t + +-------------------------------------------------------------------------------- + +fizzBuzz :: Integer -> String +fizzBuzz n | n `mod` 15 == 0 = "FizzBuzz" + | n `mod` 5 == 0 = "Buzz" + | n `mod` 3 == 0 = "Fizz" + | otherwise = show n + +-------------------------------------------------------------------------------- + +get :: Moi s s +get = Moi $ \s -> (s, s) + +put :: s -> Moi s () +put x = Moi $ \s -> ((), x) + +exec :: Moi s a -> s -> s +exec (Moi run) x = x & run & snd + +eval :: Moi s a -> s -> a +eval (Moi run) x = x & run & fst + +modify :: (s -> s) -> Moi s () +modify f = Moi $ \s -> ((), f s) diff --git a/users/wpcarro/scratch/haskell-programming-from-first-principles/traversable.hs b/users/wpcarro/scratch/haskell-programming-from-first-principles/traversable.hs new file mode 100644 index 000000000000..5dc4ea411bc2 --- /dev/null +++ b/users/wpcarro/scratch/haskell-programming-from-first-principles/traversable.hs @@ -0,0 +1,131 @@ +module TraversableScratch where + +import qualified Data.Foldable as F + +import Test.QuickCheck + +newtype Identity a = Identity a + deriving (Eq, Ord, Show) + +instance Functor Identity where + fmap f (Identity x) = Identity (f x) + +instance Foldable Identity where + foldMap f (Identity x) = f x + +instance Traversable Identity where + traverse f (Identity x) = Identity <$> f x + +-------------------------------------------------------------------------------- + +data Optional a + = Nada + | Some a + deriving (Eq, Show) + +instance Functor Optional where + fmap f Nada = Nada + fmap f (Some x) = Some (f x) + +instance Foldable Optional where + foldMap f Nada = mempty + foldMap f (Some x) = f x + +instance Traversable Optional where + traverse f Nada = pure Nada + traverse f (Some x) = Some <$> f x + +-------------------------------------------------------------------------------- + +data List a = Nil | Cons a (List a) deriving (Eq, Show) + +instance Functor List where + fmap _ Nil = Nil + fmap f (Cons x xs) = Cons (f x) (fmap f xs) + +instance Foldable List where + foldMap f Nil = mempty + foldMap f (Cons x xs) = mappend (f x) (foldMap f xs) + +instance Traversable List where + sequenceA Nil = pure Nil + sequenceA (Cons x xs) = Cons <$> x <*> sequenceA xs + +-------------------------------------------------------------------------------- + +data Three a b c = Three a b c + deriving (Eq, Show) + +instance Functor (Three a b) where + fmap f (Three x y z) = Three x y (f z) + +instance Foldable (Three a b) where + foldMap f (Three _ _ z) = f z + +instance Traversable (Three a b) where + sequenceA (Three x y z) = (\z' -> Three x y z') <$> z + +-------------------------------------------------------------------------------- + +data Pair a b = Pair a b + deriving (Eq, Show) + +instance Functor (Pair a) where + fmap f (Pair x y) = Pair x (f y) + +instance Foldable (Pair a) where + foldMap f (Pair x y) = f y + +instance Traversable (Pair a) where + sequenceA (Pair x y) = (\y' -> Pair x y') <$> y + +-------------------------------------------------------------------------------- + +data Big a b = Big a b b + deriving (Eq, Show) + +instance Functor (Big a) where + fmap f (Big x y z) = Big x (f y) (f z) + +instance Foldable (Big a) where + foldMap f (Big x y z) = f y <> f z + +instance Traversable (Big a) where + sequenceA (Big x y z) = (\y' z' -> Big x y' z') <$> y <*> z + +-------------------------------------------------------------------------------- + +data Bigger a b = Bigger a b b b + deriving (Eq, Show) + +instance Functor (Bigger a) where + fmap f (Bigger w x y z) = Bigger w (f x) (f y) (f z) + +instance Foldable (Bigger a) where + foldMap f (Bigger w x y z) = f x <> f y <> f z + +instance Traversable (Bigger a) where + sequenceA (Bigger w x y z) = (\x' y' z' -> Bigger w x' y' z') <$> x <*> y <*> z + +-------------------------------------------------------------------------------- + +data Tree a + = Empty + | Leaf a + | Node (Tree a) a (Tree a) + deriving (Eq, Show) + +instance Functor Tree where + fmap f Empty = Empty + fmap f (Leaf x) = Leaf (f x) + fmap f (Node lhs x rhs) = Node (fmap f lhs) (f x) (fmap f rhs) + +instance Foldable Tree where + foldMap f Empty = mempty + foldMap f (Leaf x) = f x + foldMap f (Node lhs x rhs) = (foldMap f lhs) <> (f x) <> (foldMap f rhs) + +instance Traversable Tree where + sequenceA Empty = pure Empty + sequenceA (Leaf x) = Leaf <$> x + sequenceA (Node lhs x rhs) = Node <$> sequenceA lhs <*> x <*> sequenceA rhs diff --git a/users/wpcarro/scratch/picoctf/.skip-subtree b/users/wpcarro/scratch/picoctf/.skip-subtree new file mode 100644 index 000000000000..e69de29bb2d1 --- /dev/null +++ b/users/wpcarro/scratch/picoctf/.skip-subtree diff --git a/users/wpcarro/scratch/picoctf/README.md b/users/wpcarro/scratch/picoctf/README.md new file mode 100644 index 000000000000..03a49817f745 --- /dev/null +++ b/users/wpcarro/scratch/picoctf/README.md @@ -0,0 +1,3 @@ +# picoCTF + +My solutions for some of the questsions at https://play.picoctf.org/practice. diff --git a/users/wpcarro/scratch/picoctf/challenge_144.py b/users/wpcarro/scratch/picoctf/challenge_144.py new file mode 100644 index 000000000000..570a7fd5a77b --- /dev/null +++ b/users/wpcarro/scratch/picoctf/challenge_144.py @@ -0,0 +1,11 @@ +def rotate_alpha(x, n): + def rotate_char(c, n): + offset = 'A' if c.isupper() else 'a' + return chr((ord(c) - ord(offset) + n) % 26 + ord(offset)) + return "".join([rotate_char(c, n) if c.isalpha() else c for c in x]) + +xs = [ + "cvpbPGS{arkg_gvzr_V'yy_gel_2_ebhaqf_bs_ebg13_Ncualgvd}", +] +for x in xs: + print(rotate_alpha(x, 13)) diff --git a/users/wpcarro/scratch/picoctf/challenge_156.py b/users/wpcarro/scratch/picoctf/challenge_156.py new file mode 100644 index 000000000000..8c87a1ce7694 --- /dev/null +++ b/users/wpcarro/scratch/picoctf/challenge_156.py @@ -0,0 +1,13 @@ +bytestring = [ + 112, 105, 99, 111, 67, 84, 70, 123, 103, 48, 48, 100, 95, 107, 49, 116, + 116, 121, 33, 95, 110, 49, 99, 51, 95, 107, 49, 116, 116, 121, 33, 95, 57, + 98, 51, 98, 55, 51, 57, 50, 125, 10, +] + +def decode(xs): + result = [] + for x in xs: + result.append(chr(x)) + return "".join(result) + +print(decode(bytestring)) diff --git a/users/wpcarro/scratch/picoctf/challenge_166/ende.py b/users/wpcarro/scratch/picoctf/challenge_166/ende.py new file mode 100644 index 000000000000..08395f920909 --- /dev/null +++ b/users/wpcarro/scratch/picoctf/challenge_166/ende.py @@ -0,0 +1,60 @@ + +import sys +import base64 +from cryptography.fernet import Fernet + + + +usage_msg = "Usage: "+ sys.argv[0] +" (-e/-d) [file]" +help_msg = usage_msg + "\n" +\ + "Examples:\n" +\ + " To decrypt a file named 'pole.txt', do: " +\ + "'$ python "+ sys.argv[0] +" -d pole.txt'\n" + + + +if len(sys.argv) < 2 or len(sys.argv) > 4: + print(usage_msg) + sys.exit(1) + + + +if sys.argv[1] == "-e": + if len(sys.argv) < 4: + sim_sala_bim = input("Please enter the password:") + else: + sim_sala_bim = sys.argv[3] + + ssb_b64 = base64.b64encode(sim_sala_bim.encode()) + c = Fernet(ssb_b64) + + with open(sys.argv[2], "rb") as f: + data = f.read() + data_c = c.encrypt(data) + sys.stdout.write(data_c.decode()) + + +elif sys.argv[1] == "-d": + if len(sys.argv) < 4: + sim_sala_bim = input("Please enter the password:") + else: + sim_sala_bim = sys.argv[3] + + ssb_b64 = base64.b64encode(sim_sala_bim.encode()) + c = Fernet(ssb_b64) + + with open(sys.argv[2], "r") as f: + data = f.read() + data_c = c.decrypt(data.encode()) + sys.stdout.buffer.write(data_c) + + +elif sys.argv[1] == "-h" or sys.argv[1] == "--help": + print(help_msg) + sys.exit(1) + + +else: + print("Unrecognized first argument: "+ sys.argv[1]) + print("Please use '-e', '-d', or '-h'.") + diff --git a/users/wpcarro/scratch/picoctf/challenge_166/flag.txt.en b/users/wpcarro/scratch/picoctf/challenge_166/flag.txt.en new file mode 100644 index 000000000000..1c4d245811e8 --- /dev/null +++ b/users/wpcarro/scratch/picoctf/challenge_166/flag.txt.en @@ -0,0 +1 @@ +gAAAAABgUAIWsYfVayn4m1dKle5X91HrZW_MIRAW4ILPgf4gD6jalLF4PysYB5_YTpDwclcQPqw_0xTxanpJ_Urx5Vi6mTeBA_rWPA_WQLvVXXHp1mG3EpOgY8Na1_NIAfc9LceH_L2o \ No newline at end of file diff --git a/users/wpcarro/scratch/picoctf/challenge_166/pw.txt b/users/wpcarro/scratch/picoctf/challenge_166/pw.txt new file mode 100644 index 000000000000..a4c1c7ae6631 --- /dev/null +++ b/users/wpcarro/scratch/picoctf/challenge_166/pw.txt @@ -0,0 +1 @@ +67c6cc9667c6cc9667c6cc9667c6cc96 diff --git a/users/wpcarro/scratch/picoctf/challenge_166/shell.nix b/users/wpcarro/scratch/picoctf/challenge_166/shell.nix new file mode 100644 index 000000000000..85d3865a51bf --- /dev/null +++ b/users/wpcarro/scratch/picoctf/challenge_166/shell.nix @@ -0,0 +1,8 @@ +{ pkgs, ... }: + +let + python = pkgs.python3.withPackages (pypkgs: with pypkgs; [ + cryptography + ]); +in +python.env diff --git a/users/wpcarro/scratch/picoctf/challenge_170/README.md b/users/wpcarro/scratch/picoctf/challenge_170/README.md new file mode 100644 index 000000000000..2507208f5c1a --- /dev/null +++ b/users/wpcarro/scratch/picoctf/challenge_170/README.md @@ -0,0 +1,11 @@ +# challenge 170 + +The following should work on most Linux distros, but it didn't for me on NixOS: + +```shell +chmod u+x ./warm +./warm -h +``` + +So instead, just call `strings` on the exectuable to search for the help text, +which contains the flag. diff --git a/users/wpcarro/scratch/simple-select/README.md b/users/wpcarro/scratch/simple-select/README.md new file mode 100644 index 000000000000..69e5707302ab --- /dev/null +++ b/users/wpcarro/scratch/simple-select/README.md @@ -0,0 +1,71 @@ +# Simple Select + +- Simple Select is a less expressive but more ergonomic query language for + tabular data than SQL. +- `slx` is a command-line tool for querying CSVs using the Simple Select query + language. + +Simple Select queries look like this: `director:"Tarantino" OR director:"Scorsese"`. + +## Example + +Say we have the following data in a CSV: + +```csv +title,year,rating,director +"Spirited Away",2001,8.5,"Hayao Miyazaki" +Andhadhun,2018,8.1,"Sriram Raghavan" +Dangal,2016,8.3,"Sriram Raghavan" +"Avengers: Infinity War",2019,8.4,"Anthony Russo" +Alien,1979,8.4,"Ridley Scott" +... +``` + +We can invoke `slx` like so... + +``` +$ slx -f /tmp/movies.csv +``` + +...and then query using the REPL: + +``` +> director:/S.*m/ OR director:"Hayao" +Andhadhun 2018 8.1 1 Sriram Raghavan 0 1 +Dangal 2016 8.3 1 Sriram Raghavan 0 1 +Howls Moving Castle 2004 8.2 0 Hayao Miyazaki 1 1 +Judgment at Nuremberg 1961 8.1 0 Stanley Kramer 0 0 +Laputa: Castle in the Sky 1986 8.0 0 Hayao Miyazaki 1 1 +Nausicaa of the Valley of the Wind 1984 8.0 0 Hayao Miyazaki 1 1 +Network 1976 8.1 0 Sidney Lumet 0 0 +``` + +## Warning + +Simple Select is **not intended for production use**. I wrote this as a toy +project for my own consumption. There are quite a few bugs of which I'm aware +and quite a few other features that I'd like to support but haven't had time to +support just yet. + +Why publish it then? Maybe this project will inspire drive-by contributions or +other, better-implemented spin-offs. + +## Wish List + +Speaking of drive-by contributions, here are some things that I'd like to +support: + +- Implicit `AND` conjunctions (`director:/Tarantino/ year:"2000"` instead of + `director:/Tarantino/ AND year:"2000"`) +- Support for types like numbers, dates (`year:2000` instead of `year:"2000"`) +- `slx` should support CSV *and* (at the very least) sqlite3 file formats (open + to other formats as well) +- Regexes should be the default query primitive (`director:Tarantino` instead of + `director:/Tarantino/`) +- Improve parsing errors (including surfacing errors to the user) +- Support for reading from `STDIN` and issuing queries from the command-line +- Unit-testing +- Configurable delimiters for output data (right now it's just `\t`) +- (Maybe) rewrite in a faster, more-type-safe languages (e.g. Rust) + +I'm likely missing other FRs, bugs, so please file issues! diff --git a/users/wpcarro/scratch/simple-select/main.py b/users/wpcarro/scratch/simple-select/main.py new file mode 100644 index 000000000000..3ae6c5d60e08 --- /dev/null +++ b/users/wpcarro/scratch/simple-select/main.py @@ -0,0 +1,262 @@ +from argparse import ArgumentParser + +import csv +from parser import Parser +import sqlite3 +import string +from scanner import Scanner +import re +import readline + +################################################################################ +# Predicates +################################################################################ + +def is_alpha(c): + return c in string.ascii_letters + +def is_digit(c): + return c in "0123456789" + +def is_alphanumeric(c): + return is_alpha(c) or is_digit(c) + +def is_whitespace(c): + return c in " \r\t\n" + +################################################################################ +# Tokenizer +################################################################################ + +AND = ("CONJUNCTION", "AND") +OR = ("CONJUNCTION", "OR") +NOT = ("PUNCTUATION", "NOT") +COLON = ("PUNCTUATION", "COLON") +LPAREN = ("PUNCTUATION", "LPAREN") +RPAREN = ("PUNCTUATION", "RPAREN") + +def tokenize(x): + s = Scanner(x) + tokens = scan_tokens(s) + return tokens + +def scan_tokens(s): + result = [] + while not s.exhausted(): + if is_whitespace(s.peek()): + s.advance() + else: + result.append(scan_token(s)) + return result + +def scan_token(s): + punctuation = { + "-": NOT, + ":": COLON, + "(": LPAREN, + ")": RPAREN, + } + c = s.peek() + if c in punctuation: + s.advance() + return punctuation[c] + if c == "\"": + return tokenize_string(s) + if c == "/": + return tokenize_regex(s) + if is_alpha(c): + return tokenize_identifier(s) + +def tokenize_string(s): + s.advance() # ignore opening 2x-quote + current = "" + while s.peek() != "\"" and not s.exhausted(): + current += s.advance() + if s.exhausted(): + raise Exception("Unterminated string") + s.advance() # ignore closing 2x-quote + return ("STRING", current) + +def tokenize_regex(s): + s.advance() # ignore opening forward-slash + current = "" + while s.peek() != "/" and not s.exhausted(): + current += s.advance() + if s.exhausted(): + raise Exception("Unterminated regex") + s.advance() # ignore closing forward-slash + return ("REGEX", current) + +def tokenize_identifier(s): + conjunctions = { + "AND", + "OR", + } + current = s.advance() + while is_alphanumeric(s.peek()): + current += s.advance() + if current.upper() in conjunctions: + return ("CONJUNCTION", current.upper()) + else: + return ("IDENTIFIER", current) + +################################################################################ +# Parser +################################################################################ + +# EBNF +# Note: we order expression types by ascending levels of precedence. +# +# expression -> conjunction ; +# conjunction -> selection ( ( "AND" | "OR" )? selection )* ; +# selection -> "-"? IDENTIFIER ":" ( REGEX | STRING ) | grouping ; +# grouping -> REGEX | STRING | "(" expression ")" ; + +def parse(x): + tokens = tokenize(x) + p = Parser(tokens) + return expression(p) + +def expression(p): + return conjunction(p) + +def conjunction(p): + lhs = selection(p) + + # TODO(wpcarro): Support default AND conjuctions when they're undefined. + while not p.exhausted() and p.match({AND, OR}): + conj = p.peek(n=-1) + rhs = selection(p) + lhs = ("CONJUNCTION", conj[1], lhs, rhs) + + return lhs + +def selection(p): + negate = False + if p.peek() == NOT: + negate = True + p.advance() + + if p.peek()[0] != "IDENTIFIER": + return grouping(p) + + ident = p.expect(lambda x: x[0] == "IDENTIFIER") + colon = p.expect(lambda x: x[1] == "COLON") + value = p.expect(lambda x: x[0] in {"REGEX", "STRING"}) + return ("SELECTION", negate, ident[1], value) + +def grouping(p): + if p.peek()[0] == "REGEX": + return p.advance() + + if p.peek()[0] == "STRING": + return p.advance() + + if p.peek() == LPAREN: + p.advance() + expr = expression(p) + p.expect(lambda x: x == RPAREN) + return ("GROUPING", expr) + +################################################################################ +# Compiler +################################################################################ + +def compile(source, table, columns): + ast = parse(source) + return "SELECT * FROM {} WHERE {};".format(table, do_compile(ast, columns)) + +def do_compile(ast, columns): + if ast[0] == "REGEX": + cols = "({})".format(" || ".join(columns)) + return "{} REGEXP '.*{}.*'".format(cols, ast[1]) + + if ast[0] == "STRING": + cols = "({})".format(" || ".join(columns)) + return "{} LIKE '%{}%'".format(cols, ast[1]) + + if ast[0] == "SELECTION": + return compile_selection(ast) + + if ast[0] == "CONJUNCTION": + _, conj, lhs, rhs = ast + lhs = do_compile(lhs, columns) + rhs = do_compile(rhs, columns) + return "{} {} {}".format(lhs, conj, rhs) + + if ast[0] == "GROUPING": + return "({})".format(do_compile(ast[1], columns)) + + raise Exception("Unexpected AST: \"{}\"".format(ast)) + +def compile_selection(ast): + _, negate, column, query = ast + match = compile_query(negate, query) + return "{} {}".format(column, match) + +def compile_query(negate, query): + query_type, query_string = query + if query_type == "REGEX": + if negate: + return "NOT REGEXP '.*{}.*'".format(query_string) + return "REGEXP '.*{}.*'".format(query_string) + + if query_type == "STRING": + if negate: + return "NOT LIKE '%{}%'".format(query_string) + return "LIKE '%{}%'".format(query_string) + +################################################################################ +# Helper Functions +################################################################################ + +def regexp(expr, x): + reg = re.compile(expr) + return reg.search(x) is not None + +################################################################################ +# Main +################################################################################ + +def main(csv_path=None, debug=False): + # Import CSV to SQLite + table = "main" + con = sqlite3.connect(":memory:") + + con.create_function("REGEXP", 2, regexp) + + cur = con.cursor() + with open(csv_path, "r") as f: + r = csv.DictReader(f) + columns = next(r).keys() + + # TODO(wpcarro): Use safer interpolation variant of "?" here and throughout. + cur.execute("CREATE TABLE {} ({});".format(table, ",".join(columns))) + rows = [tuple(row[col] for col in columns) for row in r] + cur.executemany("INSERT INTO {} ({}) VALUES ({});".format(table, ",".join(columns), ",".join("?" for _ in columns)), rows) + con.commit() + + while True: + x = input("> ") + + if debug: + print("tokens:\t{}".format(tokenize(x))) + print("AST:\t{}".format(parse(x))) + print("query:\t\"{}\"".format(compile(x, table, columns))) + + try: + compile(x, table, columns) + for row in cur.execute(compile(x, table, columns)): + print("\t".join(str(cell) for cell in row)) + except: + print("Compilation error.") + + # TODO(wpcarro): Trap exits and ensure cleanup always runs. + con.close() + +if __name__ == "__main__": + parser = ArgumentParser() + parser.add_argument("-f", "--file", dest="file", help="Path to the CSV from which to read", metavar="PATH") + parser.add_argument("-d", "--debug", dest="debug", default=False, action="store_true", help="Enable debugging") + args = parser.parse_args() + main(csv_path=args.file, debug=args.debug) diff --git a/users/wpcarro/scratch/simple-select/parser.py b/users/wpcarro/scratch/simple-select/parser.py new file mode 100644 index 000000000000..d26f970e57a2 --- /dev/null +++ b/users/wpcarro/scratch/simple-select/parser.py @@ -0,0 +1,31 @@ +class Parser(object): + def __init__(self, tokens): + self.tokens = tokens + self.i = 0 + + def exhausted(self): + return self.i >= len(self.tokens) + + def peek(self, n=0): + return self.tokens[self.i + n] + + def advance(self): + if not self.exhausted(): + self.i += 1 + return self.peek(n=-1) + + def match(self, xs): + if self.peek() in xs: + self.advance() + return True + return False + + def test(self, predicate): + return predicate(self.tokens, self.i) + + def expect(self, predicate): + if self.exhausted(): + raise Exception("Unexpected EOL") + if predicate(self.peek()): + return self.advance() + raise Exception("Unexpected token: \"{}\"".format(self.peek())) diff --git a/users/wpcarro/scratch/simple-select/scanner.py b/users/wpcarro/scratch/simple-select/scanner.py new file mode 100644 index 000000000000..5dae68aee551 --- /dev/null +++ b/users/wpcarro/scratch/simple-select/scanner.py @@ -0,0 +1,27 @@ +# According to Crafting Interpreters, the only two primitives that a +# scanner/lexer needs are peek and advance; other functions (e.g. match) are +# nice-to-haves. +class Scanner(object): + def __init__(self, chars): + self.i = 0 + self.chars = chars + + def exhausted(self): + return self.i >= len(self.chars) + + def peek(self, n=0): + return self.chars[self.i + n] if self.i in range(0, len(self.chars)) else '\0' + + def advance(self): + result = self.peek() + self.i += 1 + return result + + def match(self, x): + if self.exhausted(): + return False + if self.peek() == x: + self.advance() + return True + else: + return False |