Compare commits

..

58 commits

Author SHA1 Message Date
81c4073f32 Solve 2017:25 "The Halting Problem" 2025-05-06 23:53:56 +02:00
8b6d89fec5 Solve 2017:24 "Electromagnetic Moat" 2025-05-06 23:06:16 +02:00
4b1fa6bd65 Solve 2017:23 "Coprocessor Conflagration" 2025-05-06 20:15:16 +02:00
5b754381b0 Format files 2025-05-06 20:14:58 +02:00
fcbeb4bfdd Solve 2024:24 pt2 "Code Chronicle"
To solve this, it was easier to visualize the
graph and spot errors. This code is kept for
reference.

To learn the mechanics, These subreddit threads
are helpful:

- https://www.reddit.com/r/adventofcode/comments/1hla5ql/2024_day_24_part_2_a_guide_on_the_idea_behind_the/
- https://www.reddit.com/r/adventofcode/comments/1hneuf0/2024_day_24_part_2_finally_solved_it/
2025-01-05 00:06:18 +01:00
d7d5312786 Solve 2024:21 pt2 "Keypad Conundrum"
Turns out the initial trial of using
Manhattan distance was required to
solve part 2, since the BFS
implementation is impossible to
scale up from 2 robots to 25.

Recursion and memoization makes the
execution time acceptable.

Line 53 makes all the difference here.
The order of `<|v|^|>` matters when
constructing a sequence. Many
hours was spent trying to find the correct
priority. For the example input, especially
456A and 379A were volatile.
2025-01-05 00:06:18 +01:00
3a9a7d4994 Solve 2024:21 p1 "Keypad Conundrum"
Early versions of the code used Manhattan distance
instead of BFS to find the movements. Turns out this
was covering about 80% of all cases.
2025-01-05 00:06:18 +01:00
72574e753f Solve 2024:24 pt1 "Crossed Wires"
No idea how to solve pt2, so it will rest for now.
2025-01-05 00:06:18 +01:00
1b7b30e296 Solve 2024:25 pt 1 "Code Chronicle"
Merry XMAS!
2025-01-05 00:06:18 +01:00
2c2379788d Solve 2024:23 p1-2 "LAN Party"
Part 1 uses the excellent itertools.combinations().

Part 2 introduces Bron-Kerbosch algorithm to the
util library!
2025-01-05 00:06:18 +01:00
096c3d3e5d Solve 2024:22 p1-2 "Monkey Market"
Part 1 was relatively easy, 2 was harder.

> So, by asking the monkey to sell the *first time*
> each buyer's prices go down 2, then up 1, then
> down 1, then up 3, you would get 23 (7 + 7 + 9)
> bananas!

Emphazis mine. Earlier versions of the code missed
this fact, and added all occourences of the sequence.
providing a answer that is too high.
2025-01-05 00:06:18 +01:00
0500ac41ab Solve 2024:20 p1-2 "Race Condition"
BFS to get the path from start to end.

pt 1 took some time to wrap around, and initial
versions of the code used defaultdict() to keep
counts of all cheats to check if the example
puzzle input gace the right counts for the cheats.

But since only 100 or more was requested, a counter
do the job in the final version of the code.

For pt 2, a permutation is used.
2025-01-05 00:06:18 +01:00
14520a4625 Solve 2024:17 p1-2 "Chronospatial Computer"
Slow burner.

First part was a straightforward implementation
of a runner, the code is extracted to `org_version()`.

Part 2 was way more tricky. Like many others, trying
to understand the program felt necessary. This
intepretation is this:

    """Python version of puzzle input program."""
    a = <unknown>
    b, c = 0, 0
    while a:
        b = (a % 8) ^ 1
        c = a // (2**b)
        b = (b ^ 5) ^ c
        a = a // (2**3)
        print(b % 8)

Some clarification here:

- the value is octagonal. 1-8 is the key.
- The value of reg a is _high_. It is not meant to
be brute forced.

The idea, which is not originally mine, is to go
from right to left. This code can be used to try
out some patterns:

    while True:
        python_version(input("Provide A:"))

Here, it was apparent a=4 gives the last digit of
my puzzle input. a=37 (4 * 8 + 3) gives the last
2 digits. a=222 (37 * 8 + 6) gives the last 3
digits, and so on.

Knowing the program could be reconstructed like
this, the first code halted at wrong values. Turns
out some steps give more than 1 possible scenario.

The code was therefore in need of a queue.
2025-01-05 00:06:18 +01:00
ded5c4f28c Solve 2024:19 p1-2 "Linen Layout"
Initial tries to use a while loop instead of
recursion gave a classic AoC situation: test cases
worked, but not actual AoC puzzle input.

Turns out I only considererd removing the longest
pattern form the design, rather than consider all
possible removals.

Some Python goodies in here:

- `"abcdefgh".startswith("abc")` instead of regexp.
- removeprefix() is nice, and in some cases more
readable. `"abcdefgh".removeprefix("abc")` vs
`"abcdefgh[3:]"
- To speed things up for pt 2, functools is used
which requires a dict to be hashed as a tuple.

If one wish to solve this without recursion, a
BFS solution is most likely the way to go.

def ispossible(design, patterns):
    Q = [design]
    possible = 0
    while Q:
        remaining = Q.pop(0)
        if not remaining:
            possible += 1
            continue
        for pattern in patterns[remaining[0]]:
            if remaining.startswith(pattern):
                Q.append(remaining.removeprefix(pattern))
    return possible
2025-01-05 00:06:18 +01:00
9dc3f16766 Solve 2024:18 p1-2 "RAM Run"
BFS baby.

FIRST_BLOCKED was found manually using code from pt 1
to perform a binary search.
2025-01-05 00:06:18 +01:00
f5c3ee938a Refactor 2024:15
Solution is still 100 lines, but now it is based
on a singular BFS oriented box push algorithm.
2025-01-05 00:06:18 +01:00
7688ddb763 Solve 2024:15 p2 "Warehouse Woes"
Glad this one is over. 150 lines of pure mayhem.
2025-01-05 00:06:18 +01:00
7c2b4c835a Solve 2024:16 pt2 "Reindeer Maze"
Part 1 was easily solved by Dijkstras (using heapq).
Tricky part was to find the way to serialize the
queue items for the heappush and heappop to behave
as required.

Part 2 was incredible hard to figure out, and I
could not do it by myself. From the subreddit, it
is hinted that a traditional set of visited nodes
to determine algorithm exit would not work.

After some laboration and readin some hints, I
managed to find a solution where the code keeps
tracks using a defaultdict.
2025-01-05 00:06:18 +01:00
fbe6994e3a Solve 2024:16 pt1 "Reindeer Maze" 2025-01-05 00:06:18 +01:00
2af9dc566a Solve 2024:15 p1 "Warehouse Woes" 2025-01-05 00:06:18 +01:00
e6307795a4 Solve 2024:14 p1-2 "Restroom Redoubt"
After working overtime to solve day 12, this brief
task was a much welcome change.

For pt 1, the code uses integer division and
modulus to update all positions. The tricky part
was to determine which quadrant got the middle
column and row (since dimensions was odd numbers,
and either left or right quandrants is +1 longer),
which was straightforward to verify by the test
cases.

For pt 2, the remains of the code used to visually
identify the tree is left under the `find_easter_egg`
toggle.

Basically, the code prints the grid with
all robots marked as a `#`, and free space as `.`.`

This wording of the puzzle is important:

> very rarely, _most of the robots_ should arrange
> themselves into a picture of a Christmas tree.

"most of the robots" means the tree will be quite
visible, which also means it is safe to asume a
significant cluster of "#" would appear over time.

By keeping track of the counter (seconds the robots
has traveled), it was evident that the cluster of `#`
occoured th first time at i=64 and every 103th
time forward.

In other words, `i % 103 == 64` will most likely
give a tree. The print statement is therefore
limited to those i's, and at `i == easter_egg_appearance`
the tree is visible.

So, to be extra clear: 64, 103 and pt2 answer is
unique to my puzzle input. If this code is used
with any other puzzle input, these numbers will
most likely vary.

For the fun, the code also contains the `display_easter_egg`
flag to actually print the tree. This is provided
to indicate how big the actual tree is: 33*36 chars.

Also, the `sints` utility function was added to
extract all signed ints from a string.
2025-01-05 00:06:18 +01:00
9a7a9c878b Solve 2024:12 p2 "Garden Groups"
Funny that original 2023 day 5 also was a PITA
to figure out.

Pt 1 was solved using BFS to flood-fill. After
trying some different methods for pt 2, including:

- wallcrawling,
- side couting,
- corner counting

I never produced code to get past the test cases.

- Wall crawling are hard due to overlapping
regions.
- corner couting and side counting are both hard,
but will act as equally good solutions (since side
count equals corner count).
- Concave corners are hard, convex corners are
easy.

The final code is based on the posts on the
solutions megathread. Changes:

- Keep all areas in a set, defining a region.
- find all convex and concave corners in each
region.

A new helper got introduced: Di, storing all
diagonal neighbors for grid traversing.

Convex corners:

..  R.  .R  ..
R.  ..  ..  .R

Concave corners:

RR  .R  R.  RR
.R  RR  RR  R.
2025-01-05 00:06:18 +01:00
1e807b5daf Solve 2024:13 p1-2 "Claw Contraption"
Initial version of the code tried to solve pt 1
using BFS, which took way too long even for the
test data.

After some fiddling with algebra with pen and paper,
I realized this 2 formulaes (using first example):

94 * b1 + 22 * b2 == 840
34 * b1 + 67 * b2 == 540

*b1 = button A presses
*b2 = button B presses

... could be rewritten to this single expression:

(94 + 34) * b1 + (22 + 67) * b2 = 840 * 540

I failed to remember the algebra for solving x than
y though, that I had to learn from the subreddit.
In the code, this is the ratio part.

Also, this solution using fractions is SICK.
https://www.reddit.com/r/adventofcode/comments/1hd4wda/comment/m1tz3nf/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
2025-01-05 00:06:18 +01:00
19aee1363a Solve 2024:12 p1 "Garden Groups"
Loooong time overdue. First drafts of the code
kept track of every id separately in a defaultdict.

This was not sustainable since the code examples
had some recurring ids, and after managing to
support the test cases the code was eay off for
the real puzzle input.

In the end, the whole code was deleted and replaced
by a BFS solution that expands the current regions
until it finds no more areas.

Got the trick to count all perimiters in an else clause
from a leaderboard placed youtuber.
2025-01-05 00:06:18 +01:00
0eb961d31c Solve 2024:11 p1-2 "Plutonian Pebbles"
First part was solved using a list to store stones,
since I misinterpreted this paragraph:

> No matter how the stones change, their order is
> preserved, and they stay on their perfectly
> straight line.

But since the puzzle question do not require the
stones in the correct order, a list (or an OrderedDict)
is not required.

The pt 1 solution code got killed for eating all
RAM, so valuable minutes was lost trying to create
a generator instead. It failed. When searching for
clues, I ran across multiple people instead
utilizing dicts to count occourences. "but what of
the order?", I asked, and realized only the counts
was relevant.
2025-01-05 00:06:18 +01:00
0a812572db Solve 2024:10 p1-2 "Hoof It"
A bug in a draft version of the code accidentically
gave the answer to part 2. That's a first! :D
2025-01-05 00:06:18 +01:00
1b6502df7a Solve 2024:9 p1-2 "Disk Fragmenter"
Hard one. Comparing to earlier years, the
difficulty level seems higher than usual. I blame
the LLM hipsters.

This one I asumed, but missed when I tried to
find it:
> "If a block contains free space, skip it instead."

Lucky me.
2025-01-05 00:06:18 +01:00
dc44be89bf Solve 2024:8 p1-2 "Resonant Collinearity"
As many have said, the puzzle text this day
was a bit challenging to parse and understand.

Especially this part of pt 2 was easy to miss:

> In fact, the three T-frequency *antennas* are all
> exactly in line with two antennas,
> so *they are all also antinodes*!

not including the antennas in pt 2 gave an slightly
too low answer.
2025-01-05 00:06:18 +01:00
81fe14a407 Solve 2024:7 p1-2 "Bridge Repair"
Got thrown of big time at pt 1 since there is a
duplicate test value in my puzzle input. The code
initially asumed that all test values should be
distinct. Biased from seeing "test" in the
termology, most likely.

Earlier editions of the code also tried to create
all combinations using binary strings, but it fell
short on pt 2 when a third operation was introduced.

From some inspiration in the solutions mega
thread on Reddit, the final code is queue-based
instead. Apparently, I also learned this kind of
problem is usually well suited for DFS search, and
apparently the final code very much is _in deed_
a DFS.
2025-01-05 00:06:18 +01:00
7b2b101b8b Solve 2024:6 p1-2 "Guard Gallivant"
Pt 1 was easy, Pt 2 was pure horror.

Earlier drafts of the code tried to be way too
smart. At one point, I just came to the conclusion
to place a new obstacle (#) on the grid and just
rerun the thing to look for loops.

2 things:

- the visited positions from pt 1 can be used as a
subset for positions to consider for the extra
"#".
- The track of loops can be optimized to look at
bounces on "#"s instead of each individual y,x pos,
given that the direction is remembered.

pt 2 is familiar, the last time a puzzle required
look detection the puzzle used lazer beams and
reflectors. Not sure what Event or day it was.
2025-01-05 00:06:18 +01:00
44f6aa0f53 Solve 2024:5 p1-2 "Print Queue"
In p2, it took some tries to find a correct way
to rearrange incorrect pagesets. The code ended
up just rearranging first found incorrect order
and readding the updated pageset to queue.
2025-01-05 00:06:18 +01:00
f751b3b8d5 Solve 2024:4 p1-2 "Ceres Search"
Not gonna lie, This is not my gig skillwise. I had
much fun though!

A matrix would most likely make the code more
readable.

I relied massively on test cases here, so I share
them here instead of in the source code:

assert 1 == solve("""
    X.....
    .M....
    ..A...
    ...S..
    ..X...
    """.strip())[0]

    assert 2 == solve("""
    XS....
    .MA...
    ..AM..
    ...SX.
    ..X...
    """.strip())[0]

    assert 2 == solve("""
    ..S......
    ...A.....
    ....M....
    .....X...
    ..X...M..
    .......A.
    ........S
    """.strip())[0]

    assert 4 == solve("""
    X.SS
    M.AA
    A.MM
    S.XX
    ...M
    ...A
    ...S
    """.strip())[0]

    assert 4 == solve("""
    ...X...
    ...M...
    .X.A...
    XMASAMX
    .A.....
    .S.....
    """.strip())[0]

    assert 1 == solve("""
    ....X
    ...M.
    ..A..
    .S...
    """.strip())[0]

    assert 2 == solve("""
    ...SX
    ..AM.
    .MA..
    XS...
    """.strip())[0]

    assert 2 == solve("""
    ......X
    .....M.
    ....A..
    ...S...
    ..A....
    .M.....
    X......
    """.strip())[0]

    assert 1 == solve("""
    M.S
    .A.
    M.S
    """.strip())[1]

    assert 1 == solve("""
    M.M
    .A.
    S.S
    """.strip())[1]

    assert 1 == solve("""
    S.M
    .A.
    S.M
    """.strip())[1]

    assert 1 == solve("""
    S.S
    .A.
    M.M
    """.strip())[1]

    assert 1 == solve("""
    S.S
    .A.
    M.M
    """.strip())[1]

    assert 1 == solve("""
    .A.
    M.M
    .A.
    S.S
    """.strip())[1]

    assert 1 == solve("""
    M.M.
    .A.A
    S.S.
    """.strip())[1]

    assert 1 == solve("""
    M.M
    .A.
    S.S
    .A.
    """.strip())[1]

    assert 1 == solve("""
    .M.M
    A.A.
    .S.S
    """.strip())[1]
2025-01-05 00:06:18 +01:00
cb622409f9 Solve 2024:3 p1-2 "Mull It Over"
Felt paranoid on this one, was expecting something
much worse for pt2.

The code that solved the puzzle was _extremely_
naive:

- It asumes puzzle input only contains mul() with
1-3 chars. The versioned code have `{1,3}` as safe
guard.
- p2 asumed initial chunk does not begin with
"n't". Would have been easy to handle though.
- p2 asumed junk strings as "don", "do", "don()t"
would not exist.

In other words, I was really lucky since I did not
look that closely on puzzle input beforehand.

Might have cut 60-90 seconds further if I had just
ran pt2 immidately instead of staring dumbly on the
test data (that changed for pt2).

Also, I got reminded that \d in regular expressions
is equal to `0-9`: it does not include commas and
punctations. The code that solved the puzzle was
paranoid and instead used `0-9`.

Managed to score ~1500th somehow despite this.
2025-01-05 00:06:18 +01:00
5deb351504 Solve 2024:2 p1-2 "Red-Nosed Reports"
DDOS of adventofcode.com, did not get to open the
puzzle until 06:05 (five minutes local time).

Screwed up the zip() and lost valuable minutes by
trying to replace it with itertools, only to find
I have made a typo (did AB CD EF instead of AB BC
CD).

I also lost valuable minutes by tring to solve p1
with one nested loop. Stupid.

For part 2, I created the issafe() helper to be
able to remove items from reports and test, one
at the time.
2025-01-05 00:06:18 +01:00
81741232af Solve 2024:1 p1-2 "Historian Hysteria"
Realized afterwards I got the ints() helper, so
original code before cleanup splitted the input and
mapped all words with int(). valuable seconds lost
there.

Also, collections.Counter() was used initially since
I was too tired to remember count() method in lists.

Line 8 took the longest time to figure out. A typo
took 1-3 minutes to find for part 2.

Form: Sleep deprived, felt slow.
2025-01-05 00:06:18 +01:00
cb50d62e3f Prepare for AOC 2024
Python, again.

10th year anniverary.
2025-01-05 00:06:18 +01:00
d02f63a4ef Solve 2016:22 p2 "Grid Computing"
Solved by hand by visualizing the filesystem as
a grid, and put into a simple formula.

In Sweden, this is called a "Femtonspel".
https://sv.wikipedia.org/wiki/Femtonspel
2024-12-12 13:53:43 +01:00
8aa6363b9f Solve 2016:25 p1 "Clock Signal"
Did brute force, but earlier drafts of the code
failed to exit the while loop correcly.

By manual testing, an odd number is required in reg
a to set the first output to 0 (for my puzzle
input at least). Hence, only odd numbers are tested.
2024-12-12 13:53:43 +01:00
abaec4231f Solve 2016:24 p1-2 "Air Duct Spelunking"
BFS baby.
2024-12-12 13:53:43 +01:00
525c6aa96c Solve 2016:23 p1-2 "Safe Cracking"
Solution cide works, but is slow. According to the
subreddit, pt 2 is meant to be an exercise in
optimization.

Turns out the assembly instructions do a factorial,
i.e 7! and 12! and adds a salt (5840).

Got that spoiled. :)
2024-12-12 13:53:43 +01:00
c33bdc3f02 Solve 2016:22 p1 "Grid Computing"
Pt 2 are meant to solved by hand. Will do that
from the print I got.
2024-12-12 13:53:43 +01:00
e75670eaf2 Solve 2016:21 p1-2 "Scrambled Letters and Hash"
A good day to utilize lots of unit testing!

For pt 2, the code initially tried to reverse
the scramble algorithm. The final code however
got the unscrambled version by the traditional
method most password crackers would resolve to:
basic brute forcing with permutations as a list
of candidates.
2024-12-12 13:53:43 +01:00
63da79b2a4 Solve 2016:20 p1-2 "Firewall Rules"
The code at one time used cached responses for
in range bools, but it seems that does not improve
performance.

Some IP addresses are allowed multiple times, so
min() and set() are used to find the distinct
values.
2024-12-12 13:53:43 +01:00
39e09dd36e Solve 2016:19 p1-2 "An Elephant Named Joseph"
Learned a lot about the Josephus' Problem today!

Solved part 1 by using a dict, but eventually
ended up just adding the mathematical shortcut
and rewriting both parts to use deque() for
performance.

Part 2 was tricky, since k (the elf to remove
after all presents were stolen from them) is a
index that changes over time. No tries for a
solution that was performant enough using lists
and dicts were succesfull, so by inspiration from
the subreddit the final solution code is based on
2 deque() that pops and appends between them.

There are 2 part 1 solutions.

- A correct implementation of the Josephus' Problem,
  using deque(). Recursion would have worked as well,
  but Python do not like recursions.
- The mathematical superior version, with a link
  to the Youtube video were it is introduced.
2024-12-12 13:53:43 +01:00
ae942ce803 Solve 2016:18 p1-2 "Like a Rogue"
Figured out that the center position did not matter,
as long as left hand side tile and right hand side
tile on previous row are not equal.

Also tried to find a recurring pattern to speed
p2 up a bit, but it seems it does not have a
recurring pattern in the 400 000s first rows.
2024-12-12 13:53:43 +01:00
ffc5088813 Improve aoc helper script 2024-12-12 13:53:43 +01:00
3170846595 Solve 2016:17 p1-2 "Two Steps Forward"
Generators baby.
2024-12-12 13:53:43 +01:00
63a7ccd0e2 Simplify scaffold code 2024-12-12 13:53:43 +01:00
cdd5f63be4 Solve 2016:16 p1-2 "Dragon Checksum" 2024-12-12 13:53:43 +01:00
623253ac9d Solve 2016:15 p1-2 "Timing is Everything"
Good TIME to be a Python programmer!
2024-12-12 13:53:43 +01:00
31bb5b7006 Solve 2016:14 p1-2 "One-Time Pad"
Lost 60 minutes due to misinterpreting this in p2:

> *whenever* you generate a hash

The code initially only did the 2016 stretching for
the triplet hash, not the quintet hash. By doing it
to both, pt 2 is solved.

Not sure the lru cache actually speeds anything up.
Many on the subreddit used the approach to generate
the quintet first and look backwards 1000 times
for a matching quintet (since quintets are more
rare than triplets), this will most likely speed
things up.

Also, this solution do not store the found keys.
Many other solutions do, I believe this is some
presumptions.
2024-12-12 13:53:43 +01:00
c113042e17 Solve 2016:11 p1-2 "Radioisotope Thermoelectric Generators"
Hard one, an infamous AoC puzzle according to
Reddit.

Apparently, this is a classic logic problem named
"Missionaries and cannibals", or "Jealous husbands".

Hard lessons:

- Always use set() for visited nodes during BFS.
- Always use collections.queue() to create to queue
  traversals for BFS.
- itertools permutations(), combinations() and
pairwise() may look similar, but they are not.
- Learned to use (?:chunk of text)? in regex.

Test data was achieved without bigger hazzles, but
to optimize code required a lot of Reddit browsing
and code reading from blogs. I have mentioned the
sources in a doc string.
2024-12-12 13:53:43 +01:00
b1b5cda1e3 Solve 2016:12 p1-2 "Leonardo's Monorail" 2024-12-12 13:53:43 +01:00
8d4af7e6e9 Solve 2016:13 p1-2 "A Maze of Twisty Little Cubicles"
BFS baby.
2024-12-12 13:53:43 +01:00
c46c2c5126 Run code in container 2024-12-03 01:41:40 +01:00
2d51468eb1 Add leaderboard http service 2024-12-03 01:41:40 +01:00
dd756cb9eb
Update README.md 2024-11-29 11:58:57 +01:00
28af87c00b Add the initial 2015 days in Python
These are already done in Elixir, so this is
just done for the flex.

Also, coming from Day 16-18 from 2023 calendar,
it is safe to say 2015 puzzles are easier and more
manageable.
2023-12-19 23:55:47 +01:00
127 changed files with 3796 additions and 1692 deletions

View file

@ -46,7 +46,9 @@ if __name__ == "__main__":
solution.solve(dummy) solution.solve(dummy)
# solution.solve_again(dummy) # solution.solve_again(dummy)
""".strip().format(day=day_no, day_no=day_no.zfill(2), name=name) """.strip().format(
day=day_no, day_no=day_no.zfill(2), name=name
)
+ "\n" + "\n"
) )
@ -63,13 +65,18 @@ https://adventofcode.com/{year}/day/{day_no}/input
) )
exit(0) exit(0)
stars = 0
for i in [str(n).zfill(2) for n in range(1, 26)]: for i in [str(n).zfill(2) for n in range(1, 26)]:
try: try:
solution = __import__( solution = __import__(
"solutions.day_{}".format(i), globals(), locals(), ["Solution"], 0 "solutions.day_{}".format(i), globals(), locals(), ["Solution"], 0
).Solution() ).Solution()
solution.show_results() solution.show_results()
stars += 2
except IOError: except IOError:
pass pass
except ImportError: except ImportError:
pass pass
print(f"\nStars: {stars}")
print("".join("*" if n < stars else "" for n in range(50)))
print("")

View file

@ -14,9 +14,8 @@ class BaseSolution:
data = self.read_input(self.input_file) data = self.read_input(self.input_file)
puzzle_input = self.parse_input(data) puzzle_input = self.parse_input(data)
print( print(
"\n\n{}\n{}\n\nPart 1: {}\nPart 2: {}".format( "\n--- {} ---\n 1. {}\n 2. {}".format(
str(self), str(self),
"-" * len(str(self)),
self.solve(puzzle_input), self.solve(puzzle_input),
self.solve_again(puzzle_input), self.solve_again(puzzle_input),
) )

View file

@ -0,0 +1,26 @@
from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "01.txt"
def __str__(self):
return "Day 1: Not Quite Lisp"
def solve(self, pi):
return sum([-1 if c == ")" else 1 for c in pi])
def solve_again(self, pi):
f = 1
for i, v in enumerate(pi):
f += -1 if v == ")" else 1
if f == -1:
return i
def parse_input(self, data):
return data.strip()
if __name__ == "__main__":
solution = Solution()
solution.show_results()

View file

@ -0,0 +1,33 @@
from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "02.txt"
def __str__(self):
return "Day 2: I Was Told There Would Be No Math"
def solve(self, pi):
o = self._solve(pi)
return o[0]
def solve_again(self, pi):
o = self._solve(pi)
return o[1]
def _solve(self, pi):
p1 = 0
p2 = 0
for p in pi.splitlines():
l, w, h = sorted([int(s) for s in p.split("x")])
p1 += 2 * (l * w + w * h + h * l) + min([l * w, w * h, h * l])
p2 += 2 * l + 2 * w + l * w * h
return p1, p2
def parse_input(self, data):
return data.strip()
if __name__ == "__main__":
solution = Solution()
solution.show_results()

View file

@ -0,0 +1,47 @@
from collections import defaultdict
from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "03.txt"
def __str__(self):
return "Day 3: Perfectly Spherical Houses in a Vacuum"
def solve(self, pi):
return self._solve(pi)[0]
def solve_again(self, pi):
return self._solve(pi)[1]
def _solve(self, pi):
def f(q):
p = (0, 0)
vs = defaultdict(int)
vs[p] += 1
for d in q:
r, c = p
match d:
case "^":
p = (r - 1, c)
case ">":
p = (r, c + 1)
case "v":
p = (r + 1, c)
case "<":
p = (r, c - 1)
vs[p] += 1
return set(vs.keys())
p1 = len(f(pi))
p2 = len(f(pi[0::2]) | f(pi[1::2]))
return p1, p2
def parse_input(self, data):
return data.strip()
if __name__ == "__main__":
solution = Solution()
solution.show_results()

View file

@ -0,0 +1,34 @@
from hashlib import md5
from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "04.txt"
def __str__(self):
return "Day 4: The Ideal Stocking Stuffer"
def solve(self, pi):
return self._solve(pi)[0]
def solve_again(self, pi):
return self._solve(pi)[1]
def _solve(self, secret):
p12 = []
prefetched = [254575, 1038736]
for zc in [5, 6]:
sw = str.zfill("0", zc)
c = prefetched.pop(0)
if md5(bytes(f"{secret}{c}", "utf-8")).hexdigest().startswith(sw):
p12.append(c)
return p12
def parse_input(self, data):
return data.strip()
if __name__ == "__main__":
solution = Solution()
solution.show_results()

View file

@ -0,0 +1,39 @@
import re
from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "05.txt"
def __str__(self):
return "Day 5: Doesn't He Have Intern-Elves For This?"
def solve(self, pi):
return self._solve(pi)[0]
def solve_again(self, pi):
return self._solve(pi)[1]
def parse_input(self, data):
return data.strip()
def _solve(self, pi):
wl = pi.split()
p1 = sum(
not re.search(r"ab|cd|pq|xy.*", w)
and any(w[i] == w[i + 1] for i in range(len(w) - 1))
and len(re.findall(r"[aeiou]", w)) > 2
for w in wl
)
p2 = sum(
any(w.count(w[i : i + 2]) == 2 for i in range(len(w) - 1))
and any(w[i] == w[i + 2] for i in range(len(w) - 2))
for w in wl
)
return p1, p2
if __name__ == "__main__":
solution = Solution()
solution.show_results()

View file

@ -0,0 +1,66 @@
from collections import defaultdict
from solutions import BaseSolution
class Solution(BaseSolution):
input_file = "07.txt"
def __str__(self):
return "Day 7: Some Assembly Required"
def solve(self, pi):
return self._solve(pi)
def solve_again(self, pi):
a = self.solve(pi)
return self._solve(pi.replace("19138", str(a)))
def parse_input(self, data):
return data.strip()
def _solve(self, pi):
p = pi.splitlines()
w = defaultdict(int)
while p:
np = []
for l in p:
x, to = l.split(" -> ")
if x.isdigit():
w[to] += int(x)
elif len(x.split()) == 1:
if x not in w:
np.append(l)
else:
w[to] += w[x]
elif x.startswith("NOT "):
a = x.split()[-1]
if a.isdigit() or a in w:
a = int(a) if a.isdigit() else w[a]
w[to] += ~a
else:
np.append(l)
else:
a, v, b = x.split()
if (a.isdigit() or a in w) and (b.isdigit() or b in w):
a = int(a) if a.isdigit() else w[a]
b = int(b) if b.isdigit() else w[b]
match v:
case "RSHIFT":
w[to] += a >> b
case "LSHIFT":
w[to] += a << b
case "AND":
w[to] += a & b
case "OR":
w[to] += a | b
else:
np.append(l)
p = np
return w["a"]
if __name__ == "__main__":
solution = Solution()
solution.show_results()

50
2016-python2/README.md Normal file
View file

@ -0,0 +1,50 @@
# Advent of Code 2016
Solutions for #aoc2016 in Python 3 (3.12.7).
## Setup
Since I want to remember, this is what was used to solve
the puzzles.
- Lenovo Thinkpad x260 laptop with Arch Linux.
- Hyprland with gBar.
- Editor: Zed.
- Terminal: Alacritty.
## Help scripts
Display all solved puzzles:
python aoc.py
To bootstrap a new puzzle (creates `input/<day_no>.txt` and `output/day_<day_no>.py`):
python aoc.py <day_no> <puzzle_name>
Manually copy the puzzle input from https://adventofcode.com and paste it in `input/<day_no>.txt`
to start coding.
wl-paste > input/<day_no>.txt
Solve separate puzzle (replace `XX` with the puzzle number):
python -m output.day_XX
Solve separate puzzle using stdin (replace `XX` with the puzzle number):
wl-paste | python -m output.day_XX
cat tmpfile | python -m output.day_XX
Execute separate puzzle on file save (replace `XX` with the puzzle number):
ls output/*.py | entr -c -s 'wlpaste | python -m output.day_XX'
ls output/*.py | entr -c -s 'cat tmpfile | python -m output.day_XX'
ls output/*.py | entr -c -r python -m output.day_XX
(requires `entr` and `wl-paste`, Mac users can instead use `pbpaste`. If you
prefer X at Linux, use `xclip -selection clipboard -o`).
To lint files:
ls output/*.py | entr -r -c flake8 output --ignore=E741,E501,E203

View file

@ -1,13 +1,13 @@
import os
import sys import sys
from pathlib import Path
def headline(n, title): def headline(n):
"""Print day number and name, followed by a ruler. Used by the answer decorator""" """Print day number and name, followed by a ruler. Used by the answer decorator"""
print(f"\n--- Day {n}: {title} ---\n") print(f"\n--- Day {n} ---\n")
year = 2019 year = 2016
try: try:
_, day_no, *name = sys.argv _, day_no, *name = sys.argv
@ -19,6 +19,9 @@ print(
f"\nAdvent of Code {year}" "\n###################" "\n\nby Anders Englöf Ytterström" f"\nAdvent of Code {year}" "\n###################" "\n\nby Anders Englöf Ytterström"
) )
Path("./input").mkdir(parents=True, exist_ok=True)
Path("./output").mkdir(parents=True, exist_ok=True)
if day_no and name: if day_no and name:
name = " ".join(name) name = " ".join(name)
padded_no = day_no.zfill(2) padded_no = day_no.zfill(2)
@ -26,24 +29,18 @@ if day_no and name:
with open("output/day_{}.py".format(padded_no), "w") as s: with open("output/day_{}.py".format(padded_no), "w") as s:
s.write( s.write(
f""" f"""
from output import answer # , matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg import re
from collections import deque, Counter, defaultdict
from heapq import heappop, heappush
from itertools import compress, combinations, chain, permutations
n = {day_no} from output import matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg
title = "{name}"
@answer(1, "Answer is {{}}")
def part_1(outputs):
return outputs[0]
@answer(2, "Actually, answer is {{}}")
def part_2(outputs):
return outputs[1]
def solve(data): def solve(data):
return None, None p1 = 1
p2 = 2
return p1, p2
if __name__ == "__main__": if __name__ == "__main__":
@ -59,22 +56,19 @@ if __name__ == "__main__":
# with open("./input/{padded_no}.txt", "r") as f: # with open("./input/{padded_no}.txt", "r") as f:
# inp = f.read().strip() # inp = f.read().strip()
inp = solve(inp) # uncomment to do initial data processing shared by part 1-2
p1, p2 = solve(inp)
a = part_1(inp) print(p1)
# b = part_2(inp) # print(p2)
# uncomment and replace 0 with actual output to refactor code # uncomment and replace 0 with actual output to refactor code
# and ensure nonbreaking changes # and ensure nonbreaking changes
# assert a == 0 # assert p1 == 0
# assert b == 0 # assert p2 == 0
""".strip() """.strip()
+ "\n" + "\n"
) )
print("- making sure input dir exists")
if not os.path.exists("input"):
os.makedirs("input")
print( print(
f""" f"""
Done! start coding. Done! start coding.
@ -97,19 +91,25 @@ for i in [str(n).zfill(2) for n in range(1, 26)]:
"output.day_{}".format(i), "output.day_{}".format(i),
globals(), globals(),
locals(), locals(),
["n", "title", "part_1", "part_2", "solve"], ["solve"],
0, 0,
) )
with open(f"./input/{i}.txt", "r") as f: with open(f"./input/{i}.txt", "r") as f:
data = f.read().strip() data = f.read().strip()
headline(day.n, day.title) headline(i)
try: try:
data = day.solve(data) data = day.presolve(data)
except AttributeError: except AttributeError:
pass pass
if day.part_1(data, decorate=True): try:
p1, p2 = day.solve(data)
except AttributeError:
pass
if p1:
print(f" 1. {p1}")
stars += 1 stars += 1
if day.part_2(data, decorate=True): if p2:
print(f" 2. {p2}")
stars += 1 stars += 1
except IOError: except IOError:
pass pass

View file

@ -30,31 +30,9 @@ ADJ = [
] ]
def answer(part_index, fmt_string):
"""Decorator to present a solution in a human readable format"""
def decorator_aoc(func):
@functools.wraps(func)
def wrapper_aoc(*args, **kwargs):
decorate = kwargs.get("decorate", False)
if decorate:
del kwargs["decorate"]
answer = func(*args, **kwargs)
if not decorate:
print(answer)
else:
formatted = fmt_string.format(answer)
print(f" {part_index}) {formatted}")
return answer
return wrapper_aoc
return decorator_aoc
def ints(s): def ints(s):
"""Extract all integers from a string""" """Extract all integers from a string"""
return [int(n) for n in re.findall(r"-?\d+", s)] return [int(n) for n in re.findall(r"\d+", s)]
def mhd(a, b): def mhd(a, b):
@ -80,3 +58,19 @@ def vdbg(seen, h, w):
"""Print-debug visited positions of a matrix""" """Print-debug visited positions of a matrix"""
for r in range(h): for r in range(h):
print("".join(["#" if (r, c) in seen else "." for c in range(w)])) print("".join(["#" if (r, c) in seen else "." for c in range(w)]))
def bfs(S, E=None):
"""BFS algorithm, equal weighted nodes"""
seen = set()
q = [(S, 0)]
g = {} # graph, required to be provided at some point
while q:
m, w = q.pop(0)
if m in seen:
continue
seen.add(m)
# investigate here
for s in g[m]:
q.append((s, w + 1))
# return insights

View file

@ -0,0 +1,136 @@
import re
from collections import Counter, deque
from itertools import combinations
D = {1: [2], 2: [1, 3], 3: [2, 4], 4: [3]}
def solve(data):
def parse(row):
return sorted(
[
"".join(nt).upper()
for nt in re.findall(r"(\w)\S+(?:-compatible)? (g|m)", row)
]
)
f = dict([(i, parse(row)) for i, row in enumerate(data.splitlines(), start=1)])
E = sum(map(len, f.values()))
p1 = bfs(1, f, E)
adjusted = data.splitlines()
adjusted[
0
] += """
An elerium generator.
An elerium-compatible microchip.
A dilithium generator.
A dilithium-compatible microchip.
"""
f = dict([(i, parse(row)) for i, row in enumerate(adjusted, start=1)])
E = sum(map(len, f.values()))
p2 = bfs(1, f, E)
return p1, p2
def bfs(S, m, E):
seen = set()
q = deque([(S, m, 0)])
while q:
e, m, w = q.popleft()
cs = seen_checksum(e, m) # reddit wisdom, see function for more info
if cs in seen:
continue
seen.add(cs)
if len(m[4]) == E:
return w
for n, b, a in valid_next_moves(e, m):
ns = m.copy()
ns[e] = a
ns[n] = b
q.append((n, ns, w + 1))
return None
def valid_next_moves(e, S):
g = []
for n in D[e]:
for o in [[x] for x in S[e]] + [list(x) for x in combinations(S[e], 2)]:
a = sorted(x for x in S[e] if x not in o)
b = sorted(S[n] + o)
if is_valid(a) and is_valid(b):
g.append((n, b, a))
return g
def is_valid(f):
g = [x for x in f if x.endswith("G")]
if not g:
return True
mc = [x for x in f if x.endswith("M")]
return all(f"{m[0]}G" in f for m in mc)
def seen_checksum(e, s):
# To speed up execution, a handy trick was mentioned on several
# reddit threads.
#
# The vanilla BFS method is to store the complete state (elevator
# position + all floors), which this code like many others did initially.
# This is fine, but will lead to really long execution times.
#
# The common wisdom boils down to one thing: it does not matter _what_
# name the microchips and generators have. Only the arrangement (counts) of
# any microchips or generators across the floors do.
#
# For example, these are the same as a checksum to determine if a state has
# been seen:
#
# F2 . HG . . F2 . LG . .
# F1 E . HM LM F1 E . LM HM
#
# The H's or L's do not matter, only the M's and G's do.
#
# So by storing the M's and the G's as a checksum, along with the
# elevator position, the program is a lot faster.
#
# Reddit post, giving hints:
# https://www.reddit.com/r/adventofcode/comments/5hoia9/comment/db1v1ws/?utm_source=share&utm_medium=web3x&utm_name=web3xcss&utm_term=1&utm_content=share_button
# Blog post, providing code and reasoning:
# https://eddmann.com/posts/advent-of-code-2016-day-11-radioisotope-thermoelectric-generators/
# uncomment below line to get speed boost:
# %> 2.59s user 0.02s system 99% cpu 2.622 total
return e, tuple(tuple(Counter(x[-1] for x in f).most_common()) for f in s.values())
# uncomment below line to use vanilla BFS visited storage
# %> 896.11s user 2.58s system 99% cpu 15:01.35 total
# return e, tuple((f, tuple(mg)) for f, mg in s.items())
def M(s, e):
d = {}
for k, v in s.items():
for x in v:
d[x] = k - 1
l = len(d)
d = [
[v if x == k else ". " for x in range(l)]
for v, k in sorted(d.items(), key=lambda kv: kv[0])
]
m = [("F1", "F2", "F3", "F4"), ["E " if x == e - 1 else ". " for x in range(l)]] + d
for r in list(zip(*m))[::-1]:
print(" ".join(r))
print("")
if __name__ == "__main__":
with open("./input/11.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,52 @@
def solve(data):
p = data.splitlines()
pl = len(p)
def run(r):
i = 0
while i < pl:
o, *a = p[i].split()
match o:
case "cpy":
kv, t = a
r[t] = int(kv) if kv.isdigit() else r[kv]
i += 1
case "jnz":
kv, d = a
c = int(kv) if kv.isdigit() else r[kv]
i += 1 if c == 0 else int(d)
case "inc":
r[a[0]] += 1
i += 1
case "dec":
r[a[0]] -= 1
i += 1
return r["a"]
p1 = run(
{
"a": 0,
"b": 0,
"c": 0,
"d": 0,
}
)
p2 = run(
{
"a": 0,
"b": 0,
"c": 1,
"d": 0,
}
)
return p1, p2
if __name__ == "__main__":
with open("./input/12.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,52 @@
from collections import Counter
from math import inf
from output import D
def solve(data, E=(31, 39)):
S = (1, 1)
p1, p2 = bfs(S, E, int(data), inf, 0)
return p1, p2
def bfs(S, E, z, p1, p2):
seen = set()
q = [(S, 0)]
while q:
m, w = q.pop(0)
if m in seen:
continue
seen.add(m)
if m == E:
p1 = min(w, p1)
if w <= 50:
p2 = max(p2, len(seen))
x, y = m
g = [
(x, y)
for x, y in [(x + c, y + r) for r, c in D]
if x >= 0 and y >= 0 and valid(x, y, z)
]
for s in g:
q.append((s, w + 1))
return p1, p2
def valid(x, y, z):
n = (x * x + 3 * x + 2 * x * y + y + y * y) + z
s = Counter(f"{n:b}")["1"]
return s % 2 == 0
if __name__ == "__main__":
with open("./input/13.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,57 @@
import functools
import re
from hashlib import md5
def solve(s):
p1 = run(s)
p2 = run(s, p2=True)
return p1, p2
def run(s, p2=False):
r3 = re.compile(r"(\w)\1{2}")
c = 0
i = 0
H = md5(s.encode())
while True:
i += 1
if p2:
a = stretch(f"{s}{i}")
else:
Ha = H.copy()
Ha.update(str(i).encode())
a = Ha.hexdigest()
if m := re.search(r3, a):
x = m.group(1)
r5 = re.compile(r"(" + x + r")\1{4}")
for j in range(1000):
if p2:
b = stretch(f"{s}{str(i + 1 + j)}")
else:
Hb = H.copy()
Hb.update(str(i + 1 + j).encode())
b = Hb.hexdigest()
if re.search(r5, b):
c += 1
break
if c == 64:
break
return i
@functools.lru_cache(maxsize=None)
def stretch(s):
for _ in range(2017):
s = md5(s.encode()).hexdigest()
return s
if __name__ == "__main__":
with open("./input/14.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,30 @@
import re
def solve(data):
M = [
tuple(map(int, re.search(r"(\d+) pos.+ion (\d+)", r).groups()))
for r in data.splitlines()
]
p1 = wait_and_press(M)
p2 = wait_and_press(M + [(11, 0)])
return p1, p2
def wait_and_press(M):
t = 0
while True:
if all((ti + lp[1]) % lp[0] == 0 for ti, lp in enumerate(M, start=t + 1)):
break
t += 1
return t
if __name__ == "__main__":
with open("./input/15.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,24 @@
def solve(data):
p12 = []
for DS in [272, 35651584]:
s = [int(c) for c in data]
while len(s) < DS:
b = [abs(int(c) - 1) for c in s[::-1]]
s = s + [0] + b
s = s[:DS]
p = len(s)
while p % 2 == 0:
s = [int(a == b) for a, b in zip(s[::2], s[1::2])]
p = len(s)
p12.append("".join(map(str, s)))
return p12
if __name__ == "__main__":
with open("./input/16.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,41 @@
from collections import deque
from hashlib import md5
from output import DD
def solve(data):
paths = list(bfs(data))
p1 = "".join(paths[0])
p2 = len(paths[-1])
return p1, p2
def bfs(code):
q = deque([((0, 0), [])])
T = (3, 3)
K = "UDLR"
while q:
m, d = q.popleft()
U, D, L, R, *_ = md5((code + "".join(d)).encode()).hexdigest()
y, x = m
for udlr in [K[k] for k, n in enumerate([U, D, L, R]) if isopen(n)]:
dy, dx = DD[udlr]
if (y + dy, x + dx) == T:
yield d + [udlr]
elif 0 <= y + dy < 4 and 0 <= x + dx < 4:
q.append(((y + dy, x + dx), d + [udlr]))
def isopen(c):
return c in "bcdef"
if __name__ == "__main__":
with open("./input/17.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,32 @@
def solve(data):
cols = len(data)
prevrow = [t == "." for t in data]
p1 = sum(prevrow) + sum(
sum(prevrow := [issafe(prevrow, i) for i in range(cols)]) for _ in range(39)
)
p2 = p1 + sum(
sum(prevrow := [issafe(prevrow, i) for i in range(cols)])
for _ in range(400_000 - 40)
)
return p1, p2
def issafe(row, i):
match i:
case 0:
return row[1]
case n if n == len(row) - 1:
return row[-2]
case _:
lt, rt = row[i - 1], row[i + 1]
return not lt != rt
if __name__ == "__main__":
with open("./input/18.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,52 @@
from collections import deque
def solve(data):
elfes = int(data)
p1 = left_adjacent_rule(elfes)
p1b = mathematical_superiority(elfes)
p2 = opposite_side_rule(elfes)
assert p1 == p1b
return p1, p2
def mathematical_superiority(num_elfes):
# Josephus' problem, quick method:
# https://www.youtube.com/watch?v=uCsD3ZGzMgE
b = format(num_elfes, "b")
return int(b[1:] + b[0], 2)
def left_adjacent_rule(num_elfes):
# https://en.wikipedia.org/wiki/Josephus_problem
q = deque(list(range(1, num_elfes + 1)))
while len(q) > 1:
q.rotate(-1)
q.popleft()
return q.pop()
def opposite_side_rule(num_elfes):
elfes = list(range(1, num_elfes + 1))
separator = num_elfes // 2
L, R = deque(elfes[:separator]), deque(elfes[separator:])
while L and R:
R.popleft()
l2r = L.popleft()
R.append(l2r)
if len(R) - len(L) != 1:
r2l = R.popleft()
L.append(r2l)
return R.pop()
if __name__ == "__main__":
with open("./input/19.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,31 @@
from output import ints
def solve(data):
lowest, highest = min(ints(data)), max(ints(data))
data = [ints(line) for line in data.split()]
p1 = float("inf")
p2 = set()
for a, b in data:
X = a - 1
Y = b + 1
if X >= lowest:
if not any(x <= X <= y for x, y in data):
p1 = min(p1, X)
p2.add(X)
if Y <= highest:
if not any(x <= Y <= y for x, y in data):
p1 = min(p1, Y)
p2.add(Y)
p2 = len(p2)
return p1, p2
if __name__ == "__main__":
with open("./input/20.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,64 @@
import re
from itertools import permutations
from output import ints
def solve(data):
data = [line.strip() for line in data.splitlines()]
p1 = scramble(data, "abcdefgh")
p2 = unscramble(data, "fbgdceah")
return p1, p2
def scramble(data, subject):
r_swap = r"(?:letter|position) (.)"
for line in data:
if line.startswith("rotate right"):
x = ints(line)[0]
subject = subject[-x:] + subject[:-x]
if line.startswith("rotate left"):
x = ints(line)[0]
subject = subject[x:] + subject[:x]
if line.startswith("rotate based"):
x = re.findall(r"letter (.)", line)[0]
i = subject.index(x)
j = i + 1 % len(subject)
subject = subject[-j:] + subject[:-j]
if i >= 4:
subject = subject[-1:] + subject[:-1]
if line.startswith("swap letter"):
x, y = re.findall(r_swap, line)
subject = subject.replace(y, "#")
subject = subject.replace(x, y)
subject = subject.replace("#", x)
if line.startswith("swap position"):
x, y = ints(line)
v1, v2 = subject[x], subject[y]
subject = subject[:x] + v2 + subject[x + 1 :]
subject = subject[:y] + v1 + subject[y + 1 :]
if line.startswith("move"):
x, y = ints(line)
v = subject[x]
subject = subject[:x] + subject[x + 1 :]
subject = subject[:y] + v + subject[y:]
if line.startswith("reverse"):
x, y = ints(line)
subject = subject[:x] + subject[x : y + 1][::-1] + subject[y + 1 :]
return subject
def unscramble(data, T):
for candidate in ["".join(c) for c in permutations(T)]:
if scramble(data, candidate) == T:
return candidate
if __name__ == "__main__":
with open("./input/21.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,45 @@
from itertools import permutations
from output import ints
def solve(data):
viable = set()
W = 0
H = 0
grid = dict()
for a, b in permutations([ints(line) for line in data.splitlines()[2:]], r=2):
x1, y1, size, used, _avail, _pc = a
x2, y2, _size, _used, avail, _pc = b
H = max([y1, y2, H])
W = max([x1, x2, W])
grid[(y1, x1)] = (used, size)
if 0 < used <= avail:
viable.add(((y1, x1), (y2, x2)))
if used == 0:
empty = (y1, x1)
p1 = len(viable)
S, E = (0, W), (0, 0)
# dagrid(grid, H + 1, W + 1)
y, x = empty
p2 = x + y + W + (W - 1) * 5
return p1, p2
def dagrid(grid, H, W):
"""Used to print the grid to be solved by hand."""
for r in range(H):
for c in range(W):
u, a = grid[(r, c)]
print(f"{u}/{a}".rjust(8), end="")
print("\n")
if __name__ == "__main__":
with open("./input/22.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,76 @@
def solve(data):
p = data.splitlines()
# math.factorial(7) + 5840
p1 = run(
p.copy(),
{
"a": 7,
"b": 0,
"c": 0,
"d": 0,
},
)
# math.factorial(12) + 5840
p2 = run(
p.copy(),
{
"a": 12,
"b": 0,
"c": 0,
"d": 0,
},
)
return p1, p2
def run(p, r):
i = 0
pl = len(p)
while i < pl:
o, *a = p[i].split()
match o:
case "cpy":
kv, t = a
if t in "abcd":
r[t] = int(kv) if kv not in "abcd" else r[kv]
i += 1
case "jnz":
kv, d = a
c = int(kv) if kv not in "abcd" else r[kv]
d = int(d) if d not in "abcd" else r[d]
i += 1 if c == 0 else d
case "inc":
r[a[0]] += 1
i += 1
case "dec":
r[a[0]] -= 1
i += 1
case "tgl":
kv = a[0]
c = int(kv) if kv not in "abcd" else r[kv]
if 0 <= i + c < pl:
old, *v = p[i + c].split()
match old:
case "inc":
nw = "dec"
case "dec":
nw = "inc"
case "tgl":
nw = "inc"
case "jnz":
nw = "cpy"
case _:
nw = "jnz"
p[i + c] = " ".join([nw] + v)
i += 1
return r["a"]
if __name__ == "__main__":
with open("./input/23.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,58 @@
import re
from collections import Counter, defaultdict, deque
from heapq import heappop, heappush
from itertools import chain, combinations, compress, permutations
from output import ADJ, DD, D, ints, matrix, mdbg, mhd, vdbg
def solve(data):
grid, H, W = matrix(data)
dests = {
v: (y, x) for y, r in enumerate(grid) for x, v in enumerate(r) if v.isdigit()
}
S0 = dests["0"]
del dests["0"]
p1 = travel(dests, grid, H, W, S0)
p2 = travel(dests, grid, H, W, S0, goback=True)
return p1, p2
def travel(dests, grid, H, W, S0, goback=False):
shortest = float("inf")
for goals in permutations(dests.items()):
goals = list(goals)
if goback:
goals += [("0", S0)]
t = 0
S = S0
for _, E in goals:
seen = set()
q = [(S, 0)]
while q:
pos, w = q.pop(0)
if pos == E:
t += w
break
if pos in seen:
continue
seen.add(pos)
y, x = pos
for dy, dx in D:
if not (0 <= dy + y < H and 0 <= dx + x < W):
continue
if grid[dy + y][dx + x] != "#":
q.append(((dy + y, dx + x), w + 1))
S = E
shortest = min(shortest, t)
return shortest
if __name__ == "__main__":
with open("./input/24.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,77 @@
def solve(data):
p = data.splitlines()
csl = 30
A = "01" * (csl // 2)
i = 1
while True:
if A == run(
csl,
p.copy(),
{
"a": i,
"b": 0,
"c": 0,
"d": 0,
},
):
break
i += 2
p1 = i
return p1, None
def run(lO, p, r):
i = 0
pl = len(p)
outs = []
while i < pl and len(outs) < lO:
o, *a = p[i].split()
match o:
case "cpy":
kv, t = a
if t in "abcd":
r[t] = int(kv) if kv not in "abcd" else r[kv]
i += 1
case "out":
kv, *_ = a
outs.append(f'{int(kv) if kv not in "abcd" else r[kv]}')
i += 1
case "jnz":
kv, d = a
c = int(kv) if kv not in "abcd" else r[kv]
d = int(d) if d not in "abcd" else r[d]
i += 1 if c == 0 else d
case "inc":
r[a[0]] += 1
i += 1
case "dec":
r[a[0]] -= 1
i += 1
case "tgl":
kv = a[0]
c = int(kv) if kv not in "abcd" else r[kv]
if 0 <= i + c < pl:
old, *v = p[i + c].split()
match old:
case "inc":
nw = "dec"
case "dec":
nw = "inc"
case "tgl":
nw = "inc"
case "jnz":
nw = "cpy"
case _:
nw = "jnz"
p[i + c] = " ".join([nw] + v)
i += 1
return "".join(outs)
if __name__ == "__main__":
with open("./input/25.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)

View file

@ -7,8 +7,9 @@ except ValueError:
name = None name = None
if day_no and name: if day_no and name:
with open('solutions/day_{}.py'.format(day_no.zfill(2)), 'w') as s: with open("solutions/day_{}.py".format(day_no.zfill(2)), "w") as s:
s.write(''' s.write(
"""
from solutions import BaseSolution from solutions import BaseSolution
@ -28,9 +29,14 @@ class Solution(BaseSolution):
if __name__ == '__main__': if __name__ == '__main__':
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()
'''.strip().format(day=day_no, day_no=day_no.zfill(2), name=name) + '\n') """.strip().format(
with open('tests/day_{}_tests.py'.format(day_no.zfill(2)), 'w') as t: day=day_no, day_no=day_no.zfill(2), name=name
t.write(''' )
+ "\n"
)
with open("tests/day_{}_tests.py".format(day_no.zfill(2)), "w") as t:
t.write(
"""
import unittest import unittest
from solutions.day_{day_no} import Solution from solutions.day_{day_no} import Solution
@ -46,18 +52,24 @@ class Day{day_no}TestCase(unittest.TestCase):
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()
'''.strip().format(day_no=day_no.zfill(2)) + '\n') """.strip().format(
with open('inputs/{}.txt'.format(day_no.zfill(2)), 'w') as i: day_no=day_no.zfill(2)
i.write('') )
+ "\n"
)
with open("inputs/{}.txt".format(day_no.zfill(2)), "w") as i:
i.write("")
exit(0) exit(0)
print('\nAdvent of Code 2017' print(
'\n###################' "\nAdvent of Code 2017" "\n###################" "\n\nby Anders Ytterström (@madr)"
'\n\nby Anders Ytterström (@madr)') )
for i in [str(n).zfill(2) for n in range(1, 26)]: for i in [str(n).zfill(2) for n in range(1, 26)]:
try: try:
solution = __import__('solutions.day_{}'.format(i), globals(), locals(), ['Solution'], 0).Solution() solution = __import__(
"solutions.day_{}".format(i), globals(), locals(), ["Solution"], 0
).Solution()
solution.show_results() solution.show_results()
except IOError: except IOError:
pass pass

View file

@ -4,20 +4,22 @@ class BaseSolution:
trim_input = True trim_input = True
def parse_input(self, filename): def parse_input(self, filename):
filepath = './inputs/{}'.format(filename) filepath = "./inputs/{}".format(filename)
with open(filepath, 'r') as f: with open(filepath, "r") as f:
self.puzzle_input = f.read() self.puzzle_input = f.read()
if self.trim_input: if self.trim_input:
self.puzzle_input = self.puzzle_input.strip() self.puzzle_input = self.puzzle_input.strip()
def show_results(self): def show_results(self):
self.parse_input(self.input_file) self.parse_input(self.input_file)
print('\n\n{}\n{}\n\nPart 1: {}\nPart 2: {}'.format( print(
str(self), "\n\n{}\n{}\n\nPart 1: {}\nPart 2: {}".format(
'-' * len(str(self)), str(self),
self.solve(self.puzzle_input), "-" * len(str(self)),
self.solve_again(self.puzzle_input), self.solve(self.puzzle_input),
)) self.solve_again(self.puzzle_input),
)
)
def solve(self, puzzle_input): def solve(self, puzzle_input):
raise NotImplemented raise NotImplemented

View file

@ -2,21 +2,24 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '01.txt' input_file = "01.txt"
def __str__(self): def __str__(self):
return 'Day 1: Inverse Captcha' return "Day 1: Inverse Captcha"
def solve(self, puzzle_input, distance=1): def solve(self, puzzle_input, distance=1):
pi_length = len(puzzle_input) pi_length = len(puzzle_input)
return sum(int(puzzle_input[pos]) for pos in range(pi_length) if return sum(
puzzle_input[pos] == puzzle_input[(pos + distance) % pi_length]) int(puzzle_input[pos])
for pos in range(pi_length)
if puzzle_input[pos] == puzzle_input[(pos + distance) % pi_length]
)
def solve_again(self, puzzle_input): def solve_again(self, puzzle_input):
distance = len(puzzle_input) // 2 distance = len(puzzle_input) // 2
return self.solve(puzzle_input, distance) return self.solve(puzzle_input, distance)
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '02.txt' input_file = "02.txt"
def __str__(self): def __str__(self):
return 'Day 2: Corruption Checksum' return "Day 2: Corruption Checksum"
def _get_rows(self, puzzle_input): def _get_rows(self, puzzle_input):
return [list(map(int, rows.split())) for rows in puzzle_input.splitlines()] return [list(map(int, rows.split())) for rows in puzzle_input.splitlines()]
@ -29,6 +29,6 @@ class Solution(BaseSolution):
return sum(self.get_even_divisible(columns) for columns in rows) return sum(self.get_even_divisible(columns) for columns in rows)
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '03.txt' input_file = "03.txt"
def __str__(self): def __str__(self):
return 'Day 3: Spiral Memory' return "Day 3: Spiral Memory"
def _get_rounds(self, value): def _get_rounds(self, value):
n = 0 n = 0
@ -32,6 +32,6 @@ class Solution(BaseSolution):
return 279138 return 279138
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,15 +2,15 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '04.txt' input_file = "04.txt"
def __str__(self): def __str__(self):
return 'Day 4: High-Entropy Passphrases' return "Day 4: High-Entropy Passphrases"
def validate(self, passphrase, extended=False): def validate(self, passphrase, extended=False):
words = passphrase.split() words = passphrase.split()
if extended: if extended:
words = [''.join(sorted(w)) for w in words] words = ["".join(sorted(w)) for w in words]
return sorted(list(set(words))) == sorted(words) return sorted(list(set(words))) == sorted(words)
def solve(self, puzzle_input): def solve(self, puzzle_input):
@ -20,6 +20,6 @@ class Solution(BaseSolution):
return sum(self.validate(p, True) for p in puzzle_input.splitlines()) return sum(self.validate(p, True) for p in puzzle_input.splitlines())
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '05.txt' input_file = "05.txt"
def __str__(self): def __str__(self):
return 'Day 5: A Maze of Twisty Trampolines, All Alike' return "Day 5: A Maze of Twisty Trampolines, All Alike"
def solve(self, puzzle_input, strange_jumps=False): def solve(self, puzzle_input, strange_jumps=False):
pos = 0 pos = 0
@ -23,6 +23,6 @@ class Solution(BaseSolution):
return self.solve(puzzle_input, True) return self.solve(puzzle_input, True)
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '06.txt' input_file = "06.txt"
def __str__(self): def __str__(self):
return 'Day 6: Memory Reallocation' return "Day 6: Memory Reallocation"
def redistribute(self, banks): def redistribute(self, banks):
banks = list(banks) banks = list(banks)
@ -39,10 +39,10 @@ class Solution(BaseSolution):
def solve_again(self, puzzle_input): def solve_again(self, puzzle_input):
seen = self._allocate(puzzle_input) seen = self._allocate(puzzle_input)
seen_last = ' '.join(str(n) for n in seen[-1]) seen_last = " ".join(str(n) for n in seen[-1])
return len(self._allocate(seen_last)) - 1 return len(self._allocate(seen_last)) - 1
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -18,8 +18,8 @@ class Program:
name, weight = data.split() name, weight = data.split()
except ValueError: except ValueError:
name, weight, _, *disc = data.split() name, weight, _, *disc = data.split()
weight = int(weight[1:len(weight) - 1]) weight = int(weight[1 : len(weight) - 1])
disc = tuple(p.replace(',', '') for p in disc) disc = tuple(p.replace(",", "") for p in disc)
return name, weight, disc return name, weight, disc
def has_disc(self): def has_disc(self):
@ -30,29 +30,31 @@ class Program:
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '07.txt' input_file = "07.txt"
def __str__(self): def __str__(self):
return 'Day 7: Recursive Circus' return "Day 7: Recursive Circus"
def _get_programs(self, puzzle_input): def _get_programs(self, puzzle_input):
return [Program(data) for data in puzzle_input.splitlines()] return [Program(data) for data in puzzle_input.splitlines()]
def _get_discs(self, disc, programs): def _get_discs(self, disc, programs):
subdisc = [{'own_weight': p.weight, 'obj': p} for p in programs if p.name in disc] subdisc = [
{"own_weight": p.weight, "obj": p} for p in programs if p.name in disc
]
for t in subdisc: for t in subdisc:
t['weight'] = t['own_weight'] t["weight"] = t["own_weight"]
if t['obj'].has_disc(): if t["obj"].has_disc():
t['disc'] = self._get_discs(t['obj'].disc, programs) t["disc"] = self._get_discs(t["obj"].disc, programs)
t['weight'] += sum([st['weight'] for st in t['disc']]) t["weight"] += sum([st["weight"] for st in t["disc"]])
del (t['obj']) del t["obj"]
return subdisc return subdisc
def _find_unbalanced_disc(self, disc): def _find_unbalanced_disc(self, disc):
disc = sorted(disc, key=lambda t: t['weight']) disc = sorted(disc, key=lambda t: t["weight"])
if not disc[0]['weight'] < disc[1]['weight']: if not disc[0]["weight"] < disc[1]["weight"]:
disc = sorted(disc, key=lambda t: t['weight'], reverse=True) disc = sorted(disc, key=lambda t: t["weight"], reverse=True)
return disc[0], disc[1]['weight'] - disc[0]['weight'] return disc[0], disc[1]["weight"] - disc[0]["weight"]
def solve(self, puzzle_input): def solve(self, puzzle_input):
programs = self._get_programs(puzzle_input) programs = self._get_programs(puzzle_input)
@ -67,28 +69,30 @@ class Solution(BaseSolution):
unseen = p.unseen_discs(seen) unseen = p.unseen_discs(seen)
if len(unseen) > 0: if len(unseen) > 0:
seen += unseen seen += unseen
bottom_program = list(filter(lambda p: p.name not in seen, programs_with_discs))[0] bottom_program = list(
filter(lambda p: p.name not in seen, programs_with_discs)
)[0]
return bottom_program return bottom_program
def solve_again(self, puzzle_input): def solve_again(self, puzzle_input):
programs = self._get_programs(puzzle_input) programs = self._get_programs(puzzle_input)
bottom_program = self.solve(puzzle_input) bottom_program = self.solve(puzzle_input)
disc_tree = { disc_tree = {
'own_weight': bottom_program.weight, "own_weight": bottom_program.weight,
'disc': self._get_discs(bottom_program.disc, programs) "disc": self._get_discs(bottom_program.disc, programs),
} }
diff = -1 diff = -1
unbalanced = True unbalanced = True
while unbalanced: while unbalanced:
disc, new_diff = self._find_unbalanced_disc(disc_tree['disc']) disc, new_diff = self._find_unbalanced_disc(disc_tree["disc"])
if new_diff == 0: if new_diff == 0:
unbalanced = False unbalanced = False
else: else:
disc_tree = disc disc_tree = disc
diff = new_diff diff = new_diff
return disc_tree['own_weight'] + diff return disc_tree["own_weight"] + diff
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,22 +2,22 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '08.txt' input_file = "08.txt"
registry = {} registry = {}
def __str__(self): def __str__(self):
return 'Day 8: I Heard You Like Registers' return "Day 8: I Heard You Like Registers"
def _should_modify(self, x, comp, y): def _should_modify(self, x, comp, y):
if comp in ('==', '!=', '>=', '<=', '>', '<'): if comp in ("==", "!=", ">=", "<=", ">", "<"):
return eval('{:d} {} {:d}'.format(x, comp, y)) return eval("{:d} {} {:d}".format(x, comp, y))
return False return False
def _update_registry(self, registry, instruction): def _update_registry(self, registry, instruction):
r, action, n, _, k, comp, v = instruction.split() r, action, n, _, k, comp, v = instruction.split()
current = registry.get(r, 0) current = registry.get(r, 0)
if self._should_modify(registry.get(k, 0), comp, int(v)): if self._should_modify(registry.get(k, 0), comp, int(v)):
registry[r] = current + int(n) if action == 'inc' else current - int(n) registry[r] = current + int(n) if action == "inc" else current - int(n)
def solve(self, puzzle_input): def solve(self, puzzle_input):
registry = {} registry = {}
@ -35,6 +35,6 @@ class Solution(BaseSolution):
return max_value return max_value
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -4,30 +4,30 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '09.txt' input_file = "09.txt"
def __str__(self): def __str__(self):
return 'Day 9: Stream Processing' return "Day 9: Stream Processing"
def solve(self, puzzle_input): def solve(self, puzzle_input):
level = 0 level = 0
groups = [] groups = []
stream = re.sub(r'!.', '', puzzle_input) stream = re.sub(r"!.", "", puzzle_input)
stream = re.sub(r'<[^>]*>', '', stream) stream = re.sub(r"<[^>]*>", "", stream)
for c in stream: for c in stream:
if c == '{': if c == "{":
level += 1 level += 1
if c == '}': if c == "}":
groups.append(level) groups.append(level)
level -= 1 level -= 1
return sum(groups) return sum(groups)
def solve_again(self, puzzle_input): def solve_again(self, puzzle_input):
stream = re.sub(r'!.', '', puzzle_input) stream = re.sub(r"!.", "", puzzle_input)
garbage = re.findall(r'<([^>]*)>', stream) garbage = re.findall(r"<([^>]*)>", stream)
return sum([len(g) for g in garbage]) return sum([len(g) for g in garbage])
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,14 +2,14 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '10.txt' input_file = "10.txt"
list = [] list = []
l = 0 l = 0
skip_size = 0 skip_size = 0
pos = 0 pos = 0
def __str__(self): def __str__(self):
return 'Day 10: Knot Hash' return "Day 10: Knot Hash"
def reset(self, l=256): def reset(self, l=256):
self.list = list(range(l)) self.list = list(range(l))
@ -28,7 +28,7 @@ class Solution(BaseSolution):
def solve(self, puzzle_input, r=256): def solve(self, puzzle_input, r=256):
self.reset(r) self.reset(r)
for sublist_length in map(int, puzzle_input.split(',')): for sublist_length in map(int, puzzle_input.split(",")):
self.reverse(sublist_length) self.reverse(sublist_length)
return self.list[0] * self.list[1] return self.list[0] * self.list[1]
@ -38,10 +38,13 @@ class Solution(BaseSolution):
for _ in range(64): for _ in range(64):
for sublist_length in puzzle_input: for sublist_length in puzzle_input:
self.reverse(sublist_length) self.reverse(sublist_length)
dense_hash = [eval('^'.join(list(map(str, self.list[seq:seq+16])))) for seq in range(0, r, 16)] dense_hash = [
return ''.join(['{:x}'.format(int(i)).zfill(2) for i in dense_hash]) eval("^".join(list(map(str, self.list[seq : seq + 16]))))
for seq in range(0, r, 16)
]
return "".join(["{:x}".format(int(i)).zfill(2) for i in dense_hash])
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,20 +2,20 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '11.txt' input_file = "11.txt"
furthest_away = 0 furthest_away = 0
# https://www.redblobgames.com/grids/hexagons/#coordinates # https://www.redblobgames.com/grids/hexagons/#coordinates
DIRECTIONS = { DIRECTIONS = {
'n': lambda x, y, z: (x, y + 1, z - 1), "n": lambda x, y, z: (x, y + 1, z - 1),
'ne': lambda x, y, z: (x + 1, y, z - 1), "ne": lambda x, y, z: (x + 1, y, z - 1),
'se': lambda x, y, z: (x + 1, y - 1, z), "se": lambda x, y, z: (x + 1, y - 1, z),
's': lambda x, y, z: (x, y - 1, z + 1), "s": lambda x, y, z: (x, y - 1, z + 1),
'sw': lambda x, y, z: (x - 1, y, z + 1), "sw": lambda x, y, z: (x - 1, y, z + 1),
'nw': lambda x, y, z: (x - 1, y + 1, z), "nw": lambda x, y, z: (x - 1, y + 1, z),
} }
def __str__(self): def __str__(self):
return 'Day 11: Hex Ed' return "Day 11: Hex Ed"
def _get_end(self, steps): def _get_end(self, steps):
x = 0 x = 0
@ -28,14 +28,14 @@ class Solution(BaseSolution):
return abs(x), abs(y), abs(z) return abs(x), abs(y), abs(z)
def solve(self, puzzle_input): def solve(self, puzzle_input):
x, y, z = self._get_end(puzzle_input.split(',')) x, y, z = self._get_end(puzzle_input.split(","))
return max(x, y, z) return max(x, y, z)
def solve_again(self, puzzle_input): def solve_again(self, puzzle_input):
_, *__ = self._get_end(puzzle_input.split(',')) _, *__ = self._get_end(puzzle_input.split(","))
return self.furthest_away return self.furthest_away
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,17 +2,17 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '12.txt' input_file = "12.txt"
seen = [] seen = []
def __str__(self): def __str__(self):
return 'Day 12: Digital Plumber' return "Day 12: Digital Plumber"
def _walk(self, i, programs): def _walk(self, i, programs):
line = next(filter(lambda l: l.startswith('{} <->'.format(i)), programs)) line = next(filter(lambda l: l.startswith("{} <->".format(i)), programs))
piped = line.split()[2:] piped = line.split()[2:]
self.seen.add(i) self.seen.add(i)
for p in [int(p.replace(',', '')) for p in piped]: for p in [int(p.replace(",", "")) for p in piped]:
if p not in self.seen: if p not in self.seen:
self._walk(p, programs) self._walk(p, programs)
@ -34,6 +34,6 @@ class Solution(BaseSolution):
return groups return groups
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -4,12 +4,12 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '13.txt' input_file = "13.txt"
layers = [] layers = []
scanners = [] scanners = []
def __str__(self): def __str__(self):
return 'Day 13: Packet Scanners' return "Day 13: Packet Scanners"
def _move_scanners(self): def _move_scanners(self):
for p, l in enumerate(self.layers): for p, l in enumerate(self.layers):
@ -22,7 +22,10 @@ class Solution(BaseSolution):
self.scanners = [[0, 1] for l in self.layers] self.scanners = [[0, 1] for l in self.layers]
def _setup(self, puzzle_input): def _setup(self, puzzle_input):
pi = [tuple(map(int, line.strip().split(': '))) for line in puzzle_input.splitlines()] pi = [
tuple(map(int, line.strip().split(": ")))
for line in puzzle_input.splitlines()
]
self.layers = [0 for _ in range(pi[-1][0] + 1)] self.layers = [0 for _ in range(pi[-1][0] + 1)]
self._init_scanners() self._init_scanners()
for k, v in pi: for k, v in pi:
@ -55,13 +58,16 @@ class Solution(BaseSolution):
def solve_again(self, puzzle_input): def solve_again(self, puzzle_input):
# todo: rewrite! # todo: rewrite!
lines = [line.split(': ') for line in puzzle_input.splitlines()] lines = [line.split(": ") for line in puzzle_input.splitlines()]
heights = {int(pos): int(height) for pos, height in lines} heights = {int(pos): int(height) for pos, height in lines}
wait = next( wait = next(
wait for wait in itertools.count() if not any(self._scan(heights[pos], wait + pos) == 0 for pos in heights)) wait
for wait in itertools.count()
if not any(self._scan(heights[pos], wait + pos) == 0 for pos in heights)
)
return wait return wait
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -3,18 +3,18 @@ from solutions.day_10 import Solution as KnotHash
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '14.txt' input_file = "14.txt"
def __str__(self): def __str__(self):
return 'Day 14: Disk Defragmentation' return "Day 14: Disk Defragmentation"
def _get_grid(self, pi): def _get_grid(self, pi):
grid = '' grid = ""
ks = KnotHash() ks = KnotHash()
for n in range(128): for n in range(128):
s = '-'.join([pi, str(n)]) s = "-".join([pi, str(n)])
knothash = ks.solve_again(s) knothash = ks.solve_again(s)
grid += '{:0128b}'.format(int(knothash, 16)) grid += "{:0128b}".format(int(knothash, 16))
return grid return grid
def _find_regions(self, squares): def _find_regions(self, squares):
@ -31,10 +31,10 @@ class Solution(BaseSolution):
get_adjacent_square(i - 128) get_adjacent_square(i - 128)
if i % 128 < 127: if i % 128 < 127:
get_adjacent_square(i + 1) get_adjacent_square(i + 1)
if i < 128 ** 2 - 128: if i < 128**2 - 128:
get_adjacent_square(i + 128) get_adjacent_square(i + 128)
for i in range(128 ** 2): for i in range(128**2):
if i in seen or i not in squares: if i in seen or i not in squares:
continue continue
regions += 1 regions += 1
@ -47,10 +47,10 @@ class Solution(BaseSolution):
def solve_again(self, puzzle_input): def solve_again(self, puzzle_input):
grid = self._get_grid(puzzle_input) grid = self._get_grid(puzzle_input)
squares = [i for i, s in enumerate(list(grid)) if s == '1'] squares = [i for i, s in enumerate(list(grid)) if s == "1"]
return self._find_regions(squares) return self._find_regions(squares)
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '15.txt' input_file = "15.txt"
def __str__(self): def __str__(self):
return 'Day 15: Dueling Generators' return "Day 15: Dueling Generators"
def _calc(self, x, f, m=1): def _calc(self, x, f, m=1):
x = (x * f) % 2147483647 x = (x * f) % 2147483647
@ -21,7 +21,7 @@ class Solution(BaseSolution):
for _ in range(40 * 10**6): for _ in range(40 * 10**6):
a = self._calc(a, af) a = self._calc(a, af)
b = self._calc(b, bf) b = self._calc(b, bf)
if '{:b}'.format(a)[-16:] == '{:b}'.format(b)[-16:]: if "{:b}".format(a)[-16:] == "{:b}".format(b)[-16:]:
j += 1 j += 1
return j return j
@ -29,14 +29,14 @@ class Solution(BaseSolution):
af, bf = (16807, 48271) af, bf = (16807, 48271)
a, b = [int(pi.split()[-1]) for pi in puzzle_input.splitlines()] a, b = [int(pi.split()[-1]) for pi in puzzle_input.splitlines()]
j = 0 j = 0
for _ in range(5 * 10 ** 6): for _ in range(5 * 10**6):
a = self._calc(a, af, 4) a = self._calc(a, af, 4)
b = self._calc(b, bf, 8) b = self._calc(b, bf, 8)
if '{:b}'.format(a)[-16:] == '{:b}'.format(b)[-16:]: if "{:b}".format(a)[-16:] == "{:b}".format(b)[-16:]:
j += 1 j += 1
return j return j
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,24 +2,24 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '16.txt' input_file = "16.txt"
def __str__(self): def __str__(self):
return 'Day 16: Permutation Promenade' return "Day 16: Permutation Promenade"
def _move(self, programs, m, i): def _move(self, programs, m, i):
l = len(programs) l = len(programs)
if m == 's': if m == "s":
r = int(i) r = int(i)
return programs[-r:] + programs[:l - r] return programs[-r:] + programs[: l - r]
if m == 'x': if m == "x":
x, y = [int(s) for s in i.split('/')] x, y = [int(s) for s in i.split("/")]
z = programs[x] z = programs[x]
programs[x] = programs[y] programs[x] = programs[y]
programs[y] = z programs[y] = z
return programs return programs
if m == 'p': if m == "p":
xp, yp = i.split('/') xp, yp = i.split("/")
x = programs.index(xp) x = programs.index(xp)
y = programs.index(yp) y = programs.index(yp)
z = programs[x] z = programs[x]
@ -34,22 +34,22 @@ class Solution(BaseSolution):
def solve(self, puzzle_input, n=16): def solve(self, puzzle_input, n=16):
programs = [chr(c) for c in range(97, 97 + n)] programs = [chr(c) for c in range(97, 97 + n)]
moves = puzzle_input.split(',') moves = puzzle_input.split(",")
return ''.join(self._dance(programs, moves)) return "".join(self._dance(programs, moves))
def solve_again(self, puzzle_input, n=16): def solve_again(self, puzzle_input, n=16):
moves = puzzle_input.split(',') moves = puzzle_input.split(",")
initial = [chr(c) for c in range(97, 97 + n)] initial = [chr(c) for c in range(97, 97 + n)]
programs = list(self.solve(puzzle_input)) programs = list(self.solve(puzzle_input))
dances = 1 dances = 1
while not programs == initial: while not programs == initial:
programs = self._dance(programs, moves) programs = self._dance(programs, moves)
dances += 1 dances += 1
for _ in range(10 ** 9 % dances): for _ in range(10**9 % dances):
programs = self._dance(programs, moves) programs = self._dance(programs, moves)
return ''.join(programs) return "".join(programs)
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,10 +2,10 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '17.txt' input_file = "17.txt"
def __str__(self): def __str__(self):
return 'Day 17: Spinlock' return "Day 17: Spinlock"
def solve(self, puzzle_input): def solve(self, puzzle_input):
n = int(puzzle_input) n = int(puzzle_input)
@ -20,13 +20,13 @@ class Solution(BaseSolution):
pos = 0 pos = 0
n = int(puzzle_input) n = int(puzzle_input)
last_seen = 0 last_seen = 0
for i in range(1, 5 * 10 ** 7 + 1): for i in range(1, 5 * 10**7 + 1):
pos = (pos + n) % i + 1 pos = (pos + n) % i + 1
if pos == 1: if pos == 1:
last_seen = i last_seen = i
return last_seen return last_seen
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,16 +2,16 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '18.txt' input_file = "18.txt"
sound_freq = 0 sound_freq = 0
queue = [], [] queue = [], []
sent = [0, 0] sent = [0, 0]
def __str__(self): def __str__(self):
return 'Day 18: Duet' return "Day 18: Duet"
def _run(self, line, registry, swag_mode=True): def _run(self, line, registry, swag_mode=True):
actions = 'add jgz mod mul rcv set snd' actions = "add jgz mod mul rcv set snd"
a, *kv = line.split() a, *kv = line.split()
if len(kv) == 2: if len(kv) == 2:
k, v = kv k, v = kv
@ -19,9 +19,9 @@ class Solution(BaseSolution):
k = kv[0] k = kv[0]
v = None v = None
if a in actions: if a in actions:
if a == 'add' and k in registry: if a == "add" and k in registry:
registry[k] += registry[v] if v in registry else int(v) registry[k] += registry[v] if v in registry else int(v)
if a == 'jgz': # damn you, 'jgz 1 3' if a == "jgz": # damn you, 'jgz 1 3'
try: try:
k = int(k) k = int(k)
except ValueError: except ValueError:
@ -31,25 +31,25 @@ class Solution(BaseSolution):
except ValueError: except ValueError:
v = registry[v] if v in registry else 1 v = registry[v] if v in registry else 1
return v if k > 0 else 1 return v if k > 0 else 1
if a == 'mod' and k in registry: if a == "mod" and k in registry:
registry[k] %= registry[v] if v in registry else int(v) registry[k] %= registry[v] if v in registry else int(v)
if a == 'mul' and k in registry: if a == "mul" and k in registry:
registry[k] *= registry[v] if v in registry else int(v) registry[k] *= registry[v] if v in registry else int(v)
if a == 'set': if a == "set":
registry[k] = registry[v] if v in registry else int(v) registry[k] = registry[v] if v in registry else int(v)
if swag_mode: # Part 1: scientific wild-ass guess if swag_mode: # Part 1: scientific wild-ass guess
if a == 'rcv' and registry[k] != 0: if a == "rcv" and registry[k] != 0:
return self.STOP_SIGNAL return self.STOP_SIGNAL
if a == 'snd' and k in registry: if a == "snd" and k in registry:
self.sound_freq = registry[k] self.sound_freq = registry[k]
else: # part 2, actual instructions else: # part 2, actual instructions
if a == 'rcv': if a == "rcv":
if len(self.queue[registry['_id']]) == 0: if len(self.queue[registry["_id"]]) == 0:
return 0 return 0
registry[k] = self.queue[registry['_id']].pop(0) registry[k] = self.queue[registry["_id"]].pop(0)
if a == 'snd': if a == "snd":
self.sent[registry['_id']] += 1 self.sent[registry["_id"]] += 1
q = (registry['_id'] + 1) % 2 q = (registry["_id"] + 1) % 2
kk = registry[k] if k in registry else int(k) kk = registry[k] if k in registry else int(k)
self.queue[q].append(kk) self.queue[q].append(kk)
return 1 return 1
@ -65,7 +65,7 @@ class Solution(BaseSolution):
return self.sound_freq return self.sound_freq
def solve_again(self, puzzle_input): def solve_again(self, puzzle_input):
registry = {'p': 0, '_id': 0}, {'p': 1, '_id': 1} registry = {"p": 0, "_id": 0}, {"p": 1, "_id": 1}
lines = puzzle_input.splitlines() lines = puzzle_input.splitlines()
i = 0 i = 0
j = 0 j = 0
@ -89,6 +89,6 @@ class Solution(BaseSolution):
return self.sent[1] return self.sent[1]
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -2,48 +2,48 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '19.txt' input_file = "19.txt"
trim_input = False trim_input = False
def __str__(self): def __str__(self):
return 'Day 19: A Series of Tubes' return "Day 19: A Series of Tubes"
def _walk_maze(self, puzzle_input): def _walk_maze(self, puzzle_input):
DIRECTIONS = { DIRECTIONS = {
'D': (1, 0), "D": (1, 0),
'U': (-1, 0), "U": (-1, 0),
'R': (0, 1), "R": (0, 1),
'L': (0, -1), "L": (0, -1),
} }
maze = puzzle_input.splitlines() maze = puzzle_input.splitlines()
pos = (0, list(maze[0]).index('|')) pos = (0, list(maze[0]).index("|"))
d = DIRECTIONS['D'] d = DIRECTIONS["D"]
paths = '-|' paths = "-|"
steps = 0 steps = 0
seen = '' seen = ""
def _nc(nu): def _nc(nu):
np = (pos[0] + nu[0], pos[1] + nu[1]) np = (pos[0] + nu[0], pos[1] + nu[1])
if np[0] < len(maze) and np[1] < len(maze[np[0]]): if np[0] < len(maze) and np[1] < len(maze[np[0]]):
return maze[np[0]][np[1]] return maze[np[0]][np[1]]
else: else:
return ' ' return " "
while True: while True:
pos = (pos[0] + d[0], pos[1] + d[1]) pos = (pos[0] + d[0], pos[1] + d[1])
c = _nc((0, 0)) c = _nc((0, 0))
steps += 1 steps += 1
if c == '+': if c == "+":
nc = _nc(d) nc = _nc(d)
if nc == ' ': if nc == " ":
for v in DIRECTIONS.values(): for v in DIRECTIONS.values():
if -v[0] == d[0] and -v[1] == d[1]: if -v[0] == d[0] and -v[1] == d[1]:
continue continue
nc = _nc(v) nc = _nc(v)
if nc != ' ': if nc != " ":
d = v d = v
break break
elif c == ' ': elif c == " ":
break break
elif c not in paths: elif c not in paths:
seen += c seen += c
@ -58,6 +58,6 @@ class Solution(BaseSolution):
return steps return steps
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -6,15 +6,18 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '20.txt' input_file = "20.txt"
def __str__(self): def __str__(self):
return 'Day 20: Particle Swarm' return "Day 20: Particle Swarm"
def _get_particle(self, line): def _get_particle(self, line):
return [list(map(int, coordinate.split(','))) for coordinate in re.findall(r'.=<([^>]+)>', line)] return [
list(map(int, coordinate.split(",")))
for coordinate in re.findall(r".=<([^>]+)>", line)
]
def solve(self, puzzle_input, ticks=10 ** 4): def solve(self, puzzle_input, ticks=10**4):
particles = [self._get_particle(line) for line in puzzle_input.splitlines()] particles = [self._get_particle(line) for line in puzzle_input.splitlines()]
distances = [[] for _ in range(len(particles))] distances = [[] for _ in range(len(particles))]
for _ in range(ticks): for _ in range(ticks):
@ -27,10 +30,13 @@ class Solution(BaseSolution):
p[1] += v[1] p[1] += v[1]
p[2] += v[2] p[2] += v[2]
distances[i].append(sum(map(abs, p))) distances[i].append(sum(map(abs, p)))
d = sorted(map(lambda d: (d[0], sum(d[1]) / len(d[1])), enumerate(distances)), key=lambda x: x[1]) d = sorted(
map(lambda d: (d[0], sum(d[1]) / len(d[1])), enumerate(distances)),
key=lambda x: x[1],
)
return d[0][0] return d[0][0]
def solve_again(self, puzzle_input, ticks=10 ** 3): def solve_again(self, puzzle_input, ticks=10**3):
particles = [self._get_particle(line) for line in puzzle_input.splitlines()] particles = [self._get_particle(line) for line in puzzle_input.splitlines()]
for _ in range(ticks): for _ in range(ticks):
positions = collections.defaultdict(list) positions = collections.defaultdict(list)
@ -42,7 +48,7 @@ class Solution(BaseSolution):
p[0] += v[0] p[0] += v[0]
p[1] += v[1] p[1] += v[1]
p[2] += v[2] p[2] += v[2]
k = '-'.join(map(str, p)) k = "-".join(map(str, p))
positions[k].append(particle) positions[k].append(particle)
for duplicates in positions.values(): for duplicates in positions.values():
if len(duplicates) > 1: if len(duplicates) > 1:
@ -51,6 +57,6 @@ class Solution(BaseSolution):
return len(particles) return len(particles)
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -7,9 +7,9 @@ class Rule:
size = 0 size = 0
def __init__(self, line): def __init__(self, line):
pattern, output = line.split(' => ') pattern, output = line.split(" => ")
self.pattern = pattern.replace('/', '') self.pattern = pattern.replace("/", "")
self.output = output.replace('/', '') self.output = output.replace("/", "")
self.size = int(len(self.pattern) ** 0.5) self.size = int(len(self.pattern) ** 0.5)
self.patterns = [ self.patterns = [
@ -22,13 +22,18 @@ class Rule:
] ]
def __repr__(self): def __repr__(self):
return '[{}] {} -> {}'.format(self.size, self.pattern, self.output) return "[{}] {} -> {}".format(self.size, self.pattern, self.output)
def _r(self, b): def _r(self, b):
return ''.join(map(lambda g: ''.join(g), zip(*[b[s:s + self.size] for s in range(0, len(b), self.size)][::-1]))) return "".join(
map(
lambda g: "".join(g),
zip(*[b[s : s + self.size] for s in range(0, len(b), self.size)][::-1]),
)
)
def _f(self, b): def _f(self, b):
return b[-self.size:] + b[self.size:len(b) - self.size] + b[0:self.size] return b[-self.size :] + b[self.size : len(b) - self.size] + b[0 : self.size]
def matches(self, canvas): def matches(self, canvas):
return canvas in self.patterns return canvas in self.patterns
@ -38,13 +43,13 @@ class Rule:
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '21.txt' input_file = "21.txt"
def __str__(self): def __str__(self):
return 'Day 21: Fractal Art' return "Day 21: Fractal Art"
def _get_block(self, canvas, size, n=0): def _get_block(self, canvas, size, n=0):
s = '' s = ""
cl = int(len(canvas) ** 0.5) cl = int(len(canvas) ** 0.5)
x = n % (cl // size) x = n % (cl // size)
y = n // (cl // size) y = n // (cl // size)
@ -56,20 +61,22 @@ class Solution(BaseSolution):
def _join_blocks(self, blocks): def _join_blocks(self, blocks):
bl = len(blocks) bl = len(blocks)
c = int(bl ** 0.5) c = int(bl**0.5)
rl = int(len(blocks[0]) ** 0.5) rl = int(len(blocks[0]) ** 0.5)
canvas = '' canvas = ""
for i in range(0, bl, c): for i in range(0, bl, c):
for j in range(rl): for j in range(rl):
canvas += ''.join([block[j * rl:(j + 1) * rl] for block in blocks[i:i + c]]) canvas += "".join(
[block[j * rl : (j + 1) * rl] for block in blocks[i : i + c]]
)
return canvas return canvas
def solve(self, puzzle_input, iterations=5): def solve(self, puzzle_input, iterations=5):
canvas = '.#...####' canvas = ".#...####"
rules = [Rule(l.strip()) for l in puzzle_input.splitlines()] rules = [Rule(l.strip()) for l in puzzle_input.splitlines()]
for _ in range(iterations): for _ in range(iterations):
size = 2 if len(canvas) % 2 == 0 else 3 size = 2 if len(canvas) % 2 == 0 else 3
blocks = len(canvas) // (size ** 2) blocks = len(canvas) // (size**2)
cb = [] cb = []
for b in range(blocks): for b in range(blocks):
bc = self._get_block(canvas, size, b) bc = self._get_block(canvas, size, b)
@ -78,12 +85,12 @@ class Solution(BaseSolution):
r = rule[0] r = rule[0]
cb.append(r.enhance(bc)) cb.append(r.enhance(bc))
canvas = self._join_blocks(cb) canvas = self._join_blocks(cb)
return collections.Counter(canvas)['#'] return collections.Counter(canvas)["#"]
def solve_again(self, puzzle_input): def solve_again(self, puzzle_input):
return self.solve(puzzle_input, 18) return self.solve(puzzle_input, 18)
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -4,10 +4,10 @@ from solutions import BaseSolution
class Solution(BaseSolution): class Solution(BaseSolution):
input_file = '22.txt' input_file = "22.txt"
def __str__(self): def __str__(self):
return 'Day 22: Sporifica Virus' return "Day 22: Sporifica Virus"
infected = 0 infected = 0
@ -50,7 +50,7 @@ class Solution(BaseSolution):
dirs = dirs[1:] + [dirs[0]] dirs = dirs[1:] + [dirs[0]]
elif state == "#": elif state == "#":
dirs = [dirs[3]] + dirs[:3] dirs = [dirs[3]] + dirs[:3]
elif state == 'F': elif state == "F":
dirs = dirs[2:] + dirs[:2] dirs = dirs[2:] + dirs[:2]
return dirs return dirs
@ -61,16 +61,16 @@ class Solution(BaseSolution):
i, p = existing i, p = existing
return amap, p[2] return amap, p[2]
else: else:
amap.append([pos[0], pos[1], '.']) amap.append([pos[0], pos[1], "."])
return amap, '.' return amap, "."
def _update_state(self, amap, pos): def _update_state(self, amap, pos):
t = lambda x: x[1][0] == pos[0] and x[1][1] == pos[1] t = lambda x: x[1][0] == pos[0] and x[1][1] == pos[1]
existing = next(filter(t, enumerate(amap))) existing = next(filter(t, enumerate(amap)))
i, p = existing i, p = existing
if p[2] == '.': if p[2] == ".":
self.infected += 1 self.infected += 1
amap[i][2] = '.' if p[2] == '#' else '#' amap[i][2] = "." if p[2] == "#" else "#"
return amap return amap
def _move(self, pos, d): def _move(self, pos, d):
@ -80,12 +80,12 @@ class Solution(BaseSolution):
t = lambda x: x[1][0] == pos[0] and x[1][1] == pos[1] t = lambda x: x[1][0] == pos[0] and x[1][1] == pos[1]
existing = next(filter(t, enumerate(amap))) existing = next(filter(t, enumerate(amap)))
i, p = existing i, p = existing
if p[2] == '.': if p[2] == ".":
ns = 'W' ns = "W"
elif p[2] == 'W': elif p[2] == "W":
self.infected += 1 self.infected += 1
ns = "#" ns = "#"
elif p[2] == '#': elif p[2] == "#":
ns = "F" ns = "F"
else: else:
ns = "." ns = "."
@ -93,6 +93,6 @@ class Solution(BaseSolution):
return amap return amap
if __name__ == '__main__': if __name__ == "__main__":
solution = Solution() solution = Solution()
solution.show_results() solution.show_results()

View file

@ -0,0 +1,138 @@
from solutions import BaseSolution
from collections import defaultdict
class Solution(BaseSolution):
input_file = "23.txt"
sound_freq = 0
queue = [], []
sent = [0, 0]
def __str__(self):
return "Day 23: Coprocessor Conflagration"
def solve(self, puzzle_input):
R = defaultdict(int)
P = puzzle_input.splitlines()
i = 0
M = 0
while i < len(P):
j = 1
w, r, v = P[i].split()
match w:
case "set":
R[r] = R[v] if v in "abcdefgh" else int(v)
case "sub":
R[r] -= R[v] if v in "abcdefgh" else int(v)
case "mul":
M += 1
R[r] *= R[v] if v in "abcdefgh" else int(v)
case "jnz":
x = R[r] if r in "abcdefgh" else int(r)
if x != 0:
j = R[v] if v in "abcdefgh" else int(v)
i += j
return M
def solve_again(self, puzzle_input):
def isprime(num):
for n in range(2, int(num**0.5) + 1):
if num % n == 0:
return False
return True
b = 99 * 100 + 100_000
c = b + 17_000 + 1
return sum(not isprime(n) for n in range(b, c, 17))
def python_reconstruction(self, puzzle_input):
R = defaultdict(int)
R["a"] = 1
# 00: set b 99
R["b"] = 99
# 01: set c b
R["c"] = R["b"]
# 02: jnz a 2
if R["a"] == 0:
# 03: jnz 1 5
pass
else:
# 04: mul b 100
# 05: sub b -100000
R["b"] = R["b"] * 100 + 100_000
# 06: set c b
# 07: sub c -17000
R["c"] = R["b"] + 17_000
while True:
# 08: set f 1
# 09: set d 2
R["f"] = 1
R["d"] = 2
while True:
# 10: set e 2
R["e"] = 2
while True:
# 11: set g d
# 12: mul g e
# 13: sub g b
R["g"] = R["d"] * R["e"] - R["b"]
# 14: jnz g 2
if R["g"] == 0:
# 15: set f 0
R["f"] = 0
# 16: sub e -1
R["e"] += 1
# 17: set g e
# 18: sub g b
R["g"] = R["e"] - R["b"]
# 19: jnz g -8
if R["g"] == 0:
break
# 20: sub d -1
R["d"] += 1
# 21: set g d
# 22: sub g b
R["g"] = R["d"] - R["b"]
# 23: jnz g -13
if R["g"] == 0:
break
# 24: jnz f 2
if R["f"] == 0:
# 25: sub h -1
R["h"] += 1
# 26: set g b
# 27: sub g c
R["g"] = R["b"] - R["c"]
# 28: jnz g 2
if R["g"] == 0:
# 29: jnz 1 3
break
else:
# 30: sub b -17
R["b"] += 17
# 31: jnz 1 -23
return R["h"]
if __name__ == "__main__":
solution = Solution()
solution.show_results()

View file

@ -0,0 +1,51 @@
from solutions import BaseSolution
import re
class Solution(BaseSolution):
input_file = "24.txt"
def __str__(self):
return "Day 24: Electromagnetic Moat"
def solve(self, puzzle_input):
p1, _ = self._solve(puzzle_input)
return p1
def solve_again(self, puzzle_input):
_, p2 = self._solve(puzzle_input)
return p2
def _solve(self, puzzle_input):
components = [
tuple(map(int, re.findall(r"\d+", line))) for line in puzzle_input.split()
]
Q = [(c, []) for c in components if 0 in c]
S = 0
L = 0
LS = 0
while Q:
c, s = Q.pop()
if c in s:
S = max(S, sum(x + y for x, y in s))
if len(s) >= L:
L = len(s)
LS = max(LS, L * 1_000_000 + sum(x + y for x, y in s))
continue
cc = set(c) if not s else set(c) - set(s[-1])
if not cc:
cc = set(c)
for p1, p2 in components:
if p1 > 0 and p1 in cc:
Q.append(((p1, p2), s + [c]))
if p2 > 0 and p2 in cc:
Q.append(((p1, p2), s + [c]))
return S, LS % 1_000_000
if __name__ == "__main__":
solution = Solution()
solution.show_results()

View file

@ -0,0 +1,46 @@
from solutions import BaseSolution
from collections import defaultdict
import re
class Solution(BaseSolution):
input_file = "25.txt"
def __str__(self):
return "Day 25: The Halting Problem"
def solve(self, puzzle_input):
T = defaultdict(int)
p = 0
hd, *states = puzzle_input.split("\n\n")
S = int(re.findall(r"(\d+) steps", hd)[0])
s = re.findall(r"state (\w)\.", hd)[0]
P = {}
for state in states:
k = re.findall(r"state (\w)\:", state)[0]
v = [int(n) for n in re.findall(r"value (\d)\.", state)]
d = re.findall(r"(left|right)\.", state)
t = re.findall(r"state (\w)\.", state)
P[k] = (v[0], d[0], t[0], v[1], d[1], t[1])
for _ in range(S):
v0, d0, s0, v1, d1, s1 = P[s]
match T[p]:
case 0:
T[p] = v0
s = s0
p += 1 if d0 == "right" else -1
case 1:
T[p] = v1
s = s1
p += 1 if d1 == "right" else -1
return sum(T.values())
def solve_again(self, puzzle_input):
return "*"
if __name__ == "__main__":
solution = Solution()
solution.show_results()

View file

@ -8,18 +8,18 @@ class Day1TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_sums_equal_pairs(self): def test_sums_equal_pairs(self):
assert self.solution.solve('1122') == 3 assert self.solution.solve("1122") == 3
assert self.solution.solve('1111') == 4 assert self.solution.solve("1111") == 4
assert self.solution.solve('1234') == 0 assert self.solution.solve("1234") == 0
assert self.solution.solve('91212129') == 9 assert self.solution.solve("91212129") == 9
def test_sums_equal_pairs_halvway_around(self): def test_sums_equal_pairs_halvway_around(self):
assert self.solution.solve_again('1212') == 6 assert self.solution.solve_again("1212") == 6
assert self.solution.solve_again('1221') == 0 assert self.solution.solve_again("1221") == 0
assert self.solution.solve_again('123425') == 4 assert self.solution.solve_again("123425") == 4
assert self.solution.solve_again('123123') == 12 assert self.solution.solve_again("123123") == 12
assert self.solution.solve_again('12131415') == 4 assert self.solution.solve_again("12131415") == 4
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -13,19 +13,19 @@ class Day2TestCase(unittest.TestCase):
assert self.solution.get_diff([2, 4, 6, 8]) == 6 assert self.solution.get_diff([2, 4, 6, 8]) == 6
def test_calculates_checksum(self): def test_calculates_checksum(self):
puzzle_input = '\n'.join(['5 1 9 5', '7 5 3', '2 4 6 8']) puzzle_input = "\n".join(["5 1 9 5", "7 5 3", "2 4 6 8"])
assert self.solution.solve(puzzle_input) == 18 assert self.solution.solve(puzzle_input) == 18
def test_calculates_row_even_divisible(self): def test_calculates_row_even_divisible(self):
puzzle_input = '\n'.join(['5 9 2 8', '9 4 7 3', '3 8 6 5']) puzzle_input = "\n".join(["5 9 2 8", "9 4 7 3", "3 8 6 5"])
assert self.solution.get_even_divisible([5, 9, 2, 8]) == 4 assert self.solution.get_even_divisible([5, 9, 2, 8]) == 4
assert self.solution.get_even_divisible([9, 4, 7, 3]) == 3 assert self.solution.get_even_divisible([9, 4, 7, 3]) == 3
assert self.solution.get_even_divisible([3, 8, 6, 5]) == 2 assert self.solution.get_even_divisible([3, 8, 6, 5]) == 2
def test_calculates_row_result_sum(self): def test_calculates_row_result_sum(self):
puzzle_input = '\n'.join(['5 9 2 8', '9 4 7 3', '3 8 6 5']) puzzle_input = "\n".join(["5 9 2 8", "9 4 7 3", "3 8 6 5"])
assert self.solution.solve_again(puzzle_input) == 9 assert self.solution.solve_again(puzzle_input) == 9
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,11 +8,11 @@ class Day3TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_shortest_manhattan_distance(self): def test_shortest_manhattan_distance(self):
assert self.solution.solve('1') == 0 assert self.solution.solve("1") == 0
assert self.solution.solve('12') == 3 assert self.solution.solve("12") == 3
assert self.solution.solve('23') == 2 assert self.solution.solve("23") == 2
assert self.solution.solve('1024') == 31 assert self.solution.solve("1024") == 31
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -9,22 +9,22 @@ class Day4TestCase(unittest.TestCase):
def test_passphrase_has_only_unique_words(self): def test_passphrase_has_only_unique_words(self):
passphrases = [ passphrases = [
'aa bb cc dd ee', "aa bb cc dd ee",
'aa bb cc dd aa', "aa bb cc dd aa",
'aa bb cc dd aaa', "aa bb cc dd aaa",
] ]
assert self.solution.validate(passphrases[0]) == True assert self.solution.validate(passphrases[0]) == True
assert self.solution.validate(passphrases[1]) == False assert self.solution.validate(passphrases[1]) == False
assert self.solution.validate(passphrases[2]) == True assert self.solution.validate(passphrases[2]) == True
assert self.solution.solve('\n'.join(passphrases)) == 2 assert self.solution.solve("\n".join(passphrases)) == 2
def test_passphrase_has_no_anagrams(self): def test_passphrase_has_no_anagrams(self):
passphrases = [ passphrases = [
'abcde fghij', "abcde fghij",
'abcde xyz ecdab', "abcde xyz ecdab",
'a ab abc abd abf abj', "a ab abc abd abf abj",
'iiii oiii ooii oooi oooo', "iiii oiii ooii oooi oooo",
'oiii ioii iioi iiio', "oiii ioii iioi iiio",
] ]
assert self.solution.validate(passphrases[0], True) == True assert self.solution.validate(passphrases[0], True) == True
assert self.solution.validate(passphrases[1], True) == False assert self.solution.validate(passphrases[1], True) == False
@ -33,5 +33,5 @@ class Day4TestCase(unittest.TestCase):
assert self.solution.validate(passphrases[4], True) == False assert self.solution.validate(passphrases[4], True) == False
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,13 +8,29 @@ class Day5TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_calculate_exit_distance(self): def test_calculate_exit_distance(self):
puzzle_input = '\n'.join(['0', '3', '0', '1', '-3',]) puzzle_input = "\n".join(
[
"0",
"3",
"0",
"1",
"-3",
]
)
assert self.solution.solve(puzzle_input) == 5 assert self.solution.solve(puzzle_input) == 5
def test_calculate_stranger_exit_distance(self): def test_calculate_stranger_exit_distance(self):
puzzle_input = '\n'.join(['0', '3', '0', '1', '-3',]) puzzle_input = "\n".join(
[
"0",
"3",
"0",
"1",
"-3",
]
)
assert self.solution.solve_again(puzzle_input) == 10 assert self.solution.solve_again(puzzle_input) == 10
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,7 +8,7 @@ class Day6TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_count_redistribution_cycles(self): def test_count_redistribution_cycles(self):
puzzle_input = '0 2 7 0' puzzle_input = "0 2 7 0"
banks = list(map(int, puzzle_input.split())) banks = list(map(int, puzzle_input.split()))
assert self.solution.redistribute(banks) == (2, 4, 1, 2) assert self.solution.redistribute(banks) == (2, 4, 1, 2)
assert self.solution.redistribute((2, 4, 1, 2)) == (3, 1, 2, 3) assert self.solution.redistribute((2, 4, 1, 2)) == (3, 1, 2, 3)
@ -18,9 +18,9 @@ class Day6TestCase(unittest.TestCase):
assert self.solution.solve(puzzle_input) == 5 assert self.solution.solve(puzzle_input) == 5
def test_count_redistribution_cycles_again(self): def test_count_redistribution_cycles_again(self):
puzzle_input = '0 2 7 0' puzzle_input = "0 2 7 0"
assert self.solution.solve_again(puzzle_input) == 4 assert self.solution.solve_again(puzzle_input) == 4
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -5,7 +5,7 @@ from solutions.day_07 import Solution, Program
class Day7TestCase(unittest.TestCase): class Day7TestCase(unittest.TestCase):
def setUp(self): def setUp(self):
self.puzzle_input = ''' self.puzzle_input = """
pbga (66) pbga (66)
xhth (57) xhth (57)
ebii (61) ebii (61)
@ -19,24 +19,24 @@ class Day7TestCase(unittest.TestCase):
ugml (68) -> gyxo, ebii, jptl ugml (68) -> gyxo, ebii, jptl
gyxo (61) gyxo (61)
cntj (57) cntj (57)
'''.strip() """.strip()
self.solution = Solution() self.solution = Solution()
def test_find_bottom_tower(self): def test_find_bottom_tower(self):
p = Program('ugml (68) -> gyxo, ebii, jptl') p = Program("ugml (68) -> gyxo, ebii, jptl")
assert p.name == 'ugml' assert p.name == "ugml"
assert p.weight == 68 assert p.weight == 68
assert p.disc == ('gyxo', 'ebii', 'jptl') assert p.disc == ("gyxo", "ebii", "jptl")
p = Program('jptl (61)') p = Program("jptl (61)")
assert p.name == 'jptl' assert p.name == "jptl"
assert p.weight == 61 assert p.weight == 61
assert p.disc == () assert p.disc == ()
assert self.solution.solve(self.puzzle_input).name == 'tknk' assert self.solution.solve(self.puzzle_input).name == "tknk"
def test_find_weight_correction(self): def test_find_weight_correction(self):
corrected = self.solution.solve_again(self.puzzle_input) corrected = self.solution.solve_again(self.puzzle_input)
assert corrected == 60 assert corrected == 60
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,23 +8,23 @@ class Day8TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_largest_registry_value(self): def test_largest_registry_value(self):
puzzle_input = ''' puzzle_input = """
b inc 5 if a > 1 b inc 5 if a > 1
a inc 1 if b < 5 a inc 1 if b < 5
c dec -10 if a >= 1 c dec -10 if a >= 1
c inc -20 if c == 10 c inc -20 if c == 10
'''.strip() """.strip()
assert self.solution.solve(puzzle_input) == 1 assert self.solution.solve(puzzle_input) == 1
def test_largest_ath_registry_value(self): def test_largest_ath_registry_value(self):
puzzle_input = ''' puzzle_input = """
b inc 5 if a > 1 b inc 5 if a > 1
a inc 1 if b < 5 a inc 1 if b < 5
c dec -10 if a >= 1 c dec -10 if a >= 1
c inc -20 if c == 10 c inc -20 if c == 10
'''.strip() """.strip()
assert self.solution.solve_again(puzzle_input) == 10 assert self.solution.solve_again(puzzle_input) == 10
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,24 +8,24 @@ class Day9TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_calculates_score(self): def test_calculates_score(self):
assert self.solution.solve('{}') == 1 assert self.solution.solve("{}") == 1
assert self.solution.solve('{{{}}}') == 6 assert self.solution.solve("{{{}}}") == 6
assert self.solution.solve('{{},{}}') == 5 assert self.solution.solve("{{},{}}") == 5
assert self.solution.solve('{{{},{},{{}}}}') == 16 assert self.solution.solve("{{{},{},{{}}}}") == 16
assert self.solution.solve('{<a>,<a>,<a>,<a>}') == 1 assert self.solution.solve("{<a>,<a>,<a>,<a>}") == 1
assert self.solution.solve('{{<ab>},{<ab>},{<ab>},{<ab>}}') == 9 assert self.solution.solve("{{<ab>},{<ab>},{<ab>},{<ab>}}") == 9
assert self.solution.solve('{{<!!>},{<!!>},{<!!>},{<!!>}}') == 9 assert self.solution.solve("{{<!!>},{<!!>},{<!!>},{<!!>}}") == 9
assert self.solution.solve('{{<a!>},{<a!>},{<a!>},{<ab>}}') == 3 assert self.solution.solve("{{<a!>},{<a!>},{<a!>},{<ab>}}") == 3
def test_count_garbage(self): def test_count_garbage(self):
assert self.solution.solve_again('<>') == 0 assert self.solution.solve_again("<>") == 0
assert self.solution.solve_again('<random characters>') == 17 assert self.solution.solve_again("<random characters>") == 17
assert self.solution.solve_again('<<<<>') == 3 assert self.solution.solve_again("<<<<>") == 3
assert self.solution.solve_again('<{!>}>') == 2 assert self.solution.solve_again("<{!>}>") == 2
assert self.solution.solve_again('<!!>') == 0 assert self.solution.solve_again("<!!>") == 0
assert self.solution.solve_again('<!!!>>') == 0 assert self.solution.solve_again("<!!!>>") == 0
assert self.solution.solve_again('<{o"i!a,<{i<a>') == 10 assert self.solution.solve_again('<{o"i!a,<{i<a>') == 10
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -25,14 +25,16 @@ class Day10TestCase(unittest.TestCase):
assert self.solution.list == [3, 4, 2, 1, 0] assert self.solution.list == [3, 4, 2, 1, 0]
assert self.solution.skip_size == 4 assert self.solution.skip_size == 4
assert self.solution.pos == 4 assert self.solution.pos == 4
assert self.solution.solve('3,4,1,5', r=5) == 12 assert self.solution.solve("3,4,1,5", r=5) == 12
def test_dense_hash(self): def test_dense_hash(self):
assert self.solution.solve_again('') == 'a2582a3a0e66e6e86e3812dcb672a272' assert self.solution.solve_again("") == "a2582a3a0e66e6e86e3812dcb672a272"
assert self.solution.solve_again('AoC 2017') == '33efeb34ea91902bb2f59c9920caa6cd' assert (
assert self.solution.solve_again('1,2,3') == '3efbe78a8d82f29979031a4aa0b16a9d' self.solution.solve_again("AoC 2017") == "33efeb34ea91902bb2f59c9920caa6cd"
assert self.solution.solve_again('1,2,4') == '63960835bcdc130f0b66d7ff4f6a5a8e' )
assert self.solution.solve_again("1,2,3") == "3efbe78a8d82f29979031a4aa0b16a9d"
assert self.solution.solve_again("1,2,4") == "63960835bcdc130f0b66d7ff4f6a5a8e"
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,15 +8,15 @@ class Day11TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_distance(self): def test_distance(self):
assert self.solution.solve('ne,ne,ne') == 3 assert self.solution.solve("ne,ne,ne") == 3
assert self.solution.solve('ne,ne,sw,sw') == 0 assert self.solution.solve("ne,ne,sw,sw") == 0
assert self.solution.solve('ne,ne,s,s') == 2 assert self.solution.solve("ne,ne,s,s") == 2
assert self.solution.solve('se,sw,se,sw,sw') == 3 assert self.solution.solve("se,sw,se,sw,sw") == 3
def test_furthest_away(self): def test_furthest_away(self):
assert self.solution.solve_again('ne,ne,sw,sw') == 2 assert self.solution.solve_again("ne,ne,sw,sw") == 2
assert self.solution.solve_again('se,sw,se,sw,sw') == 3 assert self.solution.solve_again("se,sw,se,sw,sw") == 3
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,7 +8,7 @@ class Day12TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_connected_to_program0(self): def test_connected_to_program0(self):
puzzle_input = ''' puzzle_input = """
0 <-> 2 0 <-> 2
1 <-> 1 1 <-> 1
2 <-> 0, 3, 4 2 <-> 0, 3, 4
@ -16,11 +16,11 @@ class Day12TestCase(unittest.TestCase):
4 <-> 2, 3, 6 4 <-> 2, 3, 6
5 <-> 6 5 <-> 6
6 <-> 4, 5 6 <-> 4, 5
'''.strip() """.strip()
assert self.solution.solve(puzzle_input) == 6 assert self.solution.solve(puzzle_input) == 6
def test_group_coun(self): def test_group_coun(self):
puzzle_input = ''' puzzle_input = """
0 <-> 2 0 <-> 2
1 <-> 1 1 <-> 1
2 <-> 0, 3, 4 2 <-> 0, 3, 4
@ -28,9 +28,9 @@ class Day12TestCase(unittest.TestCase):
4 <-> 2, 3, 6 4 <-> 2, 3, 6
5 <-> 6 5 <-> 6
6 <-> 4, 5 6 <-> 4, 5
'''.strip() """.strip()
assert self.solution.solve_again(puzzle_input) == 2 assert self.solution.solve_again(puzzle_input) == 2
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,23 +8,23 @@ class Day13TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_get_through_firewall(self): def test_get_through_firewall(self):
puzzle_input = ''' puzzle_input = """
0: 3 0: 3
1: 2 1: 2
4: 4 4: 4
6: 4 6: 4
'''.strip() """.strip()
assert self.solution.solve(puzzle_input) == 24 assert self.solution.solve(puzzle_input) == 24
def test_wait(self): def test_wait(self):
puzzle_input = ''' puzzle_input = """
0: 3 0: 3
1: 2 1: 2
4: 4 4: 4
6: 4 6: 4
'''.strip() """.strip()
assert self.solution.solve_again(puzzle_input) == 10 assert self.solution.solve_again(puzzle_input) == 10
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,11 +8,11 @@ class Day14TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_used_squares(self): def test_used_squares(self):
assert self.solution.solve('flqrgnkx') == 8108 assert self.solution.solve("flqrgnkx") == 8108
def test_regions(self): def test_regions(self):
assert self.solution.solve_again('flqrgnkx') == 1242 assert self.solution.solve_again("flqrgnkx") == 1242
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,19 +8,19 @@ class Day15TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_40m_pairs(self): def test_40m_pairs(self):
puzzle_input = ''' puzzle_input = """
Generator A starts with 65 Generator A starts with 65
Generator B starts with 8921 Generator B starts with 8921
'''.strip() """.strip()
#assert self.solution.solve(puzzle_input) == 588 # assert self.solution.solve(puzzle_input) == 588
def test_5k_pairs(self): def test_5k_pairs(self):
puzzle_input = ''' puzzle_input = """
Generator A starts with 65 Generator A starts with 65
Generator B starts with 8921 Generator B starts with 8921
'''.strip() """.strip()
assert self.solution.solve_again(puzzle_input) == 309 assert self.solution.solve_again(puzzle_input) == 309
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,9 +8,9 @@ class Day16TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_something(self): def test_something(self):
puzzle_input = '''s1,x3/4,pe/b'''.strip() puzzle_input = """s1,x3/4,pe/b""".strip()
assert self.solution.solve(puzzle_input, 5) == 'baedc' assert self.solution.solve(puzzle_input, 5) == "baedc"
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,8 +8,8 @@ class Day17TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_something(self): def test_something(self):
assert self.solution.solve('3') == 638 assert self.solution.solve("3") == 638
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,7 +8,7 @@ class Day18TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_something(self): def test_something(self):
puzzle_input = ''' puzzle_input = """
set a 1 set a 1
add a 2 add a 2
mul a a mul a a
@ -19,11 +19,11 @@ class Day18TestCase(unittest.TestCase):
jgz a -1 jgz a -1
set a 1 set a 1
jgz a -2 jgz a -2
'''.strip() """.strip()
assert self.solution.solve(puzzle_input) == 4 assert self.solution.solve(puzzle_input) == 4
def test_something_else(self): def test_something_else(self):
puzzle_input = ''' puzzle_input = """
snd 1 snd 1
snd 2 snd 2
snd p snd p
@ -31,9 +31,9 @@ class Day18TestCase(unittest.TestCase):
rcv b rcv b
rcv c rcv c
rcv d rcv d
'''.strip() """.strip()
assert self.solution.solve_again(puzzle_input) == 3 assert self.solution.solve_again(puzzle_input) == 3
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,27 +8,27 @@ class Day19TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_seen(self): def test_seen(self):
puzzle_input = ''' | puzzle_input = """ |
| +--+ | +--+
A | C A | C
F---|----E|--+ F---|----E|--+
| | | D | | | D
+B-+ +--+ +B-+ +--+
''' """
assert self.solution.solve(puzzle_input) == 'ABCDEF' assert self.solution.solve(puzzle_input) == "ABCDEF"
def test_steps(self): def test_steps(self):
puzzle_input = ''' | puzzle_input = """ |
| +--+ | +--+
A | C A | C
F---|----E|--+ F---|----E|--+
| | | D | | | D
+B-+ +--+ +B-+ +--+
''' """
assert self.solution.solve_again(puzzle_input) == 38 assert self.solution.solve_again(puzzle_input) == 38
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,21 +8,21 @@ class Day20TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_shortest_distance_over_time(self): def test_shortest_distance_over_time(self):
puzzle_input = ''' puzzle_input = """
p=< 3,0,0>, v=< 2,0,0>, a=<-1,0,0> p=< 3,0,0>, v=< 2,0,0>, a=<-1,0,0>
p=< 4,0,0>, v=< 0,0,0>, a=<-2,0,0> p=< 4,0,0>, v=< 0,0,0>, a=<-2,0,0>
'''.strip() """.strip()
assert self.solution.solve(puzzle_input, 4) == 0 assert self.solution.solve(puzzle_input, 4) == 0
def test_something(self): def test_something(self):
puzzle_input = ''' puzzle_input = """
p=<-6,0,0>, v=< 3,0,0>, a=< 0,0,0> p=<-6,0,0>, v=< 3,0,0>, a=< 0,0,0>
p=<-4,0,0>, v=< 2,0,0>, a=< 0,0,0> p=<-4,0,0>, v=< 2,0,0>, a=< 0,0,0>
p=<-2,0,0>, v=< 1,0,0>, a=< 0,0,0> p=<-2,0,0>, v=< 1,0,0>, a=< 0,0,0>
p=< 3,0,0>, v=<-1,0,0>, a=< 0,0,0> p=< 3,0,0>, v=<-1,0,0>, a=< 0,0,0>
'''.strip() """.strip()
assert self.solution.solve_again(puzzle_input, 4) == 1 assert self.solution.solve_again(puzzle_input, 4) == 1
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -8,12 +8,12 @@ class Day21TestCase(unittest.TestCase):
self.solution = Solution() self.solution = Solution()
def test_something(self): def test_something(self):
puzzle_input = ''' puzzle_input = """
../.# => ##./#../... ../.# => ##./#../...
.#./..#/### => #..#/..../..../#..# .#./..#/### => #..#/..../..../#..#
'''.strip() """.strip()
assert self.solution.solve(puzzle_input, 2) == 12 assert self.solution.solve(puzzle_input, 2) == 12
if __name__ == '__main__': if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -26,5 +26,6 @@ class Day22TestCase(unittest.TestCase):
assert self.solution.solve_again(puzzle_input, 100) == 26 assert self.solution.solve_again(puzzle_input, 100) == 26
assert self.solution.solve_again(puzzle_input, 10000000) == 2511944 assert self.solution.solve_again(puzzle_input, 10000000) == 2511944
if __name__ == '__main__':
if __name__ == "__main__":
unittest.main() unittest.main()

View file

@ -1 +0,0 @@
__pycache__

View file

@ -1,40 +0,0 @@
from output import answer
n = 1
title = "The Tyranny of the Rocket Equation"
@answer(1, "Total fuel requirements are {}")
def part_1(o):
return o[0]
@answer(2, "Total fuel requirements are {} including fuel costs")
def part_2(o):
return o[1]
def solve(data):
lines = list(map(int, data.split()))
p1 = sum(n // 3 - 2 for n in lines)
p2 = 0
for fuel in lines:
rem = fuel
while rem > 0:
cost = rem // 3 - 2
p2 += max(0, cost)
rem = max(0, cost)
return p1, p2
if __name__ == "__main__":
with open("./input/01.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 3393938
assert b == 5088037

View file

@ -1,48 +0,0 @@
from output import answer
from output.intcode_computer import execute, parse
n = 2
title = "1202 Program Alarm"
@answer(1, "[intcode-0.1.0] Value of pos 0 is {} at halt signal")
def part_1(o):
return o[0]
@answer(2, "[intcode-0.1.1] 100 * noun + verb = {} for output 19690720")
def part_2(o):
return o[1]
def solve(data):
program = parse(data)
program[1] = 12
program[2] = 2
_code, state, *_unused = execute(program)
noun = 76 # found manually by binary search
verb = 21
p1 = state[0]
program[1] = noun
program[2] = verb
_code, state, *_unused = execute(program)
p2 = state[0]
if state[0] == 19690720:
p2 = 100 * noun + verb
return p1, p2
if __name__ == "__main__":
with open("./input/02.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 3306701
assert b == 7621

View file

@ -1,67 +0,0 @@
from collections import defaultdict
from output import answer
n = 3
title = "Crossed Wires"
directions = {
"U": (-1, 0),
"R": (0, 1),
"D": (1, 0),
"L": (0, -1),
}
@answer(
1, "As the crow flies, closest intersection Manhattan distance is {} units away"
)
def part_1(o):
return o[0]
@answer(2, "By travel, closest intersection Manhattan distance is {} units away")
def part_2(o):
return o[1]
def solve(inp):
wires = [line.split(",") for line in inp.split()]
seen = defaultdict(dict)
def follow(instructions, i):
visited = []
steps = 0
pos = (0, 0)
for instruction in instructions:
urdl, *l = instruction
distance = int("".join(l))
for _ in range(distance):
steps += 1
pos = (pos[0] + directions[urdl][0], pos[1] + directions[urdl][1])
visited.append(pos)
if i not in seen[pos]:
seen[pos][i] = steps
return set(visited)
p1w = []
for i, wire in enumerate(wires):
p1w.append(follow(wire, i))
p1 = min(sum(map(abs, i)) for i in p1w[0] & p1w[1])
p2 = min(sum(v.values()) for v in seen.values() if len(v) > 1)
return p1, p2
if __name__ == "__main__":
with open("./input/03.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 1337
assert b == 65356

View file

@ -1,44 +0,0 @@
from collections import Counter
from output import answer
n = 4
title = "Secure Container"
@answer(1, "{} combinations of valid passwords")
def part_1(o):
return o[0]
@answer(2, "{} combinations of valid passwords, including important detail")
def part_2(o):
return o[1]
def solve(data):
a, b = data.split("-")
def v1(s):
return "".join(sorted(s)) == s and any(x == y for x, y in zip(s, s[1:]))
def v2(s):
return "".join(sorted(s)) == s and 2 in Counter(s).values()
p1 = sum(v1(str(pw)) for pw in range(int(a), int(b) + 1))
p2 = sum(v2(str(pw)) for pw in range(int(a), int(b) + 1))
return p1, p2
if __name__ == "__main__":
with open("./input/04.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 544
assert b == 334

View file

@ -1,40 +0,0 @@
from output import answer
from output.intcode_computer import execute, parse
n = 5
title = "Sunny with a Chance of Asteroids"
@answer(1, "[intcode-0.2.0] Program diagnostic code, ID 1: {}")
def part_1(o):
return o[0]
@answer(2, "[intcode-0.2.1] Program diagnostic code, ID 5: {}")
def part_2(o):
return o[1]
def solve(data):
program = parse(data)
_code, _state, _cursorpos, rb, stdout = execute(program, stdin=[1])
p1 = max(stdout)
_code, _state, _cursorpos, rb, stdout = execute(program, stdin=[5])
p2 = stdout[0]
return p1, p2
if __name__ == "__main__":
with open("./input/05.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 16434972
assert b == 16694270

View file

@ -1,58 +0,0 @@
from collections import defaultdict
from output import answer
n = 6
title = "Universal Orbit Map"
@answer(1, "{} direct and indirect orbits")
def part_1(o):
return o[0]
@answer(2, "Orbit transfers needed for you to share orbit with Santa: {}")
def part_2(o):
return o[1]
def solve(data):
heritage = defaultdict(str)
for parent, child in [line.split(")") for line in data.split()]:
heritage[child] = parent
p1 = sum(len(ancestry(heritage, v)) for v in heritage.keys())
a = ancestry(heritage, "YOU")
b = ancestry(heritage, "SAN")
shared = len(set(a) & set(b))
p2 = sum(
[
len(a) - shared,
len(b) - shared,
]
)
return p1, p2
def ancestry(parents, child):
k = child
lineage = []
while k in parents:
lineage.append(parents[k])
k = parents[k]
return lineage[::-1]
if __name__ == "__main__":
with open("./input/06.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 271151
assert b == 388

View file

@ -1,77 +0,0 @@
from collections import defaultdict
from itertools import permutations
from output import answer
from output.intcode_computer import execute, parse
n = 7
title = "Amplification Circuit"
@answer(
1,
"[intcode 0.3.0] The highest achievable signal to the thruster is {}",
)
def part_1(o):
return o[0]
@answer(
2,
"[intcode 0.3.0] By creating a feedback loop, the highest achievable signal to the thruster is {}",
)
def part_2(o):
return o[0]
def solve(data):
program = parse(data)
thruster_signals = []
for settings in map(list, permutations(range(5))):
o = 0
for ps in settings:
_code, _state, _n, _rb, so = execute(program, stdin=[ps, o])
o = so.pop(0)
thruster_signals.append(o)
p1 = max(thruster_signals)
thruster_signals = []
for settings in map(list, permutations(range(5, 10))):
o = [0]
finished = set()
paused = defaultdict(tuple)
while len(finished) < 5:
for amp, ps in enumerate(settings):
if paused[amp]:
program, resume_at = paused[amp]
del paused[amp]
code, state, n, _rb, so = execute(program, stdin=o, n=resume_at)
else:
code, state, n, _rb, so = execute(program, stdin=[ps, *o])
if code == 3:
paused[amp] = (
list(state.values()),
n,
)
o = so
if code == 99:
finished.add(amp)
o = so
thruster_signals.append(o[-1])
p2 = max(thruster_signals)
return p1, p2
if __name__ == "__main__":
with open("./input/07.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 398674
assert b == 39431233

View file

@ -1,50 +0,0 @@
from collections import Counter
from textwrap import wrap
from output import answer
n = 8
title = "Space Image Format"
@answer(1, "The product of all 1s and 2s in the layer with fewest 0s is {}")
def part_1(o):
return o[0]
@answer(2, "The message is {}, the decoded image looks like above")
def part_2(o):
return o[1]
def solve(data):
layers = sorted(map(Counter, wrap(data, 25 * 6)), key=lambda c: c["0"])
width, height = 25, 6
a = layers[0]["1"]
b = layers[0]["2"]
p1 = a * b
layers = wrap(data, width * height)
pixels = zip(*layers)
lit = map(
lambda s: s.replace("0", ".").replace("1", "#"),
map(lambda p: next(filter(lambda x: x != "2", p)), pixels),
)
matrix = "\n".join(wrap("".join(lit), width))
print(matrix)
p2 = "CYUAH"
return p1, p2
if __name__ == "__main__":
with open("./input/08.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 2500
assert b == "CYUAH"

View file

@ -1,86 +0,0 @@
from output import answer
from output.intcode_computer import execute, parse
n = 9
title = "Sensor Boost"
@answer(1, "[intcode 0.3.1] BOOST keycode: {}")
def part_1(o):
return o[0]
@answer(2, "[intcode 0.3.1] Distress signal coordinates: {}")
def part_2(o):
return o[1]
def solve(data):
program = parse(data)
p12 = []
for inp in [1, 2]:
_c, _s, _n, _rb, outputs = execute(program, stdin=[inp])
p12.append(outputs.pop(0))
return p12
if __name__ == "__main__":
assert execute(
[
109,
1,
204,
-1,
1001,
100,
1,
100,
1008,
100,
16,
101,
1006,
101,
0,
99,
]
)[4] == [
109,
1,
204,
-1,
1001,
100,
1,
100,
1008,
100,
16,
101,
1006,
101,
0,
99,
]
assert len(str(execute([1102, 34915192, 34915192, 7, 4, 7, 99, 0])[4][0])) == 16
assert 1125899906842624 in execute([104, 1125899906842624, 99])[4]
assert execute([109, -1, 4, 1, 99])[4][0] == -1
assert execute([109, -1, 104, 1, 99])[4][0] == 1
assert execute([109, -1, 204, 1, 99])[4][0] == 109
assert execute([109, 1, 9, 2, 204, -6, 99])[4][0] == 204
assert execute([109, 1, 109, 9, 204, -6, 99])[4][0] == 204
assert execute([109, 1, 209, -1, 204, -106, 99])[4][0] == 204
assert execute([109, 1, 3, 3, 204, 2, 99], stdin=[666])[4][0] == 666
assert execute([109, 1, 203, 2, 204, 2, 99], stdin=[666])[4][0] == 666
assert execute([109, 6, 21001, 9, 25, 1, 104, 0, 99, 49])[4][0] == 74
with open("./input/09.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 2351176124
assert b == 73110

View file

@ -1,96 +0,0 @@
from collections import OrderedDict, defaultdict, deque
from math import atan2
from output import answer
n = 10
title = "Monitoring Station"
@answer(1, "The monitor station will see {} asteroids at best")
def part_1(o):
return o[0]
@answer(
2,
"The asteroid at y=3 x=17 (checksum {}) will be the 200th lazer vapored asteroid, making some elf happy",
)
def part_2(o):
return o[1]
def solve(data):
matrix = data.strip().split()
pos, visible = _map_visible_asteroids(matrix)
p1 = len(set(dict(visible).values()))
targets_upper = defaultdict(list)
targets_lower = defaultdict(list)
targets = dict()
for xy, angle in visible:
if angle < 0:
targets_lower[angle].append(xy)
else:
targets_upper[angle].append(xy)
for k, v in OrderedDict(
sorted(targets_upper.items(), key=lambda x: x[0], reverse=True)
+ sorted(targets_lower.items(), key=lambda x: x[0], reverse=True)
).items():
targets[k] = deque(
sorted(
v,
key=lambda xy: sum(abs(pos[i] - xy[i]) for i in range(2)),
)
)
vapored = 0
x = 0
y = 0
while vapored < 200:
popped = False
for tk in targets.keys():
if targets[tk]:
x, y = targets[tk].pop()
vapored += 1
popped = True
if vapored == 200:
break
if not popped:
break
p2 = x * 100 + y
return p1, p2
def _map_visible_asteroids(matrix):
asteroids = []
visible = defaultdict(int)
for y in range(len(matrix)):
for x in range(len(matrix[0])):
if matrix[y][x] == "#":
asteroids.append((x, y))
for a, b in asteroids:
visible[(a, b)] = [
((x, y), atan2(x - a, y - b)) for x, y in asteroids if (a, b) != (x, y)
]
return max(visible.items(), key=lambda x: len(set(dict(x[1]).values())))
if __name__ == "__main__":
with open("./input/10.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 292
assert b == 317

View file

@ -1,95 +0,0 @@
from collections import defaultdict
from output import answer
from output.intcode_computer import execute, parse
n = 11
title = "Space Police"
DIRS = [
(-1, 0),
(0, -1),
(1, 0),
(0, 1),
]
COLORS = [".", "#"]
CL = ["black", "white"]
DL = ["UP", "LEFT", "BOTTOM", "RIGHT"]
TL = ["RIGHT", "LEFT"]
@answer(1, "[intcode 0.3.2] Robot paints {} panes at least once")
def part_1(o):
return o[0]
@answer(
2,
'[intcode 0.3.2] The hull has registration identifier "{}" freshly painted, see above',
)
def part_2(o):
return o[1]
def solve(data):
program = parse(data)
path, pos, d = _paint(program)
p1 = len(path)
path, pos, d = _paint(program, 1)
print(_inspect(path.copy(), pos, d))
p2 = "JZPJRAGJ"
return p1, p2
def _paint(program, initial=0):
pos = (0, 0)
d = 0
path = defaultdict(int)
path[pos] = initial
n = 0
rb = 0
code = 0
while True:
code, program, n, rb, outputs = execute(program, n=n, rb=rb, stdin=[path[pos]])
if code == 99:
break
if outputs:
paint, turn_to = outputs
path[pos] = paint
d = (d - 1 if turn_to == 1 else d + 1) % 4
pos = (pos[0] + DIRS[d][0], pos[1] + DIRS[d][1])
return path, pos, d
def _inspect(path, p, d):
pk = path.keys()
startx = min(map(lambda yx: yx[1], pk)) - 1
endx = max(map(lambda yx: yx[1], pk)) + 2
starty = min(map(lambda yx: yx[0], pk)) - 1
endy = max(map(lambda yx: yx[0], pk)) + 2
matrix = [
[COLORS[path[(y, x)]] for x in range(startx, endx)] for y in range(starty, endy)
]
y, x = p
matrix[abs(starty) + y][abs(startx) + x] = "^<v>"[d]
return "\n".join(["".join(line) for line in matrix])
if __name__ == "__main__":
with open("./input/11.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 2720
assert b == "JZPJRAGJ"

View file

@ -1,109 +0,0 @@
from itertools import combinations
from math import lcm
from output import answer, ints # , matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg
n = 12
title = "The N-Body Problem"
@answer(1, "Answer is {}")
def part_1(outputs):
return outputs[0]
@answer(2, "Actually, answer is {}")
def part_2(outputs):
return outputs[1]
def solve(data):
M = [[ints(l), [0, 0, 0]] for l in data.splitlines()]
def G(m):
def U(x, y):
if x > y:
return -1
elif x < y:
return 1
return 0
for i, j in combinations(range(len(m)), r=2):
m1 = m[i]
m2 = m[j]
g1, v1 = m1
g2, v2 = m2
x1, y1, z1 = g1
x2, y2, z2 = g2
a1, b1, c1 = v1
a2, b2, c2 = v2
m[i] = [
g1,
[
a1 + U(x1, x2),
b1 + U(y1, y2),
c1 + U(z1, z2),
],
]
m[j] = [
g2,
[
a2 + U(x2, x1),
b2 + U(y2, y1),
c2 + U(z2, z1),
],
]
return m
def V(m):
nm = []
for gv in m:
g, v = gv
x1, y1, z1 = g
x2, y2, z2 = v
nm.append(
[
[
x1 + x2,
y1 + y2,
z1 + z2,
],
v,
]
)
return nm
P1 = M.copy()
for _ in range(1000):
P1 = V(G(P1))
p1 = sum(sum(map(abs, p)) * sum(map(abs, k)) for p, k in P1)
P2 = M.copy()
p2 = []
for i, igv in enumerate(zip(*[g for g, v in P2])):
igv = [(g, 0) for g in igv]
Q = P2.copy()
C = 0
while True:
Q = V(G(Q))
C += 1
sg = list(zip(*[g for g, v in Q]))[i]
sv = list(zip(*[v for g, v in Q]))[i]
if list(zip(sg, sv)) == igv:
p2.append(C)
break
p2 = lcm(*p2)
return p1, p2
if __name__ == "__main__":
with open("./input/12.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 12466
assert b == 360689156787864

View file

@ -1,37 +0,0 @@
from output import answer
from output.intcode_computer import execute, parse
n = 13
title = "Care Package"
@answer(1, "When game exists, {} block tiles are on the screen")
def part_1(o):
return o[0]
@answer(2, "Score when all blocks are broken: {}")
def part_2(o):
return o[1]
def solve(data):
program = parse(data)
_code, _s, _n, _rb, outputs = execute(program)
p1 = sum(outputs[i + 2] == 2 for i in range(0, len(outputs), 3))
p2 = None
return p1, p2
if __name__ == "__main__":
with open("./input/13.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
# b = part_2(inp)
assert a == 355

View file

@ -1,69 +0,0 @@
from collections import defaultdict
from output import answer # , matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg
n = 14
title = "Space Stoichiometry"
BAEST = 1_000_000_000_000
@answer(1, "Answer is {}")
def part_1(outputs):
return outputs[0]
@answer(2, "Actually, answer is {}")
def part_2(outputs):
return outputs[1]
def solve(data, verbose=False):
T = defaultdict(lambda: [0, {}])
for l in data.splitlines():
i, o = l.split(" => ")
a, o = o.split()
T[o][0] += int(a)
for vk in i.split(", "):
v, k = vk.split()
T[o][1][k] = int(v)
def f(i):
Q = {"FUEL": i}
S = defaultdict(int)
while True:
if len(Q) == 1 and "ORE" in Q:
break
nk = next(n for n in Q if n != "ORE")
rq = Q.pop(nk)
q, r = T[nk]
d = rq // q
m = rq % q
if m > 0:
S[nk] = q - m
d += 1
for k, v in r.items():
Q[k] = Q.get(k, 0) + d * v - S[k]
del S[k]
return Q["ORE"]
p1 = f(1)
p2 = 7659732 # found manually
if BAEST - f(p2) <= 0:
print(BAEST - f(p2))
assert BAEST - f(p2) >= 0
return p1, p2
if __name__ == "__main__":
with open("./input/14.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == 198984
assert b == 7659732

View file

@ -1,53 +0,0 @@
from output import answer # , matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg
n = 16
title = "Flawed Frequency Transmission"
BAEST = 10_000
@answer(1, "Answer is {}")
def part_1(outputs):
return outputs[0]
@answer(2, "Actually, answer is {}")
def part_2(outputs):
return outputs[1]
def solve(data):
bp = [0, 1, 0, -1]
o = int(data[:7])
s = [int(c) for c in data]
s2 = s * BAEST
for _ in range(100):
s = [
abs(sum(d * bp[j // (i + 1) % 4] for j, d in enumerate(s, 1))) % 10
for i in range(len(s))
]
p1 = "".join(map(str, s[:8]))
# for x in range(100):
# print(f"{x}%")
# s2 = [
# abs(sum(d * bp[j // (i + 1) % 4] for j, d in enumerate(s2, 1))) % 10
# for i in range(len(s2))
# ]
# p2 = "".join(map(str, s2[o : o + 8]))
p2 = "41781287"
return p1, p2
if __name__ == "__main__":
with open("./input/16.txt", "r") as f:
inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
b = part_2(inp)
assert a == "58100105"
assert b == "41781287"

View file

@ -1,55 +0,0 @@
from output import answer, matrix # D, DD, ADJ, ints, mhd, mdbg, vdbg
n = 18
title = "Many-Worlds Interpretation"
@answer(1, "Answer is {}")
def part_1(outputs):
return outputs[0]
@answer(2, "Actually, answer is {}")
def part_2(outputs):
return outputs[1]
def solve(data):
M, h, w = matrix(data)
p = None
for r in range(h):
for c in range(w):
if M[r][c] == "@":
p = (r, c)
break
if p:
break
print(p)
return None, None
if __name__ == "__main__":
# use dummy data
inp = """
#########
#b.A.@.a#
#########
""".strip()
# uncomment to instead use stdin
# import sys; inp = sys.stdin.read().strip()
# uncomment to use AoC provided puzzle input
# with open("./input/18.txt", "r") as f:
# inp = f.read().strip()
inp = solve(inp)
a = part_1(inp)
# b = part_2(inp)
# uncomment and replace 0 with actual output to refactor code
# and ensure nonbreaking changes
# assert a == 0
# assert b == 0

View file

@ -1,268 +0,0 @@
import sys
from collections import defaultdict
sys.set_int_max_str_digits(999_999)
"""
intcode computer, AoC 2019
Changelog
=========
0.3.3
-----
Patch release (no specific day)
- Deprecating noun and verb. Those are now up to consumer to provide.
0.3.2
-----
Patch release (day 9 part 1-2, day 11 part 1-2).
- Return relative base upon input suspension
- Improve intcode debugger
- Fix errorous state restoration upon resuming upon input
0.3.1
-----
Patch release (day 7 part 1-2).
- Supports relative parameter mode
0.3.0
-----
Minor release (day 7 part 1-2).
BREAKING CHANGE: execute() now returns 4 values.
- now: exit code, state at halt, instruction position at halt, and captured stdout
- before: final state, and captured stdout
Changes:
- Add support for a sequence of stdins
- Add interactive param to ask for manual (interactive) input on input opcode
- Add verbose param to show more output in interactive input mode
- Will now halt with code 3 (input) when input is required, stdin is empty and interactive input mode is not enabled
0.2.1
-----
Patch release (day 5 part 2).
- Add operation 5: set instruction pointer to value at parameter 2 position, based on value at parameter 1 position
- Add operation 6: set instruction pointer to value at parameter 2 position, based on value at parameter 1 position
- Add operation 7: compares values in parameter 1 position and parameter 2 position, stores at parameter 3 position
- Add operation 8: compares values in parameter 1 position and parameter 2 position, stores at parameter 3 position
0.2.0
-----
Minor release (day 5 part 1).
- Support immediate parameter mode
- Add stdin argument
- Make arguments optional: noun, verb
- Capture and return stdout
- Add operation 3: store stdin to parameter 1 position
- Add operation 4: output value at parameter 1 position to stdout
0.1.1
-----
Patch release (day 2 part 2).
- Remove initial modification 1=12, 2=2
- Add noun argument, stored at pos 1 (default value: 12)
- Add verb argument, stored at pos 2 (default value: 2)
0.1.0
-----
Initial version (day 2 part 1).
- Support positional parameter mode
- Add operation 1: adds parameter 1 to parameter 2, store to parameter 3 position
- Add operation 2: multiply parameter 1 with parameter 2, store to parameter 3 position
"""
__version__ = "0.3.3"
def parse(data):
return [int(s) for s in data.split(",")]
def execute(
program,
stdin=[],
debug=False,
interactive=False,
verbose=False,
n=0,
rb=0,
):
if verbose:
title = f"intcode computer, version {__version__}"
print("".join("=" for _ in title))
print(title)
print("".join("=" for _ in title))
state = defaultdict(int)
if isinstance(program, list):
for k, v in zip(range(len(program)), program):
state[k] = v
else:
state = program.copy()
stdout = []
def halt(code):
return code, state, n, rb, stdout
if debug and n > 0:
print(f"@{str(n).zfill(4)} [resuming program, stdin={stdin}]")
while True:
instruction = state[n]
opcode = instruction % 100
modes = str(instruction // 100).zfill(3)[::-1]
if opcode not in (1, 2, 3, 4, 5, 6, 7, 8, 9, 99):
print("opcode={opcode} not implemented, halting")
return halt(-2)
if opcode == 1:
a = state[n + 1]
b = state[n + 2]
c = n + 3
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
if debug:
print(f"@{str(n).zfill(4)} {opcode}_ADDITION | {x} + {y} to {p})")
state[p] = x + y
n += 4
if opcode == 2:
a = state[n + 1]
b = state[n + 2]
c = n + 3
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
if debug:
print(f"@{str(n).zfill(4)} {opcode}_MULTIPLY | {x} * {y} to {p}")
state[p] = x * y
n += 4
if opcode == 3:
a = n + 1
p = state[a]
if modes[0] == "2":
p += rb
if debug:
print(
f"@{str(n).zfill(4)} {opcode}_INPUT | target={p}, queued={stdin}, interactive={interactive}"
)
if stdin:
state[p] = stdin.pop(0)
else:
if interactive:
manual = int(input("> "))
state[p] = manual
print(f"set STDIN to {manual} at pos {p}")
else:
if debug:
print(f"@{str(n).zfill(4)} [suspended, awaiting input ...]")
return halt(3)
n += 2
if opcode == 4:
a = state[n + 1]
x = _values(state, modes, rb, a)
stdout.append(x)
if verbose:
print(x)
if debug:
print(f"@{str(n).zfill(4)} {opcode}_OUTPUT | echo {x}")
n += 2
if opcode == 5:
a = state[n + 1]
b = state[n + 2]
x, y = _values(state, modes, rb, a, b)
if x != 0:
if debug:
print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-1 | {x} != 0, n={y}")
n = y
else:
if debug:
print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-1 | {x} != 0, ignoring")
n += 3
if opcode == 6:
a = state[n + 1]
b = state[n + 2]
x, y = _values(state, modes, rb, a, b)
if x == 0:
if debug:
print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-0 | {x} == 0, n={y}")
n = y
else:
if debug:
print(f"@{str(n).zfill(4)} {opcode}_JMP-IF-0 | {x} == 0, ignoring")
n += 3
if opcode == 7:
a = state[n + 1]
b = state[n + 2]
c = n + 3
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
if debug:
print(f"@{str(n).zfill(4)} {opcode}_LESSTHAN | {x} < {y} to {p}")
state[p] = int(x < y)
n += 4
if opcode == 8:
a = state[n + 1]
b = state[n + 2]
c = n + 3
x, y = _values(state, modes, rb, a, b)
p = state[c]
if modes[2] == "2":
p += rb
if debug:
print(f"@{str(n).zfill(4)} {opcode}_EQUALS | {x} == {y} to {p}")
state[p] = int(x == y)
n += 4
if opcode == 9:
a = state[n + 1]
x = _values(state, modes, rb, a)
if debug:
print(f"@{str(n).zfill(4)} {opcode}_RELBASE | {rb} + {x}")
rb += x
n += 2
if opcode == 99:
if debug:
print(f"@{str(n).zfill(4)} {opcode}_HALT | n={n}")
return halt(99)
return halt(-1)
def _values(state, modes, rb, *parameters):
# for i, v in enumerate(parameters):
# if modes[i] == "0" and v < 0:
# print("================ ERROR =================")
# print("Negative index provided to position mode")
# if modes[i] == "2" and rb + v < 0:
# print("================ ERROR =================")
# print("Negative index provided to relative mode")
if len(parameters) > 1:
return [_value(state, modes, rb, k, v) for k, v in enumerate(parameters)]
return _value(state, modes, rb, 0, parameters[0])
def _value(state, modes, rb, m, s):
if modes[m] == "1":
return s
if modes[m] == "2":
return state[s + rb]
return state[s]

View file

@ -1,35 +1,48 @@
Advent of Code 2019 # Advent of Code 2024
===================
Solutions for #aoc2019 in Python 3 (3.11.5). Solutions for #aoc2024 in Python 3 (3.12.7).
Help scripts Programming setup:
------------
- Lenovo Thinkpad X260
- Arch Linux with Hyprland
- Zed editor (Ruff, Pyright)
- Firefox
- Alacritty
## Help scripts
Display all solved puzzles: Display all solved puzzles:
python aoc.py python aoc.py
To bootstrap a new puzzle (creates `input/<day_no>.txt` and `output/day_<day_no>.py`): To bootstrap a new puzzle (creates `input/<day_no>.txt` and `output/day_<day_no>.py`):
python aoc.py <dag_no> "<puzzle_name>" python aoc.py <day_no> new
Manually copy the puzzle input from https://adventofcode.com and paste it in `input/<day_no>.txt` Manually copy the puzzle input from https://adventofcode.com and paste it in `input/<day_no>.txt`
to start coding. to start coding.
wl-paste > input/<day_no>.txt
Solve separate puzzle (replace `XX` with the puzzle number): Solve separate puzzle (replace `XX` with the puzzle number):
python -m output.day_XX python -m output.day_XX
Solve separate puzzle using stdin (replace `XX` with the puzzle number): Solve separate puzzle using stdin (replace `XX` with the puzzle number):
xclip -selection clipboard -o | python -m output.day_XX wl-paste | python -m output.day_XX
cat tmpfile | python -m output.day_XX cat tmpfile | python -m output.day_XX
Execute separate puzzle on file save (replace `XX` with the puzzle number): Execute separate puzzle on file save (replace `XX` with the puzzle number):
ls output/*.py | entr -c -s 'xclip -selection clipboard -o | python -m output.day_XX' ls output/*.py | entr -c -s 'wlpaste | python -m output.day_XX'
ls output/*.py | entr -c -s 'cat tmpfile | python -m output.day_XX' ls output/*.py | entr -c -s 'cat tmpfile | python -m output.day_XX'
ls output/*.py | entr -c -r python -m output.day_XX ls output/*.py | entr -c -r python -m output.day_XX
(requires `entr` and `xclip`, Mac users can instead use `pbpaste`) (requires `entr` and `wl-paste`, Mac users can instead use `pbpaste`. If you
prefer X at Linux, use `xclip -selection clipboard -o`).
To lint files:
ls output/*.py | entr -r -c flake8 output --ignore=E741,E501,E203

115
2024-python/aoc.py Normal file
View file

@ -0,0 +1,115 @@
import sys
from pathlib import Path
def headline(n):
"""Print day number and name, followed by a ruler. Used by the answer decorator"""
print(f"\nDay {int(n)} - https://adventofcode.com/{year}/day/{int(n)}\n")
year = 2024
try:
_, day_no, *name = sys.argv
except ValueError:
day_no = None
name = None
Path("./input").mkdir(parents=True, exist_ok=True)
Path("./output").mkdir(parents=True, exist_ok=True)
if day_no and name:
name = " ".join(name)
padded_no = day_no.zfill(2)
with open("output/day_{}.py".format(padded_no), "w") as s:
s.write(
f"""
import re
from collections import deque, Counter, defaultdict
from heapq import heappop, heappush
from itertools import compress, combinations, chain, permutations
from output import matrix, D, DD, ADJ, ints, mhd, mdbg, vdbg, cw, ccw, bk
def solve(data):
p1 = None
p2 = None
return p1, p2
if __name__ == "__main__":
import os
# use dummy data
inp = \"\"\"
replace me
\"\"\".strip()
# uncomment to instead use stdin
# import sys; inp = sys.stdin.read().strip()
# uncomment to use AoC provided puzzle input
# with open("./input/{padded_no}.txt", "r") as f:
# inp = f.read().strip()
# uncomment to do initial data processing shared by part 1-2
p1, p2 = solve(inp)
print(p1)
os.system(f"echo {{p1}} | wl-copy")
# print(p2)
# os.system(f"echo {{p2}} | wl-copy")
# uncomment and replace 0 with actual output to refactor code
# and ensure nonbreaking changes
# assert p1 == 0
# assert p2 == 0
""".strip()
+ "\n"
)
exit(0)
print(
f"\n\033[95m\033[1mAdvent of Code {year}\033[0m"
"\n###################"
"\n\n\033[96mby Anders Englöf Ytterström\033[0m"
)
stars = 0
for i in [str(n).zfill(2) for n in range(1, 26)]:
if not day_no or day_no.zfill(2) == i:
try:
day = __import__(
"output.day_{}".format(i),
globals(),
locals(),
["solve"],
0,
)
with open(f"./input/{i}.txt", "r") as f:
data = f.read().strip()
headline(i)
try:
data = day.presolve(data)
except AttributeError:
pass
try:
p1, p2 = day.solve(data)
except AttributeError:
pass
if p1:
print(f" \033[92m1)\033[0m {p1}")
stars += 1
if p2:
print(f" \033[92m2)\033[0m {p2}")
stars += 1
except IOError:
pass
except ImportError:
pass
if not day_no:
print(f"\nStars: {stars}")
print("".join("*" if n < stars else "" for n in range(50)))
print("")

View file

@ -0,0 +1,195 @@
import re
# Directions/Adjacents for 2D matrices, in the order UP, RIGHT, DOWN, LEFT
D = [
(-1, 0),
(0, 1),
(1, 0),
(0, -1),
]
Di = [
(-1, -1),
(-1, 1),
(1, -1),
(1, 1),
]
# Directions for 2D matrices, as a dict with keys U, R, D, L
DD = {
"U": (-1, 0),
"R": (0, 1),
"D": (1, 0),
"L": (0, -1),
}
DDa = {
"^": (-1, 0),
">": (0, 1),
"v": (1, 0),
"<": (0, -1),
}
# Adjacent relative positions including diagonals for 2D matrices, in the order NW, N, NE, W, E, SW, S, SE
ADJ = [
(-1, -1),
(-1, 0),
(1, -1),
(0, -1),
(0, 1),
(1, 1),
(1, 0),
(1, -1),
]
def ints(s):
"""Extract all integers from a string"""
return [int(n) for n in re.findall(r"\d+", s)]
def sints(s):
"""Extract all signed integers from a string"""
return [int(n) for n in re.findall(r"-?\d+", s)]
def mhd(a, b):
"""Calculates the Manhattan distance between 2 positions in the format (y, x) or (x, y)"""
ar, ac = a
br, bc = b
return abs(ar - br) + abs(ac - bc)
def matrix(d):
"""Transform a string into an iterable matrix. Returns the matrix, row count and col count"""
m = [tuple(r) for r in d.split()]
return m, len(m), len(m[0])
def mdbg(m):
"""Print-debug a matrix"""
for r in m:
print("".join(r))
def vdbg(seen, h, w):
"""Print-debug visited positions of a matrix"""
for r in range(h):
print("".join(["#" if (r, c) in seen else " " for c in range(w)]))
def cw(y, x):
"""Flip a (y, x) direction counterwise: U->R, R->D, D->L, L->U.
>>> cw(-1, 0)
(0, 1)
>>> cw(0, 1)
(1, 0)
>>> cw(1, 0)
(0, -1)
>>> cw(0, -1)
(-1, 0)
"""
return (x, y) if y == 0 else (x, -y)
def ccw(y, x):
"""Flip a (y, x) direction counterwise: U->L, L->D, D->R, R->U.
>>> ccw(-1, 0)
(0, -1)
>>> ccw(0, -1)
(1, 0)
>>> ccw(1, 0)
(0, 1)
>>> ccw(0, 1)
(-1, 0)
"""
return (x, y) if x == 0 else (-x, y)
def bfs(S, E=None):
"""BFS algorithm, equal weighted nodes"""
seen = set()
q = [(S, 0)]
g = {} # graph, required to be provided at some point
while q:
m, w = q.pop(0)
if m in seen:
continue
seen.add(m)
# investigate here
for s in g[m]:
q.append((s, w + 1))
# return insights
def mhd_search(r, c, R=20):
"""returns all coords that are within R manhattan distance from (r,c)"""
p = set()
for d in range(1, R + 1):
p.add((r, c + d))
p.add((r, c - d))
p.add((r + d, c))
p.add((r - d, c))
for dd in range(d):
p.add((r - dd, c - d + dd))
p.add((r + dd, c - d + dd))
p.add((r - dd, c - dd + d))
p.add((r + dd, c - dd + d))
return p
def dijkstras(grid, start, target):
"""
1. Create an array that holds the distance of each vertex from the starting
vertex. Initially, set this distance to infinity for all vertices except
the starting vertex which should be set to 0.
2. Create a priority queue (heap) and insert the starting vertex with its
distance of 0.
3. While there are still vertices left in the priority queue, select the vertex
with the smallest recorded distance from the starting vertex and visit its
neighboring vertices.
4. For each neighboring vertex, check if it is visited already or not. If it
isnt visited yet, calculate its tentative distance by adding its weight
to the smallest distance found so far for its parent/previous node
(starting vertex in case of first-level vertices).
5. If this tentative distance is smaller than previously recorded value
(if any), update it in our distances array.
6. Finally, add this visited vertex with its updated distance to our priority
queue and repeat step-3 until we have reached our destination or exhausted
all nodes.
"""
import heapq
target = max(grid)
seen = set()
queue = [(start, 0)]
while queue:
cost, pos, direction, steps = heapq.heappop(queue)
y, x = pos
dy, dx = direction
if pos == target:
return cost
if ((pos, "and stuff")) in seen:
continue
seen.add((pos, "and stuff"))
neighbors = []
for n in neighbors:
heapq.heappush(queue, ("stuffs"))
return -1
def bk(graph, p, r=set(), x=set()):
"""Bron-Kerbosch algoritm, no pivot: https://en.wikipedia.org/wiki/Bron%E2%80%93Kerbosch_algorithm"""
if not p and not x:
yield r
while p:
v = p.pop()
yield from bk(graph, p & set(graph[v]), r | {v}, x & graph[v])
x.add(v)

View file

@ -0,0 +1,20 @@
from output import ints
def solve(puzzle_input):
left, right = [sorted(col) for col in zip(*map(ints, puzzle_input.splitlines()))]
p1 = sum(abs(l - r) for l, r in zip(left, right))
p2 = sum(k * right.count(k) for k in left)
return p1, p2
if __name__ == "__main__":
with open("./input/01.txt", "r") as f:
puzzle_input = f.read().strip()
p1, p2 = solve(puzzle_input)
print(p1)
print(p2)

View file

@ -0,0 +1,34 @@
from output import ints
def solve(data):
reports = [ints(line) for line in data.splitlines()]
p1 = 0
p2 = 0
for report in reports:
if issafe(report):
p1 += 1
for i in range(len(report)):
if issafe(report[:i] + report[i + 1 :]):
p2 += 1
break
return p1, p2
def issafe(report):
diffs = [a - b for a, b in zip(report, report[1:])]
return all(-3 <= d <= -1 for d in diffs) or all(1 <= d <= 3 for d in diffs)
if __name__ == "__main__":
with open("./input/02.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,26 @@
import re
from math import prod
def solve(data):
needle = re.compile(r"mul\((\d{1,3}),(\d{1,3})\)")
p1 = sum(prod(map(int, factors)) for factors in re.findall(needle, data))
p2 = sum(
sum(prod(map(int, factors)) for factors in re.findall(needle, chunk))
for chunk in data.split("do")
if not chunk.startswith("n't")
)
return p1, p2
if __name__ == "__main__":
with open("./input/03.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

View file

@ -0,0 +1,92 @@
import re
def solve(data):
row_count = len(data.split())
col_count = len(data.split()[0])
grid = [c for r in data.split() for c in r]
grid_rotated = [c for r in zip(*data.split()[::-1]) for c in r]
needle = r"(?=(XMAS|SAMX))"
p1 = len(re.findall(needle, data))
p1 += len(
re.findall(
needle,
"\n".join(["".join(r) for r in list(zip(*data.split()))]),
)
)
for cells, o in [
(grid, col_count),
(grid[::-1], col_count),
(grid_rotated, row_count),
(grid_rotated[::-1], row_count),
]:
p1 += sum(
all(
[
i % o < (o - 3),
cells[i] == "X",
cells[i + o + 1] == "M",
cells[i + 2 * (o + 1)] == "A",
cells[i + 3 * (o + 1)] == "S",
]
)
for i in range(len(cells) - 3 * (o + 1))
)
p2 = sum(
[
1 <= (i % col_count) < col_count - 1
and grid[i] == "A"
and any(
[
all(
[
grid[i - col_count - 1] == "M",
grid[i - col_count + 1] == "M",
grid[i + col_count - 1] == "S",
grid[i + col_count + 1] == "S",
]
),
all(
[
grid[i - col_count - 1] == "S",
grid[i - col_count + 1] == "S",
grid[i + col_count - 1] == "M",
grid[i + col_count + 1] == "M",
]
),
all(
[
grid[i - col_count - 1] == "M",
grid[i - col_count + 1] == "S",
grid[i + col_count - 1] == "M",
grid[i + col_count + 1] == "S",
]
),
all(
[
grid[i - col_count - 1] == "S",
grid[i - col_count + 1] == "M",
grid[i + col_count - 1] == "S",
grid[i + col_count + 1] == "M",
]
),
]
)
for i in range(col_count + 1, len(grid) - col_count - 1)
]
)
return p1, p2
if __name__ == "__main__":
with open("./input/04.txt", "r") as f:
inp = f.read().strip()
p1, p2 = solve(inp)
print(p1)
print(p2)

Some files were not shown because too many files have changed in this diff Show more