···11+import fileinput
22+import itertools
33+import functools
44+import multiprocessing
55+66+77+def mix(n, secret):
88+ return n ^ secret
99+1010+def prune(secret):
1111+ return secret % 16777216
1212+1313+@functools.lru_cache(None)
1414+def advance(n):
1515+ a = n * 64
1616+ n = mix(n, a)
1717+ n = prune(n)
1818+1919+ a = int(n / 32)
2020+ n = mix(n, a)
2121+ n = prune(n)
2222+2323+ a = n * 2048
2424+ n = mix(n, a)
2525+ n = prune(n)
2626+2727+ return n
2828+2929+3030+def possible_windows():
3131+ for a, b, c, d in itertools.product(list(range(-9, 10)), repeat=4):
3232+ yield (a, b, c, d)
3333+3434+3535+def gen_windows(n):
3636+ """Returns the 2000th secret number and all sliding delta windows."""
3737+ windows = {}
3838+ deltas = []
3939+ last = n
4040+ for i in range(2000 - 1):
4141+ n = advance(n)
4242+ delta = (n % 10) - (last % 10)
4343+ deltas.append(delta)
4444+ last = n
4545+4646+ if len(deltas) >= 4:
4747+ w = tuple(deltas[-4:])
4848+ if w not in windows:
4949+ windows[w] = n % 10
5050+5151+ return advance(n), windows
5252+5353+5454+def bananas_for_window(window):
5555+ bananas = 0
5656+ for b in BUYERS:
5757+ bananas += ALL_WINDOWS[b].get(window, 0)
5858+5959+ return bananas
6060+6161+6262+# Read problem input and solve part 1.
6363+part_1 = 0
6464+BUYERS = []
6565+ALL_WINDOWS = {}
6666+6767+for line in fileinput.input():
6868+ buyer = int(line)
6969+ BUYERS.append(buyer)
7070+ secret_2000, windows = gen_windows(buyer)
7171+ part_1 += secret_2000
7272+ ALL_WINDOWS[buyer] = windows
7373+7474+# Solve part 2.
7575+MULTITHREAD = True
7676+NUM_THREADS = multiprocessing.cpu_count()
7777+7878+if MULTITHREAD:
7979+ with multiprocessing.Pool(processes=NUM_THREADS) as pool:
8080+ results = pool.map(bananas_for_window, possible_windows())
8181+8282+else:
8383+ results = []
8484+ for window in possible_windows():
8585+ bananas = bananas_for_window(window)
8686+ results.append(bananas)
8787+8888+print("Part 1:", part_1)
8989+print("Part 2:", max(results))