Arjen Wiersma

writing

It is December again and that means it is time for the Advent of Code. Due to my workload and family obligations I will probably not be able to get very far this year, but still I wanted to write a post about it.

This year I am using Java, together with my students. My goal is to write as modern as possible Java, which means using streams and new language constructs where possible.

Day 1 {#day-1}

In day 1 we are parsing 2 lists of numbers, with the lists printed vertically. This means each line has 2 numbers, one for list one and the other for list two. To parse these data structures I used a very nice stream where I map each line onto a String[] using split.

To be sure that the input is valid, the peek method allows you to check if the result is what you intended, and otherwise an exception will terminate everything. From here I map the String[] into a Pair record which holds the 2 numbers. Streaming over the resulting pairs the left and right lists can be extracted quite easily.

I loved this approach, it is very straightforward and does not have a lot of control flow.

@Override
public List<List<Integer>> parseInput(List<String> input) {
    var pairs = input.stream()
        .map(s -> s.split("\\s+"))
        .peek(parts -> {
                if (parts.length != 2)
                    throw new IllegalArgumentException("Invalid input format");
            })
        .map(parts -> new Pair<>(Integer.parseInt(parts[0]), Integer.parseInt(parts[1])))
        .collect(Collectors.toList());

    var left = pairs.stream()
        .map(Pair::left)
        .collect(Collectors.toList());

    var right = pairs.stream()
        .map(Pair::right)
        .collect(Collectors.toList());

    return List.of(left, right);
}

Solving the problem with these lists was quite easy. In part 2 there was a need for a frequency table of a list. I also found a very nice solution to that problem using the groupingBy method from Collectors.

input.get(1).stream()
    .collect(Collectors.groupingBy(n -> n, Collectors.counting()));

Day 2 {#day-2}

I really liked day 2, the first part was quite straightforward. You have to identify increment or decrement only lists and apply some conditions to them.

Part 2 was much more interesting, here you have to account for fault tolerance. In the Python implementations that were posted the common solution is to concatenate 2 parts of the array and then rerun the validation logic.

Using streams we can something very similar. First we use an IntStream to iterate over every int[] (report). Then for every int in that report, we construct a new array by filtering out the index of the current item. After that it is a simple case of determining increment or decrement and applying the conditional logic.

Suppose you have a list of [1,2,4,7], while iterating it will first hit index 0, the filter will prevent that entry from continuing. Next 1 through 3 will continue and as a result of toArray() a new array will be constructed with only those items.

x == 0
     |    map ------.
     v  -------     v
    [1, 2, 4, 7]    [2,4,7]
input.stream()
    // Loop over the list
    .filter(in -> IntStream.range(0, in.length) // take a report
            // for every entry in that int[]
            .anyMatch(x -> {
                    // create a new list, excluding the one we are on now
                    int[] c = IntStream.range(0, in.length)
                        .filter(i -> i != x)
                        .map(i -> in[i])
                        .toArray();

                    boolean allInc = IntStream.range(0, c.length - 1)
                        .allMatch(i -> c[i] <= c[i + 1]);

                    boolean allDec = IntStream.range(0, c.length - 1)
                        .allMatch(i -> c[i] >= c[i + 1]);

                    boolean good = IntStream.range(0, c.length - 1)
                        .allMatch(i -> Math.abs(c[i] - c[i + 1]) >= 1 &&
                                  Math.abs(c[i] - c[i + 1]) <= 3);

                    // matching the condition
                    return (allInc || allDec) && good;
                })
            )
    .count();

My first solution was nothing like this, but after refining it I am very happy with how clean it came out.

Day 3 {#day-3}

This was the traditional easy puzzle after a more complicated one. Basically simple parsing for which I used regular expressions. Nothing special, on to day 4.

Day 4 {#day-4}

For day 4 I solved the first part with an over engineered path finding solution, which turned out to be quite the overkill, but extremely fun to program.

I really like the pattern in use, below is some of the code of it. First you create a Deque that holds the work, then you load it up with the initial starting points. In the case of the puzzle these are the location of the X characters.

From there you just loop over the work, taking a partial solution and seeing if any cells around it will lead to another partial solution, so from XM to XMA and on the next iteration to XMAS. The dx is a collection of Coord that indicate valid movements across the board.

Deque<Path> work = new ArrayDeque<>();

// Load initial points
for (int y = 0; y < input.length; y++) {
    for (int x = 0; x < input[y].length; x++) {
        if (input[y][x] == 'X') {
            work.add( new Path(List.of(new Coord(x,y)), "X", null) );
        }
    }
}

// Process each outstanding point..
while (!work.isEmpty()) {
    var path = work.pop();
    for (Coord d : dx) {
        if (path.dir() != null && path.dir != d) {
            continue;
        }
        var newCoord = lastStep.add(d);
        // Ensure this is a valid point on the grid
        if (newCoord.x() >= 0 && newCoord.x() < input[0].length &&
            newCoord.y() >= 0 && newCoord.y() < input.length) {
            // ... create new paths and string based on location
            // Check if we have an end case, else add it to the work
            if (target.equals(xmas)) {
                matches.add(newPath);
            } else if (target.startsWith(xmas)) {
                work.add(newPath);
            }

        }
    }
}

A more straightforward approach, which was actually needed for part 2, is to just try to solve it in one step. First you iterate over both y and x coordinates looking for an X, just as above. When you find one, iterate over [-1, 0, 1] on both the x and y axis-es, using dy and dx for the direction. If both direction are 0, we continue as it would give the current position. The beauty of this approach is that you can move outward in steps, x + 3 * dx will give you a value 3 cells in the give direction. From there it is a simple matter of checking if we are in bounds and if the letters spell MAS.

for (int y = 0; y < input.length; y++) {
    for (int x = 0; x < input[y].length; x++) {
        if (input[y][x] != 'X') continue;
        for (int dy = -1; dy <= 1; dy++) {
            for (int dx = -1; dx <= 1; dx++) {
                if (dy == dx && dx == 0) continue;
                if (!(0 <= y + 3 * dy && y + 3 * dy < input.length &&
                      0 <= x + 3 * dx && x + 3 * dx < input[y].length)) continue;

                if (input[y+1*dy][x+1*dx] == 'M' &&
                    input[y+2*dy][x+2*dx] == 'A' &&
                    input[y+3*dy][x+3*dx] == 'S') {
                    matches++;
                }
            }
        }
    }
}

For part 2 a similar approach can be used, however the order is not important. So I chose to create a List and then check against a target list with the containsAll method, it does not care about order.

var x1 = List.of(input[y-1][x-1], input[y][x], input[y+1][x+1]);
var x2 = List.of(input[y-1][x+1], input[y][x], input[y+1][x-1]);
if (x1.containsAll( target ) && x2.containsAll( target )){
    matches++;
}

Another reminder to not over engineer at the start.

Day 5 {#day-5}

Another fun puzzle, when I initially read it my mind jumped to graphs. There is a 2 part input, the first part being a list of rules, numbers that are only valid when they are placed in front of other numbers.

The second part of the input is a list of report structures. The first quest was to validate the reports and find only the valid ones.

My first attempt, in part 1, was to take the rules for a number (a List<Integer>) and see if there is an anyMatch of the sublist before it using order::contains. Basically if the pages is 75,97,47,61,53 and the rule 97|75 (97 should be before 75), the the loop will iterate over the pages, and check to see if [75] is in the list of rules for 97.

boolean isValid(List<Integer> pages, Instructions input) {
    var valid = true;
    for (int i = 0; i < pages.size(); i++) {
        var order = input.order().get(pages.get(i));
        if (order != null) {
            var hasAny = pages.subList(0,i+1).stream().anyMatch(order::contains);

            if (hasAny) {
                valid = false;
            }
        }
    }
    return valid;
}

Part 2 had us fixing the broken pages. After some initial magic with arrays I figured out it is a basic sorting problem. In Java you can use Comparator implementations to create custom sorting rules, as long as it responds with -1,0,1 for to the left, the same, to the right. So the lambda Comparator takes a left hand side and right hand side value, retrieves the rules for the left hand side (if null it is equal 0) and checks to see if the right hand side is in the ruleset (-1). If all checks fail, the value should go to the right hand side.

var answer = 0L;
for (var pages : input.pages()) {
    var valid = isValid(pages, input);
    var work = new ArrayList<>(pages); // pages is immutable
    if (!valid) {
        Collections.sort(work, (lhs, rhs) -> {
                var order = input.order().get(lhs);
                if (order == null) return 0;
                if (order.contains(rhs)) return -1;
                return 1;
            });
        answer += work.get(work.size()/2);
    }
}

A surprisingly easy solution to a messy problem when you want to implement it yourself.

Day 6 {#day-6}

Traditionally the Friday puzzles seem to be somewhat more challenging, this Friday is no exception. We are given a challenge similar to sliding puzzle games.

Instead of sliding over ice we are to map the movements of a guard to ensure we can move safely through the area. For part 1 there was nothing too exciting, just move the guard over the floor and track the places visited. Depending on your loop you might accidentally avoid an edgecase that will show up in part 2.

Lets take a look at the loop:

while (inBounds) {
    visited.add(start);
    var next = start.add(delta.get(sign));
    if (!next.inBound(0, input[0].length, 0, input.length)) {
        inBounds = false;
        continue;
    }
    if (input[next.y()][next.x()] == '#') {
        sign = turns.get(sign);
        continue;
    }
    start = next;
}

While we are *in bounds* we keep moving, adding each step into the visited list. We then get the next position by retrieving the delta (a lookup table of coordinates such as -1,0, which indicate that the guard will move -1 on the x-axis and 0 on the y-axis). If we are out of bounds, flip the switch and break out of the while loop, if the next position is an obstacle, #, we set the sign to the 90 degree turned version (another lookup table) and rerun the loop. If, for some reason, you continue checking and validating at this point you might miss the edge-case that turning can result in facing another wall. When all the conditions are checked, simply reset the start variable to the next coordinates and move on.

Part 2 becomes much more interesting; we are to find infinite loops by placing exactly 1 extra obstacle. Intuitively you will remark that the obstacle can only be placed on one of the cells that were visited in part 1. This already eliminates part of the board. From here you can loop over the list of coordinates, place an obstacle and let the guard run its route. When you visit a coordinate twice in *the same direction* you know you are in a loop.

I looked for a “smart” solution, but the brute force is done in less then 2 seconds. So I will leave it at this, but somehow feel there might be more optimizations possible.

Day 7 {#day-7}

The end of week 1, and easier then the Friday puzzle. We are given a list of numbers per line that we need to either add or multiply to get to a target number. I chose to use some recursion to solve this problem. Each iteration of the recursion will reduce the array of numbers using one of the operations.

In the end the list of numbers will be reduced to either the target number, or something else. So the base case checks to see if it was successful.

If the base case is not hit, the first recursion is to add the numbers. A trick here is to use a LongStream to range over 1 to the end, mapping the numbers. If number 1 is mapped, we add the number at position 0 to reduce the array.

The second case applies the multiplication in the same way.

The third case (part 2) is to concatenate the numbers, this is easil done through number + "" + number in java, coercing the numbers into a String and then using Long.valueOf() to read the value again.

boolean isValid(long target, long[] numbers, boolean third) {
    if (numbers.length == 1) return target == numbers[0];
    if (isValid(target, LongStream.range(1, numbers.length)
                .map(i -> {
                        if (i == 1) return numbers[0] + numbers[1];
                        return numbers[(int)i];
                    })
                .toArray(), third)) return true;
    if (isValid(target, LongStream.range(1, numbers.length)
                .map(i -> {
                        if (i == 1) return numbers[0] * numbers[1];
                        return numbers[(int)i];
                    })
                .toArray(), third)) return true;
    if (third && isValid(target, LongStream.range(1, numbers.length)
                          .map(i -> {
                                  if (i == 1) return Long.valueOf(numbers[0] + "" + numbers[1]);
                                  return numbers[(int)i];
                              })
                          .toArray(), third)) return true;
    return false;
}

One last trick is to use the Stream feature to filter the list, mapping each object to a long value and summing.

@Override public Long solver1(List<Calibration> input) {
    return input.stream().filter(i -> isValid(i.target, i.numbers, false)).mapToLong(cal -> cal.target).sum();
}

Day 8 {#day-8}

Day 8 has us back in history staring at antennas. The description was quite cryptic, but reading it carefully you learn that the necessary step is to find the difference between a pair of coordinates and then extrapolate the path inside the bounds of the grid.

To read the grid into a structure I used a simple nested loop, adding a new list to the map if it is absent, then adding the new coordinate for the antenna.

for (int r = 0; r < gridH; r++) {
    for (int c = 0; c < gridW; c++) {
        char ch = input.get(r).charAt(c);
        if (ch != '.') {
            antennas.computeIfAbsent(ch, k -> new ArrayList<>()).add(new Coord(c, r));
        }
    }
}

We then have to find the antinode for the point which, for the pair, is just a single difference step from the antenna. Interestingly we need to count the *unique* antinodes. Whenever you get such a requirement, always think about using a Set for storage.

Getting the pairs is straightforward, and we have done it earlier in the series already. The first loop starts at 0 and ends the element before the end, size - 1. The inner loop starts at current pos + 1 and ends at the size of the list.

for (int p = 0; p < coords.size() - 1; p++) {
    for (int n = p + 1; n < coords.size(); n++) {

Then just compute the difference and add the antinode when it is in bounds.

var antinode1 = cur.add(cur.diff(next));
var antinode2 = next.add(next.diff(cur));

In part 2 the path needs to be extrapolated until it goes out of bounds. This can easily be wrapped in its own method.

void addAntinodesInDirection(Set<Coord> antinodes, Coord start, Coord diff) {
    var current = start;
    while (true) {
        var next = current.add(diff);
        if (!next.inBound(0, gridW, 0, gridH)) {
            break;
        }
        antinodes.add(next);
        current = next;
    }
}

A pretty straightforward problem to solve, on to tomorrow!

Day 9 {#day-9}

For me this was a hard day. We are given a list of numbers that we use to fragment files on a disk. The first part of the puzzle was quite straightforward, create a list that holds the file ids and spaces and just follow the rules.

for (int i = 0; i < input.length(); i++) {
    for (int j = 0; j < input.charAt(i)-'0'; j++) {
        if (i%2 == 0) {
            disk.add(id);
        } else {
            disk.add(null);
        }
    }
    if (i%2 == 0) {
        id++;
    }
}

From there just create 2 pointers, one on the left and one on the right. The left tracks the empty space and the right tracks the file ids that we want to put in the empty space. The important thing in Java is to make the implementation of the list a LinkedList. This allows for little-overhead reshuffling of the list.

var l = 0;
var r = disk.size() - 1;
while (l < r) {
    if (disk.get(l) != null) {
        l++;
        continue;
    }

    if (disk.get(r) == null) {
        r--;
        continue;
    }

    disk.set(l, disk.get(r));
    disk.set(r, null);
    l++;
    r--;
}

Part 2 became much harder, we are not to find space for the blocks of files instead of fragments. I first tried the same approach, but it took forever. I then saw the error of my ways and decided to use a lookup table for the empty spaces. This table maps the empty spaces of size N, in the below example 1 and 2, to a list of start/end coordinates.

1 = [1,1] [2,2]
2 = [3,4] [6,7]

The list of coordinates needs to remain sorted, so I used a PriorityQueue for it. Then it is just a matter of determining the size of the file under the r pointer by looping over it until we hit another id, and then looking up the most left candidate of the empty spaces.

// Gets all candidates that will fit the file
for (int i = bs; i < 10; i++) {
    var earliest = free[i].peek();
    if (earliest != null && earliest < r) {
        candidates.add(new Candidate(i, earliest));
    }
}

if (candidates.isEmpty()) {
    return null;
}

// Sort based on the index (most left first)
candidates.sort((lhs, rhs) -> {
    return lhs.idx() - rhs.idx();
});

var can = candidates.getFirst();
free[can.size()].remove();

The final solution runs in a matter of milliseconds, so I am quite happy with that.

Day 10 {#day-10}

Finally a depth first / breath first path seeker! We need to identify a trail that leads to a summit, or rather all trails that lead to the summit. Part 1 wants to know the score (how many summits can a path reach) and part 2 its rating (how many trails are there that reach a summit). This is pretty straightforward in the sense that you create a Queue and put the start of the trail in, then for each direction you construct a more complete path.

As always it is important to check for bounds and if the path is incremental (business rule). If the new path is actually at the summit, add it to the finished paths. If it is not, try to complete it.

Trail solve(char[][] grid, Coord zero) {
var q = new ArrayDeque<List<Coord>>();
q.add(List.of(zero));

List<List<Coord>> paths = new ArrayList<>();
while (!q.isEmpty()) {
    var current = q.removeFirst();

    for (Coord d : zero.directNeighbors()) {
        var last = current.getLast();
        var nc = last.add(d);

        if (!nc.inBound(0,grid[0].length, 0, grid.length)) continue;
        if (grid[nc.y()][nc.x()] != grid[last.y()][last.x()] + 1) continue;

        var newPath = new ArrayList<>(current);
        newPath.add(nc);

        if (grid[nc.y()][nc.x()] == '9') {
            paths.add(newPath);
        } else {
            q.addLast(newPath);
        }

    }
}
var score = paths.stream().map(l->l.getLast()).collect(Collectors.toSet()).size();
var rating = paths.size();

return new Trail(score, rating);

Day 11 – one to remember {#day-11-one-to-remember}

For the last couple of days the discussion forums have been full with memes about brute forcing the answer. Up to now you could really do so. I have one colleague who wrote a nice brute force for Day 6 that took several minutes, but it did work. Personally I am not a fan of the brute forcing approach, I like to make it more elegant when possible.

Today is this years first Lanternfish type of problem, one where the problem space becomes so large that your computer is not able to brute force it due to memory constraints. It calls for a more elegant solution.

Part 1 and part 2 are basically the same, the difference is the amount of iterations for the problem. In this case we have some rules in which rocks change and split up. We are tasked to find the amount of rocks after 25 and 75 iterations. The first is do-able with a brute force approach, the second is not.

The rules are straightforward, but the solution to the problem space might not be. The trick is to use something called memoization.

In computing, memoization or memoisation is an optimization technique used primarily to speed up computer programs by storing the results of expensive function calls to pure functions and returning the cached result when the same inputs occur again.

So, basically we store results of method calls. Lets first look at my solution. It is a recursive function that takes a number and the number of iterations to apply to it.

if (iterations == 0) return 1;
var cache = new Cache(number,iterations);
if (memo.containsKey(cache)) {
    return memo.get(cache);
}
if (number == 0) {
    long l = ways(1, iterations - 1, memo);
    memo.put(cache, l);
    return l;
}

String s = "" + number;
int l = s.length();
if (l % 2 == 0) {
    long c = ways(Long.parseLong(s.substring(0,l/2)), iterations-1, memo) + ways(Long.parseLong(s.substring(l/2,l)), iterations-1, memo);
    memo.put(cache, c);
    return c;
}
long c =  ways(number * 2024, iterations-1, memo);
memo.put(cache, c);
return c;

For this call the result is stored in a memo, a simple HashMap that stores the method arguments in a CacheKey and then stores the count, the result of the recursive call. What happens here is that there might be many different calls to this method, such as (9,23). In this case the number 9 has 23 iterations to go. By computing the result once and then storing the computed value we save the time of doing the same calculations many other times.

This was a really fun and relatively quick challenge, greatly enjoyed it!

Day 12 – Garden Groups {#day-12-garden-groups}

This day had me stumped for quite a time. The puzzle starts off with a simple question; group the garden (grid) into areas that are the same and count the fences required. A simple flood fill type of solution works very well here.

Whenever a neighbor is not the same type (or we are the edge), a fence is required. If it is the same type, add it to the queue for further processing.

for (int r = 0; r < input.length; r++) {
    for (int c = 0; c < input[r].length; c++) {
        var start = new Coord(c, r);

        if (!seen.add(start)) {
            continue;
        }

        var queue = new ArrayDeque<Coord>();
        queue.add(start);

        var cells = new HashSet<Coord>();
        int perimeter = 0;
        char fenceType = input[r][c];

        while (!queue.isEmpty()) {
            Coord cell = queue.poll();
            cells.add(cell);

            for (Coord neighbor : Coord.directNeighbors()) {
                Coord next = cell.add(neighbor);

                if (!next.inBound(0, input[0].length, 0, input.length) ||
                    input[next.y()][next.x()] != fenceType) {
                    perimeter++;
                } else if (seen.add(next)) {
                    queue.add(next);
                }
            }
        }

        fences.add(new Fence(perimeter, cells));
    }
}

The next part had me going for a little bit. Instead of the area or fences the elves need the sides counted. This turns out to be quite a thing until it becomes clear that counting corners also works.

Lets say the below map is our grid. When looking at the A in cell 0,2 it is possible to check if it is a corner by checking that the cell above it and the cell to the right are not the same. The same goes for the cell below and the cell to the right.

{{< figure src=“/ox-hugo/corners.png” >}}

A neat trick to find the sides to an area. The code turned out to be relatively easy:

for (var cell : fence.cells()) {
    char curr = input[cell.y()][cell.x()];

    for (int ud : new int[]{-1, 1}) {
        for (int lr : new int[]{-1, 1}) {
            int ny = cell.y() + ud;
            int nx = cell.x() + lr;

            boolean outOfBoundsY = ny < 0 || ny >= input.length;
            boolean outOfBoundsX = nx < 0 || nx >= input[0].length;

            if ((outOfBoundsY || input[ny][cell.x()] != curr) &&
                (outOfBoundsX || input[cell.y()][nx] != curr)) {
                corners++;
            } else if (!outOfBoundsY && !outOfBoundsX &&
                        input[ny][cell.x()] == curr &&
                        input[cell.y()][nx] == curr &&
                        input[ny][nx] != curr) {
                corners++;

        }
    }
}

On to Friday the 13th!

Day 13 – Claw Contraption {#day-13-claw-contraption}

Today was quite something. We have claw machines that have 2 buttons. The buttons move the arm a set space on the x and y coordinates. Both buttons have a different value for the cost and we are tasked to find the cheapest path to a prize on some distant x and y location.

My initial solution was naive and used dynamic programming to solve it. It did not adhere to the rule every problem has a solution that completes in at most 15 seconds on ten-year-old hardware. So eventually I found a solution (thanks Hyperneutrino!) that uses math to solve this problem.

Basically we are trying to find the amount of x and y movements both the A button (indicated by S) and the B button (indicated by T) have to make in order to get to the prize x and y values.

axS + bxT = px
ayS + byT = py

We can make these equations the same by multiplying with by and bx. We do this so we can remove the B button from the equation and solve A.

axbyS + bxbyT = pxby
aybxS + bybxT = pybx

Now bxbyT == bxbyT

This can then be rewritten in a single equation, from which we can isolate A.

axbyS - aybxS = pxby - pybx

(axby-aybx)S =  pxby - pybx

Now we can divide by axby - aybx and get our solution for A. We have to ensure the input is never 0 to prevent division by zero. In the code we can check this by checking that ax * by == ay * bx never occurs.

S = pxby - pybx
    -----------
    axby - aybx

As we now have the A button value, we can also solve the B button.

axS + bxT = px
bxT = px - axS
T = px-axS
    ------
    bx

In code the solution looks very simple. Notice the ca%1==0 && cb%1==0 check to ensure we do not allow for fractional steps.

private long solve(double ax, double ay, double bx, double by, long px, long py) {
    long answer = 0L;
    double ca = (px * by - py * bx) / (ax * by - ay * bx);
    double cb = (px - ax * ca) / bx;
    if (ca % 1 == 0 && cb % 1 == 0) {
        answer += (long) (ca*3) + cb;
    }
    return answer;
}

Day 14 – Restroom redoubt {#day-14-restroom-redoubt}

Today we are back at Easter Bunny HQ, looking for a restroom. We are given a collection of robots, their current position on a grid and their velocity. The question becomes, where are they after 100 iterations (seconds)? An interesting part of this question is that the robots wrap around the grid.

This mechanic allows for very easy calculation of the final coordinates, as (x + vx * 100) % width will give us the final position, instead of having to go through all the calculations.

Interestingly, Java does not really like negative numbers in the modulo operator. For example, -102 % 11 yields -3 while it should yield 8 for it to be useful in our case. So, when the number is negative, just add the width to it.

for (var robot : robots) {
    int newX = (robot.start.x() + robot.vel.x() * 100) % width;
    if (newX < 0) newX += width;

    int newY = (robot.start.y() + robot.vel.y() * 100) % height;
    if (newY < 0) newY += height;

    positions.merge(new Coord(newX, newY), 1, Integer::sum);
}

Part 2 was a horrible puzzle. There was no clue what to do in order to get this:

{{< figure src=“/ox-hugo/day14.png” >}}

I finally solved it by looking at the field when all robots are on a unique position. I also have seen solution where the minimum safety value is found. The problem description was:

During the bathroom break, someone notices that these robots seem awfully similar to ones built and used at the North Pole. If they're the same type of robots, they should have a hard-coded Easter egg: very rarely, most of the robots should arrange themselves into a picture of a Christmas tree.

What is the fewest number of seconds that must elapse for the robots to display the Easter egg?

Maybe if it had said “very rarely, but when all the robots arrange themselves”, but then again, how are you supposed to know that it means non-overlapping.

Love the Christmas tree though.

Day 15 – Warehouse Woes {#day-15-warehouse-woes}

Yay, the Lanternfish have made an appearance! Sadly this puzzle had me quite stumped for a while. I had to rewrite my solution 2 times in order to get it right.

First, let me explain. We are still not finding the historian (whom I think is just Eric in a costume). Instead we are on a side-quest helping our fishy friends with robots in their warehouses. The first puzzle is straightforward; move the player around, moving objects that you run into.

When we have that sorted we are sent to a second warehouse. This time the boxes that we move are twice as large, but the robot is still the same size. This means we get into situations as the following example:

######
#    #
# [] #
# @  #
######

Here the player can move up, but we are only hitting one side of the box. We have to take into account that we need to move the other part along as well. Even more complicated, we can get into the following situation.

######
#[]  #
#[][]#
# [] #
# @  #
######

In this situation we can not move, even though the 2nd box on the middle layer might think we can, as it has a white-space above it.

I worked on arrays for a while, but eventually went for a more “Java” solution and create the factory as objects. Using the objects it is possible to attack the problem more in a “game engine” type of way, by making each object react to the interaction.

My code is horrible not-optimized, I apologize for that right away, but it gets the job done :D

Firstly, I split the process out into two segments, first to see if we can move, then to actually move. Side note: I should really use a lookup table for the coordinate to find the objects instead of looping over it.

The objects are simple POJOs, all extending the aptly named Thing.

class Player extends Thing {
    public Player(Coord start, Coord end) {
        super(start, end);
    }
}

class Wall extends Thing {
    public Wall(Coord start, Coord end) {
        super(start, end);
        this.canMove = false;
    }
}

class Box extends Thing {
    public Box(Coord start, Coord end) {
        super(start, end);
    }
}

Thing has all the logic, with canMove() and move basically doing the same thing, except for move actually moving the objects into a new coordinate. Only if we have a space as neighbor, or if *all* of the neighbors can move, only then do we move the current object.

boolean move(List<Thing> factory, Coord direction) {
    if (!canMove)
        return false;

    Coord nsp = start.add(direction);
    Coord nep = end.add(direction);

    Set<Thing> hits = new HashSet<>();
    for (var t : factory) {
        if (t == this)
            continue;

        if (t.collidesWith(nsp))
            hits.add(t);

        if (t.collidesWith(nep))
            hits.add(t);
    }

    if (hits.size() == 0) { // space, so we can move ourself
        this.start = this.start.add(direction);
        this.end = this.end.add(direction);
        return true;
    }

    if (hits.stream().allMatch(t -> t.move(factory, direction))) {
        this.start = this.start.add(direction);
        this.end = this.end.add(direction);
        return true;
    }

    return false;
}

Collision is checked against both the left and right hand side of the object. Meaning that we can easily handle boxes of size 2.

boolean collidesWith(Coord c) {
    if ((start.x() == c.x() && start.y() == c.y()) || (end.x() == c.x() && end.y() == c.y())) {
        return true;
    }
    return false;
}

Much more work then I thought it would be, but a nice solution anyways.

More to come {#more-to-come}

[This article will be update with more days]

#writing

This is an English article, there is also a Dutch version.

So, you've decided to start studying? Maybe you want to earn your Bachelor or Master's degree, or perhaps you're aiming for that highly technical certificate. It's great that you're taking this step, but once you begin, you'll quickly need to answer the question of where you'll find the time.

Time is our most valuable, non-renewable resource. Studying requires time – and not just a little – so you naturally want to use it well. Most programs expect you to invest between 12 and 24 hours per week to keep up, and that's quite a bit! If you're not currently studying, try thinking about which days and times you could free up that time. Will you eat out less or exercise less? Get up early on weekends or stay up late?

Most students run into this problem when they start studying; I've seen it all too often. You start your studies with full confidence, only to realize you haven't considered how it fits into your life and what you need to sacrifice to make it work. Many students try to do everything at once, with predictable results. Thankfully, there is a strategy you can apply. I used this strategy during my part-time Bachelor and Master's studies, and it worked perfectly for me.

The core of the strategy is using your calendar. To manage your time effectively, you need to know exactly when you'll do what. This sounds simple, but most people only note important things in their calendar, like dentist appointments or Aunt Loes's birthday.

You do a lot in a day. Personally, I have a 40-hour workweek, go to the gym, spend time with my family, and much more. If I didn't plan these activities, I'd quickly lose track. Without an overview, it becomes difficult to study effectively because suddenly Aunt Loes will show up at the door.

The strategy includes the following steps:

  1. Use a calendar that you can access anywhere.
  2. Block out your work hours and stick to them.
  3. Also block time for your family and friends.
  4. Schedule study sessions.

It doesn't really matter which calendar you use, as long as it's accessible to you. In my examples, I'll use the Proton Calendar because it's secure and based in the EU. This way, you don't have to worry about big tech companies snooping into your schedule.

An empty calendar looks like this:

{{< figure src=“/ox-hugo/20240715193929screenshot.png” caption=”Figure 1: An empty calendar, that's quite calm!” >}}

As you can see, I've given each area of my life its own color. This is super handy because you can immediately see which appointment belongs to which area.

{{< figure src=“/ox-hugo/20240715194152screenshot.png” caption=”Figure 2: Each calendar its own color, though I'm not great at choosing colors.” >}}

As the first step, let's plan the workdays. I already have all my work appointments in my calendar, but I also block my work time with a calendar event. This way, I always know when I am or am not available. If you also have to travel to and from work, plan that time as well. I do this on Wednesdays and Fridays.

{{< figure src=“/ox-hugo/20240715195048screenshot.png” caption=”Figure 3: Step 1, plan your workdays” >}}

This may seem a bit silly because you know you're working, right? Of course, but for the mental model of your available time, it's essential to block this period. This way, you know you can't use that time for studying. See, now it seems like there's still a lot of time left, right?

During my study, I also found it important to reserve time for my family and to do fun things. Studying should be enjoyable and not feel like a punishment that prevents you from doing pleasant activities. Let’s now add all the family and friends' time to the calendar.

{{< figure src=“/ox-hugo/20240715195810screenshot.png” caption=”Figure 4: Step 2, time for family and friends” >}}

Now it's a different story. Weekends were important for me. Everyone works hard during the week, so time is needed to relax together. What's immediately noticeable is that there seems to be relatively little time left for studying, but appearances can be deceiving. I found it very convenient to study in the evenings, from around 8 PM to 12 AM. That's 4 hours in a day that worked well for me. Let's schedule that time.

{{< figure src=“/ox-hugo/20240715200322screenshot.png” caption=”Figure 5: Step 3, time to study” >}}

That amounts to 4 blocks of 4 hours, plus 3 hours on Saturday morning — totaling 19 hours available for studying. While planning, it immediately became apparent that the workout session on Thursday didn't fit well, so I moved it to the morning. With all this scheduled study time, there's also room for family and friends, at times that are often convenient for everyone.

But what if you're not a night owl? I also guide students during their graduation projects, and some are more morning people. They get up at 5 AM and study until 7 AM, often because they have small children and don't want to leave their partner with all the care. Personally, I prefer the evening, but to each their own.

When necessary, such as when a deadline is approaching, you can always borrow time from another block, but always do this in consultation. The advantage of planning everything is that it provides clarity, but it also creates expectations. If you change something, even temporarily, explain it, so you don't run into problems. I often adjusted my schedule by looking at the workload of a module and creating a study plan; I'll write more about that later.

I also had bonus time in my schedule. Sunday morning was often available, depending on my son's interest in a bouldering session. Additionally, I often had an hour in the morning, between when everyone left for work and school and when I started working. I used that time to read papers and make my initial notes. But more on that later.

Using the calendar effectively to reserve time is not revolutionary. There are other methods such as the “Trident Method” (explained by Ali Abdaal), and many other techniques for good time management. But this is the method that worked well for me.

Hopefully, you'll start your study time now with a good system to manage your time. This is the first step towards a successful study period!

#learnToStudy #writing

This is a Dutch artcile, there is also an English version.

Dus, jij hebt besloten om te gaan studeren? Misschien wil je jouw HBO- of Masterdiploma halen, of juist dat ene supertechnische certificaat bemachtigen. Het is geweldig dat je deze stap gaat zetten, maar zodra je begint, zul je vrij snel de vraag moeten beantwoorden waar je de tijd vandaan haalt.

Tijd is onze meest waardevolle, niet-hernieuwbare bron. Studeren vergt tijd – en niet zo'n klein beetje ook – dus wil je het natuurlijk goed doen. De meeste studies verwachten dat je wekelijks ergens tussen de 12 en 24 uur investeert om bij te blijven, en dat is flink wat! Als je nog niet studeert, probeer dan eens na te denken over welke dagen en momenten je die tijd kunt vrijmaken. Ga je minder uit eten of juist minder sporten? Vroeg opstaan in het weekend, of juist extra laat naar bed?

De meeste studenten lopen tegen dit probleem aan wanneer ze beginnen met studeren; ik zie het maar al te vaak. Je start vol vertrouwen aan een studie, maar dan realiseer je je dat je niet hebt nagedacht over hoe het in jouw leven past en wat je moet opofferen om te kunnen studeren. Veel studenten proberen dan ook alles tegelijk te doen, met alle gevolgen van dien. Gelukkig is er een strategie die je kunt toepassen. Ik heb deze strategie zelf gebruikt tijdens mijn deeltijd HBO- en Masterstudies, en voor mij werkte het perfect.

De kern van de strategie is het gebruik van je agenda. Om goed de baas te zijn over je tijd, moet je precies weten wanneer je wat gaat doen. Dit klinkt eenvoudig, maar de meeste mensen noteren alleen belangrijke zaken in hun agenda, zoals de tandartsafspraak of de verjaardag van tante Loes.

Op een dag doe je natuurlijk heel veel. Zelf heb ik een baan van 40 uur per week, ga ik naar de sportschool, wil ik tijd doorbrengen met mijn gezin, en nog veel meer. Als ik die activiteiten niet zou inplannen, raak ik al snel het overzicht kwijt. Zonder overzicht wordt het ook moeilijk om effectief te studeren, want dan staat opeens tante Loes op de stoep.

De strategie omvat de volgende stappen:

  1. Gebruik een kalender die je overal kunt raadplegen.
  2. Blokkeer je werktijd en houd je hieraan.
  3. Blokkeer ook tijd voor je gezin en vrienden.
  4. Plan studiemomenten in.

Het maakt niet echt uit welke kalender je gebruikt, zolang deze maar voor jou beschikbaar is. In mijn voorbeelden zal ik de Proton Calendar gebruiken, die is lekker veilig en gevestigd in de EU. Zo heb je geen last van grote techbedrijven die meekijken in jouw planning.

Een lege kalender ziet er zo uit.

{{< figure src=“/ox-hugo/20240715193929screenshot.png” caption=”Figure 1: Een lege agenda, dat is nog eens rustig!” >}}

Zoals je kunt zien, heb ik elk gebied van mijn leven een eigen kleurtje gegeven. Dat is superhandig, want daarmee zie je in één oogopslag waar een afspraak bij hoort.

{{< figure src=“/ox-hugo/20240715194152screenshot.png” caption=”Figure 2: Elke kalender een eigen kleur, helaas ben ik niet goed in kleuren kiezen.” >}}

Als eerste stap plannen we de werkdagen in. Ik heb zelf al mijn werkafspraken in mijn agenda staan, maar ook dan blokkeer ik mijn werktijd met een kalenderafspraak. Op die manier weet ik altijd wanneer ik wel of niet beschikbaar ben op een gegeven tijdstip. Als je ook moet reizen naar en van je werk, plan dan ook die tijd in. Ik doe dat bijvoorbeeld op woensdag en vrijdag.

{{< figure src=“/ox-hugo/20240715195048screenshot.png” caption=”Figure 3: Stap 1, plan jouw werkdagen in” >}}

Dit lijkt misschien een beetje suf, want je weet toch dat je werkt? Natuurlijk is dat zo, maar voor het mentale model van jouw beschikbare tijd is het belangrijk om deze periode te blokkeren. Zo weet je zeker dat je die tijd niet kunt gebruiken om te studeren. En kijk, nu lijkt het alsof er nog heel veel tijd over is, toch?

Tijdens mijn studie vond ik het ook belangrijk om tijd te reserveren voor mijn gezin en om leuke dingen te doen. Studeren moet leuk zijn en mag niet aanvoelen als een straf waardoor je geen plezierige activiteiten meer kunt ondernemen. Laten we nu alle tijd voor gezin en vrienden ook in de kalender zetten.

{{< figure src=“/ox-hugo/20240715195810screenshot.png” caption=”Figure 4: Stap 2, tijd voor het gezin en vrienden” >}}

Dan wordt het al een ander verhaal. Voor mij was het weekend belangrijk. Iedereen werkt hard gedurende de week, dus er is ook tijd nodig waarin we allemaal kunnen ontspannen. Wat meteen opvalt, is dat er dan relatief weinig tijd over lijkt te zijn om te studeren, maar schijn bedriegt. Ik vond het heel fijn om 's avonds te studeren, zo ongeveer van 8 uur tot 12 uur. Dat zijn toch 4 uren op een dag die voor mij goed werkten. Laten we die tijd inplannen.

{{< figure src=“/ox-hugo/20240715200322screenshot.png” caption=”Figure 5: Stap 3, tijd om te studeren” >}}

Dat zijn 4 blokken van 4 uur, plus de zaterdagochtend van 3 uurtjes — in totaal 19 uur die beschikbaar zijn om te studeren. Tijdens het inplannen viel meteen op dat de sportsessie op donderdag niet handig uitkwam, dus die heb ik verplaatst naar de ochtend. Met al deze ingeplande studietijd blijft er ook ruimte over voor familie en vrienden, precies op die momenten dat het voor iedereen goed uitkomt.

Wat nu als je geen avondmens bent? Ik begeleid ook studenten tijdens hun afstudeertraject, en sommigen zijn meer een ochtendmens. Zij staan om 5 uur 's ochtends op en studeren tot 7 uur. Dit doen ze vaak omdat ze kleine kinderen hebben en niet willen dat hun partner met alle zorg achterblijft. Zelf geef ik de voorkeur aan de avond, maar goed, ieder zijn ding.

Wanneer het nodig is, bijvoorbeeld als je dicht bij een deadline zit, kun je natuurlijk altijd tijd van een ander blok gebruiken. Doe dat echter altijd in overleg. Het voordeel van alles uitplannen is dat het duidelijkheid schept, maar daardoor ontstaan ook verwachtingen. Als je dan iets verandert, ook al is het tijdelijk, leg het uit zodat je geen problemen krijgt. Ik paste mijn schema vaak aan door naar de studielast van een module te kijken en een studieplan te maken; daarover zal ik later meer schrijven.

Zelf had ik ook nog bonustijd in mijn schema. De zondagochtend was vaak beschikbaar, afhankelijk van hoe mijn zoon naar een bouldersessie keek. Daarnaast had ik 's ochtends vaak nog een uurtje tijd, tussen het moment dat iedereen naar werk en school gaat en dat ik daadwerkelijk aan mijn werk begin. Die tijd gebruikte ik om papers te lezen en mijn eerste aantekeningen te maken. Maar ook daarover zal ik later meer schrijven.

Het “goed” gebruiken van de kalender om tijd te reserveren is natuurlijk niet revolutionair. Er zijn ook andere methoden zoals de “Trident Method” (uitgelegd door Ali Abdaal), en talloze andere technieken om tijd goed in te delen. Maar dit is de methode die voor mij goed werkte.

Hopelijk begin je jouw studietijd nu met een goed systeem om je tijd in te delen. Dit is de eerste stap naar een succesvolle studietijd!

#writing #learnToStudy

This is a longer form article. I is relevant as of February 18th 2023. If the circumstances of my environment changes I will try to update this article to reflect the situation. You can find the full source code of my dotfiles on Github.

I like consistency and simplicity. I do not like to use many different tools to do different things, I rather spend my time learning to use a few tools very well then to follow the hype on the latest trend of tools for something we have been doing forever.

This philosophy I transfer to pretty much everything in life. I have been using the same laptop bag for ages, I have a small mechanical keyboard, and I run the same version of my OS on all my devices. One device for on the go, the other for at home. They look the same and act the same, courtesy of an Linux distribution called NixOS.

Below you will find 2 screenshots, one from my laptop, the other from my desktop. The only difference is the size of the screen.

{{< figure src=“/ox-hugo/desktop.png” caption=”Figure 1: My Linux desktop on my laptop” >}}

{{< figure src=“/ox-hugo/desktop-large.png” caption=”Figure 2: My Linux desktop on my desktop” >}}

NixOS {#nixos}

I use the NixOS distribution of Linux. NixOS is a wonderful operating system that works by declaring what you want your environment to be and then applying that declaration to the current version of the environment. That sounds difficult, but let me explain.

Suppose you have just installed a Linux distribution and you want to install the wonderful Emacs editor. In most distributions you will go to the package manager, search for Emacs and click on install. A few seconds later, Emacs is installed. With NixOS you edit a file that describes your environment, you will add a line to it saying that Emacs is part of your environment. When you have saved the file you will ask NixOS to create a new version of your environment, to do so it will install Emacs for you.

I say it will create a new version of your environment. This means there is an old version as well, right? Yes! NixOS has a concept of Generations. This means every change happens in its own version of the environment. So, if a change goes wrong, you just revert back to the previous version.

This sounds like a great deal of work, and it is. It is not for the new Linux user, that is for sure. If you spend some time learning NixOS I am sure you will be grateful for it. Just the other day I tried to use the wayland system on Linux, my configuration went horribly wrong and I was left with an unusable system. I rebooted the machine, selected the previous generation, and I was back where I started before the change. It is that useful!

As I share my configuration over multiple machines I split up the configuration into a machine specific version to my desktop, laptop, and the things that should run on both:

The shared configuration contains all the juice, it sets up the graphical user interface, creates users and assigns to groups. This means that when you run this configuration you will end up in a very barren i3 tiling window manager. More on that later.

Most of my applications are courtesy of something called home-manager. This is a user-space application that allows for easy changes to the environment. As none of these changes can actually wreck the environment I kept them outside of the default NixOS configuration.

My home-manager configuration takes care of installing all the user-space tools that I use. It also sets up my shell and configures the Emacs daemon.

You might wonder, do you create a configuration file every time you need a tool? No! When I just need a one-off tool I use something called nix-shell. In the screenshots above you will notice that I run neo-fetch. This program is not part of my normal system as I only use it for screenshots as the one above. Within a terminal I run it as follows: nix-shell -p neofetch --run neofetch. This will temporarily install neo-fetch and run it. Afterwards it can be cleaned up. I also do this for most of the tools, such as unzip. I only install then when I need them. This keeps everything that is installed very clean.

You might also notice that there are not programming language toolchains in my configuration. That is correct. When I have a programming project I use something called direnv, see the direnv webpage for some background.

Whenever I start a new programming project I run the following command in the project root: nix --extra-experimental-features "nix-command flakes" flake new -t github:nix-community/nix-direnv .. This will create a flake.nix file in which I can declare what this project needs as dependencies. As the rest of my environment is extremely clean, I will need to specify precisely what is needed. Take the listing below, it is part of a programming project in which I use Rust, Golang, Python and Java. Whenever I move into this project, all the tools will be installed. This also means that it works exactly the same on every single system where I use this setup.

{
  description = "A basic flake with a shell";
  inputs.nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable";
  inputs.flake-utils.url = "github:numtide/flake-utils";

  outputs = { self, nixpkgs, flake-utils }:
    flake-utils.lib.eachDefaultSystem (system: let
      pkgs = nixpkgs.legacyPackages.${system};
    in {
      devShells.default = pkgs.mkShell {
        packages = with pkgs; [
          pkg-config
          openssl.dev
          cargo
          rustc
          rustfmt
          clippy
          rust-analyzer
          aoc-cli
          go
          gopls
          gotools
          govulncheck
          pkgs.jdk
          pkgs.jdt-language-server
          pkgs.python311
        ];
        # Environment variable specifying the plugin directory of
        # the language server 'jdtls'.
        JDTLS_PATH = "${pkgs.jdt-language-server}/share/java";
      };
    });
}
Code Snippet 1: A nix-direnv declaration for a polyglot programming project

This might seem like a hassle. It is true, it is more work then just installing Golang on Ubuntu and “just having it”. But once you use multiple systems or work together in groups you will start appreciating it, trust me.

i3 {#i3}

As I like simplicity I tend to not use elaborate windowing environments, such as Gnome or KDE. I try them out every once in a while, but I also go back to i3. Back in the day I ran enlightenment, but now I have been using i3 WM for quite some years. My configuration is quite mature and I generally only change it when I want to add a new tool to my daily use, or when tools get good updates such as polybar. The configuration is part of my dotfiles.

When I boot my system all I have is a top bar that contains the following information:

  • 💻 Active workspaces (each has its own icon and use)
  • 💾 Current fill state of my disks
  • 🛡️ VPN status
  • 🔊 Sound and its volume percentage
  • 🛜 Wifi state (laptop only)
  • 🔋 Battery state (laptop only)
  • ⏰ Time
  • 📥 Tray icons (flameshot, bluetooth and nextcloud)

That is it. After all those years working with computers, that is all I really need. If I could I would write a toggle for the bar as well, to only show up when needed. The very appealing thing about i3 is its tiling feature. I will never have windows that overlap. Everything is neatly ordered in workspaces and within workspaces in columns or rows. As I create dedicated workspaces everything has a specific place:

  1. Terminal (alacritty with tmux)
  2. Emacs
  3. Virtual Machines
  4. Firefox
  5. Chrome

From workspace 6 on I consider them “throw-away” workspaces. The things I will store there will be used only shortly. The exception is workspace 10 (or 0). This contains my Spotify.

To launch applications I use something called Rofi. It is a window switcher, application launcher and menu replacement tool. It is very easy to customize and you can make it exactly what you want. My configuration is available on github.

{{< figure src=“/ox-hugo/rofi.png” caption=”Figure 3: Rofi launching applications in i3” >}}

You can configure your environment exactly as you want. Take a look at r/unixporn for some more extreme versions of customized desktops.

#emacs #development #writing

Let me tell you how it was to ship a product out to half a million people back in 1999. But before I do that, let me tell you why. Today I talked to one of my students and he mentioned that he was very nervous about a change he was making. He was afraid it would break things and that he would spend the afternoon working through his CI/CD pipeline to resolve issues.

Well, back in 1999 I worked on a project. Together with some friends we were building cool software in Borland Delphi and life was good. One of the things that we had built was a nifty dialler application that you could run on Windows. It would dial into your ISP and it made the entire process a lot easier and it made all the dealings with modems and telephone lines so much simpler. Why would we make such a thing? It was actually a commission for one of the earlier internet providers in The Netherlands. It was well received and we made our first big bucks. It was awesome.

After finishing the project I received a call. There was a secret project in the parent company and they needed the software as well. The project turned out to be the creation of a free internet provider. The provider was Freeler. The term free meant you only paid for your telephone line, but not for the service itself. It was a cool and radical idea and the parent company gave the project group 1 month to put everything into place and market it. In modern times that would be 1 sprint.

Needless to say it was a pressure cooker. In hindsight I did not really understand many of things going on, I was just focused on modifying my dialler application to do the job that was asked. The idea was to have a CD ROM ready just before launch time. The CD ROM would then be placed at gas stations and other high traffic areas. It had to work flawlessly. The thing with CD ROMs is that you can't send a patch if something is wrong.

As I was just out of my teen years it was all quite hectic and I had never released software on this scale. So I made my changes to the application, but how do you make sure it is correct? It worked on my computer, but how do you test something like this? Well, first you need to make your CD ROM. So we built an image and sent it to the pressing company, we received a box a few hundred testers the next day. So, time to call all our family members? Fun fact, thanks to people of the internet archive you can still download the CD ROM image from them.

{{< figure src=“/ox-hugo/freeler.png” alt=“the Freeler CD ROM” width=“300” >}}

After some calls and research my technical partner on the project found a laboratory that actually specialized in testing CD ROMs. It was one of the coolest things ever; they take your CD ROM and feed it to a robotized setup. In this laboratory they had hundreds of machines from various manufacturers running various versions of the Windows operating system. It was pure magic to behold.

We spent several days at the laboratory getting results. Some machines did not auto start the software, others ran into issues setting up the connections. It was an effort, but at the end of the day I fixed all the issues and a master cd was made. This is basically the template from which all the copies are created.

So, now we have some tested software and a distribution medium that will work with the target audience. We are finished right? Well, no. As people use the CD ROMs they will have questions. Some people will never have dialled into the internet before, some people might not even have a modem (no, that is not a joke). So to ensure their questions are answered a call center is needed. I don't remember how big the call center was, but I do remember it was in the center of Groningen.

Given the time crunch, the deadline was only in a few days, these operators needed to be trained. They needed to be trained in working with the dialler application. So I was sent to Groningen to work with the call center. Imagine the sight; you just created an application, went to a laboratory to test it, then created a bunch (a million-ish) CD ROMs and then you wait for people to call with issues. The first time the phone rings you heart drops. “Did it not work?”, “Did I miss something?”.... it is not like you can go around people's house to fix the issues. Patching it is not possible, since they use the application to get onto the internet.

Luckily for me the software worked quite nicely. Freeler grew to 350.000 members. But to release this simple piece of software I spent weeks working through many painstaking processes.

So why do I tell this story? Well... having the luxury of CI/CD, instant feedback and the ability to patch things the same minute/hour/day should be the greatest good in the world. Be fearless, merge your changes, fix your issues, deploy without anxiety.... you will never have to see a robot feed a CD ROM to a computer in order to find out if you code works.

#development #writing

Where to begin... {#where-to-begin-dot-dot-dot}

I just watched David Wilson's talk called M-x forever – why Emacs will outlast text editor trends. He gave this talk at EmacsConf 2021 which is a conference dedicated to Emacs. The talk made me reflect on my use of Emacs over the last years and then I realized that this year will mark 22 years that Emacs and I have been together. This (probably too long) article reflects on those years and dives into my use of Emacs.

First Contact {#first-contact}

Back in 1998 I was planning a move to the US. I had just finished my first professional programming gig writing a RAS Dialer for Freeler internet, the first “free” internet provider in The Netherlands. Pretty much everyone in The Netherlands (who had a modem and tried Freeler) was using my software to connect to the service. I was programming in various languages at the time, from Delphi (for the dialer) to Perl (yes, it was popular then!). This is when I first started playing with Linux and editors like Vi and Emacs.

When I moved to the US (San Francisco) I started working for Personify, a company focused on building an analytical platform for e-commerce sites. I started working in a small team which dealt with webserver logfile parsing. That sounds like a great job for Perl, right? My mentor worked in the same team and he guided me through the hectic time of working at a pre-2000 startup in Silicon Valley.

Picking Emacs for life {#picking-emacs-for-life}

As I was working closely with my mentor I started picking up on things he did. He was (and probably still is) a wizard behind the keyboard. He used Emacs as his editor. Naturally I gave it a try, but found it hard to really get productive with it and so I kept switching back and forth between Vi, Emacs and several other editors. It always amazed me how quick he was in getting stuff done. One day, my mentor told me to stop and take the time to learn my tools.

His reasoning was that when you learn your tool well it will not impede you from completing your task. You will be able to complete it as fast as you can simply because the tool becomes an extension of your thought process and it is possible to leverage the features it has to the benefit of the task at hand. It did not matter which tool I would choose, but pick one and learn it well. I accepted the challenge of working with a single tool for 30 days and see if my work speed would improve.

I chose to use Emacs, as it allowed me to look at the things he did. The availability of information on how to best use software was not as abundant as it is now. Now you can look up anything on YouTube or any blog, back then there was the Emacs Manual and other users that I ran into during technical meetups.

Now we are 22 years later and I still use Emacs everyday for pretty much anything. It has been with me through transitioning from Windows, to macOS and since 2017 full time on Linux. It came with me when moving between continents (back to Europe) and through various jobs at different companies.

Cheating on Emacs {#cheating-on-emacs}

I too have been tempted by new fashioned shiny tools that promised to be the next big thing in Editing. I even switched to Vim for a little while. The thing is, I always came back to Emacs. I think this is mostly due to the fact that new tools have something spectacular and then you want to adjust it and you find you can not do it to the degree that Emacs allows you to change the way it works for you.

While working with programming languages like Java the lure of IDEs has been quite tempting as well. In teams where pretty much everyone uses IntelliJ IDEA you stand out when just opening up Emacs. It often results in quite some interesting discussions as to what “makes a good developer”. Some people are quite unreasonable and think an IDE makes the developer, but I greatly disagree on that topic.

Serendipity {#serendipity}

Over the years there have been several serendipitous moments in the Emacs community for me. I will pick out 2:

  • I started sponsoring Jonas Bernoulli in 2021 for his work on Magit and back in 2012 he sent in a patch for my worklog package which I only found out while preparing this article and looking at the logs of my various projects.
  • I was referenced in a talk by Martin Clausen. He gave a presentation on a Clojure project and in the conclusion he talked about knowing your tools. We had been doing some live streams together working on Clojure projects and while he talked about using your tools effectively he casually dropped a “then you need to talk to this guy” and pointed my out in the crowd.

Giving back {#giving-back}

A YouTube series {#a-youtube-series}

In 2015/2016 I started working a little less and found time to do a Friday morning live-coding series. It featured me learning to write Clojure by building various projects. In the videos I, of course, used Emacs in pretty much every way possible.

I also created a series of videos called Productive Emacs in which I highlighted useful packages (focused on Clojure development). They were a big hit (at least I think so) and connected me to wonderful people like Mike Zamansky.

Writing packages {#writing-packages}

In my time with Emacs I have written and maintained some code as well. Most notably in 2003 I took over a package for tracking time (worklog) as I had a job where I need to track my time and naturally I chose to do this in Emacs.

I also have a grey theme out on melpa that is being used by several people. I always enjoy getting responses from the community when they like the work that has gone into making things.

Whenever I can I will post about some snippet of emacs-lisp I have written, such as my Advent of Code helper function. It retrieves the input for a puzzle during December and puts it into a file. It then prints the puzzle description and allows you to quickly get going. It is awesome to use your editor to make your life easier!

Providing patches {#providing-patches}

One of the most meaningful things in this entire ecosystem is to give back to the projects when you can. Whenever I run into a bug or oddity I will try and fix it. When my skill-set allows it I will send in a patch or documentation update to show my appreciation for the work people do. This entire community runs on people spending their free time working on code that helps many of us.

My configuration {#my-configuration}

Recently there was a twitter exchange with Bozhidat Batsov in which Stephen A. Ramsey calls Emacs a lifestyle choice as someone makes the remark that the people they know have been configuring Emacs as long as they have been using it.

{{< figure src=“/ox-hugo/twitter.png” >}}

I too am one of those that is always tinkering with my configuration. I think it is part of having such an extensible editor and using it constantly for everything. The community keeps making amazing packages that provide entire new dimensions to the use of Emacs (hi use-package, Projectile, Counsel, Ivy, Org-Roam, Company, and many many more) that it only makes sense to be critical and try out anything that seems useful and an improvement. The ease with which it is possible to add new functions to Emacs is amazing.

I said ease, but for new users it can be quite daunting to start with a configuration. That is why projects like Doom Emacs and Spacemacs are so important. I have tried both and I think the work done in them is amazing! I often go into their repositories and check out the packages they use and take snippets from their configuration.

A few years back I converted my pre-2000 init.el to a literal configuration using org-mode and babel. This allows you to treat your configuration as documentation and annotate it using the wonderful org-mode. Sadly I sometimes lack the discipline to properly annotate my configuration like Protesilaos does.

You can still check out my configuration in its repository. The wonderful thing of having a configuration in the open is that many people take from it. It has resulted in various connections around the globe that to this day exchange ideas and experiments using my configuration as a base.

Favorite packages {#favorite-packages}

So after all these years, there must be some favorite packages. Yes there are. It is important to note that I use Emacs for pretty much everything in my day-to-day live; from making presentations and documents in LaTeX, keeping notes and scheduling in org-mode, writing code in languages such as Java, C, Rust and Clojure and reading my Email. The list below is in no means a complete reference, but it gives a sense of what I do in my day-to-day life using Emacs.

Org-mode and Org-Roam {#org-mode-and-org-roam}

I think org mode can be seen as one of the most influential packages in the Emacs ecosystem. For me it has taken over most of my note-taking, document processing needs.

Lets start with my daily tasks. As a teacher I have many daily things to keep track of, from work that needs reviewing, classes that need preparing and the grading of papers. To keep track of everything and not loose sight of anything I have implemented a Getting Things Done (GTD) workflow.

Anything that lives in a browser is connected to my GTD system using org-protocol, anything that lives in Emacs is captured using capture templates. This means that anything that will take me more then 2 minutes to take care of will go into my GTD system and will be filed away with scheduling dates, deadlines and notes on the task itself. In my archive I have cleared over 10000 tasks in the current version of the system.

The article you are reading now is also an Org document. It lives in an archive called website and has an Emacs build script that generates this website in a Github Action.

As an extension to org-mode I use org-roam to keep track of knowledge snippets. This can be anything from development tricks, hacking methodology, background information on some technology to grading notes on papers. All this knowledge is captured in my org-mode archive and is accessible through a series of links in topic covering notes.

{{< figure src=“/ox-hugo/org-mode.png” >}}

Mu4e {#mu4e}

The use of org-mode continues into my Email. All my mail is read through mu4e. It seamlessly ties into my GTD workflow, its editing builds on the document processing I already do with org-mode and using various modules I can even read horrible HTML emails.

Most importantly, captures I have made from email into the GTD system will link directly to the email itself, so it is as if the system is one concise whole, making it very productive.

Magit {#magit}

As I put everything I find of value into git repositories I tend to use Magit quite a lot during the day. It has totally redefined how Emacs should do interfaces for tools and has shown the world how a good git interface can integrate into the editor. It puts anything else to shame in my opinion.

{{< figure src=“/ox-hugo/magit.png” >}}

Which-Key {#which-key}

A few years back I added which-key to my configuration after reading an article on discoverability of features. It is an amazing extension that allows you to press a key combination and it will pop-up a list of all following combinations that can be accessed from there. This greatly helps when you are trying to remember that one key sequence for that feature you don't use that often.

{{< figure src=“/ox-hugo/which-key.png” >}}

LSP / eglot {#lsp-eglot}

With the introduction of VSCode to the world Microsoft actually made something useful as well, the Language Server Protocol (LSP). The LSP is the thing that makes VSCode such a hit, it separated the editor frontend from the language understanding backend.

Emacs now has 2 LSP infrastructures, lsp-mode and eglot. I have tried both and eventually settled on using eglot for my daily programming tasks.

The lsp-mode package is the most extensive and feature loaded of the two, it also means it has a lot of configuration to tweak it to your liking. The package also takes care of installing the needed language server for your language and sets it up automatically.

On the other hand, eglot, is bare bones and configuration-less. Just drop it in and it works after manually installing a language server. It is simple and unobtrusive, and as I do a lot of screen sharing while coding I chose it over lsp-mode.

Both packages give Emacs an IDE set of features, such as context sensitive completion, formatting of code, refactoring, organization of imports and the execution of code actions on errors and warnings. It has redefined Emacs for me when it comes to writing code.

{{< figure src=“/ox-hugo/lsp.png” >}}

Projectile {#projectile}

The last of my favorite packages is projectile. As the name implies it is a tool to work with projects. As I said, I maintain a lot git repositories for anything I deem of importance. Projectile interacts with these git projects and allows you to quickly open files in the project, search them, run commands on them, switch between test and implementation code and so on.

As a productivity tool projectile makes code editing that extra bit enjoyable.

Helpful resources {#helpful-resources}

As the community is large there are several resources I keep track of to find new features, applications and packages.

Mastering Emacs : a book and website with cool and useful tricks in Emacs.

Emacs News by Sacha Chua : a weekly digest that summarizes the week of Emacs news, a great resource to read with a cup of coffee.

Emacs Reddit : the Emacs sub-reddit is extremely wonderful when it comes to sharing information. The community is great and supportive (who would say this about Reddit, right?)

YouTube : the following channels offer a lot of information; Protesilaos and System Crafters.

Closing thoughts {#closing-thoughts}

Emacs and I have been together for 22 years now and I don't see it changing anytime soon. Emacs itself is getting better and better with initiatives such as native-compilation and the Pure GTK patches. Its package ecosystem is in excellent condition and packages such as org-mode and eglot / lsp-mode make even the most demanding programming languages a joy to work with in Emacs.

Here's to another 22 years Emacs!

#writing #emacs