Solution to Problem 14 in Python
This commit is contained in:
parent
e79ebef6af
commit
77731980bf
161
src/Year_2017/P14.py
Normal file
161
src/Year_2017/P14.py
Normal file
@ -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()
|
Loading…
Reference in New Issue
Block a user