From a6491289a762a2d73aad7c84883b9111838c4593 Mon Sep 17 00:00:00 2001 From: Dece Date: Sun, 29 Dec 2019 02:33:09 +0100 Subject: [PATCH] Day 22 part 2: we're done! --- 2019/day22.py | 227 ++++++++++++++++---------------------------------- 1 file changed, 71 insertions(+), 156 deletions(-) diff --git a/2019/day22.py b/2019/day22.py index 890d53d..3777803 100644 --- a/2019/day22.py +++ b/2019/day22.py @@ -1,168 +1,83 @@ -EX1 = [ - "deal with increment 7", - "deal into new stack", - "deal into new stack", -] -EX2 = [ - "cut 6", - "deal with increment 7", - "deal into new stack", -] -EX3 = [ - "deal with increment 7", - "deal with increment 9", - "cut -2", -] -EX4 = [ - "deal into new stack", - "cut -2", - "deal with increment 7", - "cut 8", - "cut -4", - "deal with increment 7", - "cut 3", - "deal with increment 9", - "deal with increment 3", - "cut -1", -] +""" +Disclaimer: I could not solve part 2 on my own and had to look at some tips +abour modular arithmetic. Gotta gid gud at discrete maths... What I've +understood to get it to work in just a few milliseconds is commented below. + +Dumb part 1 is somewhere in Git history. +""" + def main(): with open("day22.txt") as input_file: lines = [l.rstrip() for l in input_file.readlines()] # Part 1 - deck = list(range(10)) - # deck = list(range(10007)) - deck = part1(deck, EX3) - print(deck) - # deck = part1(deck, lines) - # print(f"Position of card 2019: {deck.index(2019)}.") + deck_len = 10007 + o, i = shuffle(deck_len, lines) + print(o, i) + for n in range(deck_len): + if (o + i * n) % deck_len == 2019: + break + print(f"Position of card 2019: {n}.") # Part 2 - part2(10, 1, EX3, pos=0); input() - part2(10, 1, EX3, pos=1); input() - part2(10, 1, EX3, pos=2); input() - part2(10, 1, EX3, pos=3); input() - part2(10, 1, EX3, pos=4); input() - part2(10, 1, EX3, pos=5); input() - part2(10, 1, EX3, pos=6); input() - part2(10, 1, EX3, pos=7); input() - part2(10, 1, EX3, pos=8); input() - part2(10, 1, EX3, pos=9); input() - # part2(10007, 1, lines, pos=8191) - # part2(119315717514047, 101741582076661, lines) - # print(f"2020th card: {deck[2020]}.") - -def part1(deck, commands): - for command in commands: - # Deal into new stack. - if command.endswith("k"): - deck.reverse() - continue - arg = int(command[command.rfind(" "):]) - # Deal with increment. - if command[0] == "d": - new_deck = [None] * len(deck) - for i in range(len(deck)): - new_deck[(i * arg) % len(new_deck)] = deck[i] - deck = new_deck - # Cut. - elif command[0] == "c": - cut, deck = deck[:arg], deck[arg:] - deck += cut - return deck - -def part2(deck_len, num_shuf, commands, pos=2020): - required = find_required(deck_len, commands, pos) - for c, r in zip(commands, required): - print(f"- Command '{c}' requires knowing value at {r[0]} for {r[1]}.") - print(f"Which gives us value at {pos}.") - deck = {i: i for r in required for i in r} - print(required) - print("Shuffling...") - shuffle(deck, required) - print(f"Card at {pos}: {deck[pos]}.") - -def find_required(deck_len, commands, pos): - required = [] - for command in reversed(commands): - dest = pos - if command.endswith("k"): - pos = req_dins(pos, deck_len) - required.append((pos, dest)) + deck_len = 119315717514047 + num_shuf = 101741582076661 + o, i = shuffle(deck_len, lines) + # Each shuffle makes increment a multiple of its previous value % deck_len, + # and new offset is the old offset incremented by this value. + # + # Using the offset and increment of the first shuffle (O1, I1), we can write + # a function for n shuffles as they will use those same values: + # On = On-1 + (In * O1) % deck_len + # In = In-1 * I1 % deck_len + # + # We observe that the increment for n shuffles is I1^n-1 % deck_len. + # + # We observe that the offset is dependent of each previous increment value: + # O0 = 0 + # O1 = k + # O2 = k + I2*k (or: O1 + I2*O1) + # O3 = k + I2*O1 + I3*O2 (or: O2 + I3*O2) + # and this can be factorised as: + # On = k * (1 + I1 + I2 + ... + In-1) % deck_len + # This expression corresponds to a geometric series, which has a formula to + # calculate its n-point value, here for our offset function: + # On = O1 (1 - I1^n) (1 - I1)^n + inc_n = pow(i, num_shuf, deck_len) + ofs_n_op1 = 1 - pow(i, num_shuf, deck_len) + ofs_n_op2 = pow(1 - i, deck_len - 2, deck_len) + ofs_n = (o * ofs_n_op1 * ofs_n_op2) % deck_len + print(f"Card at pos 2020: {(ofs_n + inc_n * 2020) % deck_len}") + +def shuffle(deck_len, raw_commands): + commands = parse_commands(raw_commands) + offset, inc = 0, 1 + for c, a in commands: + if c == "dins": + inc = -inc + offset += inc + elif c == "cut": + offset += inc * a + elif c == "dwi": + inc *= pow(a, deck_len - 2, deck_len) # Fermat's lil theorem. + offset %= deck_len + inc %= deck_len + return offset, inc + +def parse_commands(raw_commands): + commands = [] + for rc in raw_commands: + if rc.endswith("k"): + commands.append(("dins", 0)) continue - arg = int(command[command.rfind(" "):]) - if command[0] == "d": - print(f"What's req for {pos} in deal with increment {arg}?") - pos = req_dwi(pos, deck_len, arg) - print(f"- answer: {pos}.") - elif command[0] == "c": - pos = req_cut(pos, deck_len, arg) - required.append((pos, dest)) - required.reverse() - return required - -def req_dins(pos, deck_len): - return deck_len - 1 - pos - -def req_cut(pos, deck_len, n): - return (pos + n) % deck_len - -def req_dwi(pos, deck_len, inc): - # Straight is (i * arg) % deck_len = j - # (i * arg) = modinv(j, deck_len) - # i = modinv(j, deck_len) / arg - # damn idk - return (pos * inc - deck_len) % deck_len - -def shuffle(deck, required): - for required_pair in required: - from_pos, to_pos = required_pair - deck[to_pos] = deck[from_pos] - -def dins(parameter_list): - pass - -def cut(parameter_list): - pass - -def dwi(parameter_list): - pass + arg = int(rc[rc.rfind(" "):]) + if rc[0] == "d": + commands.append(("dwi", arg)) + elif rc[0] == "c": + commands.append(("cut", arg)) + return commands if __name__ == "__main__": - # main() - - assert req_cut(0, 10, 3) == 3 - assert req_cut(1, 10, 3) == 4 - assert req_cut(2, 10, 3) == 5 - assert req_cut(5, 10, 3) == 8 - assert req_cut(8, 10, 3) == 1 - assert req_cut(0, 10, -4) == 6 - assert req_cut(0, 10, -4) == 6 - assert req_cut(1, 10, -4) == 7 - assert req_cut(2, 10, -4) == 8 - assert req_cut(3, 10, -4) == 9 - assert req_cut(4, 10, -4) == 0 - assert req_cut(5, 10, -4) == 1 - assert req_cut(6, 10, -4) == 2 - assert req_cut(7, 10, -4) == 3 - assert req_cut(8, 10, -4) == 4 - assert req_cut(9, 10, -4) == 5 - - assert req_dins(0, 10) == 9 - assert req_dins(1, 10) == 8 - assert req_dins(2, 10) == 7 - assert req_dins(8, 10) == 1 - assert req_dins(9, 10) == 0 - - assert req_dwi(0, 10, 3) == 0 - assert req_dwi(1, 10, 3) == 7 - assert req_dwi(2, 10, 3) == 4 - assert req_dwi(3, 10, 3) == 1 - assert req_dwi(4, 10, 3) == 8 - assert req_dwi(5, 10, 3) == 5 - assert req_dwi(6, 10, 3) == 2 - assert req_dwi(7, 10, 3) == 9 - assert req_dwi(8, 10, 3) == 6 - assert req_dwi(9, 10, 3) == 3 + main()