From d480b6f08b8a6b32c58e5bbdf2f193432c50fb97 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 15 Jan 2020 14:18:54 +0000 Subject: Initialize repo Adding a README and a basic .gitignore to initialize this mono-repo. I'm quite excited about this undertaking! --- .gitignore | 6 ++++++ README.md | 9 +++++++++ 2 files changed, 15 insertions(+) create mode 100644 .gitignore create mode 100644 README.md diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..51eb9644acd7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,6 @@ +# Haskell +*.hi +*.o + +# Python +__pycache__ diff --git a/README.md b/README.md new file mode 100644 index 000000000000..e7a01960b87c --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +# Mono + +This is my mono-repo. Having a personal mono-repo is a new idea for me, so at +the time of this writing, the state of this repository is fledgling. + +I'm attempting to amass a collection of functions across a variety of languages +while minimizing the costs of sharing the code across a projects. Stay tuned for +more updates as my definition of the mono-repo becomes more clear, my opinions +evolve, and my preferences change. -- cgit 1.4.1 From 456d358cd7b0701b0ca2b8571d61f255d3da1488 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 15 Jan 2020 14:21:45 +0000 Subject: Upload my 2019 Advent of Code attempts Well, unexpectedly (perhaps naively so), I only made it to Day 7. I created these before I stumbled upon the idea of the mono-repository; otherwise, I like to think I would have more granular commits introducing this work. --- advent-of-code/aoc2019.nix | 8 ++ advent-of-code/day_1.py | 119 ++++++++++++++++++++++++++ advent-of-code/day_2.py | 32 +++++++ advent-of-code/day_3.py | 137 ++++++++++++++++++++++++++++++ advent-of-code/day_4.py | 35 ++++++++ advent-of-code/day_5.ex | 50 +++++++++++ advent-of-code/day_5.py | 170 ++++++++++++++++++++++++++++++++++++++ advent-of-code/day_5.pyc | Bin 0 -> 5250 bytes advent-of-code/day_6.py | 155 ++++++++++++++++++++++++++++++++++ advent-of-code/day_7.py | 49 +++++++++++ advent-of-code/writePythonBin.nix | 18 ++++ 11 files changed, 773 insertions(+) create mode 100644 advent-of-code/aoc2019.nix create mode 100644 advent-of-code/day_1.py create mode 100644 advent-of-code/day_2.py create mode 100644 advent-of-code/day_3.py create mode 100644 advent-of-code/day_4.py create mode 100644 advent-of-code/day_5.ex create mode 100644 advent-of-code/day_5.py create mode 100644 advent-of-code/day_5.pyc create mode 100644 advent-of-code/day_6.py create mode 100644 advent-of-code/day_7.py create mode 100644 advent-of-code/writePythonBin.nix diff --git a/advent-of-code/aoc2019.nix b/advent-of-code/aoc2019.nix new file mode 100644 index 000000000000..10e6c559a9b4 --- /dev/null +++ b/advent-of-code/aoc2019.nix @@ -0,0 +1,8 @@ +with import {}; +with python35Packages; + +buildPythonPackage { + name = "wpcarro"; + src = ./day_5.py; + propagatedBuildInputs = [ pytest numpy ]; +} diff --git a/advent-of-code/day_1.py b/advent-of-code/day_1.py new file mode 100644 index 000000000000..bd4024e3ec7d --- /dev/null +++ b/advent-of-code/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/advent-of-code/day_2.py b/advent-of-code/day_2.py new file mode 100644 index 000000000000..77774c1bb5ad --- /dev/null +++ b/advent-of-code/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/advent-of-code/day_3.py b/advent-of-code/day_3.py new file mode 100644 index 000000000000..6dd863528c1c --- /dev/null +++ b/advent-of-code/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/advent-of-code/day_4.py b/advent-of-code/day_4.py new file mode 100644 index 000000000000..adef73b452dc --- /dev/null +++ b/advent-of-code/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/advent-of-code/day_5.ex b/advent-of-code/day_5.ex new file mode 100644 index 000000000000..807e3c9177be --- /dev/null +++ b/advent-of-code/day_5.ex @@ -0,0 +1,50 @@ +defmodule Interpretter do + def interpret_param({mode, x}, xs) do + case mode do + :positional -> Enum.at(xs, x) + :immediate -> x + end + end + + # Perhaps I can model the intepretter after Forth and make it a stack-based + # interpretter with an emphasis on debugability, introspection. + def interpret(i, xs) do + stack = [] + op = Enum.at(xs, i) + + # map instructions into an intermediate representation (i.e. IR) where the + # opcodes are mapped into atoms and the arguments are mapped into references + # or literals. + + instructions = + %{'01' => :add, + '02' => :multiply, + '03' => :input, + '04' => :output, + '05' => :jump_if_true, + '06' => :jump_if_false, + '07' => :less_than, + '08' => :equal_to, + '99' => :return} + + case xs do + [:add, a, b, {:positional, out} | rest] -> + a = interpret_param(a, xs) + b = interpret_param(b, xs) + Interpretter.interpret(i + 3, List.insert_at(xs, out, a + b)) + + [:multiply, a, b, {:positional, out} | rest] -> + a = interpret_param(a, xs) + b = interpret_param(b, xs) + Interpretter.interpret(i + 3, List.insert_at(xs, out, a * b)) + + [:input, a | rest] -> nil + [:output, a | rest] -> nil + [:jump_if_true, a, b | rest] -> nil + [:jump_if_false, a, b | rest] -> nil + [:less_than, a, b, out | rest] -> nil + [:equal_to, a, b, out | rest] -> nil + [:return | _rest] -> nil + end + end +end diff --git a/advent-of-code/day_5.py b/advent-of-code/day_5.py new file mode 100644 index 000000000000..3d82846e6126 --- /dev/null +++ b/advent-of-code/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/advent-of-code/day_5.pyc b/advent-of-code/day_5.pyc new file mode 100644 index 000000000000..d95573e94326 Binary files /dev/null and b/advent-of-code/day_5.pyc differ diff --git a/advent-of-code/day_6.py b/advent-of-code/day_6.py new file mode 100644 index 000000000000..aba99b8239ff --- /dev/null +++ b/advent-of-code/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/advent-of-code/day_7.py b/advent-of-code/day_7.py new file mode 100644 index 000000000000..14597d5104e3 --- /dev/null +++ b/advent-of-code/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/advent-of-code/writePythonBin.nix b/advent-of-code/writePythonBin.nix new file mode 100644 index 000000000000..1730edb2ebe4 --- /dev/null +++ b/advent-of-code/writePythonBin.nix @@ -0,0 +1,18 @@ +{ pkgs ? import {}, ... }: + +{ name, deps, src }: + +let + inherit (pkgs) pythonPackages writeTextFile; + inherit (builtins) toFile; + +in writeTextFile { + inherit name; + executable = true; + destination = "/bin/${name}"; + + text = '' + #!/bin/sh + ${pkgs.python3}/bin/python3 ${src} + ''; +} -- cgit 1.4.1 From b4ee283b23b8a85c75339e07b0adf43d1c3ca770 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 15 Jan 2020 14:23:37 +0000 Subject: Add "Crack the Coding Interview" examples I believe I have multiple other snippets and attempts scattered across /tmp, ~/programming, and other directories. Again, I created these files and others before the mono-repo. --- crack_the_coding_interview/11_1.py | 40 +++++++++++++++++++++++++++++++++++ crack_the_coding_interview/to_tree.hs | 11 ++++++++++ 2 files changed, 51 insertions(+) create mode 100644 crack_the_coding_interview/11_1.py create mode 100644 crack_the_coding_interview/to_tree.hs diff --git a/crack_the_coding_interview/11_1.py b/crack_the_coding_interview/11_1.py new file mode 100644 index 000000000000..ec7b65dae0c3 --- /dev/null +++ b/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 + +################################################################################ +# 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/crack_the_coding_interview/to_tree.hs b/crack_the_coding_interview/to_tree.hs new file mode 100644 index 000000000000..8496d88c0c0c --- /dev/null +++ b/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) -- cgit 1.4.1 From d4d8397e5ffe6734ed5861e48ce475848956a3fe Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 15 Jan 2020 14:25:33 +0000 Subject: Add InterviewCake.com examples Adds some of the code I generated while studying for a role transfer at Google using the fantastic resource, InterviewCake.com. This work predates the mono-repo. I should think of ways to DRY up this code and the code in crack_the_coding_interview, but I'm afraid I'm creating unnecessary work for myself that way. --- data_structures_and_algorithms/array-traversals.py | 87 ++++++++ .../balanced-binary-tree.py | 145 ++++++++++++ data_structures_and_algorithms/bit-manipulation.py | 32 +++ .../bracket-validator.py | 63 ++++++ data_structures_and_algorithms/bst-checker.py | 121 ++++++++++ .../cafe-order-checker.py | 91 ++++++++ data_structures_and_algorithms/cake-thief.py | 71 ++++++ data_structures_and_algorithms/coins.py | 57 +++++ .../conways-game-of-life.py | 78 +++++++ data_structures_and_algorithms/delete-node.py | 60 +++++ data_structures_and_algorithms/dft.py | 65 ++++++ .../dijkstra-shortest-path.py | 48 ++++ .../find-duplicate-optimize-for-space-beast.py | 56 +++++ .../find-duplicate-optimize-for-space.py | 61 +++++ .../find-rotation-point.py | 59 +++++ .../find-unique-int-among-duplicates.py | 45 ++++ data_structures_and_algorithms/fixtures.py | 110 +++++++++ data_structures_and_algorithms/graph-coloring.py | 180 +++++++++++++++ .../graph-to-graphviz.py | 39 ++++ .../highest-product-of-3.py | 89 ++++++++ .../inflight-entertainment.py | 35 +++ data_structures_and_algorithms/knapsack-0-1.py | 38 ++++ data_structures_and_algorithms/kth-to-last.py | 82 +++++++ data_structures_and_algorithms/largest-stack.py | 107 +++++++++ .../linked-list-cycles.py | 88 ++++++++ data_structures_and_algorithms/merge-sort.py | 28 +++ data_structures_and_algorithms/merging-ranges.py | 94 ++++++++ data_structures_and_algorithms/mesh-message.gv | 11 + data_structures_and_algorithms/mesh-message.py | 97 ++++++++ data_structures_and_algorithms/norman.py | 78 +++++++ data_structures_and_algorithms/nth-fibonacci.py | 59 +++++ data_structures_and_algorithms/optimal-stopping.py | 49 ++++ data_structures_and_algorithms/perm-tree.py | 83 +++++++ .../permutation-palindrome.py | 49 ++++ data_structures_and_algorithms/permutations.py | 55 +++++ data_structures_and_algorithms/plot.py | 9 + .../product-of-other-numbers.py | 68 ++++++ data_structures_and_algorithms/queue-two-stacks.py | 66 ++++++ data_structures_and_algorithms/rectangular-love.py | 246 +++++++++++++++++++++ .../recursive-string-permutations.py | 37 ++++ .../reverse-linked-list.py | 79 +++++++ data_structures_and_algorithms/reverse-words.py | 181 +++++++++++++++ .../second-largest-item-bst.py | 179 +++++++++++++++ .../shortest-path-inject-vertices.py | 94 ++++++++ data_structures_and_algorithms/shuffle.py | 34 +++ data_structures_and_algorithms/string-reverse.py | 22 ++ .../temperature-tracker.py | 84 +++++++ data_structures_and_algorithms/test.txt | 1 + data_structures_and_algorithms/top-scores.py | 25 +++ data_structures_and_algorithms/topo-sort.py | 31 +++ data_structures_and_algorithms/trickling-water.py | 38 ++++ .../which-appears-twice.py | 33 +++ 52 files changed, 3737 insertions(+) create mode 100644 data_structures_and_algorithms/array-traversals.py create mode 100644 data_structures_and_algorithms/balanced-binary-tree.py create mode 100644 data_structures_and_algorithms/bit-manipulation.py create mode 100644 data_structures_and_algorithms/bracket-validator.py create mode 100644 data_structures_and_algorithms/bst-checker.py create mode 100644 data_structures_and_algorithms/cafe-order-checker.py create mode 100644 data_structures_and_algorithms/cake-thief.py create mode 100644 data_structures_and_algorithms/coins.py create mode 100644 data_structures_and_algorithms/conways-game-of-life.py create mode 100644 data_structures_and_algorithms/delete-node.py create mode 100644 data_structures_and_algorithms/dft.py create mode 100644 data_structures_and_algorithms/dijkstra-shortest-path.py create mode 100644 data_structures_and_algorithms/find-duplicate-optimize-for-space-beast.py create mode 100644 data_structures_and_algorithms/find-duplicate-optimize-for-space.py create mode 100644 data_structures_and_algorithms/find-rotation-point.py create mode 100644 data_structures_and_algorithms/find-unique-int-among-duplicates.py create mode 100644 data_structures_and_algorithms/fixtures.py create mode 100644 data_structures_and_algorithms/graph-coloring.py create mode 100644 data_structures_and_algorithms/graph-to-graphviz.py create mode 100644 data_structures_and_algorithms/highest-product-of-3.py create mode 100644 data_structures_and_algorithms/inflight-entertainment.py create mode 100644 data_structures_and_algorithms/knapsack-0-1.py create mode 100644 data_structures_and_algorithms/kth-to-last.py create mode 100644 data_structures_and_algorithms/largest-stack.py create mode 100644 data_structures_and_algorithms/linked-list-cycles.py create mode 100644 data_structures_and_algorithms/merge-sort.py create mode 100644 data_structures_and_algorithms/merging-ranges.py create mode 100644 data_structures_and_algorithms/mesh-message.gv create mode 100644 data_structures_and_algorithms/mesh-message.py create mode 100644 data_structures_and_algorithms/norman.py create mode 100644 data_structures_and_algorithms/nth-fibonacci.py create mode 100644 data_structures_and_algorithms/optimal-stopping.py create mode 100644 data_structures_and_algorithms/perm-tree.py create mode 100644 data_structures_and_algorithms/permutation-palindrome.py create mode 100644 data_structures_and_algorithms/permutations.py create mode 100644 data_structures_and_algorithms/plot.py create mode 100644 data_structures_and_algorithms/product-of-other-numbers.py create mode 100644 data_structures_and_algorithms/queue-two-stacks.py create mode 100644 data_structures_and_algorithms/rectangular-love.py create mode 100644 data_structures_and_algorithms/recursive-string-permutations.py create mode 100644 data_structures_and_algorithms/reverse-linked-list.py create mode 100644 data_structures_and_algorithms/reverse-words.py create mode 100644 data_structures_and_algorithms/second-largest-item-bst.py create mode 100644 data_structures_and_algorithms/shortest-path-inject-vertices.py create mode 100644 data_structures_and_algorithms/shuffle.py create mode 100644 data_structures_and_algorithms/string-reverse.py create mode 100644 data_structures_and_algorithms/temperature-tracker.py create mode 100644 data_structures_and_algorithms/test.txt create mode 100644 data_structures_and_algorithms/top-scores.py create mode 100644 data_structures_and_algorithms/topo-sort.py create mode 100644 data_structures_and_algorithms/trickling-water.py create mode 100644 data_structures_and_algorithms/which-appears-twice.py diff --git a/data_structures_and_algorithms/array-traversals.py b/data_structures_and_algorithms/array-traversals.py new file mode 100644 index 000000000000..35cb4392812e --- /dev/null +++ b/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/data_structures_and_algorithms/balanced-binary-tree.py b/data_structures_and_algorithms/balanced-binary-tree.py new file mode 100644 index 000000000000..01fd965fd540 --- /dev/null +++ b/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/data_structures_and_algorithms/bit-manipulation.py b/data_structures_and_algorithms/bit-manipulation.py new file mode 100644 index 000000000000..dc30bb508887 --- /dev/null +++ b/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/data_structures_and_algorithms/bracket-validator.py b/data_structures_and_algorithms/bracket-validator.py new file mode 100644 index 000000000000..a50f8b074e55 --- /dev/null +++ b/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/data_structures_and_algorithms/bst-checker.py b/data_structures_and_algorithms/bst-checker.py new file mode 100644 index 000000000000..689be97a8503 --- /dev/null +++ b/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/data_structures_and_algorithms/cafe-order-checker.py b/data_structures_and_algorithms/cafe-order-checker.py new file mode 100644 index 000000000000..e34a2b136ab6 --- /dev/null +++ b/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/data_structures_and_algorithms/cake-thief.py b/data_structures_and_algorithms/cake-thief.py new file mode 100644 index 000000000000..9eddb34b2db3 --- /dev/null +++ b/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/data_structures_and_algorithms/coins.py b/data_structures_and_algorithms/coins.py new file mode 100644 index 000000000000..eb5754f98210 --- /dev/null +++ b/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/data_structures_and_algorithms/conways-game-of-life.py b/data_structures_and_algorithms/conways-game-of-life.py new file mode 100644 index 000000000000..3836bcd0c653 --- /dev/null +++ b/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/data_structures_and_algorithms/delete-node.py b/data_structures_and_algorithms/delete-node.py new file mode 100644 index 000000000000..7e431e224962 --- /dev/null +++ b/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/data_structures_and_algorithms/dft.py b/data_structures_and_algorithms/dft.py new file mode 100644 index 000000000000..127d48c1864b --- /dev/null +++ b/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/data_structures_and_algorithms/dijkstra-shortest-path.py b/data_structures_and_algorithms/dijkstra-shortest-path.py new file mode 100644 index 000000000000..03907f604044 --- /dev/null +++ b/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/data_structures_and_algorithms/find-duplicate-optimize-for-space-beast.py b/data_structures_and_algorithms/find-duplicate-optimize-for-space-beast.py new file mode 100644 index 000000000000..93fdd9eed2d6 --- /dev/null +++ b/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/data_structures_and_algorithms/find-duplicate-optimize-for-space.py b/data_structures_and_algorithms/find-duplicate-optimize-for-space.py new file mode 100644 index 000000000000..e2739f0f6055 --- /dev/null +++ b/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/data_structures_and_algorithms/find-rotation-point.py b/data_structures_and_algorithms/find-rotation-point.py new file mode 100644 index 000000000000..2103a5b84f75 --- /dev/null +++ b/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/data_structures_and_algorithms/find-unique-int-among-duplicates.py b/data_structures_and_algorithms/find-unique-int-among-duplicates.py new file mode 100644 index 000000000000..dfa5de42cc0b --- /dev/null +++ b/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/data_structures_and_algorithms/fixtures.py b/data_structures_and_algorithms/fixtures.py new file mode 100644 index 000000000000..27689ca76d04 --- /dev/null +++ b/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/data_structures_and_algorithms/graph-coloring.py b/data_structures_and_algorithms/graph-coloring.py new file mode 100644 index 000000000000..bc7f7ceea562 --- /dev/null +++ b/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/data_structures_and_algorithms/graph-to-graphviz.py b/data_structures_and_algorithms/graph-to-graphviz.py new file mode 100644 index 000000000000..0e7e97a20ca7 --- /dev/null +++ b/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/data_structures_and_algorithms/highest-product-of-3.py b/data_structures_and_algorithms/highest-product-of-3.py new file mode 100644 index 000000000000..889663e058da --- /dev/null +++ b/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/data_structures_and_algorithms/inflight-entertainment.py b/data_structures_and_algorithms/inflight-entertainment.py new file mode 100644 index 000000000000..6e17baef3709 --- /dev/null +++ b/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/data_structures_and_algorithms/knapsack-0-1.py b/data_structures_and_algorithms/knapsack-0-1.py new file mode 100644 index 000000000000..c72d19d4ed73 --- /dev/null +++ b/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/data_structures_and_algorithms/kth-to-last.py b/data_structures_and_algorithms/kth-to-last.py new file mode 100644 index 000000000000..8291e54533d5 --- /dev/null +++ b/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/data_structures_and_algorithms/largest-stack.py b/data_structures_and_algorithms/largest-stack.py new file mode 100644 index 000000000000..aab9671eb6d3 --- /dev/null +++ b/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/data_structures_and_algorithms/linked-list-cycles.py b/data_structures_and_algorithms/linked-list-cycles.py new file mode 100644 index 000000000000..75a4b993944c --- /dev/null +++ b/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/data_structures_and_algorithms/merge-sort.py b/data_structures_and_algorithms/merge-sort.py new file mode 100644 index 000000000000..6dbe0fa0f3c3 --- /dev/null +++ b/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/data_structures_and_algorithms/merging-ranges.py b/data_structures_and_algorithms/merging-ranges.py new file mode 100644 index 000000000000..4e3604d5bcca --- /dev/null +++ b/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/data_structures_and_algorithms/mesh-message.gv b/data_structures_and_algorithms/mesh-message.gv new file mode 100644 index 000000000000..1e67c3954f5f --- /dev/null +++ b/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/data_structures_and_algorithms/mesh-message.py b/data_structures_and_algorithms/mesh-message.py new file mode 100644 index 000000000000..c9d7d9d74151 --- /dev/null +++ b/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/data_structures_and_algorithms/norman.py b/data_structures_and_algorithms/norman.py new file mode 100644 index 000000000000..379ba92abba8 --- /dev/null +++ b/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/data_structures_and_algorithms/nth-fibonacci.py b/data_structures_and_algorithms/nth-fibonacci.py new file mode 100644 index 000000000000..cdb2846ea338 --- /dev/null +++ b/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/data_structures_and_algorithms/optimal-stopping.py b/data_structures_and_algorithms/optimal-stopping.py new file mode 100644 index 000000000000..af13239941d0 --- /dev/null +++ b/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/data_structures_and_algorithms/perm-tree.py b/data_structures_and_algorithms/perm-tree.py new file mode 100644 index 000000000000..0eb389c26bb9 --- /dev/null +++ b/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/data_structures_and_algorithms/permutation-palindrome.py b/data_structures_and_algorithms/permutation-palindrome.py new file mode 100644 index 000000000000..0a2136a408f2 --- /dev/null +++ b/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/data_structures_and_algorithms/permutations.py b/data_structures_and_algorithms/permutations.py new file mode 100644 index 000000000000..fc2c1ef7eebc --- /dev/null +++ b/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/data_structures_and_algorithms/plot.py b/data_structures_and_algorithms/plot.py new file mode 100644 index 000000000000..5601891a0d9b --- /dev/null +++ b/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/data_structures_and_algorithms/product-of-other-numbers.py b/data_structures_and_algorithms/product-of-other-numbers.py new file mode 100644 index 000000000000..d05e82d42b02 --- /dev/null +++ b/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/data_structures_and_algorithms/queue-two-stacks.py b/data_structures_and_algorithms/queue-two-stacks.py new file mode 100644 index 000000000000..63da08ebf79a --- /dev/null +++ b/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/data_structures_and_algorithms/rectangular-love.py b/data_structures_and_algorithms/rectangular-love.py new file mode 100644 index 000000000000..47c0f53979c6 --- /dev/null +++ b/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/data_structures_and_algorithms/recursive-string-permutations.py b/data_structures_and_algorithms/recursive-string-permutations.py new file mode 100644 index 000000000000..70461ddf5dac --- /dev/null +++ b/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/data_structures_and_algorithms/reverse-linked-list.py b/data_structures_and_algorithms/reverse-linked-list.py new file mode 100644 index 000000000000..b7396b20ce3f --- /dev/null +++ b/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/data_structures_and_algorithms/reverse-words.py b/data_structures_and_algorithms/reverse-words.py new file mode 100644 index 000000000000..5df12ebabdc7 --- /dev/null +++ b/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/data_structures_and_algorithms/second-largest-item-bst.py b/data_structures_and_algorithms/second-largest-item-bst.py new file mode 100644 index 000000000000..bc167d975a7b --- /dev/null +++ b/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/data_structures_and_algorithms/shortest-path-inject-vertices.py b/data_structures_and_algorithms/shortest-path-inject-vertices.py new file mode 100644 index 000000000000..e08ea66b8f50 --- /dev/null +++ b/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/data_structures_and_algorithms/shuffle.py b/data_structures_and_algorithms/shuffle.py new file mode 100644 index 000000000000..bdfbad24263c --- /dev/null +++ b/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/data_structures_and_algorithms/string-reverse.py b/data_structures_and_algorithms/string-reverse.py new file mode 100644 index 000000000000..8b4cdac1c271 --- /dev/null +++ b/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/data_structures_and_algorithms/temperature-tracker.py b/data_structures_and_algorithms/temperature-tracker.py new file mode 100644 index 000000000000..6b042182f01c --- /dev/null +++ b/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/data_structures_and_algorithms/test.txt b/data_structures_and_algorithms/test.txt new file mode 100644 index 000000000000..ce013625030b --- /dev/null +++ b/data_structures_and_algorithms/test.txt @@ -0,0 +1 @@ +hello diff --git a/data_structures_and_algorithms/top-scores.py b/data_structures_and_algorithms/top-scores.py new file mode 100644 index 000000000000..8e7b073dd8bd --- /dev/null +++ b/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/data_structures_and_algorithms/topo-sort.py b/data_structures_and_algorithms/topo-sort.py new file mode 100644 index 000000000000..fe295b0279ff --- /dev/null +++ b/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/data_structures_and_algorithms/trickling-water.py b/data_structures_and_algorithms/trickling-water.py new file mode 100644 index 000000000000..45621990ecf9 --- /dev/null +++ b/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/data_structures_and_algorithms/which-appears-twice.py b/data_structures_and_algorithms/which-appears-twice.py new file mode 100644 index 000000000000..e9a4f0eb24d0 --- /dev/null +++ b/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) -- cgit 1.4.1 From 8d00a456a03aed5dc4a8944519e4d3d44c446ad8 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 15 Jan 2020 14:29:18 +0000 Subject: Beging work to port f.el to Haskell This is a work-in-progress. I'd like to add a README to this project to explain my intention. The goal, roughly, is to port Elisp's fantastic f.el module to Haskell. I consider Haskell APIs to be useful but somewhat sloppily designed. In the same spirit as Elixir wrapping Erlang APIs, many of the functions I intend to define will simply wrap existing Haskell APIs, but with a hopefully cleaner API that I find more intuitive. --- haskell-file/f-todo.org | 67 +++++++++++++++++++++++++++++++++++++++++++++++++ haskell-file/f.hs | 47 ++++++++++++++++++++++++++++++++++ haskell-file/shell.nix | 9 +++++++ 3 files changed, 123 insertions(+) create mode 100644 haskell-file/f-todo.org create mode 100644 haskell-file/f.hs create mode 100644 haskell-file/shell.nix diff --git a/haskell-file/f-todo.org b/haskell-file/f-todo.org new file mode 100644 index 000000000000..6dd43a96291f --- /dev/null +++ b/haskell-file/f-todo.org @@ -0,0 +1,67 @@ +* Paths +** TODO f-join (&rest args) +** TODO f-split (path) +** TODO f-expand (path &optional dir) +** TODO f-filename (path) +** TODO f-dirname (path) +** TODO f-common-parent (paths) +** TODO f-ext (path) +** TODO f-no-ext (path) +** TODO f-swap-ext (path ext) +** TODO f-base (path) +** TODO f-relative (path &optional dir) +** TODO f-short (path) +** TODO f-long (path) +** TODO f-canonical (path) +** TODO f-slash (path) +** TODO f-full (path) +** TODO f-uniquify (paths) +** TODO f-uniquify-alist (paths) +* I/O +** TODO f-read-bytes (path) +** TODO f-write-bytes (data path) +** TODO f-read-text (path &optional coding) +** TODO f-write-text(text coding path) +** TODO f-append-text(text coding path) +** TODO f-append-bytes(text coding path) +** TODO Destructive +** TODO f-mkdir (&rest dirs) +** TODO f-delete (path &optional force) +** TODO f-symlink (source path) +** TODO f-move (from to) +** TODO f-copy (from to) +** TODO f-copy-contenst (from to) +** TODO f-touch (path) +** TODO Predicates +** TODO f-exists? (path) +** TODO f-directory? (path) +** TODO f-file? (path) +** TODO f-symlink? (path) +** TODO f-readable? (path) +** TODO f-writable? (path) +** TODO f-executable? (path) +** TODO f-absolute? (path) +** TODO f-relative? (path) +** TODO f-root? (path) +** TODO f-ext? (path ext) +** TODO f-same? (path-a path-b) +** TODO f-parent-of? (path-a path-b) +** TODO f-child-of? (path-a path-b) +** TODO f-ancestor-of? (path-a path-b) +** TODO f-descendant-of? (path-a path-b) +** TODO f-hidden? (path) +** TODO f-empty? (path) +** TODO Stats +** TODO f-size (path) +** f-depth (path) + +* Misc +** TODO f-this-file () +** TODO f-path-separator () +** TODO f-glob (pattern &optional path) +** TODO f-entries (path &optional fn recursive) +** TODO f-directories (path &optional fn recursive) +** TODO f-files (path &optional fn recursive) +** TODO f-root () +** TODO f-traverse-upwards (fn &optional path) +** TODO f-with-sandbox (path-or-paths &rest body) diff --git a/haskell-file/f.hs b/haskell-file/f.hs new file mode 100644 index 000000000000..9ddb930ee103 --- /dev/null +++ b/haskell-file/f.hs @@ -0,0 +1,47 @@ +module F + ( join + ) where + +import System.FilePath.Posix (FilePath) +import qualified System.FilePath.Posix as F + +-- TODO: Move this to a misc.hs, prelude.hs, operators.hs; somewhere. +(|>) :: a -> (a -> b) -> b +(|>) a f = f a +infixl 1 |> + +-- TODO: Move this to a test_utils.hs or elsewhere. +simpleAssert :: (Eq a) => a -> a -> () +simpleAssert x y = + if x == y then + () + else + error "Assertion error" + +-------------------------------------------------------------------------------- +-- Library +-------------------------------------------------------------------------------- + +join :: [FilePath] -> FilePath +join = F.joinPath + +-------------------------------------------------------------------------------- +-- Tests +-------------------------------------------------------------------------------- + +expected :: [([FilePath], FilePath)] +expected = [ (["path"], "path") + , (["/path"], "/path") + , (["path", "to", "file"], "path/to/file") + , (["/path", "to", "file"], "/path/to/file") + , (["/"], "/") + ] + +runTests :: [()] +runTests = + fmap (\(input, expected) -> simpleAssert (join input) expected) expected + +main :: IO () +main = do + print runTests + pure () diff --git a/haskell-file/shell.nix b/haskell-file/shell.nix new file mode 100644 index 000000000000..f2621d6eac5a --- /dev/null +++ b/haskell-file/shell.nix @@ -0,0 +1,9 @@ +with import {}; + +stdenv.mkDerivation { + name = "f-hs"; + buildInputs = [ + (pkgs.haskellPackages.ghcWithPackages (pkgs: [ + ])) + ]; +} -- cgit 1.4.1 From a62553f7b7f51c735e12dc935bec57dec8eac25e Mon Sep 17 00:00:00 2001 From: William Carroll Date: Fri, 17 Jan 2020 20:08:24 +0000 Subject: Add DeepMind subdirectory I need to prepare for my on-site with DeepMind, so I'll host some attempts to solve data structures and algorithms questions here. --- deepmind/kth-to-last.py | 64 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 64 insertions(+) create mode 100644 deepmind/kth-to-last.py diff --git a/deepmind/kth-to-last.py b/deepmind/kth-to-last.py new file mode 100644 index 000000000000..5335e419f7ec --- /dev/null +++ b/deepmind/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) -- cgit 1.4.1 From 34dc3e05c8a42c47023776d52ff33f100f2d310f Mon Sep 17 00:00:00 2001 From: William Carroll Date: Sat, 18 Jan 2020 17:04:05 +0000 Subject: Complete practice algorithms from InterviewCake.com While I've done these algorithms before, I'm preparing for an on-site with DeepMind, so I created a subdirectory called deepmind where I'm storing my second attempts at these problems. The idea of storing them in a second directory is to remove the urge to check my existing solutions that also exist in this repository. --- deepmind/inflight-entertainment.py | 51 ++++++++++++++++++++++++++++++++ deepmind/merging-ranges.py | 59 ++++++++++++++++++++++++++++++++++++++ deepmind/stock-price.py | 51 ++++++++++++++++++++++++++++++++ 3 files changed, 161 insertions(+) create mode 100644 deepmind/inflight-entertainment.py create mode 100644 deepmind/merging-ranges.py create mode 100644 deepmind/stock-price.py diff --git a/deepmind/inflight-entertainment.py b/deepmind/inflight-entertainment.py new file mode 100644 index 000000000000..2116b27b0b97 --- /dev/null +++ b/deepmind/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/deepmind/merging-ranges.py b/deepmind/merging-ranges.py new file mode 100644 index 000000000000..23b40793b8f1 --- /dev/null +++ b/deepmind/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/deepmind/stock-price.py b/deepmind/stock-price.py new file mode 100644 index 000000000000..7055b66af196 --- /dev/null +++ b/deepmind/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) -- cgit 1.4.1 From bb0de3dec2a610a3789c0fd02dd9e018e239b487 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Sat, 18 Jan 2020 17:05:32 +0000 Subject: Begin tests for Haskell File module Cameron sent over some property tests for his File.split function, which is a part of a larger effort to port f.el, a nice library for working with file paths, over to Haskell. --- haskell-file/f.hs | 17 +++++++++++++++++ haskell-file/tests.hs | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 56 insertions(+) create mode 100644 haskell-file/tests.hs diff --git a/haskell-file/f.hs b/haskell-file/f.hs index 9ddb930ee103..295575f3f48d 100644 --- a/haskell-file/f.hs +++ b/haskell-file/f.hs @@ -1,7 +1,14 @@ module F ( join + , split ) where +-------------------------------------------------------------------------------- +-- Dependencies +-------------------------------------------------------------------------------- + +import Data.List (span) +import System.FilePath (FilePath, pathSeparator) import System.FilePath.Posix (FilePath) import qualified System.FilePath.Posix as F @@ -25,6 +32,16 @@ simpleAssert x y = join :: [FilePath] -> FilePath join = F.joinPath +-- | Split path and return list containing parts. +split :: FilePath -> [String] +split = splitJoin . span (/= pathSeparator) + where + splitJoin :: (String, String) -> [String] + splitJoin ([], []) = [] + splitJoin (a, []) = [a] + splitJoin (a, [_]) = [a] + splitJoin (a, _:b) = a : split b + -------------------------------------------------------------------------------- -- Tests -------------------------------------------------------------------------------- diff --git a/haskell-file/tests.hs b/haskell-file/tests.hs new file mode 100644 index 000000000000..e3967b77de1f --- /dev/null +++ b/haskell-file/tests.hs @@ -0,0 +1,39 @@ +module FTest where +-------------------------------------------------------------------------------- +import Test.Tasty +import Test.Tasty.Hedgehog +import Hedgehog +-------------------------------------------------------------------------------- +import qualified Hedgehog as H +import qualified Hedgehog.Gen as Gen +import qualified Hedgehog.Range as Range +-------------------------------------------------------------------------------- +import Data.List (intercalate) +import System.FilePath (pathSeparator) +-------------------------------------------------------------------------------- +import F +-------------------------------------------------------------------------------- +main :: IO () +main + = defaultMain + . localOption (HedgehogTestLimit $ Just 50) + $ testGroup "f functions" + [ test_split + ] +-------------------------------------------------------------------------------- +test_split :: TestTree +test_split + = testGroup "split function" + [ testProperty "splits parts properly" splitSuccess + ] +splitSuccess :: Property +splitSuccess = property $ do + -- separator + -- <- H.forAll + -- $ Gen.element ['/', '\\'] + parts + <- H.forAll + . Gen.list (Range.linear 0 10) + $ Gen.list (Range.linear 1 10) Gen.alphaNum + let path = intercalate [pathSeparator] parts + F.split path === parts -- cgit 1.4.1 From 64bd3f030313765bab3cfdc76bccc69105c764dc Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 22 Jan 2020 10:08:56 +0000 Subject: Complete balanced binary tree problem Supporting a function that returns true if a tree has no two leaf nodes with depth differences greater than 1. --- deepmind/balanced-binary-tree.py | 123 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 123 insertions(+) create mode 100644 deepmind/balanced-binary-tree.py diff --git a/deepmind/balanced-binary-tree.py b/deepmind/balanced-binary-tree.py new file mode 100644 index 000000000000..7fc174a2a9f3 --- /dev/null +++ b/deepmind/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) -- cgit 1.4.1 From bbea699f06157d0291f837d4e09a0f391ad09a06 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 22 Jan 2020 10:09:46 +0000 Subject: Complete string permutations problem Solves an InterviewCake.com problem that returns all of the permutations of a string input. The problem states that it's acceptable to assume that your input string will not have repeated characters, which is why using a Set is acceptable. I like this solution because it builds a permutations tree and then assembles all of the permutations by doing a DFT over that tree. --- deepmind/recursive-string-permutations.py | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 deepmind/recursive-string-permutations.py diff --git a/deepmind/recursive-string-permutations.py b/deepmind/recursive-string-permutations.py new file mode 100644 index 000000000000..f50db2838707 --- /dev/null +++ b/deepmind/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) -- cgit 1.4.1 From de039c7133c80caa76e0549e2388a6e54768286f Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 22 Jan 2020 10:11:23 +0000 Subject: Complete find-rotation-point Solves an InterviewCake.com problem that returns the index of the element in a list that should be the first element in that list. It's an exercise that's useful for seeing other applications of a binary search. --- deepmind/find-rotation-point.py | 55 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 deepmind/find-rotation-point.py diff --git a/deepmind/find-rotation-point.py b/deepmind/find-rotation-point.py new file mode 100644 index 000000000000..5c21d5167ce9 --- /dev/null +++ b/deepmind/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) -- cgit 1.4.1 From 2a0540d76da26497d4600e9666bb6f36355357c1 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 22 Jan 2020 10:12:27 +0000 Subject: Create org table of sorting algorithms and their efficiency This is just a small org table that I created to help me Fun fact: In Emacs, you can insert literal TAB character by pressing `C-q TAB`. For creating tables, using TAB characters feels perfectly acceptable. Perhaps the TAB name comes from TABle. --- deepmind/efficiency.org | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 deepmind/efficiency.org diff --git a/deepmind/efficiency.org b/deepmind/efficiency.org new file mode 100644 index 000000000000..89a45c52ad8a --- /dev/null +++ b/deepmind/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) -- cgit 1.4.1 From 4f6191b34cb0f29ece4930ee7279048cba7e33f2 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 22 Jan 2020 11:05:43 +0000 Subject: Complete reverse-linked-list problem Completing the deceptively tricky reverse-linked-list problem from InterviewCake.com. --- deepmind/reverse-linked-list.py | 74 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 deepmind/reverse-linked-list.py diff --git a/deepmind/reverse-linked-list.py b/deepmind/reverse-linked-list.py new file mode 100644 index 000000000000..82fac171d5d1 --- /dev/null +++ b/deepmind/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) -- cgit 1.4.1 From 91811236a56c6323f469d5baf6eaa4e436658bb8 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 22 Jan 2020 11:06:10 +0000 Subject: Complete which-appears-twice Solves the InterviewCake.com problem that asks us to write a function that returns the number, y, that occurs twice in a list, xs, where xs is an unsorted list of integers from 1..n with a length of n + 1. --- deepmind/which-appears-twice.py | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 deepmind/which-appears-twice.py diff --git a/deepmind/which-appears-twice.py b/deepmind/which-appears-twice.py new file mode 100644 index 000000000000..c01379295d32 --- /dev/null +++ b/deepmind/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) -- cgit 1.4.1 From c010e6d6cff1e348645c337b1c63f8d66a6d6859 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 22 Jan 2020 11:07:07 +0000 Subject: Practice dijkstra's algorithm Getting some practice with Python's heapq module (which I'm unsure if I used correctly) to do a priority-first-traversal of a graph: known as Dijkstra's algorithm. --- deepmind/dijkstra.py | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 deepmind/dijkstra.py diff --git a/deepmind/dijkstra.py b/deepmind/dijkstra.py new file mode 100644 index 000000000000..6975dbe4d1d6 --- /dev/null +++ b/deepmind/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.") -- cgit 1.4.1 From 55860debe5e480a043ae47833a218b70a70bf19f Mon Sep 17 00:00:00 2001 From: William Carroll Date: Wed, 22 Jan 2020 21:52:45 +0000 Subject: Support Common Lisp Adding some Common Lisp here to get the party started! *cues music* --- common-lisp/main.lisp | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 common-lisp/main.lisp diff --git a/common-lisp/main.lisp b/common-lisp/main.lisp new file mode 100644 index 000000000000..2c4a5a411635 --- /dev/null +++ b/common-lisp/main.lisp @@ -0,0 +1,15 @@ +(in-package #:cl-user) +(defpackage #:utils + (:documentation "Some utility functions and macros to wet my beak.") + (:use #:cl) + (:shadow #:type)) +(in-package #:utils) + +(defmacro type (name in out) + `(declaim (ftype (function ,in ,out) ,name))) + +(defmacro comment (&rest _forms) nil) + +(type add (int int) int) +(defun add (a b) + (+ a b)) -- cgit 1.4.1 From d262d3054393fe8c445d80269fed9ef7e5b159d8 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Thu, 23 Jan 2020 13:45:57 +0000 Subject: Attempt to package CL unit testing with Nix My first foray trying to package Common Lisp with Nix. I'm using @tazjin's buildLisp and other libraries, all of which I'm importing as `tpkgs`, and all of which have been a tremendous boon to my productivity. --- common-lisp/anaphora.nix | 17 +++++++++++++++++ common-lisp/cl-colors.nix | 20 ++++++++++++++++++++ common-lisp/default.nix | 13 +++++++++++++ common-lisp/let-plus.nix | 19 +++++++++++++++++++ common-lisp/prove-asdf.nix | 15 +++++++++++++++ common-lisp/prove.nix | 31 +++++++++++++++++++++++++++++++ common-lisp/unit-testing.lisp | 13 +++++++++++++ 7 files changed, 128 insertions(+) create mode 100644 common-lisp/anaphora.nix create mode 100644 common-lisp/cl-colors.nix create mode 100644 common-lisp/default.nix create mode 100644 common-lisp/let-plus.nix create mode 100644 common-lisp/prove-asdf.nix create mode 100644 common-lisp/prove.nix create mode 100644 common-lisp/unit-testing.lisp diff --git a/common-lisp/anaphora.nix b/common-lisp/anaphora.nix new file mode 100644 index 000000000000..f64d01a50eee --- /dev/null +++ b/common-lisp/anaphora.nix @@ -0,0 +1,17 @@ +{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/tokenrove/anaphora.git"; + rev = "aeace4c68cf55098a67112750b28f8f2dc6d0e30"; + }; +in tpkgs.nix.buildLisp.library { + name = "anaphora"; + deps = []; + srcs = [ + "${src}/packages.lisp" + "${src}/early.lisp" + "${src}/symbolic.lisp" + "${src}/anaphora.lisp" + ]; +} diff --git a/common-lisp/cl-colors.nix b/common-lisp/cl-colors.nix new file mode 100644 index 000000000000..cff40fef342d --- /dev/null +++ b/common-lisp/cl-colors.nix @@ -0,0 +1,20 @@ +{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/tpapp/cl-colors.git"; + rev = "827410584553f5c717eec6182343b7605f707f75"; + }; +in tpkgs.nix.buildLisp.library { + name = "cl-colors"; + deps = with tpkgs.third_party.lisp; [ + alexandria + (import ./let-plus.nix {}) + ]; + srcs = [ + "${src}/package.lisp" + "${src}/colors.lisp" + "${src}/colornames.lisp" + "${src}/hexcolors.lisp" + ]; +} diff --git a/common-lisp/default.nix b/common-lisp/default.nix new file mode 100644 index 000000000000..b3c51eea704c --- /dev/null +++ b/common-lisp/default.nix @@ -0,0 +1,13 @@ +{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: + +tpkgs.nix.buildLisp.program { + name = "unit-testing"; + + deps = with tpkgs.third_party.lisp; [ + (import ./prove.nix {}) + ]; + + srcs = [ + ./unit-testing.lisp + ]; +} diff --git a/common-lisp/let-plus.nix b/common-lisp/let-plus.nix new file mode 100644 index 000000000000..5b279477de1b --- /dev/null +++ b/common-lisp/let-plus.nix @@ -0,0 +1,19 @@ +{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/tpapp/let-plus.git"; + rev = "7cf18b29ed0fe9c667a9a6a101b08ab9661a59e9"; + }; +in tpkgs.nix.buildLisp.library { + name = "let-plus"; + deps = with tpkgs.third_party.lisp; [ + alexandria + (import ./anaphora.nix {}) + ]; + srcs = [ + "${src}/package.lisp" + "${src}/let-plus.lisp" + "${src}/extensions.lisp" + ]; +} diff --git a/common-lisp/prove-asdf.nix b/common-lisp/prove-asdf.nix new file mode 100644 index 000000000000..8a692cf125ad --- /dev/null +++ b/common-lisp/prove-asdf.nix @@ -0,0 +1,15 @@ +{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/fukamachi/prove.git"; + rev = "5d71f02795b89e36f34e8c7d50e69b67ec6ca2de"; + }; +in tpkgs.nix.buildLisp.library { + name = "prove-asdf"; + deps = []; + srcs = [ + "${src}/src/output.lisp" + "${src}/src/asdf.lisp" + ]; +} diff --git a/common-lisp/prove.nix b/common-lisp/prove.nix new file mode 100644 index 000000000000..a4499dcea9bb --- /dev/null +++ b/common-lisp/prove.nix @@ -0,0 +1,31 @@ +{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/fukamachi/prove.git"; + rev = "5d71f02795b89e36f34e8c7d50e69b67ec6ca2de"; + }; +in tpkgs.nix.buildLisp.library { + name = "prove"; + deps = with tpkgs.third_party.lisp; [ + cl-ppcre + cl-ansi-text + (import ./cl-colors.nix {}) + alexandria + uiop + ]; + srcs = [ + "${src}/src/asdf.lisp" + "${src}/src/suite.lisp" + "${src}/src/color.lisp" + "${src}/src/output.lisp" + "${src}/src/prove.lisp" + "${src}/src/report.lisp" + "${src}/src/reporter.lisp" + "${src}/src/test.lisp" + "${src}/src/reporter/dot.lisp" + "${src}/src/reporter/fiveam.lisp" + "${src}/src/reporter/list.lisp" + "${src}/src/reporter/tap.lisp" + ]; +} diff --git a/common-lisp/unit-testing.lisp b/common-lisp/unit-testing.lisp new file mode 100644 index 000000000000..c0b3be9b4515 --- /dev/null +++ b/common-lisp/unit-testing.lisp @@ -0,0 +1,13 @@ +(in-package #:cl-user) +(defpackage #:my-test + (:documentation "Unit testing in Common Lisp.") + (:use #:cl)) +(in-package #:my-test) + +(plan 3) + +(ok (not (find 4 '(1 2 3)))) +(is 4 4) +(isnt 1 #\1) + +(finalize) -- cgit 1.4.1 From 01539a9988b2e7bdd86ff78ef75fee25b77f38c7 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Thu, 23 Jan 2020 16:30:06 +0000 Subject: Support .envrc Add `NIX_PATH` aliases for `depot` and `universe`. --- .envrc | 1 + 1 file changed, 1 insertion(+) create mode 100644 .envrc diff --git a/.envrc b/.envrc new file mode 100644 index 000000000000..b56819ed8cdf --- /dev/null +++ b/.envrc @@ -0,0 +1 @@ +NIX_PATH=depot=$HOME/depot:universe=$HOME/mono -- cgit 1.4.1 From 393bd0a5c7967c9eafbf228413416d7691125e59 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Thu, 23 Jan 2020 16:30:39 +0000 Subject: Nixify repo Using @tazjin's depot/default.nix to bootstrap this project. I'll be borrowing his Nix idioms until I better understand Nix and have preferences of my own. --- default.nix | 33 +++++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 default.nix diff --git a/default.nix b/default.nix new file mode 100644 index 000000000000..d1fb88e04f93 --- /dev/null +++ b/default.nix @@ -0,0 +1,33 @@ +# At the time of this writing, this configuration was taken from @tazjin's +# default.nix from his depot. I've added, changed, and removed that parts that I +# don't need, and this is what remains. +{ ... }@args: + +with builtins; + +let + fix = f: let x = f x; in x; + + # Global configuration that all packages are called with. + config = pkgs: { + inherit pkgs; + }; + + readTree' = import /home/wpcarro/depot/nix/readTree {}; + + localPkgs = readTree: { + third_party = readTree ./third_party; + }; +in fix(self: { + config = config self; + + # Expose readTree for downstream repo consumers. + readTree = { + __functor = x: (readTree' x.config); + config = self.config; + }; +} + +# Add local packages as structured by readTree +// (localPkgs (readTree' (self.config // { inherit (self) lib; }))) +) -- cgit 1.4.1 From e76843d337546fe83dbbc9eba9cff52ca6767ef7 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Thu, 23 Jan 2020 16:31:54 +0000 Subject: Create third_party Create a third_party subdirectory and a third_party/lisp. This directory layout resembles and is inspired by the layout of Google's mono-repo, Google3. @tazjin borrowed this idea from Google and I'm borrowing the idea from him. --- common-lisp/anaphora.nix | 17 ----------------- common-lisp/cl-colors.nix | 20 -------------------- common-lisp/let-plus.nix | 19 ------------------- common-lisp/prove-asdf.nix | 15 --------------- common-lisp/prove.nix | 31 ------------------------------- third_party/lisp/anaphora.nix | 17 +++++++++++++++++ third_party/lisp/cl-colors.nix | 24 ++++++++++++++++++++++++ third_party/lisp/let-plus.nix | 23 +++++++++++++++++++++++ third_party/lisp/prove.nix | 35 +++++++++++++++++++++++++++++++++++ 9 files changed, 99 insertions(+), 102 deletions(-) delete mode 100644 common-lisp/anaphora.nix delete mode 100644 common-lisp/cl-colors.nix delete mode 100644 common-lisp/let-plus.nix delete mode 100644 common-lisp/prove-asdf.nix delete mode 100644 common-lisp/prove.nix create mode 100644 third_party/lisp/anaphora.nix create mode 100644 third_party/lisp/cl-colors.nix create mode 100644 third_party/lisp/let-plus.nix create mode 100644 third_party/lisp/prove.nix diff --git a/common-lisp/anaphora.nix b/common-lisp/anaphora.nix deleted file mode 100644 index f64d01a50eee..000000000000 --- a/common-lisp/anaphora.nix +++ /dev/null @@ -1,17 +0,0 @@ -{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: - -let - src = builtins.fetchGit { - url = "https://github.com/tokenrove/anaphora.git"; - rev = "aeace4c68cf55098a67112750b28f8f2dc6d0e30"; - }; -in tpkgs.nix.buildLisp.library { - name = "anaphora"; - deps = []; - srcs = [ - "${src}/packages.lisp" - "${src}/early.lisp" - "${src}/symbolic.lisp" - "${src}/anaphora.lisp" - ]; -} diff --git a/common-lisp/cl-colors.nix b/common-lisp/cl-colors.nix deleted file mode 100644 index cff40fef342d..000000000000 --- a/common-lisp/cl-colors.nix +++ /dev/null @@ -1,20 +0,0 @@ -{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: - -let - src = builtins.fetchGit { - url = "https://github.com/tpapp/cl-colors.git"; - rev = "827410584553f5c717eec6182343b7605f707f75"; - }; -in tpkgs.nix.buildLisp.library { - name = "cl-colors"; - deps = with tpkgs.third_party.lisp; [ - alexandria - (import ./let-plus.nix {}) - ]; - srcs = [ - "${src}/package.lisp" - "${src}/colors.lisp" - "${src}/colornames.lisp" - "${src}/hexcolors.lisp" - ]; -} diff --git a/common-lisp/let-plus.nix b/common-lisp/let-plus.nix deleted file mode 100644 index 5b279477de1b..000000000000 --- a/common-lisp/let-plus.nix +++ /dev/null @@ -1,19 +0,0 @@ -{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: - -let - src = builtins.fetchGit { - url = "https://github.com/tpapp/let-plus.git"; - rev = "7cf18b29ed0fe9c667a9a6a101b08ab9661a59e9"; - }; -in tpkgs.nix.buildLisp.library { - name = "let-plus"; - deps = with tpkgs.third_party.lisp; [ - alexandria - (import ./anaphora.nix {}) - ]; - srcs = [ - "${src}/package.lisp" - "${src}/let-plus.lisp" - "${src}/extensions.lisp" - ]; -} diff --git a/common-lisp/prove-asdf.nix b/common-lisp/prove-asdf.nix deleted file mode 100644 index 8a692cf125ad..000000000000 --- a/common-lisp/prove-asdf.nix +++ /dev/null @@ -1,15 +0,0 @@ -{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: - -let - src = builtins.fetchGit { - url = "https://github.com/fukamachi/prove.git"; - rev = "5d71f02795b89e36f34e8c7d50e69b67ec6ca2de"; - }; -in tpkgs.nix.buildLisp.library { - name = "prove-asdf"; - deps = []; - srcs = [ - "${src}/src/output.lisp" - "${src}/src/asdf.lisp" - ]; -} diff --git a/common-lisp/prove.nix b/common-lisp/prove.nix deleted file mode 100644 index a4499dcea9bb..000000000000 --- a/common-lisp/prove.nix +++ /dev/null @@ -1,31 +0,0 @@ -{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: - -let - src = builtins.fetchGit { - url = "https://github.com/fukamachi/prove.git"; - rev = "5d71f02795b89e36f34e8c7d50e69b67ec6ca2de"; - }; -in tpkgs.nix.buildLisp.library { - name = "prove"; - deps = with tpkgs.third_party.lisp; [ - cl-ppcre - cl-ansi-text - (import ./cl-colors.nix {}) - alexandria - uiop - ]; - srcs = [ - "${src}/src/asdf.lisp" - "${src}/src/suite.lisp" - "${src}/src/color.lisp" - "${src}/src/output.lisp" - "${src}/src/prove.lisp" - "${src}/src/report.lisp" - "${src}/src/reporter.lisp" - "${src}/src/test.lisp" - "${src}/src/reporter/dot.lisp" - "${src}/src/reporter/fiveam.lisp" - "${src}/src/reporter/list.lisp" - "${src}/src/reporter/tap.lisp" - ]; -} diff --git a/third_party/lisp/anaphora.nix b/third_party/lisp/anaphora.nix new file mode 100644 index 000000000000..04a1dd847feb --- /dev/null +++ b/third_party/lisp/anaphora.nix @@ -0,0 +1,17 @@ +{ depot ? import {}, ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/tokenrove/anaphora.git"; + rev = "aeace4c68cf55098a67112750b28f8f2dc6d0e30"; + }; +in depot.nix.buildLisp.library { + name = "anaphora"; + deps = []; + srcs = [ + "${src}/packages.lisp" + "${src}/early.lisp" + "${src}/symbolic.lisp" + "${src}/anaphora.lisp" + ]; +} diff --git a/third_party/lisp/cl-colors.nix b/third_party/lisp/cl-colors.nix new file mode 100644 index 000000000000..2217e68ce70b --- /dev/null +++ b/third_party/lisp/cl-colors.nix @@ -0,0 +1,24 @@ +{ + depot ? import {}, + universe ? import {}, + ... +}: + +let + src = builtins.fetchGit { + url = "https://github.com/tpapp/cl-colors.git"; + rev = "827410584553f5c717eec6182343b7605f707f75"; + }; +in depot.nix.buildLisp.library { + name = "cl-colors"; + deps = [ + depot.third_party.lisp.alexandria + universe.third_party.lisp.let-plus + ]; + srcs = [ + "${src}/package.lisp" + "${src}/colors.lisp" + "${src}/colornames.lisp" + "${src}/hexcolors.lisp" + ]; +} diff --git a/third_party/lisp/let-plus.nix b/third_party/lisp/let-plus.nix new file mode 100644 index 000000000000..a3a15776bf86 --- /dev/null +++ b/third_party/lisp/let-plus.nix @@ -0,0 +1,23 @@ +{ + depot ? import {}, + universe ? import {}, + ... +}: + +let + src = builtins.fetchGit { + url = "https://github.com/tpapp/let-plus.git"; + rev = "7cf18b29ed0fe9c667a9a6a101b08ab9661a59e9"; + }; +in depot.nix.buildLisp.library { + name = "let-plus"; + deps = [ + depot.third_party.lisp.alexandria + universe.third_party.lisp.anaphora + ]; + srcs = [ + "${src}/package.lisp" + "${src}/let-plus.lisp" + "${src}/extensions.lisp" + ]; +} diff --git a/third_party/lisp/prove.nix b/third_party/lisp/prove.nix new file mode 100644 index 000000000000..d6c0fe7413cb --- /dev/null +++ b/third_party/lisp/prove.nix @@ -0,0 +1,35 @@ +{ + depot ? import {}, + universe? import {}, + ... +}: + +let + src = builtins.fetchGit { + url = "https://github.com/fukamachi/prove.git"; + rev = "5d71f02795b89e36f34e8c7d50e69b67ec6ca2de"; + }; +in depot.nix.buildLisp.library { + name = "prove"; + deps = [ + depot.third_party.lisp.cl-ppcre + depot.third_party.lisp.cl-ansi-text + depot.third_party.lisp.alexandria + depot.third_party.lisp.uiop + universe.third_party.lisp.cl-colors + ]; + srcs = [ + "${src}/src/asdf.lisp" + "${src}/src/suite.lisp" + "${src}/src/color.lisp" + "${src}/src/output.lisp" + "${src}/src/prove.lisp" + "${src}/src/report.lisp" + "${src}/src/reporter.lisp" + "${src}/src/test.lisp" + "${src}/src/reporter/dot.lisp" + "${src}/src/reporter/fiveam.lisp" + "${src}/src/reporter/list.lisp" + "${src}/src/reporter/tap.lisp" + ]; +} -- cgit 1.4.1 From 7f37acf548307448c92bd567de096d5734ac6f0d Mon Sep 17 00:00:00 2001 From: William Carroll Date: Thu, 23 Jan 2020 16:33:04 +0000 Subject: Prefer and Consuming the aliases that I defined in `NIX_PATH` in `.envrc`. --- common-lisp/default.nix | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/common-lisp/default.nix b/common-lisp/default.nix index b3c51eea704c..0969c7abf7e2 100644 --- a/common-lisp/default.nix +++ b/common-lisp/default.nix @@ -1,10 +1,13 @@ -{ tpkgs ? (import (builtins.fetchGit "https://git.tazj.in/") {}), ... }: +{ depot ? import {}, + universe ? import {}, + ... +}: -tpkgs.nix.buildLisp.program { +depot.nix.buildLisp.program { name = "unit-testing"; - deps = with tpkgs.third_party.lisp; [ - (import ./prove.nix {}) + deps = with universe.third_party.lisp; [ + prove ]; srcs = [ -- cgit 1.4.1 From 2c28f85946538397e0a32ef334b525e5db29712c Mon Sep 17 00:00:00 2001 From: William Carroll Date: Thu, 23 Jan 2020 16:34:07 +0000 Subject: Start working on a blog Attempting to write a blog where: - The server is Common Lisp. Why? I'd like to learn Common Lisp. - The blog posts can be written in Markdown. - The package is developed and deployed using Nix. Most of this is a learning exercise. The blog itself is something that I'd like to use instead of Medium and other forums. --- blog/default.nix | 15 +++++++++++++++ blog/posts/test.md | 4 ++++ blog/src/server.lisp | 10 ++++++++++ 3 files changed, 29 insertions(+) create mode 100644 blog/default.nix create mode 100644 blog/posts/test.md create mode 100644 blog/src/server.lisp diff --git a/blog/default.nix b/blog/default.nix new file mode 100644 index 000000000000..7005cc648d00 --- /dev/null +++ b/blog/default.nix @@ -0,0 +1,15 @@ +{ + depot ? import {}, + universe ? import {}, + ... +}: + +depot.nix.buildLisp.program { + name = "server"; + deps = with depot.third_party.lisp; [ + hunchentoot + ]; + srcs = [ + ./src/server.lisp + ]; +} diff --git a/blog/posts/test.md b/blog/posts/test.md new file mode 100644 index 000000000000..ec2e030b2824 --- /dev/null +++ b/blog/posts/test.md @@ -0,0 +1,4 @@ +# Testing + +The goal here is to be able to write markdown files and have a server that can +render the markdown into HTML and serve them to the clients. diff --git a/blog/src/server.lisp b/blog/src/server.lisp new file mode 100644 index 000000000000..63323e933bd3 --- /dev/null +++ b/blog/src/server.lisp @@ -0,0 +1,10 @@ +(in-package #:cl-user) +(defpackage #:server + (:documentation "Robot condemned to a life of admin work for my blog.") + (:use #:cl) + (:export :main)) +(in-package #:server) + +(defun main () + "This is the main entrypoint for our application." + (hunchentoot)) -- cgit 1.4.1 From 336a1fdf9f72ef9559fa71c073d5e076e34466d5 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Thu, 23 Jan 2020 18:24:20 +0000 Subject: Add to NIX_PATH Lest we forget our roots. --- .envrc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.envrc b/.envrc index b56819ed8cdf..bf79e325a121 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -NIX_PATH=depot=$HOME/depot:universe=$HOME/mono +export NIX_PATH=nixpkgs=$HOME/.nix-defexpr/nixpkgs:depot=$HOME/depot:universe=$HOME/mono -- cgit 1.4.1 From b25d06db7e924effcb304645ab6121aad4b18ce5 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Thu, 23 Jan 2020 18:25:10 +0000 Subject: Attempt to inject dependencies into blog/server - We need the markdown files, to be in the /nix/store and the server needs to be aware of there location. - Since we're dependending on `pandoc`, our server needs to know about it too. For both of these cases -- especially for the latter case -- I imagine there may be a more idiomatic way of doing this. --- blog/default.nix | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/blog/default.nix b/blog/default.nix index 7005cc648d00..ed1e405ddca4 100644 --- a/blog/default.nix +++ b/blog/default.nix @@ -1,15 +1,27 @@ { + pkgs ? import {}, depot ? import {}, universe ? import {}, ... }: -depot.nix.buildLisp.program { +let + injectedPosts = pkgs.writeText "posts.lisp" '' + (in-package #:server) + (setq *path-to-posts* "${./posts}") + ''; + injectedExecutables = pkgs.writeText "executables.lisp" '' + (in-package #:server) + (setq *pandoc-bin* "${pkgs.pandoc}/bin/pandoc") + ''; +in depot.nix.buildLisp.program { name = "server"; deps = with depot.third_party.lisp; [ hunchentoot ]; srcs = [ ./src/server.lisp + injectedPosts + injectedExecutables ]; } -- cgit 1.4.1 From 81d7bc405eaf631bf58377c629af6d461021bb67 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Thu, 23 Jan 2020 18:27:11 +0000 Subject: Extend blog server to consume nix injections All of this is still a work-in-progress. Just checking in my work. Also: - Write function `render-post` to convert a markdown file into HTML. This is still a work-in-progress since we need to capture the output and not just have it printed to *standard-out*. - Return dummy data in /posts --- blog/src/server.lisp | 39 ++++++++++++++++++++++++++++++++++++++- 1 file changed, 38 insertions(+), 1 deletion(-) diff --git a/blog/src/server.lisp b/blog/src/server.lisp index 63323e933bd3..7f669ddd77bf 100644 --- a/blog/src/server.lisp +++ b/blog/src/server.lisp @@ -5,6 +5,43 @@ (:export :main)) (in-package #:server) +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Nix-injected dependencies +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defvar *path-to-posts* "/tmp" + "File path pointing to the posts directory.") + +(defvar *pandoc-bin* "/usr/bin/pandoc") + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Library +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defun render-post (path) + "Render the markdown file stored at PATH to HTML using pandoc." + (uiop:run-program (list *pandoc-bin* path "--to" "html") + :output t)) + +;; TODO: Figure out how to handle this with Nix. +(defvar *posts* (uiop:directory-files *path-to-posts*) + "List of the paths to the blog posts.") + +(hunchentoot:define-easy-handler + (get-latest :uri "/latest") () + (print (parameter "name")) + (uiop:read-file-string (car *posts*))) + +(hunchentoot:define-easy-handler + (get-posts :uri "/posts") () + "Working!") + (defun main () "This is the main entrypoint for our application." - (hunchentoot)) + (hunchentoot:start (make-instance 'hunchentoot:easy-acceptor :port 4242)) + (print "Listing on port 4242...") + (sb-thread:join-thread + (find-if (lambda (th) + (string= (sb-thread:thread-name th) + "hunchentoot-listener-*:4242")) + (sb-thread:list-all-threads)))) -- cgit 1.4.1 From 2e3bb0435f2849e2a5d4da0ed8408c2993ca9052 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Thu, 23 Jan 2020 21:56:24 +0000 Subject: Package cl-arrows package Using Nix to package this library of Clojure-inspired threading macros. TODO: Send this patch to tazjin. --- third_party/lisp/cl-arrows.nix | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 third_party/lisp/cl-arrows.nix diff --git a/third_party/lisp/cl-arrows.nix b/third_party/lisp/cl-arrows.nix new file mode 100644 index 000000000000..60cd8a375b1c --- /dev/null +++ b/third_party/lisp/cl-arrows.nix @@ -0,0 +1,15 @@ +{ depot ? import {}, ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/nightfly19/cl-arrows.git"; + rev = "cbb46b69a7de40f1161c9caaf6cef93b3af9994f"; + }; +in depot.nix.buildLisp.library { + name = "cl-arrows"; + deps = []; + srcs = [ + "${src}/packages.lisp" + "${src}/arrows.lisp" + ]; +} -- cgit 1.4.1 From 6108f8df0140e2f71c9f9b50d530d16400d9cdcb Mon Sep 17 00:00:00 2001 From: William Carroll Date: Thu, 23 Jan 2020 21:58:31 +0000 Subject: Support proof-of-concept blog After some toil, I have a working proof-of-concept blog. The idea is simple: write blog posts in markdown and store the posts in the `./posts` directory. Then use the server and `pandoc` to convert these markdown files into HTML at request time. I'm using nix to package everything together. It's far from perfect, but it works at the moment, which is encouraging. --- blog/default.nix | 11 ++++++----- blog/src/server.lisp | 10 ++++++---- default.nix | 5 ++++- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/blog/default.nix b/blog/default.nix index ed1e405ddca4..5359a0ab6f37 100644 --- a/blog/default.nix +++ b/blog/default.nix @@ -1,23 +1,24 @@ { - pkgs ? import {}, + nixpkgs ? import {}, depot ? import {}, universe ? import {}, ... }: let - injectedPosts = pkgs.writeText "posts.lisp" '' + injectedPosts = nixpkgs.writeText "posts.lisp" '' (in-package #:server) (setq *path-to-posts* "${./posts}") ''; - injectedExecutables = pkgs.writeText "executables.lisp" '' + injectedExecutables = nixpkgs.writeText "executables.lisp" '' (in-package #:server) - (setq *pandoc-bin* "${pkgs.pandoc}/bin/pandoc") + (setq *pandoc-bin* "${nixpkgs.pandoc}/bin/pandoc") ''; in depot.nix.buildLisp.program { name = "server"; - deps = with depot.third_party.lisp; [ + deps = with depot.third_party.lisp; with universe.third_party.lisp; [ hunchentoot + cl-arrows ]; srcs = [ ./src/server.lisp diff --git a/blog/src/server.lisp b/blog/src/server.lisp index 7f669ddd77bf..ad8169fa1af9 100644 --- a/blog/src/server.lisp +++ b/blog/src/server.lisp @@ -2,6 +2,7 @@ (defpackage #:server (:documentation "Robot condemned to a life of admin work for my blog.") (:use #:cl) + (:import-from #:cl-arrows #:->>) (:export :main)) (in-package #:server) @@ -9,7 +10,9 @@ ;; Nix-injected dependencies ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defvar *path-to-posts* "/tmp" +;; TODO: Wrap this in an assert or ensure that there's a trailing slash so it's +;; treated as a directory. +(defvar *path-to-posts* "/tmp/" "File path pointing to the posts directory.") (defvar *pandoc-bin* "/usr/bin/pandoc") @@ -21,7 +24,7 @@ (defun render-post (path) "Render the markdown file stored at PATH to HTML using pandoc." (uiop:run-program (list *pandoc-bin* path "--to" "html") - :output t)) + :output :string)) ;; TODO: Figure out how to handle this with Nix. (defvar *posts* (uiop:directory-files *path-to-posts*) @@ -29,8 +32,7 @@ (hunchentoot:define-easy-handler (get-latest :uri "/latest") () - (print (parameter "name")) - (uiop:read-file-string (car *posts*))) + (render-post (concatenate 'string *path-to-posts* "/" "test.md"))) (hunchentoot:define-easy-handler (get-posts :uri "/posts") () diff --git a/default.nix b/default.nix index d1fb88e04f93..ff31279941ae 100644 --- a/default.nix +++ b/default.nix @@ -15,8 +15,11 @@ let readTree' = import /home/wpcarro/depot/nix/readTree {}; + # TODO: Find a better way to expose entire monorepo without introducing + # "infinite recursion". localPkgs = readTree: { - third_party = readTree ./third_party; + blog = readTree ./blog; + third_party = readTree ./third_party; }; in fix(self: { config = config self; -- cgit 1.4.1 From b6fa3941b3731e959a7f54bf8e68e43e9077bf90 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Fri, 24 Jan 2020 10:50:05 +0000 Subject: Rename common-lisp directory to lisp I could have renamed common-lisp to common_lisp. I think Nix prefers underscores to hyphens. --- common-lisp/default.nix | 16 ---------------- common-lisp/main.lisp | 15 --------------- common-lisp/unit-testing.lisp | 13 ------------- default.nix | 1 + lisp/default.nix | 16 ++++++++++++++++ lisp/main.lisp | 15 +++++++++++++++ lisp/unit-testing.lisp | 13 +++++++++++++ 7 files changed, 45 insertions(+), 44 deletions(-) delete mode 100644 common-lisp/default.nix delete mode 100644 common-lisp/main.lisp delete mode 100644 common-lisp/unit-testing.lisp create mode 100644 lisp/default.nix create mode 100644 lisp/main.lisp create mode 100644 lisp/unit-testing.lisp diff --git a/common-lisp/default.nix b/common-lisp/default.nix deleted file mode 100644 index 0969c7abf7e2..000000000000 --- a/common-lisp/default.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ depot ? import {}, - universe ? import {}, - ... -}: - -depot.nix.buildLisp.program { - name = "unit-testing"; - - deps = with universe.third_party.lisp; [ - prove - ]; - - srcs = [ - ./unit-testing.lisp - ]; -} diff --git a/common-lisp/main.lisp b/common-lisp/main.lisp deleted file mode 100644 index 2c4a5a411635..000000000000 --- a/common-lisp/main.lisp +++ /dev/null @@ -1,15 +0,0 @@ -(in-package #:cl-user) -(defpackage #:utils - (:documentation "Some utility functions and macros to wet my beak.") - (:use #:cl) - (:shadow #:type)) -(in-package #:utils) - -(defmacro type (name in out) - `(declaim (ftype (function ,in ,out) ,name))) - -(defmacro comment (&rest _forms) nil) - -(type add (int int) int) -(defun add (a b) - (+ a b)) diff --git a/common-lisp/unit-testing.lisp b/common-lisp/unit-testing.lisp deleted file mode 100644 index c0b3be9b4515..000000000000 --- a/common-lisp/unit-testing.lisp +++ /dev/null @@ -1,13 +0,0 @@ -(in-package #:cl-user) -(defpackage #:my-test - (:documentation "Unit testing in Common Lisp.") - (:use #:cl)) -(in-package #:my-test) - -(plan 3) - -(ok (not (find 4 '(1 2 3)))) -(is 4 4) -(isnt 1 #\1) - -(finalize) diff --git a/default.nix b/default.nix index ff31279941ae..f4eda0d5ea26 100644 --- a/default.nix +++ b/default.nix @@ -19,6 +19,7 @@ let # "infinite recursion". localPkgs = readTree: { blog = readTree ./blog; + lisp = readTree ./lisp; third_party = readTree ./third_party; }; in fix(self: { diff --git a/lisp/default.nix b/lisp/default.nix new file mode 100644 index 000000000000..0969c7abf7e2 --- /dev/null +++ b/lisp/default.nix @@ -0,0 +1,16 @@ +{ depot ? import {}, + universe ? import {}, + ... +}: + +depot.nix.buildLisp.program { + name = "unit-testing"; + + deps = with universe.third_party.lisp; [ + prove + ]; + + srcs = [ + ./unit-testing.lisp + ]; +} diff --git a/lisp/main.lisp b/lisp/main.lisp new file mode 100644 index 000000000000..2c4a5a411635 --- /dev/null +++ b/lisp/main.lisp @@ -0,0 +1,15 @@ +(in-package #:cl-user) +(defpackage #:utils + (:documentation "Some utility functions and macros to wet my beak.") + (:use #:cl) + (:shadow #:type)) +(in-package #:utils) + +(defmacro type (name in out) + `(declaim (ftype (function ,in ,out) ,name))) + +(defmacro comment (&rest _forms) nil) + +(type add (int int) int) +(defun add (a b) + (+ a b)) diff --git a/lisp/unit-testing.lisp b/lisp/unit-testing.lisp new file mode 100644 index 000000000000..c0b3be9b4515 --- /dev/null +++ b/lisp/unit-testing.lisp @@ -0,0 +1,13 @@ +(in-package #:cl-user) +(defpackage #:my-test + (:documentation "Unit testing in Common Lisp.") + (:use #:cl)) +(in-package #:my-test) + +(plan 3) + +(ok (not (find 4 '(1 2 3)))) +(is 4 4) +(isnt 1 #\1) + +(finalize) -- cgit 1.4.1 From 95d03facb3af93323101737d76470f190e7ca2ae Mon Sep 17 00:00:00 2001 From: William Carroll Date: Fri, 24 Jan 2020 10:52:53 +0000 Subject: Support prelude.lisp Using this module to support utility functions that I have not classified further than being miscellaneous. --- lisp/main.lisp | 15 --------------- lisp/prelude.lisp | 14 ++++++++++++++ lisp/prelude.nix | 8 ++++++++ 3 files changed, 22 insertions(+), 15 deletions(-) delete mode 100644 lisp/main.lisp create mode 100644 lisp/prelude.lisp create mode 100644 lisp/prelude.nix diff --git a/lisp/main.lisp b/lisp/main.lisp deleted file mode 100644 index 2c4a5a411635..000000000000 --- a/lisp/main.lisp +++ /dev/null @@ -1,15 +0,0 @@ -(in-package #:cl-user) -(defpackage #:utils - (:documentation "Some utility functions and macros to wet my beak.") - (:use #:cl) - (:shadow #:type)) -(in-package #:utils) - -(defmacro type (name in out) - `(declaim (ftype (function ,in ,out) ,name))) - -(defmacro comment (&rest _forms) nil) - -(type add (int int) int) -(defun add (a b) - (+ a b)) diff --git a/lisp/prelude.lisp b/lisp/prelude.lisp new file mode 100644 index 000000000000..3522567ea0f7 --- /dev/null +++ b/lisp/prelude.lisp @@ -0,0 +1,14 @@ +(in-package #:cl-user) +(defpackage #:prelude + (:documentation "Supporting miscellaneous utility functions and macros.") + (:use #:cl) + (:shadow #:type) + (:export #:type #:comment)) +(in-package #:prelude) + +;; TODO: Add documentation to these macros. + +(defmacro type (name in out) + `(declaim (ftype (function ,in ,out) ,name))) + +(defmacro comment (&rest _forms) nil) diff --git a/lisp/prelude.nix b/lisp/prelude.nix new file mode 100644 index 000000000000..9051f82394ff --- /dev/null +++ b/lisp/prelude.nix @@ -0,0 +1,8 @@ +{ depot ? import {}, ... }: + +depot.nix.buildLisp.library { + name = "prelude"; + srcs = [ + ./prelude.lisp + ]; +} -- cgit 1.4.1 From e2e59c63e7899ea06e4273f0ee58a5842f6e3384 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Fri, 24 Jan 2020 10:53:37 +0000 Subject: Cleanup lisp directory Removing the default.nix that I used to attempt to build prove, a Common Lisp unit testing library. Also removing the lisp module with the unit tests themselves. --- lisp/default.nix | 16 ---------------- lisp/unit-testing.lisp | 13 ------------- 2 files changed, 29 deletions(-) delete mode 100644 lisp/default.nix delete mode 100644 lisp/unit-testing.lisp diff --git a/lisp/default.nix b/lisp/default.nix deleted file mode 100644 index 0969c7abf7e2..000000000000 --- a/lisp/default.nix +++ /dev/null @@ -1,16 +0,0 @@ -{ depot ? import {}, - universe ? import {}, - ... -}: - -depot.nix.buildLisp.program { - name = "unit-testing"; - - deps = with universe.third_party.lisp; [ - prove - ]; - - srcs = [ - ./unit-testing.lisp - ]; -} diff --git a/lisp/unit-testing.lisp b/lisp/unit-testing.lisp deleted file mode 100644 index c0b3be9b4515..000000000000 --- a/lisp/unit-testing.lisp +++ /dev/null @@ -1,13 +0,0 @@ -(in-package #:cl-user) -(defpackage #:my-test - (:documentation "Unit testing in Common Lisp.") - (:use #:cl)) -(in-package #:my-test) - -(plan 3) - -(ok (not (find 4 '(1 2 3)))) -(is 4 4) -(isnt 1 #\1) - -(finalize) -- cgit 1.4.1 From 143fc970322237963d5358ae832fd18ea045d67a Mon Sep 17 00:00:00 2001 From: William Carroll Date: Fri, 24 Jan 2020 10:55:04 +0000 Subject: Start working on f.lisp I would like to port the `f.el` library to Common Lisp. I'm adding a README, some build files, and the module itself to get started. --- lisp/f/README.md | 5 +++++ lisp/f/default.nix | 15 +++++++++++++++ lisp/f/main.lisp | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+) create mode 100644 lisp/f/README.md create mode 100644 lisp/f/default.nix create mode 100644 lisp/f/main.lisp diff --git a/lisp/f/README.md b/lisp/f/README.md new file mode 100644 index 000000000000..34e07180d492 --- /dev/null +++ b/lisp/f/README.md @@ -0,0 +1,5 @@ +# f.lisp + +In this project, I'm attempting to port the Elisp library [`f.el`][1] to Common Lisp. + +[1]: https://github.com/rejeep/f.el diff --git a/lisp/f/default.nix b/lisp/f/default.nix new file mode 100644 index 000000000000..6911b2102634 --- /dev/null +++ b/lisp/f/default.nix @@ -0,0 +1,15 @@ +{ + depot ? import {}, + universe ? import {}, + ... +}: + +depot.nix.buildLisp.library { + name = "f"; + deps = with universe.lisp; [ + prelude + ]; + srcs = [ + ./main.lisp + ]; +} diff --git a/lisp/f/main.lisp b/lisp/f/main.lisp new file mode 100644 index 000000000000..a51c38127815 --- /dev/null +++ b/lisp/f/main.lisp @@ -0,0 +1,48 @@ +(in-package #:cl-user) +(defpackage #:main + (:documentation "Modern API for working with files and directories.") + (:use #:cl) + (:shadow #:type)) +(in-package #:main) + +;; Common Lisp distinguishes between `namestrings` and `pathnames` as two types +;; of filename representations. +;; +;; A `pathname` is a structured representation of the name of a file, which +;; consists of six parts: +;; 1. host +;; 2. device +;; 3. directory +;; 4. name +;; 5. type +;; 6. version + +;; TODO: Should I be using `string` as a type or `namestring`? + +(defmacro type (name in out) + `(declaim (ftype (function ,in ,out) ,name))) + +(type join (&rest namestring) pathname) +(defun join (&rest args) + "Join ARGS to a single path." + (apply #'merge-pathnames args)) + +(type ext (pathname) string) +(defun ext (path) + "Return the file extension of PATH." + (pathname-type path)) + +;; TODO: Define these tests elsewhere. + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Tests +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +;; join +(string= (join "path") "path") +(string= (join "path" "to") "path/to") +(string= (join "/" "path" "to" "heaven") "/path/to/heaven") + +;; ext +(string= (ext #p"path/to/file.ext") "ext") +(string= (ext #p"path/to/directory") nil) -- cgit 1.4.1 From 5a2a1694e3790e97da5933ac66ff68fb3170c8f0 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Sat, 25 Jan 2020 19:39:49 +0000 Subject: Support java directory Add an example of two java files, Main.java and Another.java, where Main.java depends on Another.java. This is part of a larger example of attempting to use Nix to package these. Also ignoring the *.class files that `javac ` outputs. --- .gitignore | 1 + java/Another.java | 5 +++++ java/Main.java | 6 ++++++ 3 files changed, 12 insertions(+) create mode 100644 java/Another.java create mode 100644 java/Main.java diff --git a/.gitignore b/.gitignore index 51eb9644acd7..12ecf28fd42f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ # Python __pycache__ +*.class diff --git a/java/Another.java b/java/Another.java new file mode 100644 index 000000000000..e431e157336b --- /dev/null +++ b/java/Another.java @@ -0,0 +1,5 @@ +public class Another { + public static String whatis() { + return "Testing"; + } +} diff --git a/java/Main.java b/java/Main.java new file mode 100644 index 000000000000..0bfa9ebc2a34 --- /dev/null +++ b/java/Main.java @@ -0,0 +1,6 @@ +public class Main { + public static void main(String[] args) { + System.out.println(System.getProperty("java.class.path")); + System.out.println(Another.whatis()); + } +} -- cgit 1.4.1 From 130155121a2e67228f96aa557e0ac03c781146f9 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Sat, 25 Jan 2020 19:41:19 +0000 Subject: Sketch idea of a buildClojure nix function The previous commit that adds Java code is part of a larger project intended to use Nix to package Clojure. I'd like to build something similar to @tazjin's buildLisp except for Clojure instead of for Common Lisp. Once building for both ecosystems is similarly easy, it will be easier for me to compare the two languages. Right now `buildLisp` is so good that it attracts me to Common Lisp even when I don't know the language. --- clojure/buildClojure.nix | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 clojure/buildClojure.nix diff --git a/clojure/buildClojure.nix b/clojure/buildClojure.nix new file mode 100644 index 000000000000..1596279de1c5 --- /dev/null +++ b/clojure/buildClojure.nix @@ -0,0 +1,11 @@ +{ universe ? import {}, ... }: + +universe.nix.buildClojure.program { + name = "test"; + deps = with universe.third_party.clojure; [ + + ]; + srcs = [ + ./main.clj + ] +} -- cgit 1.4.1 From d67993266f33bed8ef8988bccaf77882f25e458e Mon Sep 17 00:00:00 2001 From: William Carroll Date: Sun, 26 Jan 2020 00:29:34 +0000 Subject: Add index.html with AdSense script to blog Adds some dummy markup, which includes my Google AdSense ` + + + +

Welcome

+

To my blog!

+ + -- cgit 1.4.1 From be0e05c6694a715beb242c16c91f890e5fbdc08e Mon Sep 17 00:00:00 2001 From: William Carroll Date: Mon, 27 Jan 2020 14:57:47 +0000 Subject: Prefer home variable rather than hard coded path to readTree TIL: Nix support the ~ as a home variable, which makes my code more portable since it's shared between my work laptop and work desktop. --- default.nix | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/default.nix b/default.nix index f4eda0d5ea26..ff4f40d8f943 100644 --- a/default.nix +++ b/default.nix @@ -13,7 +13,7 @@ let inherit pkgs; }; - readTree' = import /home/wpcarro/depot/nix/readTree {}; + readTree' = import ~/depot/nix/readTree {}; # TODO: Find a better way to expose entire monorepo without introducing # "infinite recursion". -- cgit 1.4.1 From 7d51b40e661e53ea1904fd1cf5ca407b1497e4f2 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Mon, 27 Jan 2020 16:02:57 +0000 Subject: Rename mono -> universe Prefer the name universe for my mono-repo. A bit grandiose, sure, but I prefer the metaphor to no metaphor at all. --- .envrc | 2 +- README.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.envrc b/.envrc index bf79e325a121..365d37dbcabf 100644 --- a/.envrc +++ b/.envrc @@ -1 +1 @@ -export NIX_PATH=nixpkgs=$HOME/.nix-defexpr/nixpkgs:depot=$HOME/depot:universe=$HOME/mono +export NIX_PATH=nixpkgs=$HOME/.nix-defexpr/nixpkgs:depot=$HOME/depot:universe=$HOME/universe diff --git a/README.md b/README.md index e7a01960b87c..4fa248554751 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Mono +# Universe This is my mono-repo. Having a personal mono-repo is a new idea for me, so at the time of this writing, the state of this repository is fledgling. -- cgit 1.4.1 From af26b6a1818cf9ee8b4fc9764201556f234202cd Mon Sep 17 00:00:00 2001 From: William Carroll Date: Mon, 27 Jan 2020 16:52:16 +0000 Subject: Partially packages package that I partially want to use A Poem: I wanted to try out this package... 30 minutes later after a dozen failed attempts at packaging it... I no longer want to try this package... for now. --- third_party/lisp/linear-programming.nix | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 third_party/lisp/linear-programming.nix diff --git a/third_party/lisp/linear-programming.nix b/third_party/lisp/linear-programming.nix new file mode 100644 index 000000000000..432fedb8bcac --- /dev/null +++ b/third_party/lisp/linear-programming.nix @@ -0,0 +1,26 @@ +{ depot ? import {}, ... }: + +let + src = builtins.fetchGit { + url = "https://github.com/neil-lindquist/linear-programming.git"; + rev = "8c8d55e7584773b90c4ba4b225c5f2008f4c474a"; + }; +in depot.nix.buildLisp.library { + name = "linear-programming"; + deps = [ + (depot.nix.buildLisp.bundled "uiop") + depot.third_party.lisp.iterate + depot.third_party.lisp.alexandria + ]; + srcs = [ + "${src}/src/conditions.lisp" + "${src}/src/expressions.lisp" + "${src}/src/simplex.lisp" + "${src}/src/system-info.lisp" + "${src}/src/utils.lisp" + "${src}/src/problem.lisp" + "${src}/src/solver.lisp" + "${src}/src/external-formats.lisp" + "${src}/src/all.lisp" + ]; +} -- cgit 1.4.1 From bacaa0ca8ad26b4f7ff592746d63d5c6b6c39ab0 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Tue, 28 Jan 2020 00:00:05 +0000 Subject: Add docker/cloud_run.nix I'm attempting to setup my blog using the following: - Google Cloud Run: I whitelist a docker image that packages my application and then Google runs it "statelessly" (i.e. without persistence). The stateless part should be fine for the time being. - Nix: Using `.dockerTools.buildLayeredImage` to output docker images from Nix expressions. - Docker: Upload the output image from the Nix expressions and upload it to Google Container Registry from which it can be run from Google Cloud Run. Some helpful commands: ```shell > sudo gcloud auth login > nix-build ./docker/cloud_run.nix > sudo docker image import ./result > sudo docker tag gcr.io//: > sudo docker push gcr.io//: ``` I'm unsure if Google Cloud Run is my desired end goal, but it may help me publish a blog faster than setting up a Kubernetes cluster, which is what I'd ultimately like to do. Cloud Run should be cheaper financially and time-wise. --- docker/cloud_run.nix | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 docker/cloud_run.nix diff --git a/docker/cloud_run.nix b/docker/cloud_run.nix new file mode 100644 index 000000000000..02440df08872 --- /dev/null +++ b/docker/cloud_run.nix @@ -0,0 +1,9 @@ +# Attempting to build a Docker image with Nix to run using Google Cloud Run. +{ pkgs ? import {}, ... }: + +pkgs.dockerTools.buildLayeredImage { + name = "mysql"; + tag = "latest"; + config.Cmd = [ "${pkgs.mysql}/bin/mysqld" ]; + maxLayers = 120; +} -- cgit 1.4.1 From ff06ffcf9cd8ad78f0dcc7df4e6bbfa684e73f1e Mon Sep 17 00:00:00 2001 From: William Carroll Date: Tue, 28 Jan 2020 16:47:35 +0000 Subject: Package depot's gemma as a docker image for Cloud Run Using 's gemma project with `dockerTools.buildLayeredImage` because I need access to a nix-packaged server and gemma is the first thing that comes to mind. --- docker/cloud_run.nix | 17 +++++++++++++---- docker/config.lisp | 21 +++++++++++++++++++++ 2 files changed, 34 insertions(+), 4 deletions(-) create mode 100644 docker/config.lisp diff --git a/docker/cloud_run.nix b/docker/cloud_run.nix index 02440df08872..70be4040c36b 100644 --- a/docker/cloud_run.nix +++ b/docker/cloud_run.nix @@ -1,9 +1,18 @@ -# Attempting to build a Docker image with Nix to run using Google Cloud Run. -{ pkgs ? import {}, ... }: +{ + pkgs ? import {}, + depot ? import {}, + ... +}: pkgs.dockerTools.buildLayeredImage { - name = "mysql"; + name = "gemma"; tag = "latest"; - config.Cmd = [ "${pkgs.mysql}/bin/mysqld" ]; + config.ExposedPorts = { + "4242" = {}; + }; + config.Env = [ + "GEMMA_CONFIG=${./config.lisp}" + ]; + config.Cmd = [ "${depot.fun.gemma}/bin/gemma" ]; maxLayers = 120; } diff --git a/docker/config.lisp b/docker/config.lisp new file mode 100644 index 000000000000..54f8e5f34462 --- /dev/null +++ b/docker/config.lisp @@ -0,0 +1,21 @@ +;; Example configuration file for Gemma + +(config :port 4242 + :data-dir "/tmp/gemma/") + +(deftask bathroom/wipe-mirror 7) +(deftask bathroom/wipe-counter 7) + +;; Bedroom tasks +(deftask bedroom/change-sheets 7) +(deftask bedroom/vacuum 10) + +;; Kitchen tasks +(deftask kitchen/normal-trash 3) +(deftask kitchen/green-trash 5) +(deftask kitchen/blue-trash 5) +(deftask kitchen/wipe-counters 3) +(deftask kitchen/vacuum 5 "Kitchen has more crumbs and such!") + +;; Entire place +(deftask clean-windows 60) -- cgit 1.4.1 From 8ad51b24dd8719840aac47134835ea25cfe1b0b8 Mon Sep 17 00:00:00 2001 From: William Carroll Date: Tue, 28 Jan 2020 16:48:28 +0000 Subject: Document current deployment tactics Adding a README including my current method for deploying. See the README for more details. All of this is quite virgin and as such is subject to change. --- docker/README.md | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 docker/README.md diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 000000000000..34f762cc1970 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,59 @@ +# Deployments + +I'm documenting how I currently deploy things. + +I'd like to automate this workflow as much as possible, and I intend to do just +that. For now, I'm running things manually until I can design an generalization +that appeals to me. + +## Dependencies +- `nix-build` +- `docker` +- `gcloud` + +## Step-by-step + +1. Use `nix-build` to create our Docker image for Cloud Run. + +```shell +> nix-build ./cloud_run.nix +``` + +This outputs a Docker image at `./result`. + +1. Load the built image (i.e. `./result`) into `docker` so that we can tag it + and push it to the Google Container Registry (i.e. GCR). + +```shell +> sudo docker load <./result +``` + +1. (Optionally) Run the image locally to verify its integrity. + +```shell +> sudo docker run -d : +``` + +1. Tag and push the image to GCR. + +```shell +> sudo docker tag :