diff --git a/Cargo.lock b/Cargo.lock index c10a016..8d4e714 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,64 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "autocfg" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "console" +version = "0.15.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c926e00cc70edefdc64d3a5ff31cc65bb97a3460097762bd23afb4d8145fccf8" +dependencies = [ + "encode_unicode", + "lazy_static", + "libc", + "unicode-width", + "windows-sys", +] + +[[package]] +name = "crossbeam-deque" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce6fd6f855243022dcecf8702fef0c297d4338e226845fe067f6341ad9fa0cef" +dependencies = [ + "cfg-if", + "crossbeam-epoch", + "crossbeam-utils", +] + +[[package]] +name = "crossbeam-epoch" +version = "0.9.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae211234986c545741a7dc064309f67ee1e5ad243d0e48335adc0484d960bcc7" +dependencies = [ + "autocfg", + "cfg-if", + "crossbeam-utils", + "memoffset", + "scopeguard", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a22b2d63d4d1dc0b7f1b6b2747dd0088008a9be28b6ddf0b1e7d335e3037294" +dependencies = [ + "cfg-if", +] + [[package]] name = "day_1" version = "1.0.0" @@ -17,3 +75,177 @@ version = "1.0.0" [[package]] name = "day_4" version = "1.0.0" + +[[package]] +name = "day_5" +version = "1.0.0" +dependencies = [ + "indicatif", + "rayon", +] + +[[package]] +name = "either" +version = "1.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a26ae43d7bcc3b814de94796a5e736d4029efb0ee900c12e2d54c993ad1a1e07" + +[[package]] +name = "encode_unicode" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f" + +[[package]] +name = "indicatif" +version = "0.17.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fb28741c9db9a713d93deb3bb9515c20788cef5815265bee4980e87bde7e0f25" +dependencies = [ + "console", + "instant", + "number_prefix", + "portable-atomic", + "rayon", + "unicode-width", +] + +[[package]] +name = "instant" +version = "0.1.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7a5bbe824c507c5da5956355e86a746d82e0e1464f65d862cc5e71da70e94b2c" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "lazy_static" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" + +[[package]] +name = "libc" +version = "0.2.150" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c" + +[[package]] +name = "memoffset" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a634b1c61a95585bd15607c6ab0c4e5b226e695ff2800ba0cdccddf208c406c" +dependencies = [ + "autocfg", +] + +[[package]] +name = "number_prefix" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830b246a0e5f20af87141b25c173cd1b609bd7779a4617d6ec582abaf90870f3" + +[[package]] +name = "portable-atomic" +version = "1.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7170ef9988bc169ba16dd36a7fa041e5c4cbeb6a35b76d4c03daded371eae7c0" + +[[package]] +name = "rayon" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c27db03db7734835b3f53954b534c91069375ce6ccaa2e065441e07d9b6cdb1" +dependencies = [ + "either", + "rayon-core", +] + +[[package]] +name = "rayon-core" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5ce3fb6ad83f861aac485e76e1985cd109d9a3713802152be56c3b1f0e0658ed" +dependencies = [ + "crossbeam-deque", + "crossbeam-utils", +] + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "unicode-width" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e51733f11c9c4f72aa0c160008246859e340b00807569a0da0e7a1079b27ba85" + +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" diff --git a/Cargo.toml b/Cargo.toml index 0617796..ae3adc0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,9 @@ [workspace] members = [ - "day_1", - "day_2", - "day_3", - "day_4", + "day_*", ] resolver = "2" + +[workspace.dependencies] +rayon = { version = "=1.8.0" } +indicatif = { version = "=0.17.7", features = ["rayon"] } diff --git a/day_5/.gitignore b/day_5/.gitignore new file mode 100644 index 0000000..ea8c4bf --- /dev/null +++ b/day_5/.gitignore @@ -0,0 +1 @@ +/target diff --git a/day_5/Cargo.toml b/day_5/Cargo.toml new file mode 100644 index 0000000..f27f981 --- /dev/null +++ b/day_5/Cargo.toml @@ -0,0 +1,8 @@ +[package] +name = "day_5" +version = "1.0.0" +edition = "2021" + +[dependencies] +rayon = { workspace = true } +indicatif = { workspace = true } diff --git a/day_5/README.md b/day_5/README.md new file mode 100644 index 0000000..07e8127 --- /dev/null +++ b/day_5/README.md @@ -0,0 +1,128 @@ +# - Day 5: If You Give A Seed A Fertilizer - + +Source: + +## Instructions - Part 1 + +You take the boat and find the gardener right where you were told he would be: managing a giant "garden" that looks more to you like a farm. + +"A water source? Island Island **is** the water source!" You point out that Snow Island isn't receiving any water. + +"Oh, we had to stop the water because we **ran out of sand** to [filter](https://en.wikipedia.org/wiki/Sand_filter) it with! Can't make snow with dirty water. Don't worry, I'm sure we'll get more sand soon; we only turned off the water a few days... weeks... oh no." His face sinks into a look of horrified realization. + +"I've been so busy making sure everyone here has food that I completely forgot to check why we stopped getting more sand! There's a ferry leaving soon that is headed over in that direction - it's much faster than your boat. Could you please go check it out?" + +You barely have time to agree to this request when he brings up another. "While you wait for the ferry, maybe you can help us with our **food production problem**. The latest Island Island [Almanac](https://en.wikipedia.org/wiki/Almanac) just arrived and we're having trouble making sense of it." + +The almanac (your puzzle input) lists all of the seeds that need to be planted. It also lists what type of soil to use with each kind of seed, what type of fertilizer to use with each kind of soil, what type of water to use with each kind of fertilizer, and so on. Every type of seed, soil, fertilizer and so on is identified with a number, but numbers are reused by each category - that is, soil `123` and fertilizer `123` aren't necessarily related to each other. + +For example: + +```txt +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 +``` + +The almanac starts by listing which seeds need to be planted: seeds `79`, `14`, `55`, and `13`. + +The rest of the almanac contains a list of **maps** which describe how to convert numbers from a **source category** into numbers in a **destination category**. That is, the section that starts with `seed-to-soil map:` describes how to convert a **seed number** (the source) to a **soil number** (the destination). This lets the gardener and his team know which soil to use with which seeds, which water to use with which fertilizer, and so on. + +Rather than list every source number and its corresponding destination number one by one, the maps describe entire **ranges** of numbers that can be converted. Each line within a map contains three numbers: the **destination range start**, the **source range start**, and the **range length**. + +Consider again the example seed-to-soil map: + +```txt +50 98 2 +52 50 48 +``` + +The first line has a **destination range start** of `50`, a **source range start** of `98`, and a **range length** of `2`. This line means that the source range starts at `98` and contains two values: `98` and `99`. The destination range is the same length, but it starts at `50`, so its two values are `50` and `51`. With this information, you know that seed number `98` corresponds to soil number `50` and that seed number `99` corresponds to soil number `51`. + +The second line means that the source range starts at `50` and contains `48` values: `50`, `51`, ..., `96`, `97`. This corresponds to a destination range starting at 52 and also containing `48` values: `52`, `53`, ..., `98`, `99`. So, seed number `53` corresponds to soil number `55`. + +Any source numbers that **aren't mapped** correspond to the same destination number. So, seed number `10` corresponds to soil number `10`. + +So, the entire list of seed numbers and their corresponding soil numbers looks like this: + +```txt +seed soil +0 0 +1 1 +... ... +48 48 +49 49 +50 52 +51 53 +... ... +96 98 +97 99 +98 50 +99 51 +``` + +With this map, you can look up the soil number required for each initial seed number: + +- Seed number `79` corresponds to soil number `81`. +- Seed number `14` corresponds to soil number `14`. +- Seed number `55` corresponds to soil number `57`. +- Seed number `13` corresponds to soil number `13`. + +The gardener and his team want to get started as soon as possible, so they'd like to know the closest location that needs a seed. Using these maps, find **the lowest location number that corresponds to any of the initial seeds**. To do this, you'll need to convert each seed number through other categories until you can find its corresponding **location number**. In this example, the corresponding types are: + +- Seed `79`, soil `81`, fertilizer `81`, water `81`, light `74`, temperature `78`, humidity `78`, **location `82`**. +- Seed `14`, soil `14`, fertilizer `53`, water `49`, light `42`, temperature `42`, humidity `43`, **location `43`**. +- Seed `55`, soil `57`, fertilizer `57`, water `53`, light `46`, temperature `82`, humidity `82`, **location `86`**. +- Seed `13`, soil `13`, fertilizer `52`, water `41`, light `34`, temperature `34`, humidity `35`, **location `35`**. + +So, the lowest location number in this example is **`35`**. + +**What is the lowest location number that corresponds to any of the initial seed numbers?** + +## Instructions - Part 2 + +Everyone will starve if you only plant such a small number of seeds. Re-reading the almanac, it looks like the `seeds:` line actually describes **ranges of seed numbers**. + +The values on the initial seeds: line come in pairs. Within each pair, the first value is the start of the range and the second value is the **length** of the range. So, in the first line of the example above: + +```txt +seeds: 79 14 55 13 +``` + +This line describes two ranges of seed numbers to be planted in the garden. The first range starts with seed number `79` and contains `14` values: `79`, `80`, ..., `91`, `92`. The second range starts with seed number `55` and contains `13` values: `55`, `56`, ..., `66`, `67`. + +Now, rather than considering four seed numbers, you need to consider a total of **27** seed numbers. + +In the above example, the lowest location number can be obtained from seed number `82`, which corresponds to soil `84`, fertilizer `84`, water `84`, light `77`, temperature `45`, humidity `46`, and location `46`. So, the lowest location number is **`46`**`. + +Consider all of the initial seed numbers listed in the ranges on the first line of the almanac. **What is the lowest location number that corresponds to any of the initial seed numbers?** diff --git a/day_5/input.txt b/day_5/input.txt new file mode 100644 index 0000000..1479dbc --- /dev/null +++ b/day_5/input.txt @@ -0,0 +1,221 @@ +seeds: 2019933646 2719986 2982244904 337763798 445440 255553492 1676917594 196488200 3863266382 36104375 1385433279 178385087 2169075746 171590090 572674563 5944769 835041333 194256900 664827176 42427020 + +seed-to-soil map: +3566547172 3725495029 569472267 +2346761246 1249510998 267846697 +1812605508 937956667 271194541 +1421378697 1209151208 40359790 +2083800049 2788751092 262961197 +2938601691 473979048 463977619 +473979048 1517357695 947399649 +4136019439 3566547172 158947857 +1461738487 3051712289 350867021 +2614607943 2464757344 323993748 + +soil-to-fertilizer map: +3107230831 2583931429 576709409 +970181981 608291332 1441137369 +743954495 3859046283 158951815 +3683940240 3227916509 91282070 +608291332 2448268266 135663163 +3775222310 2049428701 398839565 +2411319350 3319198579 539847704 +2951167054 4017998098 156063777 +902906310 3160640838 67275671 + +fertilizer-to-water map: +1257642402 395703749 69589612 +1800674 2215701547 90550534 +2757853693 358464863 37238886 +3285451399 181079109 43937782 +2346544130 3513448371 192150886 +3866348216 4231433060 63534236 +1327232014 1560332334 90281838 +2538695016 616206288 114467702 +255018176 225016891 46372244 +1171065990 3705599257 27021880 +1070753744 730673990 442780 +221369008 3479799203 33649168 +2987721226 271389135 80072982 +1198087870 732917444 24556356 +199036270 2306252081 22332738 +0 731116770 1800674 +3929882452 3989920675 212758268 +631506549 757473800 322942578 +301390420 0 157952443 +2795092579 157952443 1997721 +1222644226 2619085211 34998176 +954449127 499901671 116304617 +1429766246 159950164 21128945 +2205492221 2074649638 141051909 +2749302577 3732621137 8551116 +459342863 1219863414 57737723 +3329389181 3741172253 59047790 +2797090300 2328584819 190630926 +3278448653 351462117 7002746 +126959518 1277601137 72076752 +92351208 465293361 34608310 +4142640720 3866348216 123572459 +2143980560 1080416378 43307177 +1450895191 2672287871 693085369 +517080586 3365373240 114425963 +3388436971 1662866566 411783072 +2187287737 2654083387 18204484 +1417513852 1650614172 12252394 +3067794208 1349677889 210654445 +4266213179 4202678943 28754117 +2653162718 1123723555 96139859 +1071196524 2519215745 99869466 + +water-to-light map: +512627839 90187036 1196629 +3379634653 2059506154 33434334 +3286651054 4276482087 18485209 +4233695090 28914830 61272206 +3413068987 3322576776 23288997 +3736304424 3345865773 43267308 +1246285471 2994853001 251748584 +3779571732 1946298040 113208114 +390808412 3287769466 34807310 +1881283842 2879009693 106527924 +3964031050 2506138169 12994476 +3436357984 793897944 162691614 +2255160753 2092940488 151061610 +853985057 3506201042 119010035 +301385394 1856875022 89423018 +972995092 658665705 34308693 +4159948022 1315925500 65322692 +640912738 250463411 213072319 +1761800914 91383665 102591221 +450345319 3246601585 5793995 +3186220306 4173678310 91115364 +28914830 3633635453 176360375 +456139314 193974886 56488525 +2523290324 3809995828 187303152 +2406222363 3389133081 117067961 +205275205 2782899504 96110189 +2135785589 1100569535 119375164 +1121466033 533846267 124819438 +1007303785 2244002098 114162248 +3599049598 3997298980 137254826 +4077949072 463535730 70310537 +4225270714 3625211077 8424376 +1498034055 2519132645 263766859 +2710593476 1381248192 475626830 +3977025526 692974398 100923546 +4148259609 4264793674 11688413 +1987811766 2358164346 147973823 +3892779846 1244674296 71251204 +3340510149 4134553806 39124504 +1864392135 956589558 16891707 +425615722 1219944699 24729597 +513824468 973481265 127088270 +3277335670 2985537617 9315384 +3305136263 3252395580 35373886 + +light-to-temperature map: +1094191559 698410082 28110394 +383870732 1189042355 107231661 +3711052230 2164474756 34756304 +745558539 170241759 7170863 +491102393 503970250 194439832 +4034618875 3142749029 146609939 +3781998432 1718948669 129329785 +2440091414 3071819711 70929318 +1301358031 55123603 115118156 +0 2789116652 87933685 +770729148 177412622 48955790 +3772681560 3886204605 9316872 +752729402 37123857 17999746 +3745808534 2137385460 7147939 +2028807236 3677936618 208267987 +2237075223 3289358968 92979022 +88764920 1960439220 176946240 +3568470355 2258695303 142581875 +3276170082 1848278454 112160766 +2637902204 1129503077 39814191 +3000547589 892603630 188042422 +2511020732 226368412 126881472 +1122301953 1296274016 52818372 +1440958847 1353023078 243104929 +2963423732 0 37123857 +3388330848 2199231060 48304954 +1175120325 377732544 126237706 +819684938 1349092388 3930690 +3752956473 1169317268 19725087 +3911328217 2144533399 19941357 +1416476187 353249884 24482660 +2677716395 3895521477 285707337 +265711160 2413138935 118159572 +685542225 1080646052 48857025 +3556608598 2401277178 11861757 +734399250 2247536014 11159289 +87933685 3677105383 831235 +3188590011 1596128007 87580071 +836373414 2531298507 257818145 +3471876393 2877050337 84732205 +1684063776 726520476 166083154 +823615628 3560998296 12757786 +3436635802 1683708078 35240591 +3931269574 3573756082 103349301 +1850146930 3382337990 178660306 +2330054245 2961782542 110037169 + +temperature-to-humidity map: +1773059646 4122818507 172148789 +2417158855 2859734866 110076859 +977168274 1576624124 28149321 +4275291678 3797606290 19675618 +1141296808 749646180 267286171 +3592756112 2969811725 273274339 +0 19621130 7167651 +2059084943 2697725300 48133058 +2107218001 3920609496 145140777 +1453481278 1152911564 151292167 +1408582979 1465584228 44898299 +7167651 0 19621130 +2907567891 1829621431 240604380 +2252358778 3652347291 145258999 +1005317595 1016932351 135979213 +1945208435 2745858358 113876508 +2397617777 4065750273 16506015 +3251499859 1776094709 53526722 +2867005672 4082256288 40562219 +26788781 1304203731 161380497 +3305026581 2409995769 287729531 +3866030451 3243086064 409261227 +2414123792 1773059646 3035063 +911026677 1510482527 66141597 +3148172271 3817281908 103327588 +2527235714 2070225811 339769958 +188169278 26788781 722857399 + +humidity-to-location map: +3907319746 3137303541 31421983 +3085093695 1018495475 286155292 +2898003508 2491485887 87665522 +2546787368 2901838353 7997221 +3835317650 2829836257 72002096 +2554784589 3509894030 133012322 +3487595595 3719561871 104747874 +3714670750 2667334372 120646900 +975094571 2909835574 227467967 +2985669030 3864000834 99424665 +3672962118 2449777255 41708632 +3631107133 2787981272 41854985 +3938741729 3963425499 15057061 +3447904506 3824309745 39691089 +1824175159 1304650767 641793976 +242892183 0 6504921 +3371248987 3642906352 76655519 +1698833898 2258930589 81940357 +0 6504921 242892183 +2465969135 3978482560 80818233 +3592343469 4256203632 38763664 +3953798790 3168725524 341168506 +2775979874 4134179998 122023634 +1780774255 975094571 43400904 +1311468847 1946444743 312485846 +2687796911 2579151409 88182963 +1202562538 2340870946 108906309 +1623954693 4059300793 74879205 diff --git a/day_5/input_example_1.txt b/day_5/input_example_1.txt new file mode 100644 index 0000000..f756727 --- /dev/null +++ b/day_5/input_example_1.txt @@ -0,0 +1,33 @@ +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 diff --git a/day_5/src/lib.rs b/day_5/src/lib.rs new file mode 100644 index 0000000..9d1852a --- /dev/null +++ b/day_5/src/lib.rs @@ -0,0 +1,276 @@ +use indicatif::ParallelProgressIterator; +use rayon::prelude::*; +use std::ops::Range; +use std::str::FromStr; + +#[derive(Debug, Default, PartialEq, Clone)] +pub struct RangeConverter { + pub source_range: Range, + pub destination_range: Range, +} + +impl FromStr for RangeConverter { + type Err = &'static str; + + /// Parses a string `string` to return a value of [`RangeConverter`] + /// + /// If parsing succeeds, return the value inside [`Ok`], otherwise + /// when the string is ill-formatted return an error specific to the + /// inside [`Err`]. + /// + /// # Examples + /// + /// ``` + /// use std::str::FromStr; + /// use day_5::RangeConverter; + /// + /// let string = "50 98 2 "; + /// let expected_result = RangeConverter { + /// source_range: 98..100, + /// destination_range: 50..52, + /// }; + /// let actual_result = RangeConverter::from_str(string).unwrap(); + /// + /// assert_eq!(actual_result, expected_result); + /// ``` + fn from_str(string: &str) -> Result { + let numbers = string + .trim() + .split_ascii_whitespace() + .map(|string| { + let result: usize = string.trim().parse().unwrap_or_default(); + result + }) + .collect::>(); + let destination_range_start = *numbers.first().unwrap_or(&0); + let source_range_start = *numbers.get(1).unwrap_or(&0); + let range_length = *numbers.get(2).unwrap_or(&0); + let result = RangeConverter { + source_range: source_range_start..(source_range_start + range_length), + destination_range: destination_range_start..(destination_range_start + range_length), + }; + Ok(result) + } +} + +#[derive(Debug, Default, PartialEq, Clone)] +pub struct CategoryConverter { + pub name: String, + pub ranges_converters: Vec, +} + +impl FromStr for CategoryConverter { + type Err = &'static str; + + /// Parses a string `string` to return a value of [`CategoryConverter`] + /// + /// If parsing succeeds, return the value inside [`Ok`], otherwise + /// when the string is ill-formatted return an error specific to the + /// inside [`Err`]. + /// + /// # Examples + /// + /// ``` + /// use std::str::FromStr; + /// use day_5::{CategoryConverter, RangeConverter}; + /// + /// let string = " + /// seed-to-soil map: + /// 50 98 2 + /// 52 50 48 + /// "; + /// let expected_result = CategoryConverter { + /// name: String::from("seed-to-soil map:"), + /// ranges_converters: vec![ + /// RangeConverter { + /// source_range: 98..100, + /// destination_range: 50..52, + /// }, + /// RangeConverter { + /// source_range: 50..98, + /// destination_range: 52..100, + /// }, + /// ], + /// }; + /// let actual_result = CategoryConverter::from_str(string).unwrap(); + /// + /// assert_eq!(actual_result, expected_result); + /// ``` + fn from_str(string: &str) -> Result { + let mut lines = string.trim().lines(); + let name = lines.next().unwrap_or_default(); + let mut ranges_converters = vec![]; + for line in lines { + ranges_converters.push(RangeConverter::from_str(line).unwrap_or_default()); + } + let result = CategoryConverter { + name: String::from(name), + ranges_converters, + }; + Ok(result) + } +} + +impl CategoryConverter { + pub fn convert(&self, number: usize) -> usize { + self.ranges_converters + .iter() + .find_map(|range_converter| { + if range_converter.source_range.contains(&number) { + Some( + range_converter.destination_range.start + + (number - range_converter.source_range.start), + ) + } else { + None + } + }) + .unwrap_or(number) + } +} + +#[derive(Debug, Default, PartialEq, Clone)] +pub struct Almanac { + pub seeds: Vec, + pub categories_converters: Vec, +} + +impl FromStr for Almanac { + type Err = &'static str; + + /// Parses a string `string` to return a value of [`Almanac`] + /// + /// If parsing succeeds, return the value inside [`Ok`], otherwise + /// when the string is ill-formatted return an error specific to the + /// inside [`Err`]. + /// + /// # Examples + /// + /// ``` + /// use std::str::FromStr; + /// use day_5::{Almanac, CategoryConverter, RangeConverter}; + /// + /// let string = " + /// seeds: 79 14 55 13 + /// + /// seed-to-soil map: + /// 50 98 2 + /// 52 50 48 + /// "; + /// let expected_result = Almanac { + /// seeds: vec![79, 14, 55, 13], + /// categories_converters: vec![CategoryConverter { + /// name: String::from("seed-to-soil map:"), + /// ranges_converters: vec![ + /// RangeConverter { + /// source_range: 98..100, + /// destination_range: 50..52, + /// }, + /// RangeConverter { + /// source_range: 50..98, + /// destination_range: 52..100, + /// }, + /// ], + /// }], + /// }; + /// let actual_result = Almanac::from_str(string).unwrap(); + /// + /// assert_eq!(actual_result, expected_result); + /// ``` + fn from_str(string: &str) -> Result { + let mut categories = string.trim().split("\n\n"); + let seeds = categories + .next() + .unwrap_or_default() + .strip_prefix("seeds: ") + .unwrap_or_default() + .split_ascii_whitespace() + .map(|string| { + let result: usize = string.trim().parse().unwrap_or_default(); + result + }) + .collect::>(); + let categories_converters = categories + .map(|category_string| CategoryConverter::from_str(category_string).unwrap_or_default()) + .collect::>(); + let result = Almanac { + seeds, + categories_converters, + }; + Ok(result) + } +} + +impl Almanac { + pub fn minimum_location(&self) -> usize { + self.seeds + .par_iter() + .map(|&seed| { + self.categories_converters + .iter() + .fold(seed, |accumulator, category_converter| { + category_converter.convert(accumulator) + }) + }) + .progress() + .min() + .unwrap_or_default() + } + + pub fn set_seeds_as_range_pairs(&mut self) { + self.seeds = self + .seeds + .par_chunks(2) + .progress() + .flat_map(|chunk| { + if let [range_start, range_length] = chunk { + let start = *range_start; + let length = *range_length; + let range = start..(start + length); + range.into_iter() + } else { + let empty_range = 0..0; + empty_range.into_iter() + } + }) + .collect(); + } +} + +pub fn part_1(input: &str) -> usize { + let almanac = Almanac::from_str(input).unwrap_or_default(); + almanac.minimum_location() +} + +pub fn part_2(input: &str) -> usize { + let mut almanac = Almanac::from_str(input).unwrap_or_default(); + almanac.set_seeds_as_range_pairs(); + almanac.minimum_location() +} + +#[cfg(test)] +mod day_5_tests { + use super::*; + + #[test] + fn test_part_1_example() { + assert_eq!(part_1(include_str!("../input_example_1.txt")), 35); + } + + #[test] + fn test_part_2_example() { + assert_eq!(part_2(include_str!("../input_example_1.txt")), 46); + } + + #[test] + fn test_part_1() { + assert_eq!(part_1(include_str!("../input.txt")), 313045984); + } + + #[test] + #[ignore] + /// Ignored because it is a expensive/slow test to run. + fn test_part_2() { + assert_eq!(part_2(include_str!("../input.txt")), 20283860); + } +} diff --git a/day_5/src/main.rs b/day_5/src/main.rs new file mode 100644 index 0000000..e454f5c --- /dev/null +++ b/day_5/src/main.rs @@ -0,0 +1,8 @@ +use day_5::{part_1, part_2}; + +fn main() { + let input = include_str!("../input.txt"); + println!("- Day 5: If You Give A Seed A Fertilizer -"); + println!("Answer Part 1: {}", part_1(input)); + println!("Answer Part 2: {}", part_2(input)); +}