Advent of Code 2024 – 25 Languages
This year, for Advent of Code, I tried to do something new (to me, at least): I’d try to solve each day’s puzzle with a different programming language.
For this, I collected 25 languages that I’m willing to use/learn, shuffled them and used the language planned for the given day (thus, no choosing!).
In this blog post, I’m discussing the experience with each language and will give 0-5 stars on
- How well I knew the language beforehand (0 = Never heard of, 5 = Use it all the time)
- How much I enjoyed using this language for Advent of Code
Keep in mind that this is very subjective and my experience with many of these langauges is typically tiny. Also, functional programming languages and low-level languages are typically less useful for Advent of Code specifically. I included languages that
- are reasonable to use for AoC (no esoteric languages, no painful languages like TeX, awk or assembly),
- are open source and free of cost (no Delphi or MATLAB),
- are available on Fedora Linux (no PowerShell or similar),
- are not part of a forced ecosystem (no .NET with C# or F#, no Swift), and
- are text based (no graphical languages like Scratch or LabVIEW).
What is Advent of Code?
Advent of Code is a Website created by Eric Wastl that poses 25 days of puzzles. These puzzles always include some short story on what problem the protagonists have to solve, and two tasks, an easier and a more difficult one. Each puzzle is thoroughly explained with an example and gives you a personalized, very large input that you’re supposed to solve using some programming language of your choice.
Navigation
Python
Experience: | |
Fun: |
Python is a classic, and it’s probably one of the easiest language to solve AoC problems. Using pandas
for I/O and numpy
for general data processing makes python really handy to solve these problems.
All in all, python made this pretty easy, but it’s not too wild of a language.
import numpy as np
import pandas as pd
data = pd.read_csv('input.txt', delimiter='\t', header=None)
col1 = data[0].sort_values().values
col2 = data[1].sort_values().values
a = np.absolute(col1 - col2)
print('Part 1:', sum(a))
### part 2
s = 0
for i in col1:
ii = np.where(col2 == i)[0]
s += i * len(ii)
print('Part 2:', s)
Rust
Experience: | |
Fun: |
So I’ve heard a lot of great stuff about Rust, and coming from C/C++, there’s many things that make my life more comfortable while still being low-level. Examples include foreach loops, a non-retarded way of printing stuff to stdout, explicit mutability, great tooling (cargo), and (looking at you, C++) a consistent syntax. Overall: That was fun!
use std::fs::File;
use std::io::{BufRead, BufReader};
fn main() {
let mut sum = 0;
let file = File::open("input.txt").unwrap();
let reader = BufReader::new(file);
for (_index, line) in reader.lines().enumerate() {
let line = line.unwrap();
let parts = line.split(" ").collect::<Vec<_>>();
let mut fin = false;
// iterate parts.len()..parts.len()+1 for Part 1
for j in 0..parts.len()+1 {
let mut parts_red = parts.clone();
Scala
Experience: | |
Fun: |
From the half an hour or so I had with Scala, having some JavaScript experience makes this language super easy to learn. Also, Scala compiles to Java bytecode and transpiles to JavaScript, so scala might be really cool if you dislike the weirdness of JavaScript or the “Classes down your throat”-approach of Java.
Documentation is solid, and getting into Scala is painless. I liked it.
//> using scala 3.5.2
val part1 = false
@main
def hello(): Unit =
val lines = scala.io.Source.fromFile("input.txt").mkString
val regex = "mul\\(\\d+,\\d+\\)".r
val dorx = "do\\(\\)".r
val dontrx = "don't\\(\\)".r
val m = Vector() ++ regex.findAllMatchIn(lines)
val mind = regex.findAllMatchIn(lines).map(_.start).toList
val doind = dorx.findAllMatchIn(lines).map(_.start).toList
val dontind = dontrx.findAllMatchIn(lines).map(_.start).toList
val products = m
.map(i => i.toString)
.zipWithIndex
PHP
Experience: | |
Fun: |
I’ve written some PHP back in the day with the Symfony Web framework, and I’ve always appreciated PHP for being a language for web that is easy to write code in. That said, debugging PHP is a nightmare, and I’ve had this experience with here as well. PHP is just really good of just assuming what you want to do, even if it’s incorrect. What I do like about PHP, however, is that it comes with many really handy functions built-in. Documentation is a pain to read, though.
<?php
function main() {
$file = 'input.txt';
if (!file_exists($file)) {
return;
}
$lines = array();
foreach(file($file) as $line) {
array_push(
$lines,
substr($line, 0, strlen($line)-1)
);
}
$hits = 0;
D
Experience: | |
Fun: |
If this language looks like C, the reason is that it’s supposed to. Writing in D feels a lot like writing plain C with some handy higher level stuff from C++ and abstracting away the “complicated” stuff from C. Stuff D includes: The string
data type, dynamic arrays, handy string
functions, etc.
Coming from C/C++ makes D pretty straightforward.
import std.stdio;
import std.array;
import std.conv;
import std.file;
import std.algorithm;
void main()
{
string file = cast(string)std.file.read("input.txt");
string[] input = readLines(file);
Appender!(string[]) rules_app;
Appender!(string[]) updates_app;
bool weInRulesSection = true;
for (int i = 0; i < input.length; i++) {
string ln = input[i];
if(ln == "") {
Go
Experience: | |
Fun: |
Go is nice. It didn’t annoy me, the Tour of Go is really handy to get started with Go, tooling is good, code is readable and performant. Go feels like the logical conclusion for a low-ish level language that is inspired by C.
One thing that I like about its syntax is that a C statement like
int x = 5;
would translate to
var x int = 5
in Go, which orders information based on how I think about variables with descending relevance (a variable called x that is an int and has a value of 5).
package main
import (
"bufio"
"fmt"
"os"
)
func main() {
file, _ := os.Open("input.txt")
defer file.Close()
scanner := bufio.NewScanner(file)
var input [][]string
var index int = 0
for scanner.Scan() {
var ln string = scanner.Text()
var lnarr []string
Elixir
Experience: | |
Fun: |
I did write some Elixir when I tried the Phoenix web framework, and people online would shill for this language, so I included it. Apparently, writing Elixir makes you feel smart, and the language is very elegant.
I didn’t have too much trouble writing the code for the AoC task. But look at how ugly this code looks! Let’s look at line 8, for example. I wish to split a string at :
, take the first element and treat is as an integer. In JavaScript, this would be const res = Number(ln.split(': ')[0])
, but Elixir gives me two options for taking the first element: I either use Elem.at()
, making the line unnecessarily verbose, or use pattern matching that adds me another line of code.
Given this somewhat minor pain point, I do enjoy writing code in Elixir, and the Documentation is a) good enough, if you’re new, and b) great if you’re already familiar with the erlang ecosystem.
{:ok, input} = File.read("input.txt")
operators = {"+", "*", "||"}
lines = input |> String.split("\n") |> Enum.filter(fn ln -> ln != "" end)
s = lines |> Enum.map(
fn ln ->
res = ln |> String.split(": ") |> Enum.at(0) |> Integer.parse() |> elem(0)
cont = ln |> String.split(": ") |> Enum.at(1) |> String.split(" ") |> Enum.map(fn num -> Integer.parse(num) |> elem(0) end)
exp = (cont |> length) - 1
combinations = 3**exp
# Try each combination of operators
matches = 1..combinations |> Enum.map(fn i -> i - 1 end) |> Enum.map(fn i ->
bin = i |> Integer.to_string(3) |> String.pad_leading(exp, "0")
# merge them together to an eval string
# parts = 1..String.length(bin) |> Enum.map(fn i -> i - 1 end) |> Enum.map(fn i ->
# j = bin |> String.at(i) |> Integer.parse() |> elem(0)
Java
Experience: | |
Fun: |
I mean, it’s Java. I don’t particularly love it, but I don’t mind it either. Documentation sucks, though. As you can see in the code, I’m not too fond of the object-oriented approach (except if the problem fits this approach well), so I used a single Main
class that contains all methods I need.
public class Main {
public static void main(String... args) {
BufferedReader reader;
List<String> input_al = new ArrayList<String>();
try {
reader = new BufferedReader(new FileReader("input.txt"));
String line = reader.readLine();
while (line != null) {
input_al.add(line);
line = reader.readLine();
}
reader.close();
} catch (IOException e) {
e.printStackTrace();
}
Julia
Experience: | |
Fun: |
Julia is an interesting one. It feels a bit like python and excels at the same type of stuff you would use Python/NumPy for, but has really solid performance.
The Julia Getting Started is really helpful, but finding information on how to use certain functions in the docs can be a pain point. Also, I find that Julia code tends to look pretty monotonous, making orientation in the file difficult sometimes.
Give Julia a go if you like python and want to try something similar (or want to do funky stuff like δ = 5
).
module AOC
input = read("input.txt", String)
blocks = []
print("Generating Blocks...\n")
for i = firstindex(input):lastindex(input)-1
ch = input[i]
chnum = parse(Int64, ch)
if i % 2 == 1
for j = 1:chnum
push!(blocks, div(i, 2))
end
else
for j = 1:chnum
push!(blocks, -1)
end
end
Lisp Nim
Experience: | |
Fun: |
So I’ve first decided to use Lisp, specifically Common Lisp. After three hours of trying to learn the language, I did not really proceed any further than a simple “Hello World”, and thus decided to replace Lisp with Nim instead.
The language might be cool, but getting into Lisp is a major pain and not something to do on a random tuesday to solve an AoC challenge. The primary reason for this is the lack of useful documentation and a “Common Lisp crash course” that is not a 1500 page book.
(format t "Fuck Lisp. Ima use nim instead.")
Experience: | |
Fun: |
Nim feels a lot like python with types. It’s pretty easy to write code in, getting started is efficient, but the documentation can look pretty cryptic with very little description. A large part of why is probably the quite small community.
import strutils
import sequtils
let inputfile = readFile("input.txt")
var inp: seq[seq[int]]
for ln in splitLines(inputfile):
if(ln == ""):
continue
var line: seq[int]
for ch in toSeq(ln.items):
line.add(parseInt(ch & ""))
inp.add(line)
proc is_valid(pos: array[0..1, int], input: seq[seq[int]], val: int): bool =
if pos[0] < 0:
return false
if pos[0] >= len(input):
return false
Dart
Experience: | |
Fun: |
The worst part about Dart was, for me, installing the interpreter without all the bloat that typically comes with it (a full IDE like Android Studio, for instance).
My experience with Dart was fine, and I do see programming cross-platform apps with it, but JavaScript generally gives me more tools to do the stuff I want quickly. Final verdict: Eh.
import 'dart:io';
void main() {
File file = File("input_smol.txt");
var fileContent = file.readAsStringSync();
var inputStr = fileContent.replaceAll("\n", "").split(" ");
var inp = <int>[];
for(var i = 0; i < inputStr.length; i++) {
inp.add(int.parse(inputStr[i]));
}
int s = 0;
for(var k = 0; k < inp.length; k++) {
var input = [inp[k]];
for(var i = 0; i < 30; i++) {
var len = input.length;
for(var j = 0; j < len; j++) {
int num = input[j];
Pascal
Experience: | |
Fun: |
I wanted to do Delphi, but I figured out that it was closed source and paid. I thereby chose Delphi’s daddy instead.
Pascal is a pretty old language, and I was a bit worried about how bad of a state it might be in. Fast forward a few hours, and I really enjoyed my time with pascal. This language is about as old as my parents, but writing software in pascal is honestly pretty nice!
Pascal is a bit unique in how programs are structured. For instance, all variable declarations happen at the start of a block, Pascal differs between procedure
and function
, etc.
program AOC12;
uses
Classes, SysUtils;
type
inptype = array of array of char;
coords = array of int64;
posarray = array of coords;
const
directions: array of array of int64 = ((1, 0), (0,1), (-1,0), (0,-1));
...
repeat
N := length(S);
if j = 0 then
setLength(inp, N, N);
for i:=0 to length(S)-1 do
begin
inp[j][i] := S[i+1];
end;
Gleam
Experience: | |
Fun: |
Gleam feels like a very modern functional language, which is something I like a lot. The Gleam Language Tour is super helpful and shows the advanced features pretty quickly, too. However, uhm, look at the code below.
When discussing Elixir, I already hinted at the boilerplate of array operations. Gleam takes this to the next level by trying to be perfect at error handling.
Picture this: Many functions from gleam/list
will not just return some value, but also a status code, e.g. Ok([1, 2, 3])
. This means that you can get the value of a function by using gleam/result/unwrap
or by using use
(which is the intended way). These very simple chains of string oprations below would then be split into about 5 lines for something that I’d rather just use one line for.
One cool feature would be to offer these functions both as “Error-Prone” and “Simple”, say list.first()
that might return Ok(3)
, and list.first_()
that might return 3
, similarly to the Node.js fs
module with its ...Sync
functions.
pub fn main() {
let assert Ok(inp) = sf.read(from: filepath)
let prices = inp |> str.split("\n\n") |> list.map(fn(s) {
let parts = s |> str.split("\n") |> list.filter(fn(st) { st != "" })
let ax = parts |> list.first() |> res.unwrap("") |> str.split("X+") |> list.last() |> res.unwrap("") |> str.split(",") |> list.first() |> res.unwrap("") |> int.parse() |> res.unwrap(0)
let ay = parts |> list.first() |> res.unwrap("") |> str.split("Y+") |> list.last() |> res.unwrap("") |> int.parse() |> res.unwrap(0)
let bx = parts |> list.drop(1) |> list.first() |> res.unwrap("") |> str.split("X+") |> list.last() |> res.unwrap("") |> str.split(",") |> list.first() |> res.unwrap("") |> int.parse() |> res.unwrap(0)
let by = parts |> list.drop(1) |> list.first() |> res.unwrap("") |> str.split("Y+") |> list.last() |> res.unwrap("") |> int.parse() |> res.unwrap(0)
let pxtmp = parts |> list.last() |> res.unwrap("") |> str.split("X=") |> list.last() |> res.unwrap("") |> str.split(",") |> list.first() |> res.unwrap("") |> int.parse() |> res.unwrap(0)
let pytmp = parts |> list.last() |> res.unwrap("") |> str.split("Y=") |> list.last() |> res.unwrap("") |> int.parse() |> res.unwrap(0)
let px = prefix + pxtmp
let py = prefix + pytmp
let invdet = 1.0 /. {ax * by - ay * bx |> int.to_float()}
let x = invdet *. {by * px |> int.to_float()} -. invdet *. {bx * py |> int.to_float()}
Haskell
Experience: | |
Fun: |
Damn, this was pretty bad. I’m starting to notice the increasing difficulty of AoC, and Haskell is not helping.
I’ve used some functional programming languages, but none of them not only force me to write code the way the compiler wants me to, no, it also takes a whole philosophy major just to understand what exactly the do
keyword does.
I am sure that Haskell is a great way to properly learn functional programming, but for the purpose of learning the language on the go for AoC, Haskell is just… bad.
main :: IO ()
main = do
let filename = "input_smol.txt"
inputStr <- readFile filename
let input = inputStr & splitOn "\n" & filter (\s -> s /= "")
let startPos = map (\s -> splitOn " " s & head & splitOn "=" & last & splitOn "," & map stringToInt) input
let vel = map (\s -> splitOn " " s & last & splitOn "=" & last & splitOn "," & map stringToInt) input
let endPos = zip startPos vel & map Main.iterate
let tl = endPos & filter (\p -> (head p) < (div nx 2) && (last p) < (div ny 2)) & length
let tr = endPos & filter (\p -> (head p) > (div (nx-1) 2) && (last p) < (div ny 2)) & length
let bl = endPos & filter (\p -> (head p) < (div nx 2) && (last p) > (div (ny-1) 2)) & length
let br = endPos & filter (\p -> (head p) > (div (nx-1) 2) && (last p) > (div (ny-1) 2)) & length
print(endPos)
iterate :: ([Int], [Int]) -> [Int]
iterate d =
Fortran
Experience: | |
Fun: |
Given that Fortran started as a language for punch cards, this language has come a long way. Imagine a language that is kind of like C, but with all the conveniences of NumPy
. This is what modern Fortran feels like.
Other than reading in the input data, writing the code in fortran was smooth sailing. Solid language, would use again for high-performance computing (much cooler than C++).
character, dimension(NY,2*NX) :: input
character, dimension(NC) :: instructions
open(unit=read_unit, file=filename, iostat=ios)
if ( ios /= 0 ) stop "Error opening file input_tinee.txt"
k = 1
do i = 1, NY+NL
read(read_unit,*,iostat=ios) ctmp
if(.not. bottom) then
do j = 1, NX
input(i, 2*j-1) = ctmp(j:j)
input(i, 2*j) = ctmp(j:j)
if(ctmp(j:j) == "O") then
input(i, 2*j-1) = "["
input(i, 2*j) = "]"
end if
if(ctmp(j:j) == "@") then
pos = [i, 2*j-1]
TypeScript
Experience: | |
Fun: |
I use TypeScript almost daily, and I think that it’s a great language to solve AoC puzzles quickly. First, it allows for conventional (as in “mutability and side effects”) and functional programming at the same time, and npm
has a huge number of packages to solve almost any task (e.g. the astar-typescript
package for pathfinding). Also, trying a bunch of typed languages over the last two weeks showed me how insanely “smart” TypeScript’s type checker actually is (e.g. Dart would just ignore my type cast going forward).
I like TS, it’s fun.
function main() {
const inputStr = readFileSync("input.txt", 'utf8');
const input = inputStr
.split('\n')
.filter(e => e !== "")
.map(
e => e.split("")
.filter(i => i !== "")
);
let start, end;
for(let i = 0; i < input.length; i++) {
for(let j = 0; j < input[0].length; j++) {
if(input[i][j] === "S")
start = [i, j];
else if (input[i][j] === "E")
end = [i, j];
}
}
Bash
Experience: | |
Fun: |
Bash is great. For me, it’s the ultimate example of a skill that takes minutes to learn and saves hours really quickly. Given that bash is very much a “hack something together within 3 minutes” kind of language, I am kind of impressed by how actually pretty good bash is for solving AoC puzzles.
I like that bash just lets me write code without thinking about what my code actually does under the hood. Most higher-level languages still require you to think about memory sometimes. In bash? Bash just does your shit, no questions asked. For the better or worse.
One big negative point about bash is that it becomes absolutely unreadable above about 100 lines of code that include loops and conditionals.
instrcode="${INSTRUCTIONS[$PTR]}"
skip=false
case $instrcode in
0) # adv
pow=$((2 ** $comboper))
A=$(($A / $pow))
;;
1) # bxl
B=$(($B ^ $litoper))
;;
2) # bst
B=$(($comboper % 8))
;;
3) # jnz
if [[ $A -ne 0 ]]
then
PTR=$litoper
echo "PTR: $PTR -- INSTR: $instrcode, OPER: $litoper, A: $A, B: $B, C: $C"
#skip=true
C
Experience: | |
Fun: |
So far, I’ve only used C++ but never plain C, so it was a nice change to only use gcc
for once.
What was not so nice, however, was the exercise I’m supposed to use C on: Pathfinding (Dijkstra), which usually relies heavily on dynamic arrays (std::vector
in C++, but no equivalent in C). My friends were even laughing at me when I told them that I’d code Dijkstra in plain C. Rightfully so, I must add.
Generally, I find C pretty straightforward to write. I do miss some basic C++ features, such as iostream
, fstream
and passing variables by reference instead of their pointer (same thing under the hood, but the code becomes more readable).
Of course, as any non-senior C programmer, I had my fair share of segfaults (about 20, if I recall correctly), typically because I forgot to dereference a pointer in a function.
Overall, writing AoC in C was fine, but I do miss some of the handy functions of higher-level languages. I gave C two “fun” stars because C is just C, nothing too crazy or new about it.
for(int i = 0; i < N; i++) {
for(int j = 0; j < N; j++) {
if(input[i][j] == '.') {
allNodes[numNodes] = getId(i, j);
numNodes++;
}
count_u[getId(i, j)] = 0;
}
}
// the queue of nodes to test
B[0][0] = getId(start[0], start[1]);
Bcosts[0] = 0;
Blengths[0] = 1;
Bind++;
int iterations = 0;
while(Bind > -1 && count_u[getId(end[0], end[1])] < k) {
// find lowest cost in B
Perl
Experience: | |
Fun: |
I have to say, I did not expect that. At first glance, Perl looks like PHP and bash had a child, which is not something I’d expect in a programming language.
That said, if you like RegEx, you’re gonna love Perl. It feels like the type of language that is just great for quickly hacked together scripts that process text (which, if you’re using open formats and the command line, is pretty much anything). It has a few funny design choices, such as the my
keyword to declare a variable.
One very interesting quirk of Perl is that arrays start with a @
and scalars (i.e. single values) with a $
. This prefix is not part of the variable name and can change if the operation changes. For example, this is perfectly valid Perl:
my $val = $array_of_numbers[5];
my @vals = @array_of_numbers[2..5];
Because one element of an array is a scalar, $
is used. For a slice of an array, which is an array, @
is used.
use Memoize qw(memoize);
sub say {print @_, "\n"}
open(my $in, "<", "input.txt") or die "Can't open file: $!";
my @input = <$in>;
my @patterns = map { s/\n//r } split(/, /, $input[0]);
my @designs = map { s/\n//r } @input[2..$#input];
#say "Patterns: " . join(", ", @patterns);
memoize('assign_patterns');
my $s = 0;
foreach (@designs) {
my $combinations = assign_patterns($_);
say $_ . ": " . $combinations;
$s += $combinations;
Erlang
Experience: | |
Fun: |
I’ve heard some stuff about Erlang and its Ecosystem, as I’ve already developed some Elixir stuff. I expected Erlang to behave kind of like Elixir. I would quickly learn that this is not the case.
My first impresson of Erlang was acutally quite good. I kinda liked that each function ends with a .
, and each line is terminated by ,
, kind of like a sentence in english. Erlang was a very early functional programming language (1987), so I was happy to see typical functional patterns like map
or filter
. The export
at the top is quite nice, as you have an overview about all functions in a file without having to skim through it.
And then I got into the weirdness of Erlang. For starters, a variable always has to start with a capital letter. If you don’t do that, you get a very cryptic error message:
Eshell V14.2.5.5 (press Ctrl+G to abort, type help(). for help)
1> A = 5.
5
2> a = 5.
** exception error: no match of right hand side value 5
The reason for this is that a lowercase identifier is atually an atom, which is like a constant variable that contains its own name as a value. You cannot assign anything to an atom, so 5
cannot match on a
. This error message makes sense if you know this stuff, but most beginners (like myself) don’t.
Next, one of the nice things about modern functional programming languages is that you can build pipelines to do multiple operations in a single line of code, similar to how bash’s pipes work. Elixir and Gleam have the |>
operator, Haskell has &
, JavaScript has .
and Erlang has… nothing. This leads to insane nesting of functions or every little thing needing its own variable name. Apparently, the reason for a missing pipe operator is that Erlang is not consistent with what type of argument is what argument number.
Another weirdness is Erlang’s lack of a string
/char
type. Text is stored as a list of binary
, and the Erlang shell interprets a value as it sees fit. This leads to funny behaviour like this:
1> [86, 87, 88].
"VWX"
Erlang doesn’t really have loops (it does have lists:map
, though). In order to create a while
or for
loop, you create a recursive function, like so:
% if `Count` is 10, the function call will match the top version.
loop(10) ->
ok;
loop(Count) ->
% do something
loop(Count+1).
I do kind of like this approach, but what really sucks about it is a) that it can be extremely slow without memoization, and b) that each loop requires its own function, leading to major code fragmentation.
After multiple hours of writing Dijkstra’s algorithm in Erlang, I actually managed to make it work, but it was insanely slow. It was not easily possible to memoize the dijkstra function, so I solved the challenge with TypeScript and an A* npm package instead.
-module(main).
-export([main/0, readlines/1, dijkstra/3, dijkstra_loop/5, elem/2, getCoords/2, getId/2, getNeighbors/3, updateNeighs/6, getDist/3, print/1, getIndexOf/2, removeWall/2, removeWallLn/2, getPath/3]).
main() ->
Data = readlines("input_smol.txt"),
SRow = lists:nth(1, lists:filter(fun(I) -> lists:member(<<"S">>, lists:nth(I, Data)) end, lists:seq(1, length(Data)))),
SCol = lists:nth(1, lists:filter(fun(I) -> lists:nth(I, lists:nth(SRow, Data)) == <<"S">> end, lists:seq(1, length(lists:nth(SRow, Data))))),
ERow = lists:nth(1, lists:filter(fun(I) -> lists:member(<<"E">>, lists:nth(I, Data)) end, lists:seq(1, length(Data)))),
ECol = lists:nth(1, lists:filter(fun(I) -> lists:nth(I, lists:nth(ERow, Data)) == <<"E">> end, lists:seq(1, length(lists:nth(ERow, Data))))),
X = length(Data),
Y = length(lists:nth(1, Data)),
Walls = lists:filter(fun(Ele) -> elem(Data, getCoords(Data, Ele)) == <<"#">> end, lists:seq(1, X * Y)),
_WallsCoords = lists:map(fun(Ele) -> getCoords(Data, Ele) end, Walls),
{_BDist, BPrev, BAll} = dijkstra(Data, [SRow, SCol], [ERow, ECol]),
print(BPrev),
BPath = getPath(BPrev, getId(Data, [ERow, ECol]), BAll),
_BC = length(BPath)-1,
Ruby
Experience: | |
Fun: |
I did use a little bit of Ruby when I tried Ruby on Rails a few years back. For this AoC puzzle, I found Ruby to be extremely intuitive to write. Just write code that you think might be correct Ruby, and you’ll probably be right about it.
Generally, Ruby feels a lot like Python with a few syntactic changes, but kind of less complex. For example, the map()
function in Python returns a generator that needs to be converted to a list in order to get specific elements (it is this way for performance reasons), which is not the case in Ruby. Thus,
list(map(lambda x: x * 2, list_of_numbers))
in Python would be
list_of_numbers.map{ |x| x * 2 }
in Ruby, which is shorter, more readable and chainable (like in JavaScript). I also like that some syntactic patters like the ternary operator or for
loops are kept more C-style instead of the pythonic abstractions. The one thing that I really like in Pyhton is the power of the print()
function compared to the Ruby puts
, which prints stuff a lot less nicely.
I really liked Ruby, and I’ll consider using it for tasks that Python would be good fit for otherwise. I’ll have to like into packages (plotting library, scientific stuff), but writing AoC in Ruby was great fun!
def get_instructions(inp, index)
type = KEYPADS[index]
comb = ""
pos = POS[index]
inp.each do |key|
source = pos
target = (eval type)[key]
ydist = target[0] - source[0]
xdist = target[1] - source[1]
inv = (eval type)["inv"]
if horfirst(source, target, inv)
# left first
comb += "".rjust(xdist.abs(), xdist > 0 ? ">" : "<")
comb += "".rjust(ydist.abs(), ydist > 0 ? "v" : "^")
else
Kotlin
Experience: | |
Fun: |
Kotlin wasn’t too wild. It’s a language that feels like TypeScript and can use Java libraries.
One interesting feature I found is that val
declares a constant variable, var
declares a mutable variable. Otherwise, it’s just… TS, nothing more to add.
fun main() {
val inputStream: InputStream = File("input_smol.txt").inputStream()
val lineList = mutableListOf<String>()
inputStream.bufferedReader().forEachLine { lineList.add(it) }
var sum: Long = 0
// val changes = listOf(-1, -1, 0, 2)
val changes = listOf(-2,1,-1,3)
var max: Long = 0
var maxStr = ""
var iter = 0
for(i in -9..9) {
for(j in -9..9) {
println(iter)
iter+=1
for(k in -9..9) {
for(l in -9..9) {
Zig
Experience: | |
Fun: |
I’ve heard so much great stuff about Zig. And now that I’ve actually used it, I think it was the worst experience of any language on this list (Lisp excluded). Maybe I just didn’t use it as intended, but the docs don’t really tell you how to use the language (more on that later).
The first thing that I noticed is the lack of a string
or char
type. Any text is just an array of u8
, which is an unsigned 8-bit integer. Even C has a char
type (which works the same as an u8
here, but the compiler at least knows that this is supposed to be text). This turned out to be really bad for this day of AoC, where I’d have liked to store a list of “network triangles”, which each are a list of hosts, which are a list of u8
. Ugly.
Next, Zig is an absolute type clutserfuck that’s nowhere properly explained. Slicing an array of u8
([]u8
) should just return another array of u8
, right? Sorry, no, it’s actually const [2]u8
, which is not compatible with [_]u8
or []u8
.
Now, let’s try to compare two strings. As strings are not a thing in Zig, we can’t use use a handy ==
operator. We actually need to use std.mem.eql([type], [str1], [str2])
, which compares the memory of the two objects. For AoC, I needed to compare a single character, though, but std.mem.eql
requires an array. Thus, we need to convert a character into an []u8
by using… [str[0]]
? No, of course not. This is not C from 1972, this is Zig from 2016! We need to use &[_]u8{str[0]}
instead!.
Zig was just full of this kind of crap.
This would all be kind of fine if the docs explained this stuff. But they don’t. The examples in the docs are extremely short and don’t show more involved examples, and nothing is actually explained in enough detail.
pub fn main() !void {
var splits = split(u8, data, "\n");
var edges = ArrayList(u8).init(std.heap.page_allocator);
defer edges.deinit();
while (splits.next()) |line| {
if (line.len == 0) continue;
const host1 = line[0..2];
const host2 = line[3..5];
if (compStrings(host1, host2)) {
// host1 first
const row: []const u8 = host1 ++ host2;
try edges.appendSlice(row);
} else {
// host2 first
const row: []const u8 = host2 ++ host1;
try edges.appendSlice(row);
}
}
Octave
Experience: | |
Fun: |
Octave, as a language designed for matrix operations in engineering and maths (MATLAB replacement), is not really made for Advent of Code puzzles. It did keep up quite well, though.
The first thing to understand about Octave is that any array (called cell array
) is a 2D matrix, even if it’s just one value. This makes looping over the right dimension a bit confusing at times, but trying both rows
and columns
usually resolves the issue.
Another interesting thing about Octave is that any line that is not terminated with a ;
will print its result to stdout. Thus, the line 5
is a valid statement and will print 5
to stdout.
Overall, Octave was pretty cool, and it comes with a ton of handy fuctions to do all kinds of stuff. I do find documentation a bit cumbersome to navigate, though.
DATA = fileread("input.txt");
DATA_SPLT = strsplit(DATA, "\n");
contains = @(str, pattern) ~cellfun('isempty', strfind(str, pattern));
INIT = DATA_SPLT(contains(DATA_SPLT, ":"));
INST = DATA_SPLT(contains(DATA_SPLT, "->"));
STATE = struct();
for i = 1:columns(INIT)
FIELDS = strsplit(INIT{1, i}, ": ");
STATE.(FIELDS{1, 1}) = str2num(FIELDS{1, 2});
endfor
while(columns(INST) > 0)
DEL = [];
for i = 1:columns(INST)
RES = strsplit(INST{1,i}, " -> "){1, 2};
O1 = strsplit(INST{1,i}, " "){1, 1};
O2 = strsplit(INST{1,i}, " "){1, 3};
Lua
Experience: | |
Fun: |
The only place I touched Lua so far was neovim configuration. Now that I’ve used it to solve an actual problem, I have to say that I’m impressed at how easy Lua is - even easier than Python or Ruby. I think it’s a great language to start programming in.
For example, appending to an existing array usually involves some kind of append
or push
operation in most languages. Not Lua: Just write a value to whatever index, and Lua handles the complicated memory stuff for you. Neat!
function lines_from(file)
local lines = {}
for line in io.lines(file) do
lines[#lines + 1] = line
end
return lines
end
local file = 'input.txt'
local lines = lines_from(file)
local keys = {}
local locks = {}
for i = 1, (#lines+1) / 8 do
local start = (i - 1) * 8 + 1
local entry = {}
for col = 1, 5 do
local s = 0
for row = 1,5 do
import numpy as np
import pandas as pd
data = pd.read_csv('input.txt', delimiter='\t', header=None)
col1 = data[0].sort_values().values
col2 = data[1].sort_values().values
a = np.absolute(col1 - col2)
print('Part 1:', sum(a))
### part 2
s = 0
for i in col1:
ii = np.where(col2 == i)[0]
s += i * len(ii)
print('Part 2:', s)
Comments
Subscribe via RSS.
MIT 2024 © Alexander Schoch.