december 14, 2023

{ advent of code 2023 in javascript: day 5, part 1 }


tags: javascript, algorithms, advent of code

Day 5 was 87% understanding what the problem was asking and 13% solving. Then it was apparently 92% waiting for code to run. But once I got the question, getting a JavaScript solution for Advent of Code Day 5 was actually fun.

Day 5 Challenge: If You Give A Seed A Fertilizer

You can find the day’s challenge, examples, and input here.

We’re going to need a bigger boat. Specifically, we need to take a ferry to check on a shipment of sand. Fortunately, we can pass the waiting time with our new elf acquaintance who needs help deciphering his almanac.

I am not going to explain what we’re doing today because I don’t honestly think I can. Please just go check it out. It took me several read throughs to get it.

But once I did, puzzling this one out proved rather fun. That is, it was fun after the parsing. I saw someone on Reddit call this Advent of Parsing. They weren’t wrong.

Tests for Part 1 in Jest

I decided on a global variable to store my array of maps (seed to soil, soil to fertilizer, etc). Then I have three functions. My conductor function findClosestLocation, parseMaps, and mapSourceToDestination.

I didn’t write a test for parseMaps since I decided to just visually check that my global variable matched what I expected. That was the most difficult part of coding this out.

My tests:

describe("findClosestLocation", function() {
    test("returs the correct location", async function() {
        expect(await findClosestLocation(test_file)).toEqual(35);
    })
})

describe("mapSourceToDestination", function() {
    test("returns correct destination for given source", function() {
        const seed_to_soil = [[50, 98, 2], [52, 50, 48]];

        expect(mapSourceToDestination(seed_to_soil, 79)).toEqual(81);
        expect(mapSourceToDestination(seed_to_soil, 14)).toEqual(14);
        expect(mapSourceToDestination(seed_to_soil, 55)).toEqual(57);
        expect(mapSourceToDestination(seed_to_soil, 13)).toEqual(13);

    })
})

Solution for Part 1

let maps = [];

/** findClosestLocation: given a filepath, parse file data and return the integer
 * representing the closest location for planting the seeds in the file. 
 */
async function findClosestLocation(filepath){
    const file = await fs.readFile(filepath, "utf-8");

    // populate maps
    parseMaps(file);

    let locations = [];

    // map seed to location
    const seeds = maps[0][0];
    
    for (let seed of seeds) {
        let mapIdx = 1;
        let mapResult = seed;
        while (mapIdx < maps.length) {
            let source = mapResult;
            mapResult = mapSourceToDestination(maps[mapIdx], source)
            mapIdx++;
        }
        locations.push(mapResult);
    }

    return Math.min(...locations);
}

/** parseMaps: given a string of almanac info, parse file into separate map
 * objects and add each object to global maps
 */
function parseMaps(string) {
    const separate = string.split("\n\n");
    for (let map of separate){
        const newMap = [];

        // parse individual map into an object {name : {[range], [range],...}}
        map = map.split(":");
        let data = getLinesFromString(map[1]);

        // convert data to array, filter empty elements, convert strings to nums
        for (let d of data) {
            d = d.split(" ")
            d = d.filter(el => el !== "").map(num => Number(num));
            newMap.push(d);
        } 

        maps.push(newMap);
    }
}

/** mapSourceToDestination: given a map and a source, convert the source to a
 * destination. Return destination value. 
 */
function mapSourceToDestination(map, source) {

    for (let range of map) {
        const destinationStart = range[0];
        const sourceStart = range[1];
        const rangeLength = range[2];

        if (sourceStart <= source && source <= (sourceStart + rangeLength)) {
            return destinationStart + (source - sourceStart);
        }
    }

    return source;
}

Readability has suffered somewhat and variable names went by the wayside today, but I’m actually rather pleased with this solution.

Part 2 Tests and Solution

In Part 2, the initial seeds are actually ranges. They are in pairs of two with the first number as the start and the second as the number of seeds in that range.

This is an easy enough change if you do not care about runtimes.

I thought I did not. I thought, with the power of an Intel i5 chip, what care I for runtimes?

Unfortunately, I first went over the available memory for a JS array and then I got a RangeError. So I refactored a little, started incrementing by larger numbers since the ranges are so big, waited ten seconds, and…the answer was wrong.

None of the obvious things are wrong, but it’s almost midnight so I am going to have to come back to this tomorrow.

Today’s Takeaways

Nothing really. I’m just pleased I finally got to write a function where I really saw the impact of having a bad runtime. Also, I wished I had chosen to do this in Python.

Also, when I said five days ago that I was going to focus on writing readable and well documented code, that person is gone now. I hope never to come back to these problems, so it is little matter if I can easily read them in six month’s time.