import { ExtractNumbers, Inspect, LoadInput } from "../common.ts"; /** * While this script does work for the test case, the challenge input * is too large, and causes the JS engine to run out of memory. */ const tests = [ "seeds: 79 14 55 13", "", "seed-to-soil map:", "50 98 2", "52 50 48", "", "soil-to-fertilizer map:", "0 15 37", "37 52 2", "39 0 15", "", "fertilizer-to-water map:", "49 53 8", "0 11 42", "42 0 7", "57 7 4", "", "water-to-light map:", "88 18 7", "18 25 70", "", "light-to-temperature map:", "45 77 23", "81 45 19", "68 64 13", "", "temperature-to-humidity map:", "0 69 1", "1 0 69", "", "humidity-to-location map:", "60 56 37", "56 93 4", ]; const input = await LoadInput(5); // Parse the input const maps = ParseInput(input); // Map the seeds const seeds = maps.Seeds.map((seed) => MapSeed(seed.ID, maps)); // Get the output let closestLocationID = Number.MAX_SAFE_INTEGER; if (isSeedMapArray(seeds)) { seeds.forEach((seed) => { if (seed.LocationID < closestLocationID) { closestLocationID = seed.LocationID; } }); } console.log(`The lowest location ID found is: ${closestLocationID}`); function ParseInput(almanac: string[]): RangeMaps { /** An empty initializer for our output */ const output: RangeMaps = { Seeds: [], SeedToSoil: [], SoilToFertilizer: [], FertilizerToWater: [], WaterToLight: [], LightToTemperature: [], TemperatureToHumidity: [], HumidityToLocation: [], }; for (let i = 0; i < almanac.length; i++) { let line = almanac[i]; // Parse the seed ID's if (/^seeds:/.test(line)) { // Extract all numbers from the line const seedIDs = ExtractNumbers(line); // Add to the output's Seeds array a new Seed for each number seedIDs.forEach((id) => { output.Seeds.push({ID: id}); }); // Skip the next blank i++; } // Parse the Seed to Soil ranges else if (/^seed\-to/.test(line)) { output.SeedToSoil = ParseSourceDestinationRange(++i, almanac); } // Parse the Soil to Fertilizer ranges else if (/^soil\-to/.test(line)) { output.SoilToFertilizer = ParseSourceDestinationRange(++i, almanac); } // Parse the Fertilizer to Water ranges else if (/^fertilizer\-to/.test(line)) { output.FertilizerToWater = ParseSourceDestinationRange(++i, almanac); } // Parse the Water to Light ranges else if (/^water\-to/.test(line)) { output.WaterToLight = ParseSourceDestinationRange(++i, almanac); } // Parse the Light to Temperature ranges else if (/^light\-to/.test(line)) { output.LightToTemperature = ParseSourceDestinationRange(++i, almanac); } // Parse the Temperature to Humidity ranges else if (/^temperature\-to/.test(line)) { output.TemperatureToHumidity = ParseSourceDestinationRange(++i, almanac); } // Parse the Humidity to Location ranges else if (/^humidity\-to/.test(line)) { output.HumidityToLocation = ParseSourceDestinationRange(++i, almanac); } } return output; } /** * Helper function to parse the ranges for each mappable section * * @param {number} lineNumber The line number to begin parsing from * @param {string[]} almanac The complete almanac * @returns {MappableObject[]} A list of mapped indices */ function ParseSourceDestinationRange(lineNumber: number, almanac: string[]): MappableObject[] { const rangeMap: MappableObject[] = []; do { // Get the values out of the line const [destinationRangeStart, sourceRangeStart, rangeLength] = ExtractNumbers(almanac[lineNumber]); // Create the ranges for (let idx = 0; idx < rangeLength; idx++) { rangeMap.push({ ID: sourceRangeStart + idx, PointsTo: destinationRangeStart + idx, }); } } while(almanac[++lineNumber]); return rangeMap; } /** * Find an IdentifiableObject by its ID number * * @param needle The ID number to find * @param haystack The list to find the ID in * @returns {IdentifiableObject|undefined} The object by that ID if found, or undefined */ function FindObjectByID(needle: number, haystack: IdentifiableObject[]|MappableObject[]): IdentifiableObject|MappableObject|undefined { return haystack.find((obj) => obj.ID == needle); } /** * Map a Seed ID to all of its other attributes * * Given a seed ID, finds its soil type, fertilizer type, water type, * light type, temperature type, humidity type, and location ID. * * @param {number} seedID * @param {RangeMaps} rangeMaps A parsed map of ranges * @returns */ function MapSeed(seedID: number, rangeMaps: RangeMaps): SeedMap|undefined { // Make sure the seed with that ID exists before continueing if(!FindObjectByID(seedID, rangeMaps.Seeds)) { return undefined; } // Initialize our seed map object const seed: SeedMap = { ID: seedID, SoilID: 0, FertilizerID: 0, WaterID: 0, LightID: 0, TemperatureID: 0, HumidityID: 0, LocationID: 0, }; seed.SoilID = (FindObjectByID(seedID, rangeMaps.SeedToSoil) as MappableObject)?.PointsTo || seedID; seed.FertilizerID = (FindObjectByID(seed.SoilID, rangeMaps.SoilToFertilizer) as MappableObject)?.PointsTo || seed.SoilID; seed.WaterID = (FindObjectByID(seed.FertilizerID, rangeMaps.FertilizerToWater) as MappableObject)?.PointsTo || seed.FertilizerID; seed.LightID = (FindObjectByID(seed.WaterID, rangeMaps.WaterToLight) as MappableObject)?.PointsTo || seed.WaterID; seed.TemperatureID = (FindObjectByID(seed.LightID, rangeMaps.LightToTemperature) as MappableObject)?.PointsTo|| seed.LightID; seed.HumidityID = (FindObjectByID(seed.TemperatureID, rangeMaps.TemperatureToHumidity) as MappableObject)?.PointsTo || seed.TemperatureID; seed.LocationID = (FindObjectByID(seed.HumidityID, rangeMaps.HumidityToLocation) as MappableObject)?.PointsTo || seed.HumidityID; return seed; } /** * Type guard function to ensure an array is an array of SeedMaps * * @param {any[]} valueArray The array to check * @returns {boolean} Whether the input is an array of SeedMaps or not */ function isSeedMapArray(valueArray: any[]): valueArray is SeedMap[] { const value = valueArray.shift(); if (!value || typeof value !== "object") { return false; } return Object.hasOwn(value, "ID") && Object.hasOwn(value, "SoilID") && Object.hasOwn(value, "FertilizerID") && Object.hasOwn(value, "WaterID") && Object.hasOwn(value, "LightID") && Object.hasOwn(value, "TemperatureID") && Object.hasOwn(value, "HumidityID") && Object.hasOwn(value, "LocationID"); } /** Any object with a unique identifier */ interface IdentifiableObject { /** The ID number for this object */ ID: number, }; /** A seed */ interface Seed extends IdentifiableObject {}; /** An IdentifiableObject that points to another IdentifiableObject */ interface MappableObject extends IdentifiableObject { /** The ID that this object points to */ PointsTo: number, }; /** A complete map of all object IDs and where they point to */ type RangeMaps = { /** The list of seed IDs */ Seeds: Seed[], /** The map of seed to soil IDs */ SeedToSoil: MappableObject[], /** The map of soil to fertilizer IDs */ SoilToFertilizer: MappableObject[], /** The map of fertilizer to water IDs */ FertilizerToWater: MappableObject[], /** The map of water to light IDs */ WaterToLight: MappableObject[], /** The map of light to temperature IDs */ LightToTemperature: MappableObject[], /** The map of temperature to humidity IDs */ TemperatureToHumidity: MappableObject[], /** The map of humidity to location IDs */ HumidityToLocation: MappableObject[], } /** A completed map of a seed's property IDs */ type SeedMap = { /** The ID of the seed */ ID: number, /** The ID of the soil type the seed needs planted in */ SoilID: number, /** The ID of the fertilizer type the soil needs */ FertilizerID: number, /** The ID of the water type the fertilizer needs */ WaterID: number, /** The ID of the light type the water needs */ LightID: number, /** The ID of the temperature type the light needs */ TemperatureID: number, /** The ID of the humidity type the temperature needs */ HumidityID: number, /** The ID of the location the humidity needs */ LocationID: number, }