Skip to content

Commit

Permalink
Solve day 02 part 2
Browse files Browse the repository at this point in the history
  • Loading branch information
petros committed Dec 6, 2024
1 parent 052f4af commit 3eaf901
Show file tree
Hide file tree
Showing 3 changed files with 172 additions and 43 deletions.
20 changes: 20 additions & 0 deletions lib/day02/notes.md
Original file line number Diff line number Diff line change
@@ -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.
177 changes: 151 additions & 26 deletions lib/day02/solution.ex
Original file line number Diff line number Diff line change
@@ -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(
Expand Down Expand Up @@ -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
18 changes: 1 addition & 17 deletions test/day02/solution_test.exs
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 3eaf901

Please sign in to comment.