My 2024 in Review

Updated Tuesday, Feb 18, 2025

Figure 1: Biked & camped 100 miles (160 km) from Milwaukee to Manitowoc in 36 hours

Figure 1: Biked & camped 100 miles (160 km) from Milwaukee to Manitowoc in 36 hours

It’s been a tumultuous year. It took a multitude of drafts to complete this short piece. I felt demoralized when authoring this post. Then I reviewed my 2023 in review post and it put my frustration in perspective: Life is pain and joy. It’s an undulation of anguish one day, blissful peace the next. Keep pushing through and learning. That’s all I should expect for myself. The rest is gravy.

Things I achieved this year

Figure 2: The physical journals of 2024 (digital not included!)

Figure 2: The physical journals of 2024 (digital not included!)

  1. I sold my car and lived car free in Milwaukee. Bikes and busses!
  2. Became an avid cyclist cruising around Milwaukee. I also went on multi-day a bike tour along Eastern Wisconsin. Stay tuned for bike content.
  3. Developed my exercise into an at-home calisthenics program (using the body for weight). I was exerting reps of 60 sit ups, 15 pull ups. While I cannot achieve these metrics today, soon I’ll restore to my peak performance then exceed it. Life had gotten in the way, and I need this for myself.
  4. Began going to therapy and working on my mental well being, big time. Wrote 70,000 words in journal.
  5. Tutored dozens of students. Computer literacy, Programmable Logic Controllers, Haskell, Python, Java, Perl, Computer Science things. I’ve learned a lot about teaching and my students seem to enjoy working with me.
  6. Migrated most of my computers off NixOS and towards Debian Stable/Testing. Life is too short to dink around with self-inflicted NixOS snafus and community invalidating all but the savants’ opinion on UX/DX. Will NixOS look the same in five years, hard to say. I do know Debian will, though!
  7. Bought a van to try vanliving in. Sold or donated 90% of my possessions. The less ownership, the better I felt. Turns out freedom (to me) means possessing what I need and enjoy, not what others say that I need to own.
  8. Moved out of Milwaukee and visiting with family and missed connections over the winter. Facing one’s demons is the ultimate therapy. Van travel begins sometime in early 2025.
  9. Joined two book clubs and read a LOT of books for the clubs and for myself.
Figure 3: The van for the vanlife experiment

Figure 3: The van for the vanlife experiment

Goals for 2025

  1. Continue with my fitness goals. Pain free is the name of the game. And a stronger core.
  2. Supplant my irrational desire for a tech job (solely for income) with a healthier way to earn money. 9-to-5 tech work isn’t for me. Search for serenity in the work. Eyes on the flow states. Because tech doesn’t have flow anymore… it’s mostly meetings and jumping up and down whenever boss asks you to.
  3. Continue with the navel gazing. Build up the creative muscle throughout. Express myself in more fun ways like art and writing.
  4. Continue reading lots. Continue writing lots. Monetize the writing. Poetry is really cool too; I’ll wordsmith them verses.
  5. Try vanliving and if it doesn’t work out, consider moving country where car ownership isn’t romanticized despite traffic violence manifesting as a fairly understood, tractable problem, yet we won’t solve it here in this country.
Figure 4: My main transportation for 2024. <$500 for everything in frame. DIY saves money big time.

Figure 4: My main transportation for 2024. <$500 for everything in frame. DIY saves money big time.

Thanks

Thanks to all my friends. Thanks to all that listened to me hem and haw about stuff. Thanks to all that were willing enough to take a chance and be vulnerable with me. Thanks to my parents for letting me stay around for a little while as I move on to vanliving. Thanks to my parents dog for being such a sweetie.

Figure 5: Scout is a good doggo

Figure 5: Scout is a good doggo

Don’t put up with bullshit. Most Americans live for about 2.44 gigaseconds. Make each second count.

Browser Bits

Updated Tuesday, Jan 7, 2025

All the browser userscripts that I’ve been using were scattered across a multitude of pastebin links and git repositories. It became a bit of effort to track them down to re-install in Greasemonkey (Firefox) or Tampermonkey (Chrome) with the time expended searching exceeding the time saved using my extensions. No beuno.

Enter Browser Bits: a curation of userscripts. (See awesome-userscripts to learn more!) Ultimately, the repository will contain Stylus stylesheet overrides and related hodgepodge.

My top two userscripts

Have a taste of my simplistic userscript utilization. It keeps me from breaking the computer, … erm, website.

MediaWiki Sidebar Toggle

I use my mediawiki sidebar toggle userscript almost daily (click to install). NethackWiki, Wikipedia, and more operate on the mediawiki stack. The classic Vector theme employs an obstinate sidebar devoid of a toggle button. This script adds a toggle back in and binds it to Control-'.

Advent of Code Adjustments

adventofcode.com has rather distracting styling (click to install). From text “glow” in lieu of typographically-sane emboldened text to a font choice which fails to be legible with a light foreground and dark background, it can use improvement. This userscript does not specify a replacement font, no. That’s for the user to decide! Merely specify your default fonts correctly and the userscript will just work.

You can customize websites too

Install Tampermonkey or Greasemonkey then copy-paste a template into a new userscript. Give it a shot. If you can pick up a modicum of the browser Javascript APIs, you can inject stylesheets, remove unwanted elements, and more.

As a bonus, if you post the script to a public URL ending in .user.js visitors well be greeted with a prompt asking to install the userscript. This couldn’t be easier! Or you could be like me, dump the scripts into a Git repository.

Keep it simple. The web sucks a lot more than it ever did—vis a vis, the web from the first half of my life—however all hope is not lost. You too can fix up the web enough to keep your technological sanity in check.

PS 2024 in Review in the works

I’ve been reading through my journals, all 70,000 words from 2024. It’s a slog. Stay tuned for a 2024 in Review post once my reading-while-navel-gazing-project arrives at completion.

Advent of Code 2024

Updated Thursday, Dec 12, 2024

Every December since 2015, Advent of Code publishes a Christmas Advent calendar loaded to the brim with challenging programming quibbles and trials. Advent of code can be solved in any programming environment, from Microsoft Excel to Rust, if you can write it, you can use it. In fact, my 2020 Advent of Code “challenge” (or “theme”) was a different language every day. Nope, I didn’t finish all 25 problems, though it was a heck of a lot of fun to practice Forth, various assembly languages, code golfing languages like CJam, various Lisps, Standard ML, even a real weird language called ZKL with a .howza() method in its base API.

AoC particitation is not limited to the days in December; all challenges are always available once published. Some people like myself do enjoy solving the problems as if it were a race, but I assure you, all you need to do is just log in with GitHub, Google, or another logic method then get coding!

Edit: Learn about the making of Advent of Code

After publishing this post I stumbled across this talk by Eric Wastl, the fellow who created AoC on the creation of the project.

My 2024 Goals

I don’t expect to complete AoC 2024 before the end of December, though I will achieve my own practice goals. My goal this year is to practice bog-standard Python with the allowance for click to simplify things.

Here is the basic outline of my Python solutions:

import click


@click.command()
@click.argument('puzzle-input', type=click.File())
def main(puzzle_input):
    for line in puzzle_input:
        ...  # Read the input into an appropriate data structure.

    part1 = part2 = 0  # Initialize the variables to contain the solutions.

    ...  # Crunch numbers for a solution!

    # Print the two solutions out with one per line.
    click.echo(part1)
    click.echo(part2)


if __name__ == '__main__':
    main()

Then I run it using python3 day01.py input.txt. Using click it’s about a line of code to add a --debug flag:

@click.command()
@click.argument('puzzle-input', type=click.File())
@click.option('--debug', is_flag=True)
def main(puzzle_input, debug):
    if debug:
        # Fire up the debugger.
        breakpoint()
    ...

By the way, turns out you don’t need two statements to start the built-in Python debugger. import pdb; pdb.set_trace() simplifies to breakpoint(). instead.

A secondary goal for AoC 2024 is to learn more Perl 5.

A tertiary goal is to practice Go and C.

SPOILERS: AoC 20204, day 1 remarks

This discussion is typed up after completion of the first few days. There be spoilers beyond this sentence, take heed! My goal here is to detail the solution for day 1 and discuss some of the language features used.

import click
import re


@click.command()
@click.argument('input', type=click.File())
def main(input):
    L1, L2 = [], []
    for line in input:
        a, b = [int(i) for i in re.split(r'\s+', line) if i]
        L1.append(a)
        L2.append(b)
    print(sum(abs(a - b) for a, b in zip(sorted(L1), sorted(L2))))
    print(sum(a * L2.count(a) for a in L1))


if __name__ == '__main__':
    main()

First, import click for the command line argument parsing and re for regular expression searching. Next declare the program entrypoint main(). The @click... lines are decorator calls. These calls “wrap up” main with its own to change its behavior, in our case, produce a convenient command line interface without minimal effort.

In the body of the main() function, click has already opened the file and bound its file-object to input. Using a for loop, we iterate over each line. Next we construct a list comprehension: [int(i) for i in re.split(r'\s+', line) if i] that (1) splits the line on groups of whitespace, (2) converts each non-whitespace group of characters into a number or discards it in the off chance that leading or trailing white space existed.

This line also assigns to variables a and b. This assignment acts like an assertion because the list comprehension’s result must have two objects in it or a ValueError is raised. Here’s an example:

L = list(range(3))

a, b, c = L
print(f'a got {a}')
try:
    p, q = L  # Fails
except ValueError as e:
    print(f'Got an error: {e.__class__.__name__}: {e}')
a got 0
Got an error: ValueError: too many values to unpack (expected 2)

p, q = L throws an exception: ValueError: too many values to unpack (expected 2).

Back to the day 1 Python. In the body, read the input file object line by line. The first and second number read on the line are assigned to a and b. L1.append(a) appends a to the first list, and L2.append(b) appends to the second.

Next let’s break down two concise generator comprehensions (notice no square or curly brackets):

print(sum(abs(a - b) for a, b in zip(sorted(L1), sorted(L2))))
print(sum(a * L2.count(a) for a in L1))

Both of the next lines contain the form print(sum(...)). That is, summates (and prints the total of) all the elements in the iterable sequence (a generator expression in both casees. Here’s a simpler example that sums all the square of each natural number less than 10:

print(sum(i**2 for i in range(10)))
285

This example also contains a generator expression. Notice no square brackets and a for keyword in the middle of a line. Think list comprehension, but only runs if the code actually iterates through the generator. The iterator pattern can be thought of a simulated form of lazy evaluation, a coding paradigm popular in functional programming where code is only ran if the result is used in some way e.g. saved to disk or displayed to the user.

Now that we understand generator expressions and print and sum let’s talk about each generator expression within print(sum(...)). The first print(sum(...)) contains following generator expression: abs(a - b) for a, b in zip(sorted(L1), sorted(L2)). The sorted(...) function iterates over the iterable (in this case each sorted() iterates over one of L1 or L2) and builds a new iterable of the lists’s sorted contents. Next zip() is used to combine the two sorted lists into pairs of numbers, one from each list, thereby lining up the pair of inputs according to part1. The least number in L1 is now paired with least in L2, and the the most with the most in the other). For each of these zip()‘ed pairs, the code sums up the difference between a and b then takes its absolute value (no negatives!). This sum is the answer for part 1.

And within the second print(sum(...)) is a * L2.count(a) for a in L1). For every a in L1, count how many times a occurs in L2 then multiple it against a itself. Sum all these values up for the answer of part 2.

Whew, I think that’s everything.

Other language learnings

I detailed the Python solution in this post, but also have take-aways for some of the other languages that I solved this day in.

C’s qsort() is for sorting arrays

I was not to keen on writing a sorting algorithm to use in C. After a couple Googles, I stumbled across qsort().

In my C solution, I chose to use arrays of unsigned int for storage. Here is how I used qsort() in my solution:

static int cmpuintp(const void *a, const void *b) {
        return *((unsigned int *)a) - *((unsigned int *)b);
}

int main(int argc, char** argv) {
        unsigned int *a1 = malloc(max_size);
        unsigned int *a2 = malloc(max_size);
        size_t lines = 0;

        /* Populate a1, a2... */
        /* ... */

        /* Sort a1, a2... */
        qsort(a1, lines, sizeof(unsigned int), cmpuintp);
        qsort(a1, lines, sizeof(unsigned int), cmpuintp);

        /* Do something with sorted a1, a2... */
        /* ... */

        return 0;
}

qsort() takes four arguments:

  1. Memory start address (a1 and a2 in the above example).
  2. The number of elements (lines in the above example).
  3. The size of each element (sizeof(unsigned int) in the above example).
  4. A function pointer that compare two elements and returns a number with a different sign based on its sorted order. In this case I wrote cmpuintp that subtracts the two unsigned integers and returns the difference.

Perl’s grep and list vs scalar context

Perl’s grep returns the matches in a list value of matching elements but since it returns a list, it can be used in a scalar context to gets its length. Compare the two outputs:

$, = " ";
print "(1..10):        ", (1..10), "\n";
print "LIST   context: ", grep { $_ % 2 == 0 } (1..10), "\n";
print "SCALAR context: ", scalar grep { $_ % 2 == 0 } (1..10), "\n";
(1..10):         1 2 3 4 5 6 7 8 9 10
LIST   context:  2 4 6 8 10
SCALAR context:  6

This was useful. Check out this snippet from may Perl solution:

for my $el (@a1) {
    $p2 += $el * grep { $_ == $el } @a2;
}

$p2 contains the answer for part 2 after this code runs. The return value of grep is automatically converted into a scalar context because the * operator works on scalars only.

Will you play AoC?

Take up whatever language you’re learning or practicing. Give it a go. It’s fun and easy to integrate into coding practice. Code well.

Sidenote: Is this website broken?

Yep, woops! I broke my theme used on winny.tech and blog.winny.tech! I’m working on it.