Maybe. But after a few days though. I am dying to use LiveView + AoC, so will be experiencing that the first few days.
I realized, the leaderboards persist across years. I had one created that had the code 257223-1bcda624
⊠yâall are welcome to join, and Iâd love to join yours if you want
An hour and a half(ish) to go! Iâll start the discussion once I get one done.
Advent of Code Day 1 completed. Took me 3 minutes to complete yet I did not get a position in the leaderboard.
I started with Elixir, then went ahead and did F# as well.
The Elixir one:
defmodule AdventOfCode.Y2021.Day01 do
@moduledoc """
--- Day 1: Sonar Sweep ---
Problem Link: https://adventofcode.com/2021/day/1
"""
use AdventOfCode.Helpers.InputReader, year: 2021, day: 1
def run_1, do: input!() |> parse() |> depth_increase()
def run_2, do: input!() |> parse() |> sliding_window() |> depth_increase()
def parse(data) do
data
|> String.split("\n")
|> Enum.map(&String.to_integer/1)
end
defp depth_increase(measurements) do
measurements
|> Enum.chunk_every(2, 1, :discard)
|> Enum.count(fn [a, b] -> b - a > 0 end)
end
defp sliding_window(measurements) do
measurements
|> Enum.chunk_every(3, 1, :discard)
|> Enum.map(&Enum.sum/1)
end
end
The F# One:
/// Advent of Code 2021
/// Day 1: Sonar Sweep
/// Description: https://adventofcode.com/2021/day/1
module Year2021Day01
open AdventOfCode.FSharp.Utils
module Solution =
let increase =
Seq.pairwise
>> Seq.filter (fun (a, b) -> b - a > 0)
>> Seq.length
let solvePart1 = ints >> increase >> output
let solvePart2 =
let slidingWindow =
Seq.pairwise
>> Seq.pairwise
>> Seq.map (fun ((a, b), (_, d)) -> a + b + d)
ints >> slidingWindow >> increase >> output
let solve (input: string seq) = (solvePart1 input, solvePart2 input)
Made the Elixir one smaller. Today I learned about zip_with
(from discussion started in ElixirForum)
defmodule AdventOfCode.Y2021.Day01 do
use AdventOfCode.Helpers.InputReader, year: 2021, day: 1
import Enum
def run_1, do: input!() |> parse() |> inc(2)
def run_2, do: input!() |> parse() |> inc(4)
def parse(data), do: data |> String.split("\n") |> map(&String.to_integer/1)
defp inc(ds, len),
do: ds |> chunk_every(len, 1, :discard) |> count(&(at(&1, -1) > at(&1, 0)))
end
Paste the code in backticks Mafinar
(People often browse the forum on their mobile and images can eat into their bandwidth )
Sure thing
I built a framework yesterday to waaaaaaay overdesign handling these instead of my normal per-problem-program style, unsure why, but itâs fun and I get great CLI help, lol. Itâs available at:
2021-01 is at:
The first part of the function is just opening the file and parsing it with way too much error checking (which is entirely unnecessary for an AoC, but again, overdesigned, lol). The part that solves each part is just (nums is the array of integers, yes I know I could have solved them while parsing without storing anything, and I did that originally, but I like how pretty this even if a couple microseconds slower, lol):
println!(
"Step 1: {}",
nums.iter()
.tuple_windows()
.map(|(a, b)| a < b)
.filter(|&x| x)
.count()
);
println!(
"Step 2: {}",
nums.iter()
.tuple_windows()
.map(|(a, b, c)| a + b + c)
.tuple_windows()
.map(|(a, b)| a < b)
.filter(|&x| x)
.count()
);
And my result times:
⯠cargo run --release -- -v 2021 1 ./inputs/2021/day1.input
Compiling advent_of_code v0.1.0 (/home/overminddl1/rust/advent_of_code)
Finished release [optimized] target(s) in 14.08s
Running `target/release/advent_of_code -v 2021 1 ./inputs/2021/day1.input`
AocApp { verbose: 1, command: Run(Year2021 { day: Day1(Day1 { input_file: "./inputs/2021/day1.input" }) }) }
Step 1: 1448
Step 2: 1471
Time Taken: 97.823”s
(The original version that didnât store the integers and rather just calculated as it went was at just over 92”s, so thatâs the extra cost of the allocations and such.)
EDIT1: Broke out the file reading/parsing code into a standalone module for all the tasks to share (my all generic helpers
name, lol), so now my complete 2021-01 code is now:
use crate::aoc::helpers::*;
use clap::Parser;
use itertools::Itertools;
use std::path::PathBuf;
#[derive(Debug, Parser)]
pub struct Day1 {
/// The input file of "depths"
pub input_file: PathBuf,
}
impl Day1 {
pub fn run(&self) -> anyhow::Result<()> {
let nums =
map_trimmed_nonempty_lines_of_file(
&self.input_file,
|line| Ok(line.parse::<usize>()?),
)?;
println!(
"Step 1: {}",
nums.iter()
.tuple_windows()
.map(|(a, b)| a < b)
.filter(|&x| x)
.count()
);
println!(
"Step 2: {}",
nums.iter()
.tuple_windows()
.map(|(a, b, c)| a + b + c)
.tuple_windows()
.map(|(a, b)| a < b)
.filter(|&x| x)
.count()
);
Ok(())
}
}
EDIT2: And by pretty help messages I mean like this:
⯠./target/release/advent_of_code 2021 1 --help
advent_of_code-2021-1
Advent of Code 2021, Day 1 - Sonar Sweep
USAGE:
advent_of_code 2021 1 <INPUT_FILE>
ARGS:
<INPUT_FILE> The input file of "depths"
OPTIONS:
-h, --help Print help information
Each year and day are a command subtask as well, so each has itâs own help too:
⯠./target/release/advent_of_code 2021 --help
advent_of_code-2021
Advent of Code 2021
USAGE:
advent_of_code 2021 <SUBCOMMAND>
OPTIONS:
-h, --help Print help information
SUBCOMMANDS:
1 Advent of Code 2021, Day 1 - Sonar Sweep
help Print this message or the help of the given subcommand(s)
And the top-most help:
⯠./target/release/advent_of_code --help
advent_of_code
USAGE:
advent_of_code [OPTIONS] <SUBCOMMAND>
OPTIONS:
-h, --help Print help information
-v, --verbose Level of verbosity, can be used multiple times for more verbosity
SUBCOMMANDS:
2015 Advent of Code 2015
2016 Advent of Code 2016
2017 Advent of Code 2017
2018 Advent of Code 2018
2019 Advent of Code 2019
2020 Advent of Code 2020
2021 Advent of Code 2021
help Print this message or the help of the given subcommand(s)
tui
(Itâs far more pretty in the terminal with itâs colorization and all too)
Like I said, waaaaaaay overdesigned this, lol.
EDIT3: Added ability to run all known solutions with the default inputs in the input
directory, and the output:
Year2015
Year2015 Time Taken: 70ns
Year2016
Year2016 Time Taken: 70ns
Year2017
Year2017 Time Taken: 70ns
Year2016
Year2016 Time Taken: 70ns
Year2019
Year2019 Time Taken: 80ns
Year2020
Day1
Step 1: 731731
Step 2: 116115990
Day1 Time Taken: 187.376”s
Day2
Step 1: 515
Step 2: 711
Day2 Time Taken: 485.364”s
Year2020 Time Taken: 709.758”s
Year2021
Day1
Step 1: 1448
Step 2: 1471
Day1 Time Taken: 239.692”s
Year2021 Time Taken: 253.871”s
All Time Taken: 1.005027ms
(The times are because I have verbose mode showing with -v
.)
I did todayâs using a spreadsheet (am I kicked out?)âŠbut I intend to go back and do it correctly using elixir sometime soon
The goal is to solve it any way possible, lol. They are designed so even a python program should be able to solve any of the problems in less than 15 or so seconds. Using excel though might exceed that time by a few magnitudes on a few of the problems, lol.
Right tool for the right job. Respect. No really. This problem has spreadsheet written all over it.
Didnât enjoy todayâs one as much.
I was in a noisy room so I opted for a dictionary to represent my data structure instead of tuple (taxing to be remembering which position means what), thereby giving up my chance to use the amazing Tuple.product
at the end. Anyhoo, my solutions down here:
defmodule AdventOfCode.Y2021.Day02 do
@moduledoc """
--- Day 2: Dive! ---
Problem Link: https://adventofcode.com/2021/day/2
"""
use AdventOfCode.Helpers.InputReader, year: 2021, day: 2
def run_1, do: input!() |> parse() |> track_positions() |> then(&(&1.depth * &1.horizontal))
def run_2, do: input!() |> parse() |> track_aims() |> then(&(&1.depth * &1.horizontal))
def parse(data) do
data
|> String.split("\n")
|> Enum.map(fn line ->
[direction, value] = String.split(line, " ")
{String.to_existing_atom(direction), String.to_integer(value)}
end)
end
defp track_positions(directions) do
directions
|> Enum.reduce(%{horizontal: 0, depth: 0}, fn
{:forward, v}, %{horizontal: horizontal} = acc -> %{acc | horizontal: horizontal + v}
{:backward, v}, %{horizontal: horizontal} = acc -> %{acc | horizontal: horizontal - v}
{:up, v}, %{depth: depth} = acc -> %{acc | depth: depth - v}
{:down, v}, %{depth: depth} = acc -> %{acc | depth: depth + v}
end)
end
defp track_aims(directions) do
directions
|> Enum.reduce(%{horizontal: 0, depth: 0, aim: 0}, fn
{:forward, v}, %{horizontal: horizontal, depth: depth, aim: aim} = acc ->
%{acc | horizontal: horizontal + v, depth: depth + aim * v}
{:backward, v}, %{horizontal: horizontal} = acc ->
%{acc | horizontal: horizontal - v}
{:up, v}, %{aim: aim} = acc ->
%{acc | aim: aim - v}
{:down, v}, %{aim: aim} = acc ->
%{acc | aim: aim + v}
end)
end
end
As Keanu Reeves would say: âwhoa.â
And day 2 was fun, I decided to make a âproperâ set of types of it instead of just churning in place, less efficient then it could be for sure, but fast enough to run it dozens of times in the span of a single eyeblink so meh, lol:
⯠cargo run --release -- -v 2021 2
Finished release [optimized] target(s) in 0.05s
Running `target/release/advent_of_code -v 2021 2`
Step 1: 1250395
Step 2: 1451210346
_Day2 Time Taken: 90.003”s_
_Time Taken: 92.593”s_
And the code (also available on my prior post github link), with still probably waaaay too much error checking but eh, I almost even used checked_add/sub
instead of the default overflowing +/-
but considered even that âtooâ excessive for this, lol:
use crate::aoc::helpers::*;
use crate::AocApp;
use anyhow::Context;
use clap::Parser;
use std::num::NonZeroU8;
use std::path::PathBuf;
#[derive(Debug, Parser)]
pub struct Day2 {
/// The input file of "commands"
#[clap(default_value = "inputs/2021/day2.input")]
pub input_file: PathBuf,
}
enum Commands {
Forward(NonZeroU8),
Down(NonZeroU8),
Up(NonZeroU8),
}
#[derive(Default)]
struct Pos {
depth: u32,
fore: u32,
aim: u32,
}
impl Pos {
fn solution(&self) -> u32 {
self.depth * self.fore
}
}
impl Day2 {
pub fn run(&self, _app: &AocApp) -> anyhow::Result<()> {
let commands = map_trimmed_nonempty_lines_of_file(&self.input_file, |line| {
match line
.split_once(' ')
.context("input is not a command then space then a number")?
{
("forward", n) => Ok(Commands::Forward(
n.parse().context("input is not a number")?,
)),
("down", n) => Ok(Commands::Down(n.parse().context("input is not a number")?)),
("up", n) => Ok(Commands::Up(n.parse().context("input is not a number")?)),
_ => anyhow::bail!("input is not a valid command of forward|down|up then a number"),
}
})?;
println!(
"Step 1: {}",
commands
.iter()
.fold(Pos::default(), |mut pos, cmd| {
match cmd {
Commands::Forward(n) => pos.fore += n.get() as u32,
Commands::Down(n) => pos.depth += n.get() as u32,
Commands::Up(n) => pos.depth -= n.get() as u32,
}
pos
})
.solution()
);
println!(
"Step 2: {}",
commands
.iter()
.fold(Pos::default(), |mut pos, cmd| {
match cmd {
Commands::Down(n) => pos.aim += n.get() as u32,
Commands::Up(n) => pos.aim -= n.get() as u32,
Commands::Forward(n) => {
pos.fore += n.get() as u32;
pos.depth += pos.aim * n.get() as u32;
}
}
pos
})
.solution()
);
Ok(())
}
}
EDIT: And hereâs the full output of run-all
so far:
OvermindDL1âs Advent Of Code
Year2015
Year2015 Time Taken: 40ns
Year2016
Year2016 Time Taken: 30ns
Year2017
Year2017 Time Taken: 30ns
Year2016
Year2016 Time Taken: 40ns
Year2019
Year2019 Time Taken: 30ns
Year2020
Year2020 - Day1
Step 1: 731731
Step 2: 116115990
Day1 Time Taken: 96.843”s
Year2020 - Day2
Step 1: 515
Step 2: 711
Day2 Time Taken: 214.024”s
Year2020 - Day3
Step 1: 250
Step 2: 1592662500
Day3 Time Taken: 134.3”s
Year2020 - Day4
Step 1: 206
Step 2: 123
Day4 Time Taken: 227.134”s
Year2020 Time Taken: 711.038”s
Year2021
Year2021 - Day1
Step 1: 1448
Step 2: 1471
Day1 Time Taken: 110.992”s
Year2021 - Day2
Step 1: 1250395
Step 2: 1451210346
Day2 Time Taken: 80.384”s
Year2021 Time Taken: 207.505”s
All Time Taken: 941.942”s
Todayâs part 2 took embarrassingly long time for me. I mis-read the description and thought the âfrequencyâ value should be constant, and not change per list-reduction. Anyways, once I understood the problem, it became trivial. This is a very recursion friendly question.
defmodule AdventOfCode.Y2021.Day03 do
@moduledoc """
--- Day 3: Binary Diagnostic ---
Problem Link: https://adventofcode.com/2021/day/3
"""
use AdventOfCode.Helpers.InputReader, year: 2021, day: 3
def run_1 do
input!()
|> parse()
|> transpose()
|> bit_frequencies()
|> epsilon_gamma()
|> Tuple.product()
end
def run_2, do: input!() |> parse() |> life_support_rating()
def parse(data), do: data |> String.split("\n") |> Enum.map(&String.graphemes/1)
defp transpose(data), do: data |> Enum.zip() |> Enum.map(&Tuple.to_list/1)
defp bit_frequencies(data) do
data
|> Enum.map(&Enum.frequencies/1)
|> Enum.reduce([], fn
%{"0" => lo, "1" => hi}, acc when lo >= hi -> [{"0", "1"} | acc]
_, acc -> [{"1", "0"} | acc]
end)
end
defp to_integer_by(encoded_data, index) do
encoded_data
|> Enum.map_join(&elem(&1, index))
|> String.reverse()
|> String.to_integer(2)
end
defp epsilon_gamma(encoded_data) do
{to_integer_by(encoded_data, 0), to_integer_by(encoded_data, 1)}
end
defp life_support_rating(data), do: o2(data, 0) * co2(data, 0)
defp o2([result], _), do: to_integer(result)
defp o2(data, idx) do
value = frequent_by(data, idx, :o2)
o2(
Enum.filter(data, &(Enum.at(&1, idx) == value)),
idx + 1
)
end
defp co2([result], _), do: to_integer(result)
defp co2(data, idx) do
value = frequent_by(data, idx, :co2)
co2(
Enum.filter(data, &(Enum.at(&1, idx) == value)),
idx + 1
)
end
defp frequent_by(data, idx, strategy) do
data
|> Enum.map(&Enum.at(&1, idx))
|> Enum.frequencies()
|> then(fn
%{"0" => lo, "1" => hi} when lo > hi -> (strategy == :o2 && "0") || "1"
_ -> (strategy == :o2 && "1") || "0"
end)
end
defp to_integer(result), do: result |> Enum.join() |> String.to_integer(2)
end
On the contrary, I encourage you to see how far you can go!
I completed the first six days* of last yearâs challenges using only Google Sheets (and a bit of text formatting with Notepad++).
I learned a lot about some of the more advanced techniques and functions, and I had a lot of fun doing it!
In a way, spreadsheets might embody the most popular functional language and REPL in the world.
(*) IIRC, I stopped at Day 7 because it involved recursion (walking a tree, I think). After a bit of investigation, I concluded that it might still be possible to use a spreadsheet but the situation would result in me fighting the tool.
I did Day 3 in this time:
Year2021 - Day3
Step 1: 3969000
Step 2: 4267809
Day3 Time Taken: 161.628”s
It was oddly fun to optimize, I did the oxygen/co2 lookup without allocating anything, using a kind of quicksort to subdivide the regions of bits and ignoring the bits I didnât care about until I had the final top and bottom sorted to single values and those were my co2 and oxygen values, was fun. ^.^
Itâs on github with the rest of course:
One part I initially screwed up on was when I put println!("Step 2: {}", oxygen_rating & co2_rating);
instead of println!("Step 2: {}", oxygen_rating * co2_rating);
, lolâŠ
Took much longer than I expected this one. I should get over my fear of 2 dimensions.
Did some Python. Probably the first time I wrote Python outside of work in a long, long time. Day 2 though.
"""Advent of Code Year 2021, Day 2
Problem Link: https://adventofcode.com/2021/day/2
"""
from collections import defaultdict
from helpers.input import read_input_lines
def get_input_data() -> list[tuple]:
instructions = []
for i in read_input_lines(__file__, day=2):
(direction, x) = i.strip().split(" ")
instructions.append((direction, int(x)))
return instructions
def part_1() -> int:
pos = defaultdict(int)
for instruction in get_input_data():
match instruction:
case ("forward", x):
pos["horizontal"] += x
case ("backward", x):
pos["horizontal"] -= x
case ("up", x):
pos["depth"] -= x
case ("down", x):
pos["depth"] += x
return pos["horizontal"] * pos["depth"]
def part_2() -> int:
pos = defaultdict(int)
for instruction in get_input_data():
match instruction:
case ("forward", x):
pos |= {"horizontal": pos["horizontal"] + x, "depth": pos["depth"] + pos["aim"] * x}
case ("backward", x):
pos["horizontal"] -= x
case ("up", x):
pos["aim"] -= x
case ("down", x):
pos["aim"] += x
return pos["horizontal"] * pos["depth"]
def run() -> dict[str, int]:
"""
Solution runner
:return: The solutions of both parts of day 2 for year 2021
>>> run()
{'part_1': 1660158, 'part_2': 1604592846}
"""
return {
"part_1": part_1(),
"part_2": part_2()
}
if __name__ == '__main__':
import doctest
doctest.testmod()
print(run())
These are getting trickier. The first part was easy, the second, well, needed to re-do first one to make it performant. I guess that quote where they say âOur greatest shortcomings is the inebility to comprehend exponential growthâ is true â at least for me.
Anyways, I did do pigeonhole sort in Elixir few days ago, and a similar idea can be applied here⊠bucket em up.
defmodule AdventOfCode.Y2021.Day06 do
use AdventOfCode.Helpers.InputReader, year: 2021, day: 6
def run_1, do: input!() |> parse() |> multiply(80) |> Enum.sum()
def run_2, do: input!() |> parse() |> multiply(256) |> Enum.sum()
def parse(f), do: f |> String.split(",") |> Enum.map(&String.to_integer/1) |> Enum.frequencies()
def multiply(fishes, day) do
(day == 0 && Map.values(fishes)) ||
multiply(
Map.pop(fishes, 0)
|> then(
&Map.merge(
for({k, v} <- elem(&1, 1), into: %{}, do: {k - 1, v}),
%{6 => elem(&1, 0) || 0, 8 => elem(&1, 0) || 0},
fn _, a, b -> a + b end
)
),
day - 1
)
end
end