~quf/computers-are-fast-2020

29f94177e36c850c7e44599cfe914f6f3000a88e — Lukas Himbert 3 years ago
public release
A  => .builds/alpine.yml +76 -0
@@ 1,76 @@
image: alpine/edge
packages:
 - tcc
sources:
 - https://git.sr.ht:~quf/computers-are-fast-2020
tasks:
 - run-01: |
     cd computers-are-fast-2020
     tcc -run src/01.c < testinputs/01 | tee out
     cmp out testoutputs/01
     rm out
 - run-02: |
     cd computers-are-fast-2020
     tcc -run src/02.c < testinputs/02 | tee out
     cmp out testoutputs/02
     rm out
 - run-03: |
     cd computers-are-fast-2020
     tcc -run src/03.c < testinputs/03 | tee out
     cmp out testoutputs/03
     rm out
 - run-04: |
     cd computers-are-fast-2020
     tcc -run src/04.c < testinputs/04 | tee out
     cmp out testoutputs/04
     rm out
 - run-05: |
     cd computers-are-fast-2020
     tcc -run src/05.c < testinputs/05 | tee out
     cmp out testoutputs/05
     rm out
 - run-06: |
     cd computers-are-fast-2020
     tcc -run src/06.c < testinputs/06 | tee out
     cmp out testoutputs/06
     rm out
 - run-07: |
     cd computers-are-fast-2020
     tcc -run src/07.c < testinputs/07 | tee out
     cmp out testoutputs/07
     rm out
 - run-08: |
     cd computers-are-fast-2020
     tcc -run src/08.c < testinputs/08 | tee out
     cmp out testoutputs/08
     rm out
 - run-09: |
     cd computers-are-fast-2020
     tcc -run src/09.c < testinputs/09 | tee out
     cmp out testoutputs/09
     rm out
 - run-10: |
     cd computers-are-fast-2020
     tcc -run src/10.c < testinputs/10 | tee out
     cmp out testoutputs/10
     rm out
 - run-11: |
     cd computers-are-fast-2020
     tcc -run src/11.c < testinputs/11 | tee out
     cmp out testoutputs/11
     rm out
 - run-12: |
     cd computers-are-fast-2020
     tcc -run src/12.c < testinputs/12 | tee out
     cmp out testoutputs/12
     rm out
 - run-13: |
     cd computers-are-fast-2020
     tcc -run src/13.c < testinputs/13 | tee out
     cmp out testoutputs/13
     rm out
 - run-14: |
     cd computers-are-fast-2020
     tcc -run src/14.c < testinputs/14 | tee out
     cmp out testoutputs/14
     rm out

A  => .builds/archlinux.yml +91 -0
@@ 1,91 @@
image: archlinux
packages:
 - gcc
 - make
sources:
 - https://git.sr.ht:~quf/computers-are-fast-2020
tasks:
 - run-01: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/01
     bin/01 < testinputs/01 | tee out
     cmp out testoutputs/01
     rm out
 - run-02: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/02
     bin/02 < testinputs/02 | tee out
     cmp out testoutputs/02
     rm out
 - run-03: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/03
     bin/03 < testinputs/03 | tee out
     cmp out testoutputs/03
     rm out
 - run-04: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/04
     bin/04 < testinputs/04 | tee out
     cmp out testoutputs/04
     rm out
 - run-05: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/05
     bin/05 < testinputs/05 | tee out
     cmp out testoutputs/05
     rm out
 - run-06: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/06
     bin/06 < testinputs/06 | tee out
     cmp out testoutputs/06
     rm out
 - run-07: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/07
     bin/07 < testinputs/07 | tee out
     cmp out testoutputs/07
     rm out
 - run-08: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/08
     bin/08 < testinputs/08 | tee out
     cmp out testoutputs/08
     rm out
 - run-09: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/09
     bin/09 < testinputs/09 | tee out
     cmp out testoutputs/09
     rm out
 - run-10: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/10
     bin/10 < testinputs/10 | tee out
     cmp out testoutputs/10
     rm out
 - run-11: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/11
     bin/11 < testinputs/11 | tee out
     cmp out testoutputs/11
     rm out
 - run-12: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/12
     bin/12 < testinputs/12 | tee out
     cmp out testoutputs/12
     rm out
 - run-13: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/13
     bin/13 < testinputs/13 | tee out
     cmp out testoutputs/13
     rm out
 - run-14: |
     cd computers-are-fast-2020
     CC=gcc CFLAGS=-fsanitize=address,undefined make bin/14
     env ASAN_OPTIONS=detect_leaks=0 bin/14 < testinputs/14 | tee out
     cmp out testoutputs/14
     rm out

A  => .builds/freebsd.yml +88 -0
@@ 1,88 @@
image: freebsd/latest
sources:
 - https://git.sr.ht:~quf/computers-are-fast-2020
tasks:
 - run-01: |
     cd computers-are-fast-2020
     make bin/01
     bin/01 < testinputs/01 | tee out
     cmp out testoutputs/01
     rm out
 - run-02: |
     cd computers-are-fast-2020
     make bin/02
     bin/02 < testinputs/02 | tee out
     cmp out testoutputs/02
     rm out
 - run-03: |
     cd computers-are-fast-2020
     make bin/03
     bin/03 < testinputs/03 | tee out
     cmp out testoutputs/03
     rm out
 - run-04: |
     cd computers-are-fast-2020
     make bin/04
     bin/04 < testinputs/04 | tee out
     cmp out testoutputs/04
     rm out
 - run-05: |
     cd computers-are-fast-2020
     make bin/05
     bin/05 < testinputs/05 | tee out
     cmp out testoutputs/05
     rm out
 - run-06: |
     cd computers-are-fast-2020
     make bin/06
     bin/06 < testinputs/06 | tee out
     cmp out testoutputs/06
     rm out
 - run-07: |
     cd computers-are-fast-2020
     CFLAGS=-Wno-missing-braces make bin/07
     bin/07 < testinputs/07 | tee out
     cmp out testoutputs/07
     rm out
 - run-08: |
     cd computers-are-fast-2020
     CFLAGS=-Wno-missing-braces make bin/08
     bin/08 < testinputs/08 | tee out
     cmp out testoutputs/08
     rm out
 - run-09: |
     cd computers-are-fast-2020
     make bin/09
     bin/09 < testinputs/09 | tee out
     cmp out testoutputs/09
     rm out
 - run-10: |
     cd computers-are-fast-2020
     make bin/10
     bin/10 < testinputs/10 | tee out
     cmp out testoutputs/10
     rm out
 - run-11: |
     cd computers-are-fast-2020
     make bin/11
     bin/11 < testinputs/11 | tee out
     cmp out testoutputs/11
     rm out
 - run-12: |
     cd computers-are-fast-2020
     make bin/12
     bin/12 < testinputs/12 | tee out
     cmp out testoutputs/12
     rm out
 - run-13: |
     cd computers-are-fast-2020
     make bin/13
     bin/13 < testinputs/13 | tee out
     cmp out testoutputs/13
     rm out
 - run-14: |
     cd computers-are-fast-2020
     make bin/14
     bin/14 < testinputs/14 | tee out
     cmp out testoutputs/14
     rm out

A  => .builds/openbsd.yml +88 -0
@@ 1,88 @@
image: openbsd/latest
sources:
 - https://git.sr.ht:~quf/computers-are-fast-2020
tasks:
 - run-01: |
     cd computers-are-fast-2020
     make bin/01
     bin/01 < testinputs/01 | tee out
     cmp out testoutputs/01
     rm out
 - run-02: |
     cd computers-are-fast-2020
     make bin/02
     bin/02 < testinputs/02 | tee out
     cmp out testoutputs/02
     rm out
 - run-03: |
     cd computers-are-fast-2020
     make bin/03
     bin/03 < testinputs/03 | tee out
     cmp out testoutputs/03
     rm out
 - run-04: |
     cd computers-are-fast-2020
     make bin/04
     bin/04 < testinputs/04 | tee out
     cmp out testoutputs/04
     rm out
 - run-05: |
     cd computers-are-fast-2020
     make bin/05
     bin/05 < testinputs/05 | tee out
     cmp out testoutputs/05
     rm out
 - run-06: |
     cd computers-are-fast-2020
     make bin/06
     bin/06 < testinputs/06 | tee out
     cmp out testoutputs/06
     rm out
 - run-07: |
     cd computers-are-fast-2020
     make bin/07
     bin/07 < testinputs/07 | tee out
     cmp out testoutputs/07
     rm out
 - run-08: |
     cd computers-are-fast-2020
     make bin/08
     bin/08 < testinputs/08 | tee out
     cmp out testoutputs/08
     rm out
 - run-09: |
     cd computers-are-fast-2020
     make bin/09
     bin/09 < testinputs/09 | tee out
     cmp out testoutputs/09
     rm out
 - run-10: |
     cd computers-are-fast-2020
     make bin/10
     bin/10 < testinputs/10 | tee out
     cmp out testoutputs/10
     rm out
 - run-11: |
     cd computers-are-fast-2020
     make bin/11
     bin/11 < testinputs/11 | tee out
     cmp out testoutputs/11
     rm out
 - run-12: |
     cd computers-are-fast-2020
     make bin/12
     bin/12 < testinputs/12 | tee out
     cmp out testoutputs/12
     rm out
 - run-13: |
     cd computers-are-fast-2020
     make bin/13
     bin/13 < testinputs/13 | tee out
     cmp out testoutputs/13
     rm out
 - run-14: |
     cd computers-are-fast-2020
     make bin/14
     bin/14 < testinputs/14 | tee out
     cmp out testoutputs/14
     rm out

A  => .gitattributes +1 -0
@@ 1,1 @@
*.svg binary

A  => .gitignore +2 -0
@@ 1,2 @@
bin
vgcore*

A  => README.md +212 -0
@@ 1,212 @@
Computers are fast.
===================

Or: C is my favourite scripting language

The goal of this project is to solve all parts of [Advent of Code 2020](https://adventofcode.com/2020/):

- With no dependencies other than a C99 compiler and libc,^(1,2)
- where every solution compiles and runs in 1 s or less,^3
- the total compile- and runtime does not exceed 10 s,^3
- while running single-threaded, and
- never exceeding 256 MiB memory use.^(3,4)

Stretch goal: Total time does not exceed 1 second.

^1 and, optionally, make.

^2 Some programs contain commonly held assumptions, like "`uint32_t` is available", or "exit code 0 signal success", or "the stack doesn't overflow".
   The programs may cause undefined behaviour if the input is not an unmodified one from AoC 2020.
   If undefined behaviour (such as signed integer overflow) does occur for an unmodified AoC 2020 input, that's a bug and I'd like to know about it.

^3 on the author's ~8 year old laptop with a i5-3320M CPU.

^4 virtual memory.

How to run
----------

First, copy or link the inputs files in the subfolder `inputs/`, with names `01`, `02`, …

Then, if you're on a POSIX-y system and have `tcc` installed, run the following to measure runtime:

```
$ tcc -run time.c
```

Alternatively, and this will be much slower, run:

```
$ time make
```

Some compilers (e.g. clang on FreeBSD) may refuse to compile some programs because the makefile invokes the compiler with very strict error conditions.
These errors are probably harmless suggestions and invoking `make` with the environment variable `CFLAGS=-Wno-error` should produce working executables.

To run a program individually (e.g. to see the solutions), run:

```
$ tcc -run src/01.c < inputs/01
```

Or:

```
$ make run-01
```

To run each program repeatedly and print the best time, run:

```
$ tcc -Drepeat=10 -run time.c
```

Track/limit allocations
-----------------------

Track allocations with `make bin/01 && valgrind bin/01 < inputs/01`.

Enforce memory limit with `ulimit -v 262144` (bash/fish shell on linux).

Track partial runtimes
----------------------

```
$ tcc -run time.c
```

Or (this will be slower):

```
$ time make clean run-01
```

[Day 1](src/01.c)
-----------------

Read every input number; create a lookup table of numbers (entry 0 if not present, 1 if present).
For every input number `x` (part 2: pair of numbers), check if the table at `2020-x` is set.

[Day 2](src/02.c)
-----------------

Who needs regular expressions when you have `scanf`?

[Day 3](src/03.c)
-----------------

Modular arithmetic.

[Day 4](src/04.c)
-----------------

Just read and validate input.

[Day 5](src/05.c)
-----------------

Seat specs are binary expansions of seat IDs.

The largest seat ID is pow(2, strlen(input)), so we can use statically allocate a bit vector that stores the IDs to avoid sorting.

[Day 6](src/06.c)
-----------------

Set union and intersection.
Sets are represented by 26 bits of a uint32_t.

[Day 7](src/07.c)
-----------------

The problem itself is quite simple.
Reading the input is a massive pain:

- Read the input line by line.

- Tokenize the input (split into words) and parse using a big switch that implements the following state machine:

![finite state machine flowchart, see parsing-07.dot](./parsing-07.png/)

To simplify memory management and speed up comparisons, each two-word color combination is assigned a number.
All colors have the form "modifier base", with 17 different modifiers and 33 different base colors.
The modifier can be mapped to a 5 bit number, and the base can be mapped to a 6 bit number.
The bitwise concatenation of these numbers is the color id.

All rules can be stored in a statically allocated array at the index of the outer bag id.

Part 1:

- While reading, generate a graph with flipped connections (transpose graph): For each inner bag, connect to the outer bags which need to contain that inner bag.

- Starting at the ID of "shiny gold", walk through the graph with inverted connections and count the nodes encountered.

Part 2:

- While reading, generate a graph with the straightforward connections: For each outer bag, connect to the required bags inside it, and tag with the number.

- Starting at the ID of "shiny gold", recursively compute the number of contained bags by walking through graph.

In neither case are partial results memoized.
Doing this may or may not reduce the runtime.

[Day 8](src/08.c)
-----------------

Part 1: Implement the VM, stop once the value of the instruction pointer repeats.

Part 2 is done with brute force: Change instructions one by one, check if the program terminates.

[Day 9](src/09.c)
-----------------

Part 1: Brute force.

Part 2: Sliding window sum.

[Day 10](src/10.c)
------------------

Read the adapter joltages into a bool table; this avoids having to sort them.

Part 1: Count the jolt differences.

Part 2: Dynamic programming, starting with the largest joltage, for which we know there is exactly one combination.

[Day 11](src/11.c)
------------------

There doesn't seem to be a way to shortcut the calculation, so just do it.
Optimizations:

- Use linear indices instead of Cartesian indices.

- Precompute the neighbours of each seat instead of computing them on every update.

- Avoid branches by computing all occupied neighbours before checking the update condition, and unrolling loops.

[Day 12](src/12.c)
------------------

Just a bit of basic analytic geometry.

[Day 13](src/13.c)
------------------

In part 1, the time until the bus with period `t` arrives after timestamp `t_0` is `t-(t_0%t)`.

In part 2, the solution time `t` satisfies a system of linear congruences like this:

```
(t + 0) = 0 % bus_1_period
(t + 1) = 0 % bus_2_period
```

It is solved with the Chinese Remainder Theorem.
When computed naively, interim values can become very large (~80 bits), so some care is taken to avoid overflow.

[Day 14](src/14.c)
------------------

Memory is saved in a hashmap.

A previous version used a more elegent sparse binary tree, but this was slower by a factor of > 2.

A  => inputs/.gitignore +2 -0
@@ 1,2 @@
*
!.gitignore

A  => makefile +122 -0
@@ 1,122 @@
.POSIX:

COMPILE = $(CC) -std=c99 -O0 -W -Wall -Wextra -pedantic -pedantic-errors -Werror -Wfatal-errors $(CFLAGS) $(LDFLAGS)

.PHONY: run
run: run-01 run-02 run-03 run-04 run-05 run-06 run-07 run-08 run-09 run-10 run-11 run-12 run-13 run-14

.PHONY: run-01
run-01: bin/01
	bin/01 < inputs/01

bin/01: src/01.c
	mkdir -p bin
	$(COMPILE) src/01.c -o bin/01

.PHONY: run-02
run-02: bin/02
	bin/02 < inputs/02

bin/02: src/02.c
	mkdir -p bin
	$(COMPILE) src/02.c -o bin/02

.PHONY: run-03
run-03: bin/03
	bin/03 < inputs/03

bin/03: src/03.c
	mkdir -p bin
	$(COMPILE) src/03.c -o bin/03

.PHONY: run-04
run-04: bin/04
	bin/04 < inputs/04

bin/04: src/04.c
	mkdir -p bin
	$(COMPILE) src/04.c -o bin/04

.PHONY: run-05
run-05: bin/05
	bin/05 < inputs/05

bin/05: src/05.c
	mkdir -p bin
	$(COMPILE) src/05.c -o bin/05

.PHONY: run-06
run-06: bin/06
	bin/06 < inputs/06

bin/06: src/06.c
	mkdir -p bin
	$(COMPILE) src/06.c -o bin/06

.PHONY: run-07
run-07: bin/07
	bin/07 < inputs/07

bin/07: src/07.c
	mkdir -p bin
	$(COMPILE) src/07.c -o bin/07

.PHONY: run-08
run-08: bin/08
	bin/08 < inputs/08

bin/08: src/08.c
	mkdir -p bin
	$(COMPILE) src/08.c -o bin/08

.PHONY: run-09
run-09: bin/09
	bin/09 < inputs/09

bin/09: src/09.c
	mkdir -p bin
	$(COMPILE) src/09.c -o bin/09

.PHONY: run-10
run-10: bin/10
	bin/10 < inputs/10

bin/10: src/10.c
	mkdir -p bin
	$(COMPILE) src/10.c -o bin/10

.PHONY: run-11
run-11: bin/11
	bin/11 < inputs/11

bin/11: src/11.c
	mkdir -p bin
	$(COMPILE) src/11.c -o bin/11

.PHONY: run-12
run-12: bin/12
	bin/12 < inputs/12

bin/12: src/12.c
	mkdir -p bin
	$(COMPILE) src/12.c -o bin/12

.PHONY: run-13
run-13: bin/13
	bin/13 < inputs/13

bin/13: src/13.c
	mkdir -p bin
	$(COMPILE) src/13.c -o bin/13

.PHONY: run-14
run-14: bin/14
	bin/14 < inputs/14

bin/14: src/14.c
	mkdir -p bin
	$(COMPILE) src/14.c -o bin/14

.PHONY: clean
clean:
	-rm -rf bin

A  => parsing-07.dot +19 -0
@@ 1,19 @@
digraph FSM {
  size="12,8";
  rankdir="LR";
  "0 (Start)" [ shape=rectangle ];
  "0 (Start)" -> 1 [ label="modifier" ];
  1 -> 2 [ label="base color" ];
  2 -> 3 [ label="'bags'" ];
  3 -> 4 [ label="'contain'" ];
  4 -> 5 [ label="'no'" ];
  5 -> 6 [ label="'other'" ];
  6 -> 7 [ label="'bags'" ];
  7 [ peripheries=2 ];
  4 -> 8 [ label="unsigned int" ];
  8 -> 9 [ label="modifier" ];
  9 -> 10 [ label="base color" ];
  10 -> 11 [ label="'bags'" ];
  11 [ peripheries=2 ];
  11 -> 8 [ label = "unsigned int" ];
}

A  => parsing-07.png +0 -0
A  => src/01.c +66 -0
@@ 1,66 @@
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>

#define N 2020

void day1_part1(const unsigned int *numbers, const bool *lookup_table, size_t len) {
  for (size_t i = 0; i < len; ++i) {
    unsigned int x = numbers[i];
    if (lookup_table[N - x]) {
      printf("%ld\n", (long)x*(N-(long)x));
      break;
    }
  }
}

void day1_part2(const unsigned int *numbers, const bool *lookup_table, size_t len) {
  for (size_t i = 0; i < len; ++i) {
    unsigned int x = numbers[i];
    for (size_t j = i; j < len; ++j) {
      unsigned int y = numbers[j];
      unsigned int tmp = x + y;
      if (tmp > N) {
        continue;
      }
      if (lookup_table[N - tmp]) {
        printf("%lld\n", (long long)x*(long long)y*(N-(long long)tmp));
        return;
      }
    }
  }
}

int main(void) {
  /* read input */
  size_t len = 0;
  unsigned int numbers[300] = { 0, };
  bool lookup_table[N+1] = { 0, };
  {
    int ret;
    unsigned int n;
    while ((ret = scanf("%u\n", &n)) > 0) {
      /* input validation to avoid overflow */
      if (n > N) {
        continue;
      }
      numbers[len++] = n;
      lookup_table[n] = true;
      if (len > sizeof numbers / sizeof *numbers) {
        fprintf(stderr, "Error: Input too large.\n");
        return 1;
      }
    }
    if (ret != EOF) {
      fprintf(stderr, "Error parsing input: %s.\n", strerror(errno));
      return 1;
    }
  }

  /* run search */
  day1_part1(numbers, lookup_table, len);
  day1_part2(numbers, lookup_table, len);

  return 0;
}

A  => src/02.c +51 -0
@@ 1,51 @@
#include <stdio.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>

#define MAXSIZE 40
#define MAXSIZE_S "40"

bool check_part1(unsigned i1, unsigned i2, char c, const char *pw) {
  size_t count = 0;
  while (*pw) {
    count += (*pw == c);
    ++pw;
  }
  return (i1 <= count) && (count <= i2);
}

bool check_part2(unsigned i1, unsigned i2, char c, const char *pw) {
  return (pw[i1-1] == c) ^ (pw[i2-1] == c);
}

int main(void) {
  size_t valid_1 = 0;
  size_t valid_2 = 0;
  int ret;
  while (1) {
    unsigned i1;
    unsigned i2;
    char c;
    char pw[MAXSIZE+1];
    if ((ret = scanf("%u-%u %c: %"MAXSIZE_S"s\n", &i1, &i2, &c, pw)) <= 0) {
      break;
    }
    /* input validation */
    if (i1 < 1 || i2 > strlen(pw)) {
      continue;
    }
    /* check */
    valid_1 += (size_t) check_part1(i1, i2, c, pw);
    valid_2 += (size_t) check_part2(i1, i2, c, pw);
  }
  if (ret != EOF) {
    fprintf(stderr, "Error parsing input: %s.\n", strerror(errno));
    return 1;
  }

  printf("%zu\n", valid_1);
  printf("%zu\n", valid_2);

  return 0;
}

A  => src/03.c +85 -0
@@ 1,85 @@
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

#define BUFSIZE 20000

struct grid {
  bool *data;
  size_t w;
  size_t h;
};

bool grid_get(const struct grid g, size_t x, size_t y) {
  assert(x < g.w && y < g.h);
  return g.data[x + y * g.w];
}

void grid_set(struct grid g, size_t x, size_t y, bool value) {
  assert(x < g.w && y < g.h);
  g.data[x + y * g.w] = value;
}

struct grid read_input() {
  struct grid g;
  char buf[BUFSIZE];
  size_t bufsize = fread(buf, 1, sizeof buf, stdin);
  assert(bufsize < sizeof buf);
  /* First pass, get size */
  g.w = 0;
  g.h = 0;
  for (size_t i = 0; i < bufsize; ++i) {
    if (buf[i] == '\n') {
      if (g.w == 0) {
        g.w = i;
      }
      ++g.h;
    }
  }
  assert(g.w > 0);
  /* Second pass, read into result */
  g.data = calloc(g.w * g.h, sizeof *g.data);
  assert(g.data != NULL);
  size_t i_x = 0;
  size_t i_y = 0;
  for (size_t i = 0; i < bufsize; ++i) {
    char c = buf[i];
    if (c == '#') {
      grid_set(g, i_x, i_y, true);
    }
    if (c == '\n') {
      assert(i_x == g.w);
      i_x = 0;
      ++i_y;
    }
    else {
      ++i_x;
    }
  }
  return g;
}

size_t part1(const struct grid g, size_t dx, size_t dy) {
  size_t x = 0;
  size_t y = 0;
  size_t count = 0;
  do {
    count += grid_get(g, x, y);
    x = (x + dx) % g.w;
    y += dy;
  } while (y < g.h);
  return count;
}

int main(void) {
  struct grid g = read_input();
  size_t count = part1(g, 3, 1);
  printf("%zu\n", count);
  count *= part1(g, 1, 1);
  count *= part1(g, 5, 1);
  count *= part1(g, 7, 1);
  count *= part1(g, 1, 2);
  printf("%zu\n", count);
  free(g.data);
}

A  => src/04.c +213 -0
@@ 1,213 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <ctype.h>

#define BUFSIZE 100
#define BUFSIZE_S "100"

size_t find_whitespace(const char *buf) {
  size_t i = 0;
  for (; buf[i] != '\0' && buf[i] != '\n' && buf[i] != ' '; ++i) { }
  return i;
}

size_t find_colon(const char *buf) {
  size_t i = 0;
  for (; buf[i] != '\0' && buf[i] != ':'; ++i) { }
  return i;
}

void *xmalloc(size_t size) {
  void *ret = calloc(size, 1);
  assert(ret);
  return ret;
}

struct passport {
  /* Any of these may be NULL */
  char *byr;
  char *iyr;
  char *eyr;
  char *hgt;
  char *hcl;
  char *ecl;
  char *pid;
  //char *cid; // unnecessary
};

bool validate_passport1(const struct passport *p) {
  return p->byr && p->iyr && p->eyr && p->hgt && p->hgt && p->hcl && p->ecl && p->pid;
}

bool validate_byr(const char *byr) {
  return byr != NULL && strlen(byr) == 4 && 1920 <= atoi(byr) && atoi(byr) <= 2002;
}

bool validate_iyr(const char *iyr) {
  return iyr != NULL && strlen(iyr) == 4 && 2010 <= atoi(iyr) && atoi(iyr) <= 2020;
}

bool validate_eyr(const char *eyr) {
  return eyr != NULL && strlen(eyr) == 4 && 2020 <= atoi(eyr) && atoi(eyr) <= 2030;
}

bool validate_hgt(const char *hgt) {
  if (hgt == NULL) {
    return false;
  }
  size_t len = strlen(hgt);
  if (len == 5) {
    if (strcmp(hgt + 3, "cm") != 0) {
      return false;
    }
    unsigned n = atoi(hgt);
    return 150 <= n && n <= 193;
  }
  else if (len == 4) {
    if (strcmp(hgt + 2, "in") != 0) {
      return false;
    }
    unsigned n = atoi(hgt);
    return 59 <= n && n <= 76;
  }
  return false;
}

bool validate_hcl(const char *hcl) {
  if (hcl == NULL || strlen(hcl) != 7 || hcl[0] != '#') {
    return false;
  }
  for (size_t i = 1; i < 7; ++i) {
    if (!isxdigit(hcl[i])) {
      return false;
    }
  }
  return true;
}

bool validate_ecl(const char *ecl) {
  if (ecl == NULL) {
    return false;
  }
  char *valid[] = { "amb", "blu", "brn", "gry", "grn", "hzl", "oth" };
  for (size_t i = 0; i < sizeof valid / sizeof *valid; ++i) {
    if (strcmp(ecl, valid[i]) == 0) {
      return true;
    }
  }
  return false;
}

bool validate_pid(const char *pid) {
  if (pid == NULL || strlen(pid) != 9) {
    return false;
  }
  for (size_t i = 0; i < 9; ++i) {
    if (!isdigit(pid[i])) {
      return false;
    }
  }
  return true;
}

bool validate_passport2(const struct passport *p) {
  return
    validate_byr(p->byr) &&
    validate_iyr(p->iyr) &&
    validate_eyr(p->eyr) &&
    validate_hgt(p->hgt) &&
    validate_hcl(p->hcl) &&
    validate_ecl(p->ecl) &&
    validate_pid(p->pid);
}

int main(void) {
  size_t valid1 = 0;
  size_t valid2 = 0;
  struct passport p = { 0, };
  while (true) {
    char buffer[BUFSIZE+1] = { 0, }; /* BUFSIZE+1 ensures that the line ends with two null bytes */
    if (fgets(buffer, BUFSIZE, stdin) == NULL) {
      if (ferror(stdin)) {
        fprintf(stderr, "Read error: %s\n", strerror(errno));
        return EXIT_FAILURE;
      }
      break;
    }
    size_t bufsize = strlen(buffer);
    assert(bufsize > 0);
    if (buffer[bufsize-1] != '\n') {
      fprintf(stderr, "Line too long, longer than %d.\n", BUFSIZE);
      return EXIT_FAILURE;
    }

    if (bufsize == 1) {
      /* empty line */
      valid1 += validate_passport1(&p);
      valid2 += validate_passport2(&p);
      free(p.byr);
      free(p.iyr);
      free(p.eyr);
      free(p.hgt);
      free(p.hcl);
      free(p.ecl);
      free(p.pid);
      memset(&p, 0, sizeof p);
      continue;
    }

    char *buf = buffer;
    while (*buf) { /* this loop relies on buf ending in two null bytes (or a space and a null byte) */
      /* find colon, which gives the end of the key */
      size_t k_len = find_colon(buf);
      char **entry = NULL;
      if (strncmp(buf, "byr", k_len) == 0) {
        entry = &p.byr;
      }
      else if (strncmp(buf, "iyr", k_len) == 0) {
        entry = &p.iyr;
      }
      else if (strncmp(buf, "eyr", k_len) == 0) {
        entry = &p.eyr;
      }
      else if (strncmp(buf, "hgt", k_len) == 0) {
        entry = &p.hgt;
      }
      else if (strncmp(buf, "hcl", k_len) == 0) {
        entry = &p.hcl;
      }
      else if (strncmp(buf, "ecl", k_len) == 0) {
        entry = &p.ecl;
      }
      else if (strncmp(buf, "pid", k_len) == 0) {
        entry = &p.pid;
      }
      /* advance buffer and find space or newline, which ends the value */
      buf += k_len + 1;
      size_t v_len = find_whitespace(buf);
      /* if the key is "interesting", save the value */
      if (entry != NULL) {
        *entry = xmalloc(v_len+1);
        memcpy(*entry, buf, v_len);
      }
      buf += v_len + 1;
    }
  }
  free(p.byr);
  free(p.iyr);
  free(p.eyr);
  free(p.hgt);
  free(p.hcl);
  free(p.ecl);
  free(p.pid);


  printf("%zu\n", valid1);
  printf("%zu\n", valid2);

  return EXIT_SUCCESS;
}

A  => src/05.c +59 -0
@@ 1,59 @@
#include <stdio.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>

#define BUFSIZE 10
#define BUFSIZE_S "10"

int main(void) {
  bool assigned[1024] = { 0, };
  unsigned min = 65535;
  unsigned max = 0;

  /* read input and get min and max id */
  int ret = 0;
  char buf[BUFSIZE+1];
  while ((ret = scanf("%"BUFSIZE_S"s\n", buf)) > 0) {
    unsigned id = 0;
    for (size_t i = 0; buf[i]; ++i) {
      id = id << 1 | (buf[i] == 'B' || buf[i] == 'R');
    }
    if (id > sizeof assigned / sizeof *assigned) {
      fprintf(stderr, "Error: Seat ID too large!\n");
      return 0;
    }
    else {
      assigned[id] = true;
    }
    if (id < min) {
      min = id;
    }
    if (id > max) {
      max = id;
    }
  }
  if (ret != EOF) {
    fprintf(stderr, "Error: %s.\n", strerror(errno));
    return 1;
  }

  /* part 1: print max */
  printf("%u\n", max);

  /* part 2: find empty spot in the middle */
  if (min == 0) {
    min = 1;
  }
  if (max >= (sizeof assigned / sizeof *assigned) - 1) {
    max = (sizeof assigned / sizeof *assigned) - 2;
  }
  for (size_t i = min; i < max; ++i) {
    if (!assigned[i] && assigned[i-1] && assigned[i+1]) {
      printf("%zu\n", i);
      return 0;
    }
  }
  fprintf(stderr, "Error: No seat found.\n");
  return 1;
}

A  => src/06.c +67 -0
@@ 1,67 @@
#include <stdio.h>
#include <inttypes.h>
#include <stdbool.h>
#include <errno.h>
#include <string.h>
#include <assert.h>
#include <limits.h>

#define ANSWERS_ALL_INIT 0x03ffffff /* bits 0 to 25 set */

uint16_t count_ones(uint32_t x) {
  uint16_t sum = 0;
  for (size_t i = 0; i < CHAR_BIT * sizeof x; ++i) {
    sum += (x >> i) & 1;
  }
  return sum;
}

int main(void) {
  uint16_t sum1 = 0;
  uint16_t sum2 = 0;

  uint32_t answers_any = 0;
  uint32_t answers_all = ANSWERS_ALL_INIT;
  bool group_empty = true;

  while (1) {
    char buf[28] = { 0, };
    char *ret = fgets(buf, sizeof buf, stdin);
    if (ret == NULL || strlen(buf) == 0 || (strlen(buf) == 1 && buf[0] == '\n')) {
      /* end of group, end of file, or error */
      if (!group_empty) {
        /* update sum */
        group_empty = true;
        sum1 += count_ones(answers_any);
        sum2 += count_ones(answers_all);
        answers_any = 0;
        answers_all = ANSWERS_ALL_INIT;
      }
      if (ret == NULL) {
        /* EOF */
        if (ferror(stdin)) {
          fprintf(stderr, "Input error: %s.\n", strerror(errno));
          return 1;
        }
        break;
      }
    }
    else {
      /* got answer line */
      group_empty = false;
      uint32_t answers = 0;
      for (size_t i = 0; buf[i]; ++i) {
        if ('a' <= buf[i] && buf[i] <= 'z') {
          answers |= ((uint32_t) 1) << (buf[i] - 'a');
        }
      }
      answers_any |= answers;
      answers_all &= answers;
    }
  }

  printf("%"PRIu16"\n", sum1);
  printf("%"PRIu16"\n", sum2);

  return 0;
}

A  => src/07.c +199 -0
@@ 1,199 @@
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <stdbool.h>
#include <string.h>
#include <errno.h>

#define die(...) do { \
    fprintf(stderr, __VA_ARGS__); \
    exit(EXIT_FAILURE); \
  } while (0)

#define die_unless(cond, ...) do { \
    if (!(cond)) { \
      die(__VA_ARGS__); \
    } \
  } while (0)

#define die_if(cond, ...) die_unless(!(cond), __VA_ARGS__)

typedef struct {
  uint16_t out_nodes[20];
  uint8_t out_numbers[20];
} rule_t;

const char * const modifiers[] = { "bright", "clear", "dark", "dim", "dotted", "drab", "dull", "faded", "light", "mirrored", "muted", "pale", "plaid", "posh", "shiny", "striped", "vibrant", "wavy", };
const int modifier_bits = 5;
const char * const base_colors[] = { "aqua", "beige", "black", "blue", "bronze", "brown", "chartreuse", "coral", "crimson", "cyan", "fuchsia", "gold", "gray", "green", "indigo", "lavender", "lime", "magenta", "maroon", "olive", "orange", "plum", "purple", "red", "salmon", "silver", "tan", "teal", "tomato", "turquoise", "violet", "white", "yellow", };
const int base_color_bits = 6;

size_t bisect(const char * const arr[], size_t len, const char *s) {
  size_t lo = 0;
  size_t hi = len - 1;
  if (strcmp(s, arr[lo]) == 0) {
    return lo;
  }
  if (strcmp(s, arr[hi]) == 0) {
    return hi;
  }
  while (1) {
    /* we loop forever if "s" is not found. */
    size_t mid = lo + (hi - lo) / 2;
    int x = strcmp(s, arr[mid]);
    if (x == 0) {
      return mid;
    }
    else if (x < 0) {
      hi = mid;
    }
    else {
      lo = mid;
    }
  }
}

uint16_t get_id(uint16_t mod_index, uint16_t base_index) {
  uint16_t x = (mod_index << base_color_bits) | base_index;
  die_unless(x < (1<<11), "Internal error, this should never happen.\n");
  return x;
}

size_t next_space(const char *s) {
  size_t off = 0;
  for (; s[off] && s[off] != ' '; ++off);
  return off;
}

size_t next_line(const char *s) {
  size_t off = 0;
  for (; s[off] && s[off] != '\n'; ++off);
  return off;
}

void read_input(rule_t *rules_contains, rule_t *rules_contained) {
  char line[1<<8];
  while (fgets(line, sizeof line, stdin) != NULL) {
    die_if(strlen(line) == (sizeof line), "Error: line too long.\n"); /* TODO could maybe save some time here. */
    char *word;
    /* parse rule using a handrolled state machine */
    rule_t r = { { 0, }, { 0, } };
    uint16_t mod = 0;
    uint16_t base = 1;
    uint16_t rule_id = 0;
    uint8_t num = 0;
    int state = 0;
    while ((word = strtok(state ? NULL : line, " \n")) != 0) {
      switch (state) {
        case 0: /* fall through */
        case 8:
          mod = bisect(modifiers, sizeof modifiers / sizeof *modifiers, word);
          ++state;
          break;
        case 1:
          base = bisect(base_colors, sizeof base_colors / sizeof *base_colors, word);
          rule_id = get_id(mod, base);
          ++state;
          break;
        case 4:
          {
            int n = atoi(word);
            if (n > 0) {
              /* TODO: maybe check that n <= UINT8_MAX; */
              num = (uint8_t) n;
              state = 8;
            }
            else {
              ++state;
            }
          }
          break;
        case 7:
          die("Error: unexpected word in input: %s\n", word);
          break;
        case 9:
          base = bisect(base_colors, sizeof base_colors / sizeof *base_colors, word);
          /* got number and color ID of the inner bag, now store it */
          {
            size_t i = 0;
            for (; i < sizeof r.out_numbers / sizeof *r.out_numbers && r.out_numbers[i] != 0; ++i);
            die_unless(i < sizeof r.out_numbers / sizeof *r.out_numbers, "Error: too many for one bag");
            r.out_numbers[i] = num;
            r.out_nodes[i] = get_id(mod, base);
          }
          ++state;
          break;
        case 11:
          num = atoi(word);
          state = 8;
          break;
        case 2: /* fall through*/
        case 3: /* fall through*/
        case 5: /* fall through*/
        case 6: /* fall through*/
        case 10:
          ++state;
          break;
        default:
          die("Internal error: this should never happen.\n");
      }
    }
    die_unless(state == 7 || state == 11, "Unexpected end of input (in state %d)\n",  state);
    /* add rules to the first graph ("contains") */
    memcpy(rules_contains + rule_id, &r, sizeof r);
    /* and the second ("contained") */
    for (size_t i = 0; i < sizeof r.out_nodes / sizeof *r.out_nodes; ++i) {
      if (r.out_numbers[i] == 0) {
        break;
      }
      uint16_t id = r.out_nodes[i];
      size_t j = 0;
      /* find a place to store the reverse connection */
      for (j = 0; j < (sizeof r.out_nodes / sizeof *r.out_nodes) && rules_contained[id].out_numbers[j] != 0; ++j);
      die_unless(j < sizeof r.out_nodes / sizeof *r.out_nodes, "Error: too many reverse connections.\n");
      rules_contained[id].out_numbers[j] = 1; /* or any nonzero number */
      rules_contained[id].out_nodes[j] = rule_id;
    }
  }
  die_if(ferror(stdin), "Read error: %s", strerror(errno));
}

size_t part1(rule_t *rules, uint16_t id) {
  bool visited[1<<11] = { 0, };
  uint16_t to_visit[1<<7] = { 0, };
  to_visit[0] = id;
  size_t to_visit_len = 1;
  size_t sum = 0;
  while (to_visit_len > 0) {
    uint16_t id = to_visit[--to_visit_len];
    for (size_t i = 0; i < (sizeof rules[id].out_nodes / sizeof *rules[id].out_nodes) && rules[id].out_numbers[i] != 0; ++i) {
      die_unless(to_visit_len < sizeof to_visit / sizeof *to_visit - 1, "Error: ran out of space.\n");
      size_t new_id = rules[id].out_nodes[i];
      if (!visited[new_id]) {
        visited[new_id] = true;
        sum += 1;
        to_visit[to_visit_len++] = new_id;
        continue;
      }
    }
  }
  return sum;
}

size_t part2(const rule_t *rules, uint16_t id) {
  size_t sum = 1;
  for (size_t i = 0; i < (sizeof rules[id].out_nodes / sizeof *rules[id].out_nodes) && rules[id].out_nodes[i] != 0; ++i) {
    sum += rules[id].out_numbers[i] * part2(rules, rules[id].out_nodes[i]);
  }
  return sum;
}

int main(void) {
  rule_t rules_contains[1<<11] = { 0, };
  rule_t rules_contained[1<<11] = { 0, };
  read_input(rules_contains, rules_contained);
  const uint16_t shiny_gold_id = get_id(bisect(modifiers, sizeof modifiers / sizeof *modifiers, "shiny"), bisect(base_colors, sizeof base_colors / sizeof *base_colors, "gold"));
  printf("%zu\n", part1(rules_contained, shiny_gold_id));
  printf("%zu\n", part2(rules_contains, shiny_gold_id) - 1);
  return 0;
}

A  => src/08.c +100 -0
@@ 1,100 @@
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <stdbool.h>
#include <string.h>

#define BUFSIZE 10
#define BUFSIZE_S "10"
#define MAX_INS (1<<10)

#define die(...) do { \
    fprintf(stderr, __VA_ARGS__); \
    exit(EXIT_FAILURE); \
  } while (0)

#define die_unless(cond, ...) do { \
    if (!(cond)) { \
      die(__VA_ARGS__); \
    } \
  } while (0)

#define die_if(cond, ...) die_unless(!(cond), __VA_ARGS__)

typedef enum {
  NOP, ACC, JMP,
} op_t;

typedef struct {
  op_t op;
  int arg;
} ins_t;

bool run_until_loop_or_end(const ins_t instructions[], size_t n_instructions, int *acc_ret) {
  size_t ip = 0;
  int acc = 0;
  bool visited[MAX_INS] = { 0, };
  while (ip < n_instructions && !visited[ip]) {
    visited[ip] = true;
    ins_t ins = instructions[ip];
    switch (ins.op) {
      case NOP: ++ip; break;
      case ACC: ++ip; acc += ins.arg; break;
      case JMP: ip += ins.arg; break;
    }
  }
  *acc_ret = acc;
  return (ip >= n_instructions);
}

int main(void) {
  ins_t instructions[1<<10] = { 0, };
  size_t n_ins = 0;

  /* Read input */
  char buf[BUFSIZE+1] = { 0, };
  int ret = 0;
  int n = 0;
  while ((ret = scanf("%"BUFSIZE_S"s %d\n", buf, &n)) > 0) {
    die_if(n_ins + 1 >= sizeof instructions / sizeof *instructions, "Too many instructions.\n");
    if (strcmp(buf, "nop") == 0) {
      instructions[n_ins].op = NOP;
    }
    else if (strcmp(buf, "acc") == 0) {
      instructions[n_ins].op = ACC;
    }
    else if (strcmp(buf, "jmp") == 0) {
      instructions[n_ins].op = JMP;
    }
    else {
      die("Invalid operation: %s.\n", buf);
    }
    instructions[n_ins++].arg = n;
  }
  die_unless(feof(stdin), "Expected_eof.\n");
  die_if(ferror(stdin), "Read error: %s\n", strerror(errno));

  /* Part 1 */
  (void) run_until_loop_or_end(instructions, n_ins, &ret);
  printf("%d\n", ret);

  /* Part 2 */
  for (size_t i = 0; i < n_ins; ++i) {
    ins_t ins = instructions[i];
    if (ins.op == NOP) {
      instructions[i].op = JMP;
    }
    else if (ins.op == JMP) {
      instructions[i].op = NOP;
    }
    else {
      continue;
    }
    if (run_until_loop_or_end(instructions, n_ins, &ret)) {
      printf("%d\n", ret);
      return EXIT_SUCCESS;
    }
    instructions[i].op = ins.op;
  }
  die("No termination.\n");
}

A  => src/09.c +82 -0
@@ 1,82 @@
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>
#include <limits.h>

bool is_sum_of_two(long long n, const long long *numbers, size_t len) {
  for (size_t i = 1; i < len; ++i) {
    for (size_t j = 0; j < i; ++j) {
      if (numbers[i] + numbers[j] == n) {
        return true;
      }
    }
  }
  return false;
}

void extrema(const long long *numbers, size_t len, long long *min, long long *max) {
  *min = LLONG_MAX;
  *max = 0;
  for (size_t i = 0; i < len; ++i) {
    if (numbers[i] < *min) {
      *min = numbers[i];
    }
    else if (numbers[i] > *max) {
      *max = numbers[i];
    }
  }
}

int main(void) {
  long long numbers[1<<10] = { 0, };
  size_t N = 0;

  /* read input */
  int ret = 0;
  while (N < sizeof numbers / sizeof *numbers && (ret = scanf("%lld\n", &numbers[N])) > 0) {
    ++N;
  }
  if (ret < 0 && !feof(stdin)) {
    fprintf(stderr, "Error: %s.\n", strerror(errno));
    return 1;
  }
  if (N < 26) {
    fprintf(stderr, "Error: input too short.\n");
    return 1;
  }

  /* part 1 */
  long long weak = 0;
  for (size_t i = 25; i < N; ++i) {
    if (!is_sum_of_two(numbers[i], numbers + (i - 25), 25)) {
      weak = numbers[i];
      break;
    }
  }
  printf("%lld\n", weak);

  /* part 2*/
  size_t i = 0;
  size_t j = 1;
  long long sum = numbers[0] + numbers[1];
  while (j < N) {
    if (sum == weak) {
      long long min, max;
      extrema(numbers + i, j - i + 1, &min, &max);
      printf("%lld\n", min + max);
      return 0;
    }
    if (j == i + 1) {
      sum += numbers[++j];
    }
    else if (sum < weak) {
      sum += numbers[++j];
    }
    else {
      sum -= numbers[i++];
    }
  }
  fprintf(stderr, "No solution.\n");
  return 1;
}

A  => src/10.c +52 -0
@@ 1,52 @@
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <stdbool.h>
#include <stdint.h>

int main(void) {
  bool adapters[200] = { 0, };
  adapters[0] = true;
  int ret = 0;
  size_t max_rating = 0;
  size_t rating;
  while ((ret = scanf("%zu\n", &rating)) > 0) {
    if (rating > sizeof adapters / sizeof *adapters) {
      fprintf(stderr, "Error: Too many adapters.\n");
      return 1;
    }
    adapters[rating] = true;
    if (rating > max_rating) {
      max_rating = rating;
    }
  }
  if (ferror(stdin)) {
    fprintf(stderr, "Input error: %s.\n", strerror(errno));
    return 1;
  }

  long long combinations[2 + sizeof adapters / sizeof *adapters] = { 0, };
  combinations[max_rating] = 1;
  unsigned deltas[4] = { 0, };
  size_t delta = 0;
  for (size_t i = max_rating - 1; i != SIZE_MAX; --i) {
    ++delta;
    if (adapters[i]) {
      combinations[i] += combinations[i+1];
      combinations[i] += combinations[i+2];
      combinations[i] += combinations[i+3];
      if (delta > sizeof deltas / sizeof *deltas) {
        fprintf(stderr, "Error: gap in ratings larger than 3.\n");
        return 1;
      }
      else {
        ++deltas[delta];
        delta = 0;
      }
    }
  }

  printf("%u\n", deltas[1] * (deltas[3]+1));
  printf("%lld\n", combinations[0]);
  return 0;
}

A  => src/11.c +130 -0
@@ 1,130 @@
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdbool.h>

#define MAX_DATASIZE 10000

/* tile_t was originally an enum, but this is a few ms faster: */
#define tile_t char
#define FLOOR 0
#define EMPTY 1
#define OCC 2

typedef struct {
  tile_t data[MAX_DATASIZE+1]; /* data[MAX_DATASIZE] == FLOOR for neighbour map */
  size_t sx;
  size_t sy;
} grid_t;

#define flatten(g, i, j) (i * g.sy + j)

size_t count_occupied(const grid_t *g) {
  size_t count = 0;
  for (size_t i = 0; i < g->sx * g->sy; ++i) {
    count += g->data[i] == OCC;
  }
  return count;
}

size_t run(const grid_t *g, const size_t *neighbour_map, size_t max_neigh) {
  grid_t copy;
  memcpy(&copy, g, sizeof copy);
  size_t size = g->sx * g->sy;
  bool updated = false;
  unsigned occupied_neighbours[MAX_DATASIZE];
  do {
    for (size_t i = 0; i < size; ++i) {
      occupied_neighbours[i] =
          (copy.data[neighbour_map[i*8+0]] >> 1) /* FLOOR>>1 == 0 */
        + (copy.data[neighbour_map[i*8+1]] >> 1) /* EMPTY>>1 == 1 */
        + (copy.data[neighbour_map[i*8+2]] >> 1) /* OCC>>1 == 1 */
        + (copy.data[neighbour_map[i*8+3]] >> 1)
        + (copy.data[neighbour_map[i*8+4]] >> 1)
        + (copy.data[neighbour_map[i*8+5]] >> 1)
        + (copy.data[neighbour_map[i*8+6]] >> 1)
        + (copy.data[neighbour_map[i*8+7]] >> 1);
    }
    updated = false;
    for (size_t i = 0; i < size; ++i) {
      if (copy.data[i] == EMPTY && occupied_neighbours[i] == 0) {
        copy.data[i] = OCC;
        updated = true;
      }
      else if (copy.data[i] == OCC && occupied_neighbours[i] > max_neigh) {
        copy.data[i] = EMPTY;
        updated = true;
      }
    }
  } while (updated);
  return count_occupied(&copy);
}

int main(void) {
  grid_t grid = { .data = { 0, }, .sx = 0, .sy = 0};
  {
    size_t i = 0;
    size_t ix = 0;
    size_t iy = 0;
    int c;
    while ((c = getc(stdin)) != EOF && i < sizeof grid.data / sizeof *grid.data) {
      if (c == '\n') {
        ++iy;
        if (ix > grid.sx) {
          grid.sx = ix;
        }
        ix = 0;
      }
      else {
        grid.data[i++] = (c == 'L');
        ++ix;
      }
    }
    grid.sy = iy;
    if (ferror(stdin)) {
      fprintf(stderr, "Read error: %s\n", strerror(errno));
      return 1;
    }
    else if (grid.sx * grid.sy > (sizeof grid.data / sizeof *grid.data) - 1) {
      fprintf(stderr, "Input too large.\n");
      return 1;
    }
  }

  /* get nearest neighbours for each seat */
  const size_t NEIGH_DX[] = { 1, 1, 0, -1, -1, -1,  0,  1 };
  const size_t NEIGH_DY[] = { 0, 1, 1,  1,  0, -1, -1, -1 };
  size_t neighbour_map_1[MAX_DATASIZE*8] = { 0, };
  size_t neighbour_map_2[MAX_DATASIZE*8] = { 0, };
  for (size_t i = 0; i < grid.sx; ++i) {
    for (size_t j = 0; j < grid.sy; ++j) {
      size_t index = flatten(grid, i, j);
      for (size_t k = 0; k < 8; ++k) {
        size_t i_ = i + NEIGH_DX[k];
        size_t j_ = j + NEIGH_DY[k];
        if (i_ < grid.sx && j_ < grid.sy && grid.data[flatten(grid, i_, j_)] != FLOOR) {
          neighbour_map_1[index*8+k] = flatten(grid, i_, j_);
          neighbour_map_2[index*8+k] = flatten(grid, i_, j_);
        }
        else {
          neighbour_map_1[index*8+k] = MAX_DATASIZE;
          while (i_ < grid.sx && j_ < grid.sy && grid.data[flatten(grid, i_, j_)] == FLOOR) {
            i_ += NEIGH_DX[k];
            j_ += NEIGH_DY[k];
          }
          if (i_ < grid.sx && j_ < grid.sy) {
            neighbour_map_2[index*8+k] = flatten(grid, i_, j_);
          }
          else {
            neighbour_map_2[index*8+k] = MAX_DATASIZE;
          }
        }
      }
    }
  }

  printf("%zu\n", run(&grid, (const size_t*) neighbour_map_1, 3));
  printf("%zu\n", run(&grid, (const size_t*) neighbour_map_2, 4));

  return 0;
}

A  => src/12.c +106 -0
@@ 1,106 @@
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <limits.h>

#define abs(x) (x > 0 ? x : -x)

typedef struct {
  long x;
  long y;
} point_t;

#define dir_t unsigned
#define EAST 0
#define NORTH 1
#define WEST 2
#define SOUTH 3

point_t move_forward(point_t position, dir_t direction, long steps) {
  point_t result = position;
  switch (direction) {
    case EAST: /* east */
      result.x += steps;
      break;
    case NORTH: /* north */
      result.y += steps;
      break;
    case WEST: /* west */
      result.x -= steps;
      break;
    case SOUTH: /* south */
      result.y -= steps;
      break;
    default:
      break;
  }
  return result;
}

point_t rot_r(point_t p) {
  point_t result = {
    .x =  p.y,
    .y = -p.x,
  };
  return result;
}

point_t rot_l(point_t p) {
  point_t result = {
    .x = -p.y,
    .y =  p.x,
  };
  return result;
}

int main(void) {
  point_t pos1 = { .x = 0, .y = 0 };
  dir_t dir1 = EAST;

  point_t pos2 = { .x = 0, .y = 0 };
  point_t wp2 = { .x = 10, .y = 1 };

  dir_t directions[1<<CHAR_BIT] = { 0, };
  directions['E'] = EAST;
  directions['N'] = NORTH;
  directions['W'] = WEST;
  directions['S'] = SOUTH;

  int ret = 0;
  char c = 0;
  long n = 0;
  while ((ret = scanf("%c%ld\n", &c, &n)) > 0) {
    switch(c) {
      case 'L':
        for (long i = 0; i < n; i += 90) {
          dir1 = (dir1 + 1) % 4;
          wp2 = rot_l(wp2);
        }
        break;
      case 'R':
        for (long i = 0; i < n; i += 90) {
          dir1 = (dir1 + 3) % 4;
          wp2 = rot_r(wp2);
        }
        break;
      case 'F':
        pos1 = move_forward(pos1, dir1, n);
        pos2.x += n * wp2.x;
        pos2.y += n * wp2.y;
        break;
      default:
        pos1 = move_forward(pos1, directions[(size_t) c], n);
        wp2  = move_forward(wp2,  directions[(size_t) c], n);
        break;
    }
  }
  if (ret != EOF) {
    fprintf(stderr, "Input error: %s.\n", strerror(errno));
    return 1;
  }

  printf("%ld\n", abs(pos1.x) + abs(pos1.y));
  printf("%ld\n", abs(pos2.x) + abs(pos2.y));

  return 0;
}

A  => src/13.c +101 -0
@@ 1,101 @@
#include <stdio.h>
#include <limits.h>
#include <ctype.h>
#include <string.h>
#include <errno.h>

#define abs(a) ((a<0)? -a : a)

long long mulmod(unsigned long long a, unsigned long long b, unsigned long long m) {
    /* Returns mod(a*b, c) while avoiding overflow for a*b */
    if (a < (1ULL<<32) && b < (1ULL<<32)) { /* long long is at least 64 bits wide */
        return (a * b) % m;
    }
    unsigned long long ret = 0;
    a = a % m;
    b = b % m;
    while (b) {
        if (b & 1) {
            ret = (ret + a) % m;
        }
        a = (a << 1) % m;
        b >>= 1;
    }
    return ret;
}

unsigned long long mod(long long a, long long b) {
  /* assumes b > 0*/
  if (a >= 0) {
    return (unsigned long long) a % b;
  }
  else {
    return (unsigned long long) ((a % b) + b);
  }
}

long long egcd(long long a, long long b, long long *n, long long *m) {
  if (b == 0) {
    *n = 1;
    *m = 0;
    return a;
  }
  else if (abs(a) < abs(b)) {
    return egcd(b, a, m, n);
  }
  long long k = a / b;
  long long r = a - k * b;
  long long tmp;
  long long g = egcd(b, r, &tmp, n);
  *m = tmp - k * (*n);
  return g;
}

int main(void) {
  long earliest;
  scanf("%ld\n", &earliest);
  long part1 = 0;
  long min_wait = LONG_MAX;
  long long part2 = 0;
  long long period = 1;
  long t = 0;
  int c = 0;
  for (int i = 0; (c = getchar()) != EOF;) {
    if (isdigit(c)) {
      t = 10 * t + (c - '0');
    }
    else if ((c == ',' || c == '\n') && t != 0) { /* got our number */
      /* part 1 */
      long rem = t - (earliest % t);
      if (rem < min_wait) {
        min_wait = rem;
        part1 = rem * t;
      }

      /* part 2 */
      long long g, n, m;
      g = egcd(period, t, &n, &m);
      long long d = -(part2 + (long long) i);
      long long k = d / g;
      if (d != k * g) {
        fprintf(stderr, "part2 is not solvable.\n");
        return 1;
      }
      period = t * (period / g);
      part2 = mod(mulmod((unsigned long long) t, mulmod(mod(-k, period), mod(m, period), period), period) - i, period);

      t = 0;
      ++i;
    }
    else if (c == 'x') {
      ++i;
    }
  }
  if (ferror(stdin)) {
    fprintf(stderr, "Error: %s\n", strerror(errno));
    return 1;
  }
  printf("%ld\n", part1);
  printf("%lld\n", part2);
  return 0;
}

A  => src/14.c +144 -0
@@ 1,144 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <inttypes.h>

#ifndef BUCKETSIZE
  #define BUCKETSIZE 50
#endif

typedef struct {
  size_t occupied;
  unsigned long long keys[BUCKETSIZE];
  unsigned long long values[BUCKETSIZE];
} bucket_t;

typedef struct {
  size_t n_buckets;
  bucket_t *buckets;
} hash_table_t;

hash_table_t *ht_init(size_t n_buckets) {
  /* Note: buckets NEEDS to be a power of 2! */
  hash_table_t *table = malloc(sizeof *table);
  table->n_buckets = n_buckets;
  if (table == NULL) {
    fprintf(stderr, "Allocation erorr: %s.\n", strerror(errno));
    fflush(stderr);
    exit(EXIT_FAILURE);
  }
  table->buckets = calloc(n_buckets, sizeof *table->buckets);
  if (table->buckets == NULL) {
    fprintf(stderr, "Allocation erorr: %s.\n", strerror(errno));
    exit(EXIT_FAILURE);
  }
  for (size_t i = 0; i < n_buckets; ++i) {
    table->buckets[i].occupied = 0;
  }
  return table;
}

uint32_t ht_hash(uint64_t x) {
  /* adapted djb2 from http://www.cse.yorku.ca/~oz/hash.html */
  uint32_t h = 5381;
  h = ((h << 5) + h) + (uint32_t) ( x        & 255);
  h = ((h << 5) + h) + (uint32_t) ((x >>  8) & 255);
  h = ((h << 5) + h) + (uint32_t) ((x >> 16) & 255);
  h = ((h << 5) + h) + (uint32_t) ((x >> 24) & 255);
  h = ((h << 5) + h) + (uint32_t) ((x >> 32) & 255);
  h = ((h << 5) + h) + (uint32_t) ((x >> 40) & 255);
  h = ((h << 5) + h) + (uint32_t) ((x >> 48) & 255);
  h = ((h << 5) + h) + (uint32_t) ((x >> 54) & 255);
  return h;
}

void ht_insert(hash_table_t *table, uint64_t key, uint64_t value) {
  size_t bucket_id = ht_hash(key) & (table->n_buckets-1);
  bucket_t *bucket = table->buckets + bucket_id;
  for (size_t i = 0; i < bucket->occupied; ++i) {
    if (bucket->keys[i] == key) {
      bucket->values[i] = value;
      return;
    }
  }
  if (bucket->occupied + 1 >= table->n_buckets) {
    fprintf(stderr, "Error inserting (%"PRIu64", %"PRIu64"): Bucket %zu is already full!\n", key, value, bucket_id);
    exit(EXIT_FAILURE);
  }
  bucket->keys[bucket->occupied] = key;
  bucket->values[bucket->occupied] = value;
  ++bucket->occupied;
}

unsigned long long sum(hash_table_t *table) {
  unsigned long long s = 0;
  for (size_t i = 0; i < table->n_buckets; ++i) {
    for (size_t j = 0; j < table->buckets[i].occupied; ++j) {
      s += table->buckets[i].values[j];
    }
  }
  return s;
}

void part2_ht_insert(hash_table_t *mem, unsigned long long loc, unsigned long long val, unsigned long long floating) {
  if (!floating) {
    ht_insert(mem, (uint64_t) loc, val);
    return;
  }
  int i = 0;
  for (; !(floating & 1); ++i, floating >>= 1);
  floating &= ~1LLU;
  floating <<= i;
  part2_ht_insert(mem, loc,             val, floating);
  part2_ht_insert(mem, loc ^ (1ULL<<i), val, floating);
}


int main(void) {
  hash_table_t *mem1 = ht_init(1<<8);
  hash_table_t *mem2 = ht_init(1<<12);
  char line[50];
  unsigned long long mask = 0, maskmask = 0;
  while (fgets(line, sizeof line, stdin) != NULL) {
    if (strncmp(line, "mask", 4) == 0) {
      /* set mask */
      mask = 0;
      maskmask = 0;
      char *b = line;
      for (; *b && *b != '0' && *b != '1' && *b != 'X'; ++b);
      for (; *b && *b != '\n'; ++b) {
        mask <<= 1;
        maskmask <<= 1;
        if (*b == '0') {
          maskmask |= 1;
        }
        else if (*b == '1') {
          mask |= 1;
          maskmask |= 1;
        }
      }
    }
    else {
      /* memory */
      unsigned long long loc = 0;
      unsigned long long val = 0;
      if (sscanf(line, "mem[%llu] = %llu", &loc, &val) != 2) {
        fprintf(stderr, "input error for line: %s\n", line);
        return EXIT_FAILURE;
      }

      ht_insert(mem1, (uint64_t) loc, (val | (mask & maskmask)) & (mask | ~maskmask));

      unsigned long long floating = (~maskmask) & ((1ULL<<36)-1);
      part2_ht_insert(mem2, loc | mask, val, floating);
    }
  }
  if (ferror(stdin)) {
    fprintf(stderr, "Read error: %s.\n", strerror(errno));
    return EXIT_FAILURE;
  }
  printf("%llu\n", sum(mem1));
  printf("%llu\n", sum(mem2));
  return 0;
}

A  => testinputs/01 +6 -0
@@ 1,6 @@
1721
979
366
299
675
1456

A  => testinputs/02 +3 -0
@@ 1,3 @@
1-3 a: abcde
1-3 b: cdefg
2-9 c: ccccccccc

A  => testinputs/03 +11 -0
@@ 1,11 @@
..##.......
#...#...#..
.#....#..#.
..#.#...#.#
.#...##..#.
..#.##.....
.#.#.#....#
.#........#
#.##...#...
#...##....#
.#..#...#.#

A  => testinputs/04 +13 -0
@@ 1,13 @@
ecl:gry pid:860033327 eyr:2020 hcl:#fffffd
byr:1937 iyr:2017 cid:147 hgt:183cm

iyr:2013 ecl:amb cid:350 eyr:2023 pid:028048884
hcl:#cfa07d byr:1929

hcl:#ae17e1 iyr:2013
eyr:2024
ecl:brn pid:760753108 byr:1931
hgt:179cm

hcl:#cfa07d eyr:2025 pid:166559648
iyr:2011 ecl:brn hgt:59in

A  => testinputs/05 +4 -0
@@ 1,4 @@
BFFFBBFRRR
FFFBBBFRRR
FFFBBBRFFR
BBFFBBFRLL

A  => testinputs/06 +15 -0
@@ 1,15 @@
abc

a
b
c

ab
ac

a
a
a
a

b

A  => testinputs/07 +9 -0
@@ 1,9 @@
light red bags contain 1 bright white bag, 2 muted yellow bags.
dark orange bags contain 3 bright white bags, 4 muted yellow bags.
bright white bags contain 1 shiny gold bag.
muted yellow bags contain 2 shiny gold bags, 9 faded blue bags.
shiny gold bags contain 1 dark olive bag, 2 vibrant plum bags.
dark olive bags contain 3 faded blue bags, 4 dotted black bags.
vibrant plum bags contain 5 faded blue bags, 6 dotted black bags.
faded blue bags contain no other bags.
dotted black bags contain no other bags.

A  => testinputs/08 +9 -0
@@ 1,9 @@
nop +0
acc +1
jmp +4
acc +3
jmp -3
acc -99
acc +1
jmp -4
acc +6

A  => testinputs/09 +100 -0
@@ 1,100 @@
9
12
28
42
46
30
23
10
29
40
27
32
8
3
13
1
5
48
33
17
41
47
44
22
2
42
57
73
88
98
15
53
89
93
71
115
57
120
54
101
147
209
161
90
130
124
214
235
308
218
309
382
448
625
709
969
158
1407
1274
713
779
1555
1725
661
2507
3426
2120
4746
4471
7803
5237
4603
4112
3125
7371
901
10545
2175
6899
7025
17163
17729
23500
20506
14145
33537
34488
33485
42978
58063
43160
22429
34340
79306
81123
136180
184439
341147
356694
548428

A  => testinputs/10 +11 -0
@@ 1,11 @@
16
10
15
5
1
11
7
19
6
12
4

A  => testinputs/11 +10 -0
@@ 1,10 @@
L.LL.LL.LL
LLLLLLL.LL
L.L.L..L..
LLLL.LL.LL
L.LL.LL.LL
L.LLLLL.LL
..L.L.....
LLLLLLLLLL
L.LLLLLL.L
L.LLLLL.LL

A  => testinputs/12 +5 -0
@@ 1,5 @@
F10
N3
F7
R90
F11

A  => testinputs/13 +2 -0
@@ 1,2 @@
939
7,13,x,x,59,x,31,19

A  => testinputs/14 +4 -0
@@ 1,4 @@
mask = 000000000000000000000000000000X1001X
mem[42] = 100
mask = 00000000000000000000000000000000X0XX
mem[26] = 1

A  => testoutputs/01 +2 -0
@@ 1,2 @@
514579
241861950

A  => testoutputs/02 +2 -0
@@ 1,2 @@
2
1

A  => testoutputs/03 +2 -0
@@ 1,2 @@
7
336

A  => testoutputs/04 +2 -0
@@ 1,2 @@
2
2

A  => testoutputs/05 +2 -0
@@ 1,2 @@
820
120

A  => testoutputs/06 +2 -0
@@ 1,2 @@
11
6

A  => testoutputs/07 +2 -0
@@ 1,2 @@
4
32

A  => testoutputs/08 +2 -0
@@ 1,2 @@
5
8

A  => testoutputs/09 +2 -0
@@ 1,2 @@
309
47

A  => testoutputs/10 +2 -0
@@ 1,2 @@
35
8

A  => testoutputs/11 +2 -0
@@ 1,2 @@
37
26

A  => testoutputs/12 +2 -0
@@ 1,2 @@
25
286

A  => testoutputs/13 +2 -0
@@ 1,2 @@
295
1068781

A  => testoutputs/14 +2 -0
@@ 1,2 @@
51
208

A  => time.c +202 -0
@@ 1,202 @@
#define _XOPEN_SOURCE 600
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <time.h>
#include <assert.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <stdarg.h>
#include <sys/wait.h>

typedef struct {
  struct timespec start;
  struct timespec stop;
} timer;

void timer_start(timer *t) {
  clock_gettime(CLOCK_REALTIME, &t->start);
}

void timer_stop(timer *t) {
  clock_gettime(CLOCK_REALTIME, &t->stop);
}

double timer_delta_ns(timer *t) {
  double delta_ns = (double) t->stop.tv_nsec - (double) t->start.tv_nsec;
  double delta_s = (double) t->stop.tv_sec - (double) t->start.tv_sec;
  return delta_ns + 1e9 * delta_s;
}

void print_delta_ns(FILE *f, double ns) {
  if (ns >= 1e9) {
    /* seconds */
    fprintf(f, "%0.3lg s", ns * 1e-9);
  }
  else if (ns >= 1e6) {
    fprintf(f, "%0.3lg ms", ns * 1e-6);
  }
  else if (ns >= 1e3) {
    fprintf(f, "%0.3lg us", ns * 1e-3);
  }
  else {
    fprintf(f, "%0.3lg ns", ns);
  }
}

double smallest(const double *numbers, size_t n) {
  double min = numbers[0];
  for (size_t i = 1; i < n; ++i) {
    if (numbers[i] < min) {
      min = numbers[i];
    }
  }
  return min;
}

char *my_asprintf(const char *fmt, ...) {
  va_list args;
  /* calculate size */
  va_start(args, fmt);
  int n = vsnprintf(NULL, 0, fmt, args);
  va_end(args);
  /* allocate */
  size_t size = (size_t) n + 1; // + 1 for final \0.
  char *buf = malloc(size);
  assert(buf != NULL);
  /* print */
  va_start(args, fmt);
  n = vsnprintf(buf, size, fmt, args);
  va_end(args);
  /* check and return */
  assert(n >= 0);
  return buf;
}

bool run_day(unsigned day, double *time_ns) {
  /* tries to run; return true if successful, false if the source file does not exist; aborts in case of any other problem */
  char *exe_fn = my_asprintf("src/%02u.c", day);
  char *inp_fn = my_asprintf("inputs/%02u", day);
  /* check if source code exists */
  struct stat buf;
  if (stat(exe_fn, &buf) != 0) {
    if (errno == ENOENT) {
      free(exe_fn);
      free(inp_fn);
      return false;
    }
    else {
      fprintf(stderr, "Day %02u ERROR: %s\n", day, strerror(errno));
      abort();
    }
  }
  /* Open input */
  int inp_fd = open(inp_fn, O_RDONLY);
  if (inp_fd < 0) {
    fprintf(stderr, "Day %02u ERROR: Could not open input file: %s\n", day, strerror(errno));
    abort();
  }
  /* Open /dev/null */
  int devnull_fd = open("/dev/null", O_WRONLY);
  if (devnull_fd < 0) {
    fprintf(stderr, "Day %02u ERROR: Could not open input file: %s\n", day, strerror(errno));
    abort();
  }
  /* Start, fork & exec, stop timer */
  timer t;
  timer_start(&t);
  pid_t pid;
  if ((pid = fork()) == 0) {
    /* child: replace stdin with the input, stdin & stdout with /dev/null; then exec */
    if ((dup2(inp_fd, 0) < 0) || (dup2(devnull_fd, 1) < 0) || (dup2(devnull_fd, 2) < 0)) {
      fprintf(stderr, "IO redirect error: %s\n", strerror(errno));
      abort();
    }
    char * const args[] = { "tcc", "-run", exe_fn, NULL };
    assert(execvp("tcc", args) == 0);
  }
  else if (pid > 0) {
    /* parent, success */
    int status;
    assert(wait(&status) == pid);
    if (status != 0) {
      fprintf(stderr, "Day %02u ERROR: Program exit code %d.\n", day, status);
      abort();
    }
  }
  else {
    /* parent, failure */
    fprintf(stderr, "Day %02u ERROR: fork() failed: %s\n", day, strerror(errno));
    abort();
  }
  /* stop timer, clean up */
  timer_stop(&t);
  if (time_ns != NULL) {
    *time_ns = timer_delta_ns(&t);
  }
  assert(close(inp_fd) == 0);
  assert(close(devnull_fd) == 0);
  free(inp_fn);
  free(exe_fn);
  return true;
}

#ifndef repeat
#define repeat 1
#endif

int main(void) {
  /* prepare */
  double total_times_ns[repeat] = { 0, };
  double *times_ns[25] = { 0, };
  for (size_t i = 0; i < sizeof times_ns / sizeof *times_ns; ++i) {
    times_ns[i] = calloc(repeat, sizeof **times_ns);
    assert(times_ns[i] != NULL);
  }

  /* run with total timer and local timers */
  for (unsigned rep = 0; rep < repeat; ++rep) {
    timer t;
    timer_start(&t);
    for (unsigned day = 1; day <= 25; ++day) {
      run_day(day, times_ns[day-1]+rep);
    }
    timer_stop(&t);
    total_times_ns[rep] = timer_delta_ns(&t);
  }

  /* print result */
  size_t successes = 0;
  if (repeat > 1) {
    printf("Times (best out of %d):\n", repeat);
  }
  else {
    printf("Times:\n");
  }
  for (unsigned day = 1; day <= 25; ++day) {
    printf("Day %02u: ", day);
    if (times_ns[day-1][0] != 0.0) {
      ++successes;
      print_delta_ns(stdout, smallest(times_ns[day-1], repeat));
      printf("\n");
    }
    else {
      printf("MISSING\n");
    }
    free(times_ns[day-1]);
  }
  printf("Total: ");
  print_delta_ns(stdout, smallest(total_times_ns, repeat));
  printf("\n");
  if (0 < successes && successes < 25) {
    printf("Extrapolated to 25 days: ");
    print_delta_ns(stdout, smallest(total_times_ns, repeat) * 25 / successes);
    printf("\n");
  }

  return 0;
}