»Day 1: Chronal Calibration«

In the first day’s task, we are given a list of frequency changes.


  f_changes = list(map(int,open('day1.txt').readlines()))

Part 1

The first part asks us to do nothing else than to sum up the frequency changes.

  print(f'Part 1: {sum(f_changes)}')

Part 2

The slightly more complicated second part asks us to find the first frequency that appears twice, when cummulatively summing over the list of frequency changes.

  from itertools import accumulate, cycle
  seen = {0}
  print(f'Part 2: {next(x for x in accumulate(cycle(f_changes)) if x in seen or seen.add(x))}')

»Day 2: Inventory Management System«

The second day’s task presents us with a list of box IDs.


  ids = list(map(lambda x: x.strip(), open('day2.txt').readlines()))

Part 1

We are to calculate the checksum of the inventory, for which we need to multiply two quantities. The first one is the number of IDs in which some letter appears exactly twice, the second one the number of IDs in which some letter appears exactly thrice.

  from collections import Counter
  print(f'Part 1: {sum(2 in Counter(i).values() for i in ids)*sum(3 in Counter(i).values() for i in ids)}')

Part 2

In the second part, another piece of information is revealed to us: among the pairs of IDs, there is exactly one pair which differs exactly in one letter. We simply define a distance function between two IDs to be the number of letters in which they differ, and iterate over all pairs.

  dist = lambda x,y: sum(i != j for i,j in zip(x, y))
  print(f'Part 2: {next("".join(i for i,j in zip(x,y) if i==j) for x in ids for y in ids if dist(x,y) == 1)}')

»Day 3: No Matter How You Slice It«

In the third day, we are given a list of (possibly overlaping) rectangles in a grid.


  import re
  rectangles = [tuple(map(int, re.findall(r'-?\d+', l))) for l in open('day3.txt').readlines()]

Part 1

The first part asks us to count the number of squares in the grid that are part of at least two rectangles.

  from itertools import chain
  from collections import Counter

  c = Counter(chain(*[[(x+i,y+j) for i in range(dx) for j in range(dy)] for _,x,y,dx,dy in rectangles]))
  print(f'Part 1: {sum(v >=2 for v in c.values())}')

Part 2

We are informed that precisely one of the rectangles does not overlap with any other rectangle. The problem asks us to find its ID. For this, we make use of the previously created Counter object.

  print(f'Part 2: {next(id for id,x,y,dx,dy in rectangles if all(c[(x+i, y+j)]==1 for i in range(dx) for j in range(dy)))}')  

»Day 4: Repose Record«

In the fourth day, we are given a number of records about the sleeping habits of guards. In the preprocessing step, we shall parse from the input the intervals of naps for each of the guards.


  import re
  from collections import defaultdict

  records = [re.findall(r"\[.* (.*)\] (.*)", l)[0] for l in sorted(open('day4.txt').readlines())]
  sleepd = defaultdict(list)

  sleep_start, current_id = -1, -1
  for time, log in records:
      if log.startswith('Guard'):
          id = int(re.findall(r"\d+", log)[0])
      elif log.startswith('falls'):
          sleep_start = int(time.split(':')[1])
          sleepd[id].append((sleep_start, int(time.split(':')[1])))

Part 1

There are two quantities that we need to find: the guard that has cummulatively slept for the most time, and the minute that this guard spend asleep the most.

  most_sleepy_guide = max(sleepd, key=lambda x: sum(e-s for s, e in sleepd[x]))
  most_sleepy_minute = max(range(60), key=lambda x: sum(s <= x < e for s, e in sleepd[most_sleepy_guide]))
  print(f'Part 1: {most_sleepy_guide * most_sleepy_minute}')

Part 2

For the part 2, we need to find the following quantity: among all pairs (guard, minute) we need to find the one where the given guide has spend the given minute asleep the most.

  from itertools import product
  most_sleepy_pair = max(product(sleepd.keys(), range(60)), key=lambda x: sum(s <= x[1] < e for s, e in sleepd[x[0]]))
  print(f'Part 2: {most_sleepy_pair[0] * most_sleepy_pair[1]}')

»Day 5: Alchemical Reduction«

In the problem of day 5, we are given a string consisting of lowercase and uppercase alphabet characters. When two letters of opposite lower/upper case meet in the word, they annihilate themselves.


  word = open('day5.txt').read().strip()

In the first part, we are bound to find the length of the string after all annihilations have taken place. For this, a single pass over a stack will be sufficient.

Part 1

  def react(s):
      stack = []
      for x in s:
          if (stack and stack[-1] != x and stack[-1].lower() == x.lower()) or stack.append(x):
      return len(stack)

  print('Part 1:', react(word))

In the second part, we are permitted to remove one letter (both lowercase and uppercase) from the string. Our goal is to find out what the minimum possible length of a string is after such a removal and after all annihilations take place. Brute force is sufficient here.

Part 2

  print('Part 2:', min(react(''.join(x for x in word if unit not in [x.lower(), x.upper()])) for unit in set(word.lower())))