From 77731980bf9b9f919a31f6dc1c15cd8ecea11e1e Mon Sep 17 00:00:00 2001 From: daviddoji Date: Mon, 29 May 2023 18:29:05 +0200 Subject: [PATCH] Solution to Problem 14 in Python --- src/Year_2017/P14.py | 161 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 161 insertions(+) create mode 100644 src/Year_2017/P14.py diff --git a/src/Year_2017/P14.py b/src/Year_2017/P14.py new file mode 100644 index 0000000..b7eb4c4 --- /dev/null +++ b/src/Year_2017/P14.py @@ -0,0 +1,161 @@ +# --- Day 14: Disk Defragmentation --- + +# Suddenly, a scheduled job activates the system's disk defragmenter. Were the +# situation different, you might sit and watch it for a while, but today, you +# just don't have that kind of time. It's soaking up valuable system resources +# that are needed elsewhere, and so the only option is to help it finish its +# task as soon as possible. + +# The disk in question consists of a 128x128 grid; each square of the grid is +# either free or used. On this disk, the state of the grid is tracked by the +# bits in a sequence of knot hashes. + +# A total of 128 knot hashes are calculated, each corresponding to a single row +# in the grid; each hash contains 128 bits which correspond to individual grid +# squares. Each bit of a hash indicates whether that square is free (0) or used +# (1). + +# The hash inputs are a key string (your puzzle input), a dash, and a number +# from 0 to 127 corresponding to the row. For example, if your key string were +# flqrgnkx, then the first row would be given by the bits of the knot hash of +# flqrgnkx-0, the second row from the bits of the knot hash of flqrgnkx-1, and +# so on until the last row, flqrgnkx-127. + +# The output of a knot hash is traditionally represented by 32 hexadecimal +# digits; each of these digits correspond to 4 bits, for a total of 4 * 32 = 128 +# bits. To convert to bits, turn each hexadecimal digit to its equivalent binary +# value, high-bit first: 0 becomes 0000, 1 becomes 0001, e becomes 1110, f +# becomes 1111, and so on; a hash that begins with a0c2017... in hexadecimal +# would begin with 10100000110000100000000101110000... in binary. + +# Continuing this process, the first 8 rows and columns for key flqrgnkx appear +# as follows, using # to denote used squares, and . to denote free ones: + +# ##.#.#..--> +# .#.#.#.# +# ....#.#. +# #.#.##.# +# .##.#... +# ##..#..# +# .#...#.. +# ##.#.##.--> +# | | +# V V + +# In this example, 8108 squares are used across the entire 128x128 grid. + +# Given your actual key string, how many squares are used? + +# Your puzzle input is jxqlasbh. + +from collections import deque + +input = "jxqlasbh" + + +# from P10 part2 +def simulated_hash(circle, sequence, curr_pos, skip): + for length in sequence: + circle.rotate(-curr_pos) + # creates a copy of the list + rotated_l = list(circle) + # reverse the order of the partial list + rotated_l[:length] = reversed(rotated_l[:length]) + # as the list is circular, it wraps around when it has to + circle = deque(rotated_l) + circle.rotate(curr_pos) + # move forward the current position + curr_pos = (curr_pos + length + skip) % 256 + skip += 1 + return circle, curr_pos, skip + + +def knothash(inp): + ascii_lengths = [ord(c) for c in inp] + ascii_lengths.extend([17, 31, 73, 47, 23]) + list_of_numbers = deque(list(range(256))) + curr_pos, skip = 0, 0 + for _ in range(64): + list_of_numbers, curr_pos, skip = simulated_hash( + list_of_numbers, ascii_lengths, curr_pos, skip + ) + + sparse = list(list_of_numbers) + dense = [] + + for block in range(0, 256, 16): + hashed = 0 + group = sparse[block : block + 16] + for n in group: + hashed ^= n + dense.append(hashed) + + return "".join(f"{n:02x}" for n in dense) + + +def part_1() -> None: + used = 0 + for i in range(128): + hash = knothash(input + "-" + str(i)) + bin_hash = bin(int(hash, 16))[2:].zfill(128) + used += bin_hash.count("1") + print(f"There are {used} squares used") + + +# --- Part Two --- + +# Now, all the defragmenter needs to know is the number of regions. A region is +# a group of used squares that are all adjacent, not including diagonals. Every +# used square is in exactly one region: lone used squares form their own +# isolated regions, while several adjacent squares all count as a single region. + +# In the example above, the following nine regions are visible, each marked with +# a distinct digit: + +# 11.2.3..--> +# .1.2.3.4 +# ....5.6. +# 7.8.55.9 +# .88.5... +# 88..5..8 +# .8...8.. +# 88.8.88.--> +# | | +# V V + +# Of particular interest is the region marked 8; while it does not appear +# contiguous in this small view, all of the squares marked 8 are connected when +# considering the whole 128x128 grid. In total, in this example, 1242 regions +# are present. + + +# How many regions are present given your key string? + + +def part_2() -> None: + grid = set() + for i in range(128): + hash = knothash(input + "-" + str(i)) + bin_hash = bin(int(hash, 16))[2:].zfill(128) + for col, val in enumerate(bin_hash): + if val == "1": + grid.add((i, col)) + + regions = 0 + while grid: + queued = [ + (next(iter(grid))), + ] + while queued: + (x, y) = queued.pop(0) + if (x, y) in grid: + grid.remove((x, y)) + queued += (x - 1, y), (x + 1, y), (x, y + 1), (x, y - 1) + regions += 1 + + print(f"There are {regions} regions") + + +if __name__ == "__main__": + part_1() + part_2()