Run this notebook

Use Livebook to open this notebook and explore new ideas.

It is easy to get started, on your machine or the cloud.

Click below to open and run it in your Livebook at .

(or change your Livebook location)

# Day 3 ```elixir Mix.install([ {:kino, "~> 0.11.3"} ]) ``` ## Data ```elixir input = Kino.Input.textarea("Please paste your input file:") ``` ## Solutions ### Day 3: Gear Ratios Here is an example engine schematic: ``` 467..114.. ...*...... ..35..633. ......#... 617*...... .....+.58. ..592..... ......755. ...$.*.... .664.598.. ``` Every other number is adjacent to a symbol and so is a part number; their sum is `4361`. ### Part Two Adding up all of the gear ratios produces `467835`. ```elixir input = Kino.Input.read(input) defmodule Y2023.D3 do @moduledoc """ https://adventofcode.com/2023/day/3 """ def p1(input) do input |> parse_input() |> search_part_lines() |> Enum.sum() end def parse_input(input) do input |> String.split("\n") |> Enum.map(&String.trim/1) end def search_part_lines(lines), do: search_part_line(lines, nil) def search_part_line([line, next | rest], previous) do get_part_numbers_in_line(line, previous, next) ++ search_part_line([next | rest], line) end def search_part_line([line], previous) do get_part_numbers_in_line(line, previous, nil) end def get_part_numbers_in_line(line, previous, next) do line |> get_number_indices() |> filter_part_numbers(line, previous, next) |> indices_to_numbers(line) end def get_number_indices(line) do Regex.scan(~r/\d+/, line, return: :index) end def filter_part_numbers(indices, line, previous, next) do indices |> Enum.filter(&is_part_number?(&1, line, previous, next)) end def is_part_number?([index], line, previous, next) do symbol_before?(index, line) || symbol_after?(index, line) || symbol_above?(index, previous) || symbol_below?(index, next) end def symbol_before?({0, _length}, _line), do: false def symbol_before?({start, _length}, line) do line |> String.at(start - 1) |> is_symbol?() end def symbol_after?({start, length}, line) when start + length >= length(line), do: false def symbol_after?({start, length}, line) do line |> String.at(start + length) |> is_symbol?() end def symbol_above?(_, nil), do: false def symbol_above?({start, length}, previous) do symbol_in_range?(previous, start - 1, length + 2) end def symbol_below?(_, nil), do: false def symbol_below?({start, length}, next) do symbol_in_range?(next, start - 1, length + 2) end def symbol_in_range?(line, -1, length), do: symbol_in_range?(line, 0, length - 1) def symbol_in_range?(line, start, length) when start + length >= length(line), do: symbol_in_range?(line, start, length - 1) def symbol_in_range?(line, start, length) do line |> String.slice(start, length) |> String.split("") |> Enum.any?(&is_symbol?/1) end def is_symbol?("."), do: false def is_symbol?(""), do: false def is_symbol?(nil), do: false def is_symbol?(character) do case Integer.parse(character) do :error -> true _ -> false end end def indices_to_numbers(indices, line) do Enum.map(indices, fn [{start, length}] -> line |> String.slice(start, length) |> String.to_integer() end) end def p2(input) do input |> parse_input() |> search_gear_lines() |> Enum.filter(fn {_index, numbers} -> length(numbers) === 2 end) |> Enum.map(fn {_index, numbers} -> Enum.product(numbers) end) |> Enum.sum() end def search_gear_lines(lines), do: search_gear_line(lines, nil) def search_gear_line([line, next | rest], previous) do get_gear_numbers_in_line(line, previous, next) ++ search_gear_line([next | rest], line) end def search_gear_line([line], previous) do get_gear_numbers_in_line(line, previous, nil) end def get_gear_numbers_in_line(line, previous, next) do line |> get_star_indices() |> find_adjacent_numbers(line, previous, next) end def get_star_indices(line) do Regex.scan(~r/\*/, line, return: :index) end def find_adjacent_numbers(indices, line, previous, next) do indices |> Enum.map(&index_with_adjacent_numbers(&1, line, previous, next)) end def index_with_adjacent_numbers([{index, _}], line, previous, next) do {index, adjacent_numbers(index, line, previous, next)} end def adjacent_numbers(index, line, previous, next) do number_before(index, line) ++ number_after(index, line) ++ numbers_on_line(index, previous) ++ numbers_on_line(index, next) end def number_before(0, _line), do: [] def number_before(index, line) do line |> String.slice(0, index) |> number_at_end() |> Enum.map(&String.to_integer/1) end def number_at_end(substring) do Regex.run(~r/\d+$/, substring) || [] end def number_after(index, line) when index + 1 >= length(line), do: [] def number_after(index, line) do line |> String.slice(index + 1, String.length(line)) |> number_at_start() |> Enum.map(&String.to_integer/1) end def number_at_start(substring) do Regex.run(~r/^\d+/, substring) || [] end def numbers_on_line(_index, nil), do: [] def numbers_on_line(index, previous) do numbers_on_line = previous |> get_number_indices() |> Enum.map(fn [{start, length}] -> {start, length} end) spanning_number = numbers_on_line |> numbers_spanning_index(index) |> Enum.map(fn {start, length} -> previous |> String.slice(start, length) |> String.to_integer() end) if Enum.empty?(spanning_number) do number_before(index, previous) ++ number_after(index, previous) else spanning_number end end def numbers_spanning_index(numbers_on_line, index) do Enum.filter(numbers_on_line, fn {start, length} -> start <= index && start + length > index end) end end Y2023.D3.p1(input) |> IO.inspect() Y2023.D3.p2(input) |> IO.inspect() ``` ### Notes Someone on Reddit posted the following example test data that captures more of the edge cases: ``` 12.......*.. +.........34 .......-12.. ..78........ ..*....60... 78.........9 .5.....23..$ 8...90*12... ............ 2.2......12. .*.........* 1.1..503+.56 ``` This helped a lot. ### Observations My solution to part 1 was not very helpful for part 2. I think I only reused one function. By the end, I was just trying to get finished, so the code is not very clean or optimized.
See source

Have you already installed Livebook?

If you already installed Livebook, you can configure the default Livebook location where you want to open notebooks.
Livebook up Checking status We can't reach this Livebook (but we saved your preference anyway)
Run notebook

Not yet? Install Livebook in just a minute

Livebook is open source, free, and ready to run anywhere.

Run on your machine

with Livebook Desktop

Run in the cloud

on select platforms

To run on Linux, Docker, embedded devices, or Elixir’s Mix, check our README.

PLATINUM SPONSORS
SPONSORS
Code navigation with go to definition of modules and functions Read More ×