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