That's React_ion_, not React

2 min(508 words)

The Problem

There is a (really long) string made up of a mix of uppercase and lowercase letters. The string is to be shortened by removing any lowercase letter if it's next to the corresponding uppercase letter (like "a" and "A"). The process can repeat. For instance,

dabA_cC_aCBAcCcaDA The first 'cC' is removed.
dab_Aa_CBAcCcaDA This creates 'Aa', which is removed.
dabCBA_cCc_aDA Either 'cC' or 'Cc' are removed (the result is the same).
dabCBAcaDA No further actions can be taken.

The desired solution is the length of the final string. Not much code for this one. Get the initial input:

const fs = require('fs')
let input = fs
.readFileSync(`./input/day5.txt`, 'utf8')
.trim()
.split('')

-and we're already 1/8 done. Note that I use .split('') and not .split('\n'), because I want an array of single characters.

The key feature of the polymerization reaction described here is that there's a process which has to happen an indeterminate number of times - it goes until it stops. I don't know yet how to describe each pass through the process, other than to give it a catchy name, like onePass. I do know that since the effect of one pass through the process is to shorten the length of the input, we can have the process start, then say it continues until the length of the input stops changing:

const polymerize = input => {
let nextInput = onePass(input)
while (input.length !== nextInput.length) {
input = nextInput
nextInput = onePass(input)
}
return input.length
}

In one pass through the process, every element in the input is compared to the one before it, and if certain conditions are met, they're both eliminated. I'll deal with how to determine the conditions later. Since every element is represented by a letter, elements can be marked for removal by replacing them with a non-letter character, then removed by filtering that character out:

const onePass = input => {
for (let i = 1; i < input.length; i++) {
if (condition(input[i - 1], input[i])) {
input[i - 1] = '-'
input[i] = '-'
}
}
return input.filter(c => c !== '-')
}

Note that I'm starting with i = 1 not i = 0. That's because I want to compare each element to the one before it, and input[0] doesn't have anything before it.

The condition that has to be met for the two elements to be eliminated is that they must be the uppercase and lowercase versions of the same letter. If that is the case, the elements themselves will not be equal (because JavaScript doesn't consider 'a' to be equal to 'A') but they can be made equal by converting them both into the same case (upper or lower). Turning that idea into code:

const condition = (first, second) =>
first !== second && first.toLowerCase() === second.toLowerCase()

That's all the code needed for part one! Here it is written all together:

const fs = require('fs')
let input = fs
.readFileSync(`./input/day5.txt`, 'utf8')
.trim()
.split('')
const condition = (first, second) =>
first !== second && first.toLowerCase() === second.toLowerCase()
const onePass = input => {
for (let i = 1; i < input.length; i++) {
if (condition(input[i - 1], input[i])) {
input[i - 1] = '-'
input[i] = '-'
}
}
return input.filter(c => c !== '-')
}
const polymerize = input => {
let nextInput = onePass(input)
while (input.length !== nextInput.length) {
input = nextInput
nextInput = onePass(input)
}
return input.length
}
console.log(polymerize(input))

Part two is even shorter than that. The goal of part two is to see which letter should be removed at the outset to drive the reaction the furthest. We'll need a representation of the whole alphabet, and writing out all the letters is for noobs.

One approach is to create a 26 element array with Array(26), then transform that array into letters with the String.fromCharCode() method. For some reason, calling .map() directly on an array created with Array(n) doesn't really work, so I spread the array into a new array first. (Yesterday I did (new Array(wakeTime)).fill(1) to accomplish the same thing.) At that point we're cooking with gas. String.fromCharCode() takes a numerical argument and returns a character:

String.fromCharCode(97) // "a"
String.fromCharCode(98) // "b"

So we can produce the whole alphabet with

const alphabet = [...Array(26)].map((_, i) => String.fromCharCode(i + 97))

Another approach is

const alphabet = 'abcdefghijklmnopqrstuvwyxz'.split('')

-but how fun is that?

Anyway, once the alphabet is ready, map from each letter to the polymer length produced by filtering the letter out of input before invoking polymerize():

const partTwo = alphabet.map(letter => {
const inputWithoutLetter = input.filter(
character => character.toLowerCase() !== letter
)
return polymerize(inputWithoutLetter)
})

The answer is the smallest number in partTwo:

console.log(Math.min(...partTwo))

The End.