Day 7 Recursive Circus

7.1 Part I

Day 7 can be summarized this way:

  • You have a tower made up of programs that are holding each other up in levels
  • You know their names, weights, and what programs they’re holding up
  • Part I challenge: you need to determine which program is at the bottom

The following example is provided:

if your list is the following:

pbga (66)
xhth (57)
ebii (61)
havc (66)
ktlj (57)
fwft (72) -> ktlj, cntj, xhth
qoyq (66)
padx (45) -> pbga, havc, qoyq
tknk (41) -> ugml, padx, fwft
jptl (61)
ugml (68) -> gyxo, ebii, jptl
gyxo (61)
cntj (57)
...then you would be able to recreate the structure of the towers that looks like this:

                gyxo
              /     
         ugml - ebii
       /      \     
      |         jptl
      |        
      |         pbga
     /        /
tknk --- padx - havc
     \        \
      |         qoyq
      |             
      |         ktlj
       \      /     
         fwft - cntj
              \     
                xhth
                
In this example, tknk is at the bottom of the tower (the bottom program), and is
holding up ugml, padx, and fwft. Those programs are, in turn, holding up other
programs; in this example, none of those programs are holding up any other programs,
and are all the tops of their own towers. (The actual tower balancing in front of you
is much larger.)

Let’s start by coding up the example and tidying up the input to make it useable.

## # A tibble: 13 x 3
##    program weight upper                 
##    <chr>    <dbl> <chr>                 
##  1 pbga       66. ""                    
##  2 xhth       57. ""                    
##  3 ebii       61. ""                    
##  4 havc       66. ""                    
##  5 ktlj       57. ""                    
##  6 fwft       72. " -> ktlj, cntj, xhth"
##  7 qoyq       66. ""                    
##  8 padx       45. " -> pbga, havc, qoyq"
##  9 tknk       41. " -> ugml, padx, fwft"
## 10 jptl       61. ""                    
## 11 ugml       68. " -> gyxo, ebii, jptl"
## 12 gyxo       61. ""                    
## 13 cntj       57. ""

The bottom program is the only program that is not be carried by any other program. In other words, it’s the only program that does not appear in the upper column. Using str_detect, we can identify the one program missing from upper.

## # A tibble: 1 x 4
##   program weight upper                  in_upper
##   <chr>    <dbl> <chr>                  <lgl>   
## 1 tknk       41. " -> ugml, padx, fwft" FALSE

Now pass in the puzzle input and you’re good to go!

7.2 Part II

In this section, we find out that one program is the wrong weight:

The programs explain the situation: they can't get down. Rather, they could get down, if they weren't expending all of their energy trying to keep the tower balanced. Apparently, one program has the wrong weight, and until it's fixed, they're stuck here.

For any program holding a disc, each program standing on that disc forms a sub-tower. Each of those sub-towers are supposed to be the same weight, or the disc itself isn't balanced. The weight of a tower is the sum of the weights of the programs in that tower.

In the example above, this means that for ugml's disc to be balanced, gyxo, ebii, and jptl must all have the same weight, and they do: 61.

However, for tknk to be balanced, each of the programs standing on its disc and all programs above it must each match. This means that the following sums must all be the same:

ugml + (gyxo + ebii + jptl) = 68 + (61 + 61 + 61) = 251
padx + (pbga + havc + qoyq) = 45 + (66 + 66 + 66) = 243
fwft + (ktlj + cntj + xhth) = 72 + (57 + 57 + 57) = 243
As you can see, tknk's disc is unbalanced: ugml's stack is heavier than the other two. Even though the nodes above ugml are balanced, ugml itself is too heavy: it needs to be 8 units lighter for its stack to weigh 243 and keep the towers balanced. If this change were made, its weight would be 60.

Given that exactly one program is the wrong weight, what would its weight need to be to balance the entire tower?

I decided to approach this problem in a step-wise manner. Starting at the top of the tower, I can find the “tips” by looking for programs that aren’t carrying other programs.

## # A tibble: 9 x 2
##   program weight
##   <chr>    <dbl>
## 1 pbga       66.
## 2 xhth       57.
## 3 ebii       61.
## 4 havc       66.
## 5 ktlj       57.
## 6 qoyq       66.
## 7 jptl       61.
## 8 gyxo       61.
## 9 cntj       57.

For these programs, their weights are straightforward. Since there are no other programs above them, their cumulative weights (cum_weight) are equal to their individual weights.

To determine the cumulative weights of all programs, we can initialize a tibble based on our input, and then fill in the cum_weights as we go level-by-level through the tower.

To start, we can input the weights of the tips into the program_index.

## Source: local data frame [13 x 3]
## Groups: <by row>
## 
## # A tibble: 13 x 3
##    program weight cum_weight
##    <chr>    <dbl>      <dbl>
##  1 pbga       66.        66.
##  2 xhth       57.        57.
##  3 ebii       61.        61.
##  4 havc       66.        66.
##  5 ktlj       57.        57.
##  6 fwft       72.        NA 
##  7 qoyq       66.        66.
##  8 padx       45.        NA 
##  9 tknk       41.        NA 
## 10 jptl       61.        61.
## 11 ugml       68.        NA 
## 12 gyxo       61.        61.
## 13 cntj       57.        57.

We can also define our bases variable, that includes all programs with unknown cumulative weights (cum_weight).

We can then define some functions to do the following:

  • calculate_weights: calculate weights of programs whose “upper” programs have cum_weights already calculated
  • add_cumweight_index: add the cum_weight for newly calculated base programs into the program_index
  • remove_bases: remove bases whose cum_weights are now known from the bases variable

With these functions, we can then run through our sample input once to check it:

## Source: local data frame [13 x 3]
## Groups: <by row>
## 
## # A tibble: 13 x 3
##    program weight cum_weight
##    <chr>    <dbl>      <dbl>
##  1 pbga       66.        66.
##  2 xhth       57.        57.
##  3 ebii       61.        61.
##  4 havc       66.        66.
##  5 ktlj       57.        57.
##  6 fwft       72.       243.
##  7 qoyq       66.        66.
##  8 padx       45.       243.
##  9 tknk       41.        NA 
## 10 jptl       61.        61.
## 11 ugml       68.       251.
## 12 gyxo       61.        61.
## 13 cntj       57.        57.
## # A tibble: 3 x 3
##   program weight upper
##   <chr>    <dbl> <chr>
## 1 tknk       41. ugml 
## 2 tknk       41. padx 
## 3 tknk       41. fwft

It looks good! Now we just make some small modifications to the variable names so that we can loop through it until we’ve calculated cum_weights for all bases. (I’ve also saved bases to bases2 and program_index to program_index2 to leave the original variable untouched.)

## Source: local data frame [13 x 3]
## Groups: <by row>
## 
## # A tibble: 13 x 3
##    program weight cum_weight
##    <chr>    <dbl>      <dbl>
##  1 pbga       66.        66.
##  2 xhth       57.        57.
##  3 ebii       61.        61.
##  4 havc       66.        66.
##  5 ktlj       57.        57.
##  6 fwft       72.       243.
##  7 qoyq       66.        66.
##  8 padx       45.       243.
##  9 tknk       41.       778.
## 10 jptl       61.        61.
## 11 ugml       68.       251.
## 12 gyxo       61.        61.
## 13 cntj       57.        57.

Now that the program_index (2) is filled out, we can use some summarization tricks to determine which program is causing all the problems.

## # A tibble: 1 x 4
##   program n_unique_upper uppers         upper_weights
##   <chr>            <int> <chr>          <chr>        
## 1 tknk                 2 ugml padx fwft 251 243 243

Here, we see that the ugml program causes the tower to be unbalanced. Instead of weighing 251, it should weight 243.

All that’s left is to try it with the puzzle input!