How to Upgrade to Debian 12

Author: Leo Nguyen Jun 12, 2023

This post will show you how to upgrade Debian 11 (Bullseye) to Debian 12 (Bookworm).

Step 1: Create backup

This is very important. If something goes wrong, you can always go back from the backup/snapshot.

Step 2: Update current packages

sudo apt update
sudo apt upgrade
sudo apt full-upgrade
sudo apt autoremove

Step 3: Upgrade to the new version of Debian

cat /etc/apt/sources.list # inspect content
sudo sed -i 's/bullseye/bookworm/g' /etc/apt/sources.list

cat /etc/apt/sources.list.d/*.list # inspect content
sudo sed -i 's/bullseye/bookworm/g' /etc/apt/sources.list.d/*.list

sudo apt update
sudo apt upgrade
sudo apt full-upgrade
sudo apt autoremove

Tips:

  1. During the upgrade process, Debian may ask you to handle a modified config file. It's generally a good idea to install the package maintainer's version (unless you have a reason not to), then set your custom settings in a separate config file.

  2. The full-upgrade part may install further packages that were held back during the normal upgrade part. Database servers (like MariaDB) are one example.

Step 4: Configure new system

Configure your new system accordingly, such as pointing the nginx sites to the correct updated php-fpm version.

Step 5: Reboot

A reboot is recommended to fully update your operating system and ensure a complete restart of system and services.

sudo reboot

Advent of Code: Day 8

Author: Leo Nguyen Dec 8, 2021

Oh man, Day 8 is hard.

Part 1

You're given a seven-segment display. That's the numbers on your digital clock, each one formed by some segments. However, the segments are all mixed up, so you'll need to demix them somehow.

For the input, you're given the list of all digits that are now represented by the mixed up segments. However, the mixup itself is consistent for all digits. Now if we pay careful attention, we will realize that the digits 1, 4, 7, and 8 each has a unique number of segments. So for Part 1, calculate how many times these 4 digits appear.

It's just a simple counting problem, so here's my Python code:

def read(filename):
    file = open(filename)
    lines = file.read().splitlines()

    entries = []

    for line in lines:
        fields = line.split(' | ')
        left = fields[0]
        right = fields[1]
        segments = left.split()
        digits = right.split()
        entries.append([segments, digits])

    return entries


def solve(entries):
    # Count unique digits
    count = 0

    for entry in entries:
        digits = entry[1]
        for digit in digits:
            if len(digit) in [2, 4, 3, 7]:
                count += 1
                print(digit)

    print(count)


entries = read('input.txt')
solve(entries)

Part 2: The Real Monster

After Part 1, the real problem appears. In the input, you're also given a 4-digit number for each line. Of course, each has segments mixed up like before. Now, use analysis to decode these numbers then add them up.

This took me one full day to complete, not gonna lie.

After a lot of thinking, I found that we could reuse part of Part 1. Basically, now we know what 1, 4, 7, 8 look like. Notice that if we take the segments of 7 and subtract 1's segments, we now get the segment a left. And that's our first segment decoded!

Next, for each entry, if we count the number of times a segment appears in the digits from 0 to 9, we will have these unique situations:

  • Segment b appears exactly 6 times

  • Segment e appears exactly 4 times

  • Segment f appears exactly 9 times

So this gives us another 3 segments decoded. Also, some pairs of segments appear the same number of times too:

  • Segments a and c appear exactly 8 times

  • Segments d and g appear exactly 7 times

For ac, since we already knew a from before, we can easily deduct segment c.

Now, if we subtract 7's segments from 4's, we have two segments of bd. Coupled with the segments dg above, we can find the only segment d that satisfies two criteria. The only segment left, g must be the remaining one.

As usual, below is my solution code. Please excuse its length:

def read(filename):
    file = open(filename)
    lines = file.read().splitlines()

    entries = []

    for line in lines:
        fields = line.split(' | ')
        left = fields[0]
        right = fields[1]
        digits = left.split()
        outputs = right.split()
        entries.append([digits, outputs])

    return entries


def parse_digit(dictionary, mixed_digit):
    a = dict()
    a['abcefg'] = 0
    a['cf'] = 1
    a['acdeg'] = 2
    a['acdfg'] = 3
    a['bcdf'] = 4
    a['abdfg'] = 5
    a['abdefg'] = 6
    a['acf'] = 7
    a['abcdefg'] = 8
    a['abcdfg'] = 9

    segments = [dictionary[mixed_segment] for mixed_segment in mixed_digit]
    segments.sort()
    return a[''.join(segments)]


def solve(entries):
    # create dictionary(a -> b) and digits([0] = 'ab')
    dictionary = dict()
    digits = [''] * 10

    total = 0

    for entry in entries:
        mixed_digits, outputs = entry

        # Determine unique digits
        digits[1] = [digit for digit in mixed_digits if len(digit) == 2][0]
        digits[7] = [digit for digit in mixed_digits if len(digit) == 3][0]
        digits[4] = [digit for digit in mixed_digits if len(digit) == 4][0]
        digits[8] = [digit for digit in mixed_digits if len(digit) == 7][0]

        # Determine segment a from 7 - 1
        a = list(set(digits[7]) - set(digits[1]))[0]
        dictionary[a] = 'a'

        # Count characters
        character_count = dict()

        for char in 'abcdefg':
            character_count[char] = 0

        for digit in mixed_digits:
            for char in digit:
                character_count[char] += 1

        # Determine character maps
        ac, dg = [], []
        for char in 'abcdefg':
            match character_count[char]:
                case 6:
                    dictionary[char] = 'b'
                case 4:
                    dictionary[char] = 'e'
                case 9:
                    dictionary[char] = 'f'
                case 8:  # could be either a or c
                    ac.append(char)
                case 7:  # could be either d or g
                    dg.append(char)

        # Determine a and c
        # c = ac - a
        dictionary[list(set(ac) - set(a))[0]] = 'c'

        # Determine d and g
        # d exists in bd = 4 - 1
        bd = set(digits[4]) - set(digits[1])
        if dg[0] in bd:
            dictionary[dg[0]] = 'd'
            dictionary[dg[1]] = 'g'
        else:
            dictionary[dg[1]] = 'd'
            dictionary[dg[0]] = 'g'

        # Parse mixed digits to digits
        parsed_number = [parse_digit(dictionary, mixed_digit) for mixed_digit in outputs]

        s = parsed_number[0] * 1000 + parsed_number[1] * 100 + parsed_number[2] * 10 + parsed_number[3]
        total += s

    print(total)


entries = read('input.txt')
solve(entries)

Advent of Code: Day 7

Author: Leo Nguyen Dec 7, 2021

On Day 7, we are attacked by a giant whale and thankfully a swarm of crabs come to the rescue. Each crab has its own submarine that it controls. (Don't ask me, I felt like it's going out of control too.)

Now each crab is determined by its horizontal position. The example input is 16,1,2,0,4,2,7,1,2,14. Now the crabs need to move horizontally until they're at the same position. Each unit of horizontal movement costs one fuel. Your goal is to find the optimal position that costs the least fuel for all crabs. To submit your result, enter the total fuel.

For the example above, the optimal position is 2. And the total fuel needed is 37.

It's pretty straight-forward so my solution is quite simple:

def read(filename):
    file = open(filename)
    crabs = [int(i) for i in file.readline().split(',')]

    return crabs


def solve(crabs):
    fuels = []

    for position in range(min(crabs), max(crabs) + 1):
        # Calculate total fuel if all crabs move to this position
        fuel = 0
        for crab in crabs:
            moves = abs(crab - position)
            fuel += moves

        fuels.append(fuel)

    print(min(fuels))


crabs = read('input.txt')
solve(crabs)

Part 2 has a modification that makes the puzzle more interesting.

The cost to move each horizontal unit, instead of 1, is now increasing. This means step 1 takes 1 fuel, step 2 takes 2 fuel, and so on. So now our solution needs some adjustment:

def read(filename):
    file = open(filename)
    crabs = [int(i) for i in file.readline().split(',')]

    return crabs


def solve(crabs):
    fuels = []

    for position in range(min(crabs), max(crabs) + 1):
        # Calculate total fuel if all crabs move to this position
        fuel = 0
        for crab in crabs:
            moves = abs(crab - position)
            fuel += (moves + 1) * moves / 2

        fuels.append(fuel)

    print(min(fuels))


crabs = read('input.txt')
solve(crabs)

I mean, it's pretty fun to have crabs zeroing in to save you underwater, isn't it?


Advent of Code: Day 6

Author: Leo Nguyen Dec 6, 2021

Day 6 is my top favorite in terms of mathematical optimization so far.

Part 1

There are many lanternfish at the sea floor. Each lanternfish has an internal timer that is in a range from 0 to 6. This timer decreases by 1 every day. However, when the timer is 0 and ticks to the next day, it will become 6 instead and a new lanternfish is created! This new lanternfish has a timer of 8.

You're given an input of lanternfish, decoded by their timer. For example, 3,4,3,1,2. Your goal is to find out how many total lanternfish after 80 days.

Thus, my solution is so:

def read(filename):
    file = open(filename)
    fishes = list(map(int, file.read().split(',')))

    return fishes


def solve(fishes):
    for day in range(80):
        for i in range(len(fishes)):
            # Grow
            if fishes[i] == 0:  # breed
                fishes[i] = 6
                fishes.append(8)
                continue

            fishes[i] -= 1

    print(len(fishes))
    return


fishes = read('input.txt')
solve(fishes)

Part 2

Surprisingly, Part 2 doesn't introduce any new mechanics. It simply asks you the same question: how many lanternfish are there after 256 days? So the only detail that changed is the days.

Naively, I changed the days variable into 256 and hit run. A decade later, it still didn't finish. I knew then something was up.

It turned out that the real challenge of this puzzle is memory limit and processing speed. Since the lanternfish grow exponentially, after roughly 100 days, the drag kicked in. My computer couldn't process the growth of these creatures efficiently anymore.

After a session of penning on my notebook, I discovered an elegant solution, a purely mathematical one: Let's call f(x) is the number of lanternfish at timer x. Then after every day, f(x) becomes f(x + 1) with f[8] = f[0] (new fish bred) and f[6] += f[0] due to timer reset from breeding.

This f(x) allows me from maintaining a list of billions of lanternfish to an f(x) list of just 9 members. The solution is now therefore:

def read(filename):
    file = open(filename)
    fishes = list(map(int, file.read().split(',')))

    return fishes


def solve(fishes):
    # Call f(x) is the number of fish at timer x
    f = [0] * 9
    for fish in fishes:
        f[fish] += 1

    for day in range(256):
        f_zero = f[0]
        # Fish decrease value
        for x in range(8):
            f[x] = f[x + 1]

        # Fish 0 breeds new and become 6
        f[6] += f_zero
        f[8] = f_zero

    print(sum(f))
    return


fishes = read('input.txt')
solve(fishes)