|
| 1 | +"""Everyone Codes Day N.""" |
| 2 | + |
| 3 | +import collections |
| 4 | +import functools |
| 5 | +from lib import parsers |
| 6 | + |
| 7 | + |
| 8 | +def solve(part: int, data: str) -> int: |
| 9 | + """Solve the parts.""" |
| 10 | + rev_pairs = collections.defaultdict(set) |
| 11 | + pairs = collections.defaultdict(set) |
| 12 | + for line in data.split("\n\n")[1].splitlines(): |
| 13 | + a, b = line.split(" > ") |
| 14 | + for c in b.split(","): |
| 15 | + pairs[a].add(c) |
| 16 | + rev_pairs[c].add(a) |
| 17 | + |
| 18 | + @functools.cache |
| 19 | + def possibilities(size: int, letter: str) -> int: |
| 20 | + if size == 11: |
| 21 | + return 1 |
| 22 | + count = 0 if size < 7 else 1 |
| 23 | + size += 1 |
| 24 | + return count + sum(possibilities(size, i) for i in pairs[letter]) |
| 25 | + |
| 26 | + total = 0 |
| 27 | + names = data.split("\n\n")[0].split(",") |
| 28 | + if part == 3: |
| 29 | + names = set(names) |
| 30 | + names = {n for n in names if not any(n != m and n.startswith(m) for m in names)} |
| 31 | + |
| 32 | + for idx, name in enumerate(names, start=1): |
| 33 | + for a, b in zip(name, name[1:]): |
| 34 | + if a not in rev_pairs[b]: |
| 35 | + break |
| 36 | + else: |
| 37 | + if part == 1: |
| 38 | + return name |
| 39 | + if part == 2: |
| 40 | + total += idx |
| 41 | + else: |
| 42 | + total += possibilities(len(name), name[-1]) |
| 43 | + return total |
| 44 | + |
| 45 | + |
| 46 | +TEST_DATA = [ |
| 47 | + """\ |
| 48 | +Oronris,Urakris,Oroneth,Uraketh |
| 49 | +
|
| 50 | +r > a,i,o |
| 51 | +i > p,w |
| 52 | +n > e,r |
| 53 | +o > n,m |
| 54 | +k > f,r |
| 55 | +a > k |
| 56 | +U > r |
| 57 | +e > t |
| 58 | +O > r |
| 59 | +t > h""", |
| 60 | + """\ |
| 61 | +Xanverax,Khargyth,Nexzeth,Helther,Braerex,Tirgryph,Kharverax |
| 62 | +
|
| 63 | +r > v,e,a,g,y |
| 64 | +a > e,v,x,r |
| 65 | +e > r,x,v,t |
| 66 | +h > a,e,v |
| 67 | +g > r,y |
| 68 | +y > p,t |
| 69 | +i > v,r |
| 70 | +K > h |
| 71 | +v > e |
| 72 | +B > r |
| 73 | +t > h |
| 74 | +N > e |
| 75 | +p > h |
| 76 | +H > e |
| 77 | +l > t |
| 78 | +z > e |
| 79 | +X > a |
| 80 | +n > v |
| 81 | +x > z |
| 82 | +T > i""", |
| 83 | + """\ |
| 84 | +Xaryt |
| 85 | +
|
| 86 | +X > a,o |
| 87 | +a > r,t |
| 88 | +r > y,e,a |
| 89 | +h > a,e,v |
| 90 | +t > h |
| 91 | +v > e |
| 92 | +y > p,t""", |
| 93 | + """\ |
| 94 | +Khara,Xaryt,Noxer,Kharax |
| 95 | +
|
| 96 | +r > v,e,a,g,y |
| 97 | +a > e,v,x,r,g |
| 98 | +e > r,x,v,t |
| 99 | +h > a,e,v |
| 100 | +g > r,y |
| 101 | +y > p,t |
| 102 | +i > v,r |
| 103 | +K > h |
| 104 | +v > e |
| 105 | +B > r |
| 106 | +t > h |
| 107 | +N > e |
| 108 | +p > h |
| 109 | +H > e |
| 110 | +l > t |
| 111 | +z > e |
| 112 | +X > a |
| 113 | +n > v |
| 114 | +x > z |
| 115 | +T > i""", |
| 116 | +] |
| 117 | +TESTS = [ |
| 118 | + (1, TEST_DATA[0], "Oroneth"), |
| 119 | + (2, TEST_DATA[1], 23), |
| 120 | + (3, TEST_DATA[2], 25), |
| 121 | + (3, TEST_DATA[3], 1154), |
| 122 | +] |
| 123 | + |
| 124 | +if __name__ == "__main__": |
| 125 | + for _part, _data, expected in TESTS: |
| 126 | + assert solve(_part, PARSER.parse(_data)) == expected |
| 127 | + print("Tests pass.") |
| 128 | + day = __file__.split("_", maxsplit=-1)[-1].split(".")[0] |
| 129 | + for _part in range(1, 4): |
| 130 | + with open(f"inputs/{day}.{_part}.txt", encoding="utf-8") as f: |
| 131 | + print(_part, solve(_part, PARSER.parse(f.read()))) |
0 commit comments