DEV Community

Cover image for Knights of the Dinner Table
Robert Mion
Robert Mion

Posted on

Knights of the Dinner Table

Advent of Code 2015 Day 13

Part 1

  1. Another combination puzzle?!
  2. How many possible combinations?
  3. Leveraging that combo-generator library
  4. Creating each person's happiness dictionary
  5. Look right, look left, then add

Another combination puzzle?!

  • You'd think I'd be sick of them by now
  • But this one seems equally as intriguing as the others thus far this year!

How many possible combinations?

The example input features four people:

First chair: 4 options Second chair: 3 options Third chair: 2 options Fourth chair: 1 option 4 * 3 * 2 * 1 4! 24 combinations 
Enter fullscreen mode Exit fullscreen mode

My puzzle input features eight people:

8! 40,320 combinations 
Enter fullscreen mode Exit fullscreen mode

Thus, I'll have to determine the happiness scores of over 40K combinations to find the optimal seating arrangement.

Leveraging that combo-generator library

  • In an earlier combo-themed puzzle, I resorted to browsing the reddit Solution Megathread for help
  • In one JavaScript solver's post, they referenced a handy library, js-combinatorics
  • I intend to use it to make solving this puzzle a bit easier

The library has a method, Permutation, that accepts a string of characters and returns a collection of all the ways those characters could be arranged.

I'll take the first initial of each person from my input and concatenate them into a string:

const C = require('js-combinatorics') // Example input: Alice, Bob, Carol, David const arrangements = [...new C.Permutation('ABCD')] console.log(arrangements.length) // 24 // Example input: Alice, Bob, Carol, David, Eric, Frank, George, Mallory const arrangements = [...new C.Permutation('ABCDEFGM')] console.log(arrangements.length) // 40320 
Enter fullscreen mode Exit fullscreen mode

Fantastic! Each collection includes the expected factorial amount.

Now, how do I perform my calculations on these collections?

Creating each person's happiness dictionary

In the example input, these are Alice's scores:

Alice would gain 54 happiness units by sitting next to Bob. Alice would lose 79 happiness units by sitting next to Carol. Alice would lose 2 happiness units by sitting next to David. 
Enter fullscreen mode Exit fullscreen mode

From this, I want to craft this dictionary:

{ 'A': { 'B': 54, 'C': -79, 'D': -2 } } 
Enter fullscreen mode Exit fullscreen mode

From each line, I need to extract four parts:

  • Family member's first initial
  • gain or lose
  • Amount of happiness units
  • Adjacent family member's first initial

I could use a regular expression, but since each line is the same number of words, I'll just use split() and access the relevant items:

let [X, , sign, amount, , , , , , , Y] = line.split(' ') X = X[0] Y = Y[0] sign = sign == 'gain' ? '+' : '-' amount = Number(sign + amount) 
Enter fullscreen mode Exit fullscreen mode

Then, I need to create the appropriate keys if they don't already exist, and set the appropriate amount.

My full dictionary-creation algorithm in JavaScript:

input.reduce((dict, line) => { let [X, , sign, amount, , , , , , , Y] = line.split(' ') X = X[0] Y = Y[0] sign = sign == 'gain' ? '+' : '-' amount = Number(sign + amount) if (!(X in dict)) dict[X] = {} dict[X][Y] = amount return dict }, {}) 
Enter fullscreen mode Exit fullscreen mode

The algorithm generates this dictionary for the example input:

{ A: { B: 54, C: -79, D: -2 }, B: { A: 83, C: -7, D: -63 }, C: { A: -62, B: 60, D: 55 }, D: { A: 46, B: -7, C: 41 } } 
Enter fullscreen mode Exit fullscreen mode

Look right, look left, then add

By now I have:

  • Each permutation of seating arrangements
  • The happiness units of each family member's adjacent family member

Time to do some math!

Using the first permutation of the example input:

['A','B','C','D'] 
Enter fullscreen mode Exit fullscreen mode

Look right:

A -> B: +54 B -> C: -7 C -> D: +55 D -> A: +46 ----------- 148 
Enter fullscreen mode Exit fullscreen mode

Look left:

D <- A: -2 A <- B: +83 B <- C: +60 C <- D: +41 ----------- 182 
Enter fullscreen mode Exit fullscreen mode

Then add:

 182 +148 ----------- Total: 330 -> Optimal arrangement! 
Enter fullscreen mode Exit fullscreen mode

My algorithm, animated:
Animation of my algorithm

My algorithm in JavaScript:

arrangements.reduce( (optimal, permutation) => Math.max( optimal, permutation.reduce( (sum, seat, index, RA) => sum += happiness[seat][RA[(index + 1) % RA.length]] , 0) + permutation.reverse().reduce( (sum, seat, index, RA) => sum += happiness[seat][RA[(index + 1) % RA.length]] , 0) ) , 0) 
Enter fullscreen mode Exit fullscreen mode

It generated the correct answer!

Part 2

  1. Inserting myself into the puzzle

Inserting myself into the puzzle

  • I could manipulate the input, but that feels taboo
  • I'll add myself programmatically, instead
happiness['R'] = {} for (let seat in happiness) { // Adding other family members' happiness units toward me happiness[seat]['R'] = 0 // Adding my happiness units toward them happiness['R'][seat] = 0 } 
Enter fullscreen mode Exit fullscreen mode

Generating 9! combinations instead of 8!:

const C = require('js-combinatorics') const arrangements = [...new C.Permutation('ABCDEFGMR')] 
Enter fullscreen mode Exit fullscreen mode

Running the program again...

...and it generates the correct answer again!

I did it!!

  • I solved both parts!
  • Leveraging a combination-generating library I used previously!
  • And my knack for treating arrays like circular lists!

I was a bit disappointed in how little increase of difficulty Part 2 was from Part 1.

Maybe it was intended to be a stress test for made-from-scratch combination-generation algorithms?

Regardless, this was another fun combo-themed puzzle!

Top comments (0)