5_1.ts 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270
  1. import { ExtractNumbers, Inspect, LoadInput } from "../common.ts";
  2. const tests = [
  3. "seeds: 79 14 55 13",
  4. "",
  5. "seed-to-soil map:",
  6. "50 98 2",
  7. "52 50 48",
  8. "",
  9. "soil-to-fertilizer map:",
  10. "0 15 37",
  11. "37 52 2",
  12. "39 0 15",
  13. "",
  14. "fertilizer-to-water map:",
  15. "49 53 8",
  16. "0 11 42",
  17. "42 0 7",
  18. "57 7 4",
  19. "",
  20. "water-to-light map:",
  21. "88 18 7",
  22. "18 25 70",
  23. "",
  24. "light-to-temperature map:",
  25. "45 77 23",
  26. "81 45 19",
  27. "68 64 13",
  28. "",
  29. "temperature-to-humidity map:",
  30. "0 69 1",
  31. "1 0 69",
  32. "",
  33. "humidity-to-location map:",
  34. "60 56 37",
  35. "56 93 4",
  36. ];
  37. const input = await LoadInput(5);
  38. // Parse the input
  39. const maps = ParseInput(input);
  40. // Map the seeds
  41. const seeds = maps.Seeds.map((seed) => MapSeed(seed, maps));
  42. // Get the output
  43. let closestLocationID = Number.MAX_SAFE_INTEGER;
  44. if (isSeedMapArray(seeds)) {
  45. seeds.forEach((seed) => {
  46. if (seed.LocationID < closestLocationID) { closestLocationID = seed.LocationID; }
  47. });
  48. }
  49. console.log(`The lowest location ID found is: ${closestLocationID}`);
  50. function ParseInput(almanac: string[]): RangeMaps {
  51. /** An empty initializer for our output */
  52. const output: RangeMaps = {
  53. Seeds: [],
  54. SeedToSoil: [],
  55. SoilToFertilizer: [],
  56. FertilizerToWater: [],
  57. WaterToLight: [],
  58. LightToTemperature: [],
  59. TemperatureToHumidity: [],
  60. HumidityToLocation: [],
  61. };
  62. for (let i = 0; i < almanac.length; i++) {
  63. let line = almanac[i];
  64. // Parse the seed ID's
  65. if (/^seeds:/.test(line)) {
  66. // Extract all numbers from the line
  67. const seedIDs = ExtractNumbers(line);
  68. // Add to the output's Seeds array a new Seed for each number
  69. seedIDs.forEach((id) => { output.Seeds.push(id); });
  70. // Skip the next blank
  71. i++;
  72. }
  73. // Parse the Seed to Soil ranges
  74. else if (/^seed\-to/.test(line)) {
  75. output.SeedToSoil = ParseSourceDestinationRange(++i, almanac);
  76. }
  77. // Parse the Soil to Fertilizer ranges
  78. else if (/^soil\-to/.test(line)) {
  79. output.SoilToFertilizer = ParseSourceDestinationRange(++i, almanac);
  80. }
  81. // Parse the Fertilizer to Water ranges
  82. else if (/^fertilizer\-to/.test(line)) {
  83. output.FertilizerToWater = ParseSourceDestinationRange(++i, almanac);
  84. }
  85. // Parse the Water to Light ranges
  86. else if (/^water\-to/.test(line)) {
  87. output.WaterToLight = ParseSourceDestinationRange(++i, almanac);
  88. }
  89. // Parse the Light to Temperature ranges
  90. else if (/^light\-to/.test(line)) {
  91. output.LightToTemperature = ParseSourceDestinationRange(++i, almanac);
  92. }
  93. // Parse the Temperature to Humidity ranges
  94. else if (/^temperature\-to/.test(line)) {
  95. output.TemperatureToHumidity = ParseSourceDestinationRange(++i, almanac);
  96. }
  97. // Parse the Humidity to Location ranges
  98. else if (/^humidity\-to/.test(line)) {
  99. output.HumidityToLocation = ParseSourceDestinationRange(++i, almanac);
  100. }
  101. }
  102. return output;
  103. }
  104. /**
  105. * Helper function to parse the ranges for each mappable section
  106. *
  107. * @param {number} lineNumber The line number to begin parsing from
  108. * @param {string[]} almanac The complete almanac
  109. * @returns {RangeMap[]} A list of mapped indices
  110. */
  111. function ParseSourceDestinationRange(lineNumber: number, almanac: string[]): RangeMap[] {
  112. const rangeMap: RangeMap[] = [];
  113. do {
  114. // Get the values out of the line
  115. const [destinationRangeStart, sourceRangeStart, rangeLength] = ExtractNumbers(almanac[lineNumber]);
  116. rangeMap.push({
  117. SourceRanges: {
  118. Start: sourceRangeStart,
  119. End: sourceRangeStart + rangeLength - 1,
  120. },
  121. DestinationRange: {
  122. Start: destinationRangeStart,
  123. End: destinationRangeStart + rangeLength - 1,
  124. }
  125. });
  126. } while(almanac[++lineNumber]);
  127. return rangeMap;
  128. }
  129. /**
  130. * Maps one object to another based on ID ranges
  131. *
  132. * If the ID given doesn't fit in any of the ranges given, then the
  133. * input is returned
  134. *
  135. * @param {number} needle The ID number to find
  136. * @param {RangeMap[]} haystack The list of ranges to find the ID in
  137. * @returns {number} The ID of the mapped object
  138. */
  139. function MapObjectByID(needle: number, haystack: RangeMap[]): number {
  140. for (const range of haystack) {
  141. if (range.SourceRanges.Start <= needle && range.SourceRanges.End >= needle) {
  142. return range.DestinationRange.Start + (needle - range.SourceRanges.Start);
  143. }
  144. }
  145. return needle;
  146. }
  147. /**
  148. * Map a Seed ID to all of its other attributes
  149. *
  150. * Given a seed ID, finds its soil type, fertilizer type, water type,
  151. * light type, temperature type, humidity type, and location ID.
  152. *
  153. * @param {number} seedID
  154. * @param {RangeMaps} rangeMaps A parsed map of ranges
  155. * @returns
  156. */
  157. function MapSeed(seedID: number, rangeMaps: RangeMaps): SeedMap|undefined {
  158. // Make sure the seed with that ID exists before continueing
  159. if(!rangeMaps.Seeds.includes(seedID)) { return undefined; }
  160. // Initialize our seed map object
  161. const seed: SeedMap = {
  162. ID: seedID,
  163. SoilID: 0,
  164. FertilizerID: 0,
  165. WaterID: 0,
  166. LightID: 0,
  167. TemperatureID: 0,
  168. HumidityID: 0,
  169. LocationID: 0,
  170. };
  171. seed.SoilID = MapObjectByID(seedID, rangeMaps.SeedToSoil);
  172. seed.FertilizerID = MapObjectByID(seed.SoilID, rangeMaps.SoilToFertilizer);
  173. seed.WaterID = MapObjectByID(seed.FertilizerID, rangeMaps.FertilizerToWater);
  174. seed.LightID = MapObjectByID(seed.WaterID, rangeMaps.WaterToLight);
  175. seed.TemperatureID = MapObjectByID(seed.LightID, rangeMaps.LightToTemperature);
  176. seed.HumidityID = MapObjectByID(seed.TemperatureID, rangeMaps.TemperatureToHumidity);
  177. seed.LocationID = MapObjectByID(seed.HumidityID, rangeMaps.HumidityToLocation);
  178. return seed;
  179. }
  180. /**
  181. * Type guard function to ensure an array is an array of SeedMaps
  182. *
  183. * @param {any[]} valueArray The array to check
  184. * @returns {boolean} Whether the input is an array of SeedMaps or not
  185. */
  186. function isSeedMapArray(valueArray: any[]): valueArray is SeedMap[] {
  187. const value = valueArray.shift();
  188. if (!value || typeof value !== "object") { return false; }
  189. return Object.hasOwn(value, "ID")
  190. && Object.hasOwn(value, "SoilID")
  191. && Object.hasOwn(value, "FertilizerID")
  192. && Object.hasOwn(value, "WaterID")
  193. && Object.hasOwn(value, "LightID")
  194. && Object.hasOwn(value, "TemperatureID")
  195. && Object.hasOwn(value, "HumidityID")
  196. && Object.hasOwn(value, "LocationID");
  197. }
  198. type IDRange = {
  199. Start: number,
  200. End: number,
  201. };
  202. /** An IdentifiableObject that points to another IdentifiableObject */
  203. type RangeMap = {
  204. /** The ID that this object points to */
  205. SourceRanges: IDRange,
  206. DestinationRange: IDRange,
  207. };
  208. /** A complete map of all object IDs and where they point to */
  209. type RangeMaps = {
  210. /** The list of seed IDs */
  211. Seeds: number[],
  212. /** The map of seed to soil IDs */
  213. SeedToSoil: RangeMap[],
  214. /** The map of soil to fertilizer IDs */
  215. SoilToFertilizer: RangeMap[],
  216. /** The map of fertilizer to water IDs */
  217. FertilizerToWater: RangeMap[],
  218. /** The map of water to light IDs */
  219. WaterToLight: RangeMap[],
  220. /** The map of light to temperature IDs */
  221. LightToTemperature: RangeMap[],
  222. /** The map of temperature to humidity IDs */
  223. TemperatureToHumidity: RangeMap[],
  224. /** The map of humidity to location IDs */
  225. HumidityToLocation: RangeMap[],
  226. }
  227. /** A completed map of a seed's property IDs */
  228. type SeedMap = {
  229. /** The ID of the seed */
  230. ID: number,
  231. /** The ID of the soil type the seed needs planted in */
  232. SoilID: number,
  233. /** The ID of the fertilizer type the soil needs */
  234. FertilizerID: number,
  235. /** The ID of the water type the fertilizer needs */
  236. WaterID: number,
  237. /** The ID of the light type the water needs */
  238. LightID: number,
  239. /** The ID of the temperature type the light needs */
  240. TemperatureID: number,
  241. /** The ID of the humidity type the temperature needs */
  242. HumidityID: number,
  243. /** The ID of the location the humidity needs */
  244. LocationID: number,
  245. }