diff --git a/.github/workflows/run_tests.yml b/.github/workflows/run_tests.yml index db7dca3..ccae114 100644 --- a/.github/workflows/run_tests.yml +++ b/.github/workflows/run_tests.yml @@ -54,11 +54,22 @@ jobs: if: ${{ matrix.language == 'python'}} run: python -m unittest discover -t . -s ${{ matrix.folder }} -p "*.py" + - name: Setup Z3 + if: ${{ matrix.folder == 'year_2023' }} + id: z3 + uses: cda-tum/setup-z3@v1 + with: + add_to_library_path: true + version: 4.11.2 + - name: Setup Rust if: ${{ matrix.language == 'rust' }} - uses: moonrepo/setup-rust@v1 + uses: dtolnay/rust-toolchain@stable with: - channel: ${{ matrix.version }} + toolchain: ${{ matrix.version }} + - name: Setup Cache + if: ${{ matrix.language == 'rust' }} + uses: Swatinem/rust-cache@v2 - name: Build if: ${{ matrix.language == 'rust' }} working-directory: ${{ matrix.folder }} diff --git a/year_2023/Cargo.lock b/year_2023/Cargo.lock index 7bf5ac0..950a512 100644 --- a/year_2023/Cargo.lock +++ b/year_2023/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "aho-corasick" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" +dependencies = [ + "memchr", +] + [[package]] name = "aoc_utils" version = "0.1.0" @@ -15,6 +24,120 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" +[[package]] +name = "bindgen" +version = "0.66.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f2b84e06fc203107bfbad243f4aba2af864eb7db3b1cf46ea0a023b0b433d2a7" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn", +] + +[[package]] +name = "bitflags" +version = "2.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", +] + +[[package]] +name = "cfg-if" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" + +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + +[[package]] +name = "glob" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" + +[[package]] +name = "lazy_static" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" + +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + +[[package]] +name = "libc" +version = "0.2.168" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5aaeb2981e0606ca11d79718f8bb01164f1d6ed75080182d3abf017e6d244b6d" + +[[package]] +name = "libloading" +version = "0.8.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc2f4eb4bc735547cfed7c0a4922cbd04a4655978c09b54f1f7b228750664c34" +dependencies = [ + "cfg-if", + "windows-targets", +] + +[[package]] +name = "log" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" + +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num-traits" version = "0.2.19" @@ -24,9 +147,175 @@ dependencies = [ "autocfg", ] +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + +[[package]] +name = "proc-macro2" +version = "1.0.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "quote" +version = "1.0.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "regex" +version = "1.11.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" + +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + +[[package]] +name = "syn" +version = "2.0.90" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "919d3b74a5dd0ccd15aeb8f93e7006bd9e14c295087c9896a110f490752bcf31" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "unicode-ident" +version = "1.0.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "adb9e6ca4f869e1180728b7950e35922a7fc6397f7b641499e8f3ef06e50dc83" + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_gnullvm", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + [[package]] name = "year_2023" version = "0.1.0" dependencies = [ "aoc_utils", + "z3", +] + +[[package]] +name = "z3" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a7ff5718c079e7b813378d67a5bed32ccc2086f151d6185074a7e24f4a565e8" +dependencies = [ + "log", + "z3-sys", +] + +[[package]] +name = "z3-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7cf70fdbc0de3f42b404f49b0d4686a82562254ea29ff0a155eef2f5430f4b0" +dependencies = [ + "bindgen", ] diff --git a/year_2023/Cargo.toml b/year_2023/Cargo.toml index 154fc08..4c4cc79 100644 --- a/year_2023/Cargo.toml +++ b/year_2023/Cargo.toml @@ -7,3 +7,4 @@ edition = "2021" [dependencies] aoc_utils = { path = "../aoc_utils" } +z3 = "0.12.1" diff --git a/year_2023/src/day1/example1.txt b/year_2023/input/day1-example1.txt similarity index 100% rename from year_2023/src/day1/example1.txt rename to year_2023/input/day1-example1.txt diff --git a/year_2023/src/day1/example2.txt b/year_2023/input/day1-example2.txt similarity index 100% rename from year_2023/src/day1/example2.txt rename to year_2023/input/day1-example2.txt diff --git a/year_2023/src/day1/mine.txt b/year_2023/input/day1.txt similarity index 100% rename from year_2023/src/day1/mine.txt rename to year_2023/input/day1.txt diff --git a/year_2023/src/day10/example1.txt b/year_2023/input/day10-example1.txt similarity index 100% rename from year_2023/src/day10/example1.txt rename to year_2023/input/day10-example1.txt diff --git a/year_2023/src/day10/example2.txt b/year_2023/input/day10-example2.txt similarity index 100% rename from year_2023/src/day10/example2.txt rename to year_2023/input/day10-example2.txt diff --git a/year_2023/src/day10/example3.txt b/year_2023/input/day10-example3.txt similarity index 100% rename from year_2023/src/day10/example3.txt rename to year_2023/input/day10-example3.txt diff --git a/year_2023/src/day10/example4.txt b/year_2023/input/day10-example4.txt similarity index 100% rename from year_2023/src/day10/example4.txt rename to year_2023/input/day10-example4.txt diff --git a/year_2023/src/day10/example5.txt b/year_2023/input/day10-example5.txt similarity index 100% rename from year_2023/src/day10/example5.txt rename to year_2023/input/day10-example5.txt diff --git a/year_2023/src/day10/example6.txt b/year_2023/input/day10-example6.txt similarity index 100% rename from year_2023/src/day10/example6.txt rename to year_2023/input/day10-example6.txt diff --git a/year_2023/src/day10/example7.txt b/year_2023/input/day10-example7.txt similarity index 100% rename from year_2023/src/day10/example7.txt rename to year_2023/input/day10-example7.txt diff --git a/year_2023/src/day10/mine.txt b/year_2023/input/day10.txt similarity index 100% rename from year_2023/src/day10/mine.txt rename to year_2023/input/day10.txt diff --git a/year_2023/src/day11/test_input.txt b/year_2023/input/day11-test_input.txt similarity index 100% rename from year_2023/src/day11/test_input.txt rename to year_2023/input/day11-test_input.txt diff --git a/year_2023/src/day11/mine.txt b/year_2023/input/day11.txt similarity index 100% rename from year_2023/src/day11/mine.txt rename to year_2023/input/day11.txt diff --git a/year_2023/src/day12/example.txt b/year_2023/input/day12-example.txt similarity index 100% rename from year_2023/src/day12/example.txt rename to year_2023/input/day12-example.txt diff --git a/year_2023/src/day12/mine.txt b/year_2023/input/day12.txt similarity index 100% rename from year_2023/src/day12/mine.txt rename to year_2023/input/day12.txt diff --git a/year_2023/src/day13/mine.txt b/year_2023/input/day13.txt similarity index 100% rename from year_2023/src/day13/mine.txt rename to year_2023/input/day13.txt diff --git a/year_2023/src/day14/mine.txt b/year_2023/input/day14.txt similarity index 100% rename from year_2023/src/day14/mine.txt rename to year_2023/input/day14.txt diff --git a/year_2023/src/day15/mine.txt b/year_2023/input/day15.txt similarity index 100% rename from year_2023/src/day15/mine.txt rename to year_2023/input/day15.txt diff --git a/year_2023/src/day16/mine.txt b/year_2023/input/day16.txt similarity index 100% rename from year_2023/src/day16/mine.txt rename to year_2023/input/day16.txt diff --git a/year_2023/src/day17/mine.txt b/year_2023/input/day17.txt similarity index 100% rename from year_2023/src/day17/mine.txt rename to year_2023/input/day17.txt diff --git a/year_2023/src/day18/mine.txt b/year_2023/input/day18.txt similarity index 100% rename from year_2023/src/day18/mine.txt rename to year_2023/input/day18.txt diff --git a/year_2023/src/day19/mine.txt b/year_2023/input/day19.txt similarity index 100% rename from year_2023/src/day19/mine.txt rename to year_2023/input/day19.txt diff --git a/year_2023/src/day2/example.txt b/year_2023/input/day2-example.txt similarity index 100% rename from year_2023/src/day2/example.txt rename to year_2023/input/day2-example.txt diff --git a/year_2023/src/day2/mine.txt b/year_2023/input/day2.txt similarity index 100% rename from year_2023/src/day2/mine.txt rename to year_2023/input/day2.txt diff --git a/year_2023/src/day20/mine.txt b/year_2023/input/day20.txt similarity index 100% rename from year_2023/src/day20/mine.txt rename to year_2023/input/day20.txt diff --git a/year_2023/src/day21/mine.txt b/year_2023/input/day21.txt similarity index 100% rename from year_2023/src/day21/mine.txt rename to year_2023/input/day21.txt diff --git a/year_2023/src/day22/mine.txt b/year_2023/input/day22.txt similarity index 100% rename from year_2023/src/day22/mine.txt rename to year_2023/input/day22.txt diff --git a/year_2023/src/day23/mine.txt b/year_2023/input/day23.txt similarity index 100% rename from year_2023/src/day23/mine.txt rename to year_2023/input/day23.txt diff --git a/year_2023/src/day24/mine.txt b/year_2023/input/day24.txt similarity index 100% rename from year_2023/src/day24/mine.txt rename to year_2023/input/day24.txt diff --git a/year_2023/input/day25-example.txt b/year_2023/input/day25-example.txt new file mode 100644 index 0000000..f5bdcc2 --- /dev/null +++ b/year_2023/input/day25-example.txt @@ -0,0 +1,13 @@ +jqt: rhn xhk nvd +rsh: frs pzl lsr +xhk: hfx +cmg: qnr nvd lhk bvb +rhn: xhk bvb hfx +bvb: xhk hfx +pzl: lsr hfx nvd +qnr: nvd +ntq: jqt hfx bvb xhk +nvd: lhk +lsr: lhk +rzs: qnr cmg lsr rsh +frs: qnr lhk lsr \ No newline at end of file diff --git a/year_2023/input/day25.txt b/year_2023/input/day25.txt new file mode 100644 index 0000000..cefa06a --- /dev/null +++ b/year_2023/input/day25.txt @@ -0,0 +1,1252 @@ +pzt: zfx qdv +tzg: bqj +fcj: znv sjk pqc zcr +jrm: qpf lrs xgm +dxx: lrz xvc pcs +zbt: vpp khl hnc +pjt: jbr +qpf: trv gnl +jjd: pnj lfr sdv +xrt: lkc qfj bmx rtt +vvl: mkq +cdn: kkz fdl hsr +sgq: xqd +htv: qcb vgz jdz fqd llm +mkp: kmj ktt dbg +txd: hrf bcf lpr +scv: jmj jdf glc +kpx: xmd jqh +zhj: qbq sqr rqr tvg tjs cnz +vtf: slb gxt mrg +bkc: gmk rmk +jrj: xpg rsz qnp sln +nbf: vcz tbn nfb +xfz: jgh dkp mkc +hsr: bqh vpp fkq +dbc: hfb ksl fgs +qls: pgg +kvn: hfp +fcr: vzx lfl jmj pmn spt sjl lgg +kpq: jkj sjz dkc zhq +dst: ftf thj smm ltr dkb pzt +fgv: tzz prd hcq dkr +qcr: vqs +khl: fdl fbd +gsn: rlm +xrm: dkc +sqf: mzj tkj ntf +fpt: tmn dmp bls +kst: cqt pmr +lbb: gnd jtz dpm cmq cgr +sjz: rxv kcp xpf xck mpp ddr dcm +scs: kkg gxz +mdq: sdh ldj dsg +ppm: mjg kmd +qbl: cmh vdz +svb: jqr jsx +bfb: fml bln xkj +jnh: qbq vfd kqh +fqz: ctq fqc zfb +ndl: xmk njr +zzx: nnf qqh tgm kzh kmr +svm: rpb fpg pkc grk +crm: smm dlg gnl vgz vnp +qkf: jtr cvv ghs khg ntd +rll: cbx vgt zfb +spt: znb +ckr: bfd xxq zkm +nnd: kkz msm tdz gxk +pmc: xkf tnr pbq +pxf: tsp zcv +fxh: rxl sxn zbb rmc +mqs: mzj vkc +jkl: xrl vln zpf lvg +tkx: zgz vpp knn mzc +bml: qcm tcq lvz lqs +cfn: pjd +pnj: pcc khl mbf bzp +fsm: snd gmr mqf nrg +ftt: hnc qrr qfb smr +dhr: dpc +mns: gbd tqh +cdr: xvj sxn kcq +cfc: zbh +tlt: qxd xxg xlq jnx +kkx: ltv +jqq: pzv bkv jlk +brr: sqf mrn mkp hmc sjz +qgg: srn lvp dsg +jnx: rpd scv +cjc: xlh stk fdp +ktb: rjx qdh szh sfq +zfb: jzq +fdz: dfp rsz +phl: djf gql +dxg: gxk skv +qcx: pfp kmx spt ghp +hhd: qzv qqh +xsm: gxt hkb xtk cbx +lkn: fnl htd mjt kvm +xpk: gtl pxx gmh +kxz: gmz mmm hpk +ksq: jbr sdv hpt sfl +mcx: mzg qcm mhc hgt +pdk: lvg xfc jkv nfj +nhk: kmd zgh ckr +nnt: qxc pnj bdp sqz +qfq: npp vnr fsq kgh +ggq: xpg qlc nnx qjq +nrm: rzv bln +krc: xsz znt +llv: vkm xfq xbk xvk +tgm: vjt fmc +bxg: jlv lvz +vld: xgt +hpt: tzp dtm vgq lmj glv +qvx: tnr czl snh jmb +qtf: dsj vtn +zdp: ffr cbq tcp +lxx: qkp pzt plr +sdc: spr zbh +bkq: zfx knp +dhn: xkj hjr rnh +jbx: nnm vgk +kqk: xvx mhm ffv +slb: lrt jcg rkb +lvj: ffv ntl stz +mpt: ztk +jdg: mzp fcf jxv pxp +slr: jjd svr jcg +mdj: qrk vck jzx fqb +nqp: hkk szb +mlh: fhq +thc: pqm rsh +nrt: nrm btg qhr mpc kst +smm: gnt +jpm: plr +qsv: fhq pbl kqk fjx qbl +jlm: zdd czn bmr lcx +sps: zgz jbr +dtm: str +vkh: btx +hcp: fmc +pmf: lnq vxb qfj mqf +xvk: svl jbr mzz +ftj: cnf dxp mjg hrz +jmn: ddr sxv mzb nzk lgz dhp +bjc: xxg +snx: dbf pbm cjc xfz hlm gcn +pbm: kps bkr rsr +jjl: sfq drk rgv gcl +dmt: ktp +zzd: qnv +tct: ftz glc dqc +ssq: jcb +dfl: qvt zcp mtn kcr +xsn: fdl gqd vqs +gch: sbt brs sct cmq gln +tpb: nfg rpb trg bhh +bmr: zxq vgh mts +hhr: bhd xhf jcb krx +hls: mkq rjx rzj +spq: msq cmh rgv ddv pfp +vhn: hnq sls bnc fdv +fkk: gpm rdx kxb ndd +lkg: qpf xpp +qgc: mjm mct gbr +hhq: gjp tmh tcp sgz +nlz: vkc jgb xmd fbh +lrs: fpm +ghg: nzk kfs gqk +jqt: glv bfb fpl btk +rdt: nkx pzg +fbd: skn +dpj: pjs +vkm: xgt pxp gdv +lvq: mds mdq lxk xtg +dxl: grb hjc srj +jxr: sfl mbz nnc fbd +qtk: pbq +knp: tmd +phb: vgk +nsv: phl rnj szh cgr +bnd: zdg xlj mjg +fxd: xjk dbt fkq vnr +vzh: nnm dfq htd jqg +ggx: ntz bkr fzq zjf +rkb: tzp xmh glj +sxv: rsr nzh +pkf: kcp hpk vsh +gdz: bll fmc +hjs: dhk +qhg: gln +mgq: bkc xnh xvj xkf rpd +rsd: lsl znb hcq +mgr: kvl +lqb: nss fpl hkb gsn jzk +npq: grk ddk rsh vfq +gcn: mrq mzb brs sfp +rff: cvn +fdx: xfq gjq +tqm: pqm bgx +ntp: fbd scj cbq +pfg: ckb vkh vxb rtl +xpg: jsg zdf +djf: szb zpk +vft: hcq xkf +ldj: pqm +jbk: jdz nfq gmj hkk jsn +rqj: qts kvj pdd cpn +crx: vpp +dsv: zpk vxr bkn +mhm: rpp mpg nzk kmr +kcf: sfn +nkx: jlp +rvh: lrs zlp rgd rkj +ffq: ksk lzm bnm spn qqt +pxs: cfc kvj zrc xjn +jjv: rhp pkf lvb +ntz: mzj nsg +dmx: zln pbl fdp mkc ppk +dqb: pxg gcq sjk lnx blz +bss: tdq jrk +qjv: xtp kcj bmx vcq zcg +bxj: vnl xfl +ntc: tng +khm: jcb qpd +mzd: fzq dvp brh zxl +rqm: gbq +ldb: tmg +tsq: cfn frd xck rxl +qpr: lnq lfd qfb +ckb: lxk nfb +rlk: gxm pfr tbh gql +kcb: ssh pcc +lfc: ltm hdg xrf hrf +xjm: kgr cbn vpf dlz dgh +pzv: qrs njj +vpj: qbq +vxf: lhk +mjp: tch pzg zlb mts +jlt: vdz mtb +blz: ftx +pkc: vrt pzr +spn: rmn bff mdf +rnk: fbd cqt +ckj: jkv djf cxq llm ndn +jmz: sjh cjg nlz jgh +nzz: nzs pqm nrg zhl +lvb: dpj mzf +jkj: zmz qls bqv +bfc: dhk +czq: klv ssh +npp: jqs pjt bhh gpm +jmj: lqs rsm sml +vgx: nfc +mfs: ffv +scn: fhq +svg: jlv +jjc: tbb lzn +rcs: vcf fnx gnz smr llk xsz dtm +pvk: gvx bpd mrc vck stm +dzb: pdq dlt tcr hvd +dpg: pdd +cjx: xsz vtn fjb grv +jkf: nmc vlp pxj krp +lmg: tvj jqh +lxv: mrc xns gln +dvn: rrn krc ndm +jhh: ztj lcc bkn +ntl: cvh +rlj: vjj lnx pzg dfq +mhj: lzm pgj lfp cbh +tnr: qdt +vjn: kkz zfb dtp sfn +jsn: xpf +mdf: ckj xfc zmt +hjr: gbq mbx +rqn: stm xlq +dpf: kvk +sgs: rhp +mtm: tdb fqd lgx blr +klh: tzg +rsm: qfd +fqb: dpf tmd hfp gvx +ffv: kvk +scf: rkt xns mcs nrf +zzb: nft hcp nkh gxm +lhs: klv dtp ffr shv +xzx: fzt srn vxb ghs +rjx: qkk djv +lnm: vfp zhl gll czq +snh: tdb vzn nrf mxn +pzd: fhj bkh +vhq: dsg ssn hdx zcr +zrd: bll +rxm: tcx kxb zgz +htm: grb sbk pcm +mpg: dvp jsx +vlm: ttd dpx lss +vnl: ksl txn +czg: hrz gks kfl +mkc: kns +jjr: hkq cqm xqd tqj vbx +fnl: qcl thc +tgg: dxg nck fcn +kgv: xgl ztk dfs +lqs: bll +xkl: hkk +hmc: hkk +ztb: rmp rxv jzz +pzr: jdh qcd gsn svx +zlg: gpm +jvd: bjc crn brh lrn +xhh: bzp gbq +zdk: crn gks sct lqh +vbq: nzh fzq sxn jgs +txv: tkg qfk jlj bkr +tzl: bcf +cts: gjl vgk +dcd: pkc gcp ndd rxm +khj: lvz qfd rkt kcp +lxz: dkc qgj ztb +cbq: dgh +ckl: mts shv +gmh: rdr phb +tmf: xzt gjl mzr +cjr: fnl tsp ptq +xhf: xpf btv +qbf: tbh pcp +hfp: xlj +lgg: dbn ltv bnm +gqd: zzd rdr +rfm: hgf hjl vcz +mcj: xkj hnh dhk +xml: nkx +xvf: czp +drz: dpg pxk jtl +hld: jzl krp lrt +gjf: cjc qxd pfm lgz +ctf: vkh +mnh: fcm tsf qnv zbk tth +gdg: cpn vjj vgk +vtg: jsn mkp ddv tnr +nft: ntq prb jjc lcc +zqp: tkg jrm kmj +szm: nfp vgh +xmz: qbq tjp zvf +mzp: bln dhn bvj xgl +gvj: hjs xml +nhx: pcl mrg +xvc: pxk +bmx: lhx htm khg xfl +jqd: drp zjf msq lfl +llm: mzb +lns: cfn srv mrn +scq: zcf slb lmp mpc +cnt: zkn hgk knp lvj +kbb: ktt nvf ccd mrn +gpr: kpx hgl jsx bll +dfm: fbp dbg xzq mmm +qdk: czl +smr: pcc +hps: dng rfl +lgv: jcg qcl fxk vfc +nrf: hkk tmd +lrt: blz +svr: sps xbk khg +dlt: sjh xvf +qvt: vrn dxp ffx +fcf: gcp nnm ntp +jzk: xgt fqc mfs +rnq: jsr vzx gqk ccr +kmx: znb pgj lkg zdg vzx +vxr: crn +vxc: fgq +jfr: kvn qdk rpd glc +vtd: qbh xxg qkp ntl +grv: zzd pjt sch +jrk: grk +mbx: xqb bgx +rpz: xqb lrz tqz nbf pqc +xsz: pjx +qbh: zrr kns ntz +jph: qkk +xvx: mbj zbg pgj sln jnh +cbd: zzd bgf cpn +nfg: tzp jlp bsp +gjp: lbs zlg hjp +vqf: shv mpt jjf xtp btk xsn +qrv: vjr dfq +kxk: lvn vgn rff +ksk: xvf +stz: mzf ffx +kzx: kcf rnh bzc dfs ztk +tng: qbf +ntm: zfb fdx qfb vhj +pcl: ngs tdq +xjt: pxk lhx mts +nvl: ckr khm gql vkc +rqr: vhg vdz +knn: btg pxx +fpl: nhg nrg +gjq: vrh +mxc: hnq nfc +mct: xrm +kjk: cvv fkq dlz +ppk: ndn dxr jgs +hkq: pjh +rvc: dsg mgr pcl lmd +rgl: xvf pgg gbd qlc qrk +blr: rkj lpp tnr svf +rcj: pjx fpt hfz rlj +xlj: xgm +hkb: klh gkd +zcr: cfc qtf +zvc: xqd ltv +kvl: bgx +djv: bnc mff ztj +tzs: dfp dsv jlj nzm sln +dgn: hcj vtf kst rdr +glc: tjp gqj +xnk: mns rmk czp gvx +pcp: bjv +srn: gjp xtg hdg +lxk: rnh +vfd: tdb dpc +ngp: hgv gbq crx +zsd: sdh bkz dgc +lvk: nss grb mzn +hgt: vsx +xgl: bzp pkh +dpt: mzg sxn xkl kps ppk +flz: ncg hnn mrq xfc +fgd: rph hfz dbc +hbl: ghf jqq mfv gdg mpc +gxh: crv nnd nhx +vdn: kmd bjv +cnc: rxq jfj svb +ksl: sfn +msm: pqm szm +rdz: ckc cnc sxj lrs +nnc: lbs jrk +bvj: cvn xfl +rsz: pcp +mgs: nsx tsq vgz dsv +pxx: xmh +lvp: mxl +btt: bhd dpc qlc +ddv: jgb rps +drp: xlq +vln: gnd xxg +rsj: jqt mpt hld vgt +kzh: tgm jsn blx +sdf: rff qrv htd fkf fhk dpg +hgb: jvk tzv qmn vnz +nvd: pzp tvj +sjh: qzv +svl: sdv hjs +kzz: hlx bzp gtl +rps: zfx srv jvk +tcp: kbg +qrk: dkr bkq +vfk: cbx ppt +mxh: xsj ftx hrf +lcc: pfm +rmm: hss dxr mrq +xck: rsm +mvr: jbk xnh zhh pbq ffv +xmh: bfc +htr: pxf chr gmm fgs bps rdr +tkr: jnx bfd ntc xsd +bdg: rpp gks qqm ctp +rbg: klr zhh +pmq: bcv knp glc +jzl: nqf gsx qtr ctf vcd +ddk: kkg lcx lhj +tjb: htm chr vgt xjg +vcq: tdz zdd +llk: jbx jtl tlb rmd +jqj: stk nfj vtb bjh kcr fkn bhc +cbx: lnq +txt: rbg kcp +vnz: tzv tbg +bhc: cqv zrz +vzf: vkm trg lpj dmp +qcl: fjb +zxq: ssh gjl sbj mbz +kqh: zrz vbx +vhp: gjp vbg fqz +nrp: znv +kjl: rjg krp mxl +rcg: mtr nxf kvm npr +rqx: kvb nmc +ntd: nbf cts +bkz: zcv +zgh: lqf bff nnn +hgk: kns tcq qvl +kqq: hnc vfp srn tmn tjb +kgh: smr tbn pmr vfk +chr: ngs kcb +zxm: jqs ptq mxr +lmp: hnh szc dqb xtk +hrs: tmf fml +mpc: cfc +qkp: fbh +ffx: ksk +pnk: mct scn jbs slm +fqm: sch knb nkx bzp +blx: cvh hgl xkf +pmr: hjs pxj +gnj: fqj zxl rqg +ngb: vds ljh qhr dfc +cmq: gfx czl +xfl: njr +ggj: jmj lsl ttz jtz +lss: ttz +ktm: dng cqt fhc +prx: zbh pjt +hqj: czp jlv vtb vgc +kgf: rsr +vcz: hgv +tgc: fkq ndl rjg +cpf: nfc +vtx: glj zdd fgs zxm lrb +mtn: bjc djv gxm +vrn: ssq +cvg: cdp gln dkp +htk: qzv bcq jmb vqr pmn xkf +lrk: cvv msr +zcf: scs +skp: gmj hcp gnt zjk rxl +krx: vxf jfj dgq vft +xxg: cpf tdb +vsh: rkt rxb +tmh: qts tdn rnk xjk +snd: jjf gjq pzg pbc +mxk: ztz ztj kqs +qnh: gks scn hkq hgk +qvl: xvj +dql: txn cdn rph zdp +knb: ctq +vxk: tjp +zbk: kzh ffv xqd lbp +qvv: jbs ldr bcq vkf +cqt: xjn xgt hvk +hjp: mgd +nzm: khq qgc hjb tsf +mnx: ktt +ghs: ccj cqq +qfk: nfj zrz +vbg: khg pxg hnh +cqv: fxb +clp: ntd txd rkb kzk +kjx: dhr qtk +zcp: pzd dmt rbk +rdd: qrs vgn lvn lcx mgd +vnf: dlt qgc bcv +fmt: zkn +znb: jcb +fdv: lmg fdz dbg skz ffd +xdl: kss tnp ggh bqh pdh +qgn: dlg jph rmc qbl +ndt: fjb vkh hgv +grb: nzs +qtr: sfn ndl +vrh: trg vds +bnm: nsg gqk +mqf: npr qrr +xgx: dgh bxj nqv +cxc: hrs vjs kpm +nqv: dbj vgh nfp +pnm: hgk mqs mzb vlm bkc rgr +vgt: hgf ldb +pcd: nzh fbh rgv kfs +plr: mrc pzd tkj +vrj: tcr dgx kvn +fzt: sdc bqj +gbh: zpf plz fdz zvf qkk +vjt: rbk +ljh: tbg skv tmg hdg vcf +bzz: fgs nrg rdt vjj +drq: cxc xgv tzl ksl +dzx: lpr hfz tsr ssn knn +xsj: srj mzc qnv +xgv: nss jlk +nnf: vmp czp +bps: jxv ssh cbx +xjk: sgj dtp ngm +bkv: ddn qhr +mzg: fmt +cpk: hzn rrn bvj tdz +vcf: rpb bzp +vmf: lgk hhd bjv rqg hcp +hrz: xrm +mzc: fjb psb nfp +sbj: mbz +mts: dsj +ggh: msr ckl +rxq: hls qkp xnk +qtc: hnh zbn ckl hkb +cxq: mnx prb +lmv: nsx sxj hgt cdb nnn +zhl: dsg +fjx: mxk mzf gbh +ldp: zlx khd +jhb: fhc pcc +hpp: rjx sml stk ccd +fxb: kgx +fvf: mfz vrt rkb hgf csb hrf +dfp: bkq dxp +xnl: fvb hdg lfr jtl +rjg: bsp ffr +fcm: zkn +xzl: bjg pfr +pzp: sls rnq ftn mqs mzh +pjd: bff jqh +gfb: fhr zkn ncg jmj +gcf: rll jcc pxs +nkh: vrn drp stk +frl: bkz fzv +vfp: xkt bkv +kmj: czl mpp +lmq: czn sqz +ndj: njr btx hjl +njr: dfq fzv +fnj: zvc tvf gks nnf xrm sxv +xqb: qrr ctq +cbh: cmh lcc qzz +pnv: bqh fcn xrf dbj +mjm: fdp +vcr: pzv bfc fqc ngp kvb +qng: nds vld cbq kzz +dng: sgx +njz: mzr czn kkz khd +sfn: spr +gzm: rgt qqf gtl +bnc: qzz mzb +xpp: xrl mhc qcb +gkd: lmq +sns: njk hrf fhc vnl +ccd: rxr +hsp: xgm lpp rbg cxq +qrb: tgm scn zmz bgc +nvf: crn jbm qfk +gkn: vlk ktp dpj +vgk: gxz +jtg: svl kvl ptq npr +hlx: sgj cjr xjm sdh +hgn: dpg zlx cpn khg ppt +dkb: fzq jhh +gnd: vpj +bcv: xhf ldr +qqm: rxb klr mtb qqh +zcg: fgs vqs ctf +bmv: pjh mgx gmz gdp +ddz: rhn lns fhj qhg +zlr: bdr +fml: dtp +mzj: llm +vlp: pkh +mgx: jqh rmc rmk +pmn: jlt +lqh: lgz zpk +zrc: pdg btx gtl +qqt: ndz dkp +nnx: kcp lfp hnq +nqf: sdv tsr lrt +rgz: tvj lxz zkm zrr tjp nzk +zpk: tbg +lbp: rnz +bqh: zgz +dlz: lrk +vnp: fdp bjg gql +gnt: qqj +rqg: ttz +pbt: gxh vgq kvf jtl xrf +rxd: pxg jbx tmn +tqh: qzv fpm +jsr: zpr +mzq: fqc csb scj mpt +rjf: cbq svx sbk +dvk: kjx mkq dpj +fvb: msm +dhl: xlf drz ddn xhh sjk +btk: jhb jlp +cbk: hjr nmc kgv rdx thc jfb +dlg: cdp +kcx: jqj fpm bhc lxv +tcg: svg +qbt: dpf rqn jbh jqr +zdx: ljn bgx rnh zxq +sqk: zrh gsx lrk +jqs: pzg vzh +jdj: pxp kcj htz rqm dvn mtr +qdv: jsr kxz cjg +pjs: slm vtb +hlv: xml dfr zbh kkg +vqm: vcd nrm pdd rlf xvp +ccj: glm htz +zlb: zrh klh zlg kbg +srb: gnz hvg +bgj: jqh jdz mlh +kvj: vjs +xcj: fqm gsn rnk qcr +zzv: qcx mmm jzx hvs +smh: csr jsx kcq +fhq: tkj +ktn: vvl jbs gqj vsh +kbj: tqp crv rqx xhh +str: bdp sbk fcn vgn +hpk: bnd +mhx: nrm pfc rhs txd znv bjm +klc: kfl kgx jpm prd +xbv: rpp mxc vlk fxb +mjt: gcp grv +qfn: zlb hps vjr +bcf: hfb csb +lkv: rnh vfc pxs zcf +xbq: ppm ltr rzj kmr +dms: vxk dgq kmd +hqf: nvd tcg jsr zmt vxk +sdv: lnx +lqn: ntz zzb czp +rlf: zrh +ccs: xxq vpj zzb +jln: kvl srn dbj +gxk: phb +jzx: lss tvj cjg prb +mbj: qqj mrn +xrl: lqs gql +brx: npp prx pcm knb +jtl: lfd +qxm: zbb lzn zlp zjf hgt +qkk: jjc tcr +lpj: sbj jqg xjg +svc: qdh qvl sml zcp +qfv: kxb zkp +tjz: tbh gnl jdf +kps: stm +dbf: vlk khm bld +xtp: vfk tlb +pkk: nft czp slm mjm +vtq: gdz bpd dgq rxr brh +qfb: dng dbj blz +mzz: gmm pzg jgv +vqn: rmd fgd hsg ktm +hdx: qqf +lxs: vgx dbn gbd +ntq: nzh nsg +vtb: jmb sqr +drk: mpg gql fmt +zvv: bcq dkc rmc zpf +gdv: vxc rgt pzg +gjl: psb +lhx: fzv bgx +sqr: cpf xxq +qhr: pkh +pfp: dtk +xsb: btx xql njj +jdf: cdp +fgx: pjs gmz +scx: bdr kjk zfb gmr htz +rkv: szb +rjq: lbs klh zkp bgf +xkv: jtl rqx vld tnp +rnp: hmc kcq dgx jbh +dhp: gnf trv +bzk: pxg pbc kbg njz xmk +rkj: rpp ztz rbk +tmn: xql xtk +frd: zrd +qcb: rbg +lcf: tqp tmg +ghp: qvl jsg jjc +rpg: xsj tzb ldb bdr +zpc: ckb fzt rhs spr rzv +lvz: cmh +dgc: mxl btg vjf +jbd: tlb lkv xpk mgd +csn: mbf +vht: mzh lkg gqh +rsr: lbp mns +lgk: zrd +zgt: mzh lgd svb vpj +pcs: fdx tcx sdc npr hjl +vbk: zkm mlh pcd vvl plr nvf +jqg: ffj +jlg: qcm dhr nhk cvh +tqz: jhb +dfz: mlh dbg qgj frz +ptv: xgx tzl qpr jtl +lfl: kfl ssq +pxp: cbl +ldr: rbk sln gmk +rgd: mrq pjs gbr +vkx: pdp tmg gvj grt +hvk: mfs nss skv +fvk: scs xjg +ktt: qfd +vvv: xgt ftx msr vfc +nfb: pxj +ndm: vjs +lpp: jfj jgh +rxv: sls kmd +fbv: mkp nfc dtk gnx szb gkn bnd +sjp: nds kcf khg bdr +lgx: qdk qzz +zlx: bxj tqz +rtl: tqz lcf qfv fgq +rrl: nzs tmg jcc +mrg: qhr vds +bld: vxf bkn fgx +vfq: ffj +gxm: lss +tch: zbt kvb ggh mgd +zzt: kcb pms jcg +tqp: vpf crx +vgf: cdr fcm jkv jpm +jlj: pfp +gcl: cqm gnl rcn jlj pdq +qnv: xgc +mbd: vrf ppm bjc zkm +jgl: cpf dmt vkf +vrs: hgv klv fvb ngm +qvp: ngs dxg ngm cfc +lpt: dql pxx lvk +hzc: frl grv jjf gjp +cdb: bjg +sjl: kgf tct rsm +fvj: clr bhh bnr xgc +zbg: bpg tjs frd +fsb: kgr vcq gxz +dbt: ljn zbn vlp +fnv: trv dkp +rdx: jqg gxz dtm +ssn: vqs dfs +cnf: qmn hpp kqs fqb ghg +bct: xlj cvg pbq vht +dbr: dpm sfp brh kmd +dff: gbh hzp kqf jbs +xsd: jbm qdt +zcs: jlp lhj bdp ldp +mfz: kgr sjk +gfj: cvv phb +ljn: mzc gvj dfq hvg +rmd: dgh dfs +ttd: fmc zmz mzh +qdh: ztz chl pmc mzh +bfd: ktp vxr +nxf: vjs gvj rlf +tdn: mbz rlf +mvn: dtg ldb sdc +hkn: khd ndj xkt mxl zrh sgz vfq +vqr: sgs ccd pmn kvn +tkg: mct +shh: hgl szh zmz qlc +dsh: qtc qfb ndm gcq +fkn: vbx gvx +cmz: zdp scq mvb fkf +fqv: lbb cnt rkt fnv +hgf: qcd +thj: mpp nvd mxc +xjn: ngm vtn +zln: dkp rqr rhp +btg: xzt kvl +rfl: kzk xvp pxk nfb bfc +scm: jlk tzl vfc +tvk: rcn lxx nvf mzj +cqq: lpr kkz +tcr: dkr +sbk: kcf +rnj: cqv zrr gql smm +fkf: mzn clr +khq: zpk bgj sbt +crv: bls bqj mbx +mml: lvg +stp: kvj tnp lhs btk +qdz: dpt dhp gdp dxp +rmc: hmc +rcn: gbd +fcq: dmt zrd dtk lqn kqh +xjg: hvg +rzj: svg +lkc: ldp pxf qqf +mfx: hjs ctq skn +gkv: vgx pjh bff +rxl: vlk tnr +hsg: fst gmr cvv +dfr: qrs svr xfq lrt +jqr: hvd +zkq: rqg gnd kgf bll +xgc: lvn bcl +cjg: gfx +brh: sgs +pgg: nqp sls +dxr: rmb +gmr: ffr +xnh: hgl +vmp: zpf jlt +gmm: bcl +vzn: xpf tvg cqv +ftf: dhr pvl zpr +gck: hvd dgx bjc mhc +fhk: cfc kvf +rgv: sgq +pht: kfp gxk rjf +hzp: dmt sfq btt +sxf: phb qfn rfm lcf tqm fhn +msq: ctp tsf +knc: hjp cbd rph +jrf: lvn gmh zkp +cbl: fhc gsx pxk +nbv: hrs mjt xkt rjg +pxl: gnj chl bmv +vkc: gfx +kgk: nrp qcd bss vxc tcn +jjf: rlm vcz +ndn: ssq +pms: ndl pjx gfj +btz: qcr tgc mds txn bdp mvb glm bzc +xzq: mnx mjm fmt pfm +lfr: lmq rrl +ndz: brs cdb fmc dvp tcq +sxj: jbh fcm +fqd: czp +pdh: drq bdr jxv gjl +mnr: pqm fvk scm qtr btg +ztt: scs zcv +zvf: hrz jsg +zxl: tvf rsz hgl rkt +bkh: tbb qfd qdt frd +gqh: hpp mpp jzz +shj: kgx sml ncg mnx +qlc: hfp +sbt: nkh +rhn: czp +npb: klr frd fpm kkx +zmx: vhj xgc ndd +pmt: bxg xsd dlg +qxc: tsr mpt +znt: bln rsh +rgt: tzg +xlf: kvf vxc bls +tgx: gqd szc zbn qrs +sch: sbk +pkl: nlz ntf kcp +dfx: fxd jcg kgk lfd xml +kxm: xck kkx rhn zdf cdb +zhh: gqj +hcq: ftz +prd: zfx ltv mjg jsx +lhj: kcf dfc +pdd: rdr +zhq: nsg mtm vft +vcd: fhk jcc +rhc: mrn hls xkl rxr vhg +vts: rkv gmz spt +lmj: fml sqz tbn +mff: lvg +dls: zhl qqf lpr xsz +trs: pnj xhh kjl vkh +rlm: rlf ffr +btv: hhd +jdz: xgh +cnz: bgn qqh mtb +sht: fvk nzs +bjh: jdf ktt pcp +ktj: nfj jbh tbh mml +zkp: nhg +bgf: mfx vrh +ssf: ncg jkv jfj jqr +svf: xhf vjt tvg +chc: grb hps sdh ngs +pvl: rpp +bqv: kgf khm +vlc: hvg vxb grk ftx hjc +kkg: hfb +jfb: zbn xtg xmk +hrf: gsx +tsz: xsd qtk pfr pjh +jgv: gjl klv qrv pcl +ltr: tkg qbf fbh +rhs: vjf tqm chc +zdf: tkj +bbm: kgf qnp jpm mff qnx +ddr: qqj +mzf: lrr rxb +gdp: npb xnh +xmb: lbs qxc krc ndm jbd +srj: pkh +jsx: vrn +gll: glm bzc +mmm: nnn mzj kdl qnp +stm: bjv +pxj: dsj +cvn: vds rzv +zbb: fbp mzg ghp +qts: nfg jln +tnp: lcx +jtz: jlt +gzc: xpk qcr jrf rcj qgg +dmp: bss jqg +tdz: znt kzk +ghn: zcf vgn mpt +tth: rkv mtb mxc tng +qjd: cbh pvp tcg pjh jbm +fzv: sgj +glv: nnc bzc +lff: ntq vmp nsg xmd +nhf: frl sps czq xsm +tqj: sbt hkq rcn +qjq: rmp +dfc: mgr ptq +mzr: tdq sgz +gsl: vfd tmd ssf dtk xmz +zdd: glm +tmv: xrf vhp cqq svx +zjk: sct tct xfc +gsb: tzp rqm sgx lvn +tzv: svg lhk bcq chl rhp +ccr: xmd mml vlk +vrf: qhg lgk lpz bhd +tsr: xql +zbn: hdx +fxk: dlz sht hsr +qnp: dgq +mfq: mbf vtn hfz mxr +rfx: sbj njj rjf slr zzt +nds: hzn +jdh: bqj nzs tbn ztt +cgr: mxn mpp +vjf: zcf qtf +tpk: xgm rbg frz gdz +qmx: czp gnl lpz bpd +xkj: ffj +xlh: dxr lqh zrz +zqk: njr sqz ldj ppt xsb +lrb: jcc ndd mbf +kcj: xql nmc +dmh: npb kxm nqp lzn +nlx: mpd fxb txt pmt +xql: nhg +fsq: cqt krp sfl +kqf: jph hpp vrj lvj +npc: hfb gll zcg dgc +scj: lnq +vgq: sbj +nnn: kqs dbn +rmt: hnq ttd vts kvk +pqc: ldj zlr kxb mfs +tcn: znt krp prx szc +pjf: jzq tgg svx psb +gmj: mff dbn +rct: qxd zdg cmh xxq +dqc: vck gnt +vgc: qls xvj bkr +vnr: mrg bsp +lsl: czp ddr +ffd: sdl mkc tjs +fht: vrt sfn pxj rxd +dtg: tlb gmm bkz +vvn: ndt klq spr cts ccj +kdl: vdn kfs sgq +cbn: hzn kxb hdx +xhc: drk czp mzf rxl +sfp: bgn bqv +ckc: xxq kjx tjz qjq lvb qqt +jzq: pcm gkd +xns: ttz xrm +cvp: kjx tjs ggj ttt +ghf: vfc xvp rgt scm qfv +tcx: kzk mfx +qnx: nsg kcr zxl +nmx: psb rff cbk pxk +dkv: vgf szh mbj fbp +mfv: vjr +jzz: hfp rsr +kvb: pcm bhh +qcm: kps +lvn: zbh +sgz: fdl +gnx: zqp lzm ckc +rrn: gfj vqs xfq +hpn: vnz vxr tcg dhr xrm +xcp: vsx pvl fkn jsg +crc: grk lhs vld dpg +xbk: ngp +vpf: jbr +qfj: xkt +mxn: gmk +ltm: tdn kjk qcl +qtj: rsh nrp zmx +clr: pht +nfc: kcq +tvf: rhp lgd +vvq: grt xvc jxv jjf +njk: sht bxj ptq vgq +xdz: ztj rgr zpr lqv +mpq: qfj fjb lxk mvn vhp +gxt: srb xvp tzg +gnf: jvk vbx +kcp: ktp +gvd: gjq zlg mxl pxx txn +vpm: zdf lrr rmb lmg +ztf: fst bcf qnv sdv ndd +lrz: csn xmh dxl +qmv: qqm brh qdk mcs kkx jrj +nzh: mml +tsp: njr +frz: fgx nrf +fhj: gmk kgx +lfp: kcr bgc xnk +dpx: sls zdf bml +kbp: zlr vlp zkp sjk +bqk: znv vfq zcv rpb ctf +plz: rsm cfn +pns: qzz rhn phl qdv mrc pxl cmq +mvb: nmc htd +lzm: btv zpf +ftn: lgd rbk +qqs: dxx vnl lpt vfc +tcq: qtk jsn +nkq: knc vhq xgv fhn +pvp: tbb vdn zvc +xzt: ddn nnm +grt: szc +pdp: fgq scj hjl jlk +fhr: jgh ndn +glm: jrk +fqj: ztz jlv jgl +rmp: drk gnf +njj: tsp hzn rqm +jms: tjs qqj vln fnv +hlm: qjq txt dbr jph pvl vck +tzz: ftz csr zpk +dpc: cvh +msr: hjc xmk +xzj: vxf lhk kpx srv tng +sxd: kns bhd fnv +ksb: ntl bxg jjv zpr sbt +jtr: vrx szm tcp bcl +kfp: dsj sch sfl zlr +klr: dkr +bpg: ksk jvk qls chl tkj +hnn: ntc svg sgq lqv +pgj: bjg szb +rfj: kvm zsd clr lkn +fjb: pjx +nps: bjm mfz njk srb +vkf: vgz tqh tbb +lpz: hss ndn +jlb: pkl vln rnz lgx +zdg: jgs dgx +gnz: lvp grt +csr: trv +rtt: xrf ddn csb +fst: ffj qtj +qgj: sfq btv +bts: nhg gxz nhx dxl +fpg: scj ddn nrp +qpd: rxr gmk kmr +jsf: fbp smh htv bpd +mcs: lqf lhk +hss: xkf jgs +tlj: rmm jlg ccs xbq +hbh: xsd mgx slm xlh +bnr: krp bls pcl +sct: hnq +ccp: zrr ffx scf vnf +ffr: fdl +trg: qrr +vrt: bcl +glj: bqk lvn +pgn: xjt skn kxk gcq xbk +qkm: knb vjr glm +rnz: mzb rxb +ttt: pfm hgl tvg cqm +hjb: pdq gqk stz vjt +gqj: mzb +bgn: qhg +pbc: fvb rdt +nxh: pzd xqd czg xzl +fnx: shv gkd mgr +rpd: jcb +nsx: ctp rzj +lqv: bxg dqc +jcg: ppt +fvd: scn lgk prb blr +zmt: xsd vxk +pdg: pxk mfv gkd xvc bsp +sdl: qdt tvg jlj ntz +kvm: trg +rgr: kfl mxn +tzb: qcl fcn pcc +thb: dpf dkb dms +vjj: vhj +zxc: hhd sjh fhr rcn +ldv: ntc hpk pmq zxc lgz rxv +lrn: vdz lhk pdq qqj +kmm: vhp lnx gpm smr xtg +bgc: sxd sgs +nfq: fmt ffv qcb +vrx: srj bgx +vzj: skv hsr mfv czn +dcm: srv xkl +lfb: vvl mgx jbm plz +mzn: qcd sqz +mds: dhk fpl +bbj: kfs tkr zjf thb +qmn: fqd bct mkq cdp ftn +kpm: pfc htz +vzx: brs +pfc: vpp dqb hjp +hvs: stk xlq zhh czl +mrq: rkv +vsx: fmc sjz +bjm: mxh bzz qkm vgh zbn nck +fth: rsd bjh csr dvp rqn +lfd: gcq +mxr: csb mzn vjs +hcj: lvp nds gzm +lrr: jlt lzn +xgh: dcm lbp +rmn: lxs xlq jtz kqs +rmk: tsf +dpm: jbh zhh +mtr: zbn csn +ntf: bgn lrn +thl: hjc gkd jxv sgx rfm +hnc: hgv +hjj: vnp sfq vhg xgh jrm +fhn: vrx vpf +skn: rzv +kss: vgq fpg ghn xtk +tdq: tmg +htz: fdx vfq lhx +ctp: vhg bll +fgq: sgx sgj +cbv: sjp ztt nds gcf +kvf: xml +pfr: hvd zpr +bsp: csn +szh: ttz +ccc: dvk cqm dpf gck +ltv: gfx +rph: scj ztk nck +skz: xzl jqj vnp +khd: npr +mpd: gkv pjd drp +kgm: crx hpt fsb kvf fsq +tbg: zrz +rgq: mcj nfp kpm lcx +lqf: vgx bjg +zlp: kvk kpx lss +nck: btx +gcp: rgt jrk +pbl: ftz qxd +klq: kbg sqk sjk vhj +gbr: prd bkn +lmd: kgr pnv trg +jgb: zkn jmb +rmb: mhc lgd jbh \ No newline at end of file diff --git a/year_2023/src/day3/example.txt b/year_2023/input/day3-example.txt similarity index 100% rename from year_2023/src/day3/example.txt rename to year_2023/input/day3-example.txt diff --git a/year_2023/src/day3/mine.txt b/year_2023/input/day3.txt similarity index 100% rename from year_2023/src/day3/mine.txt rename to year_2023/input/day3.txt diff --git a/year_2023/src/day4/example.txt b/year_2023/input/day4-example.txt similarity index 100% rename from year_2023/src/day4/example.txt rename to year_2023/input/day4-example.txt diff --git a/year_2023/src/day4/mine.txt b/year_2023/input/day4.txt similarity index 100% rename from year_2023/src/day4/mine.txt rename to year_2023/input/day4.txt diff --git a/year_2023/src/day5/example.txt b/year_2023/input/day5-example.txt similarity index 100% rename from year_2023/src/day5/example.txt rename to year_2023/input/day5-example.txt diff --git a/year_2023/src/day5/mine.txt b/year_2023/input/day5.txt similarity index 100% rename from year_2023/src/day5/mine.txt rename to year_2023/input/day5.txt diff --git a/year_2023/src/day6/example.txt b/year_2023/input/day6-example.txt similarity index 100% rename from year_2023/src/day6/example.txt rename to year_2023/input/day6-example.txt diff --git a/year_2023/src/day6/mine.txt b/year_2023/input/day6.txt similarity index 100% rename from year_2023/src/day6/mine.txt rename to year_2023/input/day6.txt diff --git a/year_2023/src/day7/example.txt b/year_2023/input/day7-example.txt similarity index 100% rename from year_2023/src/day7/example.txt rename to year_2023/input/day7-example.txt diff --git a/year_2023/src/day7/mine.txt b/year_2023/input/day7.txt similarity index 100% rename from year_2023/src/day7/mine.txt rename to year_2023/input/day7.txt diff --git a/year_2023/src/day8/example1.txt b/year_2023/input/day8-example1.txt similarity index 100% rename from year_2023/src/day8/example1.txt rename to year_2023/input/day8-example1.txt diff --git a/year_2023/src/day8/example2.txt b/year_2023/input/day8-example2.txt similarity index 100% rename from year_2023/src/day8/example2.txt rename to year_2023/input/day8-example2.txt diff --git a/year_2023/src/day8/example3.txt b/year_2023/input/day8-example3.txt similarity index 100% rename from year_2023/src/day8/example3.txt rename to year_2023/input/day8-example3.txt diff --git a/year_2023/src/day8/mine.txt b/year_2023/input/day8.txt similarity index 100% rename from year_2023/src/day8/mine.txt rename to year_2023/input/day8.txt diff --git a/year_2023/src/day9/example.txt b/year_2023/input/day9-example.txt similarity index 100% rename from year_2023/src/day9/example.txt rename to year_2023/input/day9-example.txt diff --git a/year_2023/src/day9/mine.txt b/year_2023/input/day9.txt similarity index 100% rename from year_2023/src/day9/mine.txt rename to year_2023/input/day9.txt diff --git a/year_2023/src/day1/mod.rs b/year_2023/src/day1.rs similarity index 75% rename from year_2023/src/day1/mod.rs rename to year_2023/src/day1.rs index 5253792..a3744f3 100644 --- a/year_2023/src/day1/mod.rs +++ b/year_2023/src/day1.rs @@ -1,14 +1,9 @@ -use aoc_utils as utils; +pub fn execute() -> String { + let input = aoc_utils::read_lines("input/day1.txt"); + let part1 = sum_lines(&input, false).unwrap(); + let part2 = sum_lines(&input, true).unwrap(); -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { - let input = utils::read_lines("src/day1/mine.txt"); - assert_eq!(Some(54927), sum_lines(&input, false)); - assert_eq!(Some(54581), sum_lines(&input, true)); + format!("{} {}", part1, part2) } struct Digit<'a> { @@ -70,15 +65,6 @@ const DIGITS: [Digit; 10] = [ }, ]; -#[test] -fn test_sum_lines() { - let example1 = utils::read_lines("src/day1/example1.txt"); - assert_eq!(Some(142), sum_lines(&example1, false)); - - let example2 = utils::read_lines("src/day1/example2.txt"); - assert_eq!(Some(281), sum_lines(&example2, true)); -} - fn sum_lines>(input: &[T], with_text: bool) -> Option { let mut total: Option = None; for line in input { @@ -119,3 +105,22 @@ fn calculate_line(line: &str, with_text: bool) -> Option { return None; } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "54927 54581"); + } + + #[test] + fn test_sum_lines() { + let example1 = aoc_utils::read_lines("input/day1-example1.txt"); + assert_eq!(Some(142), sum_lines(&example1, false)); + + let example2 = aoc_utils::read_lines("input/day1-example2.txt"); + assert_eq!(Some(281), sum_lines(&example2, true)); + } +} diff --git a/year_2023/src/day10.rs b/year_2023/src/day10.rs new file mode 100644 index 0000000..a47437b --- /dev/null +++ b/year_2023/src/day10.rs @@ -0,0 +1,577 @@ +use std::collections::{HashMap, HashSet, VecDeque}; +use std::fmt::{Display, Formatter}; + +pub fn execute() -> String { + let map = PipeMap::from_file("day10.txt"); + let net = map.to_network(); + let length = net.pipe_length(map.start().unwrap()); + + let part1 = length / 2; + let part2 = map.inner_size(); + + format!("{} {}", part1, part2) +} + +struct Network { + connections: HashMap>, +} + +impl Network { + fn new() -> Self { + Self { + connections: HashMap::new(), + } + } + + fn add(&mut self, from: Position, to: Position) { + self.connections + .entry(from) + .or_insert(HashSet::new()) + .insert(to); + } + + fn get(&self, pos: &Position) -> HashSet { + let neighbours = self.connections.get(pos); + if neighbours.is_some() { + neighbours.unwrap().clone() + } else { + HashSet::new() + } + } + + fn clean(&self, start: &Position) -> Self { + let mut clean = Self::new(); + for position in self.reachable_from(start) { + for neighbour in self.get(&position) { + clean.add(position.clone(), neighbour); + } + } + clean + } + + fn pipe_length(&self, start: &Position) -> usize { + let pipe_positions = self.reachable_from(start); + pipe_positions.len() + } + + fn reachable_from(&self, start: &Position) -> HashSet { + let mut reachable = HashSet::new(); + let mut next_positions = VecDeque::from([start.clone()]); + + while !next_positions.is_empty() { + let next = next_positions.pop_front().unwrap(); + let neighbours = self.get(&next); + + reachable.insert(next); + + for neighbour in neighbours { + if !reachable.contains(&neighbour) { + next_positions.push_back(neighbour.clone()); + } + } + } + reachable + } + + fn inner_region(&self, start: &Position) -> HashSet { + let expanded = self.clean(start).expand(); + let inner_expanded = expanded._inner_region_core(); + + let positions = inner_expanded + .iter() + .filter(|&pos| pos.0 % 2 == 0 && pos.1 % 2 == 0) + .map(|pos| Position(pos.0 / 2, pos.1 / 2)); + + HashSet::from_iter(positions) + } + fn _inner_region_core(&self) -> HashSet { + let max_x = self.connections.keys().map(|pos| pos.0).max().unwrap() + 1; + let max_y = self.connections.keys().map(|pos| pos.1).max().unwrap() + 1; + + let outer = self._outer_region(max_x, max_y); + + let mut inner = HashSet::::new(); + for i in 0..max_x { + for j in 0..max_y { + let current = Position(i, j); + if self.connections.contains_key(¤t) || outer.contains(¤t) { + continue; + } + inner.insert(Position(i, j)); + } + } + inner + } + + fn _outer_region(&self, max_x: usize, max_y: usize) -> HashSet { + let mut outer = HashSet::new(); + let mut to_visit = VecDeque::from([Position(0, 0), Position(max_x, max_y)]); + + while !to_visit.is_empty() { + use Direction::*; + + let current = to_visit.pop_front().unwrap(); + + if self.connections.contains_key(¤t) { + continue; + } + + for direction in [North, South, East, West] { + if !current.can_step(&direction) { + continue; + } + let next = current.to(&direction).unwrap(); + if next.0 > max_x + || next.1 > max_y + || outer.contains(&next) + || to_visit.contains(&next) + { + continue; + } + to_visit.push_back(next); + } + + outer.insert(current); + } + outer + } + + fn expand(&self) -> Self { + let mut expanded = Self::new(); + for (pos, neighbours) in self.connections.iter() { + let exp_pos = Position(pos.0 * 2, pos.1 * 2); + for neigh in neighbours { + let exp_neigh = Position(neigh.0 * 2, neigh.1 * 2); + let exp_betwn = + Position((exp_pos.0 + exp_neigh.0) / 2, (exp_pos.1 + exp_neigh.1) / 2); + + expanded.add(exp_pos.clone(), exp_betwn.clone()); + expanded.add(exp_betwn, exp_neigh); + } + } + expanded + } +} + +fn get_direction(a: &Position, b: &Position) -> Result { + let delta = (b.0 as i64 - a.0 as i64, b.1 as i64 - a.1 as i64); + match delta { + (0, -1) => Ok(Direction::North), + (0, 1) => Ok(Direction::South), + (1, 0) => Ok(Direction::East), + (-1, 0) => Ok(Direction::West), + _ => Err("Not a valid step"), + } +} + +struct PipeMap { + nodes: HashMap, +} + +impl PipeMap { + fn from_file(filename: &str) -> PipeMap { + let path = format!("input/{}", &filename); + let lines = aoc_utils::read_lines(&path); + + let mut nodes = HashMap::::new(); + for (i, line) in lines.iter().enumerate() { + for (j, char) in line.chars().enumerate() { + let pos = Position(j + 1, i + 1); + nodes.insert(pos, pipe_from_char(char)); + } + } + + PipeMap { nodes } + } + + fn start(&self) -> Option<&Position> { + let start_item = self + .nodes + .iter() + .find_map(|(position, pipe)| matches!(pipe, Pipe::Start).then_some(position)); + start_item + } + + fn to_network(&self) -> Network { + let mut network = Network::new(); + for (position, pipe) in self.nodes.iter() { + for forward in pipe_to_directions(pipe) { + if !position.can_step(&forward) { + continue; + } + let dest = position.to(&forward).unwrap(); + + let backward = get_direction(&dest, position).unwrap(); + let dest_pipe = self.nodes.get(&dest); + if !dest_pipe.is_some() { + continue; + } + + let dest_directions = pipe_to_directions(dest_pipe.unwrap()); + if dest_directions.contains(&backward) { + network.add(position.clone(), dest); + } + } + } + network + } + + fn inner_size(&self) -> usize { + let net = self.to_network(); + let start = self.start().unwrap(); + let inner = net.inner_region(start); + inner.len() + } +} + +#[derive(Debug, PartialEq, Eq, Hash)] +enum Direction { + North, + South, + East, + West, +} + +#[derive(PartialEq, Eq, Hash, Clone, Debug, PartialOrd, Ord)] +struct Position(usize, usize); + +impl Position { + fn to(&self, direction: &Direction) -> Result { + if self.can_step(direction) { + let mut result = self.clone(); + result.step(direction); + Ok(result) + } else { + Err("Would step out") + } + } + + fn step(&mut self, direction: &Direction) { + use Direction::*; + match direction { + North => self.1 -= 1, + South => self.1 += 1, + East => self.0 += 1, + West => self.0 -= 1, + }; + } + + fn can_step(&self, direction: &Direction) -> bool { + use Direction::*; + match direction { + North => self.1 > 0, + West => self.0 > 0, + _ => true, + } + } +} + +enum Pipe { + NorthSouth, + NorthEast, + NorthWest, + SouthEast, + SouthWest, + EastWest, + Start, + None, +} + +impl Display for Pipe { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let value = match self { + Pipe::NorthSouth => '|', + Pipe::NorthEast => 'L', + Pipe::NorthWest => 'J', + Pipe::SouthEast => 'F', + Pipe::SouthWest => '7', + Pipe::EastWest => '-', + Pipe::Start => 'S', + Pipe::None => '.', + }; + write!(f, "{}", value) + } +} + +fn pipe_from_char(c: char) -> Pipe { + match c { + '|' => Pipe::NorthSouth, + 'L' => Pipe::NorthEast, + 'J' => Pipe::NorthWest, + 'F' => Pipe::SouthEast, + '7' => Pipe::SouthWest, + '-' => Pipe::EastWest, + 'S' => Pipe::Start, + _ => Pipe::None, + } +} + +fn pipe_to_directions(pipe: &Pipe) -> HashSet { + use Direction::*; + match pipe { + Pipe::NorthSouth => HashSet::from([North, South]), + Pipe::NorthEast => HashSet::from([North, East]), + Pipe::NorthWest => HashSet::from([North, West]), + Pipe::SouthEast => HashSet::from([South, East]), + Pipe::SouthWest => HashSet::from([South, West]), + Pipe::EastWest => HashSet::from([East, West]), + Pipe::Start => HashSet::from([North, South, East, West]), + Pipe::None => HashSet::new(), + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "6942 297"); + } + + #[test] + fn test_inner_size() { + let map2 = PipeMap::from_file("day10-example2.txt"); + assert_eq!(map2.inner_size(), 1); + let map3 = PipeMap::from_file("day10-example3.txt"); + assert_eq!(map3.inner_size(), 1); + let map4 = PipeMap::from_file("day10-example4.txt"); + assert_eq!(map4.inner_size(), 4); + let map5 = PipeMap::from_file("day10-example5.txt"); + assert_eq!(map5.inner_size(), 4); + let map6 = PipeMap::from_file("day10-example6.txt"); + assert_eq!(map6.inner_size(), 8); + let map7 = PipeMap::from_file("day10-example7.txt"); + assert_eq!(map7.inner_size(), 10); + } + + #[test] + fn test_inner_region() { + let map2 = PipeMap::from_file("day10-example2.txt"); + let net2 = map2.to_network(); + + let inner = net2.inner_region(map2.start().unwrap()); + + assert_eq!(inner.len(), 1); + assert!(inner.contains(&Position(3, 3))); + } + + #[test] + fn test_expand_network() { + let map3 = PipeMap::from_file("day10-example3.txt"); + let net3 = map3.to_network().clean(map3.start().unwrap()); + assert_eq!(8, net3.connections.len()); + let exp3 = net3.expand(); + assert_eq!(8, net3.connections.len()); + assert_eq!(16, exp3.connections.len()); + } + + #[test] + fn test_pipe_length() { + let map3 = PipeMap::from_file("day10-example3.txt"); + let net3 = map3.to_network(); + let length = net3.pipe_length(map3.start().unwrap()); + assert_eq!(8, length); + + // This is the furthest point away + assert_eq!(4, length / 2); + } + + #[test] + fn test_clean() { + let map2 = PipeMap::from_file("day10-example2.txt"); + let net2 = map2.to_network(); + let map3 = PipeMap::from_file("day10-example3.txt"); + let net3 = map3.to_network(); + let clean3 = net3.clean(map3.start().unwrap()); + + assert_eq!(net2.connections, clean3.connections); + } + + #[test] + fn test_map_to_network() { + let net1 = PipeMap::from_file("day10-example1.txt").to_network(); + + assert_eq!(8, net1.connections.len()); + + assert_eq!( + HashSet::from([Position(2, 3), Position(3, 2)]), + net1.get(&Position(2, 2)), + ); + assert_eq!( + HashSet::from([Position(4, 2), Position(4, 4)]), + net1.get(&Position(4, 3)), + ); + assert_eq!( + HashSet::from([Position(2, 3), Position(3, 4)]), + net1.get(&Position(2, 4)), + ); + + let net2 = PipeMap::from_file("day10-example2.txt").to_network(); + assert_eq!(net1.connections.len(), net2.connections.len()); + assert_eq!(net1.connections, net2.connections); + + let net3 = PipeMap::from_file("day10-example3.txt").to_network(); + + assert!(net3.connections.len() >= net1.connections.len()); + for (pos, neighbours) in net1.connections.iter() { + assert_eq!(&net3.get(pos), neighbours); + } + } + #[test] + fn test_get_direction() { + use Direction::*; + + fn check(expected: Result, a: Position, b: Position) { + assert_eq!(expected, get_direction(&a, &b)); + } + + check(Ok(North), Position(1, 2), Position(1, 1)); + check(Ok(South), Position(1, 1), Position(1, 2)); + check(Ok(East), Position(1, 1), Position(2, 1)); + check(Ok(West), Position(2, 1), Position(1, 1)); + check(Err("Not a valid step"), Position(1, 1), Position(1, 1)); + check(Err("Not a valid step"), Position(1, 1), Position(1, 3)); + check(Err("Not a valid step"), Position(1, 3), Position(1, 1)); + check(Err("Not a valid step"), Position(3, 1), Position(1, 1)); + check(Err("Not a valid step"), Position(1, 1), Position(3, 1)); + check(Err("Not a valid step"), Position(1, 1), Position(3, 3)); + check(Err("Not a valid step"), Position(3, 3), Position(1, 1)); + } + + #[test] + fn test_parse_pipe_map() { + let map1 = PipeMap::from_file("day10-example1.txt"); + assert_eq!(5 * 5, map1.nodes.len()); + assert!(matches!(map1.start(), None)); + assert!(matches!(map1.nodes[&Position(1, 1)], Pipe::None)); + assert!(matches!(map1.nodes[&Position(2, 2)], Pipe::SouthEast)); + assert!(matches!(map1.nodes[&Position(3, 2)], Pipe::EastWest)); + assert!(matches!(map1.nodes[&Position(4, 2)], Pipe::SouthWest)); + assert!(matches!(map1.nodes[&Position(2, 3)], Pipe::NorthSouth)); + assert!(matches!(map1.nodes[&Position(4, 3)], Pipe::NorthSouth)); + assert!(matches!(map1.nodes[&Position(2, 4)], Pipe::NorthEast)); + assert!(matches!(map1.nodes[&Position(3, 4)], Pipe::EastWest)); + assert!(matches!(map1.nodes[&Position(4, 4)], Pipe::NorthWest)); + assert!(matches!(map1.nodes[&Position(1, 5)], Pipe::None)); + assert!(matches!(map1.nodes[&Position(5, 5)], Pipe::None)); + + let map2 = PipeMap::from_file("day10-example2.txt"); + assert_eq!(5 * 5, map2.nodes.len()); + assert!(matches!(map2.start(), Some(Position(2, 2)))); + assert!(matches!(map2.nodes[&Position(1, 1)], Pipe::None)); + assert!(matches!(map2.nodes[&Position(2, 2)], Pipe::Start)); + assert!(matches!(map2.nodes[&Position(4, 2)], Pipe::SouthWest)); + assert!(matches!(map2.nodes[&Position(2, 4)], Pipe::NorthEast)); + + let map3 = PipeMap::from_file("day10-example3.txt"); + assert_eq!(5 * 5, map3.nodes.len()); + assert!(matches!(map3.start(), Some(Position(2, 2)))); + assert!(matches!(map3.nodes[&Position(1, 1)], Pipe::EastWest)); + assert!(matches!(map3.nodes[&Position(2, 2)], Pipe::Start)); + assert!(matches!(map3.nodes[&Position(5, 1)], Pipe::SouthWest)); + assert!(matches!(map3.nodes[&Position(1, 5)], Pipe::NorthEast)); + assert!(matches!(map3.nodes[&Position(5, 5)], Pipe::SouthEast)); + } + + #[test] + fn test_position_to() { + use Direction::*; + + let origin = Position(0, 0); + assert_eq!(Position(0, 0), origin); + let east = origin.to(&East).unwrap(); + assert_eq!(Position(1, 0), east); + let southeast = east.to(&South).unwrap(); + assert_eq!(Position(1, 1), southeast); + let south = southeast.to(&West).unwrap(); + assert_eq!(Position(0, 1), south); + let origin_again = south.to(&North).unwrap(); + assert_eq!(Position(0, 0), origin_again); + } + #[test] + fn test_position_step() { + use Direction::*; + + let mut pos = Position(0, 0); + assert_eq!(Position(0, 0), pos); + pos.step(&East); + assert_eq!(Position(1, 0), pos); + pos.step(&South); + assert_eq!(Position(1, 1), pos); + pos.step(&West); + assert_eq!(Position(0, 1), pos); + pos.step(&North); + assert_eq!(Position(0, 0), pos); + + pos.step(&East); + pos.step(&East); + pos.step(&East); + assert_eq!(Position(3, 0), pos); + pos.step(&South); + pos.step(&South); + pos.step(&South); + pos.step(&South); + pos.step(&South); + assert_eq!(Position(3, 5), pos); + pos.step(&West); + pos.step(&West); + assert_eq!(Position(1, 5), pos); + pos.step(&North); + assert_eq!(Position(1, 4), pos); + } + + #[test] + fn test_position_can_step() { + use Direction::*; + assert!(!Position(0, 0).can_step(&North)); + assert!(Position(0, 0).can_step(&South)); + assert!(Position(0, 0).can_step(&East)); + assert!(!Position(0, 0).can_step(&West)); + + assert!(!Position(1, 0).can_step(&North)); + assert!(Position(1, 0).can_step(&South)); + assert!(Position(1, 0).can_step(&East)); + assert!(Position(1, 0).can_step(&West)); + + assert!(Position(0, 1).can_step(&North)); + assert!(Position(0, 1).can_step(&South)); + assert!(Position(0, 1).can_step(&East)); + assert!(!Position(0, 1).can_step(&West)); + + assert!(Position(1, 1).can_step(&North)); + assert!(Position(1, 1).can_step(&South)); + assert!(Position(1, 1).can_step(&East)); + assert!(Position(1, 1).can_step(&West)); + } + + #[test] + fn test_parse_pipe() { + assert!(matches!(pipe_from_char('|'), Pipe::NorthSouth)); + assert!(matches!(pipe_from_char('L'), Pipe::NorthEast)); + assert!(matches!(pipe_from_char('J'), Pipe::NorthWest)); + assert!(matches!(pipe_from_char('F'), Pipe::SouthEast)); + assert!(matches!(pipe_from_char('7'), Pipe::SouthWest)); + assert!(matches!(pipe_from_char('-'), Pipe::EastWest)); + assert!(matches!(pipe_from_char('S'), Pipe::Start)); + + for i in 0..255u8 { + let pipe = pipe_from_char(i as char); + if "|LJF7-S".contains(i as char) { + assert!(matches!( + &pipe, + Pipe::Start + | Pipe::NorthEast + | Pipe::NorthWest + | Pipe::SouthEast + | Pipe::SouthWest + | Pipe::EastWest + | Pipe::NorthSouth + )); + assert!(!matches!(pipe, Pipe::None)); + } else { + assert!(matches!(pipe, Pipe::None)); + } + } + } +} diff --git a/year_2023/src/day10/mod.rs b/year_2023/src/day10/mod.rs deleted file mode 100644 index a2cf768..0000000 --- a/year_2023/src/day10/mod.rs +++ /dev/null @@ -1,579 +0,0 @@ -use aoc_utils as utils; -use std::collections::{HashMap, HashSet, VecDeque}; -use std::fmt::{Display, Formatter}; - -#[test] -fn test_mine() { - execute(); -} - -pub fn execute() { - let map = PipeMap::from_file("mine.txt"); - let net = map.to_network(); - let length = net.pipe_length(map.start().unwrap()); - - assert_eq!(13884, length); - assert_eq!(6942, length / 2); - - let inner = map.inner_size(); - assert_eq!(297, inner); -} - -#[test] -fn test_inner_size() { - let map2 = PipeMap::from_file("example2.txt"); - assert_eq!(map2.inner_size(), 1); - let map3 = PipeMap::from_file("example3.txt"); - assert_eq!(map3.inner_size(), 1); - let map4 = PipeMap::from_file("example4.txt"); - assert_eq!(map4.inner_size(), 4); - let map5 = PipeMap::from_file("example5.txt"); - assert_eq!(map5.inner_size(), 4); - let map6 = PipeMap::from_file("example6.txt"); - assert_eq!(map6.inner_size(), 8); - let map7 = PipeMap::from_file("example7.txt"); - assert_eq!(map7.inner_size(), 10); -} - -#[test] -fn test_inner_region() { - let map2 = PipeMap::from_file("example2.txt"); - let net2 = map2.to_network(); - - let inner = net2.inner_region(map2.start().unwrap()); - - assert_eq!(inner.len(), 1); - assert!(inner.contains(&Position(3, 3))); -} - -#[test] -fn test_expand_network() { - let map3 = PipeMap::from_file("example3.txt"); - let net3 = map3.to_network().clean(map3.start().unwrap()); - assert_eq!(8, net3.len()); - let exp3 = net3.expand(); - assert_eq!(8, net3.len()); - assert_eq!(16, exp3.len()); -} - -#[test] -fn test_pipe_length() { - let map3 = PipeMap::from_file("example3.txt"); - let net3 = map3.to_network(); - let length = net3.pipe_length(map3.start().unwrap()); - assert_eq!(8, length); - - // This is the furthest point away - assert_eq!(4, length / 2); -} - -#[test] -fn test_clean() { - let map2 = PipeMap::from_file("example2.txt"); - let net2 = map2.to_network(); - let map3 = PipeMap::from_file("example3.txt"); - let net3 = map3.to_network(); - let clean3 = net3.clean(map3.start().unwrap()); - - assert_eq!(net2.connections, clean3.connections); -} - -#[test] -fn test_map_to_network() { - let net1 = PipeMap::from_file("example1.txt").to_network(); - - assert_eq!(8, net1.len()); - - assert_eq!( - HashSet::from([Position(2, 3), Position(3, 2)]), - net1.get(&Position(2, 2)), - ); - assert_eq!( - HashSet::from([Position(4, 2), Position(4, 4)]), - net1.get(&Position(4, 3)), - ); - assert_eq!( - HashSet::from([Position(2, 3), Position(3, 4)]), - net1.get(&Position(2, 4)), - ); - - let net2 = PipeMap::from_file("example2.txt").to_network(); - assert_eq!(net1.len(), net2.len()); - assert_eq!(net1.connections, net2.connections); - - let net3 = PipeMap::from_file("example3.txt").to_network(); - - assert!(net3.len() >= net1.len()); - for (pos, neighbours) in net1.connections.iter() { - assert_eq!(&net3.get(pos), neighbours); - } -} - -struct Network { - connections: HashMap>, -} - -impl Network { - fn new() -> Self { - Self { - connections: HashMap::new(), - } - } - - fn add(&mut self, from: Position, to: Position) { - self.connections - .entry(from) - .or_insert(HashSet::new()) - .insert(to); - } - - fn get(&self, pos: &Position) -> HashSet { - let neighbours = self.connections.get(pos); - if neighbours.is_some() { - neighbours.unwrap().clone() - } else { - HashSet::new() - } - } - - fn len(&self) -> usize { - self.connections.len() - } - - fn clean(&self, start: &Position) -> Self { - let mut clean = Self::new(); - for position in self.reachable_from(start) { - for neighbour in self.get(&position) { - clean.add(position.clone(), neighbour); - } - } - clean - } - - fn pipe_length(&self, start: &Position) -> usize { - let pipe_positions = self.reachable_from(start); - pipe_positions.len() - } - - fn reachable_from(&self, start: &Position) -> HashSet { - let mut reachable = HashSet::new(); - let mut next_positions = VecDeque::from([start.clone()]); - - while !next_positions.is_empty() { - let next = next_positions.pop_front().unwrap(); - let neighbours = self.get(&next); - - reachable.insert(next); - - for neighbour in neighbours { - if !reachable.contains(&neighbour) { - next_positions.push_back(neighbour.clone()); - } - } - } - reachable - } - - fn inner_region(&self, start: &Position) -> HashSet { - let expanded = self.clean(start).expand(); - let inner_expanded = expanded._inner_region_core(); - - let positions = inner_expanded - .iter() - .filter(|&pos| pos.0 % 2 == 0 && pos.1 % 2 == 0) - .map(|pos| Position(pos.0 / 2, pos.1 / 2)); - - HashSet::from_iter(positions) - } - fn _inner_region_core(&self) -> HashSet { - let max_x = self.connections.keys().map(|pos| pos.0).max().unwrap() + 1; - let max_y = self.connections.keys().map(|pos| pos.1).max().unwrap() + 1; - - let outer = self._outer_region(max_x, max_y); - - let mut inner = HashSet::::new(); - for i in 0..max_x { - for j in 0..max_y { - let current = Position(i, j); - if self.connections.contains_key(¤t) || outer.contains(¤t) { - continue; - } - inner.insert(Position(i, j)); - } - } - inner - } - - fn _outer_region(&self, max_x: usize, max_y: usize) -> HashSet { - let mut outer = HashSet::new(); - let mut to_visit = VecDeque::from([Position(0, 0), Position(max_x, max_y)]); - - while !to_visit.is_empty() { - use Direction::*; - - let current = to_visit.pop_front().unwrap(); - - if self.connections.contains_key(¤t) { - continue; - } - - for direction in [North, South, East, West] { - if !current.can_step(&direction) { - continue; - } - let next = current.to(&direction).unwrap(); - if next.0 > max_x - || next.1 > max_y - || outer.contains(&next) - || to_visit.contains(&next) - { - continue; - } - to_visit.push_back(next); - } - - outer.insert(current); - } - outer - } - - fn expand(&self) -> Self { - let mut expanded = Self::new(); - for (pos, neighbours) in self.connections.iter() { - let exp_pos = Position(pos.0 * 2, pos.1 * 2); - for neigh in neighbours { - let exp_neigh = Position(neigh.0 * 2, neigh.1 * 2); - let exp_betwn = - Position((exp_pos.0 + exp_neigh.0) / 2, (exp_pos.1 + exp_neigh.1) / 2); - - expanded.add(exp_pos.clone(), exp_betwn.clone()); - expanded.add(exp_betwn, exp_neigh); - } - } - expanded - } -} - -#[test] -fn test_get_direction() { - use Direction::*; - - fn check(expected: Result, a: Position, b: Position) { - assert_eq!(expected, get_direction(&a, &b)); - } - - check(Ok(North), Position(1, 2), Position(1, 1)); - check(Ok(South), Position(1, 1), Position(1, 2)); - check(Ok(East), Position(1, 1), Position(2, 1)); - check(Ok(West), Position(2, 1), Position(1, 1)); - check(Err("Not a valid step"), Position(1, 1), Position(1, 1)); - check(Err("Not a valid step"), Position(1, 1), Position(1, 3)); - check(Err("Not a valid step"), Position(1, 3), Position(1, 1)); - check(Err("Not a valid step"), Position(3, 1), Position(1, 1)); - check(Err("Not a valid step"), Position(1, 1), Position(3, 1)); - check(Err("Not a valid step"), Position(1, 1), Position(3, 3)); - check(Err("Not a valid step"), Position(3, 3), Position(1, 1)); -} - -fn get_direction(a: &Position, b: &Position) -> Result { - let delta = (b.0 as i64 - a.0 as i64, b.1 as i64 - a.1 as i64); - match delta { - (0, -1) => Ok(Direction::North), - (0, 1) => Ok(Direction::South), - (1, 0) => Ok(Direction::East), - (-1, 0) => Ok(Direction::West), - _ => Err("Not a valid step"), - } -} - -#[test] -fn test_parse_pipe_map() { - let map1 = PipeMap::from_file("example1.txt"); - assert_eq!(5 * 5, map1.nodes.len()); - assert!(matches!(map1.start(), None)); - assert!(matches!(map1.nodes[&Position(1, 1)], Pipe::None)); - assert!(matches!(map1.nodes[&Position(2, 2)], Pipe::SouthEast)); - assert!(matches!(map1.nodes[&Position(3, 2)], Pipe::EastWest)); - assert!(matches!(map1.nodes[&Position(4, 2)], Pipe::SouthWest)); - assert!(matches!(map1.nodes[&Position(2, 3)], Pipe::NorthSouth)); - assert!(matches!(map1.nodes[&Position(4, 3)], Pipe::NorthSouth)); - assert!(matches!(map1.nodes[&Position(2, 4)], Pipe::NorthEast)); - assert!(matches!(map1.nodes[&Position(3, 4)], Pipe::EastWest)); - assert!(matches!(map1.nodes[&Position(4, 4)], Pipe::NorthWest)); - assert!(matches!(map1.nodes[&Position(1, 5)], Pipe::None)); - assert!(matches!(map1.nodes[&Position(5, 5)], Pipe::None)); - - let map2 = PipeMap::from_file("example2.txt"); - assert_eq!(5 * 5, map2.nodes.len()); - assert!(matches!(map2.start(), Some(Position(2, 2)))); - assert!(matches!(map2.nodes[&Position(1, 1)], Pipe::None)); - assert!(matches!(map2.nodes[&Position(2, 2)], Pipe::Start)); - assert!(matches!(map2.nodes[&Position(4, 2)], Pipe::SouthWest)); - assert!(matches!(map2.nodes[&Position(2, 4)], Pipe::NorthEast)); - - let map3 = PipeMap::from_file("example3.txt"); - assert_eq!(5 * 5, map3.nodes.len()); - assert!(matches!(map3.start(), Some(Position(2, 2)))); - assert!(matches!(map3.nodes[&Position(1, 1)], Pipe::EastWest)); - assert!(matches!(map3.nodes[&Position(2, 2)], Pipe::Start)); - assert!(matches!(map3.nodes[&Position(5, 1)], Pipe::SouthWest)); - assert!(matches!(map3.nodes[&Position(1, 5)], Pipe::NorthEast)); - assert!(matches!(map3.nodes[&Position(5, 5)], Pipe::SouthEast)); -} - -struct PipeMap { - nodes: HashMap, -} - -impl PipeMap { - fn from_file(filename: &str) -> PipeMap { - let path = format!("src/day10/{}", &filename); - let lines = utils::read_lines(&path); - - let mut nodes = HashMap::::new(); - for (i, line) in lines.iter().enumerate() { - for (j, char) in line.chars().enumerate() { - let pos = Position(j + 1, i + 1); - nodes.insert(pos, pipe_from_char(char)); - } - } - - PipeMap { nodes } - } - - fn start(&self) -> Option<&Position> { - let start_item = self - .nodes - .iter() - .find_map(|(position, pipe)| matches!(pipe, Pipe::Start).then_some(position)); - start_item - } - - fn to_network(&self) -> Network { - let mut network = Network::new(); - for (position, pipe) in self.nodes.iter() { - for forward in pipe_to_directions(pipe) { - if !position.can_step(&forward) { - continue; - } - let dest = position.to(&forward).unwrap(); - - let backward = get_direction(&dest, position).unwrap(); - let dest_pipe = self.nodes.get(&dest); - if !dest_pipe.is_some() { - continue; - } - - let dest_directions = pipe_to_directions(dest_pipe.unwrap()); - if dest_directions.contains(&backward) { - network.add(position.clone(), dest); - } - } - } - network - } - - fn inner_size(&self) -> usize { - let net = self.to_network(); - let start = self.start().unwrap(); - let inner = net.inner_region(start); - inner.len() - } -} - -#[test] -fn test_position_to() { - use Direction::*; - - let origin = Position(0, 0); - assert_eq!(Position(0, 0), origin); - let east = origin.to(&East).unwrap(); - assert_eq!(Position(1, 0), east); - let southeast = east.to(&South).unwrap(); - assert_eq!(Position(1, 1), southeast); - let south = southeast.to(&West).unwrap(); - assert_eq!(Position(0, 1), south); - let origin_again = south.to(&North).unwrap(); - assert_eq!(Position(0, 0), origin_again); -} -#[test] -fn test_position_step() { - use Direction::*; - - let mut pos = Position(0, 0); - assert_eq!(Position(0, 0), pos); - pos.step(&East); - assert_eq!(Position(1, 0), pos); - pos.step(&South); - assert_eq!(Position(1, 1), pos); - pos.step(&West); - assert_eq!(Position(0, 1), pos); - pos.step(&North); - assert_eq!(Position(0, 0), pos); - - pos.step(&East); - pos.step(&East); - pos.step(&East); - assert_eq!(Position(3, 0), pos); - pos.step(&South); - pos.step(&South); - pos.step(&South); - pos.step(&South); - pos.step(&South); - assert_eq!(Position(3, 5), pos); - pos.step(&West); - pos.step(&West); - assert_eq!(Position(1, 5), pos); - pos.step(&North); - assert_eq!(Position(1, 4), pos); -} - -#[test] -fn test_position_can_step() { - use Direction::*; - assert!(!Position(0, 0).can_step(&North)); - assert!(Position(0, 0).can_step(&South)); - assert!(Position(0, 0).can_step(&East)); - assert!(!Position(0, 0).can_step(&West)); - - assert!(!Position(1, 0).can_step(&North)); - assert!(Position(1, 0).can_step(&South)); - assert!(Position(1, 0).can_step(&East)); - assert!(Position(1, 0).can_step(&West)); - - assert!(Position(0, 1).can_step(&North)); - assert!(Position(0, 1).can_step(&South)); - assert!(Position(0, 1).can_step(&East)); - assert!(!Position(0, 1).can_step(&West)); - - assert!(Position(1, 1).can_step(&North)); - assert!(Position(1, 1).can_step(&South)); - assert!(Position(1, 1).can_step(&East)); - assert!(Position(1, 1).can_step(&West)); -} - -#[derive(Debug, PartialEq, Eq, Hash)] -enum Direction { - North, - South, - East, - West, -} - -#[derive(PartialEq, Eq, Hash, Clone, Debug, PartialOrd, Ord)] -struct Position(usize, usize); - -impl Position { - fn to(&self, direction: &Direction) -> Result { - if self.can_step(direction) { - let mut result = self.clone(); - result.step(direction); - Ok(result) - } else { - Err("Would step out") - } - } - - fn step(&mut self, direction: &Direction) { - use Direction::*; - match direction { - North => self.1 -= 1, - South => self.1 += 1, - East => self.0 += 1, - West => self.0 -= 1, - }; - } - - fn can_step(&self, direction: &Direction) -> bool { - use Direction::*; - match direction { - North => self.1 > 0, - West => self.0 > 0, - _ => true, - } - } -} - -#[test] -fn test_parse_pipe() { - assert!(matches!(pipe_from_char('|'), Pipe::NorthSouth)); - assert!(matches!(pipe_from_char('L'), Pipe::NorthEast)); - assert!(matches!(pipe_from_char('J'), Pipe::NorthWest)); - assert!(matches!(pipe_from_char('F'), Pipe::SouthEast)); - assert!(matches!(pipe_from_char('7'), Pipe::SouthWest)); - assert!(matches!(pipe_from_char('-'), Pipe::EastWest)); - assert!(matches!(pipe_from_char('S'), Pipe::Start)); - - for i in 0..255u8 { - let pipe = pipe_from_char(i as char); - if "|LJF7-S".contains(i as char) { - assert!(matches!( - &pipe, - Pipe::Start - | Pipe::NorthEast - | Pipe::NorthWest - | Pipe::SouthEast - | Pipe::SouthWest - | Pipe::EastWest - | Pipe::NorthSouth - )); - assert!(!matches!(pipe, Pipe::None)); - } else { - assert!(matches!(pipe, Pipe::None)); - } - } -} - -enum Pipe { - NorthSouth, - NorthEast, - NorthWest, - SouthEast, - SouthWest, - EastWest, - Start, - None, -} - -impl Display for Pipe { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - let value = match self { - Pipe::NorthSouth => '|', - Pipe::NorthEast => 'L', - Pipe::NorthWest => 'J', - Pipe::SouthEast => 'F', - Pipe::SouthWest => '7', - Pipe::EastWest => '-', - Pipe::Start => 'S', - Pipe::None => '.', - }; - write!(f, "{}", value) - } -} - -fn pipe_from_char(c: char) -> Pipe { - match c { - '|' => Pipe::NorthSouth, - 'L' => Pipe::NorthEast, - 'J' => Pipe::NorthWest, - 'F' => Pipe::SouthEast, - '7' => Pipe::SouthWest, - '-' => Pipe::EastWest, - 'S' => Pipe::Start, - _ => Pipe::None, - } -} - -fn pipe_to_directions(pipe: &Pipe) -> HashSet { - use Direction::*; - match pipe { - Pipe::NorthSouth => HashSet::from([North, South]), - Pipe::NorthEast => HashSet::from([North, East]), - Pipe::NorthWest => HashSet::from([North, West]), - Pipe::SouthEast => HashSet::from([South, East]), - Pipe::SouthWest => HashSet::from([South, West]), - Pipe::EastWest => HashSet::from([East, West]), - Pipe::Start => HashSet::from([North, South, East, West]), - Pipe::None => HashSet::new(), - } -} diff --git a/year_2023/src/day11.rs b/year_2023/src/day11.rs new file mode 100644 index 0000000..a842f13 --- /dev/null +++ b/year_2023/src/day11.rs @@ -0,0 +1,217 @@ +use std::cmp::max; +use std::cmp::min; +use std::collections::HashSet; + +pub fn execute() -> String { + let universe = Universe::from_file("day11.txt", 2); + let part1 = universe.sum_shortest_distance(); + + let universe_expanding = Universe::from_file("day11.txt", 1000000); + let part2 = universe_expanding.sum_shortest_distance(); + + format!("{} {}", part1, part2) +} + +#[derive(PartialEq, Eq, Hash, Clone, Debug, PartialOrd, Ord)] +struct Position(usize, usize); + +struct Universe { + galaxies: Vec, + empty_lines: HashSet, + empty_columns: HashSet, + expansion: usize, +} + +impl Universe { + fn from_lines(lines: Vec, expansion: usize) -> Universe { + let num_lines = lines.len(); + let num_cols = if num_lines > 0 { lines[0].len() } else { 0 }; + + let mut empty_lines: HashSet = (1..num_lines + 1).collect(); + let mut empty_columns: HashSet = (1..num_cols + 1).collect(); + + let mut galaxies = Vec::new(); + for (i, line) in lines.iter().enumerate() { + assert_eq!(line.len(), num_cols); + for (j, char) in line.chars().enumerate() { + if char == '#' { + let pos = Position(j + 1, i + 1); + galaxies.push(pos); + + empty_lines.remove(&(i + 1)); + empty_columns.remove(&(j + 1)); + } + } + } + Universe { + galaxies, + empty_lines, + empty_columns, + expansion, + } + } + fn from_file(filename: &str, expansion: usize) -> Universe { + let path = format!("input/{}", filename); + let lines = aoc_utils::read_lines(&path); + Universe::from_lines(lines, expansion) + } + + fn distance(&self, a: &Position, b: &Position) -> usize { + let mut dist = 0; + let minx = min(a.0, b.0); + let maxx = max(a.0, b.0); + for x in minx..maxx { + dist += if self.empty_columns.contains(&x) { + self.expansion + } else { + 1 + }; + } + let miny = min(a.1, b.1); + let maxy = max(a.1, b.1); + for y in miny..maxy { + dist += if self.empty_lines.contains(&y) { + self.expansion + } else { + 1 + }; + } + dist + } + + fn galaxy_pairs(&self) -> Vec<(&Position, &Position)> { + let mut pairs = Vec::new(); + + let size = self.galaxies.len(); + if size > 0 { + for (i, pos1) in self.galaxies[0..size - 1].iter().enumerate() { + for pos2 in self.galaxies[i + 1..size].iter() { + pairs.push((pos1, pos2)); + } + } + } + pairs + } + + fn sum_shortest_distance(&self) -> usize { + let mut total: usize = 0; + for pair in self.galaxy_pairs() { + total += self.distance(pair.0, pair.1); + } + total + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "10422930 699909023130"); + } + + #[test] + fn test_parse_lines() { + let no_line: Vec = vec![]; + let no_line_universe: Universe = Universe::from_lines(no_line, 2); + assert_eq!(0, no_line_universe.galaxies.len()); + + let one_line_no_galaxy = vec!["......".to_string()]; + let one_line_no_galaxy_universe: Universe = Universe::from_lines(one_line_no_galaxy, 2); + assert_eq!(0, one_line_no_galaxy_universe.galaxies.len()); + + let no_galaxy = vec!["....".to_string(), "....".to_string(), "....".to_string()]; + let no_galaxy_universe: Universe = Universe::from_lines(no_galaxy, 2); + assert_eq!(0, no_galaxy_universe.galaxies.len()); + + let only_one_galaxy = vec!["#".to_string()]; + let only_one_galaxy_universe: Universe = Universe::from_lines(only_one_galaxy, 2); + assert_eq!(1, only_one_galaxy_universe.galaxies.len()); + assert!(only_one_galaxy_universe.galaxies.contains(&Position(1, 1))); + + let one_line_one_galaxy = vec!["...#..".to_string()]; + let one_line_one_galaxy_universe: Universe = Universe::from_lines(one_line_one_galaxy, 2); + assert_eq!(1, one_line_one_galaxy_universe.galaxies.len()); + assert!(one_line_one_galaxy_universe + .galaxies + .contains(&Position(4, 1))); + + let one_galaxy = vec!["....".to_string(), "....".to_string(), ".#..".to_string()]; + let one_galaxy_universe: Universe = Universe::from_lines(one_galaxy, 2); + assert_eq!(1, one_galaxy_universe.galaxies.len()); + assert!(one_galaxy_universe.galaxies.contains(&Position(2, 3))); + + let multiple_galaxies = vec!["...#".to_string(), ".#..".to_string(), "#.#.".to_string()]; + let multiple_galaxies_universe: Universe = Universe::from_lines(multiple_galaxies, 2); + assert_eq!(4, multiple_galaxies_universe.galaxies.len()); + assert!(multiple_galaxies_universe + .galaxies + .contains(&Position(4, 1))); + assert!(multiple_galaxies_universe + .galaxies + .contains(&Position(2, 2))); + assert!(multiple_galaxies_universe + .galaxies + .contains(&Position(1, 3))); + assert!(multiple_galaxies_universe + .galaxies + .contains(&Position(3, 3))); + } + + #[test] + fn test_parse_file() { + let universe = Universe::from_file("day11-test_input.txt", 2); + assert_eq!(9, universe.galaxies.len()); + assert!(universe.galaxies.contains(&Position(4, 1))); + assert!(universe.galaxies.contains(&Position(8, 2))); + assert!(universe.galaxies.contains(&Position(1, 3))); + assert!(universe.galaxies.contains(&Position(7, 5))); + assert!(universe.galaxies.contains(&Position(2, 6))); + assert!(universe.galaxies.contains(&Position(10, 7))); + assert!(universe.galaxies.contains(&Position(8, 9))); + assert!(universe.galaxies.contains(&Position(1, 10))); + assert!(universe.galaxies.contains(&Position(5, 10))); + } + + #[test] + fn test_distance() { + let universe = Universe::from_file("day11-test_input.txt", 2); + assert_eq!(15, universe.distance(&Position(4, 1), &Position(8, 9))); + assert_eq!(17, universe.distance(&Position(1, 3), &Position(10, 7))); + assert_eq!(5, universe.distance(&Position(1, 10), &Position(5, 10))); + assert_eq!(5, universe.distance(&Position(5, 10), &Position(1, 10))); + } + + #[test] + fn test_galaxy_pairs() { + let universe = Universe::from_file("day11-test_input.txt", 2); + let pairs_vec = universe.galaxy_pairs(); + let pairs_set: HashSet<(&Position, &Position)> = + HashSet::from_iter(pairs_vec.iter().cloned()); + + assert_eq!(pairs_vec.len(), pairs_set.len()); + assert_eq!(36, pairs_vec.len()); + assert!(pairs_vec.contains(&(&Position(4, 1), &Position(8, 2)))); + assert!(pairs_vec.contains(&(&Position(4, 1), &Position(1, 3)))); + assert!(pairs_vec.contains(&(&Position(8, 2), &Position(1, 3)))); + + assert!(!pairs_vec.contains(&(&Position(8, 2), &Position(4, 1)))); + assert!(!pairs_vec.contains(&(&Position(4, 1), &Position(4, 1)))); + assert!(!pairs_vec.contains(&(&Position(8, 2), &Position(8, 2)))); + } + + #[test] + fn test_sum_shortest_distance() { + let universe = Universe::from_file("day11-test_input.txt", 2); + assert_eq!(374, universe.sum_shortest_distance()); + } + + #[test] + fn test_sum_shortest_distance_with_expansion() { + let universe_expand_10 = Universe::from_file("day11-test_input.txt", 10); + assert_eq!(1030, universe_expand_10.sum_shortest_distance()); + let universe_expand_100 = Universe::from_file("day11-test_input.txt", 100); + assert_eq!(8410, universe_expand_100.sum_shortest_distance()); + } +} diff --git a/year_2023/src/day11/mod.rs b/year_2023/src/day11/mod.rs deleted file mode 100644 index 29fcd66..0000000 --- a/year_2023/src/day11/mod.rs +++ /dev/null @@ -1,209 +0,0 @@ -use aoc_utils as utils; -use std::cmp::max; -use std::cmp::min; -use std::collections::HashSet; - -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { - let universe = Universe::from_file("mine.txt", 2); - assert_eq!(10422930, universe.sum_shortest_distance()); - let universe_expanding = Universe::from_file("mine.txt", 1000000); - assert_eq!(699909023130, universe_expanding.sum_shortest_distance()); -} - -#[derive(PartialEq, Eq, Hash, Clone, Debug, PartialOrd, Ord)] -struct Position(usize, usize); - -struct Universe { - galaxies: Vec, - empty_lines: HashSet, - empty_columns: HashSet, - expansion: usize, -} - -impl Universe { - fn from_lines(lines: Vec, expansion: usize) -> Universe { - let num_lines = lines.len(); - let num_cols = if num_lines > 0 { lines[0].len() } else { 0 }; - - let mut empty_lines: HashSet = (1..num_lines + 1).collect(); - let mut empty_columns: HashSet = (1..num_cols + 1).collect(); - - let mut galaxies = Vec::new(); - for (i, line) in lines.iter().enumerate() { - assert_eq!(line.len(), num_cols); - for (j, char) in line.chars().enumerate() { - if char == '#' { - let pos = Position(j + 1, i + 1); - galaxies.push(pos); - - empty_lines.remove(&(i + 1)); - empty_columns.remove(&(j + 1)); - } - } - } - Universe { - galaxies, - empty_lines, - empty_columns, - expansion, - } - } - fn from_file(filename: &str, expansion: usize) -> Universe { - let path = format!("src/day11/{}", filename); - let lines = utils::read_lines(&path); - Universe::from_lines(lines, expansion) - } - - fn distance(&self, a: &Position, b: &Position) -> usize { - let mut dist = 0; - let minx = min(a.0, b.0); - let maxx = max(a.0, b.0); - for x in minx..maxx { - dist += if self.empty_columns.contains(&x) { - self.expansion - } else { - 1 - }; - } - let miny = min(a.1, b.1); - let maxy = max(a.1, b.1); - for y in miny..maxy { - dist += if self.empty_lines.contains(&y) { - self.expansion - } else { - 1 - }; - } - dist - } - - fn galaxy_pairs(&self) -> Vec<(&Position, &Position)> { - let mut pairs = Vec::new(); - - let size = self.galaxies.len(); - if size > 0 { - for (i, pos1) in self.galaxies[0..size - 1].iter().enumerate() { - for pos2 in self.galaxies[i + 1..size].iter() { - pairs.push((pos1, pos2)); - } - } - } - pairs - } - - fn sum_shortest_distance(&self) -> usize { - let mut total: usize = 0; - for pair in self.galaxy_pairs() { - total += self.distance(pair.0, pair.1); - } - total - } -} - -#[test] -fn test_parse_lines() { - let no_line: Vec = vec![]; - let no_line_universe: Universe = Universe::from_lines(no_line, 2); - assert_eq!(0, no_line_universe.galaxies.len()); - - let one_line_no_galaxy = vec!["......".to_string()]; - let one_line_no_galaxy_universe: Universe = Universe::from_lines(one_line_no_galaxy, 2); - assert_eq!(0, one_line_no_galaxy_universe.galaxies.len()); - - let no_galaxy = vec!["....".to_string(), "....".to_string(), "....".to_string()]; - let no_galaxy_universe: Universe = Universe::from_lines(no_galaxy, 2); - assert_eq!(0, no_galaxy_universe.galaxies.len()); - - let only_one_galaxy = vec!["#".to_string()]; - let only_one_galaxy_universe: Universe = Universe::from_lines(only_one_galaxy, 2); - assert_eq!(1, only_one_galaxy_universe.galaxies.len()); - assert!(only_one_galaxy_universe.galaxies.contains(&Position(1, 1))); - - let one_line_one_galaxy = vec!["...#..".to_string()]; - let one_line_one_galaxy_universe: Universe = Universe::from_lines(one_line_one_galaxy, 2); - assert_eq!(1, one_line_one_galaxy_universe.galaxies.len()); - assert!(one_line_one_galaxy_universe - .galaxies - .contains(&Position(4, 1))); - - let one_galaxy = vec!["....".to_string(), "....".to_string(), ".#..".to_string()]; - let one_galaxy_universe: Universe = Universe::from_lines(one_galaxy, 2); - assert_eq!(1, one_galaxy_universe.galaxies.len()); - assert!(one_galaxy_universe.galaxies.contains(&Position(2, 3))); - - let multiple_galaxies = vec!["...#".to_string(), ".#..".to_string(), "#.#.".to_string()]; - let multiple_galaxies_universe: Universe = Universe::from_lines(multiple_galaxies, 2); - assert_eq!(4, multiple_galaxies_universe.galaxies.len()); - assert!(multiple_galaxies_universe - .galaxies - .contains(&Position(4, 1))); - assert!(multiple_galaxies_universe - .galaxies - .contains(&Position(2, 2))); - assert!(multiple_galaxies_universe - .galaxies - .contains(&Position(1, 3))); - assert!(multiple_galaxies_universe - .galaxies - .contains(&Position(3, 3))); -} - -#[test] -fn test_parse_file() { - let universe = Universe::from_file("test_input.txt", 2); - assert_eq!(9, universe.galaxies.len()); - assert!(universe.galaxies.contains(&Position(4, 1))); - assert!(universe.galaxies.contains(&Position(8, 2))); - assert!(universe.galaxies.contains(&Position(1, 3))); - assert!(universe.galaxies.contains(&Position(7, 5))); - assert!(universe.galaxies.contains(&Position(2, 6))); - assert!(universe.galaxies.contains(&Position(10, 7))); - assert!(universe.galaxies.contains(&Position(8, 9))); - assert!(universe.galaxies.contains(&Position(1, 10))); - assert!(universe.galaxies.contains(&Position(5, 10))); -} - -#[test] -fn test_distance() { - let universe = Universe::from_file("test_input.txt", 2); - assert_eq!(15, universe.distance(&Position(4, 1), &Position(8, 9))); - assert_eq!(17, universe.distance(&Position(1, 3), &Position(10, 7))); - assert_eq!(5, universe.distance(&Position(1, 10), &Position(5, 10))); - assert_eq!(5, universe.distance(&Position(5, 10), &Position(1, 10))); -} - -#[test] -fn test_galaxy_pairs() { - let universe = Universe::from_file("test_input.txt", 2); - let pairs_vec = universe.galaxy_pairs(); - let pairs_set: HashSet<(&Position, &Position)> = HashSet::from_iter(pairs_vec.iter().cloned()); - - assert_eq!(pairs_vec.len(), pairs_set.len()); - assert_eq!(36, pairs_vec.len()); - assert!(pairs_vec.contains(&(&Position(4, 1), &Position(8, 2)))); - assert!(pairs_vec.contains(&(&Position(4, 1), &Position(1, 3)))); - assert!(pairs_vec.contains(&(&Position(8, 2), &Position(1, 3)))); - - assert!(!pairs_vec.contains(&(&Position(8, 2), &Position(4, 1)))); - assert!(!pairs_vec.contains(&(&Position(4, 1), &Position(4, 1)))); - assert!(!pairs_vec.contains(&(&Position(8, 2), &Position(8, 2)))); -} - -#[test] -fn test_sum_shortest_distance() { - let universe = Universe::from_file("test_input.txt", 2); - assert_eq!(374, universe.sum_shortest_distance()); -} - -#[test] -fn test_sum_shortest_distance_with_expansion() { - let universe_expand_10 = Universe::from_file("test_input.txt", 10); - assert_eq!(1030, universe_expand_10.sum_shortest_distance()); - let universe_expand_100 = Universe::from_file("test_input.txt", 100); - assert_eq!(8410, universe_expand_100.sum_shortest_distance()); -} diff --git a/year_2023/src/day12.rs b/year_2023/src/day12.rs new file mode 100644 index 0000000..4981380 --- /dev/null +++ b/year_2023/src/day12.rs @@ -0,0 +1,320 @@ +use std::collections::HashMap; + +pub fn execute() -> String { + let mine = aoc_utils::read_lines("input/day12.txt"); + + let part1 = mine + .iter() + .map(|line| SpringRow::from_line(line.clone())) + .map(|row| row.count_valid_arrangements()) + .sum::(); + let part2 = mine + .iter() + .map(|line| SpringRow::from_line(line.clone()).unfold(5)) + .map(|row| row.count_valid_arrangements()) + .sum::(); + + format!("{} {}", part1, part2) +} + +#[derive(Clone, Copy, Debug)] +enum Condition { + Damaged, + Operational, +} + +fn parse_condition(condition: char) -> Option { + match condition { + '#' => Some(Condition::Damaged), + '.' => Some(Condition::Operational), + '?' => None, + _ => panic!("invalid condition"), + } +} + +#[derive(Clone, Debug)] +struct SpringRow { + condition: Vec>, + checksum: Vec, +} + +impl SpringRow { + fn from_line(line: String) -> SpringRow { + let (condition_text, checksum_text) = line.split_once(' ').unwrap(); + let condition = condition_text.chars().map(parse_condition).collect(); + let checksum = checksum_text + .split(',') + .map(|n| -> usize { n.parse::().unwrap() }) + .collect(); + SpringRow { + condition, + checksum, + } + } + + fn count_valid_arrangements(&self) -> usize { + let mut cache = HashMap::new(); + self._count_valid_arrangements(0, 0, 0, &mut cache) + } + + fn _count_valid_arrangements( + &self, + _condition_index: usize, + _checksum_index: usize, + _current_count: usize, + _cache: &mut HashMap<(usize, usize, usize), usize>, + ) -> usize { + let mut current_count = _current_count; + let mut condition_index = _condition_index; + let mut checksum_index = _checksum_index; + + let cache_key = (_condition_index, _checksum_index, _current_count); + if _cache.contains_key(&cache_key) { + return _cache[&cache_key]; + } + + while condition_index < self.condition.len() { + let condition = self.condition[condition_index].as_ref(); + match condition { + None => { + let mut count = 0; + + if checksum_index < self.checksum.len() + && current_count < self.checksum[checksum_index] + { + count += self._count_valid_arrangements( + condition_index + 1, + checksum_index, + current_count + 1, + _cache, + ); + } + + if current_count == 0 { + count += self._count_valid_arrangements( + condition_index + 1, + checksum_index, + 0, + _cache, + ); + } + + if checksum_index < self.checksum.len() + && current_count == self.checksum[checksum_index] + { + count += self._count_valid_arrangements( + condition_index + 1, + checksum_index + 1, + 0, + _cache, + ); + } + + _cache.insert(cache_key, count); + return count; + } + Some(Condition::Damaged) => { + current_count += 1; + if checksum_index >= self.checksum.len() + || current_count > self.checksum[checksum_index] + { + _cache.insert(cache_key, 0); + return 0; + } + } + Some(Condition::Operational) => { + if current_count > 0 { + if current_count != self.checksum[checksum_index] { + _cache.insert(cache_key, 0); + return 0; + } else { + checksum_index += 1 + } + } + current_count = 0; + } + } + + condition_index += 1; + } + + if current_count > 0 { + if current_count != self.checksum[checksum_index] { + _cache.insert(cache_key, 0); + return 0; + } else { + checksum_index += 1 + } + } + + if checksum_index == self.checksum.len() { + _cache.insert(cache_key, 1); + 1 + } else { + _cache.insert(cache_key, 0); + 0 + } + } + + fn unfold(&self, folds: usize) -> SpringRow { + let sep = Option::::None; + let mut new_condition = Vec::>::new(); + for _ in 1..folds { + new_condition.extend(self.condition.clone()); + new_condition.push(sep); + } + new_condition.extend(self.condition.clone()); + SpringRow { + condition: new_condition, + checksum: self.checksum.repeat(folds), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "7204 1672318386674"); + } + #[test] + fn test_parse_row() { + let row1 = SpringRow::from_line("???.### 1,1,3".to_string()); + assert_eq!(7, row1.condition.len()); + assert!(matches!(row1.condition[0], None)); + assert!(matches!(row1.condition[1], None)); + assert!(matches!(row1.condition[2], None)); + assert!(matches!(row1.condition[3], Some(Condition::Operational))); + assert!(matches!(row1.condition[4], Some(Condition::Damaged))); + assert!(matches!(row1.condition[5], Some(Condition::Damaged))); + assert!(matches!(row1.condition[6], Some(Condition::Damaged))); + assert_eq!(3, row1.checksum.len()); + assert!(matches!(row1.checksum[0], 1)); + assert!(matches!(row1.checksum[1], 1)); + assert!(matches!(row1.checksum[2], 3)); + } + impl SpringRow { + fn is_consistent(&self) -> bool { + self.count_valid_arrangements() > 0 + } + } + + fn check_consistent_line(line: &str) { + assert!(SpringRow::from_line(line.to_string()).is_consistent()); + } + fn check_inconsistent_line(line: &str) { + assert!(!SpringRow::from_line(line.to_string()).is_consistent()); + } + + #[test] + fn test_is_consistent() { + check_consistent_line("# 1"); + check_consistent_line("#. 1"); + check_consistent_line("#.. 1"); + check_consistent_line(".# 1"); + check_consistent_line("..# 1"); + check_consistent_line(".#. 1"); + check_consistent_line("..#.. 1"); + + check_consistent_line("## 2"); + check_consistent_line(".##. 2"); + + check_consistent_line("#.# 1,1"); + check_consistent_line("#..# 1,1"); + check_consistent_line(".#..#. 1,1"); + check_consistent_line(".#..##..### 1,2,3"); + check_consistent_line("###.##..# 3,2,1"); + + check_consistent_line("? 1"); + check_consistent_line(".? 1"); + check_consistent_line("?. 1"); + check_consistent_line("?# 1"); + check_consistent_line("#? 1"); + check_consistent_line(".?#. 1"); + check_consistent_line(".#?. 1"); + + check_consistent_line("#?? 1"); + check_consistent_line(".???????? 1"); + check_consistent_line("????????? 1"); + check_consistent_line("????????? 1,1,1,1,1"); + check_consistent_line("????????? 9"); + + check_inconsistent_line("? 2"); + check_inconsistent_line("#?# 2"); + check_inconsistent_line("#?# 2"); + check_inconsistent_line("#?# 2,1"); + check_inconsistent_line("????????? 10"); + } + + #[test] + fn test_example_all_consistent() { + let example = aoc_utils::read_lines("input/day12-example.txt"); + assert!(example + .iter() + .map(|line| SpringRow::from_line(line.to_string())) + .all(|row| row.is_consistent())); + } + + #[test] + fn test_mine_all_consistent() { + let mine = aoc_utils::read_lines("input/day12.txt"); + assert!(mine + .iter() + .map(|line| SpringRow::from_line(line.to_string())) + .all(|row| row.is_consistent())); + } + + fn test_valid_arangements(line: &str) -> usize { + SpringRow::from_line(line.to_string()).count_valid_arrangements() + } + + #[test] + fn test_count_valid_arrangements() { + assert_eq!(1, test_valid_arangements("# 1")); + assert_eq!(0, test_valid_arangements("## 1")); + assert_eq!(1, test_valid_arangements("?. 1")); + assert_eq!(1, test_valid_arangements("?# 1")); + assert_eq!(2, test_valid_arangements("?? 1")); + assert_eq!(3, test_valid_arangements("??? 1")); + assert_eq!(1, test_valid_arangements("??? 1,1")); + } + + #[test] + fn test_example_valid_arrangements() { + let example = aoc_utils::read_lines("input/day12-example.txt"); + assert_eq!( + 21, + example + .iter() + .map(|line| SpringRow::from_line(line.clone())) + .map(|row| row.count_valid_arrangements()) + .sum::() + ); + } + + #[test] + fn test_unfold() { + let row1 = SpringRow::from_line("# 1".to_string()).unfold(5); + assert_eq!(9, row1.condition.len()); + assert_eq!(5, row1.checksum.len()); + + let row2 = SpringRow::from_line("#?# 1,1".to_string()).unfold(5); + assert_eq!(19, row2.condition.len()); + assert_eq!(10, row2.checksum.len()); + } + + #[test] + fn test_example_unfolded_valid_arrangements() { + let example = aoc_utils::read_lines("input/day12-example.txt"); + assert_eq!( + 525152, + example + .iter() + .map(|line| SpringRow::from_line(line.clone()).unfold(5)) + .map(|row| row.count_valid_arrangements()) + .sum::() + ); + } +} diff --git a/year_2023/src/day12/mod.rs b/year_2023/src/day12/mod.rs deleted file mode 100644 index 90b9978..0000000 --- a/year_2023/src/day12/mod.rs +++ /dev/null @@ -1,319 +0,0 @@ -use aoc_utils as utils; -use std::collections::HashMap; - -#[test] -fn test_mine() { - crate::day12::execute() -} - -pub fn execute() { - let mine = utils::read_lines("src/day12/mine.txt"); - assert_eq!( - 7204, - mine.iter() - .map(|line| SpringRow::from_line(line.clone())) - .map(|row| row.count_valid_arrangements()) - .sum::() - ); - // Too slow for now - assert_eq!( - 1672318386674, - mine.iter() - .map(|line| SpringRow::from_line(line.clone()).unfold(5)) - .map(|row| row.count_valid_arrangements()) - .sum::() - ); -} - -#[derive(Clone, Copy, Debug)] -enum Condition { - Damaged, - Operational, -} - -fn parse_condition(condition: char) -> Option { - match condition { - '#' => Some(Condition::Damaged), - '.' => Some(Condition::Operational), - '?' => None, - _ => panic!("invalid condition"), - } -} - -#[derive(Clone, Debug)] -struct SpringRow { - condition: Vec>, - checksum: Vec, -} - -impl SpringRow { - fn from_line(line: String) -> SpringRow { - let (condition_text, checksum_text) = line.split_once(' ').unwrap(); - let condition = condition_text.chars().map(parse_condition).collect(); - let checksum = checksum_text - .split(',') - .map(|n| -> usize { n.parse::().unwrap() }) - .collect(); - SpringRow { - condition, - checksum, - } - } - - fn is_consistent(&self) -> bool { - self.count_valid_arrangements() > 0 - } - - fn count_valid_arrangements(&self) -> usize { - let mut cache = HashMap::new(); - self._count_valid_arrangements(0, 0, 0, &mut cache) - } - - fn _count_valid_arrangements( - &self, - _condition_index: usize, - _checksum_index: usize, - _current_count: usize, - _cache: &mut HashMap<(usize, usize, usize), usize>, - ) -> usize { - let mut current_count = _current_count; - let mut condition_index = _condition_index; - let mut checksum_index = _checksum_index; - - let cache_key = (_condition_index, _checksum_index, _current_count); - if _cache.contains_key(&cache_key) { - return _cache[&cache_key]; - } - - while condition_index < self.condition.len() { - let condition = self.condition[condition_index].as_ref(); - match condition { - None => { - let mut count = 0; - - if checksum_index < self.checksum.len() - && current_count < self.checksum[checksum_index] - { - count += self._count_valid_arrangements( - condition_index + 1, - checksum_index, - current_count + 1, - _cache, - ); - } - - if current_count == 0 { - count += self._count_valid_arrangements( - condition_index + 1, - checksum_index, - 0, - _cache, - ); - } - - if checksum_index < self.checksum.len() - && current_count == self.checksum[checksum_index] - { - count += self._count_valid_arrangements( - condition_index + 1, - checksum_index + 1, - 0, - _cache, - ); - } - - _cache.insert(cache_key, count); - return count; - } - Some(Condition::Damaged) => { - current_count += 1; - if checksum_index >= self.checksum.len() - || current_count > self.checksum[checksum_index] - { - _cache.insert(cache_key, 0); - return 0; - } - } - Some(Condition::Operational) => { - if current_count > 0 { - if current_count != self.checksum[checksum_index] { - _cache.insert(cache_key, 0); - return 0; - } else { - checksum_index += 1 - } - } - current_count = 0; - } - } - - condition_index += 1; - } - - if current_count > 0 { - if current_count != self.checksum[checksum_index] { - _cache.insert(cache_key, 0); - return 0; - } else { - checksum_index += 1 - } - } - - if checksum_index == self.checksum.len() { - _cache.insert(cache_key, 1); - 1 - } else { - _cache.insert(cache_key, 0); - 0 - } - } - - fn unfold(&self, folds: usize) -> SpringRow { - let sep = Option::::None; - let mut new_condition = Vec::>::new(); - for _ in 1..folds { - new_condition.extend(self.condition.clone()); - new_condition.push(sep); - } - new_condition.extend(self.condition.clone()); - SpringRow { - condition: new_condition, - checksum: self.checksum.repeat(folds), - } - } -} - -#[test] -fn test_parse_row() { - let row1 = SpringRow::from_line("???.### 1,1,3".to_string()); - assert_eq!(7, row1.condition.len()); - assert!(matches!(row1.condition[0], None)); - assert!(matches!(row1.condition[1], None)); - assert!(matches!(row1.condition[2], None)); - assert!(matches!(row1.condition[3], Some(Condition::Operational))); - assert!(matches!(row1.condition[4], Some(Condition::Damaged))); - assert!(matches!(row1.condition[5], Some(Condition::Damaged))); - assert!(matches!(row1.condition[6], Some(Condition::Damaged))); - assert_eq!(3, row1.checksum.len()); - assert!(matches!(row1.checksum[0], 1)); - assert!(matches!(row1.checksum[1], 1)); - assert!(matches!(row1.checksum[2], 3)); -} - -#[test] -fn test_is_consistent() { - check_consistent_line("# 1"); - check_consistent_line("#. 1"); - check_consistent_line("#.. 1"); - check_consistent_line(".# 1"); - check_consistent_line("..# 1"); - check_consistent_line(".#. 1"); - check_consistent_line("..#.. 1"); - - check_consistent_line("## 2"); - check_consistent_line(".##. 2"); - - check_consistent_line("#.# 1,1"); - check_consistent_line("#..# 1,1"); - check_consistent_line(".#..#. 1,1"); - check_consistent_line(".#..##..### 1,2,3"); - check_consistent_line("###.##..# 3,2,1"); - - check_consistent_line("? 1"); - check_consistent_line(".? 1"); - check_consistent_line("?. 1"); - check_consistent_line("?# 1"); - check_consistent_line("#? 1"); - check_consistent_line(".?#. 1"); - check_consistent_line(".#?. 1"); - - check_consistent_line("#?? 1"); - check_consistent_line(".???????? 1"); - check_consistent_line("????????? 1"); - check_consistent_line("????????? 1,1,1,1,1"); - check_consistent_line("????????? 9"); - - check_inconsistent_line("? 2"); - check_inconsistent_line("#?# 2"); - check_inconsistent_line("#?# 2"); - check_inconsistent_line("#?# 2,1"); - check_inconsistent_line("????????? 10"); -} - -#[test] -fn test_example_all_consistent() { - let example = utils::read_lines("src/day12/example.txt"); - assert!(example - .iter() - .map(|line| SpringRow::from_line(line.to_string())) - .all(|row| row.is_consistent())); -} - -#[test] -fn test_mine_all_consistent() { - let mine = utils::read_lines("src/day12/mine.txt"); - assert!(mine - .iter() - .map(|line| SpringRow::from_line(line.to_string())) - .all(|row| row.is_consistent())); -} - -fn check_consistent_line(line: &str) { - assert!(SpringRow::from_line(line.to_string()).is_consistent()); -} - -fn check_inconsistent_line(line: &str) { - assert!(!SpringRow::from_line(line.to_string()).is_consistent()); -} - -#[test] -fn test_count_valid_arrangements() { - assert_eq!(1, test_valid_arangements("# 1")); - assert_eq!(0, test_valid_arangements("## 1")); - assert_eq!(1, test_valid_arangements("?. 1")); - assert_eq!(1, test_valid_arangements("?# 1")); - assert_eq!(2, test_valid_arangements("?? 1")); - assert_eq!(3, test_valid_arangements("??? 1")); - assert_eq!(1, test_valid_arangements("??? 1,1")); -} - -fn test_valid_arangements(line: &str) -> usize { - SpringRow::from_line(line.to_string()).count_valid_arrangements() -} - -#[test] -fn test_example_valid_arrangements() { - let example = utils::read_lines("src/day12/example.txt"); - assert_eq!( - 21, - example - .iter() - .map(|line| SpringRow::from_line(line.clone())) - .map(|row| row.count_valid_arrangements()) - .sum::() - ); -} - -#[test] -fn test_unfold() { - let row1 = SpringRow::from_line("# 1".to_string()).unfold(5); - assert_eq!(9, row1.condition.len()); - assert_eq!(5, row1.checksum.len()); - - let row2 = SpringRow::from_line("#?# 1,1".to_string()).unfold(5); - assert_eq!(19, row2.condition.len()); - assert_eq!(10, row2.checksum.len()); -} - -#[test] -fn test_example_unfolded_valid_arrangements() { - let example = utils::read_lines("src/day12/example.txt"); - assert_eq!( - 525152, - example - .iter() - .map(|line| SpringRow::from_line(line.clone()).unfold(5)) - .map(|row| row.count_valid_arrangements()) - .sum::() - ); -} diff --git a/year_2023/src/day13/mod.rs b/year_2023/src/day13.rs similarity index 61% rename from year_2023/src/day13/mod.rs rename to year_2023/src/day13.rs index d7554e6..0b3e98e 100644 --- a/year_2023/src/day13/mod.rs +++ b/year_2023/src/day13.rs @@ -1,32 +1,21 @@ -use aoc_utils as utils; use std::fmt; -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { - let mut patterns: Vec = utils::read_lines("src/day13/mine.txt") +pub fn execute() -> String { + let mut patterns: Vec = aoc_utils::read_lines("input/day13.txt") .split(|line| line.len() == 0) .map(|pattern_lines| Pattern::from_lines(pattern_lines.to_vec())) .collect(); - assert_eq!(100, patterns.len()); - assert_eq!( - 28651, - patterns - .iter() - .map(|pattern| { pattern.symmetry_score().unwrap() }) - .sum::() - ); - assert_eq!( - 25450, - patterns - .iter_mut() - .map(|pattern| { pattern.new_symmetry_score().unwrap() }) - .sum::() - ); + let part1 = patterns + .iter() + .map(|pattern| pattern.symmetry_score().unwrap()) + .sum::(); + let part2 = patterns + .iter_mut() + .map(|pattern| pattern.new_symmetry_score().unwrap()) + .sum::(); + + format!("{} {}", part1, part2) } #[derive(Clone)] @@ -176,31 +165,6 @@ impl Pattern { } } -#[test] -fn test_from_lines() { - assert_eq!(0, Pattern::from_lines(vec![]).rows.len()); - assert_eq!(1, Pattern::from_lines(vec!["".to_string()]).rows.len()); - - let example1 = _example1(); - - assert_eq!(7, example1.rows.len()); - assert_eq!(9, example1.rows.iter().map(|row| row.len()).min().unwrap()); - assert_eq!(9, example1.rows.iter().map(|row| row.len()).max().unwrap()); - assert_eq!( - vec![true, false, true, true, false, false, true, true, false], - example1.rows[0] - ); - - let example2 = _example2(); - assert_eq!(7, example2.rows.len()); - assert_eq!(9, example2.rows.iter().map(|row| row.len()).min().unwrap()); - assert_eq!(9, example2.rows.iter().map(|row| row.len()).max().unwrap()); - assert_eq!( - vec![true, false, false, false, true, true, false, false, true], - example2.rows[0] - ); -} - fn _example1() -> Pattern { Pattern::from_lines(vec![ "#.##..##.".to_string(), @@ -225,73 +189,108 @@ fn _example2() -> Pattern { ]) } -#[test] -fn test_is_row_symmetric() { - let example1 = _example1(); - - let symmetries = vec![ - (5, 0), - (7, 0), - (1, 1), - (5, 1), - (1, 2), - (5, 2), - (1, 3), - (5, 3), - (1, 4), - (5, 4), - (1, 5), - (3, 5), - (5, 5), - (7, 5), - (5, 6), - ]; - - for j in 0..example1.rows.len() { - for i in 0..example1.rows[j].len() { - let coords = (i, j); - let result = example1.is_row_symmetric(coords); - if symmetries.contains(&coords) { - assert!(result, "{:?} is not symmetric", &coords); - } else { - assert!(!result, "{:?} is symmetric", &coords); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "28651 25450"); + } + + #[test] + fn test_from_lines() { + assert_eq!(0, Pattern::from_lines(vec![]).rows.len()); + assert_eq!(1, Pattern::from_lines(vec!["".to_string()]).rows.len()); + + let example1 = _example1(); + + assert_eq!(7, example1.rows.len()); + assert_eq!(9, example1.rows.iter().map(|row| row.len()).min().unwrap()); + assert_eq!(9, example1.rows.iter().map(|row| row.len()).max().unwrap()); + assert_eq!( + vec![true, false, true, true, false, false, true, true, false], + example1.rows[0] + ); + + let example2 = _example2(); + assert_eq!(7, example2.rows.len()); + assert_eq!(9, example2.rows.iter().map(|row| row.len()).min().unwrap()); + assert_eq!(9, example2.rows.iter().map(|row| row.len()).max().unwrap()); + assert_eq!( + vec![true, false, false, false, true, true, false, false, true], + example2.rows[0] + ); + } + + #[test] + fn test_is_row_symmetric() { + let example1 = _example1(); + + let symmetries = vec![ + (5, 0), + (7, 0), + (1, 1), + (5, 1), + (1, 2), + (5, 2), + (1, 3), + (5, 3), + (1, 4), + (5, 4), + (1, 5), + (3, 5), + (5, 5), + (7, 5), + (5, 6), + ]; + + for j in 0..example1.rows.len() { + for i in 0..example1.rows[j].len() { + let coords = (i, j); + let result = example1.is_row_symmetric(coords); + if symmetries.contains(&coords) { + assert!(result, "{:?} is not symmetric", &coords); + } else { + assert!(!result, "{:?} is symmetric", &coords); + } } } } -} -#[test] -fn test_row_symmetries() { - let example1 = _example1(); - let symmetries1 = example1.row_symmetries(); - assert_eq!(1, symmetries1.len()); - assert_eq!(5, symmetries1[0]); + #[test] + fn test_row_symmetries() { + let example1 = _example1(); + let symmetries1 = example1.row_symmetries(); + assert_eq!(1, symmetries1.len()); + assert_eq!(5, symmetries1[0]); - let example2 = _example2(); - let symmetries2 = example2.row_symmetries(); - assert_eq!(0, symmetries2.len()); -} + let example2 = _example2(); + let symmetries2 = example2.row_symmetries(); + assert_eq!(0, symmetries2.len()); + } -#[test] -fn test_col_symmetries() { - let example1 = _example1(); - let symmetries1 = example1.col_symmetries(); - assert_eq!(0, symmetries1.len()); + #[test] + fn test_col_symmetries() { + let example1 = _example1(); + let symmetries1 = example1.col_symmetries(); + assert_eq!(0, symmetries1.len()); - let example2 = _example2(); - let symmetries2 = example2.col_symmetries(); - assert_eq!(1, symmetries2.len()); - assert_eq!(4, symmetries2[0]); -} + let example2 = _example2(); + let symmetries2 = example2.col_symmetries(); + assert_eq!(1, symmetries2.len()); + assert_eq!(4, symmetries2[0]); + } -#[test] -fn test_new_score() { - let mut example = vec![_example1(), _example2()]; - assert_eq!( - 400, - example - .iter_mut() - .map(|pattern| { pattern.new_symmetry_score().unwrap() }) - .sum::() - ) + #[test] + fn test_new_score() { + let mut example = vec![_example1(), _example2()]; + assert_eq!( + 400, + example + .iter_mut() + .map(|pattern| { pattern.new_symmetry_score().unwrap() }) + .sum::() + ) + } } diff --git a/year_2023/src/day14/mod.rs b/year_2023/src/day14.rs similarity index 57% rename from year_2023/src/day14/mod.rs rename to year_2023/src/day14.rs index 4a35ec6..3a4196a 100644 --- a/year_2023/src/day14/mod.rs +++ b/year_2023/src/day14.rs @@ -1,21 +1,15 @@ -use aoc_utils as utils; use std::collections::HashMap; -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { - let mut mine = Platform::from_lines(utils::read_lines("src/day14/mine.txt")); - assert_eq!(100, mine.rows.len()); - +pub fn execute() -> String { + let mut mine = Platform::from_lines(aoc_utils::read_lines("input/day14.txt")); mine.slide_north(); - assert_eq!(107430, mine.load_north()); + let part1 = mine.load_north(); - let mut mine_cycled = Platform::from_lines(utils::read_lines("src/day14/mine.txt")); + let mut mine_cycled = Platform::from_lines(aoc_utils::read_lines("input/day14.txt")); mine_cycled.cycle_much(1000000000); - assert_eq!(96317, mine_cycled.load_north()); // 96314 < n < 96344 + let part2 = mine_cycled.load_north(); + + format!("{} {}", part1, part2) } #[derive(Clone, Debug)] @@ -23,12 +17,12 @@ enum Shape { Sphere, Cube, } + #[derive(Clone, Debug)] struct Platform { rows: Vec>>, width: usize, } - impl Platform { fn to_lines(&self) -> Vec { self.rows @@ -205,17 +199,6 @@ impl Platform { } } -#[test] -fn test_from_lines() { - let empty = Platform::from_lines(vec!["".to_string()]); - assert_eq!(1, empty.rows.len()); - assert_eq!(0, empty.rows[0].len()); - - let example = _example(); - assert_eq!(10, example.rows.len()); - assert_eq!(10, example.rows[0].len()); -} - fn _example() -> Platform { Platform::from_lines(vec![ "O....#....".to_string(), @@ -230,84 +213,104 @@ fn _example() -> Platform { "#OO..#....".to_string(), ]) } +#[cfg(test)] +mod tests { + use super::*; -#[test] -fn test_slide_north() { - let mut example = _example(); - example.slide_north(); - - assert!(matches!(example.rows[0][0], Some(Shape::Sphere))); - assert!(matches!(example.rows[1][0], Some(Shape::Sphere))); - assert!(matches!(example.rows[2][0], Some(Shape::Sphere))); - assert!(matches!(example.rows[3][0], Some(Shape::Sphere))); - assert!(matches!(example.rows[4][0], None)); - assert!(matches!(example.rows[5][0], None)); - assert!(matches!(example.rows[6][0], None)); - assert!(matches!(example.rows[7][0], None)); - assert!(matches!(example.rows[8][0], Some(Shape::Cube))); - assert!(matches!(example.rows[9][0], Some(Shape::Cube))); - - assert!(matches!(example.rows[0][5], Some(Shape::Cube))); - assert!(matches!(example.rows[1][5], None)); - assert!(matches!(example.rows[2][5], Some(Shape::Cube))); - assert!(matches!(example.rows[3][5], Some(Shape::Sphere))); - assert!(matches!(example.rows[4][5], None)); - assert!(matches!(example.rows[5][5], None)); - assert!(matches!(example.rows[6][5], Some(Shape::Cube))); - assert!(matches!(example.rows[7][5], None)); - assert!(matches!(example.rows[8][5], Some(Shape::Cube))); - assert!(matches!(example.rows[9][5], Some(Shape::Cube))); -} + #[test] + fn test_mine() { + assert_eq!(execute(), "107430 96317"); + } -#[test] -fn test_load_north() { - let mut example = _example(); - example.slide_north(); - assert_eq!(136, example.load_north()); -} + #[test] + fn test_from_lines() { + let empty = Platform::from_lines(vec!["".to_string()]); + assert_eq!(1, empty.rows.len()); + assert_eq!(0, empty.rows[0].len()); -#[test] -fn test_cycle() { - let mut example = _example(); - example.cycle(); - - assert!(matches!(example.rows[0][0], None)); - assert!(matches!(example.rows[1][0], None)); - assert!(matches!(example.rows[2][0], None)); - assert!(matches!(example.rows[3][0], None)); - assert!(matches!(example.rows[4][0], None)); - assert!(matches!(example.rows[5][0], None)); - assert!(matches!(example.rows[6][0], None)); - assert!(matches!(example.rows[7][0], None)); - assert!(matches!(example.rows[8][0], Some(Shape::Cube))); - assert!(matches!(example.rows[9][0], Some(Shape::Cube))); - - assert!(matches!(example.rows[0][1], None)); - assert!(matches!(example.rows[1][1], None)); - assert!(matches!(example.rows[2][1], None)); - assert!(matches!(example.rows[3][1], Some(Shape::Sphere))); - assert!(matches!(example.rows[4][1], None)); - assert!(matches!(example.rows[5][1], Some(Shape::Sphere))); - assert!(matches!(example.rows[6][1], None)); - assert!(matches!(example.rows[7][1], None)); - assert!(matches!(example.rows[8][1], None)); - assert!(matches!(example.rows[9][1], None)); - - assert!(matches!(example.rows[0][5], Some(Shape::Cube))); - assert!(matches!(example.rows[1][5], None)); - assert!(matches!(example.rows[2][5], Some(Shape::Cube))); - assert!(matches!(example.rows[3][5], None)); - assert!(matches!(example.rows[4][5], Some(Shape::Sphere))); - assert!(matches!(example.rows[5][5], None)); - assert!(matches!(example.rows[6][5], Some(Shape::Cube))); - assert!(matches!(example.rows[7][5], None)); - assert!(matches!(example.rows[8][5], Some(Shape::Cube))); - assert!(matches!(example.rows[9][5], Some(Shape::Cube))); -} + let example = _example(); + assert_eq!(10, example.rows.len()); + assert_eq!(10, example.rows[0].len()); + } + + #[test] + fn test_slide_north() { + let mut example = _example(); + example.slide_north(); + + assert!(matches!(example.rows[0][0], Some(Shape::Sphere))); + assert!(matches!(example.rows[1][0], Some(Shape::Sphere))); + assert!(matches!(example.rows[2][0], Some(Shape::Sphere))); + assert!(matches!(example.rows[3][0], Some(Shape::Sphere))); + assert!(matches!(example.rows[4][0], None)); + assert!(matches!(example.rows[5][0], None)); + assert!(matches!(example.rows[6][0], None)); + assert!(matches!(example.rows[7][0], None)); + assert!(matches!(example.rows[8][0], Some(Shape::Cube))); + assert!(matches!(example.rows[9][0], Some(Shape::Cube))); + + assert!(matches!(example.rows[0][5], Some(Shape::Cube))); + assert!(matches!(example.rows[1][5], None)); + assert!(matches!(example.rows[2][5], Some(Shape::Cube))); + assert!(matches!(example.rows[3][5], Some(Shape::Sphere))); + assert!(matches!(example.rows[4][5], None)); + assert!(matches!(example.rows[5][5], None)); + assert!(matches!(example.rows[6][5], Some(Shape::Cube))); + assert!(matches!(example.rows[7][5], None)); + assert!(matches!(example.rows[8][5], Some(Shape::Cube))); + assert!(matches!(example.rows[9][5], Some(Shape::Cube))); + } -#[test] -fn test_cycle_much() { - let mut example = _example(); - example.cycle_much(1000000000); - assert_eq!(64, example.load_north()); + #[test] + fn test_load_north() { + let mut example = _example(); + example.slide_north(); + assert_eq!(136, example.load_north()); + } + + #[test] + fn test_cycle() { + let mut example = _example(); + example.cycle(); + + assert!(matches!(example.rows[0][0], None)); + assert!(matches!(example.rows[1][0], None)); + assert!(matches!(example.rows[2][0], None)); + assert!(matches!(example.rows[3][0], None)); + assert!(matches!(example.rows[4][0], None)); + assert!(matches!(example.rows[5][0], None)); + assert!(matches!(example.rows[6][0], None)); + assert!(matches!(example.rows[7][0], None)); + assert!(matches!(example.rows[8][0], Some(Shape::Cube))); + assert!(matches!(example.rows[9][0], Some(Shape::Cube))); + + assert!(matches!(example.rows[0][1], None)); + assert!(matches!(example.rows[1][1], None)); + assert!(matches!(example.rows[2][1], None)); + assert!(matches!(example.rows[3][1], Some(Shape::Sphere))); + assert!(matches!(example.rows[4][1], None)); + assert!(matches!(example.rows[5][1], Some(Shape::Sphere))); + assert!(matches!(example.rows[6][1], None)); + assert!(matches!(example.rows[7][1], None)); + assert!(matches!(example.rows[8][1], None)); + assert!(matches!(example.rows[9][1], None)); + + assert!(matches!(example.rows[0][5], Some(Shape::Cube))); + assert!(matches!(example.rows[1][5], None)); + assert!(matches!(example.rows[2][5], Some(Shape::Cube))); + assert!(matches!(example.rows[3][5], None)); + assert!(matches!(example.rows[4][5], Some(Shape::Sphere))); + assert!(matches!(example.rows[5][5], None)); + assert!(matches!(example.rows[6][5], Some(Shape::Cube))); + assert!(matches!(example.rows[7][5], None)); + assert!(matches!(example.rows[8][5], Some(Shape::Cube))); + assert!(matches!(example.rows[9][5], Some(Shape::Cube))); + } + + #[test] + fn test_cycle_much() { + let mut example = _example(); + example.cycle_much(1000000000); + assert_eq!(64, example.load_north()); + } } diff --git a/year_2023/src/day15.rs b/year_2023/src/day15.rs new file mode 100644 index 0000000..f7e41d2 --- /dev/null +++ b/year_2023/src/day15.rs @@ -0,0 +1,310 @@ +use std::fs::read_to_string; +pub fn execute() -> String { + let mine = read_sequence("input/day15.txt"); + + let part1 = hash_sequence(&mine); + + let mut lab = Lab::new(); + mine.iter().for_each(|instruction| lab.execute(instruction)); + let part2 = lab.score(); + + format!("{} {}", part1, part2) +} + +fn hash_sequence(sequence: &Vec) -> u32 { + sequence.iter().map(|step| hash_step(&step) as u32).sum() +} + +fn hash_step(step: &String) -> u8 { + let mut result = 0; + for c in step.chars() { + let c_val = c as u8; + let add: u16 = result as u16 + c_val as u16; + let mult = 17u16 * add; + result = mult as u8; + } + result +} +fn read_sequence(filename: &str) -> Vec { + read_to_string(filename) + .unwrap() + .split(',') + .map(|s| s.to_string()) + .collect() +} + +#[derive(Clone)] +struct LabelledLens { + label: String, + power: u8, +} +impl LabelledLens { + fn new(label: &str, power: u8) -> LabelledLens { + LabelledLens { + label: label.to_string(), + power, + } + } +} + +#[derive(Clone)] +struct LensBox { + lenses: Vec, +} + +impl LensBox { + fn new() -> LensBox { + LensBox { lenses: vec![] } + } + + fn add(&mut self, new_lens: LabelledLens) { + let replaced = self + .lenses + .iter_mut() + .find(|lens| lens.label == new_lens.label) + .and_then(|lens| { + lens.power = new_lens.power; + Some(lens) + }); + if replaced.is_some() { + replaced.unwrap().power = new_lens.power; + } else { + self.lenses.push(new_lens); + }; + } + + fn remove(&mut self, label: String) { + self.lenses.retain(|lens| lens.label != label); + } + + fn score(&self) -> u32 { + self.lenses + .iter() + .enumerate() + .map(|(i, lens)| (i as u32 + 1) * lens.power as u32) + .sum() + } +} + +struct Lab { + boxes: Vec, +} +impl Lab { + fn new() -> Lab { + Lab { + boxes: vec![LensBox::new(); 256], + } + } + + fn execute(&mut self, instruction: &String) { + if instruction.ends_with("-") { + self.remove(instruction.strip_suffix("-").unwrap().to_string()); + } else { + let mut parts = instruction.split("="); + let label = parts.next().unwrap(); + let power = parts.next().unwrap().to_string().parse::().unwrap(); + self.add(LabelledLens::new(label, power)); + } + } + + fn add(&mut self, new_lens: LabelledLens) { + let box_index = hash_step(&new_lens.label); + self.boxes[box_index as usize].add(new_lens); + } + + fn remove(&mut self, label: String) { + let box_index = hash_step(&label); + self.boxes[box_index as usize].remove(label); + } + + fn score(&self) -> u32 { + self.boxes + .iter() + .enumerate() + .map(|(i, lensbox)| (i as u32 + 1) * lensbox.score()) + .sum() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "505459 228508"); + } + + #[test] + fn test_hash_step() { + assert_eq!(52, hash_step(&"HASH".to_string())); + + assert_eq!(30, hash_step(&"rn=1".to_string())); + assert_eq!(253, hash_step(&"cm-".to_string())); + assert_eq!(97, hash_step(&"qp=3".to_string())); + assert_eq!(47, hash_step(&"cm=2".to_string())); + assert_eq!(14, hash_step(&"qp-".to_string())); + assert_eq!(180, hash_step(&"pc=4".to_string())); + assert_eq!(9, hash_step(&"ot=9".to_string())); + assert_eq!(197, hash_step(&"ab=5".to_string())); + assert_eq!(48, hash_step(&"pc-".to_string())); + assert_eq!(214, hash_step(&"pc=6".to_string())); + assert_eq!(231, hash_step(&"ot=7".to_string())); + } + + #[test] + fn test_hash_sequence() { + let example = vec![ + "rn=1".to_string(), + "cm-".to_string(), + "qp=3".to_string(), + "cm=2".to_string(), + "qp-".to_string(), + "pc=4".to_string(), + "ot=9".to_string(), + "ab=5".to_string(), + "pc-".to_string(), + "pc=6".to_string(), + "ot=7".to_string(), + ]; + assert_eq!(1320, hash_sequence(&example)); + } + + #[test] + fn test_add_lens() { + let mut lensbox = LensBox::new(); + + lensbox.add(LabelledLens::new("he", 1)); + assert_eq!(1, lensbox.lenses.len()); + assert_eq!("he", lensbox.lenses[0].label); + assert_eq!(1, lensbox.lenses[0].power); + + lensbox.add(LabelledLens::new("he", 2)); + assert_eq!(1, lensbox.lenses.len()); + assert_eq!("he", lensbox.lenses[0].label); + assert_eq!(2, lensbox.lenses[0].power); + + lensbox.add(LabelledLens::new("llo", 5)); + assert_eq!(2, lensbox.lenses.len()); + assert_eq!("he", lensbox.lenses[0].label); + assert_eq!(2, lensbox.lenses[0].power); + assert_eq!("llo", lensbox.lenses[1].label); + assert_eq!(5, lensbox.lenses[1].power); + + lensbox.add(LabelledLens::new("he", 8)); + assert_eq!(2, lensbox.lenses.len()); + assert_eq!("he", lensbox.lenses[0].label); + assert_eq!(8, lensbox.lenses[0].power); + assert_eq!("llo", lensbox.lenses[1].label); + assert_eq!(5, lensbox.lenses[1].power); + } + + #[test] + fn test_remove_lens() { + let mut lensbox = LensBox::new(); + lensbox.add(LabelledLens::new("he", 1)); + lensbox.add(LabelledLens::new("llo", 2)); + lensbox.add(LabelledLens::new("wo", 6)); + lensbox.add(LabelledLens::new("rld", 9)); + assert_eq!(4, lensbox.lenses.len()); + + lensbox.remove("llo".to_string()); + assert_eq!(3, lensbox.lenses.len()); + assert_eq!("he", lensbox.lenses[0].label); + assert_eq!("wo", lensbox.lenses[1].label); + assert_eq!("rld", lensbox.lenses[2].label); + } + + #[test] + fn test_apply_instructions() { + let mut lab = Lab::new(); + + lab.add(LabelledLens::new("rn", 1)); + assert_eq!(1, lab.boxes[0].lenses.len()); + for i in 1..256 { + assert_eq!(0, lab.boxes[i].lenses.len()); + } + + lab.remove("cm".to_string()); + assert_eq!(1, lab.boxes[0].lenses.len()); + for i in 1..256 { + assert_eq!(0, lab.boxes[i].lenses.len()); + } + + lab.add(LabelledLens::new("qp", 3)); + assert_eq!(1, lab.boxes[0].lenses.len()); + assert_eq!(1, lab.boxes[1].lenses.len()); + for i in 2..256 { + assert_eq!(0, lab.boxes[i].lenses.len()); + } + + lab.add(LabelledLens::new("cm", 2)); + assert_eq!(2, lab.boxes[0].lenses.len()); + assert_eq!(1, lab.boxes[1].lenses.len()); + for i in 2..256 { + assert_eq!(0, lab.boxes[i].lenses.len()); + } + + lab.remove("qp".to_string()); + assert_eq!(2, lab.boxes[0].lenses.len()); + for i in 1..256 { + assert_eq!(0, lab.boxes[i].lenses.len()); + } + } + + #[test] + fn test_execute() { + let mut lab = Lab::new(); + + lab.execute(&"rn=1".to_string()); + assert_eq!(1, lab.boxes[0].lenses.len()); + for i in 1..256 { + assert_eq!(0, lab.boxes[i].lenses.len()); + } + + lab.execute(&"cm-".to_string()); + assert_eq!(1, lab.boxes[0].lenses.len()); + for i in 1..256 { + assert_eq!(0, lab.boxes[i].lenses.len()); + } + + lab.execute(&"qp=3".to_string()); + assert_eq!(1, lab.boxes[0].lenses.len()); + assert_eq!(1, lab.boxes[1].lenses.len()); + for i in 2..256 { + assert_eq!(0, lab.boxes[i].lenses.len()); + } + + lab.execute(&"cm=2".to_string()); + assert_eq!(2, lab.boxes[0].lenses.len()); + assert_eq!(1, lab.boxes[1].lenses.len()); + for i in 2..256 { + assert_eq!(0, lab.boxes[i].lenses.len()); + } + + lab.execute(&"qp-".to_string()); + assert_eq!(2, lab.boxes[0].lenses.len()); + for i in 1..256 { + assert_eq!(0, lab.boxes[i].lenses.len()); + } + } + + #[test] + fn test_example() { + let mut lab = Lab::new(); + + lab.execute(&"rn=1".to_string()); + lab.execute(&"cm-".to_string()); + lab.execute(&"qp=3".to_string()); + lab.execute(&"cm=2".to_string()); + lab.execute(&"qp-".to_string()); + lab.execute(&"pc=4".to_string()); + lab.execute(&"ot=9".to_string()); + lab.execute(&"ab=5".to_string()); + lab.execute(&"pc-".to_string()); + lab.execute(&"pc=6".to_string()); + lab.execute(&"ot=7".to_string()); + + assert_eq!(145, lab.score()); + } +} diff --git a/year_2023/src/day15/mod.rs b/year_2023/src/day15/mod.rs deleted file mode 100644 index afb9b47..0000000 --- a/year_2023/src/day15/mod.rs +++ /dev/null @@ -1,306 +0,0 @@ -use std::fs::read_to_string; - -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { - let mine = read_sequence("src/day15/mine.txt"); - assert_eq!(505459, hash_sequence(&mine)); - - let mut lab = Lab::new(); - mine.iter().for_each(|instruction| lab.execute(instruction)); - - assert_eq!(228508, lab.score()) -} - -fn hash_sequence(sequence: &Vec) -> u32 { - sequence.iter().map(|step| hash_step(&step) as u32).sum() -} -fn hash_step(step: &String) -> u8 { - let mut result = 0; - for c in step.chars() { - let c_val = c as u8; - let add: u16 = result as u16 + c_val as u16; - let mult = 17u16 * add; - result = mult as u8; - } - result -} - -fn read_sequence(filename: &str) -> Vec { - read_to_string(filename) - .unwrap() - .split(',') - .map(|s| s.to_string()) - .collect() -} - -#[test] -fn test_hash_step() { - assert_eq!(52, hash_step(&"HASH".to_string())); - - assert_eq!(30, hash_step(&"rn=1".to_string())); - assert_eq!(253, hash_step(&"cm-".to_string())); - assert_eq!(97, hash_step(&"qp=3".to_string())); - assert_eq!(47, hash_step(&"cm=2".to_string())); - assert_eq!(14, hash_step(&"qp-".to_string())); - assert_eq!(180, hash_step(&"pc=4".to_string())); - assert_eq!(9, hash_step(&"ot=9".to_string())); - assert_eq!(197, hash_step(&"ab=5".to_string())); - assert_eq!(48, hash_step(&"pc-".to_string())); - assert_eq!(214, hash_step(&"pc=6".to_string())); - assert_eq!(231, hash_step(&"ot=7".to_string())); -} - -#[test] -fn test_hash_sequence() { - let example = vec![ - "rn=1".to_string(), - "cm-".to_string(), - "qp=3".to_string(), - "cm=2".to_string(), - "qp-".to_string(), - "pc=4".to_string(), - "ot=9".to_string(), - "ab=5".to_string(), - "pc-".to_string(), - "pc=6".to_string(), - "ot=7".to_string(), - ]; - assert_eq!(1320, hash_sequence(&example)); -} - -#[derive(Clone)] -struct LabelledLens { - label: String, - power: u8, -} - -impl LabelledLens { - fn new(label: &str, power: u8) -> LabelledLens { - LabelledLens { - label: label.to_string(), - power, - } - } -} - -#[derive(Clone)] -struct LensBox { - lenses: Vec, -} - -impl LensBox { - fn new() -> LensBox { - LensBox { lenses: vec![] } - } - - fn add(&mut self, new_lens: LabelledLens) { - let replaced = self - .lenses - .iter_mut() - .find(|lens| lens.label == new_lens.label) - .and_then(|lens| { - lens.power = new_lens.power; - Some(lens) - }); - if replaced.is_some() { - replaced.unwrap().power = new_lens.power; - } else { - self.lenses.push(new_lens); - }; - } - - fn remove(&mut self, label: String) { - self.lenses.retain(|lens| lens.label != label); - } - - fn score(&self) -> u32 { - self.lenses - .iter() - .enumerate() - .map(|(i, lens)| (i as u32 + 1) * lens.power as u32) - .sum() - } -} - -#[test] -fn test_add_lens() { - let mut lensbox = LensBox::new(); - - lensbox.add(LabelledLens::new("he", 1)); - assert_eq!(1, lensbox.lenses.len()); - assert_eq!("he", lensbox.lenses[0].label); - assert_eq!(1, lensbox.lenses[0].power); - - lensbox.add(LabelledLens::new("he", 2)); - assert_eq!(1, lensbox.lenses.len()); - assert_eq!("he", lensbox.lenses[0].label); - assert_eq!(2, lensbox.lenses[0].power); - - lensbox.add(LabelledLens::new("llo", 5)); - assert_eq!(2, lensbox.lenses.len()); - assert_eq!("he", lensbox.lenses[0].label); - assert_eq!(2, lensbox.lenses[0].power); - assert_eq!("llo", lensbox.lenses[1].label); - assert_eq!(5, lensbox.lenses[1].power); - - lensbox.add(LabelledLens::new("he", 8)); - assert_eq!(2, lensbox.lenses.len()); - assert_eq!("he", lensbox.lenses[0].label); - assert_eq!(8, lensbox.lenses[0].power); - assert_eq!("llo", lensbox.lenses[1].label); - assert_eq!(5, lensbox.lenses[1].power); -} - -#[test] -fn test_remove_lens() { - let mut lensbox = LensBox::new(); - lensbox.add(LabelledLens::new("he", 1)); - lensbox.add(LabelledLens::new("llo", 2)); - lensbox.add(LabelledLens::new("wo", 6)); - lensbox.add(LabelledLens::new("rld", 9)); - assert_eq!(4, lensbox.lenses.len()); - - lensbox.remove("llo".to_string()); - assert_eq!(3, lensbox.lenses.len()); - assert_eq!("he", lensbox.lenses[0].label); - assert_eq!("wo", lensbox.lenses[1].label); - assert_eq!("rld", lensbox.lenses[2].label); -} - -struct Lab { - boxes: Vec, -} - -impl Lab { - fn new() -> Lab { - Lab { - boxes: vec![LensBox::new(); 256], - } - } - - fn execute(&mut self, instruction: &String) { - if instruction.ends_with("-") { - self.remove(instruction.strip_suffix("-").unwrap().to_string()); - } else { - let mut parts = instruction.split("="); - let label = parts.next().unwrap(); - let power = parts.next().unwrap().to_string().parse::().unwrap(); - self.add(LabelledLens::new(label, power)); - } - } - - fn add(&mut self, new_lens: LabelledLens) { - let box_index = hash_step(&new_lens.label); - self.boxes[box_index as usize].add(new_lens); - } - - fn remove(&mut self, label: String) { - let box_index = hash_step(&label); - self.boxes[box_index as usize].remove(label); - } - - fn score(&self) -> u32 { - self.boxes - .iter() - .enumerate() - .map(|(i, lensbox)| (i as u32 + 1) * lensbox.score()) - .sum() - } -} - -#[test] -fn test_apply_instructions() { - let mut lab = Lab::new(); - - lab.add(LabelledLens::new("rn", 1)); - assert_eq!(1, lab.boxes[0].lenses.len()); - for i in 1..256 { - assert_eq!(0, lab.boxes[i].lenses.len()); - } - - lab.remove("cm".to_string()); - assert_eq!(1, lab.boxes[0].lenses.len()); - for i in 1..256 { - assert_eq!(0, lab.boxes[i].lenses.len()); - } - - lab.add(LabelledLens::new("qp", 3)); - assert_eq!(1, lab.boxes[0].lenses.len()); - assert_eq!(1, lab.boxes[1].lenses.len()); - for i in 2..256 { - assert_eq!(0, lab.boxes[i].lenses.len()); - } - - lab.add(LabelledLens::new("cm", 2)); - assert_eq!(2, lab.boxes[0].lenses.len()); - assert_eq!(1, lab.boxes[1].lenses.len()); - for i in 2..256 { - assert_eq!(0, lab.boxes[i].lenses.len()); - } - - lab.remove("qp".to_string()); - assert_eq!(2, lab.boxes[0].lenses.len()); - for i in 1..256 { - assert_eq!(0, lab.boxes[i].lenses.len()); - } -} - -#[test] -fn test_execute() { - let mut lab = Lab::new(); - - lab.execute(&"rn=1".to_string()); - assert_eq!(1, lab.boxes[0].lenses.len()); - for i in 1..256 { - assert_eq!(0, lab.boxes[i].lenses.len()); - } - - lab.execute(&"cm-".to_string()); - assert_eq!(1, lab.boxes[0].lenses.len()); - for i in 1..256 { - assert_eq!(0, lab.boxes[i].lenses.len()); - } - - lab.execute(&"qp=3".to_string()); - assert_eq!(1, lab.boxes[0].lenses.len()); - assert_eq!(1, lab.boxes[1].lenses.len()); - for i in 2..256 { - assert_eq!(0, lab.boxes[i].lenses.len()); - } - - lab.execute(&"cm=2".to_string()); - assert_eq!(2, lab.boxes[0].lenses.len()); - assert_eq!(1, lab.boxes[1].lenses.len()); - for i in 2..256 { - assert_eq!(0, lab.boxes[i].lenses.len()); - } - - lab.execute(&"qp-".to_string()); - assert_eq!(2, lab.boxes[0].lenses.len()); - for i in 1..256 { - assert_eq!(0, lab.boxes[i].lenses.len()); - } -} - -#[test] -fn test_example() { - let mut lab = Lab::new(); - - lab.execute(&"rn=1".to_string()); - lab.execute(&"cm-".to_string()); - lab.execute(&"qp=3".to_string()); - lab.execute(&"cm=2".to_string()); - lab.execute(&"qp-".to_string()); - lab.execute(&"pc=4".to_string()); - lab.execute(&"ot=9".to_string()); - lab.execute(&"ab=5".to_string()); - lab.execute(&"pc-".to_string()); - lab.execute(&"pc=6".to_string()); - lab.execute(&"ot=7".to_string()); - - assert_eq!(145, lab.score()); -} diff --git a/year_2023/src/day16/mod.rs b/year_2023/src/day16.rs similarity index 66% rename from year_2023/src/day16/mod.rs rename to year_2023/src/day16.rs index a8469fc..587a48f 100644 --- a/year_2023/src/day16/mod.rs +++ b/year_2023/src/day16.rs @@ -1,4 +1,3 @@ -use aoc_utils as utils; use std::collections::HashSet; #[test] @@ -6,10 +5,15 @@ fn test_mine() { execute(); } -pub fn execute() { - let mine = Contraption::from_lines(utils::read_lines("src/day16/mine.txt")); - assert_eq!(7111, mine.energized_count()); - assert_eq!(((55, 0, Direction::Down), 7831), mine.optimize_energizing()); +pub fn execute() -> String { + let mine = Contraption::from_lines(aoc_utils::read_lines("input/day16.txt")); + + let part1 = mine.energized_count(); + + let (_optimal_laser, optimal_count) = mine.optimize_energizing(); + let part2 = optimal_count; + + format!("{} {}", part1, part2) } enum Apparatus { @@ -19,6 +23,7 @@ enum Apparatus { MirrorTopLeftBottomRight, MirrorTopRightBottomLeft, } + impl Apparatus { fn from_char(c: char) -> Apparatus { use Apparatus::*; @@ -31,18 +36,7 @@ impl Apparatus { _ => unreachable!(), } } - // fn to_char(&self) -> char { - // use Apparatus::*; - // match self { - // None => '.', - // SplitterHorizontal => '-', - // SplitterVertical => '|', - // MirrorTopLeftBottomRight => '\\', - // MirrorTopRightBottomLeft => '/', - // } - // } } - #[derive(Eq, PartialEq, Hash, Clone, Debug)] enum Direction { Down, @@ -168,51 +162,61 @@ impl Contraption { } } -#[test] -fn test_laser_progress() { - use Direction::*; - let contraption = Contraption::from_lines(vec![ - r".\..".to_string(), - r"..|.".to_string(), - r"/-/.".to_string(), - r"\.-.".to_string(), - ]); - - assert_eq!(contraption.progress(&(0, 0, Right)), vec![(1, 0, Right)]); - assert_eq!(contraption.progress(&(1, 0, Right)), vec![(1, 1, Down)]); - assert_eq!(contraption.progress(&(1, 1, Down)), vec![(1, 2, Down)]); - assert_eq!( - contraption.progress(&(1, 2, Down)), - vec![(0, 2, Left), (2, 2, Right)] - ); - assert_eq!(contraption.progress(&(0, 2, Left)), vec![(0, 3, Down)]); - assert_eq!(contraption.progress(&(2, 2, Right)), vec![(2, 1, Up)]); - assert_eq!(contraption.progress(&(0, 3, Down)), vec![(1, 3, Right)]); - assert_eq!(contraption.progress(&(2, 1, Up)), vec![(2, 0, Up)]); - assert_eq!(contraption.progress(&(1, 3, Right)), vec![(2, 3, Right)]); - assert_eq!(contraption.progress(&(2, 0, Up)), vec![]); - - assert_eq!(contraption.progress(&(3, 1, Right)), vec![]); - assert_eq!(contraption.progress(&(1, 3, Down)), vec![]); - assert_eq!(contraption.progress(&(0, 1, Left)), vec![]); - assert_eq!(contraption.progress(&(2, 0, Up)), vec![]); -} +#[cfg(test)] +mod tests { + use super::*; -#[test] -fn test_example() { - let example = Contraption::from_lines(vec![ - r".|...\....".to_string(), - r"|.-.\.....".to_string(), - r".....|-...".to_string(), - r"........|.".to_string(), - r"..........".to_string(), - r".........\".to_string(), - r"..../.\\..".to_string(), - r".-.-/..|..".to_string(), - r".|....-|.\".to_string(), - r"..//.|....".to_string(), - ]); - assert_eq!(46, example.energized_count()); - - assert_eq!(((3, 0, Direction::Down), 51), example.optimize_energizing()); + #[test] + fn test_mine() { + assert_eq!(execute(), "7111 7831"); + } + + #[test] + fn test_laser_progress() { + use Direction::*; + let contraption = Contraption::from_lines(vec![ + r".\..".to_string(), + r"..|.".to_string(), + r"/-/.".to_string(), + r"\.-.".to_string(), + ]); + + assert_eq!(contraption.progress(&(0, 0, Right)), vec![(1, 0, Right)]); + assert_eq!(contraption.progress(&(1, 0, Right)), vec![(1, 1, Down)]); + assert_eq!(contraption.progress(&(1, 1, Down)), vec![(1, 2, Down)]); + assert_eq!( + contraption.progress(&(1, 2, Down)), + vec![(0, 2, Left), (2, 2, Right)] + ); + assert_eq!(contraption.progress(&(0, 2, Left)), vec![(0, 3, Down)]); + assert_eq!(contraption.progress(&(2, 2, Right)), vec![(2, 1, Up)]); + assert_eq!(contraption.progress(&(0, 3, Down)), vec![(1, 3, Right)]); + assert_eq!(contraption.progress(&(2, 1, Up)), vec![(2, 0, Up)]); + assert_eq!(contraption.progress(&(1, 3, Right)), vec![(2, 3, Right)]); + assert_eq!(contraption.progress(&(2, 0, Up)), vec![]); + + assert_eq!(contraption.progress(&(3, 1, Right)), vec![]); + assert_eq!(contraption.progress(&(1, 3, Down)), vec![]); + assert_eq!(contraption.progress(&(0, 1, Left)), vec![]); + assert_eq!(contraption.progress(&(2, 0, Up)), vec![]); + } + + #[test] + fn test_example() { + let example = Contraption::from_lines(vec![ + r".|...\....".to_string(), + r"|.-.\.....".to_string(), + r".....|-...".to_string(), + r"........|.".to_string(), + r"..........".to_string(), + r".........\".to_string(), + r"..../.\\..".to_string(), + r".-.-/..|..".to_string(), + r".|....-|.\".to_string(), + r"..//.|....".to_string(), + ]); + assert_eq!(46, example.energized_count()); + + assert_eq!(((3, 0, Direction::Down), 51), example.optimize_energizing()); + } } diff --git a/year_2023/src/day17/mod.rs b/year_2023/src/day17.rs similarity index 65% rename from year_2023/src/day17/mod.rs rename to year_2023/src/day17.rs index b3bed9c..f69f0d3 100644 --- a/year_2023/src/day17/mod.rs +++ b/year_2023/src/day17.rs @@ -1,20 +1,16 @@ -use aoc_utils as utils; use std::cmp::min; use std::collections::HashMap; -#[test] -fn test_mine() { - execute(); -} - -pub fn execute() { - let example_city = City::from_lines(utils::read_lines("src/day17/mine.txt")); +pub fn execute() -> String { + let example_city = City::from_lines(aoc_utils::read_lines("input/day17.txt")); let mut nav1 = Navigator::new(1, 3); - assert_eq!(698, nav1.solve(&example_city)); + let part1 = nav1.solve(&example_city); let mut nav2 = Navigator::new(4, 10); - assert_eq!(825, nav2.solve(&example_city)) + let part2 = nav2.solve(&example_city); + + format!("{} {}", part1, part2) } #[derive(Hash, Eq, PartialEq, Clone, Debug)] @@ -71,20 +67,6 @@ impl City { } } -#[test] -fn test_from_lines() { - let example = City::from_lines(_example()); - assert_eq!(example.heat_loss[&Coord { x: 0, y: 0 }], 2); - assert_eq!(example.heat_loss[&Coord { x: 1, y: 0 }], 4); - assert_eq!(example.heat_loss[&Coord { x: 2, y: 0 }], 1); - assert_eq!(example.heat_loss[&Coord { x: 0, y: 1 }], 3); - assert_eq!(example.heat_loss[&Coord { x: 0, y: 2 }], 3); - - assert_eq!(example.heat_loss[&Coord { x: 12, y: 0 }], 3); - assert_eq!(example.heat_loss[&Coord { x: 0, y: 12 }], 4); - assert_eq!(example.heat_loss[&Coord { x: 12, y: 12 }], 3); -} - fn _example() -> Vec { vec![ "2413432311323".to_string(), @@ -102,7 +84,6 @@ fn _example() -> Vec { "4322674655533".to_string(), ] } - #[derive(Hash, Eq, PartialEq, Clone)] enum Direction { Vertical, @@ -248,83 +229,107 @@ impl Navigator { } } -#[test] -fn test_progress() { - use Direction::*; - - let example_city = City::from_lines(_example()); - let nav = Navigator::new(1, 3); - - let p1 = NavPoint { - coord: Coord { x: 0, y: 0 }, - direction: Horizontal, - }; - let p1_next = nav.progress(&example_city, &p1); - assert_eq!(p1_next.len(), 3); - assert_eq!( - HashMap::from([ - (Coord { x: 0, y: 1 }, 3), - (Coord { x: 0, y: 2 }, 6), - (Coord { x: 0, y: 3 }, 9), - ]), - p1_next - .iter() - .map(|(point, cost)| (point.coord.clone(), *cost)) - .collect::>(), - ); - - let p2 = NavPoint { - coord: Coord { x: 0, y: 0 }, - direction: Vertical, - }; - let p2_next = nav.progress(&example_city, &p2); - assert_eq!(p2_next.len(), 3); - assert_eq!( - HashMap::from([ - (Coord { x: 1, y: 0 }, 4), - (Coord { x: 2, y: 0 }, 5), - (Coord { x: 3, y: 0 }, 8), - ]), - p2_next - .iter() - .map(|(point, cost)| (point.coord.clone(), *cost)) - .collect::>(), - ); -} +#[cfg(test)] +mod tests { + use super::*; -#[test] -fn test_progress_all() { - let example_city = City::from_lines(_example()); - let mut nav = Navigator::new(1, 3); - - nav.progress_all(&example_city); - - assert_eq!(nav.nav_points.len(), 6); - assert_eq!( - HashMap::from([ - (Coord { x: 0, y: 1 }, 3), - (Coord { x: 0, y: 2 }, 6), - (Coord { x: 0, y: 3 }, 9), - (Coord { x: 1, y: 0 }, 4), - (Coord { x: 2, y: 0 }, 5), - (Coord { x: 3, y: 0 }, 8), - ]), - nav.nav_points - .iter() - .map(|point| (point.coord.clone(), *nav.costs.get(point).unwrap())) - .collect::>(), - ); + #[test] + fn test_mine() { + assert_eq!(execute(), "698 825"); + } - assert_eq!(nav.costs.len(), 8); -} + #[test] + fn test_from_lines() { + let example = City::from_lines(_example()); + assert_eq!(example.heat_loss[&Coord { x: 0, y: 0 }], 2); + assert_eq!(example.heat_loss[&Coord { x: 1, y: 0 }], 4); + assert_eq!(example.heat_loss[&Coord { x: 2, y: 0 }], 1); + assert_eq!(example.heat_loss[&Coord { x: 0, y: 1 }], 3); + assert_eq!(example.heat_loss[&Coord { x: 0, y: 2 }], 3); + + assert_eq!(example.heat_loss[&Coord { x: 12, y: 0 }], 3); + assert_eq!(example.heat_loss[&Coord { x: 0, y: 12 }], 4); + assert_eq!(example.heat_loss[&Coord { x: 12, y: 12 }], 3); + } -#[test] -fn test_solve() { - let example_city = City::from_lines(_example()); + #[test] + fn test_progress() { + use Direction::*; - let mut nav1 = Navigator::new(1, 3); - assert_eq!(102, nav1.solve(&example_city)); + let example_city = City::from_lines(_example()); + let nav = Navigator::new(1, 3); - let mut nav2 = Navigator::new(4, 10); - assert_eq!(94, nav2.solve(&example_city)); + let p1 = NavPoint { + coord: Coord { x: 0, y: 0 }, + direction: Horizontal, + }; + let p1_next = nav.progress(&example_city, &p1); + assert_eq!(p1_next.len(), 3); + assert_eq!( + HashMap::from([ + (Coord { x: 0, y: 1 }, 3), + (Coord { x: 0, y: 2 }, 6), + (Coord { x: 0, y: 3 }, 9), + ]), + p1_next + .iter() + .map(|(point, cost)| (point.coord.clone(), *cost)) + .collect::>(), + ); + + let p2 = NavPoint { + coord: Coord { x: 0, y: 0 }, + direction: Vertical, + }; + let p2_next = nav.progress(&example_city, &p2); + assert_eq!(p2_next.len(), 3); + assert_eq!( + HashMap::from([ + (Coord { x: 1, y: 0 }, 4), + (Coord { x: 2, y: 0 }, 5), + (Coord { x: 3, y: 0 }, 8), + ]), + p2_next + .iter() + .map(|(point, cost)| (point.coord.clone(), *cost)) + .collect::>(), + ); + } + + #[test] + fn test_progress_all() { + let example_city = City::from_lines(_example()); + let mut nav = Navigator::new(1, 3); + + nav.progress_all(&example_city); + + assert_eq!(nav.nav_points.len(), 6); + assert_eq!( + HashMap::from([ + (Coord { x: 0, y: 1 }, 3), + (Coord { x: 0, y: 2 }, 6), + (Coord { x: 0, y: 3 }, 9), + (Coord { x: 1, y: 0 }, 4), + (Coord { x: 2, y: 0 }, 5), + (Coord { x: 3, y: 0 }, 8), + ]), + nav.nav_points + .iter() + .map(|point| (point.coord.clone(), *nav.costs.get(point).unwrap())) + .collect::>(), + ); + + assert_eq!(nav.costs.len(), 8); + } + + #[test] + fn test_solve() { + let example_city = City::from_lines(_example()); + + let mut nav1 = Navigator::new(1, 3); + assert_eq!(102, nav1.solve(&example_city)); + + let mut nav2 = Navigator::new(4, 10); + assert_eq!(94, nav2.solve(&example_city)); + } } diff --git a/year_2023/src/day18.rs b/year_2023/src/day18.rs new file mode 100644 index 0000000..0774022 --- /dev/null +++ b/year_2023/src/day18.rs @@ -0,0 +1,239 @@ +use std::ops::Index; + +pub fn execute() -> String { + let canvas1 = Canvas::from_lines_part1(aoc_utils::read_lines("input/day18.txt")); + let part1 = canvas1.to_area(); + + let canvas2 = Canvas::from_lines_part2(aoc_utils::read_lines("input/day18.txt")); + let part2 = canvas2.to_area(); + + format!("{} {}", part1, part2) +} + +#[derive(Eq, PartialEq, Debug)] +enum Direction { + Up, + Down, + Left, + Right, +} + +struct Instruction { + direction: Direction, + distance: i64, +} + +#[derive(Clone, Eq, PartialEq, Hash, Debug)] +struct Coord { + x: i64, + y: i64, +} + +struct Line { + start: Coord, + end: Coord, +} + +struct Canvas { + instructions: Vec, +} + +impl Canvas { + fn from_lines_part1(lines: Vec) -> Canvas { + let instructions = lines + .iter() + .map(|line| { + let (dir_str, rest) = line.split_once(" ").unwrap(); + let (distance_str, _color_str) = rest.split_once(" ").unwrap(); + + let direction = match dir_str { + "U" => Direction::Up, + "D" => Direction::Down, + "L" => Direction::Left, + "R" => Direction::Right, + _ => unreachable!(), + }; + let distance = distance_str.parse::().unwrap(); + + Instruction { + direction, + distance, + } + }) + .collect(); + Canvas { instructions } + } + + fn from_lines_part2(lines: Vec) -> Canvas { + let instructions = lines + .iter() + .map(|line| { + let (_, instruction_str) = line.rsplit_once(" ").unwrap(); + let distance_str = &instruction_str[2..7]; + let dir_str = &instruction_str[7..8]; + + let direction = match dir_str { + "0" => Direction::Right, + "1" => Direction::Down, + "2" => Direction::Left, + "3" => Direction::Up, + _ => unreachable!(), + }; + let distance = i64::from_str_radix(distance_str, 16).unwrap(); + + Instruction { + direction, + distance, + } + }) + .collect(); + Canvas { instructions } + } + + fn to_lines(&self) -> Vec { + use Direction::*; + + let mut current = Coord { x: 0, y: 0 }; + self.instructions + .iter() + .map(|instruction| { + let start = current.clone(); + match instruction.direction { + Up => current.y -= instruction.distance, + Down => current.y += instruction.distance, + Left => current.x -= instruction.distance, + Right => current.x += instruction.distance, + } + let end = current.clone(); + Line { start, end } + }) + .collect() + } + + fn to_area(&self) -> i64 { + let abs = |x: i64| if x < 0 { -x } else { x }; + + let lines = self.to_lines(); + + // Uses the trapezoids formula, but only covers the area drawn by the MIDDLE of the trench + let trapezoids = lines.iter().fold(0, |acc, line| { + acc + (line.start.x - line.end.x) * (line.start.y + line.end.y) + }) / 2; + // So we need to add the other half of the trench + let missing_edges = lines.iter().fold(0, |acc, line| { + acc + abs(line.start.x - line.end.x) + abs(line.start.y - line.end.y) + }) / 2; + // And the outside corners + let missing_corners = self.dominant_turns().1 as i64 / 4; + + trapezoids + missing_edges + missing_corners + } + + fn dominant_turns(&self) -> (Direction, u32) { + use Direction::*; + + let mut left = 0; + let mut right = 0; + + for (i, instruction) in self.instructions.iter().enumerate() { + let prev_index = if i > 0 { + i - 1 + } else { + self.instructions.len() - 1 + }; + let prev = self.instructions.index(prev_index); + + match (&prev.direction, &instruction.direction) { + (Left, Up) | (Right, Down) | (Up, Right) | (Down, Left) => right += 1, + (Left, Down) | (Right, Up) | (Up, Left) | (Down, Right) => left += 1, + _ => unreachable!(), + }; + } + + if left > right { + (Left, left - right) + } else { + (Right, right - left) + } + } +} + +fn _example() -> Vec { + vec![ + "R 6 (#70c710)".to_string(), + "D 5 (#0dc571)".to_string(), + "L 2 (#5713f0)".to_string(), + "D 2 (#d2c081)".to_string(), + "R 2 (#59c680)".to_string(), + "D 2 (#411b91)".to_string(), + "L 5 (#8ceee2)".to_string(), + "U 2 (#caa173)".to_string(), + "L 1 (#1b58a2)".to_string(), + "U 2 (#caa171)".to_string(), + "R 2 (#7807d2)".to_string(), + "U 3 (#a77fa3)".to_string(), + "L 2 (#015232)".to_string(), + "U 2 (#7a21e3)".to_string(), + ] +} +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "62573 54662804037719"); + } + + #[test] + fn test_from_lines() { + use Direction::*; + + let example = Canvas::from_lines_part1(_example()); + + assert_eq!(example.instructions.len(), 14); + + assert!(matches!(example.instructions[0].direction, Right)); + assert!(matches!(example.instructions[1].direction, Down)); + assert!(matches!(example.instructions[2].direction, Left)); + assert!(matches!(example.instructions[13].direction, Up)); + + assert_eq!(example.instructions[0].distance, 6); + assert_eq!(example.instructions[1].distance, 5); + assert_eq!(example.instructions[2].distance, 2); + assert_eq!(example.instructions[13].distance, 2); + } + + #[test] + fn test_to_lines() { + let example = Canvas::from_lines_part1(_example()); + let lines = example.to_lines(); + + assert_eq!(lines.len(), 14); + + assert_eq!(Coord { x: 0, y: 0 }, lines[0].start); + assert_eq!(Coord { x: 6, y: 0 }, lines[0].end); + assert_eq!(Coord { x: 6, y: 0 }, lines[1].start); + assert_eq!(Coord { x: 6, y: 5 }, lines[1].end); + assert_eq!(Coord { x: 6, y: 5 }, lines[2].start); + assert_eq!(Coord { x: 4, y: 5 }, lines[2].end); + assert_eq!(Coord { x: 0, y: 2 }, lines[13].start); + assert_eq!(Coord { x: 0, y: 0 }, lines[13].end); + } + + #[test] + fn test_to_area() { + let part1 = Canvas::from_lines_part1(_example()); + assert_eq!(62, part1.to_area()); + + let part2 = Canvas::from_lines_part2(_example()); + assert_eq!(952408144115, part2.to_area()); + } + + #[test] + fn test_dominant_turns() { + let part1 = Canvas::from_lines_part1(_example()); + + assert_eq!((Direction::Right, 4), part1.dominant_turns()) + } +} diff --git a/year_2023/src/day18/mod.rs b/year_2023/src/day18/mod.rs deleted file mode 100644 index e4fe934..0000000 --- a/year_2023/src/day18/mod.rs +++ /dev/null @@ -1,374 +0,0 @@ -use aoc_utils as utils; -use std::collections::{HashMap, HashSet}; -use std::ops::Index; - -#[test] -fn test_mine() { - execute(); -} - -pub fn execute() { - let part1 = Canvas::from_lines_part1(utils::read_lines("src/day18/mine.txt")); - - let pixels = part1.to_pixels_filled(); - assert_eq!(62573, pixels.len()); - assert_eq!(62573, part1.to_area()); - - // for line in part1.to_lines() { - // println!( - // "", - // line.start.x, line.start.y, line.end.x, line.end.y, line.color, - // ); - // } - - let part2 = Canvas::from_lines_part2(utils::read_lines("src/day18/mine.txt")); - assert_eq!(54662804037719, part2.to_area()); -} - -#[derive(Eq, PartialEq, Debug)] -enum Direction { - Up, - Down, - Left, - Right, -} - -struct Instruction { - direction: Direction, - distance: i64, - color: String, -} - -#[derive(Clone, Eq, PartialEq, Hash, Debug)] -struct Coord { - x: i64, - y: i64, -} - -struct Line { - start: Coord, - end: Coord, - color: String, -} - -struct Canvas { - instructions: Vec, -} - -impl Canvas { - fn from_lines_part1(lines: Vec) -> Canvas { - let instructions = lines - .iter() - .map(|line| { - let (dir_str, rest) = line.split_once(" ").unwrap(); - let (distance_str, color_str) = rest.split_once(" ").unwrap(); - - let direction = match dir_str { - "U" => Direction::Up, - "D" => Direction::Down, - "L" => Direction::Left, - "R" => Direction::Right, - _ => unreachable!(), - }; - let distance = distance_str.parse::().unwrap(); - let color = color_str - .strip_prefix("(") - .unwrap() - .strip_suffix(")") - .unwrap() - .to_string(); - - Instruction { - direction, - distance, - color, - } - }) - .collect(); - Canvas { instructions } - } - - fn from_lines_part2(lines: Vec) -> Canvas { - let instructions = lines - .iter() - .map(|line| { - let (_, instruction_str) = line.rsplit_once(" ").unwrap(); - let distance_str = &instruction_str[2..7]; - let dir_str = &instruction_str[7..8]; - - let direction = match dir_str { - "0" => Direction::Right, - "1" => Direction::Down, - "2" => Direction::Left, - "3" => Direction::Up, - _ => unreachable!(), - }; - let distance = i64::from_str_radix(distance_str, 16).unwrap(); - let color = "#ffffff".to_string(); - - Instruction { - direction, - distance, - color, - } - }) - .collect(); - Canvas { instructions } - } - - fn to_lines(&self) -> Vec { - use Direction::*; - - let mut current = Coord { x: 0, y: 0 }; - self.instructions - .iter() - .map(|instruction| { - let start = current.clone(); - match instruction.direction { - Up => current.y -= instruction.distance, - Down => current.y += instruction.distance, - Left => current.x -= instruction.distance, - Right => current.x += instruction.distance, - } - let end = current.clone(); - Line { - start, - end, - color: instruction.color.clone(), - } - }) - .collect() - } - - fn to_pixels_outline(&self) -> HashMap { - use Direction::*; - - let mut result = HashMap::new(); - - let mut current = Coord { x: 0, y: 0 }; - for instruction in self.instructions.iter() { - for _ in 0..instruction.distance { - match instruction.direction { - Up => current.y -= 1, - Down => current.y += 1, - Left => current.x -= 1, - Right => current.x += 1, - } - result.insert(current.clone(), instruction.color.clone()); - } - } - - result - } - - fn to_pixels_filled(&self) -> HashSet { - let mut result = self.to_pixels_outline().into_keys().collect::>(); - let min = Coord { - x: result.iter().min_by_key(|&coord| coord.x).unwrap().x, - y: result.iter().min_by_key(|&coord| coord.y).unwrap().y, - }; - let max = Coord { - x: result.iter().max_by_key(|&coord| coord.x).unwrap().x, - y: result.iter().max_by_key(|&coord| coord.y).unwrap().y, - }; - - let start = Coord { - x: (min.x + max.x) / 2, - y: (min.y + max.y) / 2, - }; - - let mut frontline = vec![start.clone()]; - result.insert(start); - - while !frontline.is_empty() { - let mut new_frontline = vec![]; - for current in frontline.iter() { - for next in vec![ - Coord { - x: current.x + 1, - y: current.y, - }, - Coord { - x: current.x, - y: current.y + 1, - }, - Coord { - x: current.x - 1, - y: current.y, - }, - Coord { - x: current.x, - y: current.y - 1, - }, - ] { - if next.x >= min.x - && next.x <= max.x - && next.y >= min.y - && next.y <= max.y - && result.insert(next.clone()) - { - new_frontline.push(next); - } - } - } - frontline = new_frontline; - } - - result - } - - fn to_area(&self) -> i64 { - let abs = |x: i64| if x < 0 { -x } else { x }; - - let lines = self.to_lines(); - - // Uses the trapezoids formula, but only covers the area drawn by the MIDDLE of the trench - let trapezoids = lines.iter().fold(0, |acc, line| { - acc + (line.start.x - line.end.x) * (line.start.y + line.end.y) - }) / 2; - // So we need to add the other half of the trench - let missing_edges = lines.iter().fold(0, |acc, line| { - acc + abs(line.start.x - line.end.x) + abs(line.start.y - line.end.y) - }) / 2; - // And the outside corners - let missing_corners = self.dominant_turns().1 as i64 / 4; - - trapezoids + missing_edges + missing_corners - } - - fn dominant_turns(&self) -> (Direction, u32) { - use Direction::*; - - let mut left = 0; - let mut right = 0; - - for (i, instruction) in self.instructions.iter().enumerate() { - let prev_index = if i > 0 { - i - 1 - } else { - self.instructions.len() - 1 - }; - let prev = self.instructions.index(prev_index); - - match (&prev.direction, &instruction.direction) { - (Left, Up) | (Right, Down) | (Up, Right) | (Down, Left) => right += 1, - (Left, Down) | (Right, Up) | (Up, Left) | (Down, Right) => left += 1, - _ => unreachable!(), - }; - } - - if left > right { - (Left, left - right) - } else { - (Right, right - left) - } - } -} - -#[test] -fn test_from_lines() { - use Direction::*; - - let example = Canvas::from_lines_part1(_example()); - - assert_eq!(example.instructions.len(), 14); - - assert!(matches!(example.instructions[0].direction, Right)); - assert!(matches!(example.instructions[1].direction, Down)); - assert!(matches!(example.instructions[2].direction, Left)); - assert!(matches!(example.instructions[13].direction, Up)); - - assert_eq!(example.instructions[0].distance, 6); - assert_eq!(example.instructions[1].distance, 5); - assert_eq!(example.instructions[2].distance, 2); - assert_eq!(example.instructions[13].distance, 2); - - assert_eq!(example.instructions[0].color, "#70c710".to_string()); - assert_eq!(example.instructions[1].color, "#0dc571".to_string()); - assert_eq!(example.instructions[2].color, "#5713f0".to_string()); - assert_eq!(example.instructions[13].color, "#7a21e3".to_string()); -} - -fn _example() -> Vec { - vec![ - "R 6 (#70c710)".to_string(), - "D 5 (#0dc571)".to_string(), - "L 2 (#5713f0)".to_string(), - "D 2 (#d2c081)".to_string(), - "R 2 (#59c680)".to_string(), - "D 2 (#411b91)".to_string(), - "L 5 (#8ceee2)".to_string(), - "U 2 (#caa173)".to_string(), - "L 1 (#1b58a2)".to_string(), - "U 2 (#caa171)".to_string(), - "R 2 (#7807d2)".to_string(), - "U 3 (#a77fa3)".to_string(), - "L 2 (#015232)".to_string(), - "U 2 (#7a21e3)".to_string(), - ] -} - -#[test] -fn test_to_lines() { - let example = Canvas::from_lines_part1(_example()); - let lines = example.to_lines(); - - assert_eq!(lines.len(), 14); - - assert_eq!(Coord { x: 0, y: 0 }, lines[0].start); - assert_eq!(Coord { x: 6, y: 0 }, lines[0].end); - assert_eq!(Coord { x: 6, y: 0 }, lines[1].start); - assert_eq!(Coord { x: 6, y: 5 }, lines[1].end); - assert_eq!(Coord { x: 6, y: 5 }, lines[2].start); - assert_eq!(Coord { x: 4, y: 5 }, lines[2].end); - assert_eq!(Coord { x: 0, y: 2 }, lines[13].start); - assert_eq!(Coord { x: 0, y: 0 }, lines[13].end); - - assert_eq!(lines[0].color, "#70c710".to_string()); - assert_eq!(lines[1].color, "#0dc571".to_string()); - assert_eq!(lines[2].color, "#5713f0".to_string()); - assert_eq!(lines[13].color, "#7a21e3".to_string()); -} - -#[test] -#[ignore] -fn test_make_svg() { - let example = Canvas::from_lines_part2(_example()); - for line in example.to_lines() { - println!( - "", - line.start.x, line.start.y, line.end.x, line.end.y, line.color, - ); - } -} - -#[test] -fn test_to_pixels_outline() { - let example = Canvas::from_lines_part1(_example()); - let pixels = example.to_pixels_outline(); - - assert_eq!(38, pixels.len()) -} - -#[test] -fn test_to_pixels_filled() { - let part1 = Canvas::from_lines_part1(_example()); - let pixels_part1 = part1.to_pixels_filled(); - assert_eq!(62, pixels_part1.len()); -} - -#[test] -fn test_to_area() { - let part1 = Canvas::from_lines_part1(_example()); - assert_eq!(62, part1.to_area()); - - let part2 = Canvas::from_lines_part2(_example()); - assert_eq!(952408144115, part2.to_area()); -} - -#[test] -fn test_dominant_turns() { - let part1 = Canvas::from_lines_part1(_example()); - - assert_eq!((Direction::Right, 4), part1.dominant_turns()) -} diff --git a/year_2023/src/day19/mod.rs b/year_2023/src/day19.rs similarity index 57% rename from year_2023/src/day19/mod.rs rename to year_2023/src/day19.rs index 2662ace..00a7d04 100644 --- a/year_2023/src/day19/mod.rs +++ b/year_2023/src/day19.rs @@ -1,31 +1,19 @@ -use aoc_utils as utils; use std::collections::HashMap; use std::fmt::Debug; -#[test] -fn test_mine() { - execute(); -} -pub fn execute() { - let mine = TriageCenter::from_lines(utils::read_lines("src/day19/mine.txt")); - assert_eq!(376008, mine.process()); - - assert_eq!(124078207789312, mine.count_combinations()); -} +pub fn execute() -> String { + let mine = TriageCenter::from_lines(aoc_utils::read_lines("input/day19.txt")); -#[test] -fn test_example() { - let example = TriageCenter::from_lines(_example()); - assert_eq!(19114, example.process()); + let part1 = mine.process(); + let part2 = mine.count_combinations(); - assert_eq!(167409079868000, example.count_combinations()); + format!("{} {}", part1, part2) } struct TriageCenter { parts: Vec, workflows: HashMap, } - impl TriageCenter { fn from_lines(lines: Vec) -> TriageCenter { let mut workflow_strs = Vec::new(); @@ -178,36 +166,9 @@ enum Branch { False(Condition), } -#[test] -fn test_process_part() { - let example = TriageCenter::from_lines(_example()); - - assert!(matches!( - example.process_part(&_test_part(787, 2655, 1222, 2876)), - Decision::Accept - )); - assert!(matches!( - example.process_part(&_test_part(1679, 44, 2067, 496)), - Decision::Reject - )); - assert!(matches!( - example.process_part(&_test_part(2036, 264, 79, 2244)), - Decision::Accept - )); - assert!(matches!( - example.process_part(&_test_part(2461, 1339, 466, 291)), - Decision::Reject - )); - assert!(matches!( - example.process_part(&_test_part(2127, 1623, 2188, 1013)), - Decision::Accept - )); -} - struct Part { ratings: HashMap, } - impl Part { fn from_line(line: &String) -> Part { let ratings = line @@ -259,29 +220,10 @@ impl Workflow { } } -#[test] -fn test_workflow_decide() { - let wf = Workflow::from_line(&"px{a<2006:qkq,m>2090:A,rfg}".to_string()).1; - - assert!(matches!( - wf.decide(&_test_part(0, 0, 1000, 0)), - Decision::Redirect(next) if next == "qkq", - )); - assert!(matches!( - wf.decide(&_test_part(0, 3000, 3000, 0)), - Decision::Accept - )); - assert!(matches!( - wf.decide(&_test_part(0, 1000, 3000, 0)), - Decision::Redirect(next) if next == "rfg" - )); -} - struct Rule { condition: Option, decision: Decision, } - impl Rule { fn from_string(s: &str) -> Rule { if s.contains(":") { @@ -314,50 +256,12 @@ impl Rule { } } -#[test] -fn test_rule_decide() { - let rule1 = Rule::from_string("x<10:qkq"); - assert!(matches!(rule1.decide(&_test_part(20, 0, 0, 0)), None)); - assert!(matches!(rule1.decide(&_test_part(10, 0, 0, 0)), None)); - assert!(matches!( - rule1.decide(&_test_part(9, 3, 4, 5)), - Some(Decision::Redirect(next)) if next == "qkq", - )); - - let rule2 = Rule::from_string("m>10:A"); - assert!(matches!(rule2.decide(&_test_part(0, 2, 0, 0)), None)); - assert!(matches!(rule2.decide(&_test_part(0, 10, 0, 0)), None)); - assert!(matches!( - rule2.decide(&_test_part(15, 30, 40, 50)), - Some(Decision::Accept) - )); - - let rule3 = Rule::from_string("rfg"); - assert!(matches!( - rule3.decide(&_test_part(0, 0, 0, 0)), - Some(Decision::Redirect(next)) if next == "rfg")); - assert!(matches!( - rule3.decide(&_test_part(10,10, 10, 10)), - Some(Decision::Redirect(next)) if next == "rfg")); - - let rule4 = Rule::from_string("R"); - assert!(matches!( - rule4.decide(&_test_part(0, 0, 0, 0)), - Some(Decision::Reject) - )); - assert!(matches!( - rule4.decide(&_test_part(10, 10, 10, 10)), - Some(Decision::Reject) - )); -} - #[derive(Eq, PartialEq, Clone)] struct Condition { category: Category, test: Test, threshold: i32, } - impl Condition { fn from_string(s: &str) -> Condition { let category = Category::from_string(&s[0..1]); @@ -392,52 +296,6 @@ impl Debug for Condition { } } -#[test] -fn test_condition_matches() { - let c1 = Condition::from_string("a<1234"); - for i in [-10, 0, 1233, 1234, 1235] { - for j in [-10, 0, 1233, 1234, 1235] { - let part = _test_part(j, j, i, j); - assert_eq!(c1.matches(&part), i < 1234); - } - } - - let c2 = Condition::from_string("a>1234"); - for i in [-10, 0, 1233, 1234, 1235] { - for j in [-10, 0, 1233, 1234, 1235] { - let part = _test_part(j, j, i, j); - assert_eq!(c2.matches(&part), i > 1234); - } - } - - let part = _test_part(2, 3, 4, 5); - - let condition = |ct, val| Condition::from_string(&format!("{ct}{val}").to_string()); - - for i in -1..7 { - assert_eq!(condition("x>", i).matches(&part), 2 > i); - assert_eq!(condition("m>", i).matches(&part), 3 > i); - assert_eq!(condition("a>", i).matches(&part), 4 > i); - assert_eq!(condition("s>", i).matches(&part), 5 > i); - - assert_eq!(condition("x<", i).matches(&part), 2 < i); - assert_eq!(condition("m<", i).matches(&part), 3 < i); - assert_eq!(condition("a<", i).matches(&part), 4 < i); - assert_eq!(condition("s<", i).matches(&part), 5 < i); - } -} - -fn _test_part(x: i32, m: i32, a: i32, s: i32) -> Part { - Part { - ratings: HashMap::from([ - (Category::X, x), - (Category::M, m), - (Category::A, a), - (Category::S, s), - ]), - } -} - #[derive(Hash, Eq, PartialEq, Clone, Debug)] enum Category { X, @@ -445,7 +303,6 @@ enum Category { A, S, } - impl Category { fn from_string(s: &str) -> Category { match s { @@ -507,64 +364,209 @@ impl Decision { } } -#[test] -fn test_from_lines() { - let example = TriageCenter::from_lines(_example()); - - assert_eq!(11, example.workflows.len()); - - let wf1 = &example.get_workflow(&"px".to_string()); - assert_eq!(3, wf1.rules.len()); - let wf1r1 = &wf1.rules[0]; - assert!(matches!(&wf1r1.decision, Decision::Redirect(to) if to == "qkq")); - let wf1r1cond = wf1r1.condition.as_ref().unwrap(); - assert_eq!(Category::A, wf1r1cond.category); - assert_eq!(Test::LessThan, wf1r1cond.test); - assert_eq!(2006, wf1r1cond.threshold); - - let wf1r2 = &wf1.rules[1]; - assert!(matches!(&wf1r2.decision, Decision::Accept,)); - let wf1r2cond = wf1r2.condition.as_ref().unwrap(); - assert_eq!(Category::M, wf1r2cond.category); - assert_eq!(Test::MoreThan, wf1r2cond.test); - assert_eq!(2090, wf1r2cond.threshold); - - let wf1r3 = &wf1.rules[2]; - assert!(matches!(&wf1r3.decision, Decision::Redirect(to) if to == "rfg")); - assert!(matches!(wf1r3.condition, None)); - - assert_eq!(5, example.parts.len()); - assert!(example.parts.iter().all(|part| part.ratings.len() == 4)); - - assert_eq!(787, example.parts[0].ratings[&Category::X]); - assert_eq!(2655, example.parts[0].ratings[&Category::M]); - assert_eq!(1222, example.parts[0].ratings[&Category::A]); - assert_eq!(2876, example.parts[0].ratings[&Category::S]); - - assert_eq!(2127, example.parts[4].ratings[&Category::X]); - assert_eq!(1623, example.parts[4].ratings[&Category::M]); - assert_eq!(2188, example.parts[4].ratings[&Category::A]); - assert_eq!(1013, example.parts[4].ratings[&Category::S]); -} +#[cfg(test)] +mod tests { + use super::*; -fn _example() -> Vec { - vec![ - "px{a<2006:qkq,m>2090:A,rfg}".to_string(), - "pv{a>1716:R,A}".to_string(), - "lnx{m>1548:A,A}".to_string(), - "rfg{s<537:gd,x>2440:R,A}".to_string(), - "qs{s>3448:A,lnx}".to_string(), - "qkq{x<1416:A,crn}".to_string(), - "crn{x>2662:A,R}".to_string(), - "in{s<1351:px,qqz}".to_string(), - "qqz{s>2770:qs,m<1801:hdj,R}".to_string(), - "gd{a>3333:R,R}".to_string(), - "hdj{m>838:A,pv}".to_string(), - "".to_string(), - "{x=787,m=2655,a=1222,s=2876}".to_string(), - "{x=1679,m=44,a=2067,s=496}".to_string(), - "{x=2036,m=264,a=79,s=2244}".to_string(), - "{x=2461,m=1339,a=466,s=291}".to_string(), - "{x=2127,m=1623,a=2188,s=1013}".to_string(), - ] + #[test] + fn test_mine() { + assert_eq!(execute(), "376008 124078207789312"); + } + + #[test] + fn test_example() { + let example = TriageCenter::from_lines(_example()); + assert_eq!(19114, example.process()); + + assert_eq!(167409079868000, example.count_combinations()); + } + + #[test] + fn test_process_part() { + let example = TriageCenter::from_lines(_example()); + + assert!(matches!( + example.process_part(&_test_part(787, 2655, 1222, 2876)), + Decision::Accept + )); + assert!(matches!( + example.process_part(&_test_part(1679, 44, 2067, 496)), + Decision::Reject + )); + assert!(matches!( + example.process_part(&_test_part(2036, 264, 79, 2244)), + Decision::Accept + )); + assert!(matches!( + example.process_part(&_test_part(2461, 1339, 466, 291)), + Decision::Reject + )); + assert!(matches!( + example.process_part(&_test_part(2127, 1623, 2188, 1013)), + Decision::Accept + )); + } + + #[test] + fn test_workflow_decide() { + let wf = Workflow::from_line(&"px{a<2006:qkq,m>2090:A,rfg}".to_string()).1; + + assert!(matches!( + wf.decide(&_test_part(0, 0, 1000, 0)), + Decision::Redirect(next) if next == "qkq", + )); + assert!(matches!( + wf.decide(&_test_part(0, 3000, 3000, 0)), + Decision::Accept + )); + assert!(matches!( + wf.decide(&_test_part(0, 1000, 3000, 0)), + Decision::Redirect(next) if next == "rfg" + )); + } + + #[test] + fn test_rule_decide() { + let rule1 = Rule::from_string("x<10:qkq"); + assert!(matches!(rule1.decide(&_test_part(20, 0, 0, 0)), None)); + assert!(matches!(rule1.decide(&_test_part(10, 0, 0, 0)), None)); + assert!(matches!( + rule1.decide(&_test_part(9, 3, 4, 5)), + Some(Decision::Redirect(next)) if next == "qkq", + )); + + let rule2 = Rule::from_string("m>10:A"); + assert!(matches!(rule2.decide(&_test_part(0, 2, 0, 0)), None)); + assert!(matches!(rule2.decide(&_test_part(0, 10, 0, 0)), None)); + assert!(matches!( + rule2.decide(&_test_part(15, 30, 40, 50)), + Some(Decision::Accept) + )); + + let rule3 = Rule::from_string("rfg"); + assert!(matches!( + rule3.decide(&_test_part(0, 0, 0, 0)), + Some(Decision::Redirect(next)) if next == "rfg")); + assert!(matches!( + rule3.decide(&_test_part(10,10, 10, 10)), + Some(Decision::Redirect(next)) if next == "rfg")); + + let rule4 = Rule::from_string("R"); + assert!(matches!( + rule4.decide(&_test_part(0, 0, 0, 0)), + Some(Decision::Reject) + )); + assert!(matches!( + rule4.decide(&_test_part(10, 10, 10, 10)), + Some(Decision::Reject) + )); + } + + #[test] + fn test_condition_matches() { + let c1 = Condition::from_string("a<1234"); + for i in [-10, 0, 1233, 1234, 1235] { + for j in [-10, 0, 1233, 1234, 1235] { + let part = _test_part(j, j, i, j); + assert_eq!(c1.matches(&part), i < 1234); + } + } + + let c2 = Condition::from_string("a>1234"); + for i in [-10, 0, 1233, 1234, 1235] { + for j in [-10, 0, 1233, 1234, 1235] { + let part = _test_part(j, j, i, j); + assert_eq!(c2.matches(&part), i > 1234); + } + } + + let part = _test_part(2, 3, 4, 5); + + let condition = |ct, val| Condition::from_string(&format!("{ct}{val}").to_string()); + + for i in -1..7 { + assert_eq!(condition("x>", i).matches(&part), 2 > i); + assert_eq!(condition("m>", i).matches(&part), 3 > i); + assert_eq!(condition("a>", i).matches(&part), 4 > i); + assert_eq!(condition("s>", i).matches(&part), 5 > i); + + assert_eq!(condition("x<", i).matches(&part), 2 < i); + assert_eq!(condition("m<", i).matches(&part), 3 < i); + assert_eq!(condition("a<", i).matches(&part), 4 < i); + assert_eq!(condition("s<", i).matches(&part), 5 < i); + } + } + + fn _test_part(x: i32, m: i32, a: i32, s: i32) -> Part { + Part { + ratings: HashMap::from([ + (Category::X, x), + (Category::M, m), + (Category::A, a), + (Category::S, s), + ]), + } + } + + #[test] + fn test_from_lines() { + let example = TriageCenter::from_lines(_example()); + + assert_eq!(11, example.workflows.len()); + + let wf1 = &example.get_workflow(&"px".to_string()); + assert_eq!(3, wf1.rules.len()); + let wf1r1 = &wf1.rules[0]; + assert!(matches!(&wf1r1.decision, Decision::Redirect(to) if to == "qkq")); + let wf1r1cond = wf1r1.condition.as_ref().unwrap(); + assert_eq!(Category::A, wf1r1cond.category); + assert_eq!(Test::LessThan, wf1r1cond.test); + assert_eq!(2006, wf1r1cond.threshold); + + let wf1r2 = &wf1.rules[1]; + assert!(matches!(&wf1r2.decision, Decision::Accept,)); + let wf1r2cond = wf1r2.condition.as_ref().unwrap(); + assert_eq!(Category::M, wf1r2cond.category); + assert_eq!(Test::MoreThan, wf1r2cond.test); + assert_eq!(2090, wf1r2cond.threshold); + + let wf1r3 = &wf1.rules[2]; + assert!(matches!(&wf1r3.decision, Decision::Redirect(to) if to == "rfg")); + assert!(matches!(wf1r3.condition, None)); + + assert_eq!(5, example.parts.len()); + assert!(example.parts.iter().all(|part| part.ratings.len() == 4)); + + assert_eq!(787, example.parts[0].ratings[&Category::X]); + assert_eq!(2655, example.parts[0].ratings[&Category::M]); + assert_eq!(1222, example.parts[0].ratings[&Category::A]); + assert_eq!(2876, example.parts[0].ratings[&Category::S]); + + assert_eq!(2127, example.parts[4].ratings[&Category::X]); + assert_eq!(1623, example.parts[4].ratings[&Category::M]); + assert_eq!(2188, example.parts[4].ratings[&Category::A]); + assert_eq!(1013, example.parts[4].ratings[&Category::S]); + } + + fn _example() -> Vec { + vec![ + "px{a<2006:qkq,m>2090:A,rfg}".to_string(), + "pv{a>1716:R,A}".to_string(), + "lnx{m>1548:A,A}".to_string(), + "rfg{s<537:gd,x>2440:R,A}".to_string(), + "qs{s>3448:A,lnx}".to_string(), + "qkq{x<1416:A,crn}".to_string(), + "crn{x>2662:A,R}".to_string(), + "in{s<1351:px,qqz}".to_string(), + "qqz{s>2770:qs,m<1801:hdj,R}".to_string(), + "gd{a>3333:R,R}".to_string(), + "hdj{m>838:A,pv}".to_string(), + "".to_string(), + "{x=787,m=2655,a=1222,s=2876}".to_string(), + "{x=1679,m=44,a=2067,s=496}".to_string(), + "{x=2036,m=264,a=79,s=2244}".to_string(), + "{x=2461,m=1339,a=466,s=291}".to_string(), + "{x=2127,m=1623,a=2188,s=1013}".to_string(), + ] + } } diff --git a/year_2023/src/day2/mod.rs b/year_2023/src/day2.rs similarity index 76% rename from year_2023/src/day2/mod.rs rename to year_2023/src/day2.rs index 99efce3..1d6be89 100644 --- a/year_2023/src/day2/mod.rs +++ b/year_2023/src/day2.rs @@ -1,33 +1,17 @@ -use aoc_utils as utils; use std::cmp::max; -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { +pub fn execute() -> String { + let input = aoc_utils::read_lines("input/day2.txt"); let bag_content = CubeHand { red: 12, green: 13, blue: 14, }; - let input = utils::read_lines("src/day2/mine.txt"); - assert_eq!(2317, sum_possibles(&bag_content, &input)); - assert_eq!(74804, sum_powers(&input)); -} - -#[test] -fn test_sum_possibles() { - let bag_content = CubeHand { - red: 12, - green: 13, - blue: 14, - }; + let part1 = sum_possibles(&bag_content, &input); + let part2 = sum_powers(&input); - let example = utils::read_lines("src/day2/example.txt"); - assert_eq!(8, sum_possibles(&bag_content, &example)); + format!("{} {}", part1, part2) } fn sum_possibles(bag: &CubeHand, games: &Vec) -> u32 { @@ -41,12 +25,6 @@ fn sum_possibles(bag: &CubeHand, games: &Vec) -> u32 { return total; } -#[test] -fn test_sum_powers() { - let example = utils::read_lines("src/day2/example.txt"); - assert_eq!(2286, sum_powers(&example)); -} - fn sum_powers(games: &Vec) -> u32 { let mut total: u32 = 0; for line in games { @@ -130,3 +108,31 @@ impl CubeHand { return self.red * self.green * self.blue; } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "2317 74804"); + } + + #[test] + fn test_sum_possibles() { + let bag_content = CubeHand { + red: 12, + green: 13, + blue: 14, + }; + + let example = aoc_utils::read_lines("input/day2-example.txt"); + assert_eq!(8, sum_possibles(&bag_content, &example)); + } + + #[test] + fn test_sum_powers() { + let example = aoc_utils::read_lines("input/day2-example.txt"); + assert_eq!(2286, sum_powers(&example)); + } +} diff --git a/year_2023/src/day20.rs b/year_2023/src/day20.rs new file mode 100644 index 0000000..27fcbdb --- /dev/null +++ b/year_2023/src/day20.rs @@ -0,0 +1,491 @@ +use std::collections::{HashMap, VecDeque}; + +pub fn execute() -> String { + let mut mine = Desert::from_lines(aoc_utils::read_lines("input/day20.txt")); + let (mut seen_low, mut seen_high) = mine.button_press(&mut HashMap::new()); + for _ in 1..1000 { + (seen_low, seen_high) = mine.button_press(&mut HashMap::new()); + } + let part1 = seen_low * seen_high; + + let mut desert = Desert::from_lines(aoc_utils::read_lines("input/day20.txt")); + let part2 = desert.find_min_button_presses_for_rx(); + + format!("{} {}", part1, part2) +} + +struct Desert { + modules: Vec>, + pulses: VecDeque, + pulses_seen_low: u32, + pulses_seen_high: u32, +} + +struct Pulse { + from: String, + to: String, + high: bool, +} + +impl Desert { + fn from_lines(lines: Vec) -> Desert { + let mut inputs: HashMap> = HashMap::new(); + let mut modules: Vec> = lines + .iter() + .map(|line| -> Box { + let (module_str, destinations_str) = line.split_once(" -> ").unwrap(); + let destinations = destinations_str + .split(", ") + .map(String::from) + .collect::>(); + let type_str = &module_str[0..1]; + let name = match type_str { + "%" => &module_str[1..], + "&" => &module_str[1..], + _ => &module_str, + } + .to_string(); + + for dest in destinations.iter() { + inputs + .entry(dest.clone()) + .or_insert(Vec::new()) + .push(name.clone()); + } + + match type_str { + "%" => Box::new(FlipFlop::new(name.clone(), destinations)), + "&" => Box::new(Conjunction::new(name.clone(), destinations)), + _ => Box::new(Broadcast::new(name.clone(), destinations)), + } + }) + .collect(); + + let empty: Vec = vec![]; + for module in modules.iter_mut() { + let name = module.name(); + module.reset_inputs(inputs.get(name).unwrap_or(&empty).clone()); + } + + Desert { + modules, + pulses: VecDeque::new(), + pulses_seen_low: 0, + pulses_seen_high: 0, + } + } + + fn get_module_mut(&mut self, name: &String) -> Option<&mut Box> { + self.modules.iter_mut().find(|module| module.name() == name) + } + + fn send_pulses(&mut self, pulses: Vec) { + for pulse in &pulses { + if pulse.high { + self.pulses_seen_high += 1 + } else { + self.pulses_seen_low += 1 + } + } + self.pulses.extend(pulses.into_iter()); + } + + fn button_press(&mut self, probes: &mut HashMap>) -> (u32, u32) { + self.send_pulses(vec![Pulse { + from: "button".to_string(), + to: "broadcaster".to_string(), + high: false, + }]); + + while !self.pulses.is_empty() { + let pulse = self.pulses.pop_front().unwrap(); + if let Some(module) = self.get_module_mut(&pulse.to) { + probes + .entry(pulse.from.clone()) + .and_modify(|v| v.push(pulse.high)); + + let pulses = module.pulse(pulse.high, pulse.from); + self.send_pulses(pulses); + } + } + + (self.pulses_seen_low, self.pulses_seen_high) + } + + fn find_min_button_presses_for_rx(&mut self) -> usize { + let parent_name = self + .modules + .iter() + .find(|&module| module.destinations().contains(&"rx".to_string())) + .unwrap() + .name(); + let grandparents = self + .modules + .iter() + .filter_map(|module| { + module + .destinations() + .contains(&parent_name) + .then_some(module.name().clone()) + }) + .collect::>(); + + let mut first_high_pulse = HashMap::::new(); + + let mut pulse_probes: HashMap> = HashMap::new(); + for gparent in &grandparents { + pulse_probes.insert(gparent.clone(), vec![]); + } + + for n in 1.. { + self.button_press(&mut pulse_probes); + + for (module, pulses) in &pulse_probes { + if pulses.iter().any(|&pulse| pulse) { + first_high_pulse.entry(module.clone()).or_insert(n); + } + } + pulse_probes.values_mut().for_each(|v| v.clear()); + + if first_high_pulse.len() == grandparents.len() { + break; + } + } + + let prime_factors = first_high_pulse + .values() + .map(|v| aoc_utils::prime_factors(&(*v as u32))) + .collect::>(); + let mut common_factors = vec![]; + for factor in prime_factors[0].keys() { + if let Some(min_common) = prime_factors + .iter() + .map(|factors| factors.get(factor).unwrap_or(&0)) + .min() + { + common_factors.push((factor, *min_common)); + } + } + let divider = common_factors + .iter() + .filter_map(|(&factor, repeats)| (*repeats > 0).then_some(factor * repeats)) + .product::(); + let cycles = first_high_pulse + .values() + .map(|cycle| cycle / (divider as usize)); + + cycles.product() + } +} + +trait Module { + fn reset_inputs(&mut self, _inputs: Vec) {} + fn name(&self) -> &String; + fn destinations(&self) -> &Vec; + fn pulse(&mut self, high: bool, from: String) -> Vec; +} +struct FlipFlop { + name: String, + is_on: bool, + destinations: Vec, +} + +impl FlipFlop { + fn new(name: String, destinations: Vec) -> FlipFlop { + FlipFlop { + name, + is_on: false, + destinations, + } + } +} + +impl Module for FlipFlop { + fn reset_inputs(&mut self, _inputs: Vec) {} + fn name(&self) -> &String { + &self.name + } + fn destinations(&self) -> &Vec { + &self.destinations + } + fn pulse(&mut self, high: bool, _from: String) -> Vec { + if high { + vec![] + } else { + self.is_on = !self.is_on; + self.destinations + .iter() + .map(|dest| Pulse { + from: self.name.clone(), + to: dest.clone(), + high: self.is_on, + }) + .collect() + } + } +} + +struct Conjunction { + name: String, + inputs: HashMap, + destinations: Vec, +} +impl Conjunction { + fn new(name: String, destinations: Vec) -> Conjunction { + Conjunction { + name, + inputs: HashMap::new(), + destinations, + } + } +} + +impl Module for Conjunction { + fn reset_inputs(&mut self, inputs: Vec) { + self.inputs = inputs.iter().map(|input| (input.clone(), false)).collect(); + } + fn name(&self) -> &String { + &self.name + } + fn destinations(&self) -> &Vec { + &self.destinations + } + fn pulse(&mut self, high: bool, from: String) -> Vec { + if !self.inputs.contains_key(&from) { + panic!("Unknown input {}", from); + } + self.inputs.entry(from).and_modify(|h| *h = high); + + let send_high = !self.inputs.values().all(|&high| high); + self.destinations + .iter() + .map(|dest| Pulse { + from: self.name.clone(), + to: dest.clone(), + high: send_high, + }) + .collect() + } +} + +struct Broadcast { + name: String, + destinations: Vec, +} +impl Broadcast { + fn new(name: String, destinations: Vec) -> Broadcast { + Broadcast { name, destinations } + } +} + +impl Module for Broadcast { + fn name(&self) -> &String { + &self.name + } + fn destinations(&self) -> &Vec { + &self.destinations + } + fn pulse(&mut self, high: bool, _from: String) -> Vec { + self.destinations + .iter() + .map(|dest| Pulse { + from: self.name.clone(), + to: dest.clone(), + high, + }) + .collect() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + use std::ops::Deref; + + #[test] + fn test_mine() { + assert_eq!(execute(), "861743850 247023644760071"); + } + + #[test] + fn test_from_lines() { + let mut example1 = Desert::from_lines(vec![ + "broadcaster -> a, b, c".to_string(), + "%a -> b".to_string(), + "%b -> c".to_string(), + "%c -> inv".to_string(), + "&inv -> a".to_string(), + ]); + + assert_eq!(5, example1.modules.len()); + assert_eq!( + HashSet::from([ + "broadcaster".to_string(), + "a".to_string(), + "b".to_string(), + "c".to_string(), + "inv".to_string() + ]), + example1 + .modules + .iter() + .map(|m| m.name().clone()) + .collect::>() + ); + assert_eq!( + vec!["a".to_string(), "b".to_string(), "c".to_string(),], + *example1 + .get_module(&"broadcaster".to_string()) + .unwrap() + .deref() + .destinations() + ); + } + + impl Desert { + fn get_module(&mut self, name: &String) -> Option<&Box> { + self.modules.iter().find(|module| module.name() == name) + } + } + + #[test] + fn test_button_press() { + let mut example1 = Desert::from_lines(vec![ + "broadcaster -> a, b, c".to_string(), + "%a -> b".to_string(), + "%b -> c".to_string(), + "%c -> inv".to_string(), + "&inv -> a".to_string(), + ]); + + let (mut seen_low, mut seen_high) = example1.button_press(&mut HashMap::new()); + assert_eq!(8, seen_low); + assert_eq!(4, seen_high); + + for _ in 1..1000 { + (seen_low, seen_high) = example1.button_press(&mut HashMap::new()); + } + assert_eq!(8000, seen_low); + assert_eq!(4000, seen_high); + + let mut example2 = Desert::from_lines(vec![ + "broadcaster -> a".to_string(), + "%a -> inv, con".to_string(), + "&inv -> b".to_string(), + "%b -> con".to_string(), + "&con -> output".to_string(), + ]); + let (mut seen_low, mut seen_high) = example2.button_press(&mut HashMap::new()); + for _ in 1..1000 { + (seen_low, seen_high) = example2.button_press(&mut HashMap::new()); + } + assert_eq!(4250, seen_low); + assert_eq!(2750, seen_high); + } + + #[test] + fn test_pulse_flipflop() { + let mut module = FlipFlop::new("foo".to_string(), vec!["bar".to_string()]); + assert!(!module.is_on); + assert_eq!(module.destinations.len(), 1); + + let pulses = module.pulse(true, "baz".to_string()); + assert!(!module.is_on); + assert_eq!(pulses.len(), 0); + + let pulses = module.pulse(false, "baz".to_string()); + assert!(module.is_on); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].from, "foo".to_string()); + assert_eq!(pulses[0].to, "bar".to_string()); + assert_eq!(pulses[0].high, true); + + let pulses = module.pulse(true, "baz".to_string()); + assert!(module.is_on); + assert_eq!(pulses.len(), 0); + + let pulses = module.pulse(false, "baz".to_string()); + assert!(!module.is_on); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].from, "foo".to_string()); + assert_eq!(pulses[0].to, "bar".to_string()); + assert_eq!(pulses[0].high, false); + } + + #[test] + fn test_conjunction_pulse_one_input() { + let mut module = Conjunction::new("inv".to_string(), vec!["out".to_string()]); + module.reset_inputs(vec!["inp".to_string()]); + + let pulses = module.pulse(false, "inp".to_string()); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].from, "inv".to_string()); + assert_eq!(pulses[0].to, "out".to_string()); + assert_eq!(pulses[0].high, true); + + let pulses = module.pulse(false, "inp".to_string()); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].high, true); + + let pulses = module.pulse(true, "inp".to_string()); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].high, false); + + let pulses = module.pulse(true, "inp".to_string()); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].high, false); + + let pulses = module.pulse(false, "inp".to_string()); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].high, true); + } + + #[test] + fn test_conjunction_pulse_multiple_input() { + let mut module = Conjunction::new("conj".to_string(), vec!["out".to_string()]); + module.reset_inputs(vec![ + "bim".to_string(), + "bam".to_string(), + "boom".to_string(), + ]); + + let pulses = module.pulse(false, "bim".to_string()); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].from, "conj".to_string()); + assert_eq!(pulses[0].to, "out".to_string()); + assert_eq!(pulses[0].high, true); + + let pulses = module.pulse(true, "bim".to_string()); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].high, true); + + let pulses = module.pulse(true, "bam".to_string()); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].high, true); + + let pulses = module.pulse(true, "boom".to_string()); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].high, false); + + let pulses = module.pulse(false, "bam".to_string()); + assert_eq!(pulses.len(), 1); + assert_eq!(pulses[0].high, true); + } + + #[test] + #[should_panic] + fn test_conjunction_pulse_without_inputs() { + let mut module = Conjunction::new("foo".to_string(), vec!["bar".to_string()]); + module.reset_inputs(vec![]); + module.pulse(false, "baz".to_string()); + } + + #[test] + #[should_panic] + fn test_conjunction_pulse_with_unknown_input() { + let mut no_input = Conjunction::new("foo".to_string(), vec!["bar".to_string()]); + no_input.reset_inputs(vec!["baz".to_string()]); + no_input.pulse(false, "boo".to_string()); + } +} diff --git a/year_2023/src/day20/mod.rs b/year_2023/src/day20/mod.rs deleted file mode 100644 index 792212f..0000000 --- a/year_2023/src/day20/mod.rs +++ /dev/null @@ -1,415 +0,0 @@ -use aoc_utils as utils; -use std::collections::{HashMap, HashSet, VecDeque}; -use std::ops::Deref; - -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { - let mut mine = Desert::from_lines(utils::read_lines("src/day20/mine.txt")); - let (mut seen_low, mut seen_high) = mine.button_press(); - for _ in 1..1000 { - (seen_low, seen_high) = mine.button_press(); - } - assert_eq!(18150, seen_low); - assert_eq!(47479, seen_high); - - assert_eq!(861743850, seen_low * seen_high); -} - -struct Desert { - modules: Vec>, - pulses: VecDeque, - pulses_seen_low: u32, - pulses_seen_high: u32, -} - -struct Pulse { - from: String, - to: String, - high: bool, -} - -impl Desert { - fn from_lines(lines: Vec) -> Desert { - let mut inputs: HashMap> = HashMap::new(); - let mut modules: Vec> = lines - .iter() - .map(|line| -> Box { - let (module_str, destinations_str) = line.split_once(" -> ").unwrap(); - let destinations = destinations_str - .split(", ") - .map(String::from) - .collect::>(); - let type_str = &module_str[0..1]; - let name = match type_str { - "%" => &module_str[1..], - "&" => &module_str[1..], - _ => &module_str, - } - .to_string(); - - for dest in destinations.iter() { - inputs - .entry(dest.clone()) - .or_insert(Vec::new()) - .push(name.clone()); - } - - match type_str { - "%" => Box::new(FlipFlop::new(name.clone(), destinations)), - "&" => Box::new(Conjunction::new(name.clone(), destinations)), - _ => Box::new(Broadcast::new(name.clone(), destinations)), - } - }) - .collect(); - - let empty: Vec = vec![]; - for module in modules.iter_mut() { - let name = module.name(); - module.reset_inputs(inputs.get(name).unwrap_or(&empty).clone()); - } - - Desert { - modules, - pulses: VecDeque::new(), - pulses_seen_low: 0, - pulses_seen_high: 0, - } - } - - fn get_module(&mut self, name: String) -> Option<&mut Box> { - self.modules - .iter_mut() - .find(|module| module.name() == &name) - } - - fn send_pulses(&mut self, pulses: Vec) { - for pulse in &pulses { - if pulse.high { - self.pulses_seen_high += 1 - } else { - self.pulses_seen_low += 1 - } - } - self.pulses.extend(pulses.into_iter()); - } - - fn button_press(&mut self) -> (u32, u32) { - self.send_pulses(vec![Pulse { - from: "button".to_string(), - to: "broadcaster".to_string(), - high: false, - }]); - - while !self.pulses.is_empty() { - let pulse = self.pulses.pop_front().unwrap(); - let module = self.get_module(pulse.to); - if module.is_some() { - let pulses = module.unwrap().pulse(pulse.high, pulse.from); - self.send_pulses(pulses); - } - } - - (self.pulses_seen_low, self.pulses_seen_high) - } -} - -#[test] -fn test_from_lines() { - let mut example1 = Desert::from_lines(vec![ - "broadcaster -> a, b, c".to_string(), - "%a -> b".to_string(), - "%b -> c".to_string(), - "%c -> inv".to_string(), - "&inv -> a".to_string(), - ]); - - assert_eq!(5, example1.modules.len()); - assert_eq!( - HashSet::from([ - "broadcaster".to_string(), - "a".to_string(), - "b".to_string(), - "c".to_string(), - "inv".to_string() - ]), - example1 - .modules - .iter() - .map(|m| m.name().clone()) - .collect::>() - ); - assert_eq!( - vec!["a".to_string(), "b".to_string(), "c".to_string(),], - *example1 - .get_module("broadcaster".to_string()) - .unwrap() - .deref() - .destinations() - ); -} - -#[test] -fn test_button_press() { - let mut example1 = Desert::from_lines(vec![ - "broadcaster -> a, b, c".to_string(), - "%a -> b".to_string(), - "%b -> c".to_string(), - "%c -> inv".to_string(), - "&inv -> a".to_string(), - ]); - - let (mut seen_low, mut seen_high) = example1.button_press(); - assert_eq!(8, seen_low); - assert_eq!(4, seen_high); - - for _ in 1..1000 { - (seen_low, seen_high) = example1.button_press(); - } - assert_eq!(8000, seen_low); - assert_eq!(4000, seen_high); - - let mut example2 = Desert::from_lines(vec![ - "broadcaster -> a".to_string(), - "%a -> inv, con".to_string(), - "&inv -> b".to_string(), - "%b -> con".to_string(), - "&con -> output".to_string(), - ]); - let (mut seen_low, mut seen_high) = example2.button_press(); - for _ in 1..1000 { - (seen_low, seen_high) = example2.button_press(); - } - assert_eq!(4250, seen_low); - assert_eq!(2750, seen_high); -} - -trait Module { - fn reset_inputs(&mut self, _inputs: Vec) {} - fn name(&self) -> &String; - fn destinations(&self) -> &Vec; - fn pulse(&mut self, high: bool, from: String) -> Vec; -} - -struct FlipFlop { - name: String, - is_on: bool, - destinations: Vec, -} - -impl FlipFlop { - fn new(name: String, destinations: Vec) -> FlipFlop { - FlipFlop { - name, - is_on: false, - destinations, - } - } -} - -impl Module for FlipFlop { - fn reset_inputs(&mut self, _inputs: Vec) {} - fn name(&self) -> &String { - &self.name - } - fn destinations(&self) -> &Vec { - &self.destinations - } - fn pulse(&mut self, high: bool, _from: String) -> Vec { - if high { - vec![] - } else { - self.is_on = !self.is_on; - self.destinations - .iter() - .map(|dest| Pulse { - from: self.name.clone(), - to: dest.clone(), - high: self.is_on, - }) - .collect() - } - } -} - -#[test] -fn test_pulse_flipflop() { - let mut module = FlipFlop::new("foo".to_string(), vec!["bar".to_string()]); - assert!(!module.is_on); - assert_eq!(module.destinations.len(), 1); - - let pulses = module.pulse(true, "baz".to_string()); - assert!(!module.is_on); - assert_eq!(pulses.len(), 0); - - let pulses = module.pulse(false, "baz".to_string()); - assert!(module.is_on); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].from, "foo".to_string()); - assert_eq!(pulses[0].to, "bar".to_string()); - assert_eq!(pulses[0].high, true); - - let pulses = module.pulse(true, "baz".to_string()); - assert!(module.is_on); - assert_eq!(pulses.len(), 0); - - let pulses = module.pulse(false, "baz".to_string()); - assert!(!module.is_on); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].from, "foo".to_string()); - assert_eq!(pulses[0].to, "bar".to_string()); - assert_eq!(pulses[0].high, false); -} - -struct Conjunction { - name: String, - inputs: HashMap, - destinations: Vec, -} - -impl Conjunction { - fn new(name: String, destinations: Vec) -> Conjunction { - Conjunction { - name, - inputs: HashMap::new(), - destinations, - } - } -} - -impl Module for Conjunction { - fn reset_inputs(&mut self, inputs: Vec) { - self.inputs = inputs.iter().map(|input| (input.clone(), false)).collect(); - } - fn name(&self) -> &String { - &self.name - } - fn destinations(&self) -> &Vec { - &self.destinations - } - fn pulse(&mut self, high: bool, from: String) -> Vec { - if !self.inputs.contains_key(&from) { - panic!("Unknown input {}", from); - } - self.inputs.entry(from).and_modify(|h| *h = high); - - let send_high = !self.inputs.values().all(|&high| high); - self.destinations - .iter() - .map(|dest| Pulse { - from: self.name.clone(), - to: dest.clone(), - high: send_high, - }) - .collect() - } -} - -#[test] -fn test_conjunction_pulse_one_input() { - let mut module = Conjunction::new("inv".to_string(), vec!["out".to_string()]); - module.reset_inputs(vec!["inp".to_string()]); - - let pulses = module.pulse(false, "inp".to_string()); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].from, "inv".to_string()); - assert_eq!(pulses[0].to, "out".to_string()); - assert_eq!(pulses[0].high, true); - - let pulses = module.pulse(false, "inp".to_string()); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].high, true); - - let pulses = module.pulse(true, "inp".to_string()); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].high, false); - - let pulses = module.pulse(true, "inp".to_string()); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].high, false); - - let pulses = module.pulse(false, "inp".to_string()); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].high, true); -} - -#[test] -fn test_conjunction_pulse_multiple_input() { - let mut module = Conjunction::new("conj".to_string(), vec!["out".to_string()]); - module.reset_inputs(vec![ - "bim".to_string(), - "bam".to_string(), - "boom".to_string(), - ]); - - let pulses = module.pulse(false, "bim".to_string()); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].from, "conj".to_string()); - assert_eq!(pulses[0].to, "out".to_string()); - assert_eq!(pulses[0].high, true); - - let pulses = module.pulse(true, "bim".to_string()); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].high, true); - - let pulses = module.pulse(true, "bam".to_string()); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].high, true); - - let pulses = module.pulse(true, "boom".to_string()); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].high, false); - - let pulses = module.pulse(false, "bam".to_string()); - assert_eq!(pulses.len(), 1); - assert_eq!(pulses[0].high, true); -} - -#[test] -#[should_panic] -fn test_conjunction_pulse_without_inputs() { - let mut module = Conjunction::new("foo".to_string(), vec!["bar".to_string()]); - module.reset_inputs(vec![]); - module.pulse(false, "baz".to_string()); -} - -#[test] -#[should_panic] -fn test_conjunction_pulse_with_unknown_input() { - let mut no_input = Conjunction::new("foo".to_string(), vec!["bar".to_string()]); - no_input.reset_inputs(vec!["baz".to_string()]); - no_input.pulse(false, "boo".to_string()); -} - -struct Broadcast { - name: String, - destinations: Vec, -} - -impl Broadcast { - fn new(name: String, destinations: Vec) -> Broadcast { - Broadcast { name, destinations } - } -} - -impl Module for Broadcast { - fn name(&self) -> &String { - &self.name - } - fn destinations(&self) -> &Vec { - &self.destinations - } - fn pulse(&mut self, high: bool, _from: String) -> Vec { - self.destinations - .iter() - .map(|dest| Pulse { - from: self.name.clone(), - to: dest.clone(), - high, - }) - .collect() - } -} diff --git a/year_2023/src/day21/mod.rs b/year_2023/src/day21.rs similarity index 82% rename from year_2023/src/day21/mod.rs rename to year_2023/src/day21.rs index 2cf231f..355d4fb 100644 --- a/year_2023/src/day21/mod.rs +++ b/year_2023/src/day21.rs @@ -1,17 +1,12 @@ -use aoc_utils as utils; use std::collections::{HashMap, HashSet}; -#[test] -fn test_mine() { - execute(); -} - -pub fn execute() { - let garden = GardenPatch::from_lines(utils::read_lines("src/day21/mine.txt")); +pub fn execute() -> String { + let garden = GardenPatch::from_lines(aoc_utils::read_lines("input/day21.txt")); - assert_eq!(3847, garden.count_part_1(64)); + let part1 = garden.count_part_1(64); + let part2 = garden.count_part_2(26501365); - assert_eq!(637537341306357, garden.count_part_2(26501365)); + format!("{} {}", part1, part2) } struct GardenPatch { @@ -176,46 +171,6 @@ fn _explore_direction( result } -#[test] -fn test_from_lines() { - let lines = _example(); - - let example = GardenPatch::from_lines(lines); - - assert_eq!(81, example.plots.len()); - assert_eq!(Coordinates(5, 5), example.start); -} - -fn _example() -> Vec { - vec![ - "...........".to_string(), - ".....###.#.".to_string(), - ".###.##..#.".to_string(), - "..#.#...#..".to_string(), - "....#.#....".to_string(), - ".##..S####.".to_string(), - ".##..#...#.".to_string(), - ".......##..".to_string(), - ".##.#.####.".to_string(), - ".##..##.##.".to_string(), - "...........".to_string(), - ] -} - -#[test] -fn test_example() { - let garden = GardenPatch::from_lines(_example()); - - assert_eq!(16, garden.count_part_1(6)); - - assert_eq!(50, garden.count_part_2(10)); - assert_eq!(1594, garden.count_part_2(50)); - assert_eq!(6536, garden.count_part_2(100)); - assert_eq!(167004, garden.count_part_2(500)); - assert_eq!(668697, garden.count_part_2(1000)); - assert_eq!(16733044, garden.count_part_2(5000)); -} - #[derive(Clone)] struct PatchNavigator { distances: HashMap, @@ -376,25 +331,77 @@ impl PatchNavigator { fn _distance(&self, position: &Coordinates) -> i64 { *self.distances.get(position).unwrap() } +} - fn print(&self, garden: &GardenPatch, width: usize) { - for y in 0..garden.side { - let line = (0..garden.side) - .map(|x| { - let coordinates = Coordinates(x, y); - let distance = self.distances.get(&coordinates); - - if distance.is_some() { - format!("{:^width$}", distance.unwrap()) - } else if garden.plots.contains(&coordinates) { - format!("{:^width$}", ".") - } else { - format!("{:^width$}", "#") - } - }) - .collect::>() - .join(" "); - println!("{}", line); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "3847 637537341306357"); + } + + #[test] + fn test_from_lines() { + let lines = _example(); + + let example = GardenPatch::from_lines(lines); + + assert_eq!(81, example.plots.len()); + assert_eq!(Coordinates(5, 5), example.start); + } + + fn _example() -> Vec { + vec![ + "...........".to_string(), + ".....###.#.".to_string(), + ".###.##..#.".to_string(), + "..#.#...#..".to_string(), + "....#.#....".to_string(), + ".##..S####.".to_string(), + ".##..#...#.".to_string(), + ".......##..".to_string(), + ".##.#.####.".to_string(), + ".##..##.##.".to_string(), + "...........".to_string(), + ] + } + + #[test] + fn test_example() { + let garden = GardenPatch::from_lines(_example()); + + assert_eq!(16, garden.count_part_1(6)); + + assert_eq!(50, garden.count_part_2(10)); + assert_eq!(1594, garden.count_part_2(50)); + assert_eq!(6536, garden.count_part_2(100)); + assert_eq!(167004, garden.count_part_2(500)); + assert_eq!(668697, garden.count_part_2(1000)); + assert_eq!(16733044, garden.count_part_2(5000)); + } + + impl PatchNavigator { + fn _print(&self, garden: &GardenPatch, width: usize) { + for y in 0..garden.side { + let line = (0..garden.side) + .map(|x| { + let coordinates = Coordinates(x, y); + let distance = self.distances.get(&coordinates); + + if distance.is_some() { + format!("{:^width$}", distance.unwrap()) + } else if garden.plots.contains(&coordinates) { + format!("{:^width$}", ".") + } else { + format!("{:^width$}", "#") + } + }) + .collect::>() + .join(" "); + println!("{}", line); + } } } } diff --git a/year_2023/src/day22/mod.rs b/year_2023/src/day22.rs similarity index 57% rename from year_2023/src/day22/mod.rs rename to year_2023/src/day22.rs index c8ca111..fbccd50 100644 --- a/year_2023/src/day22/mod.rs +++ b/year_2023/src/day22.rs @@ -1,22 +1,16 @@ -use aoc_utils as utils; use std::collections::{HashMap, HashSet, VecDeque}; -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { - let mut mine = BrickYard::from_lines(utils::read_lines("src/day22/mine.txt")); +pub fn execute() -> String { + let mut mine = BrickYard::from_lines(aoc_utils::read_lines("input/day22.txt")); mine.drop(); - assert_eq!(1221, mine.bricks.len()); - assert_eq!(3656, mine.blocks.len()); - let disintegratable = mine.disintegratable_bricks(); - assert_eq!(389, disintegratable.len()); let chain_reactions = mine.chain_reactions(); - assert_eq!(70609, chain_reactions.values().sum::()); + + let part1 = disintegratable.len(); + let part2 = chain_reactions.values().sum::(); + + format!("{} {}", part1, part2) } type Dimension = u16; @@ -77,67 +71,10 @@ impl Brick { } } -fn _example() -> Vec { - vec![ - "1,0,1~1,2,1".to_string(), - "0,0,2~2,0,2".to_string(), - "0,2,3~2,2,3".to_string(), - "0,0,4~0,2,4".to_string(), - "2,0,5~2,2,5".to_string(), - "0,1,6~2,1,6".to_string(), - "1,1,8~1,1,9".to_string(), - ] -} - -#[test] -fn test_brick_from_line() { - let example1 = Brick::from_line(0, &String::from("1,0,1~1,2,1")); - assert_eq!(0, example1.id); - assert_eq!(1, example1.start.x); - assert_eq!(0, example1.start.y); - assert_eq!(1, example1.start.z); - assert_eq!(1, example1.end.x); - assert_eq!(2, example1.end.y); - assert_eq!(1, example1.end.z); - - assert_eq!(3, example1.blocks.len()); - assert!(example1.blocks.contains(&example1.start)); - assert!(example1.blocks.contains(&example1.end)); - assert!(example1.blocks.contains(&Coordinates { x: 1, y: 1, z: 1 })); - - let example2 = Brick::from_line(1, &String::from("0,0,2~2,0,2")); - assert_eq!(1, example2.id); - assert_eq!(0, example2.start.x); - assert_eq!(0, example2.start.y); - assert_eq!(2, example2.start.z); - assert_eq!(2, example2.end.x); - assert_eq!(0, example2.end.y); - assert_eq!(2, example2.end.z); - - assert_eq!(3, example2.blocks.len()); - assert!(example2.blocks.contains(&example2.start)); - assert!(example2.blocks.contains(&example2.end)); - assert!(example2.blocks.contains(&Coordinates { x: 1, y: 0, z: 2 })); - - let example2 = Brick::from_line(2, &String::from("1,1,8~1,1,9")); - assert_eq!(2, example2.id); - assert_eq!(1, example2.start.x); - assert_eq!(1, example2.start.y); - assert_eq!(8, example2.start.z); - assert_eq!(1, example2.end.x); - assert_eq!(1, example2.end.y); - assert_eq!(9, example2.end.z); - - assert_eq!(2, example2.blocks.len()); - assert!(example2.blocks.contains(&example2.start)); - assert!(example2.blocks.contains(&example2.end)); -} - struct BrickYard { bricks: Vec, blocks: HashSet, } - impl BrickYard { fn from_lines(lines: Vec) -> BrickYard { let mut bricks = lines @@ -291,80 +228,147 @@ impl BrickYard { reactions } } -#[test] -fn test_from_lines() { - let example = BrickYard::from_lines(_example()); - assert_eq!(7, example.bricks.len()); - assert_eq!(20, example.blocks.len()); - assert_eq!(Coordinates { x: 1, y: 0, z: 1 }, example.bricks[0].start); - - let unordered = BrickYard::from_lines(vec![ - "0,0,2~2,0,2".to_string(), - "0,2,3~2,2,3".to_string(), - "1,0,1~1,2,1".to_string(), - ]); - assert_eq!(2, unordered.bricks[0].id); - assert_eq!(0, unordered.bricks[1].id); - assert_eq!(1, unordered.bricks[2].id); -} -#[test] -fn test_drop() { - let mut example = BrickYard::from_lines(_example()); - example.drop(); +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "389 70609"); + } + + fn _example() -> Vec { + vec![ + "1,0,1~1,2,1".to_string(), + "0,0,2~2,0,2".to_string(), + "0,2,3~2,2,3".to_string(), + "0,0,4~0,2,4".to_string(), + "2,0,5~2,2,5".to_string(), + "0,1,6~2,1,6".to_string(), + "1,1,8~1,1,9".to_string(), + ] + } - assert!(example - .bricks - .iter() - .enumerate() - .all(|(index, brick)| index as BrickID == brick.id)); + #[test] + fn test_brick_from_line() { + let example1 = Brick::from_line(0, &String::from("1,0,1~1,2,1")); + assert_eq!(0, example1.id); + assert_eq!(1, example1.start.x); + assert_eq!(0, example1.start.y); + assert_eq!(1, example1.start.z); + assert_eq!(1, example1.end.x); + assert_eq!(2, example1.end.y); + assert_eq!(1, example1.end.z); + + assert_eq!(3, example1.blocks.len()); + assert!(example1.blocks.contains(&example1.start)); + assert!(example1.blocks.contains(&example1.end)); + assert!(example1.blocks.contains(&Coordinates { x: 1, y: 1, z: 1 })); + + let example2 = Brick::from_line(1, &String::from("0,0,2~2,0,2")); + assert_eq!(1, example2.id); + assert_eq!(0, example2.start.x); + assert_eq!(0, example2.start.y); + assert_eq!(2, example2.start.z); + assert_eq!(2, example2.end.x); + assert_eq!(0, example2.end.y); + assert_eq!(2, example2.end.z); + + assert_eq!(3, example2.blocks.len()); + assert!(example2.blocks.contains(&example2.start)); + assert!(example2.blocks.contains(&example2.end)); + assert!(example2.blocks.contains(&Coordinates { x: 1, y: 0, z: 2 })); + + let example2 = Brick::from_line(2, &String::from("1,1,8~1,1,9")); + assert_eq!(2, example2.id); + assert_eq!(1, example2.start.x); + assert_eq!(1, example2.start.y); + assert_eq!(8, example2.start.z); + assert_eq!(1, example2.end.x); + assert_eq!(1, example2.end.y); + assert_eq!(9, example2.end.z); + + assert_eq!(2, example2.blocks.len()); + assert!(example2.blocks.contains(&example2.start)); + assert!(example2.blocks.contains(&example2.end)); + } - assert_eq!(Coordinates { x: 1, y: 0, z: 1 }, example.bricks[0].start); - assert_eq!(Coordinates { x: 1, y: 2, z: 1 }, example.bricks[0].end); + #[test] + fn test_from_lines() { + let example = BrickYard::from_lines(_example()); + assert_eq!(7, example.bricks.len()); + assert_eq!(20, example.blocks.len()); + assert_eq!(Coordinates { x: 1, y: 0, z: 1 }, example.bricks[0].start); + + let unordered = BrickYard::from_lines(vec![ + "0,0,2~2,0,2".to_string(), + "0,2,3~2,2,3".to_string(), + "1,0,1~1,2,1".to_string(), + ]); + assert_eq!(2, unordered.bricks[0].id); + assert_eq!(0, unordered.bricks[1].id); + assert_eq!(1, unordered.bricks[2].id); + } - assert_eq!(Coordinates { x: 0, y: 0, z: 2 }, example.bricks[1].start); - assert_eq!(Coordinates { x: 2, y: 0, z: 2 }, example.bricks[1].end); + #[test] + fn test_drop() { + let mut example = BrickYard::from_lines(_example()); + example.drop(); - assert_eq!(Coordinates { x: 0, y: 2, z: 2 }, example.bricks[2].start); - assert_eq!(Coordinates { x: 2, y: 2, z: 2 }, example.bricks[2].end); + assert!(example + .bricks + .iter() + .enumerate() + .all(|(index, brick)| index as BrickID == brick.id)); - assert_eq!(Coordinates { x: 0, y: 0, z: 3 }, example.bricks[3].start); - assert_eq!(Coordinates { x: 0, y: 2, z: 3 }, example.bricks[3].end); + assert_eq!(Coordinates { x: 1, y: 0, z: 1 }, example.bricks[0].start); + assert_eq!(Coordinates { x: 1, y: 2, z: 1 }, example.bricks[0].end); - assert_eq!(Coordinates { x: 2, y: 0, z: 3 }, example.bricks[4].start); - assert_eq!(Coordinates { x: 2, y: 2, z: 3 }, example.bricks[4].end); + assert_eq!(Coordinates { x: 0, y: 0, z: 2 }, example.bricks[1].start); + assert_eq!(Coordinates { x: 2, y: 0, z: 2 }, example.bricks[1].end); - assert_eq!(Coordinates { x: 0, y: 1, z: 4 }, example.bricks[5].start); - assert_eq!(Coordinates { x: 2, y: 1, z: 4 }, example.bricks[5].end); + assert_eq!(Coordinates { x: 0, y: 2, z: 2 }, example.bricks[2].start); + assert_eq!(Coordinates { x: 2, y: 2, z: 2 }, example.bricks[2].end); - assert_eq!(Coordinates { x: 1, y: 1, z: 5 }, example.bricks[6].start); - assert_eq!(Coordinates { x: 1, y: 1, z: 6 }, example.bricks[6].end); -} + assert_eq!(Coordinates { x: 0, y: 0, z: 3 }, example.bricks[3].start); + assert_eq!(Coordinates { x: 0, y: 2, z: 3 }, example.bricks[3].end); -#[test] -fn test_destroy_one() { - let mut example = BrickYard::from_lines(_example()); - example.drop(); - let destroyables = example.disintegratable_bricks(); + assert_eq!(Coordinates { x: 2, y: 0, z: 3 }, example.bricks[4].start); + assert_eq!(Coordinates { x: 2, y: 2, z: 3 }, example.bricks[4].end); - assert_eq!(5, destroyables.len()); -} + assert_eq!(Coordinates { x: 0, y: 1, z: 4 }, example.bricks[5].start); + assert_eq!(Coordinates { x: 2, y: 1, z: 4 }, example.bricks[5].end); + + assert_eq!(Coordinates { x: 1, y: 1, z: 5 }, example.bricks[6].start); + assert_eq!(Coordinates { x: 1, y: 1, z: 6 }, example.bricks[6].end); + } -#[test] -fn test_chain_reactions() { - let mut example = BrickYard::from_lines(_example()); - example.drop(); + #[test] + fn test_destroy_one() { + let mut example = BrickYard::from_lines(_example()); + example.drop(); + let destroyables = example.disintegratable_bricks(); - let chain_reactions = example.chain_reactions(); + assert_eq!(5, destroyables.len()); + } - assert_eq!(7, chain_reactions.len()); - assert_eq!(7, chain_reactions.values().sum::()); + #[test] + fn test_chain_reactions() { + let mut example = BrickYard::from_lines(_example()); + example.drop(); - assert_eq!(6, chain_reactions[&0]); - assert_eq!(0, chain_reactions[&1]); - assert_eq!(0, chain_reactions[&2]); - assert_eq!(0, chain_reactions[&3]); - assert_eq!(0, chain_reactions[&4]); - assert_eq!(1, chain_reactions[&5]); - assert_eq!(0, chain_reactions[&6]); + let chain_reactions = example.chain_reactions(); + + assert_eq!(7, chain_reactions.len()); + assert_eq!(7, chain_reactions.values().sum::()); + + assert_eq!(6, chain_reactions[&0]); + assert_eq!(0, chain_reactions[&1]); + assert_eq!(0, chain_reactions[&2]); + assert_eq!(0, chain_reactions[&3]); + assert_eq!(0, chain_reactions[&4]); + assert_eq!(1, chain_reactions[&5]); + assert_eq!(0, chain_reactions[&6]); + } } diff --git a/year_2023/src/day23.rs b/year_2023/src/day23.rs new file mode 100644 index 0000000..9f1a141 --- /dev/null +++ b/year_2023/src/day23.rs @@ -0,0 +1,368 @@ +use std::collections::{HashMap, HashSet, VecDeque}; + +pub fn execute() -> String { + let mine_slippery = Map::from_lines(aoc_utils::read_lines("input/day23.txt"), true); + let part1 = mine_slippery.find_longest_route(); + + let mine_sticky = Map::from_lines(aoc_utils::read_lines("input/day23.txt"), false); + let part2 = mine_sticky.find_longest_route(); + + format!("{} {}", part1, part2) +} + +#[derive(Clone, Eq, PartialEq, Debug)] +enum Direction { + Left, + Right, + Up, + Down, +} + +#[derive(Clone, Eq, PartialEq, Debug)] +enum Tile { + Path, + Forest, + Slope(Direction), +} + +struct Map { + tiles: Vec>, + start: Coordinates, + end: Coordinates, +} + +impl Map { + fn from_lines(lines: Vec, with_slopes: bool) -> Map { + let tiles: Vec> = lines + .iter() + .map(|line| { + line.chars() + .map(|c| match c { + '#' => Tile::Forest, + '.' => Tile::Path, + '<' => { + if with_slopes { + Tile::Slope(Direction::Left) + } else { + Tile::Path + } + } + '>' => { + if with_slopes { + Tile::Slope(Direction::Right) + } else { + Tile::Path + } + } + '^' => { + if with_slopes { + Tile::Slope(Direction::Up) + } else { + Tile::Path + } + } + 'v' => { + if with_slopes { + Tile::Slope(Direction::Down) + } else { + Tile::Path + } + } + _ => unreachable!(), + }) + .collect() + }) + .collect(); + + assert!(tiles.iter().all(|row| row.len() == tiles.len())); + + let start_index = tiles + .first() + .unwrap() + .iter() + .position(|tile| *tile == Tile::Path) + .unwrap(); + + let start = Coordinates { + x: start_index, + y: 0, + }; + + let end_index = tiles + .last() + .unwrap() + .iter() + .position(|tile| *tile == Tile::Path) + .unwrap(); + + let end = Coordinates { + x: end_index, + y: tiles.len() - 1, + }; + + Map { tiles, start, end } + } + + fn get(&self, position: &Coordinates) -> &Tile { + &self.tiles[position.y][position.x] + } + + fn step(&self, from: Coordinates, direction: &Direction) -> (Coordinates, Vec) { + use Direction::*; + + let mut result = vec![]; + + let next = from.towards(direction).unwrap(); + + for next_direction in [Left, Right, Up, Down] { + if let Some(further) = next.towards(&next_direction) { + if further.x < self.tiles.len() && further.y < self.tiles.len() && from != further { + let further_tile = self.get(&further); + if *further_tile == Tile::Forest { + continue; + } + if let Tile::Slope(slope) = further_tile { + if *slope != next_direction { + continue; + } + } + result.push(next_direction); + } + } + } + + (next, result) + } + + fn find_longest_route(&self) -> i32 { + let (segments, junctions) = self.get_segments(); + let junction_ids = junctions + .iter() + .enumerate() + .map(|(i, junction)| (junction, i)) + .collect::>(); + + let n_vertex = junctions.len(); + let mut edges = vec![HashMap::::new(); n_vertex]; + for segment in segments { + let from = junction_ids[&segment.from]; + let to = junction_ids[&segment.to]; + edges[from].insert(to, segment.steps); + if !segment.one_way { + edges[to].insert(from, segment.steps); + }; + } + + fn dfs( + from: usize, + to: usize, + path: &mut Vec, + len: i32, + n_vertex: usize, + edges: &[HashMap], + seen: &mut [bool], + ) -> (i32, Vec) { + path.push(from); + let mut max_len = 0; + let mut max_edges = vec![]; + + let from_edges = &edges[from]; + for (&next, &edge_len) in from_edges.iter() { + if next != from && !seen[next] { + seen[next] = true; + + let option_len = len + edge_len; + let (next_len, next_edges) = if next == to { + let mut last_edges = path.clone(); + last_edges.push(next); + (option_len, last_edges) + } else { + dfs(next, to, path, option_len, n_vertex, edges, seen) + }; + + if next_len > max_len { + max_len = next_len; + max_edges = next_edges; + } + + seen[next] = false; + } + } + path.pop(); + (max_len, max_edges) + } + + let mut seen = vec![false; n_vertex]; + let (best_len, _best_edges) = dfs( + junction_ids[&self.start], + junction_ids[&self.end], + &mut vec![], + 0, + n_vertex, + edges.as_slice(), + seen.as_mut_slice(), + ); + + best_len + } + + fn get_segments(&self) -> (Vec, HashSet) { + let mut segments = vec![]; + let mut junctions = HashSet::from([self.start.clone()]); + + let mut starts = VecDeque::from([(self.start.clone(), Direction::Down)]); + + while !starts.is_empty() { + let (start, mut direction) = starts.pop_front().unwrap(); + let mut walker = start.clone(); + let mut steps = 0; + let mut seen_slope = false; + loop { + steps += 1; + let (new_pos, mut directions) = self.step(walker, &direction); + if let Tile::Slope(_) = self.get(&new_pos) { + seen_slope = true; + } + walker = new_pos; + + if !seen_slope && directions.len() == 1 { + direction = directions.pop().unwrap(); + } else { + if directions.len() != 0 || walker == self.end { + segments.push(Segment { + from: start.clone(), + to: walker.clone(), + steps, + one_way: seen_slope, + }); + } + if junctions.insert(walker.clone()) { + for direction in directions { + starts.push_back((walker.clone(), direction)); + } + } + break; + } + } + } + (segments, junctions) + } +} + +#[derive(Clone, Hash, Eq, PartialEq, Debug)] +struct Coordinates { + x: usize, + y: usize, +} + +impl Coordinates { + fn towards(&self, direction: &Direction) -> Option { + use Direction::*; + + match direction { + Left => { + if self.x > 0 { + Some(Coordinates { + x: self.x - 1, + y: self.y, + }) + } else { + None + } + } + Right => Some(Coordinates { + x: self.x + 1, + y: self.y, + }), + Up => { + if self.y > 0 { + Some(Coordinates { + x: self.x, + y: self.y - 1, + }) + } else { + None + } + } + Down => Some(Coordinates { + x: self.x, + y: self.y + 1, + }), + } + } +} + +#[derive(Clone, Debug)] +struct Segment { + from: Coordinates, + to: Coordinates, + steps: i32, + one_way: bool, +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "2394 6554"); + } + + #[test] + fn test_from_lines() { + let example = Map::from_lines(example(), true); + assert_eq!(example.tiles.len(), 23); + assert_eq!(example.tiles[0].len(), 23); + assert_eq!(example.start, Coordinates { x: 1, y: 0 }); + assert_eq!(example.end, Coordinates { x: 21, y: 22 }); + + assert_eq!(example.get(&Coordinates { x: 0, y: 0 }), &Tile::Forest); + assert_eq!(example.get(&Coordinates { x: 1, y: 1 }), &Tile::Path); + assert_eq!( + example.get(&Coordinates { x: 10, y: 3 }), + &Tile::Slope(Direction::Right) + ); + assert_eq!( + example.get(&Coordinates { x: 3, y: 4 }), + &Tile::Slope(Direction::Down) + ); + } + + #[test] + fn test_find_longest_route() { + let example_slippery = Map::from_lines(example(), true); + assert_eq!(94, example_slippery.find_longest_route()); + + let example_sticky = Map::from_lines(example(), false); + assert_eq!(154, example_sticky.find_longest_route()); + } + + fn example() -> Vec { + vec![ + "#.#####################".to_string(), + "#.......#########...###".to_string(), + "#######.#########.#.###".to_string(), + "###.....#.>.>.###.#.###".to_string(), + "###v#####.#v#.###.#.###".to_string(), + "###.>...#.#.#.....#...#".to_string(), + "###v###.#.#.#########.#".to_string(), + "###...#.#.#.......#...#".to_string(), + "#####.#.#.#######.#.###".to_string(), + "#.....#.#.#.......#...#".to_string(), + "#.#####.#.#.#########v#".to_string(), + "#.#...#...#...###...>.#".to_string(), + "#.#.#v#######v###.###v#".to_string(), + "#...#.>.#...>.>.#.###.#".to_string(), + "#####v#.#.###v#.#.###.#".to_string(), + "#.....#...#...#.#.#...#".to_string(), + "#.#########.###.#.#.###".to_string(), + "#...###...#...#...#.###".to_string(), + "###.###.#.###v#####v###".to_string(), + "#...#...#.#.>.>.#.>.###".to_string(), + "#.###.###.#.###.#.#v###".to_string(), + "#.....###...###...#...#".to_string(), + "#####################.#".to_string(), + ] + } +} diff --git a/year_2023/src/day23/mod.rs b/year_2023/src/day23/mod.rs deleted file mode 100644 index 5aa6b18..0000000 --- a/year_2023/src/day23/mod.rs +++ /dev/null @@ -1,352 +0,0 @@ -use aoc_utils as utils; -use std::collections::{HashSet, VecDeque}; - -#[test] -fn test_mine() { - execute(); -} - -pub fn execute() { - let mine_slippery = Map::from_lines(utils::read_lines("src/day23/mine.txt"), true); - let slippery_paths = find_paths(&mine_slippery); - assert_eq!( - 2394, - slippery_paths.iter().map(|p| p.len() - 1).max().unwrap() - ); - - // let mine_sticky = Map::from_lines(utils::read_lines("src/day23/mine.txt"), false); - // let sticky_paths = find_paths(&mine_sticky); - // assert_eq!( - // 2394, - // sticky_paths.iter().map(|p| p.len() - 1).max().unwrap() - // ); -} - -#[derive(Clone, Eq, PartialEq, Debug)] -enum Direction { - Left, - Right, - Up, - Down, -} - -#[derive(Clone, Eq, PartialEq, Debug)] -enum Tile { - Path, - Forest, - Slope(Direction), -} - -struct Map { - tiles: Vec>, - start: Coordinates, - end: Coordinates, -} - -impl Map { - fn from_lines(lines: Vec, with_slopes: bool) -> Map { - let tiles: Vec> = lines - .iter() - .map(|line| { - line.chars() - .map(|c| match c { - '#' => Tile::Forest, - '.' => Tile::Path, - '<' => { - if with_slopes { - Tile::Slope(Direction::Left) - } else { - Tile::Path - } - } - '>' => { - if with_slopes { - Tile::Slope(Direction::Right) - } else { - Tile::Path - } - } - '^' => { - if with_slopes { - Tile::Slope(Direction::Up) - } else { - Tile::Path - } - } - 'v' => { - if with_slopes { - Tile::Slope(Direction::Down) - } else { - Tile::Path - } - } - _ => unreachable!(), - }) - .collect() - }) - .collect(); - - assert!(tiles.iter().all(|row| row.len() == tiles.len())); - - let start_index = tiles - .first() - .unwrap() - .iter() - .position(|tile| *tile == Tile::Path) - .unwrap(); - - let start = Coordinates { - x: start_index, - y: 0, - }; - - let end_index = tiles - .last() - .unwrap() - .iter() - .position(|tile| *tile == Tile::Path) - .unwrap(); - - let end = Coordinates { - x: end_index, - y: tiles.len() - 1, - }; - - Map { tiles, start, end } - } - - fn get(&self, position: &Coordinates) -> &Tile { - &self.tiles[position.y][position.x] - } -} - -#[test] -fn test_from_lines() { - let example = Map::from_lines(_example(), true); - assert_eq!(example.tiles.len(), 23); - assert_eq!(example.tiles[0].len(), 23); - assert_eq!(example.start, Coordinates { x: 1, y: 0 }); - assert_eq!(example.end, Coordinates { x: 21, y: 22 }); - - assert_eq!(example.get(&Coordinates { x: 0, y: 0 }), &Tile::Forest); - assert_eq!(example.get(&Coordinates { x: 1, y: 1 }), &Tile::Path); - assert_eq!( - example.get(&Coordinates { x: 10, y: 3 }), - &Tile::Slope(Direction::Right) - ); - assert_eq!( - example.get(&Coordinates { x: 3, y: 4 }), - &Tile::Slope(Direction::Down) - ); -} - -fn _example() -> Vec { - vec![ - "#.#####################".to_string(), - "#.......#########...###".to_string(), - "#######.#########.#.###".to_string(), - "###.....#.>.>.###.#.###".to_string(), - "###v#####.#v#.###.#.###".to_string(), - "###.>...#.#.#.....#...#".to_string(), - "###v###.#.#.#########.#".to_string(), - "###...#.#.#.......#...#".to_string(), - "#####.#.#.#######.#.###".to_string(), - "#.....#.#.#.......#...#".to_string(), - "#.#####.#.#.#########v#".to_string(), - "#.#...#...#...###...>.#".to_string(), - "#.#.#v#######v###.###v#".to_string(), - "#...#.>.#...>.>.#.###.#".to_string(), - "#####v#.#.###v#.#.###.#".to_string(), - "#.....#...#...#.#.#...#".to_string(), - "#.#########.###.#.#.###".to_string(), - "#...###...#...#...#.###".to_string(), - "###.###.#.###v#####v###".to_string(), - "#...#...#.#.>.>.#.>.###".to_string(), - "#.###.###.#.###.#.#v###".to_string(), - "#.....###...###...#...#".to_string(), - "#####################.#".to_string(), - ] -} - -#[derive(Clone, Hash, Eq, PartialEq, Debug)] -struct Coordinates { - x: usize, - y: usize, -} - -impl Coordinates { - fn next(&self) -> Vec { - use Direction::*; - let mut result = vec![]; - - for direction in [Left, Right, Up, Down] { - let maybe_next = self.towards(direction); - if maybe_next.is_some() { - result.push(maybe_next.unwrap()); - } - } - result - } - - fn towards(&self, direction: Direction) -> Option { - use Direction::*; - - match direction { - Left => { - if self.x > 0 { - Some(Coordinates { - x: self.x - 1, - y: self.y, - }) - } else { - None - } - } - Right => Some(Coordinates { - x: self.x + 1, - y: self.y, - }), - Up => { - if self.y > 0 { - Some(Coordinates { - x: self.x, - y: self.y - 1, - }) - } else { - None - } - } - Down => Some(Coordinates { - x: self.x, - y: self.y + 1, - }), - } - } -} - -#[derive(Clone)] -struct Walker { - positions: HashSet, - current: Coordinates, -} - -impl Walker { - fn new(position: Coordinates) -> Walker { - Walker { - current: position.clone(), - positions: HashSet::from([position]), - } - } - - fn next(self, map: &Map) -> Vec { - let actual_next_iter = self - .current - .next() - .into_iter() - .filter(|pos| &Tile::Forest != map.get(pos) && !self.positions.contains(pos)); - - let mut result: Vec = vec![]; - let mut to_remove = vec![]; - let mut actual_next = vec![]; - - for (i, pos) in actual_next_iter.enumerate() { - if i != 0 { - result.push(self.clone()); - } - actual_next.push(pos); - } - if actual_next.len() > 0 { - result.push(self); - } - - for i in 0..actual_next.len() { - let pos = actual_next.pop().unwrap(); - match map.get(&pos) { - Tile::Path => { - result[i].positions.insert(pos.clone()); - result[i].current = pos; - } - Tile::Slope(d) => { - let bottom = pos.towards(d.clone()).unwrap(); - if result[i].positions.insert(bottom.clone()) { - result[i].positions.insert(pos); - result[i].current = bottom; - } else { - to_remove.push(i); - } - } - _ => unreachable!(), - } - } - for i in to_remove.iter().rev() { - result.remove(*i); - } - result - } - - fn print(&self, map: &Map) { - map.tiles.iter().enumerate().for_each(|(y, row)| { - let line = row - .iter() - .enumerate() - .map(|(x, tile)| { - let coords = Coordinates { x, y }; - if self.positions.contains(&coords) { - "O" - } else { - match tile { - Tile::Forest => "#", - Tile::Path => ".", - Tile::Slope(Direction::Left) => "<", - Tile::Slope(Direction::Right) => ">", - Tile::Slope(Direction::Up) => "^", - Tile::Slope(Direction::Down) => "v", - } - } - }) - .collect::>() - .join(""); - println!("{}", line); - }) - } -} - -fn find_paths(map: &Map) -> Vec> { - let mut result = vec![]; - - let mut walkers = VecDeque::from([Walker::new(map.start.clone())]); - while !walkers.is_empty() { - let current = walkers.pop_front().unwrap(); - if current.current == map.end { - result.push(current.positions); - } else { - for new_walker in current.next(&map) { - walkers.push_back(new_walker); - } - } - } - - result -} - -#[test] -fn test_find_paths() { - let example_slippery = Map::from_lines(_example(), true); - let slippery_paths = find_paths(&example_slippery); - let slippery_paths_lengths = slippery_paths - .iter() - .map(|p| p.len() - 1) - .collect::>(); - - assert_eq!(6, slippery_paths.len()); - assert_eq!(94, *slippery_paths_lengths.iter().max().unwrap()); - - let example_sticky = Map::from_lines(_example(), false); - let sticky_paths = find_paths(&example_sticky); - let sticky_paths_lengths = sticky_paths - .iter() - .map(|p| p.len() - 1) - .collect::>(); - - assert_eq!(154, *sticky_paths_lengths.iter().max().unwrap()); -} diff --git a/year_2023/src/day24.rs b/year_2023/src/day24.rs new file mode 100644 index 0000000..61ef846 --- /dev/null +++ b/year_2023/src/day24.rs @@ -0,0 +1,296 @@ +pub fn execute() -> String { + let storm = HailStorm::from_lines(aoc_utils::read_lines("input/day24.txt")); + let intersections = storm.valid_intersects_xy(200000000000000.0, 400000000000000.0); + + let part1 = intersections.len(); + + let throw_position = storm.find_throw_position(); + let part2 = throw_position.x + throw_position.y + throw_position.z; + + format!("{} {}", part1, part2) +} + +type Coordinate = f64; + +#[derive(Debug, Clone, Copy, PartialEq)] +struct Vector { + x: Coordinate, + y: Coordinate, + z: Coordinate, +} + +impl Vector { + fn from_string(s: &str) -> Self { + let (x_str, rest) = s.split_once(',').unwrap(); + let (y_str, z_str) = rest.split_once(',').unwrap(); + let x = x_str.trim().parse().unwrap(); + let y = y_str.trim().parse().unwrap(); + let z = z_str.trim().parse().unwrap(); + Vector { x, y, z } + } +} + +#[derive(Debug, Clone, Copy, PartialEq)] +struct Stone { + position: Vector, + velocity: Vector, +} + +impl Stone { + fn from_line(line: String) -> Stone { + let (position_str, velocity_str) = line.split_once('@').unwrap(); + let position = Vector::from_string(position_str.trim()); + let velocity = Vector::from_string(velocity_str.trim()); + Stone { position, velocity } + } + + fn intersects_xy(&self, other: &Stone) -> Option { + let den = self.velocity.x * other.velocity.y - self.velocity.y * other.velocity.x; + + if den == 0.0 { + return None; + } + + let num1 = other.velocity.x * (self.position.y - other.position.y) + + other.velocity.y * (other.position.x - self.position.x); + let num2 = self.velocity.x * (self.position.y - other.position.y) + + self.velocity.y * (other.position.x - self.position.x); + + if num1 * den < 0.0 || num2 * den < 0.0 { + return None; + } + + let x = self.position.x + self.velocity.x * num1 / den; + let y = self.position.y + self.velocity.y * num1 / den; + + let z = 0.0; + + Some(Vector { x, y, z }) + } +} + +struct HailStorm { + stones: Vec, +} +impl HailStorm { + fn from_lines(lines: Vec) -> HailStorm { + let stones = lines.into_iter().map(|l| Stone::from_line(l)).collect(); + HailStorm { stones } + } + + fn valid_intersects_xy(&self, min: Coordinate, max: Coordinate) -> Vec { + let mut result = Vec::new(); + for (i, a) in self.stones.iter().enumerate() { + for b in self.stones[i + 1..].iter() { + let intersect = a.intersects_xy(b); + if intersect.is_some_and(|intersect| { + intersect.x >= min + && intersect.x <= max + && intersect.y >= min + && intersect.y <= max + }) { + result.push(intersect.unwrap()); + } + } + } + result + } + + fn find_throw_position(&self) -> Vector { + use z3::ast::Ast; + use z3::*; + + let config = Config::new(); + let context = Context::new(&config); + let solver = Solver::new(&context); + + // Unknowns are: + // - position to throw from (x0, y0, z0) + // - velocity to throw with (u0, v0, w0) + // - time of colisions with stones t1, t2, t3 + let x_0 = ast::Int::new_const(&context, "x0"); + let y_0 = ast::Int::new_const(&context, "y0"); + let z_0 = ast::Int::new_const(&context, "z0"); + let u_0 = ast::Int::new_const(&context, "u0"); + let v_0 = ast::Int::new_const(&context, "v0"); + let w_0 = ast::Int::new_const(&context, "w0"); + + let zero = ast::Int::from_i64(&context, 0); + + for i in 0..self.stones.len() { + let t_n = ast::Int::new_const(&context, format!("t{}", i + 1)); + + let stone = &self.stones[i]; + + let x_n = ast::Int::from_i64(&context, stone.position.x as i64); + let y_n = ast::Int::from_i64(&context, stone.position.y as i64); + let z_n = ast::Int::from_i64(&context, stone.position.z as i64); + let u_n = ast::Int::from_i64(&context, stone.velocity.x as i64); + let v_n = ast::Int::from_i64(&context, stone.velocity.y as i64); + let w_n = ast::Int::from_i64(&context, stone.velocity.z as i64); + + let eq_x = &x_0 + &u_0 * &t_n - x_n - u_n * &t_n; + let eq_y = &y_0 + &v_0 * &t_n - y_n - v_n * &t_n; + let eq_z = &z_0 + &w_0 * &t_n - z_n - w_n * &t_n; + + solver.assert(&eq_x._eq(&zero)); + solver.assert(&eq_y._eq(&zero)); + solver.assert(&eq_z._eq(&zero)); + } + + if matches!(solver.check(), SatResult::Sat) { + if let Some(model) = solver.get_model() { + return Vector { + x: model.get_const_interp(&x_0).unwrap().as_i64().unwrap() as Coordinate, + y: model.get_const_interp(&y_0).unwrap().as_i64().unwrap() as Coordinate, + z: model.get_const_interp(&z_0).unwrap().as_i64().unwrap() as Coordinate, + }; + } + } + + panic!("No solution found."); + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "15889 801386475216902"); + } + + #[test] + fn test_stone_from_line() { + let ex1 = Stone::from_line("9, 13, 0 @ -2, 1, -2".to_string()); + assert_eq!( + Vector { + x: 9.0, + y: 13.0, + z: 0.0 + }, + ex1.position + ); + assert_eq!( + Vector { + x: -2.0, + y: 1.0, + z: -2.0 + }, + ex1.velocity + ); + } + + #[test] + fn test_intersect_xy() { + let example = HailStorm::from_lines(_example()); + + fn check_xy(ex: Coordinate, ey: Coordinate, intersect: Vector) { + assert!( + Coordinate::abs(intersect.x - ex) < 0.000000001, + "X mismatch: {} vs {}", + ex, + intersect.x + ); + assert!( + Coordinate::abs(intersect.y - ey) < 0.000000001, + "Y mismatch: {} vs {}", + ey, + intersect.y + ); + } + + let intersect_0_1 = example.stones[0].intersects_xy(&example.stones[1]); + check_xy(14.0 + 1.0 / 3.0, 15.0 + 1.0 / 3.0, intersect_0_1.unwrap()); + let intersect_0_2 = example.stones[0].intersects_xy(&example.stones[2]); + check_xy(11.0 + 2.0 / 3.0, 16.0 + 2.0 / 3.0, intersect_0_2.unwrap()); + let intersect_0_3 = example.stones[0].intersects_xy(&example.stones[3]); + check_xy(6.2, 19.4, intersect_0_3.unwrap()); + let intersect_0_4 = example.stones[0].intersects_xy(&example.stones[4]); + assert_eq!(intersect_0_4, None); + + let intersect_1_2 = example.stones[1].intersects_xy(&example.stones[2]); + assert_eq!(intersect_1_2, None); + let intersect_1_3 = example.stones[1].intersects_xy(&example.stones[3]); + check_xy(-6.0, -5.0, intersect_1_3.unwrap()); + let intersect_1_4 = example.stones[1].intersects_xy(&example.stones[4]); + assert_eq!(intersect_1_4, None); + + let intersect_2_3 = example.stones[2].intersects_xy(&example.stones[3]); + check_xy(-2.0, 3.0, intersect_2_3.unwrap()); + let intersect_2_4 = example.stones[2].intersects_xy(&example.stones[4]); + assert_eq!(intersect_2_4, None); + + let intersect_3_4 = example.stones[3].intersects_xy(&example.stones[4]); + assert_eq!(intersect_3_4, None); + } + + #[test] + fn test_hailstorm_from_line() { + let lines = _example(); + let hailstorm = HailStorm::from_lines(lines); + + assert_eq!(5, hailstorm.stones.len()); + + assert_eq!( + Vector { + x: 19.0, + y: 13.0, + z: 30.0, + }, + hailstorm.stones[0].position + ); + assert_eq!( + Vector { + x: -2.0, + y: 1.0, + z: -2.0 + }, + hailstorm.stones[0].velocity + ); + + assert_eq!( + Vector { + x: 20.0, + y: 19.0, + z: 15.0, + }, + hailstorm.stones[4].position + ); + assert_eq!( + Vector { + x: 1.0, + y: -5.0, + z: -3.0 + }, + hailstorm.stones[4].velocity + ); + } + + #[test] + fn test_valid_intersects_xy() { + let example = HailStorm::from_lines(_example()); + let intersections = example.valid_intersects_xy(7.0, 27.0); + assert_eq!(intersections.len(), 2); + } + + #[test] + fn test_find_throw_position() { + let example = HailStorm::from_lines(_example()); + let position = example.find_throw_position(); + assert_eq!(position.x, 24.0); + assert_eq!(position.y, 13.0); + assert_eq!(position.z, 10.0); + } + + fn _example() -> Vec { + vec![ + String::from("19, 13, 30 @ -2, 1, -2"), + String::from("18, 19, 22 @ -1, -1, -2"), + String::from("20, 25, 34 @ -2, -2, -4"), + String::from("12, 31, 28 @ -1, -2, -1"), + String::from("20, 19, 15 @ 1, -5, -3"), + ] + } +} diff --git a/year_2023/src/day24/mod.rs b/year_2023/src/day24/mod.rs deleted file mode 100644 index aed4e2a..0000000 --- a/year_2023/src/day24/mod.rs +++ /dev/null @@ -1,224 +0,0 @@ -use aoc_utils as utils; - -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { - let mine = HailStorm::from_lines(utils::read_lines("src/day24/mine.txt")); - let intersections = mine.valid_intersects_xy(200000000000000.0, 400000000000000.0); - assert_eq!(intersections.len(), 15889); -} - -type Coordinate = f64; - -#[derive(Debug, Clone, Copy, PartialEq)] -struct Vector { - x: Coordinate, - y: Coordinate, - z: Coordinate, -} - -impl Vector { - fn from_string(s: &str) -> Self { - let (x_str, rest) = s.split_once(',').unwrap(); - let (y_str, z_str) = rest.split_once(',').unwrap(); - let x = x_str.trim().parse().unwrap(); - let y = y_str.trim().parse().unwrap(); - let z = z_str.trim().parse().unwrap(); - Vector { x, y, z } - } -} - -#[derive(Debug, Clone, Copy, PartialEq)] -struct Stone { - position: Vector, - velocity: Vector, -} - -impl Stone { - fn from_line(line: String) -> Stone { - let (position_str, velocity_str) = line.split_once('@').unwrap(); - let position = Vector::from_string(position_str.trim()); - let velocity = Vector::from_string(velocity_str.trim()); - Stone { position, velocity } - } - - fn intersects_xy(&self, other: &Stone) -> Option { - let den = self.velocity.x * other.velocity.y - self.velocity.y * other.velocity.x; - - if den == 0.0 { - return None; - } - - let num1 = other.velocity.x * (self.position.y - other.position.y) - + other.velocity.y * (other.position.x - self.position.x); - let num2 = self.velocity.x * (self.position.y - other.position.y) - + self.velocity.y * (other.position.x - self.position.x); - - if num1 * den < 0.0 || num2 * den < 0.0 { - return None; - } - - let x = self.position.x + self.velocity.x * num1 / den; - let y = self.position.y + self.velocity.y * num1 / den; - - let z = 0.0; - - Some(Vector { x, y, z }) - } -} - -#[test] -fn test_stone_from_line() { - let ex1 = Stone::from_line("9, 13, 0 @ -2, 1, -2".to_string()); - assert_eq!( - Vector { - x: 9.0, - y: 13.0, - z: 0.0 - }, - ex1.position - ); - assert_eq!( - Vector { - x: -2.0, - y: 1.0, - z: -2.0 - }, - ex1.velocity - ); -} - -#[test] -fn test_intersect_xy() { - let example = HailStorm::from_lines(_example()); - - fn check_xy(ex: Coordinate, ey: Coordinate, intersect: Vector) { - assert!( - Coordinate::abs(intersect.x - ex) < 0.000000001, - "X mismatch: {} vs {}", - ex, - intersect.x - ); - assert!( - Coordinate::abs(intersect.y - ey) < 0.000000001, - "Y mismatch: {} vs {}", - ey, - intersect.y - ); - } - - let intersect_0_1 = example.stones[0].intersects_xy(&example.stones[1]); - check_xy(14.0 + 1.0 / 3.0, 15.0 + 1.0 / 3.0, intersect_0_1.unwrap()); - let intersect_0_2 = example.stones[0].intersects_xy(&example.stones[2]); - check_xy(11.0 + 2.0 / 3.0, 16.0 + 2.0 / 3.0, intersect_0_2.unwrap()); - let intersect_0_3 = example.stones[0].intersects_xy(&example.stones[3]); - check_xy(6.2, 19.4, intersect_0_3.unwrap()); - let intersect_0_4 = example.stones[0].intersects_xy(&example.stones[4]); - assert_eq!(intersect_0_4, None); - - let intersect_1_2 = example.stones[1].intersects_xy(&example.stones[2]); - assert_eq!(intersect_1_2, None); - let intersect_1_3 = example.stones[1].intersects_xy(&example.stones[3]); - check_xy(-6.0, -5.0, intersect_1_3.unwrap()); - let intersect_1_4 = example.stones[1].intersects_xy(&example.stones[4]); - assert_eq!(intersect_1_4, None); - - let intersect_2_3 = example.stones[2].intersects_xy(&example.stones[3]); - check_xy(-2.0, 3.0, intersect_2_3.unwrap()); - let intersect_2_4 = example.stones[2].intersects_xy(&example.stones[4]); - assert_eq!(intersect_2_4, None); - - let intersect_3_4 = example.stones[3].intersects_xy(&example.stones[4]); - assert_eq!(intersect_3_4, None); -} - -struct HailStorm { - stones: Vec, -} - -impl HailStorm { - fn from_lines(lines: Vec) -> HailStorm { - let stones = lines.into_iter().map(|l| Stone::from_line(l)).collect(); - HailStorm { stones } - } - - fn valid_intersects_xy(&self, min: Coordinate, max: Coordinate) -> Vec { - let mut result = Vec::new(); - for (i, a) in self.stones.iter().enumerate() { - for b in self.stones[i + 1..].iter() { - let intersect = a.intersects_xy(b); - if intersect.is_some_and(|intersect| { - intersect.x >= min - && intersect.x <= max - && intersect.y >= min - && intersect.y <= max - }) { - result.push(intersect.unwrap()); - } - } - } - result - } -} - -#[test] -fn test_hailstorm_from_line() { - let lines = _example(); - let hailstorm = HailStorm::from_lines(lines); - - assert_eq!(5, hailstorm.stones.len()); - - assert_eq!( - Vector { - x: 19.0, - y: 13.0, - z: 30.0, - }, - hailstorm.stones[0].position - ); - assert_eq!( - Vector { - x: -2.0, - y: 1.0, - z: -2.0 - }, - hailstorm.stones[0].velocity - ); - - assert_eq!( - Vector { - x: 20.0, - y: 19.0, - z: 15.0, - }, - hailstorm.stones[4].position - ); - assert_eq!( - Vector { - x: 1.0, - y: -5.0, - z: -3.0 - }, - hailstorm.stones[4].velocity - ); -} - -#[test] -fn test_valid_intersects_xy() { - let example = HailStorm::from_lines(_example()); - let intersections = example.valid_intersects_xy(7.0, 27.0); - assert_eq!(intersections.len(), 2); -} - -fn _example() -> Vec { - vec![ - String::from("19, 13, 30 @ -2, 1, -2"), - String::from("18, 19, 22 @ -1, -1, -2"), - String::from("20, 25, 34 @ -2, -2, -4"), - String::from("12, 31, 28 @ -1, -2, -1"), - String::from("20, 19, 15 @ 1, -5, -3"), - ] -} diff --git a/year_2023/src/day25.rs b/year_2023/src/day25.rs new file mode 100644 index 0000000..cbf0297 --- /dev/null +++ b/year_2023/src/day25.rs @@ -0,0 +1,300 @@ +use std::collections::{HashMap, HashSet, VecDeque}; + +pub fn execute() -> String { + let data = aoc_utils::read_lines("input/day25.txt"); + let network = Graph::from_lines(data); + + let part1 = network.find_min_cut_solution(); + let part2 = 456; + + format!("{} {}", part1, part2) +} + +#[derive(Debug, Clone)] +struct Graph { + v_index: HashMap, + v_name: Vec, + edge: Vec>, +} + +impl Graph { + fn from_lines(lines: Vec) -> Self { + let mut pairs = vec![]; + let mut components = HashSet::new(); + + for line in lines { + let (source, targets_str) = line.split_once(": ").unwrap(); + components.insert(source.to_string()); + let targets = targets_str.split(" "); + for target in targets { + pairs.push((source.to_string(), target.to_string())); + components.insert(target.to_string()); + } + } + + let v_names = Vec::from_iter(components.into_iter()); + let vertices = HashMap::from_iter(v_names.iter().enumerate().map(|(i, c)| (c.clone(), i))); + + let mut edges = vec![HashMap::new(); vertices.len()]; + for (a, b) in pairs.iter() { + let i_a = vertices[a]; + let i_b = vertices[b]; + *edges[i_a].entry(i_b).or_default() = 1; + *edges[i_b].entry(i_a).or_default() = 1; + } + + Graph { + v_index: vertices, + v_name: v_names, + edge: edges, + } + } + + fn dfs(&self, start: usize, to: usize, seen: &mut Vec, visited: &mut Vec) -> bool { + seen.push(start); + visited[start] = true; + if start == to { + return true; + } + + let next_edges = &self.edge[start]; + for (&next, &capacity) in next_edges.iter() { + if capacity > 0 + && !visited[next] + && !seen.contains(&next) + && self.dfs(next, to, seen, visited) + { + return true; + } + } + + seen.pop(); + false + } + + fn reachable(&self, start: usize) -> HashSet { + let mut visited = HashSet::new(); + visited.insert(start); + + let mut to_visit = VecDeque::new(); + to_visit.push_back(start); + + while !to_visit.is_empty() { + let current = to_visit.pop_front().unwrap(); + let edges = &self.edge[current]; + for (&next, &capacity) in edges { + if capacity > 0 && visited.insert(next) { + to_visit.push_back(next); + } + } + } + + visited + } + + fn max_flow(&mut self, from: usize, to: usize) -> usize { + let mut count = 0; + let mut seen = vec![]; + let mut visited = vec![false; self.v_index.len()]; + while self.dfs(from, to, &mut seen, &mut visited) { + for i in 0..seen.len() - 1 { + let a = seen[i]; + let b = seen[i + 1]; + self.inc_capacity(a, b, -1); + self.inc_capacity(b, a, 1); + } + + seen.clear(); + visited = vec![false; self.v_index.len()]; + count += 1; + } + count + } + + fn find_min_cut_solution(&self) -> usize { + for i in 0..self.v_index.len() { + for j in i + 1..self.v_index.len() { + let mut residual = self.clone(); + let max_flow = residual.max_flow(i, j); + + if max_flow == 3 { + let g1 = residual.reachable(i).len(); + let g2 = residual.v_name.len() - g1; + + return g1 * g2; + } + } + } + + panic!("did not find a solution!"); + } + + fn inc_capacity(&mut self, from: usize, to: usize, value: i32) { + let current = self.edge[from].entry(to).or_default(); + *current += value; + + if *current == 0 { + self.edge[from].remove(&to); + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use std::collections::HashSet; + + impl Graph { + fn num_edges(&self) -> usize { + self.edge + .iter() + .map(|v| v.values().filter(|&v| *v >= 1).count()) + .sum::() + } + } + + #[test] + fn test_mine() { + assert_eq!(execute(), "603368 456"); + } + + #[test] + fn test_from_lines() { + let network = Graph::from_lines(example()); + let v = |vertice: &str| -> &usize { &network.v_index[vertice] }; + + assert_eq!(network.v_index.len(), 15); + assert_eq!(network.num_edges(), 66); + + assert_ne!(network.edge[*v("cmg")][v("bvb")], 0); + assert_ne!(network.edge[*v("bvb")][v("cmg")], 0); + } + + #[test] + fn test_dfs() { + let network = Graph::from_lines(example()); + let v = |vertice: &str| -> usize { network.v_index[vertice] }; + + let mut seen = vec![]; + let mut visited = vec![false; network.v_index.len()]; + assert!(network.dfs(v("jqt"), v("jqt"), &mut seen, &mut visited)); + assert_eq!(seen.len(), 1); + assert_eq!(seen, vec![v("jqt")]); + + let mut seen = vec![]; + let mut visited = vec![false; network.v_index.len()]; + assert!(network.dfs(v("jqt"), v("xhk"), &mut seen, &mut visited)); + assert_eq!(*seen.first().unwrap(), v("jqt")); + assert_eq!(*seen.last().unwrap(), v("xhk")); + } + + #[test] + fn test_max_flow() { + let network = Graph::from_lines(example()); + let v = |vertex: &str| -> usize { network.v_index[vertex] }; + + for v1 in [ + "cmg", "frs", "lhk", "lsr", "nvd", "pzl", "qnr", "rsh", "rzs", + ] { + for v2 in ["bvb", "hfx", "jqt", "ntq", "rhn", "xhk"] { + let flow1 = network.clone().max_flow(v(v1), v(v2)); + let flow2 = network.clone().max_flow(v(v2), v(v1)); + assert_eq!( + flow1, flow2, + "{0} -> {1}: {2} || {1} -> {0}: {3}", + v1, v2, flow1, flow2 + ); + assert_eq!(flow1, 3, "{} -> {}: {}", v1, v2, flow1); + assert_eq!(flow2, 3, "{} -> {}: {}", v1, v2, flow2); + println!("{0} -> {1}: {2} || {1} -> {0}: {3}", v1, v2, flow1, flow2); + } + } + } + + #[test] + fn test_reachable() { + let network = Graph::from_lines(example()); + let v = |vertex: &str| -> usize { network.v_index[vertex] }; + + for v1 in [ + "cmg", "frs", "lhk", "lsr", "nvd", "pzl", "qnr", "rsh", "rzs", + ] { + for v2 in ["bvb", "hfx", "jqt", "ntq", "rhn", "xhk"] { + let mut g1 = network.clone(); + g1.max_flow(v(v1), v(v2)); + let s1 = g1.reachable(v(v1)); + assert_eq!(s1.len(), 9); + + let mut g2 = network.clone(); + g2.max_flow(v(v2), v(v1)); + let s2 = g2.reachable(v(v2)); + assert_eq!(s2.len(), 6); + } + } + } + + #[test] + fn test_assumptions() { + let example_pairs = pairs_from_lines(example()); + + let uniq_pairs = HashSet::<(String, String)>::from_iter(example_pairs.clone().into_iter()); + assert_eq!(example_pairs.len(), 33); + assert_eq!(uniq_pairs.len(), example_pairs.len()); + + let mut components = HashSet::new(); + for pair in uniq_pairs { + components.insert(pair.0); + components.insert(pair.1); + } + assert_eq!(components.len(), 15); + + for component in components { + let degree = example_pairs + .iter() + .filter(|(a, b)| *a == component || *b == component) + .count(); + println!("{:?}: {}", component, degree); + } + } + + #[test] + fn test_my_assumptions() { + let data = aoc_utils::read_lines("input/day25.txt"); + + let my_pairs = pairs_from_lines(data); + let my_uniq_pairs = HashSet::<(String, String)>::from_iter(my_pairs.clone().into_iter()); + assert_eq!(my_pairs.len(), 3490); + assert_eq!(my_uniq_pairs.len(), my_pairs.len()); + + let mut components = HashSet::new(); + for pair in my_uniq_pairs { + components.insert(pair.0); + components.insert(pair.1); + } + assert_eq!(components.len(), 1554); + + for component in components { + let degree = my_pairs + .iter() + .filter(|(a, b)| *a == component || *b == component) + .count(); + println!("{:?}: {}", component, degree); + } + } + + fn pairs_from_lines(lines: Vec) -> Vec<(String, String)> { + let mut pairs = vec![]; + for line in lines { + let (source, targets_str) = line.split_once(": ").unwrap(); + let targets = targets_str.split(" "); + for target in targets { + pairs.push((source.to_string(), target.to_string())); + } + } + pairs + } + + fn example() -> Vec { + aoc_utils::read_lines("input/day25-example.txt") + } +} diff --git a/year_2023/src/day3/mod.rs b/year_2023/src/day3.rs similarity index 84% rename from year_2023/src/day3/mod.rs rename to year_2023/src/day3.rs index fb20fa4..81a36ca 100644 --- a/year_2023/src/day3/mod.rs +++ b/year_2023/src/day3.rs @@ -1,26 +1,14 @@ -use aoc_utils as utils; use std::collections::HashMap; use std::ops::Index; -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { - let data = utils::read_lines("src/day3/mine.txt"); +pub fn execute() -> String { + let data = aoc_utils::read_lines("input/day3.txt"); let my_map = CharMap::from_text(&data); - assert_eq!(507214, sum_part_numbers(&my_map)); - assert_eq!(72553319, sum_gear_ratios(&my_map)); -} + let part1 = sum_part_numbers(&my_map); + let part2 = sum_gear_ratios(&my_map); -#[test] -fn test_sum_part_numbers() { - let example = utils::read_lines("src/day3/example.txt"); - let example_map = CharMap::from_text(&example); - - assert_eq!(4361, sum_part_numbers(&example_map)) + format!("{} {}", part1, part2) } fn sum_part_numbers(map: &CharMap) -> u32 { @@ -28,14 +16,6 @@ fn sum_part_numbers(map: &CharMap) -> u32 { return part_numbers.iter().sum(); } -#[test] -fn test_sum_gear_ratios() { - let example = utils::read_lines("src/day3/example.txt"); - let example_map = CharMap::from_text(&example); - - assert_eq!(467835, sum_gear_ratios(&example_map)); -} - fn sum_gear_ratios(map: &CharMap) -> u32 { let possible_gears: HashMap<(usize, usize), Vec> = map.find_possible_gears(); return possible_gears @@ -187,3 +167,29 @@ impl Index<(usize, usize)> for CharMap { return &self.chars[y][x..x + 1]; } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "507214 72553319"); + } + + #[test] + fn test_sum_part_numbers() { + let example = aoc_utils::read_lines("input/day3-example.txt"); + let example_map = CharMap::from_text(&example); + + assert_eq!(4361, sum_part_numbers(&example_map)) + } + + #[test] + fn test_sum_gear_ratios() { + let example = aoc_utils::read_lines("input/day3-example.txt"); + let example_map = CharMap::from_text(&example); + + assert_eq!(467835, sum_gear_ratios(&example_map)); + } +} diff --git a/year_2023/src/day4/mod.rs b/year_2023/src/day4.rs similarity index 69% rename from year_2023/src/day4/mod.rs rename to year_2023/src/day4.rs index 4a58a37..5fe4fed 100644 --- a/year_2023/src/day4/mod.rs +++ b/year_2023/src/day4.rs @@ -1,36 +1,18 @@ -use aoc_utils as utils; use std::collections::HashSet; -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { - let cards = Card::from_file("mine.txt"); - - assert_eq!(21959, simple_wins(&cards)); - assert_eq!(5132675, correct_wins(&cards)); -} +pub fn execute() -> String { + let cards = Card::from_file("day4.txt"); -#[test] -fn test_simple_wins() { - let example_cards = Card::from_file("example.txt"); + let part1 = simple_wins(&cards); + let part2 = correct_wins(&cards); - assert_eq!(13, simple_wins(&example_cards)); - assert_eq!(30, correct_wins(&example_cards)); + format!("{} {}", part1, part2) } fn simple_wins(cards: &Vec) -> u32 { return cards.iter().map(Card::simple_score).sum::(); } -#[test] -fn test_correct_wins() { - let example_cards = Card::from_file("example.txt"); - assert_eq!(30, correct_wins(&example_cards)); -} - fn correct_wins(cards: &Vec) -> u32 { let mut copies = vec![1; cards.len()]; @@ -55,8 +37,8 @@ struct Card { impl Card { fn from_file(filename: &str) -> Vec { - let path = format!("src/day4/{}", &filename); - let lines = utils::read_lines(&path); + let path = format!("input/{}", &filename); + let lines = aoc_utils::read_lines(&path); return Vec::from_iter(lines.iter().map(|line| Card::from_text(&line))); } @@ -95,3 +77,26 @@ impl Card { return matches.count(); } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "21959 5132675"); + } + #[test] + fn test_simple_wins() { + let example_cards = Card::from_file("day4-example.txt"); + + assert_eq!(13, simple_wins(&example_cards)); + assert_eq!(30, correct_wins(&example_cards)); + } + + #[test] + fn test_correct_wins() { + let example_cards = Card::from_file("day4-example.txt"); + assert_eq!(30, correct_wins(&example_cards)); + } +} diff --git a/year_2023/src/day5/mod.rs b/year_2023/src/day5.rs similarity index 60% rename from year_2023/src/day5/mod.rs rename to year_2023/src/day5.rs index 26454c4..e7fa48f 100644 --- a/year_2023/src/day5/mod.rs +++ b/year_2023/src/day5.rs @@ -1,71 +1,25 @@ -use aoc_utils as utils; use std::cmp::min; use std::collections::HashSet; use std::iter::zip; use std::ops::Index; -#[test] -fn test_mine() { - execute() -} - -pub fn execute() { - let almanac = Almanac::from_text("mine.txt"); - assert_eq!(313045984, almanac.lowest_location_1()); - - for mapping in &almanac.mappings { - assert!(mapping.is_bijection()); - } - - assert_eq!(20283860, almanac.lowest_location_2()); -} - -#[test] -fn test_almanac_example() { - let example = Almanac::from_text("example.txt"); - assert_eq!(7, example.mappings.len()); - assert_eq!(4, example.seeds.len()); - assert_eq!(2, example.mappings[0].entries.len()); - assert_eq!("seed", example.mappings[0].from); - assert_eq!("soil", example.mappings[0].to); - assert_eq!(3, example.mappings[1].entries.len()); - assert_eq!("soil", example.mappings[1].from); - assert_eq!("fertilizer", example.mappings[1].to); - - assert_eq!(82, example.get_location(79)); - assert_eq!(43, example.get_location(14)); - assert_eq!(86, example.get_location(55)); - assert_eq!(35, example.get_location(13)); - - assert_eq!(35, example.lowest_location_1()); - assert_eq!(46, example.lowest_location_2()); - - for mapping in &example.mappings { - assert!(mapping.is_bijection()); - } +pub fn execute() -> String { + let almanac = Almanac::from_text("day5.txt"); - let mut sum_origin = 0; - let mut sum_forward = 0; - let mut sum_backward = 0; + let part1 = almanac.lowest_location_1(); + let part2 = almanac.lowest_location_2(); - for i in 0..100 { - sum_origin += i; - sum_forward += example.get_location(i); - sum_backward += example.get_seed(i); - } - assert_eq!(sum_origin, sum_forward); - assert_eq!(sum_origin, sum_backward); + format!("{} {}", part1, part2) } struct Almanac { seeds: Vec, mappings: Vec, } - impl Almanac { fn from_text(filename: &str) -> Almanac { - let path = format!("src/day5/{}", &filename); - let data = utils::read_lines(&path); + let path = format!("input/{}", &filename); + let data = aoc_utils::read_lines(&path); let mut blocks = data.split(|line| line.trim().is_empty()); let seeds_block = blocks.next().unwrap(); @@ -82,6 +36,7 @@ impl Almanac { let mut mappings = Vec::new(); for block in blocks { let mapping = Mapping::from_text(block); + assert!(mapping.is_bijection()); mappings.push(mapping); } return Almanac { seeds, mappings }; @@ -150,54 +105,21 @@ impl Almanac { } } -#[test] -fn test_mapping() { - let mut text = Vec::new(); - text.push("seed-to-soil map:"); - text.push("50 98 2"); - text.push("52 50 48"); - - let mapping = Mapping::from_text(&text); - assert_eq!("seed", mapping.from); - assert_eq!("soil", mapping.to); - assert_eq!(2, mapping.entries.len()); - - for i in 0..50 - 1 { - assert_eq!(i, mapping.forward(i)); - } - for i in 50..50 + 48 { - assert_eq!(i - 50 + 52, mapping.forward(i)); - } - for i in 98..98 + 2 { - assert_eq!(i - 98 + 50, mapping.forward(i)); - } - for i in 100..200 { - assert_eq!(i, mapping.forward(i)); - } -} - struct Mapping { - from: String, - to: String, entries: Vec, } - impl Mapping { fn from_text(text: &[impl AsRef]) -> Mapping { let mut lines = text.into_iter(); - let name_line = lines.next().unwrap(); - let names = name_line.as_ref().split_once(" ").unwrap().0; - let (from_str, to_str) = names.split_once("-to-").unwrap(); - let from = String::from(from_str); - let to = String::from(to_str); + let _name_line = lines.next().unwrap(); let mut entries = Vec::new(); for line in lines { entries.push(MappingEntry::from_text(line.as_ref())); } - return Mapping { from, to, entries }; + return Mapping { entries }; } fn src_edges(&self) -> HashSet { @@ -245,34 +167,6 @@ impl Mapping { } } -#[test] -fn test_compress() { - type Range = Vec<(usize, usize)>; - - assert_eq!(Range::from([]), compress(Range::from([]))); - assert_eq!(Range::from([(0, 1)]), compress(Range::from([(0, 1)]))); - assert_eq!( - Range::from([(0, 5)]), - compress(Range::from([(0, 2), (2, 3)])) - ); - assert_eq!( - Range::from([(0, 2), (3, 3)]), - compress(Range::from([(0, 2), (3, 3)])) - ); - assert_eq!( - Range::from([(0, 5)]), - compress(Range::from([(0, 2), (2, 1), (3, 2)])) - ); - assert_eq!( - Range::from([(0, 5)]), - compress(Range::from([(0, 2), (3, 2), (2, 1)])) - ); - assert_eq!( - Range::from([(0, 5)]), - compress(Range::from([(3, 2), (0, 2), (2, 1)])) - ); -} - fn compress(entries: Vec<(usize, usize)>) -> Vec<(usize, usize)> { if entries.len() < 2 { return entries; @@ -300,45 +194,18 @@ fn compress(entries: Vec<(usize, usize)>) -> Vec<(usize, usize)> { return result; } - -#[test] -fn test_mapping_entry() { - let entry = MappingEntry::from_text("0 15 37"); - assert_eq!(0usize, entry.dst); - assert_eq!(15usize, entry.src); - assert_eq!(37usize, entry.width); - - for i in 0..15 - 1 { - assert_eq!(None, entry.forward(i)); - } - for i in 15..15 + 37 { - assert_eq!(Some(i - 15 + 0), entry.forward(i)); - } - for i in 15 + 37..100 { - assert_eq!(None, entry.forward(i)); - } - - for i in 0..37 - 1 { - assert_eq!(Some(i + 15 - 0), entry.backward(i)); - } - for i in 37..100 { - assert_eq!(None, entry.backward(i)); - } -} - struct MappingEntry { src: usize, dst: usize, width: usize, } - impl MappingEntry { fn from_text(text: &str) -> MappingEntry { let mut parts = text.splitn(3, " "); let dst = parts.next().unwrap().parse::().unwrap(); let src = parts.next().unwrap().parse::().unwrap(); let width = parts.next().unwrap().parse::().unwrap(); - return MappingEntry { src, dst, width }; + MappingEntry { src, dst, width } } fn forward(&self, origin: usize) -> Option { @@ -358,3 +225,123 @@ impl MappingEntry { return None; } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "313045984 20283860"); + } + + #[test] + fn test_almanac_example() { + let example = Almanac::from_text("day5-example.txt"); + assert_eq!(7, example.mappings.len()); + assert_eq!(4, example.seeds.len()); + assert_eq!(2, example.mappings[0].entries.len()); + assert_eq!(3, example.mappings[1].entries.len()); + + assert_eq!(82, example.get_location(79)); + assert_eq!(43, example.get_location(14)); + assert_eq!(86, example.get_location(55)); + assert_eq!(35, example.get_location(13)); + + assert_eq!(35, example.lowest_location_1()); + assert_eq!(46, example.lowest_location_2()); + + for mapping in &example.mappings { + assert!(mapping.is_bijection()); + } + + let mut sum_origin = 0; + let mut sum_forward = 0; + let mut sum_backward = 0; + + for i in 0..100 { + sum_origin += i; + sum_forward += example.get_location(i); + sum_backward += example.get_seed(i); + } + assert_eq!(sum_origin, sum_forward); + assert_eq!(sum_origin, sum_backward); + } + + #[test] + fn test_mapping() { + let mut text = Vec::new(); + text.push("seed-to-soil map:"); + text.push("50 98 2"); + text.push("52 50 48"); + + let mapping = Mapping::from_text(&text); + assert_eq!(2, mapping.entries.len()); + + for i in 0..50 - 1 { + assert_eq!(i, mapping.forward(i)); + } + for i in 50..50 + 48 { + assert_eq!(i - 50 + 52, mapping.forward(i)); + } + for i in 98..98 + 2 { + assert_eq!(i - 98 + 50, mapping.forward(i)); + } + for i in 100..200 { + assert_eq!(i, mapping.forward(i)); + } + } + + #[test] + fn test_compress() { + type Range = Vec<(usize, usize)>; + + assert_eq!(Range::from([]), compress(Range::from([]))); + assert_eq!(Range::from([(0, 1)]), compress(Range::from([(0, 1)]))); + assert_eq!( + Range::from([(0, 5)]), + compress(Range::from([(0, 2), (2, 3)])) + ); + assert_eq!( + Range::from([(0, 2), (3, 3)]), + compress(Range::from([(0, 2), (3, 3)])) + ); + assert_eq!( + Range::from([(0, 5)]), + compress(Range::from([(0, 2), (2, 1), (3, 2)])) + ); + assert_eq!( + Range::from([(0, 5)]), + compress(Range::from([(0, 2), (3, 2), (2, 1)])) + ); + assert_eq!( + Range::from([(0, 5)]), + compress(Range::from([(3, 2), (0, 2), (2, 1)])) + ); + } + + #[test] + fn test_mapping_entry() { + let entry = MappingEntry::from_text("0 15 37"); + assert_eq!(0usize, entry.dst); + assert_eq!(15usize, entry.src); + assert_eq!(37usize, entry.width); + + for i in 0..15 - 1 { + assert_eq!(None, entry.forward(i)); + } + for i in 15..15 + 37 { + assert_eq!(Some(i - 15 + 0), entry.forward(i)); + } + for i in 15 + 37..100 { + assert_eq!(None, entry.forward(i)); + } + + for i in 0..37 - 1 { + assert_eq!(Some(i + 15 - 0), entry.backward(i)); + } + for i in 37..100 { + assert_eq!(None, entry.backward(i)); + } + } +} diff --git a/year_2023/src/day6/mod.rs b/year_2023/src/day6.rs similarity index 53% rename from year_2023/src/day6/mod.rs rename to year_2023/src/day6.rs index 6dc2962..412f96f 100644 --- a/year_2023/src/day6/mod.rs +++ b/year_2023/src/day6.rs @@ -1,32 +1,14 @@ -use aoc_utils as utils; use std::iter::zip; -#[test] -fn test_mine() { - execute(); -} - -pub fn execute() { - let races = parse_races("mine.txt"); - assert_eq!(633080, optimize_races(&races)); +pub fn execute() -> String { + let races = parse_races("day6.txt"); + let part1 = optimize_races(&races); - let race = Race::from_file("mine.txt"); + let race = Race::from_file("day6.txt"); let (min, max) = race.optimize(); - assert_eq!(7430123, min); - assert_eq!(27478863, max); - assert_eq!(20048741, max - min + 1); -} - -#[test] -fn test_optimize_races() { - let races = parse_races("example.txt"); - assert_eq!(288, optimize_races(&races)); + let part2 = max - min + 1; - let race = Race::from_file("example.txt"); - let (min, max) = race.optimize(); - assert_eq!(14, min); - assert_eq!(71516, max); - assert_eq!(71503, max - min + 1); + format!("{} {}", part1, part2) } fn optimize_races(races: &Vec) -> u64 { @@ -36,23 +18,9 @@ fn optimize_races(races: &Vec) -> u64 { .map(|(min, max)| max - min + 1) .product(); } - -#[test] -fn test_parse_races() { - let races = parse_races("example.txt"); - assert_eq!(3, races.len()); - - assert_eq!(7, races[0].time); - assert_eq!(9, races[0].dist); - assert_eq!(15, races[1].time); - assert_eq!(40, races[1].dist); - assert_eq!(30, races[2].time); - assert_eq!(200, races[2].dist); -} - fn parse_races(filename: &str) -> Vec { - let path = format!("src/day6/{}", &filename); - let lines = utils::read_lines(&path); + let path = format!("input/{}", &filename); + let lines = aoc_utils::read_lines(&path); let times_line = lines[0].clone(); let dists_line = lines[1].clone(); @@ -74,32 +42,11 @@ fn parse_races(filename: &str) -> Vec { return Vec::from_iter(zip(times, dists).map(|(time, dist)| Race { time, dist })); } -#[test] -fn test_parse_race_v2() { - let race = Race::from_file("example.txt"); - assert_eq!(71530, race.time); - assert_eq!(940200, race.dist); -} - struct Race { time: u64, dist: u64, } -#[test] -fn test_optimize_race() { - assert_eq!((2, 5), Race { time: 7, dist: 9 }.optimize()); - assert_eq!((4, 11), Race { time: 15, dist: 40 }.optimize()); - assert_eq!( - (11, 19), - Race { - time: 30, - dist: 200 - } - .optimize() - ); -} - impl Race { fn optimize(&self) -> (u64, u64) { let mut min = 1u64; @@ -122,8 +69,8 @@ impl Race { } fn from_file(filename: &str) -> Race { - let path = format!("src/day6/{}", &filename); - let lines = utils::read_lines(&path); + let path = format!("input/{}", &filename); + let lines = aoc_utils::read_lines(&path); let time_line = lines[0].clone(); let dist_line = lines[1].clone(); @@ -143,3 +90,58 @@ fn race(charge_time: u64, race_time: u64) -> u64 { let charge = charge_time as u64; return race * charge - charge * charge; } + +#[cfg(test)] +mod tests { + use super::*; + #[test] + fn test_mine() { + assert_eq!(execute(), "633080 20048741"); + } + + #[test] + fn test_optimize_races() { + let races = parse_races("day6-example.txt"); + assert_eq!(288, optimize_races(&races)); + + let race = Race::from_file("day6-example.txt"); + let (min, max) = race.optimize(); + assert_eq!(14, min); + assert_eq!(71516, max); + assert_eq!(71503, max - min + 1); + } + + #[test] + fn test_parse_races() { + let races = parse_races("day6-example.txt"); + assert_eq!(3, races.len()); + + assert_eq!(7, races[0].time); + assert_eq!(9, races[0].dist); + assert_eq!(15, races[1].time); + assert_eq!(40, races[1].dist); + assert_eq!(30, races[2].time); + assert_eq!(200, races[2].dist); + } + + #[test] + fn test_parse_race_v2() { + let race = Race::from_file("day6-example.txt"); + assert_eq!(71530, race.time); + assert_eq!(940200, race.dist); + } + + #[test] + fn test_optimize_race() { + assert_eq!((2, 5), Race { time: 7, dist: 9 }.optimize()); + assert_eq!((4, 11), Race { time: 15, dist: 40 }.optimize()); + assert_eq!( + (11, 19), + Race { + time: 30, + dist: 200 + } + .optimize() + ); + } +} diff --git a/year_2023/src/day7.rs b/year_2023/src/day7.rs new file mode 100644 index 0000000..36d4894 --- /dev/null +++ b/year_2023/src/day7.rs @@ -0,0 +1,343 @@ +use std::cmp::Ordering; +use std::collections::HashMap; +use std::ops::Index; +use std::slice::Iter; + +pub fn execute() -> String { + let hands = Hand::from_file("day7.txt", false); + let part1 = score(hands); + + let hands_with_jokers = Hand::from_file("day7.txt", true); + let part2 = score(hands_with_jokers); + + format!("{} {}", part1, part2) +} + +type Score = u64; +fn score(mut hands: Vec) -> Score { + hands.sort(); + + hands + .iter() + .enumerate() + .map(|(i, hand)| (i as Score + 1) * hand.bid) + .sum() +} + +#[derive(Debug, Eq)] +struct Hand { + cards: Vec, + bid: Score, + jokers: bool, +} +impl Hand { + fn from_file(filename: &str, jokers: bool) -> Vec { + let path = format!("input/{}", &filename); + aoc_utils::read_lines(&path) + .iter() + .map(|line| Hand::from_text(line, jokers)) + .collect() + } + + fn from_text(hand: &str, jokers: bool) -> Self { + let cards_text = &hand[0..5]; + let cards = cards_text + .chars() + .map(|card| card_from_text(card, jokers)) + .collect(); + + let bid_text = &hand[5..].trim(); + let bid = bid_text.parse::().unwrap_or(0); + + Hand { cards, bid, jokers } + } + + fn get_type(&self) -> HandType { + let mut counts = HashMap::::new(); + for &card_type in all_cards(self.jokers) { + let count = self.cards.iter().filter(|&&card| card == card_type).count(); + counts.insert(card_type, count); + } + + let joker = card_from_text('J', self.jokers); + + let mut counts_of_counts: Vec = counts + .iter() + .filter(|(&card, &count)| card != joker && count > 0) + .map(|(_card, &count)| count) + .collect(); + + counts_of_counts.sort(); + counts_of_counts.reverse(); + + let num_jokers = counts[&joker]; + + if self.jokers { + if counts_of_counts.is_empty() { + counts_of_counts.push(0); + } + counts_of_counts[0] += num_jokers; + } else if num_jokers > 0 { + counts_of_counts.push(num_jokers); + } + + counts_of_counts.sort(); + counts_of_counts.reverse(); + + if counts_of_counts == vec![5] { + return HandType::FiveOfAKind; + } else if counts_of_counts == vec![4, 1] { + return HandType::FourOfAKind; + } else if counts_of_counts == vec![3, 2] { + return HandType::FullHouse; + } else if counts_of_counts == vec![3, 1, 1] { + return HandType::ThreeOfAKind; + } else if counts_of_counts == vec![2, 2, 1] { + return HandType::TwoPairs; + } else if counts_of_counts == vec![2, 1, 1, 1] { + return HandType::OnePair; + } else if counts_of_counts == vec![1, 1, 1, 1, 1] { + return HandType::HighCard; + } + panic!("Unknown hand type: {:?}", self) + } + + fn get_ord_value(&self) -> (u8, u8, u8, u8, u8, u8) { + let hand_type = hand_type_value(self.get_type()); + ( + hand_type, + self.cards[0], + self.cards[1], + self.cards[2], + self.cards[3], + self.cards[4], + ) + } +} + +impl Ord for Hand { + fn cmp(&self, other: &Self) -> Ordering { + self.get_ord_value().cmp(&other.get_ord_value()) + } +} + +impl PartialOrd for Hand { + fn partial_cmp(&self, other: &Self) -> Option { + Some(self.cmp(other)) + } +} + +impl PartialEq for Hand { + fn eq(&self, other: &Self) -> bool { + self.get_ord_value() == other.get_ord_value() + } +} + +impl Index for Hand { + type Output = Card; + fn index(&self, index: usize) -> &Self::Output { + self.cards.index(index) + } +} + +enum HandType { + FiveOfAKind, + FourOfAKind, + FullHouse, + ThreeOfAKind, + TwoPairs, + OnePair, + HighCard, +} + +fn hand_type_value(hand_type: HandType) -> u8 { + match hand_type { + HandType::FiveOfAKind => 6, + HandType::FourOfAKind => 5, + HandType::FullHouse => 4, + HandType::ThreeOfAKind => 3, + HandType::TwoPairs => 2, + HandType::OnePair => 1, + HandType::HighCard => 0, + } +} + +type Card = u8; + +fn all_cards(jokers: bool) -> Iter<'static, Card> { + static CARDS: [u8; 13] = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; + static CARDS_WITH_JOKER: [u8; 13] = [2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 12, 13, 14]; + + if jokers { + CARDS_WITH_JOKER.iter() + } else { + CARDS.iter() + } +} + +fn card_from_text(card: char, jokers: bool) -> Card { + match card { + '2' => 2, + '3' => 3, + '4' => 4, + '5' => 5, + '6' => 6, + '7' => 7, + '8' => 8, + '9' => 9, + 'T' => 10, + 'J' => { + if jokers { + 1 + } else { + 11 + } + } + 'Q' => 12, + 'K' => 13, + 'A' => 14, + _ => { + panic!("Not a card: {card}") + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "253910319 254083736"); + } + + #[test] + fn test_score() { + let hands = Hand::from_file("day7-example.txt", false); + assert_eq!(6440, score(hands)); + + let hands_with_jokers = Hand::from_file("day7-example.txt", true); + assert_eq!(5905, score(hands_with_jokers)); + } + + #[test] + fn test_hands() { + let mut hands = Hand::from_file("day7-example.txt", false); + assert_eq!(5, hands.len()); + + hands.sort(); + assert_eq!(5, hands.len()); + + for (i, hand) in hands.iter().enumerate() { + let hand_text = match i { + 0 => "32T3K", + 1 => "KTJJT", + 2 => "KK677 ", + 3 => "T55J5", + 4 => "QQQJA", + _ => "Fail", + }; + assert_eq!(hand, &Hand::from_text(hand_text, false)); + } + } + + #[test] + fn test_hand() { + let hand1 = Hand::from_text("32T3K", false); + assert_eq!(5, hand1.cards.len()); + assert_eq!(hand1[0], 3); + assert_eq!(hand1[1], 2); + assert_eq!(hand1[2], 10); + assert_eq!(hand1[3], 3); + assert_eq!(hand1[4], 13); + + assert_eq!(hand1.bid, 0); + + assert!(matches!(hand1.get_type(), HandType::OnePair)); + + let hand2 = Hand::from_text("T55J5", false); + assert!(matches!(hand2.get_type(), HandType::ThreeOfAKind)); + + let hand3 = Hand::from_text("KK677", false); + assert!(matches!(hand3.get_type(), HandType::TwoPairs)); + + let hand4 = Hand::from_text("KTJJT 220", false); + assert!(matches!(hand4.get_type(), HandType::TwoPairs)); + assert_eq!(220, hand4.bid); + + let hand5 = Hand::from_text("QQQJA 48", false); + assert!(matches!(hand5.get_type(), HandType::ThreeOfAKind)); + assert_eq!(48, hand5.bid); + + assert!(hand1 < hand2); + assert!(hand1 < hand3); + assert!(hand1 < hand4); + assert!(hand1 < hand5); + assert!(hand4 < hand2); + assert!(hand4 < hand3); + assert!(hand4 < hand5); + assert!(hand3 < hand2); + assert!(hand3 < hand5); + assert!(hand2 < hand5); + + let mut hands = vec![&hand1, &hand2, &hand3, &hand4, &hand5]; + hands.sort(); + let expected = vec![&hand1, &hand4, &hand3, &hand2, &hand5]; + assert_eq!(expected, hands) + } + + #[test] + fn test_hand_with_jokers() { + let hand1 = Hand::from_text("32T3K", true); + assert!(matches!(hand1.get_type(), HandType::OnePair)); + + let hand2 = Hand::from_text("T55J5", true); + assert!(matches!(hand2.get_type(), HandType::FourOfAKind)); + + let hand3 = Hand::from_text("KK677", true); + assert!(matches!(hand3.get_type(), HandType::TwoPairs)); + + let hand4 = Hand::from_text("KTJJT", true); + assert!(matches!(hand4.get_type(), HandType::FourOfAKind)); + + let hand5 = Hand::from_text("QQQJA", true); + assert!(matches!(hand5.get_type(), HandType::FourOfAKind)); + + let hand6a = Hand::from_text("J2AAA", true); + assert!(matches!(hand6a.get_type(), HandType::FourOfAKind)); + + let hand6b = Hand::from_text("2JAAA", true); + assert!(matches!(hand6b.get_type(), HandType::FourOfAKind)); + + assert!(hand6a < hand6b); + + fn hand_type(hand_text: &str) -> HandType { + let hand = Hand::from_text(hand_text, true); + hand.get_type() + } + + assert!(matches!(hand_type("32T3K"), HandType::OnePair)); + assert!(matches!(hand_type("T55J5"), HandType::FourOfAKind)); + assert!(matches!(hand_type("KK677"), HandType::TwoPairs)); + assert!(matches!(hand_type("KTJJT"), HandType::FourOfAKind)); + assert!(matches!(hand_type("QQQJA"), HandType::FourOfAKind)); + + assert!(matches!(hand_type("23456"), HandType::HighCard)); + + assert!(matches!(hand_type("J3456"), HandType::OnePair)); + assert!(matches!(hand_type("J3356"), HandType::ThreeOfAKind)); + assert!(matches!(hand_type("J3336"), HandType::FourOfAKind)); + assert!(matches!(hand_type("J3333"), HandType::FiveOfAKind)); + + assert!(matches!(hand_type("JJ456"), HandType::ThreeOfAKind)); + assert!(matches!(hand_type("JJ446"), HandType::FourOfAKind)); + assert!(matches!(hand_type("JJ444"), HandType::FiveOfAKind)); + + assert!(matches!(hand_type("JJJ56"), HandType::FourOfAKind)); + assert!(matches!(hand_type("JJJ55"), HandType::FiveOfAKind)); + + assert!(matches!(hand_type("JJJJ6"), HandType::FiveOfAKind)); + + assert!(matches!(hand_type("JJJJJ"), HandType::FiveOfAKind)); + } +} diff --git a/year_2023/src/day7/mod.rs b/year_2023/src/day7/mod.rs deleted file mode 100644 index 8a16a7c..0000000 --- a/year_2023/src/day7/mod.rs +++ /dev/null @@ -1,339 +0,0 @@ -use aoc_utils as utils; -use std::cmp::Ordering; -use std::collections::HashMap; -use std::ops::Index; -use std::slice::Iter; - -#[test] -fn test_mine() { - execute(); -} - -pub fn execute() { - let hands = Hand::from_file("mine.txt", false); - assert_eq!(253910319, score(hands)); - - let hands_with_jokers = Hand::from_file("mine.txt", true); - assert_eq!(254083736, score(hands_with_jokers)); -} - -#[test] -fn test_score() { - let hands = Hand::from_file("example.txt", false); - assert_eq!(6440, score(hands)); - - let hands_with_jokers = Hand::from_file("example.txt", true); - assert_eq!(5905, score(hands_with_jokers)); -} - -type Score = u64; - -fn score(mut hands: Vec) -> Score { - hands.sort(); - - hands - .iter() - .enumerate() - .map(|(i, hand)| (i as Score + 1) * hand.bid) - .sum() -} - -#[test] -fn test_hands() { - let mut hands = Hand::from_file("example.txt", false); - assert_eq!(5, hands.len()); - - hands.sort(); - assert_eq!(5, hands.len()); - - for (i, hand) in hands.iter().enumerate() { - let hand_text = match i { - 0 => "32T3K", - 1 => "KTJJT", - 2 => "KK677 ", - 3 => "T55J5", - 4 => "QQQJA", - _ => "Fail", - }; - assert_eq!(hand, &Hand::from_text(hand_text, false)); - } -} - -#[test] -fn test_hand() { - let hand1 = Hand::from_text("32T3K", false); - assert_eq!(5, hand1.cards.len()); - assert_eq!(hand1[0], 3); - assert_eq!(hand1[1], 2); - assert_eq!(hand1[2], 10); - assert_eq!(hand1[3], 3); - assert_eq!(hand1[4], 13); - - assert_eq!(hand1.bid, 0); - - assert!(matches!(hand1.get_type(), HandType::OnePair)); - - let hand2 = Hand::from_text("T55J5", false); - assert!(matches!(hand2.get_type(), HandType::ThreeOfAKind)); - - let hand3 = Hand::from_text("KK677", false); - assert!(matches!(hand3.get_type(), HandType::TwoPairs)); - - let hand4 = Hand::from_text("KTJJT 220", false); - assert!(matches!(hand4.get_type(), HandType::TwoPairs)); - assert_eq!(220, hand4.bid); - - let hand5 = Hand::from_text("QQQJA 48", false); - assert!(matches!(hand5.get_type(), HandType::ThreeOfAKind)); - assert_eq!(48, hand5.bid); - - assert!(hand1 < hand2); - assert!(hand1 < hand3); - assert!(hand1 < hand4); - assert!(hand1 < hand5); - assert!(hand4 < hand2); - assert!(hand4 < hand3); - assert!(hand4 < hand5); - assert!(hand3 < hand2); - assert!(hand3 < hand5); - assert!(hand2 < hand5); - - let mut hands = vec![&hand1, &hand2, &hand3, &hand4, &hand5]; - hands.sort(); - let expected = vec![&hand1, &hand4, &hand3, &hand2, &hand5]; - assert_eq!(expected, hands) -} - -#[test] -fn test_hand_with_jokers() { - let hand1 = Hand::from_text("32T3K", true); - assert!(matches!(hand1.get_type(), HandType::OnePair)); - - let hand2 = Hand::from_text("T55J5", true); - assert!(matches!(hand2.get_type(), HandType::FourOfAKind)); - - let hand3 = Hand::from_text("KK677", true); - assert!(matches!(hand3.get_type(), HandType::TwoPairs)); - - let hand4 = Hand::from_text("KTJJT", true); - assert!(matches!(hand4.get_type(), HandType::FourOfAKind)); - - let hand5 = Hand::from_text("QQQJA", true); - assert!(matches!(hand5.get_type(), HandType::FourOfAKind)); - - let hand6a = Hand::from_text("J2AAA", true); - assert!(matches!(hand6a.get_type(), HandType::FourOfAKind)); - - let hand6b = Hand::from_text("2JAAA", true); - assert!(matches!(hand6b.get_type(), HandType::FourOfAKind)); - - assert!(hand6a < hand6b); - - fn hand_type(hand_text: &str) -> HandType { - let hand = Hand::from_text(hand_text, true); - hand.get_type() - } - - assert!(matches!(hand_type("32T3K"), HandType::OnePair)); - assert!(matches!(hand_type("T55J5"), HandType::FourOfAKind)); - assert!(matches!(hand_type("KK677"), HandType::TwoPairs)); - assert!(matches!(hand_type("KTJJT"), HandType::FourOfAKind)); - assert!(matches!(hand_type("QQQJA"), HandType::FourOfAKind)); - - assert!(matches!(hand_type("23456"), HandType::HighCard)); - - assert!(matches!(hand_type("J3456"), HandType::OnePair)); - assert!(matches!(hand_type("J3356"), HandType::ThreeOfAKind)); - assert!(matches!(hand_type("J3336"), HandType::FourOfAKind)); - assert!(matches!(hand_type("J3333"), HandType::FiveOfAKind)); - - assert!(matches!(hand_type("JJ456"), HandType::ThreeOfAKind)); - assert!(matches!(hand_type("JJ446"), HandType::FourOfAKind)); - assert!(matches!(hand_type("JJ444"), HandType::FiveOfAKind)); - - assert!(matches!(hand_type("JJJ56"), HandType::FourOfAKind)); - assert!(matches!(hand_type("JJJ55"), HandType::FiveOfAKind)); - - assert!(matches!(hand_type("JJJJ6"), HandType::FiveOfAKind)); - - assert!(matches!(hand_type("JJJJJ"), HandType::FiveOfAKind)); -} - -#[derive(Debug, Eq)] -struct Hand { - cards: Vec, - bid: Score, - jokers: bool, -} - -impl Hand { - fn from_file(filename: &str, jokers: bool) -> Vec { - let path = format!("src/day7/{}", &filename); - utils::read_lines(&path) - .iter() - .map(|line| Hand::from_text(line, jokers)) - .collect() - } - - fn from_text(hand: &str, jokers: bool) -> Self { - let cards_text = &hand[0..5]; - let cards = cards_text - .chars() - .map(|card| card_from_text(card, jokers)) - .collect(); - - let bid_text = &hand[5..].trim(); - let bid = bid_text.parse::().unwrap_or(0); - - Hand { cards, bid, jokers } - } - - fn get_type(&self) -> HandType { - let mut counts = HashMap::::new(); - for &card_type in all_cards(self.jokers) { - let count = self.cards.iter().filter(|&&card| card == card_type).count(); - counts.insert(card_type, count); - } - - let joker = card_from_text('J', self.jokers); - - let mut counts_of_counts: Vec = counts - .iter() - .filter(|(&card, &count)| card != joker && count > 0) - .map(|(_card, &count)| count) - .collect(); - - counts_of_counts.sort(); - counts_of_counts.reverse(); - - let num_jokers = counts[&joker]; - - if self.jokers { - if counts_of_counts.is_empty() { - counts_of_counts.push(0); - } - counts_of_counts[0] += num_jokers; - } else if num_jokers > 0 { - counts_of_counts.push(num_jokers); - } - - counts_of_counts.sort(); - counts_of_counts.reverse(); - - if counts_of_counts == vec![5] { - return HandType::FiveOfAKind; - } else if counts_of_counts == vec![4, 1] { - return HandType::FourOfAKind; - } else if counts_of_counts == vec![3, 2] { - return HandType::FullHouse; - } else if counts_of_counts == vec![3, 1, 1] { - return HandType::ThreeOfAKind; - } else if counts_of_counts == vec![2, 2, 1] { - return HandType::TwoPairs; - } else if counts_of_counts == vec![2, 1, 1, 1] { - return HandType::OnePair; - } else if counts_of_counts == vec![1, 1, 1, 1, 1] { - return HandType::HighCard; - } - panic!("Unknown hand type: {:?}", self) - } - - fn get_ord_value(&self) -> (u8, u8, u8, u8, u8, u8) { - let hand_type = hand_type_value(self.get_type()); - ( - hand_type, - self.cards[0], - self.cards[1], - self.cards[2], - self.cards[3], - self.cards[4], - ) - } -} - -impl Ord for Hand { - fn cmp(&self, other: &Self) -> Ordering { - self.get_ord_value().cmp(&other.get_ord_value()) - } -} - -impl PartialOrd for Hand { - fn partial_cmp(&self, other: &Self) -> Option { - Some(self.cmp(other)) - } -} - -impl PartialEq for Hand { - fn eq(&self, other: &Self) -> bool { - self.get_ord_value() == other.get_ord_value() - } -} - -impl Index for Hand { - type Output = Card; - fn index(&self, index: usize) -> &Self::Output { - self.cards.index(index) - } -} - -enum HandType { - FiveOfAKind, - FourOfAKind, - FullHouse, - ThreeOfAKind, - TwoPairs, - OnePair, - HighCard, -} - -fn hand_type_value(hand_type: HandType) -> u8 { - match hand_type { - HandType::FiveOfAKind => 6, - HandType::FourOfAKind => 5, - HandType::FullHouse => 4, - HandType::ThreeOfAKind => 3, - HandType::TwoPairs => 2, - HandType::OnePair => 1, - HandType::HighCard => 0, - } -} - -type Card = u8; - -fn all_cards(jokers: bool) -> Iter<'static, Card> { - static CARDS: [u8; 13] = [2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14]; - static CARDS_WITH_JOKER: [u8; 13] = [2, 3, 4, 5, 6, 7, 8, 9, 10, 1, 12, 13, 14]; - - if jokers { - CARDS_WITH_JOKER.iter() - } else { - CARDS.iter() - } -} - -fn card_from_text(card: char, jokers: bool) -> Card { - match card { - '2' => 2, - '3' => 3, - '4' => 4, - '5' => 5, - '6' => 6, - '7' => 7, - '8' => 8, - '9' => 9, - 'T' => 10, - 'J' => { - if jokers { - 1 - } else { - 11 - } - } - 'Q' => 12, - 'K' => 13, - 'A' => 14, - _ => { - panic!("Not a card: {card}") - } - } -} diff --git a/year_2023/src/day8.rs b/year_2023/src/day8.rs new file mode 100644 index 0000000..6b9cf84 --- /dev/null +++ b/year_2023/src/day8.rs @@ -0,0 +1,222 @@ +use std::collections::HashMap; + +pub fn execute() -> String { + let map = Map::from_file("day8.txt"); + let part1 = camel_steps(&map); + let part2 = ghost_steps(&map); + + format!("{} {}", part1, part2) +} + +fn camel_steps(map: &Map) -> usize { + let nav = map.navigator("AAA".into(), Some("ZZZ".into())); + nav.count() +} +fn ghost_steps(map: &Map) -> u64 { + let periods: Vec<_> = map + .nodes + .keys() + .filter(|&k| k.ends_with("A")) + .map(|start| ghost_navigator(map, start.clone()).count() as u32) + .collect(); + + let prime_factors = periods.iter().map(aoc_utils::prime_factors); + + let mut max_exponents = HashMap::new(); + for factors in prime_factors { + for (number, exponent) in factors { + let max_exponent = max_exponents.entry(number).or_insert(0); + if exponent > *max_exponent { + *max_exponent = exponent; + } + } + } + + max_exponents + .iter() + .map(|(&n, &exponent)| n.pow(exponent) as u64) + .product() +} +fn ghost_navigator(map: &Map, start: String) -> Navigator { + let nav = map.navigator(start, None); + nav +} +struct Navigator<'a> { + map: &'a Map, + location: Option, + position: usize, + end: Option, +} + +impl<'a> Iterator for Navigator<'a> { + type Item = String; + + fn next(&mut self) -> Option { + let current_location = &self.location.clone().unwrap(); + let current_node = self.map.nodes.get(current_location).unwrap(); + + if self.end.is_some() && self.end == Some(current_node.name.clone()) { + return None; + } else if self.end.is_none() && current_node.name.ends_with("Z") { + return None; + } + + if self.position >= self.map.instructions.len() { + self.position = 0; + } + let direction = self.map.instructions[self.position]; + + self.location = match direction { + 'L' => Some(current_node.left.clone()), + 'R' => Some(current_node.right.clone()), + _ => panic!("Invalid direction: {direction}"), + }; + self.position += 1; + + return self.location.clone(); + } +} + +struct Map { + instructions: Vec, + nodes: HashMap, +} + +impl Map { + fn from_file(filename: &str) -> Map { + let path = format!("input/{filename}"); + let lines = aoc_utils::read_lines(&path); + + let instructions = Vec::::from_iter(lines[0].chars()); + let mut nodes = HashMap::::new(); + for line in &lines[2..] { + let node = Node::from_line(line); + nodes.insert(node.name.clone(), node); + } + + Map { + instructions, + nodes, + } + } + + fn navigator(&self, start: String, end: Option) -> Navigator { + Navigator { + map: self, + location: Some(start), + position: 0, + end, + } + } +} + +struct Node { + name: String, + left: String, + right: String, +} +impl Node { + fn from_line(line: &str) -> Node { + let (name, directions) = line.split_once(" = ").unwrap(); + let (left, right) = directions + .trim() + .trim_matches(|c| "()".contains(c)) + .split_once(", ") + .unwrap(); + Node { + name: name.into(), + left: left.into(), + right: right.into(), + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "19199 13663968099527"); + } + + #[test] + fn test_camel_steps() { + let map1 = Map::from_file("day8-example1.txt"); + assert_eq!(2, camel_steps(&map1)); + + let map2 = Map::from_file("day8-example2.txt"); + assert_eq!(6, camel_steps(&map2)); + } + + #[test] + fn test_ghost_steps() { + let map3 = Map::from_file("day8-example3.txt"); + assert_eq!(6, ghost_steps(&map3)); + } + + #[test] + fn test_ghost_navigator() { + let map3 = Map::from_file("day8-example3.txt"); + assert_eq!(2, ghost_navigator(&map3, "11A".into()).count()); + assert_eq!(3, ghost_navigator(&map3, "22A".into()).count()); + } + #[test] + fn test_navigator() { + let map1 = Map::from_file("day8-example1.txt"); + let mut nav1 = map1.navigator("AAA".into(), Some("ZZZ".into())); + assert_eq!(0, nav1.position); + assert_eq!(Some(String::from("CCC")), nav1.next()); + assert_eq!(1, nav1.position); + assert_eq!(Some(String::from("ZZZ")), nav1.next()); + assert_eq!(2, nav1.position); + assert_eq!(None, nav1.next()); + + let map1 = Map::from_file("day8-example2.txt"); + let mut nav2 = map1.navigator("AAA".into(), Some("ZZZ".into())); + assert_eq!(0, nav2.position); + assert_eq!(Some(String::from("BBB")), nav2.next()); + assert_eq!(1, nav2.position); + assert_eq!(Some(String::from("AAA")), nav2.next()); + assert_eq!(2, nav2.position); + assert_eq!(Some(String::from("BBB")), nav2.next()); + assert_eq!(3, nav2.position); + assert_eq!(Some(String::from("AAA")), nav2.next()); + assert_eq!(1, nav2.position); + assert_eq!(Some(String::from("BBB")), nav2.next()); + assert_eq!(2, nav2.position); + assert_eq!(Some(String::from("ZZZ")), nav2.next()); + assert_eq!(3, nav2.position); + assert_eq!(None, nav2.next()); + } + + #[test] + fn test_parse_map() { + let map1 = Map::from_file("day8-example1.txt"); + + assert_eq!(vec!['R', 'L'], map1.instructions); + assert_eq!(7, map1.nodes.len()); + assert_eq!("ZZZ", map1.nodes["CCC"].left); + assert_eq!("GGG", map1.nodes["CCC"].right); + + let map2 = Map::from_file("day8-example2.txt"); + + assert_eq!(vec!['L', 'L', 'R'], map2.instructions); + assert_eq!(3, map2.nodes.len()); + assert_eq!("AAA", map2.nodes["BBB"].left); + assert_eq!("ZZZ", map2.nodes["BBB"].right); + } + + #[test] + fn test_node() { + let a = Node::from_line("AAA = (BBB, CCC)"); + assert_eq!("AAA", a.name); + assert_eq!("BBB", a.left); + assert_eq!("CCC", a.right); + + let a = Node::from_line("BBB = (DDD, EEE)"); + assert_eq!("BBB", a.name); + assert_eq!("DDD", a.left); + assert_eq!("EEE", a.right); + } +} diff --git a/year_2023/src/day8/mod.rs b/year_2023/src/day8/mod.rs deleted file mode 100644 index 9f58069..0000000 --- a/year_2023/src/day8/mod.rs +++ /dev/null @@ -1,220 +0,0 @@ -use aoc_utils as utils; -use std::collections::HashMap; - -#[test] -fn test_mine() { - execute(); -} - -pub fn execute() { - let map = Map::from_file("mine.txt"); - assert_eq!(19199, camel_steps(&map)); - assert_eq!(13663968099527, ghost_steps(&map)); -} - -#[test] -fn test_camel_steps() { - let map1 = Map::from_file("example1.txt"); - assert_eq!(2, camel_steps(&map1)); - - let map2 = Map::from_file("example2.txt"); - assert_eq!(6, camel_steps(&map2)); -} - -fn camel_steps(map: &Map) -> usize { - let nav = map.navigator("AAA".into(), Some("ZZZ".into())); - nav.count() -} - -#[test] -fn test_ghost_steps() { - let map3 = Map::from_file("example3.txt"); - assert_eq!(6, ghost_steps(&map3)); -} - -fn ghost_steps(map: &Map) -> u64 { - let periods: Vec<_> = map - .nodes - .keys() - .filter(|&k| k.ends_with("A")) - .map(|start| ghost_navigator(map, start.clone()).count() as u32) - .collect(); - - let prime_factors = periods.iter().map(utils::prime_factors); - - let mut max_exponents = HashMap::new(); - for factors in prime_factors { - for (number, exponent) in factors { - let max_exponent = max_exponents.entry(number).or_insert(0); - if exponent > *max_exponent { - *max_exponent = exponent; - } - } - } - - max_exponents - .iter() - .map(|(&n, &exponent)| n.pow(exponent) as u64) - .product() -} - -#[test] -fn test_ghost_navigator() { - let map3 = Map::from_file("example3.txt"); - assert_eq!(2, ghost_navigator(&map3, "11A".into()).count()); - assert_eq!(3, ghost_navigator(&map3, "22A".into()).count()); -} -fn ghost_navigator(map: &Map, start: String) -> Navigator { - let nav = map.navigator(start, None); - nav -} - -#[test] -fn test_navigator() { - let map1 = Map::from_file("example1.txt"); - let mut nav1 = map1.navigator("AAA".into(), Some("ZZZ".into())); - assert_eq!(0, nav1.position); - assert_eq!(Some(String::from("CCC")), nav1.next()); - assert_eq!(1, nav1.position); - assert_eq!(Some(String::from("ZZZ")), nav1.next()); - assert_eq!(2, nav1.position); - assert_eq!(None, nav1.next()); - - let map1 = Map::from_file("example2.txt"); - let mut nav2 = map1.navigator("AAA".into(), Some("ZZZ".into())); - assert_eq!(0, nav2.position); - assert_eq!(Some(String::from("BBB")), nav2.next()); - assert_eq!(1, nav2.position); - assert_eq!(Some(String::from("AAA")), nav2.next()); - assert_eq!(2, nav2.position); - assert_eq!(Some(String::from("BBB")), nav2.next()); - assert_eq!(3, nav2.position); - assert_eq!(Some(String::from("AAA")), nav2.next()); - assert_eq!(1, nav2.position); - assert_eq!(Some(String::from("BBB")), nav2.next()); - assert_eq!(2, nav2.position); - assert_eq!(Some(String::from("ZZZ")), nav2.next()); - assert_eq!(3, nav2.position); - assert_eq!(None, nav2.next()); -} - -struct Navigator<'a> { - map: &'a Map, - location: Option, - position: usize, - end: Option, -} - -impl<'a> Iterator for Navigator<'a> { - type Item = String; - - fn next(&mut self) -> Option { - let current_location = &self.location.clone().unwrap(); - let current_node = self.map.nodes.get(current_location).unwrap(); - - if self.end.is_some() && self.end == Some(current_node.name.clone()) { - return None; - } else if self.end.is_none() && current_node.name.ends_with("Z") { - return None; - } - - if self.position >= self.map.instructions.len() { - self.position = 0; - } - let direction = self.map.instructions[self.position]; - - self.location = match direction { - 'L' => Some(current_node.left.clone()), - 'R' => Some(current_node.right.clone()), - _ => panic!("Invalid direction: {direction}"), - }; - self.position += 1; - - return self.location.clone(); - } -} - -#[test] -fn test_parse_map() { - let map1 = Map::from_file("example1.txt"); - - assert_eq!(vec!['R', 'L'], map1.instructions); - assert_eq!(7, map1.nodes.len()); - assert_eq!("ZZZ", map1.nodes["CCC"].left); - assert_eq!("GGG", map1.nodes["CCC"].right); - - let map2 = Map::from_file("example2.txt"); - - assert_eq!(vec!['L', 'L', 'R'], map2.instructions); - assert_eq!(3, map2.nodes.len()); - assert_eq!("AAA", map2.nodes["BBB"].left); - assert_eq!("ZZZ", map2.nodes["BBB"].right); -} - -struct Map { - instructions: Vec, - nodes: HashMap, -} - -impl Map { - fn from_file(filename: &str) -> Map { - let path = format!("src/day8/{filename}"); - let lines = utils::read_lines(&path); - - let instructions = Vec::::from_iter(lines[0].chars()); - let mut nodes = HashMap::::new(); - for line in &lines[2..] { - let node = Node::from_line(line); - nodes.insert(node.name.clone(), node); - } - - Map { - instructions, - nodes, - } - } - - fn navigator(&self, start: String, end: Option) -> Navigator { - Navigator { - map: self, - location: Some(start), - position: 0, - end, - } - } -} - -#[test] -fn test_node() { - let a = Node::from_line("AAA = (BBB, CCC)"); - assert_eq!("AAA", a.name); - assert_eq!("BBB", a.left); - assert_eq!("CCC", a.right); - - let a = Node::from_line("BBB = (DDD, EEE)"); - assert_eq!("BBB", a.name); - assert_eq!("DDD", a.left); - assert_eq!("EEE", a.right); -} - -struct Node { - name: String, - left: String, - right: String, -} - -impl Node { - fn from_line(line: &str) -> Node { - let (name, directions) = line.split_once(" = ").unwrap(); - let (left, right) = directions - .trim() - .trim_matches(|c| "()".contains(c)) - .split_once(", ") - .unwrap(); - Node { - name: name.into(), - left: left.into(), - right: right.into(), - } - } -} diff --git a/year_2023/src/day9.rs b/year_2023/src/day9.rs new file mode 100644 index 0000000..5cb39a9 --- /dev/null +++ b/year_2023/src/day9.rs @@ -0,0 +1,187 @@ +use std::collections::VecDeque; + +pub fn execute() -> String { + let readings = Reading::from_file("day9.txt"); + + let (part1, part2) = extrapolate_all(readings); + + format!("{} {}", part1, part2) +} + +fn extrapolate_all(mut readings: Vec) -> (Value, Value) { + let mut total_right = 0 as Value; + let mut total_left = 0 as Value; + + for reading in readings.iter_mut() { + reading.extrapolate(); + total_right += reading.last(); + total_left += reading.first(); + } + + (total_right, total_left) +} + +type Value = i32; +type Values = VecDeque; +struct Reading { + values: Values, +} + +impl Reading { + fn from_file(filename: &str) -> Vec { + let path = format!("input/{}", &filename); + let lines = aoc_utils::read_lines(&path); + + lines + .iter() + .map(String::as_str) + .map(Reading::from_line) + .collect() + } + + fn from_line(line: &str) -> Reading { + let values = line + .trim() + .split_whitespace() + .map(str::parse::) + .map(Result::unwrap) + .collect(); + Reading { values } + } + + fn first(&self) -> Value { + self.values[0] + } + fn last(&self) -> Value { + self.values[self.values.len() - 1] + } + + fn extrapolate(&mut self) { + let mut derivatives = self.derive_fully(); + + let mut increment_back = 0 as Value; + let mut increment_front = 0 as Value; + for derivative in derivatives.iter_mut().rev() { + increment_back = derivative.last() + increment_back; + derivative.values.push_back(increment_back); + + increment_front = derivative.first() - increment_front; + derivative.values.push_front(increment_front); + } + + let last_value = self.last(); + self.values.push_back(last_value + increment_back); + + let first_value = self.first(); + self.values.push_front(first_value - increment_front); + } + + fn derive_fully(&self) -> Vec { + let mut result = Vec::new(); + let mut cur = self; + while !cur.is_zeros() { + let new = cur.derive(); + result.push(new); + cur = result.last().unwrap(); + } + result + } + + fn derive(&self) -> Reading { + let values = (1..self.values.len()) + .map(|i| self.values[i] - self.values[i - 1]) + .collect(); + Reading { values } + } + + fn is_zeros(&self) -> bool { + self.values.iter().all(|&v| v == 0) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_mine() { + assert_eq!(execute(), "1772145754 867"); + } + + #[test] + fn test_extrapolate_all() { + let readings = Reading::from_file("day9-example.txt"); + assert_eq!((114, 2), extrapolate_all(readings)); + } + + #[test] + fn test_extrapolate() { + let mut reading1 = Reading::from_line("0 3 6 9 12 15"); + reading1.extrapolate(); + + assert_eq!(8, reading1.values.len()); + assert_eq!(18, reading1.values[7]); + assert_eq!(-3, reading1.values[0]); + + let mut reading2 = Reading::from_line("10 13 16 21 30 45"); + reading2.extrapolate(); + + assert_eq!(8, reading2.values.len()); + assert_eq!(68, reading2.values[7]); + assert_eq!(5, reading2.values[0]); + } + + #[test] + fn test_derive_fully() { + let reading1 = Reading::from_line("0 3 6 9 12 15"); + assert_eq!(2, reading1.derive_fully().len()); + + let reading2 = Reading::from_line("1 3 6 10 15 21"); + assert_eq!(3, reading2.derive_fully().len()); + + let reading3 = Reading::from_line("10 13 16 21 30 45"); + assert_eq!(4, reading3.derive_fully().len()); + } + + #[test] + fn test_is_zeros() { + let reading1 = Reading::from_line("0 3 6 9 12 15"); + assert_eq!(false, reading1.is_zeros()); + let reading2 = Reading::from_line("0 0 0"); + assert_eq!(true, reading2.is_zeros()); + let reading2 = Reading::from_line(""); + assert_eq!(true, reading2.is_zeros()); + } + + #[test] + fn test_derive_reading() { + let reading1 = Reading::from_line("0 3 6 9 12 15"); + let derivative1_1 = reading1.derive(); + assert_eq!(Values::from([3; 5]), derivative1_1.values); + let derivative1_2 = derivative1_1.derive(); + assert_eq!(Values::from([0; 4]), derivative1_2.values); + + let reading2 = Reading::from_line("1 3 6 10 15 21"); + let derivative2_1 = reading2.derive(); + assert_eq!(Values::from([2, 3, 4, 5, 6]), derivative2_1.values); + let derivative2_2 = derivative2_1.derive(); + assert_eq!(Values::from([1; 4]), derivative2_2.values); + let derivative2_3 = derivative2_2.derive(); + assert_eq!(Values::from([0; 3]), derivative2_3.values); + } + + #[test] + fn test_parse_readings() { + let readings = Reading::from_file("day9-example.txt"); + assert_eq!(3, readings.len()); + assert_eq!(Values::from([0, 3, 6, 9, 12, 15]), readings[0].values); + assert_eq!(Values::from([1, 3, 6, 10, 15, 21]), readings[1].values); + assert_eq!(Values::from([10, 13, 16, 21, 30, 45]), readings[2].values); + } + + #[test] + fn test_parse_reading() { + let reading = Reading::from_line("0 3 6 9 -12 15"); + assert_eq!(Values::from([0, 3, 6, 9, -12, 15]), reading.values); + } +} diff --git a/year_2023/src/day9/mod.rs b/year_2023/src/day9/mod.rs deleted file mode 100644 index b8dbc1d..0000000 --- a/year_2023/src/day9/mod.rs +++ /dev/null @@ -1,181 +0,0 @@ -use aoc_utils as utils; -use std::collections::VecDeque; - -#[test] -fn test_mine() { - execute(); -} - -pub fn execute() { - let readings = Reading::from_file("mine.txt"); - assert_eq!((1772145754, 867), extrapolate_all(readings)); -} - -#[test] -fn test_extrapolate_all() { - let readings = Reading::from_file("example.txt"); - assert_eq!((114, 2), extrapolate_all(readings)); -} - -fn extrapolate_all(mut readings: Vec) -> (Value, Value) { - let mut total_right = 0 as Value; - let mut total_left = 0 as Value; - - for reading in readings.iter_mut() { - reading.extrapolate(); - total_right += reading.last(); - total_left += reading.first(); - } - - (total_right, total_left) -} - -#[test] -fn test_extrapolate() { - let mut reading1 = Reading::from_line("0 3 6 9 12 15"); - reading1.extrapolate(); - - assert_eq!(8, reading1.values.len()); - assert_eq!(18, reading1.values[7]); - assert_eq!(-3, reading1.values[0]); - - let mut reading2 = Reading::from_line("10 13 16 21 30 45"); - reading2.extrapolate(); - - assert_eq!(8, reading2.values.len()); - assert_eq!(68, reading2.values[7]); - assert_eq!(5, reading2.values[0]); -} - -#[test] -fn test_derive_fully() { - let reading1 = Reading::from_line("0 3 6 9 12 15"); - assert_eq!(2, reading1.derive_fully().len()); - - let reading2 = Reading::from_line("1 3 6 10 15 21"); - assert_eq!(3, reading2.derive_fully().len()); - - let reading3 = Reading::from_line("10 13 16 21 30 45"); - assert_eq!(4, reading3.derive_fully().len()); -} - -#[test] -fn test_is_zeros() { - let reading1 = Reading::from_line("0 3 6 9 12 15"); - assert_eq!(false, reading1.is_zeros()); - let reading2 = Reading::from_line("0 0 0"); - assert_eq!(true, reading2.is_zeros()); - let reading2 = Reading::from_line(""); - assert_eq!(true, reading2.is_zeros()); -} - -#[test] -fn test_derive_reading() { - let reading1 = Reading::from_line("0 3 6 9 12 15"); - let derivative1_1 = reading1.derive(); - assert_eq!(Values::from([3; 5]), derivative1_1.values); - let derivative1_2 = derivative1_1.derive(); - assert_eq!(Values::from([0; 4]), derivative1_2.values); - - let reading2 = Reading::from_line("1 3 6 10 15 21"); - let derivative2_1 = reading2.derive(); - assert_eq!(Values::from([2, 3, 4, 5, 6]), derivative2_1.values); - let derivative2_2 = derivative2_1.derive(); - assert_eq!(Values::from([1; 4]), derivative2_2.values); - let derivative2_3 = derivative2_2.derive(); - assert_eq!(Values::from([0; 3]), derivative2_3.values); -} - -#[test] -fn test_parse_readings() { - let readings = Reading::from_file("example.txt"); - assert_eq!(3, readings.len()); - assert_eq!(Values::from([0, 3, 6, 9, 12, 15]), readings[0].values); - assert_eq!(Values::from([1, 3, 6, 10, 15, 21]), readings[1].values); - assert_eq!(Values::from([10, 13, 16, 21, 30, 45]), readings[2].values); -} - -#[test] -fn test_parse_reading() { - let reading = Reading::from_line("0 3 6 9 -12 15"); - assert_eq!(Values::from([0, 3, 6, 9, -12, 15]), reading.values); -} - -type Value = i32; -type Values = VecDeque; - -struct Reading { - values: Values, -} - -impl Reading { - fn from_file(filename: &str) -> Vec { - let path = format!("src/day9/{}", &filename); - let lines = utils::read_lines(&path); - - lines - .iter() - .map(String::as_str) - .map(Reading::from_line) - .collect() - } - - fn from_line(line: &str) -> Reading { - let values = line - .trim() - .split_whitespace() - .map(str::parse::) - .map(Result::unwrap) - .collect(); - Reading { values } - } - - fn first(&self) -> Value { - self.values[0] - } - fn last(&self) -> Value { - self.values[self.values.len() - 1] - } - - fn extrapolate(&mut self) { - let mut derivatives = self.derive_fully(); - - let mut increment_back = 0 as Value; - let mut increment_front = 0 as Value; - for derivative in derivatives.iter_mut().rev() { - increment_back = derivative.last() + increment_back; - derivative.values.push_back(increment_back); - - increment_front = derivative.first() - increment_front; - derivative.values.push_front(increment_front); - } - - let last_value = self.last(); - self.values.push_back(last_value + increment_back); - - let first_value = self.first(); - self.values.push_front(first_value - increment_front); - } - - fn derive_fully(&self) -> Vec { - let mut result = Vec::new(); - let mut cur = self; - while !cur.is_zeros() { - let new = cur.derive(); - result.push(new); - cur = result.last().unwrap(); - } - result - } - - fn derive(&self) -> Reading { - let values = (1..self.values.len()) - .map(|i| self.values[i] - self.values[i - 1]) - .collect(); - Reading { values } - } - - fn is_zeros(&self) -> bool { - self.values.iter().all(|&v| v == 0) - } -} diff --git a/year_2023/src/main.rs b/year_2023/src/main.rs index 12344e0..0313928 100644 --- a/year_2023/src/main.rs +++ b/year_2023/src/main.rs @@ -15,6 +15,7 @@ mod day21; mod day22; mod day23; mod day24; +mod day25; mod day3; mod day4; mod day5; @@ -22,31 +23,31 @@ mod day6; mod day7; mod day8; mod day9; -mod utils; fn main() { - day1::execute(); - day2::execute(); - day3::execute(); - day4::execute(); - day5::execute(); - day6::execute(); - day7::execute(); - day8::execute(); - day9::execute(); - day10::execute(); - day11::execute(); - day12::execute(); - day13::execute(); - day14::execute(); - day15::execute(); - day16::execute(); - day17::execute(); - day18::execute(); - day19::execute(); - day20::execute(); - day21::execute(); - day22::execute(); - day23::execute(); - day24::execute(); + println!("Day 1: {}", day1::execute()); + println!("Day 2: {}", day2::execute()); + println!("Day 3: {}", day3::execute()); + println!("Day 4: {}", day4::execute()); + println!("Day 5: {}", day5::execute()); + println!("Day 6: {}", day6::execute()); + println!("Day 7: {}", day7::execute()); + println!("Day 8: {}", day8::execute()); + println!("Day 9: {}", day9::execute()); + println!("Day 10: {}", day10::execute()); + println!("Day 11: {}", day11::execute()); + println!("Day 12: {}", day12::execute()); + println!("Day 13: {}", day13::execute()); + println!("Day 14: {}", day14::execute()); + println!("Day 15: {}", day15::execute()); + println!("Day 16: {}", day16::execute()); + println!("Day 17: {}", day17::execute()); + println!("Day 18: {}", day18::execute()); + println!("Day 19: {}", day19::execute()); + println!("Day 20: {}", day20::execute()); + println!("Day 21: {}", day21::execute()); + println!("Day 22: {}", day22::execute()); + println!("Day 23: {}", day23::execute()); + println!("Day 24: {}", day24::execute()); + println!("Day 25: {}", day25::execute()); } diff --git a/year_2023/src/utils.rs b/year_2023/src/utils.rs deleted file mode 100644 index 8b13789..0000000 --- a/year_2023/src/utils.rs +++ /dev/null @@ -1 +0,0 @@ -