Powered by AppSignal & Oban Pro
Would you like to see your link here? Contact us

Advent of Code 2022 - Day 4

advent-of-code/2022/day4.livemd

Advent of Code 2022 - Day 4

Mix.install([
  {:kino, "~> 0.7.0"}
])

Part 1

> Space needs to be cleared before the last supplies can be unloaded from the ships, and so several Elves have been assigned the job of cleaning up sections of the camp. Every section has a unique ID number, and each Elf is assigned a range of section IDs.

Okay, sections have an id number

> However, as some of the Elves compare their section assignments with each other, they’ve noticed that many of the assignments overlap.

Ok section id number assignments can overlap. We can’t have that. Luckily it’s only the assignments that overlap and not something like the section areas.

> To try to quickly find overlaps and reduce duplicated effort, the Elves pair up and make a big list of the section assignments for each pair (your puzzle input).

So we’ll have some kind of list structure of section ids and we need to identify the duplicates.

Sample Input

> > 2-4,6-8 > 2-3,4-5 > 5-7,7-9 > 2-8,3-7 > 6-6,4-6 > 2-6,4-8 >

For the first line

> The first Elf was assigned sections 2-4 (sections 2, 3, and 4), while the second Elf was assigned sections 6-8 (sections 6, 7, 8).

So each line of input will be elf assignment ranges separated by commas.

Visualization

> > .234..... 2-4 > .....678. 6-8 > > .23...... 2-3 > ...45.... 4-5 > > ....567.. 5-7 > ......789 7-9 > > .2345678. 2-8 > ..34567.. 3-7 > > .....6... 6-6 > ...456... 4-6 > > .23456... 2-6 > ...45678. 4-8 >

> Some of the pairs have noticed that one of their assignments fully contains the other. For example, 2-8 fully contains 3-7, and 6-6 is fully contained by 4-6. In pairs where one assignment fully contains the other, one Elf in the pair would be exclusively cleaning sections their partner will already be cleaning, so these seem like the most in need of reconsideration. In this example, there are 2 such pairs. > > In how many assignment pairs does one range fully contain the other?

Ok! First let’s the the input transformed into ranges.

sample_input = """
2-4,6-8
2-3,4-5
5-7,7-9
2-8,3-7
6-6,4-6
2-6,4-8
"""
sample_input
|> String.split()
|> Enum.map(&String.split(&1, ","))
|> Enum.map(fn assignment_pair ->
  assignment_pair
  |> Enum.map(&String.split(&1, "-"))
end)
|> Enum.map(fn assignment_pair ->
  assignment_pair
  |> Enum.map(fn assignment ->
    Enum.map(assignment, &String.to_integer/1)
  end)
end)
|> Enum.map(fn assignment_pair ->
  assignment_pair
  |> Enum.map(fn [a, b] ->
    a..b
  end)
end)

Now, do we have a function to check range inclusion?

We could turn the ranges into MapSets but that probably isn’t a great idea if the ranges could be very large.

Hmm, there’s no Range function for this. In that case we don’t really need to transform the input into ranges but only into the first last integers for comparision.

sample_input
|> String.split()
|> Enum.map(&String.split(&1, ","))
|> Enum.map(fn assignment_pair ->
  assignment_pair
  |> Enum.map(&String.split(&1, "-"))
end)
|> Enum.map(fn assignment_pair ->
  assignment_pair
  |> Enum.map(fn assignment ->
    Enum.map(assignment, &String.to_integer/1)
  end)
end)
defmodule Day4.Part1.A do
  def assignments(input) do
    input
    |> String.split()
    |> Enum.map(&String.split(&1, ","))
    |> Enum.map(fn assignment_pair ->
      assignment_pair
      |> Enum.map(&String.split(&1, "-"))
    end)
    |> Enum.map(fn assignment_pair ->
      assignment_pair
      |> Enum.map(fn assignment ->
        Enum.map(assignment, &String.to_integer/1)
      end)
    end)
  end

  def complete_overlap([[first1, last1], [first2, last2]]) do
    cond do
      first1 >= first2 and last1 <= last2 ->
        true

      first2 >= first1 and last2 <= last1 ->
        true

      true ->
        false
    end
  end
end
sample_input
|> Day4.Part1.A.assignments()
|> Enum.count(&amp;Day4.Part1.A.complete_overlap/1)

Ok! That matches the sample input. Let’s try the puzzle.

puzzle_input = Kino.Input.textarea("Paste your puzzle input")
puzzle_input
|> Kino.Input.read()
|> Day4.Part1.A.assignments()
|> Enum.count(&amp;Day4.Part1.A.complete_overlap/1)

Part 2

Now the elves want to know if any sections overlap at all, not just if the sections completely overlap.

> 5-7,7-9 overlaps in a single section, 7. > 2-8,3-7 overlaps all of the sections 3 through 7. > 6-6,4-6 overlaps in a single section, 6. > 2-6,4-8 overlaps in sections 4, 5, and 6.

For the sample input there are 4 assignments that even partially overlap.

defmodule Day4.Part2.A do
  def assignments(input) do
    input
    |> String.split()
    |> Enum.map(&amp;String.split(&amp;1, ","))
    |> Enum.map(fn assignment_pair ->
      assignment_pair
      |> Enum.map(&amp;String.split(&amp;1, "-"))
    end)
    |> Enum.map(fn assignment_pair ->
      assignment_pair
      |> Enum.map(fn assignment ->
        Enum.map(assignment, &amp;String.to_integer/1)
      end)
    end)
  end

  def overlap([[first1, last1], [first2, last2]]) do
    cond do
      first1 >= first2 and first1 <= last2 ->
        true

      first2 >= first1 and first2 <= last1 ->
        true

      true ->
        false
    end
  end
end
sample_input
|> Day4.Part2.A.assignments()
|> Enum.filter(&amp;Day4.Part2.A.overlap/1)
|> Enum.count()
|> dbg()

That works! Let’s try the puzzle input.

puzzle_input
|> Kino.Input.read()
|> Day4.Part2.A.assignments()
|> Enum.count(&amp;Day4.Part2.A.overlap/1)

Day 3 (Ref)

Each line of input in today’s puzzle is the contents of a rucksack.

Each rucksack has two compartments.

Each half of an input line is the contents of a rucksack compartment.

Each letter of an input is a type of item.

Each compartment is supposed to hold all items of one type (i.e. no type should be split between both compartments.)

For each line: find the type that is in both compartments.

Each type is also assigned a score (priority). Find each type that’s incorrectly in both compartments and sum up their scores.

sample_input = """
vJrwpWtwJgWrhcsFMMfFFhFp
jqHRNqRjqzjGDLGLrsFMfFZSrLrFZsSL
PmmdzqPrVvPwwTWBwg
wMqvLMZHhHMvwLHjbvcjnnSBnvTQFn
ttgJtRGJQctTZtZT
CrZsJsPPZsGzwwsLwLmpwMDw
"""
sample_input
|> String.split("\n", trim: true)
|> Enum.map(&amp;String.split(&amp;1, "", trim: true))
|> Enum.map(fn rucksack ->
  compartment_length = (Enum.count(rucksack) / 2) |> round()
  Enum.split(rucksack, compartment_length)
end)
|> Enum.map(fn {compartment1, compartment2} ->
  [compartment1, compartment2] |> Enum.map(&amp;MapSet.new/1)
end)
|> Enum.map(fn [contents1, contents2] ->
  MapSet.intersection(contents1, contents2)
end)

Ok cool, cool. MapSet makes this pretty straightforward. Now let’s translate letters to priorities.

  • Lowercase item types a through z have priorities 1 through 26.
  • Uppercase item types A through Z have priorities 27 through 52.
[
  a: ?a - 96,
  b: ?b - 96,
  c: ?b - 96,
  x: ?x - 96,
  y: ?y - 96,
  z: ?z - 96,
  A: ?A - 38,
  B: ?B - 38,
  C: ?C - 38,
  X: ?X - 38,
  Y: ?Y - 38,
  Z: ?Z - 38
]
defmodule Day3.Part1.A do
  def rucksacks(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&amp;String.split(&amp;1, "", trim: true))
  end

  def compartments(rucksacks) do
    rucksacks
    |> Enum.map(fn rucksack ->
      compartment_length = (Enum.count(rucksack) / 2) |> round()
      Enum.split(rucksack, compartment_length)
    end)
  end

  def common_types(rucksacks) do
    rucksacks
    |> Enum.map(fn {compartment1, compartment2} ->
      [compartment1, compartment2] |> Enum.map(&amp;MapSet.new/1)
    end)
    |> Enum.map(fn [contents1, contents2] ->
      MapSet.intersection(contents1, contents2)
      |> Enum.into([])
      |> Enum.at(0)
    end)
  end

  def priorities(characters) do
    characters
    |> Enum.map(&amp;String.to_charlist/1)
    |> Enum.map(&amp;hd/1)
    |> Enum.map(&amp;priority/1)
  end

  def priority(character) when character in ?a..?z do
    character - 96
  end

  def priority(character) when character in ?A..?Z do
    character - 38
  end

  def total(priorities) do
    Enum.sum(priorities)
  end
end
sample_input
|> Day3.Part1.A.rucksacks()
|> Day3.Part1.A.compartments()
|> Day3.Part1.A.common_types()
|> Day3.Part1.A.priorities()
|> Day3.Part1.A.total()

That matches the expectations for the sample input. Let’s try the puzzle!

puzzle_input = Kino.Input.textarea("Please paste your puzzle input")
puzzle_input
|> Kino.Input.read()
|> Day3.Part1.A.rucksacks()
|> Day3.Part1.A.compartments()
|> Day3.Part1.A.common_types()
|> Day3.Part1.A.priorities()
|> Day3.Part1.A.total()

Part 2

Another issue!

For safety, the Elves are divided into groups of three.

Every Elf carries a badge that identifies their group.

Within each group of three Elves, the badge is the only item type carried by all three Elves.

The only way to tell which item type is the right one is by finding the one item type that is common between all three Elves in each group.

Every set of three lines in your list corresponds to a single group, but each group can have a different badge item type.

In the sample input the first group of three has the common type r and the second group has the common type Z. The badge types are still prioritized the same way.

Find the badge for each group of three, determine the priority of their type, and sum the badge priorities.

defmodule Day3.Part2.A do
  def rucksacks(input) do
    input
    |> String.split("\n", trim: true)
    |> Enum.map(&amp;String.split(&amp;1, "", trim: true))
  end

  def group(rucksacks) do
    rucksacks
    |> Enum.chunk_every(3)
  end

  def badges(groups) do
    groups
    |> Enum.map(fn group ->
      Enum.map(group, &amp;MapSet.new/1)
    end)
    |> Enum.map(fn [elf1, elf2, elf3] ->
      elf1
      |> MapSet.intersection(elf2)
      |> MapSet.intersection(elf3)
      |> Enum.into([])
      |> Enum.at(0)
    end)
  end

  def priorities(characters) do
    characters
    |> Enum.map(&amp;String.to_charlist/1)
    |> Enum.map(&amp;hd/1)
    |> Enum.map(&amp;priority/1)
  end

  def priority(character) when character in ?a..?z do
    character - 96
  end

  def priority(character) when character in ?A..?Z do
    character - 38
  end

  def total(priorities) do
    Enum.sum(priorities)
  end
end
sample_input
|> Day3.Part2.A.rucksacks()
|> Day3.Part2.A.group()
|> Day3.Part2.A.badges()
|> Day3.Part2.A.priorities()
|> Day3.Part2.A.total()

Ok, that matches the sample puzzle requirements.

puzzle_input
|> Kino.Input.read()
|> Day3.Part2.A.rucksacks()
|> Day3.Part2.A.group()
|> Day3.Part2.A.badges()
|> Day3.Part2.A.priorities()
|> Day3.Part2.A.total()