|
@@ -0,0 +1,267 @@
|
|
|
+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,
|
|
|
+}
|