diff --git a/lib/day02/notes.md b/lib/day02/notes.md new file mode 100644 index 0000000..782e351 --- /dev/null +++ b/lib/day02/notes.md @@ -0,0 +1,20 @@ +Notes for Day 02 Part 2 +======================= + +This report is unsafe with the code from Part 1. + +```elixir +report = [1, 3, 2, 4, 5] +Solution.safe?(report) +false +``` + +However if we are to remove 3, it's safe: + +```elixir +report = [1, 2, 4, 5] +Solution.safe?(report) +true +``` + +We can only remove one level. Now, how do we know which level to remove? The only way to know is to try removing all levels one by one and see if the report ever becomes safe. That's my approach. diff --git a/lib/day02/solution.ex b/lib/day02/solution.ex index d67b3d9..45876cc 100644 --- a/lib/day02/solution.ex +++ b/lib/day02/solution.ex @@ -1,28 +1,15 @@ defmodule Aoc2024.Day02.Solution do - def load_input() do - path = "lib/day02/input.txt" - {:ok, contents} = File.read(path) - - contents - |> String.split("\n", trim: true) - |> Enum.map(fn line -> - line - |> String.split(" ", trim: true) - |> Enum.map(&String.to_integer/1) - end) - end + @doc """ + # Examples + iex> Aoc2024.Day02.Solution.sorted?([1, 2, 3]) + true - def solve_part1() do - load_input() - |> safe_count() - end - - def safe_count(reports) do - reports - |> Enum.map(&safe?(&1)) - |> Enum.count(fn x -> x end) - end + iex> Aoc2024.Day02.Solution.sorted?([3, 2, 1]) + true + iex> Aoc2024.Day02.Solution.sorted?([3, 1, 2]) + false + """ def sorted?(list) do asc = Enum.chunk_while( @@ -51,15 +38,153 @@ defmodule Aoc2024.Day02.Solution do List.first(asc) == List.last(list) or List.first(desc) == List.last(list) end - defp safe?(report) do - sorted?(report) and safe_levels?(report) - end + @doc """ + # Examples + iex> Aoc2024.Day02.Solution.safe_levels?([7, 6, 4, 2, 1]) + true - defp safe_levels?(report) do + iex> Aoc2024.Day02.Solution.safe_levels?([1, 2, 7, 8, 9]) + false + """ + def safe_levels?(report) do report |> Enum.chunk_every(2, 1, :discard) |> Enum.map(fn [x, y] -> abs(x - y) end) |> Enum.map(fn x -> x >= 1 and x <= 3 end) |> Enum.all?() end + + @doc """ + # Examples + iex> Aoc2024.Day02.Solution.remove_one_level_permutations([1, 2, 3]) + [[2, 3], [1, 3], [1, 2]] + + iex> Aoc2024.Day02.Solution.remove_one_level_permutations([8, 6, 4, 4, 1]) + [[6, 4, 4, 1], [8, 4, 4, 1], [8, 6, 4, 1], [8, 6, 4, 1], [8, 6, 4, 4]] + """ + def remove_one_level_permutations(report) do + Enum.with_index(report, fn _level, index -> + List.delete_at(report, index) + end) + end + + @doc """ + 7 6 4 2 1: Safe because the levels are all decreasing by 1 or 2. + 1 2 7 8 9: Unsafe because 2 7 is an increase of 5. + 9 7 6 2 1: Unsafe because 6 2 is a decrease of 4. + 1 3 2 4 5: Unsafe because 1 3 is increasing but 3 2 is decreasing. + 8 6 4 4 1: Unsafe because 4 4 is neither an increase or a decrease. + 1 3 6 7 9: Safe because the levels are all increasing by 1, 2, or 3. + + # Examples + iex> Aoc2024.Day02.Solution.safe?([7, 6, 4, 2, 1]) + true + + iex> Aoc2024.Day02.Solution.safe?([1, 2, 7, 8, 9]) + false + + iex> Aoc2024.Day02.Solution.safe?([9, 7, 6, 2, 1]) + false + + iex> Aoc2024.Day02.Solution.safe?([1, 3, 2, 4, 5]) + false + + iex> Aoc2024.Day02.Solution.safe?([8, 6, 4, 4, 1]) + false + + iex> Aoc2024.Day02.Solution.safe?([1, 3, 6, 7, 9]) + true + """ + def safe?(report) do + sorted?(report) and safe_levels?(report) + end + + @doc """ + 7 6 4 2 1: Safe without removing any level. + 1 2 7 8 9: Unsafe regardless of which level is removed. + 9 7 6 2 1: Unsafe regardless of which level is removed. + 1 3 2 4 5: Safe by removing the second level, 3. + 8 6 4 4 1: Safe by removing the third level, 4. + 1 3 6 7 9: Safe without removing any level. + + # Examples + iex> Aoc2024.Day02.Solution.safe_with_problem_dampener?([7, 6, 4, 2, 1]) + true + + iex> Aoc2024.Day02.Solution.safe_with_problem_dampener?([1, 2, 7, 8, 9]) + false + + iex> Aoc2024.Day02.Solution.safe_with_problem_dampener?([9, 7, 6, 2, 1]) + false + + iex> Aoc2024.Day02.Solution.safe_with_problem_dampener?([1, 3, 2, 4, 5]) + true + + iex> Aoc2024.Day02.Solution.safe_with_problem_dampener?([8, 6, 4, 4, 1]) + true + + iex> Aoc2024.Day02.Solution.safe_with_problem_dampener?([1, 3, 6, 7, 9]) + true + """ + def safe_with_problem_dampener?(report) do + remove_one_level_permutations(report) + |> Enum.map(&safe?(&1)) + |> Enum.any?() + end + + @doc """ + # Examples + iex> reports = [ + ...> [7, 6, 4, 2, 1], + ...> [1, 2, 7, 8, 9], + ...> [9, 7, 6, 2, 1], + ...> [1, 3, 2, 4, 5], + ...> [8, 6, 4, 4, 1], + ...> [1, 3, 6, 7, 9] + ...> ] + iex> Aoc2024.Day02.Solution.solve_part1(reports) + 2 + """ + def solve_part1(reports) do + reports + |> Enum.map(&safe?(&1)) + |> Enum.count(fn x -> x end) + end + + @doc """ + # Examples + iex> reports = [ + ...> [7, 6, 4, 2, 1], + ...> [1, 2, 7, 8, 9], + ...> [9, 7, 6, 2, 1], + ...> [1, 3, 2, 4, 5], + ...> [8, 6, 4, 4, 1], + ...> [1, 3, 6, 7, 9] + ...> ] + iex> Aoc2024.Day02.Solution.solve_part2(reports) + 4 + """ + def solve_part2(reports) do + reports + |> Enum.map(&safe_with_problem_dampener?(&1)) + |> Enum.count(fn x -> x end) + end + + @doc """ + # Examples + iex> Aoc2024.Day02.Solution.load_input() |> Enum.count() + 1000 + """ + def load_input() do + path = "lib/day02/input.txt" + {:ok, contents} = File.read(path) + + contents + |> String.split("\n", trim: true) + |> Enum.map(fn line -> + line + |> String.split(" ", trim: true) + |> Enum.map(&String.to_integer/1) + end) + end end diff --git a/test/day02/solution_test.exs b/test/day02/solution_test.exs index 1a6f8da..bdb9262 100644 --- a/test/day02/solution_test.exs +++ b/test/day02/solution_test.exs @@ -1,20 +1,4 @@ defmodule Aoc2024Test.Day02 do use ExUnit.Case - doctest Aoc2024 - - alias Aoc2024.Day02.Solution - - test "day 02 part 1" do - reports = [ - [7, 6, 4, 2, 1], - [1, 2, 7, 8, 9], - [9, 7, 6, 2, 1], - [1, 3, 2, 4, 5], - [8, 6, 4, 4, 1], - [1, 3, 6, 7, 9] - ] - - assert Solution.safe_count(reports) == 2 - assert Solution.solve_part1() == 421 - end + doctest Aoc2024.Day02.Solution end