Advent of Code 2021

Well, then maybe also LFE would be worth a try? :wink:

4 Likes

You know what, it’s actually a great idea. I’ll try learning that. How is the interactive development with it like? Editor support?

2 Likes

Unfortunately, I’ve got absolutely no idea :smiley:
The experts are over there: LFE Forum - Erlang Forums

3 Likes

Yes saw that. I was surprised to know about SICP version of LFE as well. But looks like I will stick to Elixir this year too. What I started as a “warm-up” before AoC ended up being too much fun for me to be continuing, this will try to keep that as a sister project and alias libraries as needed.

2 Likes

A friend of mine was telling me if we’d do a live coding session on solving problems together in two different languages (He’s big on OCaml, me Elixir), I think I might try that out :smiley: Just discuss while code and then share the solutions. Not sure how that would play out.

Tagging @yawaramin - the aforementioned friend :smiley:

3 Likes

Do it Mafinar :lol:

1 Like

Maybe. But after a few days though. I am dying to use LiveView + AoC, so will be experiencing that the first few days.

2 Likes

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 :slight_smile:

An hour and a half(ish) to go! I’ll start the discussion once I get one done.

2 Likes

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)
4 Likes

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
4 Likes

Paste the code in backticks Mafinar :lol:

(People often browse the forum on their mobile and images can eat into their bandwidth :upside_down_face:)

2 Likes

Sure thing :smiley:

2 Likes

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.)

5 Likes

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

3 Likes

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.

2 Likes

Right tool for the right job. Respect. No really. This problem has spreadsheet written all over it.

3 Likes

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
2 Likes

As Keanu Reeves would say: “whoa.”

2 Likes

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

1 Like

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