Intro

Another day closer to Christmas, another coding Advent of code puzzle! Bring on day 4!

The Puzzle

This time I’m going to be helping the elves clean up areas of their camp. Each elf has been given sections of the camp to clean up which is given as a range: 1-3 for example tells the elf to clean up sections 1,2 and 3.

However there seems to have been a mistake and the elves have been assigned overlapping sections of the camp to tidy up! The elves get into pairs and start comparing their assignments. My job is to figure out how many of the pairs have an assignment that is a completely covered by the other elf in the pair.

The first pair might have sections 1-3 and 2-3 because the first assignment covers all the sections of the second assignment I need to count this pair.

Input

Today’s input is a list of the elves’ assignments in pairs. Like so:

5-90,4-90
52-52,3-51
46-81,45-80
15-48,49-75
14-81,14-81
... many lines of input!

Right then, I’m gong to get parsing that input into something I can easily work with. I want to have a sequence of sequences of 4 numbers each representing the coordinates of the assignments. In Clojure I basically want to do this:

"5-90,4-90" ~> '(5 90 4 90)

That shouldn’t be too hard. I’ll slurp the puzzle input and as usual use split-lines to get a sequence of strings, one for each elf pair. Now I want to strip out the commas and hyphens from all the strings to leave me with just the numbers. I can do that with clojure.string/split another useful function in Clojure string. split takes a string and a regex and will return a vector of the splits.

(clojure.string/split "Split this string" #" ")
["Split" "this" "string"]

And I can use Java inter-op to parse the result vector of numbers. Pulling it all together gives me this:

(ns day4.core
  (:require [clojure.string :as str]))

(defn parse-coordinates [coords]
  (map #(Integer/parseInt %) coords))

(def input
  (->> "./advent-of-code/day4/input.txt"
       slurp
       str/split-lines
       (map
      #(->> (str/split % #"[-|,]")
	    (parse-coordinates)))))
(take 4 input)
((5 90 4 90) (52 52 3 51) (46 81 45 80) (15 48 49 75))

Great that’ll be a pretty nice starting point! Let’s find some overlaps!

Part 1

To be honest, most of the heavy lifting has already been done above. Now we have an easy to use input I just need to write a function to determine if a pair of elves have fully contained assignments. That will be pretty easy.

One assignment is be contained in the other if the starting coordinate of one elf is less that or equal to the starting coordinate of the other AND the end coordinate is greater than or equal to the end coordinate of the other. That might be clearer expressed as code:

(defn assignments-fully-contained?
  [[elf-a-start elf-a-end elf-b-start elf-b-end]]
  (or
   (and
    (<= elf-a-start elf-b-start)
    (>= elf-a-end elf-b-end))
   (and
    (<= elf-b-start elf-a-start)
    (>= elf-b-end elf-a-end))))

If you’re not sure what is going on with the nested vector at the top, I’m using destructuring to pull out the 4 coordinates for the elf assignments. Then I just need to check if one range is contained in the other, I have to check both if the first assignment is contained in the second assignment or the other way round hence the or.

Let’s try that out:

(assignments-fully-contained? [1 5 2 3])
(assignments-fully-contained? [2 3 1 5])
(assignments-fully-contained? [1 4 4 8])
true
true
false

Right that looks good! I’m going to write a function that takes the input then using map with assignments-fully-contained? , filters only for true vales and counts the number of fully contained assignments!

(defn part-1 [assignments]
  (->> assignments
       (map assignments-fully-contained?)
       (filter true?)
       count))
(part-1 input)
536

I’ll plug 536 into the puzzle input and…

That’s the right answer!

Nice onto the second half! :-)

Part 2

Okay what will part to have me do today? Even knowing how many pairs have completely overlapping assignments there seems to be a lot of redundancy. Now the elves would like to know how many pairs have ANY overlap at all not just total overlap.

That’s not too much of a change. I can reuse the input parsing and I’ll just need to define a new function to check for partial overlaps between the pairs.

There is a partial overlap if the elf with the smaller starting coordinate has a ending coordinate greater than or equal to the elf with the larger starting coordinate. Again I feel that might read better as code so…

(defn assignments-overlap?
  [[elf-a-start elf-a-end elf-b-start elf-b-end]]
  (if (< elf-a-start elf-b-start)
    (>= elf-a-end elf-b-start)
    (>= elf-b-end elf-a-start)))
#'day4.core/assignments-overlap?
(assignments-overlap? [1 5 6 9])
(assignments-overlap? [1 5 5 9])
(assignments-overlap? [1 1 1 1])
(assignments-overlap? [1 4 1 1])
(assignments-overlap? [1 1 1 4])
false
true
true
true
true

Yeah neat! Okay I can write a part-2 function that dose the same as my part-1 function but use assignments-overlap? instead.

(defn part-2 [assignments]
  (->> assignments
       (map assignments-overlap?)
       (filter true?)
       count))
(part-2 input)
845

Neat! Let’s put that into the Advent of code website and!

You have completed Day 4!

Awesome! Day 4 down! I think that was easier than Day 3 but now I’ve said that I’m sure day 5 will be quite tricky!

If you’re interested, I’ve put the code for this puzzle and the other AoC challenges I’ve done on github.

Thanks for reading! :-)