Powered by AppSignal & Oban Pro

Advent of Code 2023 Day 1

2023/day-01.livemd

Advent of Code 2023 Day 1

Mix.install([
  {:kino, "~> 0.11.3"},
  {:kino_aoc, "~> 0.1.5"}
])

Input

{:ok, puzzle_input} =
  KinoAOC.download_puzzle("2023", "1", System.fetch_env!("LB_AOC_SESSION"))
IO.puts(puzzle_input)

Part 1

puzzle_input
|> String.split("\n")
|> Stream.map(&:binary.bin_to_list/1)
|> Stream.map(fn chars ->
  Enum.filter(chars, &(&1 in ?0..?9))
end)
|> Stream.map(fn nums ->
  {List.first(nums) - ?0, List.last(nums) - ?0}
end)
|> Stream.map(fn {a, b} -> a * 10 + b end)
|> Enum.sum()

Part 2

num_words = %{
  "one" => 1,
  "two" => 2,
  "three" => 3,
  "four" => 4,
  "five" => 5,
  "six" => 6,
  "seven" => 7,
  "eight" => 8,
  "nine" => 9,
  "0" => 0,
  "1" => 1,
  "2" => 2,
  "3" => 3,
  "4" => 4,
  "5" => 5,
  "6" => 6,
  "7" => 7,
  "8" => 8,
  "9" => 9
}
regex =
  num_words
  |> Map.keys()
  |> Enum.join("|")
  |> then(&"(?=(#{&1}))")
  |> Regex.compile!()

About the regex above

This regex matches a slit (a.k.a. a zero-width match) whose right hand side matches 0 or 1 or …

The right hand side is a capture group, so that the text it matches can be retrieved after the matching procedure.

When this regex is used in scanning a string, the scan pointer moves ahead one character at a time.

# Here's a code snipped showing the trick of
# how to use positive lookahead in a regex scan
# to handle the overlapping.
Regex.scan(regex, "oneightwo", capture: :all_but_first)
puzzle_input
|> String.split("\n")
|> Stream.map(fn line ->
  regex
  |> Regex.scan(line, capture: :all_but_first)
  |> List.flatten()
end)
|> Stream.map(fn words ->
  {List.first(words), List.last(words)}
end)
|> Stream.map(fn {w1, w2} -> num_words[w1] * 10 + num_words[w2] end)
|> Enum.sum()