Advent Of Code 2022 - Day 3

Yea ok there are some elegant solutions in here, wow :exploding_head: Made me almost not post mine :see_no_evil:
Regardless, I had some fun with and learned about String.myers_difference and Enum.frequencies :smile:

defmodule ExAOC2022.Day3 do
  @input "./lib/day3_input.txt"

  def puzzle1() do
    prio = priority_map([?a..?z, ?A..?Z])

    @input
    |> file_by_line()
    |> Enum.map(&by_compartment/1)
    |> Enum.map(&eq/1)
    |> Enum.map(&Keyword.get(prio, String.to_atom(&1)))
    |> Enum.sum()
  end

  def puzzle2() do
    prio = priority_map([?a..?z, ?A..?Z])

    @input
    |> file_by_line()
    |> Enum.chunk_every(3)
    |> Enum.map(&find_common_chars/1)
    |> Enum.map(&Keyword.get(prio, String.to_atom(&1)))
    |> Enum.sum()
  end

  # PRIVATE SHENANIGANS
  defp by_compartment(content) do
    mid_len = content |> String.length() |> div(2)

    content |> String.split_at(mid_len)
  end

  defp eq({str1, str2}), do: String.myers_difference(str1, str2)[:eq]

  defp find_common_chars(list_of_strings) do
    list_of_strings
    |> Enum.map(&uniq/1)
    |> Enum.join()
    |> String.graphemes()
    |> Enum.frequencies()
    |> Map.filter(fn {_k, v} -> v == 3 end)
    |> Map.keys()
    |> List.first()
  end

  defp uniq(str) do
    str
    |> String.graphemes()
    |> Enum.uniq()
    |> Enum.join()
  end

  defp priority_map(ranges) do
    ranges
    |> Enum.map(&priority/1)
    |> Enum.concat()
    |> Enum.with_index(1)
  end

  defp priority(range) do
    range
    |> Enum.map(&String.to_atom(<<&1::utf8>>))
  end

  defp file_by_line(file) do
    file
    |> File.read!()
    |> String.split(~r/\R/, trim: true)
  end
end
1 Like

I made a small change to the ReqAOC fetch! function signature because I thought it looked nicer :slight_smile:

Originally I was going to handle the session token behind the scenes, but Livebook is soooo good that the explicit version arguably provides better UX:

Mix.install([
  {:req_aoc, github: "mcrumm/req_aoc", ref: "eef58c9"}
])

System.fetch_env!("LB_AOC_SESSION") |> ReqAOC.fetch!({2022, 03}, max_retries: 0)

…it will prompt you to add/grant access to the app secret if it does not already exist!

EDIT: Run in Livebook

2 Likes

When I tested earlier today I had no problem, but now I’m getting “(Argument Error) unknown registry: Req.Finch” when I try to compile. Any idea what that’s about?

Not a specific idea, but anytime I have issues like that in Livebook first I try closing and re-opening the session and if that doesn’t working then I try forcing a reinstall with Mix.install(packages, force: true).

1 Like

Not using in Livebook but just a regular project with req_aoc added to the deps in mix.exs.

Same advice, different command: rm -rf _build && mix compile :slight_smile:

1 Like

Yeah, I’ve tried that same error. Full error output:

Compiling 1 file (.ex)

22:17:30.446 [warning] Failed to lookup telemetry handlers. Ensure the telemetry application has been started.

22:17:30.455 [warning] Failed to lookup telemetry handlers. Ensure the telemetry application has been started.

== Compilation error in file lib/day4.ex ==
** (ArgumentError) unknown registry: Req.Finch
    (elixir 1.14.0) lib/registry.ex:1382: Registry.key_info!/1
    (elixir 1.14.0) lib/registry.ex:580: Registry.lookup/2
    (finch 0.14.0) lib/finch/pool_manager.ex:44: Finch.PoolManager.lookup_pool/2
    (finch 0.14.0) lib/finch/pool_manager.ex:34: Finch.PoolManager.get_pool/2
    (finch 0.14.0) lib/finch.ex:284: Finch.__stream__/5
    (finch 0.14.0) lib/finch.ex:324: anonymous fn/4 in Finch.request/3
    (telemetry 1.1.0) /home/matt/advent_of_code/2022/day4/deps/telemetry/src/telemetry.erl:320: :telemetry.span/3
    (req 0.3.2) lib/req/steps.ex:589: Req.Steps.run_finch/1

Can you post a minimal example somewhere? I’d be happy to take a closer look. Req starts its own default Finch pool on application start so if you’re trying to use ReqAOC at compile-time it may require some extra steps.

Oh, yeah. I’m a doofus. Set up the project as a lib but at the bottom of my day4.ex file I’m just outright calling the functions defined in that file. So at compile time no application is started. If I remove those and just run iex -S mix I can make it work. Thanks for helping me think it through.

1 Like

Part 1

input
|> String.split("\n", trim: true)
|> Enum.map(fn line ->
  [compartment_1, compartment_2] = line |> String.to_charlist() |> Enum.chunk_every(floor(byte_size(line) / 2))
  compartment_1_set = MapSet.new(compartment_1)
  compartment_2_set = MapSet.new(compartment_2)
  intersection = MapSet.intersection(compartment_1_set, compartment_2_set)
  [item | _] = MapSet.to_list(intersection)
  item
end)
|> Enum.reduce(0, fn
  item, acc when item in ?a..?z ->
    acc + item - ?z + 26
  item, acc when item in ?A..?Z ->
    acc + item - ?Z + 26 + 26
end)

Part 2

input
|> String.split("\n", trim: true)
|> Enum.map(&String.to_charlist/1)
|> Enum.chunk_every(3)
|> Enum.map(fn [a, b, c] ->
  [item | _] = MapSet.new(a) |> MapSet.intersection(MapSet.new(b)) |> MapSet.intersection(MapSet.new(c)) |> MapSet.to_list()
  item
end)
|> Enum.reduce(0, fn
  item, acc when item in ?a..?z ->
    acc + item - ?z + 26
  item, acc when item in ?A..?Z ->
    acc + item - ?Z + 26 + 26
end)

A little bit late to the game, but here’s my solution. I opted to do it in Livebook and, like many others, went with charlists and MapSet.intersection: advent-of-code/day03.livemd at main · cgrothaus/advent-of-code · GitHub

part 1, I used Perl for fun :slight_smile: enjoy!

#!/usr/bin/env perl -lnp
$l=y///c/2;s!.{$l}!$'=~/[$&]/;$&!e;$\+=-(ord>96?96:38)+ord}{

part 2:

priorities=[?a..?z, ?A..?Z] |> Enum.concat() |> Enum.with_index(1) |> Map.new()
File.read!("input")
 |> String.split("\n")
 |> Enum.map(& String.to_charlist(&1) |> MapSet.new())
 |> Enum.chunk_every(3)
 |> Enum.flat_map(fn g -> Enum.reduce(g, &MapSet.intersection/2) end)
 |> Enum.map(&priorities[&1])
 |> Enum.sum()
 |> IO.inspect()

My eyes! :scream:

1 Like

open curly bracket at the end

Yes! It’s a classic trick for Perl golf-ing. Challenge: can you find where it comes from? ( hint, it’s linked to the -p option)

Like some other people in this thread, I also went with code points to calculate the item priorities, but I took an exotic approach with List.myers_difference/2 for part 1. Before getting to part 2, I didn’t even know that MapSet exists… :laughing:. Oh well, like in the previous days, I’m always happy to get feedback. It’s a great help to improve as I’m starting with Elixir.

I got the same feeling as you, but let’s take this as a learning opportunity :smiley:. I also took myers_difference, but from List.myers_difference/2. I didn’t even know it was in String.