Compare commits

..

120 Commits

Author SHA1 Message Date
Mikaël Capelle
f1cd7e6c85 2024 day 4.
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-04 07:35:22 +01:00
Mikael CAPELLE
d16ee7f9ad Refactor 2024 day 3.
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-03 15:22:54 +01:00
0c46d3ed18 Add .drone.yml for CI. (#2)
All checks were successful
continuous-integration/drone/push Build is passing
2024-12-03 13:38:03 +00:00
Mikael CAPELLE
acb767184e Fix linting. 2024-12-03 14:11:29 +01:00
Mikael CAPELLE
cb0145baa2 2024 day 3. 2024-12-03 08:29:25 +01:00
Mikaël Capelle
a4ad0259a9 Fix 2024 day 2. 2024-12-02 18:44:50 +01:00
Mikael CAPELLE
82452c0751 Update Python dependencies. 2024-12-02 17:08:50 +01:00
Mikael CAPELLE
79cc208875 2024 day 2. 2024-12-02 15:42:56 +01:00
Mikaël Capelle
4dd2d5d9c9 2024 day 1. 2024-12-01 10:26:02 +01:00
Mikaël Capelle
def4305c1c 2015 day 22, part 1. 2024-12-01 10:25:49 +01:00
Mikaël Capelle
3edaa249fc 2015 day 21. 2024-01-20 17:57:37 +01:00
Mikaël Capelle
57fcb47fe9 2015 day 20. 2024-01-06 22:07:34 +01:00
Mikaël Capelle
cfa7718475 2015 day 19. 2024-01-06 21:35:48 +01:00
Mikaël Capelle
2d23e355b2 2015 day 18. 2024-01-06 16:43:35 +01:00
Mikaël Capelle
fab4899715 2015 day 17. 2024-01-06 15:46:43 +01:00
Mikaël Capelle
b6e20eefa3 2015 day 16. 2024-01-06 15:27:45 +01:00
Mikaël Capelle
872fd72dcd 2015 day 15. 2024-01-06 15:11:47 +01:00
Mikaël Capelle
98f28e96f8 2015 day 12, 13 & 14. 2024-01-06 14:56:30 +01:00
Mikaël Capelle
ed7aba80ad 2015 day 11. 2024-01-06 11:46:59 +01:00
Mikaël Capelle
e507dad5e0 2015 day 10. 2024-01-06 11:30:10 +01:00
Mikael CAPELLE
04172beb5a 2015 day 9. 2024-01-05 14:46:05 +01:00
Mikael CAPELLE
15ef67e757 2015 day 8. 2024-01-05 10:01:02 +01:00
Mikaël Capelle
cd0ada785c 2015 day 4, 5, 6, 7. 2024-01-04 21:05:42 +01:00
Mikael CAPELLE
42bd8d6983 2015 day 3. 2024-01-04 18:36:30 +01:00
Mikael CAPELLE
0567ab7440 2015 day 1 & 2. 2024-01-04 18:27:17 +01:00
Mikaël Capelle
7d2eb6b5ec Faster 2023 day 24 (part 1). 2024-01-01 18:44:13 +01:00
Mikaël Capelle
3a9c7e728b Minor cleaning 2023. 2023-12-30 19:35:06 +01:00
Mikaël Capelle
d002e419c3 2023 day 25. 2023-12-25 10:36:36 +01:00
Mikaël Capelle
19d93e0c1d 2023 day 24. 2023-12-25 10:36:29 +01:00
Mikaël Capelle
5c05ee5c85 2023 day 23. 2023-12-23 11:05:35 +01:00
Mikaël Capelle
103af21915 2021 day 9. 2023-12-22 21:26:13 +01:00
Mikaël Capelle
af2fbf2da1 2023 day 22. 2023-12-22 14:49:31 +01:00
Mikaël Capelle
c496ea25c9 2023 day 21, version 2. 2023-12-21 21:56:38 +01:00
Mikael CAPELLE
5f8c74fd1c 2023 day 21. 2023-12-21 16:47:43 +01:00
Mikael CAPELLE
dda2be2505 2023 day 20. 2023-12-20 14:27:25 +01:00
Mikael CAPELLE
12891194bb Poetry stuff. 2023-12-19 17:45:33 +01:00
Mikaël Capelle
f15908876d 2023 day 19. 2023-12-19 10:41:53 +01:00
Mikael CAPELLE
5f5ebda674 2023 day 18. 2023-12-18 11:40:32 +01:00
Mikaël Capelle
5b30cc00d5 2023 day 17. 2023-12-17 18:19:49 +01:00
Mikaël Capelle
3a7f8e83dc 2023 day 16. 2023-12-16 18:33:11 +01:00
Mikaël Capelle
ba5b01c594 2023 day 15. 2023-12-15 16:18:21 +01:00
Mikaël Capelle
d0970c090b 2023 day 14. 2023-12-14 19:34:38 +01:00
Mikaël Capelle
8e90bf7002 2023 day 13. 2023-12-13 20:05:05 +01:00
9698dfcdac 2023 day 12. 2023-12-12 20:20:26 +00:00
Mikaël Capelle
1a6ab1cc0e 2023 day 11. 2023-12-11 10:25:56 +01:00
Mikaël Capelle
f5aabbee8f 2021 day 8. 2023-12-10 20:23:22 +01:00
Mikaël Capelle
6c00341ab0 2023 day 10. 2023-12-10 10:09:12 +01:00
Mikaël Capelle
755e0bd4b3 2021 day 7. 2023-12-09 12:52:46 +01:00
Mikaël Capelle
a52d077a40 2021 day 6. 2023-12-09 12:36:10 +01:00
Mikaël Capelle
3fc0f94b1c 2021 day 1-5. 2023-12-09 11:01:28 +01:00
Mikaël Capelle
8a0412f926 Clean 2022. 2023-12-09 09:54:53 +01:00
Mikaël Capelle
855efeb0aa 2023 day 9. 2023-12-09 08:08:46 +01:00
Mikaël Capelle
f2a65e03e5 2023 day 8. 2023-12-08 08:44:03 +01:00
Mikaël Capelle
759f47bfab 2023 day 7. 2023-12-08 08:38:08 +01:00
Mikael CAPELLE
999207b007 Clean 2023. 2023-12-06 08:47:59 +01:00
Mikaël Capelle
d92e4744a4 2023 day 6. 2023-12-06 07:14:26 +01:00
Mikael CAPELLE
8dbf0f101c 2023 day 5. 2023-12-05 13:41:17 +01:00
Mikaël Capelle
b8d8df06d6 2023 day 5 part 1. 2023-12-05 07:22:56 +01:00
Mikaël Capelle
825ebea299 Prepare 2023 days. 2023-12-04 19:32:41 +01:00
Mikaël Capelle
869cd4477f 2023 day 4. 2023-12-04 19:30:44 +01:00
Mikaël Capelle
fd777627d6 2023 day 3. 2023-12-03 09:09:25 +01:00
Mikaël Capelle
98f605f30e 2023 day 2. 2023-12-02 09:47:52 +01:00
Mikaël Capelle
d51fed283c Fix 2023 day 1. 2023-12-01 20:27:42 +01:00
Mikael CAPELLE
e991cd8b04 2023: Day 1. 2023-12-01 10:41:13 +01:00
Mikael CAPELLE
10f67e6bfd Post-christmas cleaning. 2023-01-06 13:48:18 +01:00
Mikaël Capelle
f291b0aa3f Day 25. 2022-12-25 11:34:49 +01:00
Mikaël Capelle
0eb5b5a88f Faster day 24. 2022-12-24 23:00:14 +01:00
Mikaël Capelle
2ec0a3d5f9 Day 24. 2022-12-24 20:50:37 +01:00
Mikael CAPELLE
0327a3f36a Add day 23. 2022-12-23 13:25:22 +01:00
Mikaël Capelle
3732e70ef7 Ugly day 22. 2022-12-22 23:00:36 +01:00
Mikaël Capelle
b0cc6b4a46 Update day 22. 2022-12-22 18:46:59 +01:00
Mikael CAPELLE
8c24b9f9e2 Update day 22. 2022-12-22 17:22:56 +01:00
Mikael CAPELLE
dca6f6a08f Update day 22. 2022-12-22 17:00:09 +01:00
Mikael CAPELLE
8d7a20f575 Day 22 part 1. 2022-12-22 14:10:30 +01:00
Mikael CAPELLE
3934dfd152 Start day 22. 2022-12-22 08:20:09 +01:00
Mikael CAPELLE
b656e8929e Remove tqdm from day 19. 2022-12-21 17:39:43 +01:00
Mikael CAPELLE
c9c69f479b Day 21. 2022-12-21 09:44:05 +01:00
Mikaël Capelle
72ebcfff1f Clean day 20. 2022-12-20 23:39:26 +01:00
Mikaël Capelle
dd72bb3238 Add empty files for following days. 2022-12-20 21:51:38 +01:00
Mikael CAPELLE
c1dd74c57d Faster day 20. 2022-12-20 18:27:09 +01:00
Mikael CAPELLE
1bf2de62c7 Day 20, slow. 2022-12-20 13:39:36 +01:00
Mikael CAPELLE
df808bc98a Start day 20. 2022-12-20 12:35:01 +01:00
Mikaël Capelle
f46e190e98 Add all tests from previous days. 2022-12-19 22:32:15 +01:00
Mikaël Capelle
7f4a34b2d7 Day 19. 2022-12-19 22:09:20 +01:00
Mikael CAPELLE
ddebd26db2 Tmp 2022-12-19 18:46:16 +01:00
Mikael CAPELLE
01300e23b2 Start working on day 19. 2022-12-19 13:51:32 +01:00
Mikaël Capelle
b8e2faa8c9 Clean day 17. 2022-12-18 16:46:00 +01:00
Mikaël Capelle
ea5b757180 Day 18. 2022-12-18 09:57:35 +01:00
Mikaël Capelle
89a71c175f Day 17. 2022-12-17 12:27:05 +01:00
Mikaël Capelle
9ffb332dea Day 17. 2022-12-17 12:25:48 +01:00
Mikaël Capelle
8167ab34c7 Less BFS for day 16. 2022-12-16 22:56:34 +01:00
Mikaël Capelle
100df02a09 Better day 16. 2022-12-16 22:52:03 +01:00
Mikael CAPELLE
15b987a590 Update ugly day 16. 2022-12-16 18:40:21 +01:00
Mikael CAPELLE
b1578f5709 Add ugly day 16, to be improved... 2022-12-16 09:21:54 +01:00
Mikael CAPELLE
d80dbb6c7c Update day 15. 2022-12-15 14:17:04 +01:00
Mikael CAPELLE
b679c1f895 Add day 15. 2022-12-15 09:36:19 +01:00
Mikael CAPELLE
e9d5f9747b Add day 14. 2022-12-14 09:29:56 +01:00
Mikael CAPELLE
fe3aad7ddd Clean day 9. 2022-12-13 11:18:31 +01:00
Mikael CAPELLE
7ac9981ae5 Clean day 13. 2022-12-13 08:59:53 +01:00
Mikael CAPELLE
652756a341 Add day 13. 2022-12-13 08:54:15 +01:00
Mikael CAPELLE
c7ef505f1b Clean day 12. 2022-12-12 17:55:24 +01:00
Mikael CAPELLE
c55f6ac8e1 One to many Dijkstra. 2022-12-12 17:50:48 +01:00
Mikael CAPELLE
726a6aecac Generic Dijkstra for day 12. 2022-12-12 15:59:19 +01:00
Mikael CAPELLE
291b188238 Clean day 12. 2022-12-12 10:48:37 +01:00
Mikael CAPELLE
289e3b7d02 Add day 12. 2022-12-12 09:35:12 +01:00
Mikaël Capelle
9820765e9c Clean monkey code. 2022-12-11 11:50:23 +01:00
Mikaël Capelle
c6522de8a2 Add day 11. 2022-12-11 11:42:47 +01:00
Mikaël Capelle
80465e5e53 Add day 10. 2022-12-10 10:24:23 +01:00
Mikaël Capelle
af1428b5e1 Add day 9. 2022-12-09 10:45:00 +01:00
Mikael CAPELLE
fca283527d Add 2022 day 8. 2022-12-08 08:59:25 +01:00
Mikael CAPELLE
0d37458ec5 Add 2021 day 5. 2022-12-07 18:41:39 +01:00
Mikael CAPELLE
198927e4a3 Add day 7. 2022-12-07 09:28:06 +01:00
Mikael CAPELLE
4192c98bba Cleaning. 2022-12-06 15:28:46 +01:00
Mikael CAPELLE
7cb8317659 Add day 6. 2022-12-06 09:03:22 +01:00
Mikaël Capelle
f46cb51c60 Add day 4. 2022-12-05 19:09:55 +01:00
Mikael CAPELLE
261a396ae7 Add day 5. 2022-12-05 08:58:25 +01:00
Mikaël Capelle
4b3af377ab Day 3. 2022-12-03 11:42:09 +01:00
Mikael CAPELLE
f697415ef2 More comments. 2022-12-02 15:06:37 +01:00
Mikael CAPELLE
ac2806b0fb Comments. 2022-12-02 09:21:38 +01:00
Mikael CAPELLE
c62b8abfd0 Day 2. 2022-12-02 09:12:49 +01:00
193 changed files with 3511 additions and 17480 deletions

518
poetry.lock generated
View File

@ -1,15 +1,4 @@
# This file is automatically @generated by Poetry 1.7.1 and should not be changed by hand.
[[package]]
name = "absl-py"
version = "2.1.0"
description = "Abseil Python Common Libraries, see https://github.com/abseil/abseil-py."
optional = false
python-versions = ">=3.7"
files = [
{file = "absl-py-2.1.0.tar.gz", hash = "sha256:7820790efbb316739cde8b4e19357243fc3608a152024288513dd968d7d959ff"},
{file = "absl_py-2.1.0-py3-none-any.whl", hash = "sha256:526a04eadab8b4ee719ce68f204172ead1027549089702d99b9059f129ff1308"},
]
# This file is automatically @generated by Poetry 1.8.4 and should not be changed by hand.
[[package]]
name = "appnope"
@ -144,30 +133,6 @@ traitlets = ">=4"
[package.extras]
test = ["pytest"]
[[package]]
name = "cplex"
version = "22.1.1.2"
description = "A Python interface to the CPLEX Callable Library, Community Edition."
optional = false
python-versions = "*"
files = [
{file = "cplex-22.1.1.2-cp310-cp310-macosx_10_6_x86_64.whl", hash = "sha256:d74a91f6e9c6f4ad4c7c69f7ab937ea9e91178a556f6f105c87eef9e434ea42e"},
{file = "cplex-22.1.1.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b066b5aa01fcf7cb471ad41920b3fecdd87dc95686e9a7031ff470873f0db10f"},
{file = "cplex-22.1.1.2-cp310-cp310-manylinux1_x86_64.whl", hash = "sha256:a7230032928b1a657c384d3f49c04d8b80d0ab8134a2f4c0b26ff50e71ec767a"},
{file = "cplex-22.1.1.2-cp310-cp310-manylinux2014_ppc64le.whl", hash = "sha256:dd81e8ee7a7f1fb5769bcbb1349b084b37c495cbd0db1a095d774f97d790ee3c"},
{file = "cplex-22.1.1.2-cp310-cp310-win_amd64.whl", hash = "sha256:68b33bb9ff84be3442a6f71000e7214781c6aa8674143a9aa79cb9a84e697dfd"},
{file = "cplex-22.1.1.2-cp311-cp311-macosx_10_6_x86_64.whl", hash = "sha256:cda2f59af50d6c3d6476b2e38aba1e947f9bd72591d71961a9d53b5582062ba9"},
{file = "cplex-22.1.1.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0f69a539ed50994e26e32c3d2b203eb5f112d1ba64400241614e1a91c0933974"},
{file = "cplex-22.1.1.2-cp311-cp311-manylinux1_x86_64.whl", hash = "sha256:2a0f6984980779e6878a6cded52ee08806bae49af6bd209c7740549417e69e96"},
{file = "cplex-22.1.1.2-cp311-cp311-manylinux2014_ppc64le.whl", hash = "sha256:0ac0005414a09facbeaa976a89b3153d4ed15f23a89bf5d283f65d4e951f63be"},
{file = "cplex-22.1.1.2-cp311-cp311-win_amd64.whl", hash = "sha256:46550cac476d74cc95dc3abf6f9bfe08c9fd61d889e20f2028f754b9fe503b88"},
{file = "cplex-22.1.1.2-cp39-cp39-macosx_10_6_x86_64.whl", hash = "sha256:21f6fd1ad4876a7775e64fe8a1fb43f6bb7a010c5e099abdb8c01887a8cc1d84"},
{file = "cplex-22.1.1.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:d6acbf74c3fe32f2138a98730d1ebe3fa275c8c3fdcd8b1f68d312bfe9ef6899"},
{file = "cplex-22.1.1.2-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:3211f9c84f44c6317ea1e83a6c2ff1cfdc08532f421987b7faa8c07a018dfae5"},
{file = "cplex-22.1.1.2-cp39-cp39-manylinux2014_ppc64le.whl", hash = "sha256:648ad8c1c83ea30b0802c571a0dbf7fac23dcb9dc121ea0768c1794313aec2a7"},
{file = "cplex-22.1.1.2-cp39-cp39-win_amd64.whl", hash = "sha256:285b26008a77942c6c9c29bff91e1658c1beed2aa520e1a8b26137d81abf81dc"},
]
[[package]]
name = "debugpy"
version = "1.8.9"
@ -214,19 +179,6 @@ files = [
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
]
[[package]]
name = "docplex"
version = "2.28.240"
description = "The IBM Decision Optimization CPLEX Modeling for Python"
optional = false
python-versions = "*"
files = [
{file = "docplex-2.28.240.tar.gz", hash = "sha256:c0de407e33f8709bb4cd91b6efeb96fd88bfecbdce2caec51afb79253bde6ff5"},
]
[package.dependencies]
six = "*"
[[package]]
name = "exceptiongroup"
version = "1.2.2"
@ -255,50 +207,6 @@ files = [
[package.extras]
tests = ["asttokens (>=2.1.0)", "coverage", "coverage-enable-subprocess", "ipython", "littleutils", "pytest", "rich"]
[[package]]
name = "imageio"
version = "2.36.1"
description = "Library for reading and writing a wide range of image, video, scientific, and volumetric data formats."
optional = false
python-versions = ">=3.9"
files = [
{file = "imageio-2.36.1-py3-none-any.whl", hash = "sha256:20abd2cae58e55ca1af8a8dcf43293336a59adf0391f1917bf8518633cfc2cdf"},
{file = "imageio-2.36.1.tar.gz", hash = "sha256:e4e1d231f47f9a9e16100b0f7ce1a86e8856fb4d1c0fa2c4365a316f1746be62"},
]
[package.dependencies]
numpy = "*"
pillow = ">=8.3.2"
[package.extras]
all-plugins = ["astropy", "av", "imageio-ffmpeg", "numpy (>2)", "pillow-heif", "psutil", "rawpy", "tifffile"]
all-plugins-pypy = ["av", "imageio-ffmpeg", "pillow-heif", "psutil", "tifffile"]
build = ["wheel"]
dev = ["black", "flake8", "fsspec[github]", "pytest", "pytest-cov"]
docs = ["numpydoc", "pydata-sphinx-theme", "sphinx (<6)"]
ffmpeg = ["imageio-ffmpeg", "psutil"]
fits = ["astropy"]
full = ["astropy", "av", "black", "flake8", "fsspec[github]", "gdal", "imageio-ffmpeg", "itk", "numpy (>2)", "numpydoc", "pillow-heif", "psutil", "pydata-sphinx-theme", "pytest", "pytest-cov", "rawpy", "sphinx (<6)", "tifffile", "wheel"]
gdal = ["gdal"]
itk = ["itk"]
linting = ["black", "flake8"]
pillow-heif = ["pillow-heif"]
pyav = ["av"]
rawpy = ["numpy (>2)", "rawpy"]
test = ["fsspec[github]", "pytest", "pytest-cov"]
tifffile = ["tifffile"]
[[package]]
name = "immutabledict"
version = "4.2.1"
description = "Immutable wrapper around dictionaries (a fork of frozendict)"
optional = false
python-versions = ">=3.8"
files = [
{file = "immutabledict-4.2.1-py3-none-any.whl", hash = "sha256:c56a26ced38c236f79e74af3ccce53772827cef5c3bce7cab33ff2060f756373"},
{file = "immutabledict-4.2.1.tar.gz", hash = "sha256:d91017248981c72eb66c8ff9834e99c2f53562346f23e7f51e7a5ebcf66a3bcc"},
]
[[package]]
name = "ipykernel"
version = "6.29.5"
@ -522,133 +430,68 @@ files = [
[[package]]
name = "numpy"
version = "2.2.0"
version = "2.1.3"
description = "Fundamental package for array computing in Python"
optional = false
python-versions = ">=3.10"
files = [
{file = "numpy-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:1e25507d85da11ff5066269d0bd25d06e0a0f2e908415534f3e603d2a78e4ffa"},
{file = "numpy-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:a62eb442011776e4036af5c8b1a00b706c5bc02dc15eb5344b0c750428c94219"},
{file = "numpy-2.2.0-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:b606b1aaf802e6468c2608c65ff7ece53eae1a6874b3765f69b8ceb20c5fa78e"},
{file = "numpy-2.2.0-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:36b2b43146f646642b425dd2027730f99bac962618ec2052932157e213a040e9"},
{file = "numpy-2.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7fe8f3583e0607ad4e43a954e35c1748b553bfe9fdac8635c02058023277d1b3"},
{file = "numpy-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:122fd2fcfafdefc889c64ad99c228d5a1f9692c3a83f56c292618a59aa60ae83"},
{file = "numpy-2.2.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:3f2f5cddeaa4424a0a118924b988746db6ffa8565e5829b1841a8a3bd73eb59a"},
{file = "numpy-2.2.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:7fe4bb0695fe986a9e4deec3b6857003b4cfe5c5e4aac0b95f6a658c14635e31"},
{file = "numpy-2.2.0-cp310-cp310-win32.whl", hash = "sha256:b30042fe92dbd79f1ba7f6898fada10bdaad1847c44f2dff9a16147e00a93661"},
{file = "numpy-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:54dc1d6d66f8d37843ed281773c7174f03bf7ad826523f73435deb88ba60d2d4"},
{file = "numpy-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:9874bc2ff574c40ab7a5cbb7464bf9b045d617e36754a7bc93f933d52bd9ffc6"},
{file = "numpy-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:0da8495970f6b101ddd0c38ace92edea30e7e12b9a926b57f5fabb1ecc25bb90"},
{file = "numpy-2.2.0-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0557eebc699c1c34cccdd8c3778c9294e8196df27d713706895edc6f57d29608"},
{file = "numpy-2.2.0-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:3579eaeb5e07f3ded59298ce22b65f877a86ba8e9fe701f5576c99bb17c283da"},
{file = "numpy-2.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40deb10198bbaa531509aad0cd2f9fadb26c8b94070831e2208e7df543562b74"},
{file = "numpy-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c2aed8fcf8abc3020d6a9ccb31dbc9e7d7819c56a348cc88fd44be269b37427e"},
{file = "numpy-2.2.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:a222d764352c773aa5ebde02dd84dba3279c81c6db2e482d62a3fa54e5ece69b"},
{file = "numpy-2.2.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:4e58666988605e251d42c2818c7d3d8991555381be26399303053b58a5bbf30d"},
{file = "numpy-2.2.0-cp311-cp311-win32.whl", hash = "sha256:4723a50e1523e1de4fccd1b9a6dcea750c2102461e9a02b2ac55ffeae09a4410"},
{file = "numpy-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:16757cf28621e43e252c560d25b15f18a2f11da94fea344bf26c599b9cf54b73"},
{file = "numpy-2.2.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:cff210198bb4cae3f3c100444c5eaa573a823f05c253e7188e1362a5555235b3"},
{file = "numpy-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:58b92a5828bd4d9aa0952492b7de803135038de47343b2aa3cc23f3b71a3dc4e"},
{file = "numpy-2.2.0-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:ebe5e59545401fbb1b24da76f006ab19734ae71e703cdb4a8b347e84a0cece67"},
{file = "numpy-2.2.0-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e2b8cd48a9942ed3f85b95ca4105c45758438c7ed28fff1e4ce3e57c3b589d8e"},
{file = "numpy-2.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57fcc997ffc0bef234b8875a54d4058afa92b0b0c4223fc1f62f24b3b5e86038"},
{file = "numpy-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85ad7d11b309bd132d74397fcf2920933c9d1dc865487128f5c03d580f2c3d03"},
{file = "numpy-2.2.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:cb24cca1968b21355cc6f3da1a20cd1cebd8a023e3c5b09b432444617949085a"},
{file = "numpy-2.2.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:0798b138c291d792f8ea40fe3768610f3c7dd2574389e37c3f26573757c8f7ef"},
{file = "numpy-2.2.0-cp312-cp312-win32.whl", hash = "sha256:afe8fb968743d40435c3827632fd36c5fbde633b0423da7692e426529b1759b1"},
{file = "numpy-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:3a4199f519e57d517ebd48cb76b36c82da0360781c6a0353e64c0cac30ecaad3"},
{file = "numpy-2.2.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:f8c8b141ef9699ae777c6278b52c706b653bf15d135d302754f6b2e90eb30367"},
{file = "numpy-2.2.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0f0986e917aca18f7a567b812ef7ca9391288e2acb7a4308aa9d265bd724bdae"},
{file = "numpy-2.2.0-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:1c92113619f7b272838b8d6702a7f8ebe5edea0df48166c47929611d0b4dea69"},
{file = "numpy-2.2.0-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:5a145e956b374e72ad1dff82779177d4a3c62bc8248f41b80cb5122e68f22d13"},
{file = "numpy-2.2.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18142b497d70a34b01642b9feabb70156311b326fdddd875a9981f34a369b671"},
{file = "numpy-2.2.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7d41d1612c1a82b64697e894b75db6758d4f21c3ec069d841e60ebe54b5b571"},
{file = "numpy-2.2.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:a98f6f20465e7618c83252c02041517bd2f7ea29be5378f09667a8f654a5918d"},
{file = "numpy-2.2.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e09d40edfdb4e260cb1567d8ae770ccf3b8b7e9f0d9b5c2a9992696b30ce2742"},
{file = "numpy-2.2.0-cp313-cp313-win32.whl", hash = "sha256:3905a5fffcc23e597ee4d9fb3fcd209bd658c352657548db7316e810ca80458e"},
{file = "numpy-2.2.0-cp313-cp313-win_amd64.whl", hash = "sha256:a184288538e6ad699cbe6b24859206e38ce5fba28f3bcfa51c90d0502c1582b2"},
{file = "numpy-2.2.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:7832f9e8eb00be32f15fdfb9a981d6955ea9adc8574c521d48710171b6c55e95"},
{file = "numpy-2.2.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:f0dd071b95bbca244f4cb7f70b77d2ff3aaaba7fa16dc41f58d14854a6204e6c"},
{file = "numpy-2.2.0-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:b0b227dcff8cdc3efbce66d4e50891f04d0a387cce282fe1e66199146a6a8fca"},
{file = "numpy-2.2.0-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:6ab153263a7c5ccaf6dfe7e53447b74f77789f28ecb278c3b5d49db7ece10d6d"},
{file = "numpy-2.2.0-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e500aba968a48e9019e42c0c199b7ec0696a97fa69037bea163b55398e390529"},
{file = "numpy-2.2.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:440cfb3db4c5029775803794f8638fbdbf71ec702caf32735f53b008e1eaece3"},
{file = "numpy-2.2.0-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:a55dc7a7f0b6198b07ec0cd445fbb98b05234e8b00c5ac4874a63372ba98d4ab"},
{file = "numpy-2.2.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4bddbaa30d78c86329b26bd6aaaea06b1e47444da99eddac7bf1e2fab717bd72"},
{file = "numpy-2.2.0-cp313-cp313t-win32.whl", hash = "sha256:30bf971c12e4365153afb31fc73f441d4da157153f3400b82db32d04de1e4066"},
{file = "numpy-2.2.0-cp313-cp313t-win_amd64.whl", hash = "sha256:d35717333b39d1b6bb8433fa758a55f1081543de527171543a2b710551d40881"},
{file = "numpy-2.2.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:e12c6c1ce84628c52d6367863773f7c8c8241be554e8b79686e91a43f1733773"},
{file = "numpy-2.2.0-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:b6207dc8fb3c8cb5668e885cef9ec7f70189bec4e276f0ff70d5aa078d32c88e"},
{file = "numpy-2.2.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a50aeff71d0f97b6450d33940c7181b08be1441c6c193e678211bff11aa725e7"},
{file = "numpy-2.2.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:df12a1f99b99f569a7c2ae59aa2d31724e8d835fc7f33e14f4792e3071d11221"},
{file = "numpy-2.2.0.tar.gz", hash = "sha256:140dd80ff8981a583a60980be1a655068f8adebf7a45a06a6858c873fcdcd4a0"},
{file = "numpy-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:c894b4305373b9c5576d7a12b473702afdf48ce5369c074ba304cc5ad8730dff"},
{file = "numpy-2.1.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b47fbb433d3260adcd51eb54f92a2ffbc90a4595f8970ee00e064c644ac788f5"},
{file = "numpy-2.1.3-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:825656d0743699c529c5943554d223c021ff0494ff1442152ce887ef4f7561a1"},
{file = "numpy-2.1.3-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:6a4825252fcc430a182ac4dee5a505053d262c807f8a924603d411f6718b88fd"},
{file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e711e02f49e176a01d0349d82cb5f05ba4db7d5e7e0defd026328e5cfb3226d3"},
{file = "numpy-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:78574ac2d1a4a02421f25da9559850d59457bac82f2b8d7a44fe83a64f770098"},
{file = "numpy-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c7662f0e3673fe4e832fe07b65c50342ea27d989f92c80355658c7f888fcc83c"},
{file = "numpy-2.1.3-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:fa2d1337dc61c8dc417fbccf20f6d1e139896a30721b7f1e832b2bb6ef4eb6c4"},
{file = "numpy-2.1.3-cp310-cp310-win32.whl", hash = "sha256:72dcc4a35a8515d83e76b58fdf8113a5c969ccd505c8a946759b24e3182d1f23"},
{file = "numpy-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:ecc76a9ba2911d8d37ac01de72834d8849e55473457558e12995f4cd53e778e0"},
{file = "numpy-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4d1167c53b93f1f5d8a139a742b3c6f4d429b54e74e6b57d0eff40045187b15d"},
{file = "numpy-2.1.3-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c80e4a09b3d95b4e1cac08643f1152fa71a0a821a2d4277334c88d54b2219a41"},
{file = "numpy-2.1.3-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:576a1c1d25e9e02ed7fa5477f30a127fe56debd53b8d2c89d5578f9857d03ca9"},
{file = "numpy-2.1.3-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:973faafebaae4c0aaa1a1ca1ce02434554d67e628b8d805e61f874b84e136b09"},
{file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:762479be47a4863e261a840e8e01608d124ee1361e48b96916f38b119cfda04a"},
{file = "numpy-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc6f24b3d1ecc1eebfbf5d6051faa49af40b03be1aaa781ebdadcbc090b4539b"},
{file = "numpy-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:17ee83a1f4fef3c94d16dc1802b998668b5419362c8a4f4e8a491de1b41cc3ee"},
{file = "numpy-2.1.3-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:15cb89f39fa6d0bdfb600ea24b250e5f1a3df23f901f51c8debaa6a5d122b2f0"},
{file = "numpy-2.1.3-cp311-cp311-win32.whl", hash = "sha256:d9beb777a78c331580705326d2367488d5bc473b49a9bc3036c154832520aca9"},
{file = "numpy-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:d89dd2b6da69c4fff5e39c28a382199ddedc3a5be5390115608345dec660b9e2"},
{file = "numpy-2.1.3-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:f55ba01150f52b1027829b50d70ef1dafd9821ea82905b63936668403c3b471e"},
{file = "numpy-2.1.3-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:13138eadd4f4da03074851a698ffa7e405f41a0845a6b1ad135b81596e4e9958"},
{file = "numpy-2.1.3-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:a6b46587b14b888e95e4a24d7b13ae91fa22386c199ee7b418f449032b2fa3b8"},
{file = "numpy-2.1.3-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:0fa14563cc46422e99daef53d725d0c326e99e468a9320a240affffe87852564"},
{file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8637dcd2caa676e475503d1f8fdb327bc495554e10838019651b76d17b98e512"},
{file = "numpy-2.1.3-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2312b2aa89e1f43ecea6da6ea9a810d06aae08321609d8dc0d0eda6d946a541b"},
{file = "numpy-2.1.3-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:a38c19106902bb19351b83802531fea19dee18e5b37b36454f27f11ff956f7fc"},
{file = "numpy-2.1.3-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:02135ade8b8a84011cbb67dc44e07c58f28575cf9ecf8ab304e51c05528c19f0"},
{file = "numpy-2.1.3-cp312-cp312-win32.whl", hash = "sha256:e6988e90fcf617da2b5c78902fe8e668361b43b4fe26dbf2d7b0f8034d4cafb9"},
{file = "numpy-2.1.3-cp312-cp312-win_amd64.whl", hash = "sha256:0d30c543f02e84e92c4b1f415b7c6b5326cbe45ee7882b6b77db7195fb971e3a"},
{file = "numpy-2.1.3-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:96fe52fcdb9345b7cd82ecd34547fca4321f7656d500eca497eb7ea5a926692f"},
{file = "numpy-2.1.3-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:f653490b33e9c3a4c1c01d41bc2aef08f9475af51146e4a7710c450cf9761598"},
{file = "numpy-2.1.3-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:dc258a761a16daa791081d026f0ed4399b582712e6fc887a95af09df10c5ca57"},
{file = "numpy-2.1.3-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:016d0f6f5e77b0f0d45d77387ffa4bb89816b57c835580c3ce8e099ef830befe"},
{file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c181ba05ce8299c7aa3125c27b9c2167bca4a4445b7ce73d5febc411ca692e43"},
{file = "numpy-2.1.3-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5641516794ca9e5f8a4d17bb45446998c6554704d888f86df9b200e66bdcce56"},
{file = "numpy-2.1.3-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:ea4dedd6e394a9c180b33c2c872b92f7ce0f8e7ad93e9585312b0c5a04777a4a"},
{file = "numpy-2.1.3-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:b0df3635b9c8ef48bd3be5f862cf71b0a4716fa0e702155c45067c6b711ddcef"},
{file = "numpy-2.1.3-cp313-cp313-win32.whl", hash = "sha256:50ca6aba6e163363f132b5c101ba078b8cbd3fa92c7865fd7d4d62d9779ac29f"},
{file = "numpy-2.1.3-cp313-cp313-win_amd64.whl", hash = "sha256:747641635d3d44bcb380d950679462fae44f54b131be347d5ec2bce47d3df9ed"},
{file = "numpy-2.1.3-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:996bb9399059c5b82f76b53ff8bb686069c05acc94656bb259b1d63d04a9506f"},
{file = "numpy-2.1.3-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:45966d859916ad02b779706bb43b954281db43e185015df6eb3323120188f9e4"},
{file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:baed7e8d7481bfe0874b566850cb0b85243e982388b7b23348c6db2ee2b2ae8e"},
{file = "numpy-2.1.3-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:a9f7f672a3388133335589cfca93ed468509cb7b93ba3105fce780d04a6576a0"},
{file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d7aac50327da5d208db2eec22eb11e491e3fe13d22653dce51b0f4109101b408"},
{file = "numpy-2.1.3-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4394bc0dbd074b7f9b52024832d16e019decebf86caf909d94f6b3f77a8ee3b6"},
{file = "numpy-2.1.3-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:50d18c4358a0a8a53f12a8ba9d772ab2d460321e6a93d6064fc22443d189853f"},
{file = "numpy-2.1.3-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:14e253bd43fc6b37af4921b10f6add6925878a42a0c5fe83daee390bca80bc17"},
{file = "numpy-2.1.3-cp313-cp313t-win32.whl", hash = "sha256:08788d27a5fd867a663f6fc753fd7c3ad7e92747efc73c53bca2f19f8bc06f48"},
{file = "numpy-2.1.3-cp313-cp313t-win_amd64.whl", hash = "sha256:2564fbdf2b99b3f815f2107c1bbc93e2de8ee655a69c261363a1172a79a257d4"},
{file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:4f2015dfe437dfebbfce7c85c7b53d81ba49e71ba7eadbf1df40c915af75979f"},
{file = "numpy-2.1.3-pp310-pypy310_pp73-macosx_14_0_x86_64.whl", hash = "sha256:3522b0dfe983a575e6a9ab3a4a4dfe156c3e428468ff08ce582b9bb6bd1d71d4"},
{file = "numpy-2.1.3-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c006b607a865b07cd981ccb218a04fc86b600411d83d6fc261357f1c0966755d"},
{file = "numpy-2.1.3-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:e14e26956e6f1696070788252dcdff11b4aca4c3e8bd166e0df1bb8f315a67cb"},
{file = "numpy-2.1.3.tar.gz", hash = "sha256:aa08e04e08aaf974d4458def539dece0d28146d866a39da5639596f4921fd761"},
]
[[package]]
name = "opencv-python"
version = "4.10.0.84"
description = "Wrapper package for OpenCV python bindings."
optional = false
python-versions = ">=3.6"
files = [
{file = "opencv-python-4.10.0.84.tar.gz", hash = "sha256:72d234e4582e9658ffea8e9cae5b63d488ad06994ef12d81dc303b17472f3526"},
{file = "opencv_python-4.10.0.84-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:fc182f8f4cda51b45f01c64e4cbedfc2f00aff799debebc305d8d0210c43f251"},
{file = "opencv_python-4.10.0.84-cp37-abi3-macosx_12_0_x86_64.whl", hash = "sha256:71e575744f1d23f79741450254660442785f45a0797212852ee5199ef12eed98"},
{file = "opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09a332b50488e2dda866a6c5573ee192fe3583239fb26ff2f7f9ceb0bc119ea6"},
{file = "opencv_python-4.10.0.84-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9ace140fc6d647fbe1c692bcb2abce768973491222c067c131d80957c595b71f"},
{file = "opencv_python-4.10.0.84-cp37-abi3-win32.whl", hash = "sha256:2db02bb7e50b703f0a2d50c50ced72e95c574e1e5a0bb35a8a86d0b35c98c236"},
{file = "opencv_python-4.10.0.84-cp37-abi3-win_amd64.whl", hash = "sha256:32dbbd94c26f611dc5cc6979e6b7aa1f55a64d6b463cc1dcd3c95505a63e48fe"},
]
[package.dependencies]
numpy = [
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
{version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""},
{version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""},
{version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""},
]
[[package]]
name = "ortools"
version = "9.11.4210"
description = "Google OR-Tools python libraries and modules"
optional = false
python-versions = ">=3.8"
files = [
{file = "ortools-9.11.4210-cp310-cp310-macosx_10_15_x86_64.whl", hash = "sha256:127f20f03ce04c28f979eac635d1cacdc01597c9e035a1981070506294d7db9c"},
{file = "ortools-9.11.4210-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:250c62ba9e5fcaf18ada449bc0128c71bb0dbea83ddec5559cc506cff920235c"},
{file = "ortools-9.11.4210-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7c15cefc7bc71aa2f70bee157aaa7e51ed8cb74c3edd499d15b6f5cd79cdf5b"},
{file = "ortools-9.11.4210-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9c3693f0cb07ee0b5d36c4817bcfe05a745e3a613798a2ed62eb998d7fff979c"},
{file = "ortools-9.11.4210-cp310-cp310-win_amd64.whl", hash = "sha256:6b9d4ae6c7e9efac7cbef8a6289e97e238c2f0a8ef587b3e56b71af14c2f04e6"},
{file = "ortools-9.11.4210-cp311-cp311-macosx_10_15_x86_64.whl", hash = "sha256:0f902caa1576d737714f6a4fa165db62469bce82115e250409607197b3b6b434"},
{file = "ortools-9.11.4210-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c6f3e2869396dc6d8ee2d11b65d6f88f6386bb3ad64212c0ad7a6d32ddcb48ca"},
{file = "ortools-9.11.4210-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a56c5844ff927ce3c5428159cdd01b7fdbe243e8062bf1dfdb2e0eb305a55a30"},
{file = "ortools-9.11.4210-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d5a17a37aeaa7d149e2fe8c8dfd5f09630ae28ad734a109ad55536605f8059df"},
{file = "ortools-9.11.4210-cp311-cp311-win_amd64.whl", hash = "sha256:d9b858f0273e19f81555428d54d407428d0a70a8cb5df2c320935bb735f2c6bb"},
{file = "ortools-9.11.4210-cp312-cp312-macosx_10_15_x86_64.whl", hash = "sha256:079bea08c6341dcfe3fb9586eb6edec6ae80f4ed16ed366fd7a46ef4b5709009"},
{file = "ortools-9.11.4210-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7f55124f9d1afa6434d0d6de07c6a4eb836f29b00b3413d27138634d5d79b606"},
{file = "ortools-9.11.4210-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f48e3d4053a169440608d881c1abd2a706db885d9b0af85bf45b444a1fec244"},
{file = "ortools-9.11.4210-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:03ecd32e5d760d48e59832ef6bf724f8cac95e4e40db72a7fb912abf7adcf931"},
{file = "ortools-9.11.4210-cp312-cp312-win_amd64.whl", hash = "sha256:bc1b6e4cc0a121ef888481a99194765e6df72d4d3da81f928543171a2bac8cbb"},
{file = "ortools-9.11.4210-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:a503291dc12dc44da48567c5e1f79c77cd054fb86176f2c99d2860bc5a57e03d"},
{file = "ortools-9.11.4210-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:2fd0aa0b4edfb3088f086bc05d42a381cc3d03da4b8ad18ce18ba213ab2c719f"},
{file = "ortools-9.11.4210-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c242738a49279c3a58b4611a64dd24634f1638f4dbb435163e3f9308e7c84c9"},
{file = "ortools-9.11.4210-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:db9186bbc7a30538f277e704e9e77ff52bc75aa2c17095a125b2dc212a75cf8e"},
{file = "ortools-9.11.4210-cp38-cp38-win_amd64.whl", hash = "sha256:afcca4919e1095a79af0375276c5377a2d75794ebd592c4cc841d9979e0526e7"},
{file = "ortools-9.11.4210-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:a2828e91960c4e4fcf27bc6e200e2cd61ce1c42d4a3b95481a842a58c8315345"},
{file = "ortools-9.11.4210-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:8d7d1a105f105502cf2f785816b09b796e5845fd47efac0dc0e3c0476b4c961a"},
{file = "ortools-9.11.4210-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9f250641d7b822be25237fb78aed0878b07e8afdefddb700bafcc52f32ad520a"},
{file = "ortools-9.11.4210-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bd2c0b319f4c0999360ab45a85d5764838c9dd0fd33437d12e32b2c07cbe04e4"},
{file = "ortools-9.11.4210-cp39-cp39-win_amd64.whl", hash = "sha256:219ffa56e8e4f52561586cea3dd55eb0f5d174a84c83a819d71debad766338e3"},
]
[package.dependencies]
absl-py = ">=2.0.0"
immutabledict = ">=3.0.0"
numpy = ">=1.13.3"
pandas = ">=2.0.0"
protobuf = ">=5.26.1,<5.27"
[[package]]
name = "packaging"
version = "24.2"
@ -713,9 +556,9 @@ files = [
[package.dependencies]
numpy = [
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
{version = ">=1.23.2", markers = "python_version == \"3.11\""},
{version = ">=1.22.4", markers = "python_version < \"3.11\""},
{version = ">=1.23.2", markers = "python_version == \"3.11\""},
{version = ">=1.26.0", markers = "python_version >= \"3.12\""},
]
python-dateutil = ">=2.8.2"
pytz = ">=2020.1"
@ -797,98 +640,6 @@ files = [
[package.dependencies]
ptyprocess = ">=0.5"
[[package]]
name = "pillow"
version = "11.0.0"
description = "Python Imaging Library (Fork)"
optional = false
python-versions = ">=3.9"
files = [
{file = "pillow-11.0.0-cp310-cp310-macosx_10_10_x86_64.whl", hash = "sha256:6619654954dc4936fcff82db8eb6401d3159ec6be81e33c6000dfd76ae189947"},
{file = "pillow-11.0.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:b3c5ac4bed7519088103d9450a1107f76308ecf91d6dabc8a33a2fcfb18d0fba"},
{file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a65149d8ada1055029fcb665452b2814fe7d7082fcb0c5bed6db851cb69b2086"},
{file = "pillow-11.0.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:88a58d8ac0cc0e7f3a014509f0455248a76629ca9b604eca7dc5927cc593c5e9"},
{file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_aarch64.whl", hash = "sha256:c26845094b1af3c91852745ae78e3ea47abf3dbcd1cf962f16b9a5fbe3ee8488"},
{file = "pillow-11.0.0-cp310-cp310-manylinux_2_28_x86_64.whl", hash = "sha256:1a61b54f87ab5786b8479f81c4b11f4d61702830354520837f8cc791ebba0f5f"},
{file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_aarch64.whl", hash = "sha256:674629ff60030d144b7bca2b8330225a9b11c482ed408813924619c6f302fdbb"},
{file = "pillow-11.0.0-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:598b4e238f13276e0008299bd2482003f48158e2b11826862b1eb2ad7c768b97"},
{file = "pillow-11.0.0-cp310-cp310-win32.whl", hash = "sha256:9a0f748eaa434a41fccf8e1ee7a3eed68af1b690e75328fd7a60af123c193b50"},
{file = "pillow-11.0.0-cp310-cp310-win_amd64.whl", hash = "sha256:a5629742881bcbc1f42e840af185fd4d83a5edeb96475a575f4da50d6ede337c"},
{file = "pillow-11.0.0-cp310-cp310-win_arm64.whl", hash = "sha256:ee217c198f2e41f184f3869f3e485557296d505b5195c513b2bfe0062dc537f1"},
{file = "pillow-11.0.0-cp311-cp311-macosx_10_10_x86_64.whl", hash = "sha256:1c1d72714f429a521d8d2d018badc42414c3077eb187a59579f28e4270b4b0fc"},
{file = "pillow-11.0.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:499c3a1b0d6fc8213519e193796eb1a86a1be4b1877d678b30f83fd979811d1a"},
{file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8b2351c85d855293a299038e1f89db92a2f35e8d2f783489c6f0b2b5f3fe8a3"},
{file = "pillow-11.0.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6f4dba50cfa56f910241eb7f883c20f1e7b1d8f7d91c750cd0b318bad443f4d5"},
{file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_aarch64.whl", hash = "sha256:5ddbfd761ee00c12ee1be86c9c0683ecf5bb14c9772ddbd782085779a63dd55b"},
{file = "pillow-11.0.0-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:45c566eb10b8967d71bf1ab8e4a525e5a93519e29ea071459ce517f6b903d7fa"},
{file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:b4fd7bd29610a83a8c9b564d457cf5bd92b4e11e79a4ee4716a63c959699b306"},
{file = "pillow-11.0.0-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:cb929ca942d0ec4fac404cbf520ee6cac37bf35be479b970c4ffadf2b6a1cad9"},
{file = "pillow-11.0.0-cp311-cp311-win32.whl", hash = "sha256:006bcdd307cc47ba43e924099a038cbf9591062e6c50e570819743f5607404f5"},
{file = "pillow-11.0.0-cp311-cp311-win_amd64.whl", hash = "sha256:52a2d8323a465f84faaba5236567d212c3668f2ab53e1c74c15583cf507a0291"},
{file = "pillow-11.0.0-cp311-cp311-win_arm64.whl", hash = "sha256:16095692a253047fe3ec028e951fa4221a1f3ed3d80c397e83541a3037ff67c9"},
{file = "pillow-11.0.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:d2c0a187a92a1cb5ef2c8ed5412dd8d4334272617f532d4ad4de31e0495bd923"},
{file = "pillow-11.0.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:084a07ef0821cfe4858fe86652fffac8e187b6ae677e9906e192aafcc1b69903"},
{file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8069c5179902dcdce0be9bfc8235347fdbac249d23bd90514b7a47a72d9fecf4"},
{file = "pillow-11.0.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f02541ef64077f22bf4924f225c0fd1248c168f86e4b7abdedd87d6ebaceab0f"},
{file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:fcb4621042ac4b7865c179bb972ed0da0218a076dc1820ffc48b1d74c1e37fe9"},
{file = "pillow-11.0.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:00177a63030d612148e659b55ba99527803288cea7c75fb05766ab7981a8c1b7"},
{file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8853a3bf12afddfdf15f57c4b02d7ded92c7a75a5d7331d19f4f9572a89c17e6"},
{file = "pillow-11.0.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:3107c66e43bda25359d5ef446f59c497de2b5ed4c7fdba0894f8d6cf3822dafc"},
{file = "pillow-11.0.0-cp312-cp312-win32.whl", hash = "sha256:86510e3f5eca0ab87429dd77fafc04693195eec7fd6a137c389c3eeb4cfb77c6"},
{file = "pillow-11.0.0-cp312-cp312-win_amd64.whl", hash = "sha256:8ec4a89295cd6cd4d1058a5e6aec6bf51e0eaaf9714774e1bfac7cfc9051db47"},
{file = "pillow-11.0.0-cp312-cp312-win_arm64.whl", hash = "sha256:27a7860107500d813fcd203b4ea19b04babe79448268403172782754870dac25"},
{file = "pillow-11.0.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:bcd1fb5bb7b07f64c15618c89efcc2cfa3e95f0e3bcdbaf4642509de1942a699"},
{file = "pillow-11.0.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:0e038b0745997c7dcaae350d35859c9715c71e92ffb7e0f4a8e8a16732150f38"},
{file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0ae08bd8ffc41aebf578c2af2f9d8749d91f448b3bfd41d7d9ff573d74f2a6b2"},
{file = "pillow-11.0.0-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d69bfd8ec3219ae71bcde1f942b728903cad25fafe3100ba2258b973bd2bc1b2"},
{file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:61b887f9ddba63ddf62fd02a3ba7add935d053b6dd7d58998c630e6dbade8527"},
{file = "pillow-11.0.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:c6a660307ca9d4867caa8d9ca2c2658ab685de83792d1876274991adec7b93fa"},
{file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:73e3a0200cdda995c7e43dd47436c1548f87a30bb27fb871f352a22ab8dcf45f"},
{file = "pillow-11.0.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:fba162b8872d30fea8c52b258a542c5dfd7b235fb5cb352240c8d63b414013eb"},
{file = "pillow-11.0.0-cp313-cp313-win32.whl", hash = "sha256:f1b82c27e89fffc6da125d5eb0ca6e68017faf5efc078128cfaa42cf5cb38798"},
{file = "pillow-11.0.0-cp313-cp313-win_amd64.whl", hash = "sha256:8ba470552b48e5835f1d23ecb936bb7f71d206f9dfeee64245f30c3270b994de"},
{file = "pillow-11.0.0-cp313-cp313-win_arm64.whl", hash = "sha256:846e193e103b41e984ac921b335df59195356ce3f71dcfd155aa79c603873b84"},
{file = "pillow-11.0.0-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:4ad70c4214f67d7466bea6a08061eba35c01b1b89eaa098040a35272a8efb22b"},
{file = "pillow-11.0.0-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:6ec0d5af64f2e3d64a165f490d96368bb5dea8b8f9ad04487f9ab60dc4bb6003"},
{file = "pillow-11.0.0-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c809a70e43c7977c4a42aefd62f0131823ebf7dd73556fa5d5950f5b354087e2"},
{file = "pillow-11.0.0-cp313-cp313t-manylinux_2_28_x86_64.whl", hash = "sha256:4b60c9520f7207aaf2e1d94de026682fc227806c6e1f55bba7606d1c94dd623a"},
{file = "pillow-11.0.0-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:1e2688958a840c822279fda0086fec1fdab2f95bf2b717b66871c4ad9859d7e8"},
{file = "pillow-11.0.0-cp313-cp313t-win32.whl", hash = "sha256:607bbe123c74e272e381a8d1957083a9463401f7bd01287f50521ecb05a313f8"},
{file = "pillow-11.0.0-cp313-cp313t-win_amd64.whl", hash = "sha256:5c39ed17edea3bc69c743a8dd3e9853b7509625c2462532e62baa0732163a904"},
{file = "pillow-11.0.0-cp313-cp313t-win_arm64.whl", hash = "sha256:75acbbeb05b86bc53cbe7b7e6fe00fbcf82ad7c684b3ad82e3d711da9ba287d3"},
{file = "pillow-11.0.0-cp39-cp39-macosx_10_10_x86_64.whl", hash = "sha256:2e46773dc9f35a1dd28bd6981332fd7f27bec001a918a72a79b4133cf5291dba"},
{file = "pillow-11.0.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:2679d2258b7f1192b378e2893a8a0a0ca472234d4c2c0e6bdd3380e8dfa21b6a"},
{file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eda2616eb2313cbb3eebbe51f19362eb434b18e3bb599466a1ffa76a033fb916"},
{file = "pillow-11.0.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ec184af98a121fb2da42642dea8a29ec80fc3efbaefb86d8fdd2606619045d"},
{file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_aarch64.whl", hash = "sha256:8594f42df584e5b4bb9281799698403f7af489fba84c34d53d1c4bfb71b7c4e7"},
{file = "pillow-11.0.0-cp39-cp39-manylinux_2_28_x86_64.whl", hash = "sha256:c12b5ae868897c7338519c03049a806af85b9b8c237b7d675b8c5e089e4a618e"},
{file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_aarch64.whl", hash = "sha256:70fbbdacd1d271b77b7721fe3cdd2d537bbbd75d29e6300c672ec6bb38d9672f"},
{file = "pillow-11.0.0-cp39-cp39-musllinux_1_2_x86_64.whl", hash = "sha256:5178952973e588b3f1360868847334e9e3bf49d19e169bbbdfaf8398002419ae"},
{file = "pillow-11.0.0-cp39-cp39-win32.whl", hash = "sha256:8c676b587da5673d3c75bd67dd2a8cdfeb282ca38a30f37950511766b26858c4"},
{file = "pillow-11.0.0-cp39-cp39-win_amd64.whl", hash = "sha256:94f3e1780abb45062287b4614a5bc0874519c86a777d4a7ad34978e86428b8dd"},
{file = "pillow-11.0.0-cp39-cp39-win_arm64.whl", hash = "sha256:290f2cc809f9da7d6d622550bbf4c1e57518212da51b6a30fe8e0a270a5b78bd"},
{file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_10_15_x86_64.whl", hash = "sha256:1187739620f2b365de756ce086fdb3604573337cc28a0d3ac4a01ab6b2d2a6d2"},
{file = "pillow-11.0.0-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:fbbcb7b57dc9c794843e3d1258c0fbf0f48656d46ffe9e09b63bbd6e8cd5d0a2"},
{file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d203af30149ae339ad1b4f710d9844ed8796e97fda23ffbc4cc472968a47d0b"},
{file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:21a0d3b115009ebb8ac3d2ebec5c2982cc693da935f4ab7bb5c8ebe2f47d36f2"},
{file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:73853108f56df97baf2bb8b522f3578221e56f646ba345a372c78326710d3830"},
{file = "pillow-11.0.0-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:e58876c91f97b0952eb766123bfef372792ab3f4e3e1f1a2267834c2ab131734"},
{file = "pillow-11.0.0-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:224aaa38177597bb179f3ec87eeefcce8e4f85e608025e9cfac60de237ba6316"},
{file = "pillow-11.0.0-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:5bd2d3bdb846d757055910f0a59792d33b555800813c3b39ada1829c372ccb06"},
{file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:375b8dd15a1f5d2feafff536d47e22f69625c1aa92f12b339ec0b2ca40263273"},
{file = "pillow-11.0.0-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:daffdf51ee5db69a82dd127eabecce20729e21f7a3680cf7cbb23f0829189790"},
{file = "pillow-11.0.0-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7326a1787e3c7b0429659e0a944725e1b03eeaa10edd945a86dead1913383944"},
{file = "pillow-11.0.0.tar.gz", hash = "sha256:72bacbaf24ac003fea9bff9837d1eedb6088758d41e100c1552930151f677739"},
]
[package.extras]
docs = ["furo", "olefile", "sphinx (>=8.1)", "sphinx-copybutton", "sphinx-inline-tabs", "sphinxext-opengraph"]
fpx = ["olefile"]
mic = ["olefile"]
tests = ["check-manifest", "coverage", "defusedxml", "markdown2", "olefile", "packaging", "pyroma", "pytest", "pytest-cov", "pytest-timeout"]
typing = ["typing-extensions"]
xmp = ["defusedxml"]
[[package]]
name = "platformdirs"
version = "4.3.6"
@ -938,26 +689,6 @@ files = [
[package.dependencies]
wcwidth = "*"
[[package]]
name = "protobuf"
version = "5.26.1"
description = ""
optional = false
python-versions = ">=3.8"
files = [
{file = "protobuf-5.26.1-cp310-abi3-win32.whl", hash = "sha256:3c388ea6ddfe735f8cf69e3f7dc7611e73107b60bdfcf5d0f024c3ccd3794e23"},
{file = "protobuf-5.26.1-cp310-abi3-win_amd64.whl", hash = "sha256:e6039957449cb918f331d32ffafa8eb9255769c96aa0560d9a5bf0b4e00a2a33"},
{file = "protobuf-5.26.1-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:38aa5f535721d5bb99861166c445c4105c4e285c765fbb2ac10f116e32dcd46d"},
{file = "protobuf-5.26.1-cp37-abi3-manylinux2014_aarch64.whl", hash = "sha256:fbfe61e7ee8c1860855696e3ac6cfd1b01af5498facc6834fcc345c9684fb2ca"},
{file = "protobuf-5.26.1-cp37-abi3-manylinux2014_x86_64.whl", hash = "sha256:f7417703f841167e5a27d48be13389d52ad705ec09eade63dfc3180a959215d7"},
{file = "protobuf-5.26.1-cp38-cp38-win32.whl", hash = "sha256:d693d2504ca96750d92d9de8a103102dd648fda04540495535f0fec7577ed8fc"},
{file = "protobuf-5.26.1-cp38-cp38-win_amd64.whl", hash = "sha256:9b557c317ebe6836835ec4ef74ec3e994ad0894ea424314ad3552bc6e8835b4e"},
{file = "protobuf-5.26.1-cp39-cp39-win32.whl", hash = "sha256:b9ba3ca83c2e31219ffbeb9d76b63aad35a3eb1544170c55336993d7a18ae72c"},
{file = "protobuf-5.26.1-cp39-cp39-win_amd64.whl", hash = "sha256:7ee014c2c87582e101d6b54260af03b6596728505c79f17c8586e7523aaa8f8c"},
{file = "protobuf-5.26.1-py3-none-any.whl", hash = "sha256:da612f2720c0183417194eeaa2523215c4fcc1a1949772dc65f05047e08d5932"},
{file = "protobuf-5.26.1.tar.gz", hash = "sha256:8ca2a1d97c290ec7b16e4e5dff2e5ae150cc1582f55b5ab300d45cb0dfa90e51"},
]
[[package]]
name = "psutil"
version = "6.1.0"
@ -1024,19 +755,6 @@ files = [
{file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"},
]
[[package]]
name = "pygifsicle"
version = "1.1.0"
description = "Python package wrapping the gifsicle library for editing and optimizing gifs."
optional = false
python-versions = "*"
files = [
{file = "pygifsicle-1.1.0.tar.gz", hash = "sha256:dcef433520ace4c1136dfc7060e77042142a3dbd6bdb6a19bd9149ef5cbe7441"},
]
[package.extras]
test = ["pytest", "pytest-cov", "touch", "validate_version_code"]
[[package]]
name = "pygments"
version = "2.18.0"
@ -1053,13 +771,13 @@ windows-terminal = ["colorama (>=0.4.6)"]
[[package]]
name = "pyright"
version = "1.1.390"
version = "1.1.389"
description = "Command line wrapper for pyright"
optional = false
python-versions = ">=3.7"
files = [
{file = "pyright-1.1.390-py3-none-any.whl", hash = "sha256:ecebfba5b6b50af7c1a44c2ba144ba2ab542c227eb49bc1f16984ff714e0e110"},
{file = "pyright-1.1.390.tar.gz", hash = "sha256:aad7f160c49e0fbf8209507a15e17b781f63a86a1facb69ca877c71ef2e9538d"},
{file = "pyright-1.1.389-py3-none-any.whl", hash = "sha256:41e9620bba9254406dc1f621a88ceab5a88af4c826feb4f614d95691ed243a60"},
{file = "pyright-1.1.389.tar.gz", hash = "sha256:716bf8cc174ab8b4dcf6828c3298cac05c5ed775dda9910106a5dcfe4c7fe220"},
]
[package.dependencies]
@ -1308,40 +1026,90 @@ cffi = {version = "*", markers = "implementation_name == \"pypy\""}
[[package]]
name = "ruff"
version = "0.8.3"
version = "0.8.1"
description = "An extremely fast Python linter and code formatter, written in Rust."
optional = false
python-versions = ">=3.7"
files = [
{file = "ruff-0.8.3-py3-none-linux_armv6l.whl", hash = "sha256:8d5d273ffffff0acd3db5bf626d4b131aa5a5ada1276126231c4174543ce20d6"},
{file = "ruff-0.8.3-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:e4d66a21de39f15c9757d00c50c8cdd20ac84f55684ca56def7891a025d7e939"},
{file = "ruff-0.8.3-py3-none-macosx_11_0_arm64.whl", hash = "sha256:c356e770811858bd20832af696ff6c7e884701115094f427b64b25093d6d932d"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9c0a60a825e3e177116c84009d5ebaa90cf40dfab56e1358d1df4e29a9a14b13"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:75fb782f4db39501210ac093c79c3de581d306624575eddd7e4e13747e61ba18"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7f26bc76a133ecb09a38b7868737eded6941b70a6d34ef53a4027e83913b6502"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:01b14b2f72a37390c1b13477c1c02d53184f728be2f3ffc3ace5b44e9e87b90d"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:53babd6e63e31f4e96ec95ea0d962298f9f0d9cc5990a1bbb023a6baf2503a82"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1ae441ce4cf925b7f363d33cd6570c51435972d697e3e58928973994e56e1452"},
{file = "ruff-0.8.3-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d7c65bc0cadce32255e93c57d57ecc2cca23149edd52714c0c5d6fa11ec328cd"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:5be450bb18f23f0edc5a4e5585c17a56ba88920d598f04a06bd9fd76d324cb20"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:8faeae3827eaa77f5721f09b9472a18c749139c891dbc17f45e72d8f2ca1f8fc"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_i686.whl", hash = "sha256:db503486e1cf074b9808403991663e4277f5c664d3fe237ee0d994d1305bb060"},
{file = "ruff-0.8.3-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:6567be9fb62fbd7a099209257fef4ad2c3153b60579818b31a23c886ed4147ea"},
{file = "ruff-0.8.3-py3-none-win32.whl", hash = "sha256:19048f2f878f3ee4583fc6cb23fb636e48c2635e30fb2022b3a1cd293402f964"},
{file = "ruff-0.8.3-py3-none-win_amd64.whl", hash = "sha256:f7df94f57d7418fa7c3ffb650757e0c2b96cf2501a0b192c18e4fb5571dfada9"},
{file = "ruff-0.8.3-py3-none-win_arm64.whl", hash = "sha256:fe2756edf68ea79707c8d68b78ca9a58ed9af22e430430491ee03e718b5e4936"},
{file = "ruff-0.8.3.tar.gz", hash = "sha256:5e7558304353b84279042fc584a4f4cb8a07ae79b2bf3da1a7551d960b5626d3"},
{file = "ruff-0.8.1-py3-none-linux_armv6l.whl", hash = "sha256:fae0805bd514066f20309f6742f6ee7904a773eb9e6c17c45d6b1600ca65c9b5"},
{file = "ruff-0.8.1-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:b8a4f7385c2285c30f34b200ca5511fcc865f17578383db154e098150ce0a087"},
{file = "ruff-0.8.1-py3-none-macosx_11_0_arm64.whl", hash = "sha256:cd054486da0c53e41e0086e1730eb77d1f698154f910e0cd9e0d64274979a209"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2029b8c22da147c50ae577e621a5bfbc5d1fed75d86af53643d7a7aee1d23871"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:2666520828dee7dfc7e47ee4ea0d928f40de72056d929a7c5292d95071d881d1"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:333c57013ef8c97a53892aa56042831c372e0bb1785ab7026187b7abd0135ad5"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:288326162804f34088ac007139488dcb43de590a5ccfec3166396530b58fb89d"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b12c39b9448632284561cbf4191aa1b005882acbc81900ffa9f9f471c8ff7e26"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:364e6674450cbac8e998f7b30639040c99d81dfb5bbc6dfad69bc7a8f916b3d1"},
{file = "ruff-0.8.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b22346f845fec132aa39cd29acb94451d030c10874408dbf776af3aaeb53284c"},
{file = "ruff-0.8.1-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:b2f2f7a7e7648a2bfe6ead4e0a16745db956da0e3a231ad443d2a66a105c04fa"},
{file = "ruff-0.8.1-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:adf314fc458374c25c5c4a4a9270c3e8a6a807b1bec018cfa2813d6546215540"},
{file = "ruff-0.8.1-py3-none-musllinux_1_2_i686.whl", hash = "sha256:a885d68342a231b5ba4d30b8c6e1b1ee3a65cf37e3d29b3c74069cdf1ee1e3c9"},
{file = "ruff-0.8.1-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:d2c16e3508c8cc73e96aa5127d0df8913d2290098f776416a4b157657bee44c5"},
{file = "ruff-0.8.1-py3-none-win32.whl", hash = "sha256:93335cd7c0eaedb44882d75a7acb7df4b77cd7cd0d2255c93b28791716e81790"},
{file = "ruff-0.8.1-py3-none-win_amd64.whl", hash = "sha256:2954cdbe8dfd8ab359d4a30cd971b589d335a44d444b6ca2cb3d1da21b75e4b6"},
{file = "ruff-0.8.1-py3-none-win_arm64.whl", hash = "sha256:55873cc1a473e5ac129d15eccb3c008c096b94809d693fc7053f588b67822737"},
{file = "ruff-0.8.1.tar.gz", hash = "sha256:3583db9a6450364ed5ca3f3b4225958b24f78178908d5c4bc0f46251ccca898f"},
]
[[package]]
name = "scipy"
version = "1.14.1"
description = "Fundamental algorithms for scientific computing in Python"
optional = false
python-versions = ">=3.10"
files = [
{file = "scipy-1.14.1-cp310-cp310-macosx_10_13_x86_64.whl", hash = "sha256:b28d2ca4add7ac16ae8bb6632a3c86e4b9e4d52d3e34267f6e1b0c1f8d87e389"},
{file = "scipy-1.14.1-cp310-cp310-macosx_12_0_arm64.whl", hash = "sha256:d0d2821003174de06b69e58cef2316a6622b60ee613121199cb2852a873f8cf3"},
{file = "scipy-1.14.1-cp310-cp310-macosx_14_0_arm64.whl", hash = "sha256:8bddf15838ba768bb5f5083c1ea012d64c9a444e16192762bd858f1e126196d0"},
{file = "scipy-1.14.1-cp310-cp310-macosx_14_0_x86_64.whl", hash = "sha256:97c5dddd5932bd2a1a31c927ba5e1463a53b87ca96b5c9bdf5dfd6096e27efc3"},
{file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2ff0a7e01e422c15739ecd64432743cf7aae2b03f3084288f399affcefe5222d"},
{file = "scipy-1.14.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e32dced201274bf96899e6491d9ba3e9a5f6b336708656466ad0522d8528f69"},
{file = "scipy-1.14.1-cp310-cp310-musllinux_1_2_x86_64.whl", hash = "sha256:8426251ad1e4ad903a4514712d2fa8fdd5382c978010d1c6f5f37ef286a713ad"},
{file = "scipy-1.14.1-cp310-cp310-win_amd64.whl", hash = "sha256:a49f6ed96f83966f576b33a44257d869756df6cf1ef4934f59dd58b25e0327e5"},
{file = "scipy-1.14.1-cp311-cp311-macosx_10_13_x86_64.whl", hash = "sha256:2da0469a4ef0ecd3693761acbdc20f2fdeafb69e6819cc081308cc978153c675"},
{file = "scipy-1.14.1-cp311-cp311-macosx_12_0_arm64.whl", hash = "sha256:c0ee987efa6737242745f347835da2cc5bb9f1b42996a4d97d5c7ff7928cb6f2"},
{file = "scipy-1.14.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:3a1b111fac6baec1c1d92f27e76511c9e7218f1695d61b59e05e0fe04dc59617"},
{file = "scipy-1.14.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:8475230e55549ab3f207bff11ebfc91c805dc3463ef62eda3ccf593254524ce8"},
{file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:278266012eb69f4a720827bdd2dc54b2271c97d84255b2faaa8f161a158c3b37"},
{file = "scipy-1.14.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fef8c87f8abfb884dac04e97824b61299880c43f4ce675dd2cbeadd3c9b466d2"},
{file = "scipy-1.14.1-cp311-cp311-musllinux_1_2_x86_64.whl", hash = "sha256:b05d43735bb2f07d689f56f7b474788a13ed8adc484a85aa65c0fd931cf9ccd2"},
{file = "scipy-1.14.1-cp311-cp311-win_amd64.whl", hash = "sha256:716e389b694c4bb564b4fc0c51bc84d381735e0d39d3f26ec1af2556ec6aad94"},
{file = "scipy-1.14.1-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:631f07b3734d34aced009aaf6fedfd0eb3498a97e581c3b1e5f14a04164a456d"},
{file = "scipy-1.14.1-cp312-cp312-macosx_12_0_arm64.whl", hash = "sha256:af29a935803cc707ab2ed7791c44288a682f9c8107bc00f0eccc4f92c08d6e07"},
{file = "scipy-1.14.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:2843f2d527d9eebec9a43e6b406fb7266f3af25a751aa91d62ff416f54170bc5"},
{file = "scipy-1.14.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:eb58ca0abd96911932f688528977858681a59d61a7ce908ffd355957f7025cfc"},
{file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:30ac8812c1d2aab7131a79ba62933a2a76f582d5dbbc695192453dae67ad6310"},
{file = "scipy-1.14.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f9ea80f2e65bdaa0b7627fb00cbeb2daf163caa015e59b7516395fe3bd1e066"},
{file = "scipy-1.14.1-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:edaf02b82cd7639db00dbff629995ef185c8df4c3ffa71a5562a595765a06ce1"},
{file = "scipy-1.14.1-cp312-cp312-win_amd64.whl", hash = "sha256:2ff38e22128e6c03ff73b6bb0f85f897d2362f8c052e3b8ad00532198fbdae3f"},
{file = "scipy-1.14.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:1729560c906963fc8389f6aac023739ff3983e727b1a4d87696b7bf108316a79"},
{file = "scipy-1.14.1-cp313-cp313-macosx_12_0_arm64.whl", hash = "sha256:4079b90df244709e675cdc8b93bfd8a395d59af40b72e339c2287c91860deb8e"},
{file = "scipy-1.14.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:e0cf28db0f24a38b2a0ca33a85a54852586e43cf6fd876365c86e0657cfe7d73"},
{file = "scipy-1.14.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:0c2f95de3b04e26f5f3ad5bb05e74ba7f68b837133a4492414b3afd79dfe540e"},
{file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b99722ea48b7ea25e8e015e8341ae74624f72e5f21fc2abd45f3a93266de4c5d"},
{file = "scipy-1.14.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5149e3fd2d686e42144a093b206aef01932a0059c2a33ddfa67f5f035bdfe13e"},
{file = "scipy-1.14.1-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:e4f5a7c49323533f9103d4dacf4e4f07078f360743dec7f7596949149efeec06"},
{file = "scipy-1.14.1-cp313-cp313-win_amd64.whl", hash = "sha256:baff393942b550823bfce952bb62270ee17504d02a1801d7fd0719534dfb9c84"},
{file = "scipy-1.14.1.tar.gz", hash = "sha256:5a275584e726026a5699459aa72f828a610821006228e841b94275c4a7c08417"},
]
[package.dependencies]
numpy = ">=1.23.5,<2.3"
[package.extras]
dev = ["cython-lint (>=0.12.2)", "doit (>=0.36.0)", "mypy (==1.10.0)", "pycodestyle", "pydevtool", "rich-click", "ruff (>=0.0.292)", "types-psutil", "typing_extensions"]
doc = ["jupyterlite-pyodide-kernel", "jupyterlite-sphinx (>=0.13.1)", "jupytext", "matplotlib (>=3.5)", "myst-nb", "numpydoc", "pooch", "pydata-sphinx-theme (>=0.15.2)", "sphinx (>=5.0.0,<=7.3.7)", "sphinx-design (>=0.4.0)"]
test = ["Cython", "array-api-strict (>=2.0)", "asv", "gmpy2", "hypothesis (>=6.30)", "meson", "mpmath", "ninja", "pooch", "pytest", "pytest-cov", "pytest-timeout", "pytest-xdist", "scikit-umfpack", "threadpoolctl"]
[[package]]
name = "six"
version = "1.17.0"
version = "1.16.0"
description = "Python 2 and 3 compatibility utilities"
optional = false
python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7"
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
files = [
{file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"},
{file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"},
{file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
{file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
]
[[package]]
@ -1477,20 +1245,6 @@ files = [
docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"]
test = ["argcomplete (>=3.0.3)", "mypy (>=1.7.0)", "pre-commit", "pytest (>=7.0,<8.2)", "pytest-mock", "pytest-mypy-testing"]
[[package]]
name = "types-networkx"
version = "3.4.2.20241115"
description = "Typing stubs for networkx"
optional = false
python-versions = ">=3.8"
files = [
{file = "types-networkx-3.4.2.20241115.tar.gz", hash = "sha256:d669b650cf6c6c9ec879a825449eb04a5c10742f3109177e1683f57ee49e0f59"},
{file = "types_networkx-3.4.2.20241115-py3-none-any.whl", hash = "sha256:f0c382924d6614e06bf0b1ca0b837b8f33faa58982bc086ea762efaf39aa98dd"},
]
[package.dependencies]
numpy = ">=1.20"
[[package]]
name = "typing-extensions"
version = "4.12.2"
@ -1527,4 +1281,4 @@ files = [
[metadata]
lock-version = "2.0"
python-versions = "^3.10"
content-hash = "5b57bccd8dc65a9acecbe187939bae625ef6a259f4188c6587907245bcfa604f"
content-hash = "b643261f91a781d77735e05f6d2ac1002867600c2df6393a9d1a15f5e1189109"

View File

@ -12,12 +12,10 @@ python = "^3.10"
numpy = "^2.1.3"
tqdm = "^4.67.1"
parse = "^1.20.2"
scipy = "^1.14.1"
sympy = "^1.13.3"
networkx = "^3.4.2"
pillow = "^11.0.0"
imageio = "^2.36.1"
pygifsicle = "^1.1.0"
opencv-python = "^4.10.0.84"
pandas = "^2.2.3"
[tool.poetry.group.dev.dependencies]
pyright = "^1.1.389"
@ -25,14 +23,6 @@ ruff = "^0.8.1"
poethepoet = "^0.31.1"
ipykernel = "^6.29.5"
networkx-stubs = "^0.0.1"
types-networkx = "^3.4.2.20241115"
[tool.poetry.group.cplex.dependencies]
docplex = "^2.28.240"
cplex = "^22.1.1.2"
[tool.poetry.group.ortools.dependencies]
ortools = "^9.11.4210"
[tool.poetry.scripts]
holt59-aoc = "holt59.aoc.__main__:main"

View File

@ -1,12 +1,10 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
line = sys.stdin.read().strip()
floor = 0
floors = [(floor := floor + (1 if c == "(" else -1)) for c in line]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
floor = 0
floors = [(floor := floor + (1 if c == "(" else -1)) for c in input]
yield floors[-1]
yield floors.index(-1)
print(f"answer 1 is {floors[-1]}")
print(f"answer 2 is {floors.index(-1)}")

View File

@ -1,7 +1,7 @@
import itertools
from typing import Any, Iterator
import sys
from ..base import BaseSolver
line = sys.stdin.read().strip()
# see http://www.se16.info/js/lands2.htm for the explanation of 'atoms' (or elements)
#
@ -9,7 +9,7 @@ from ..base import BaseSolver
# CodeGolf answer https://codegolf.stackexchange.com/a/8479/42148
# fmt: off
ATOMS: list[tuple[str, tuple[int, ...]]] = [
atoms = [
("22", (0, )), # 0
("13112221133211322112211213322112", (71, 90, 0, 19, 2, )), # 1
("312211322212221121123222112", (1, )), # 2
@ -105,7 +105,7 @@ ATOMS: list[tuple[str, tuple[int, ...]]] = [
]
# fmt: on
STARTERS = [
starters = [
"1",
"11",
"21",
@ -122,26 +122,27 @@ def look_and_say_length(s: str, n: int) -> int:
if n == 0:
return len(s)
if s in STARTERS:
if s in starters:
return look_and_say_length(
"".join(f"{len(list(g))}{k}" for k, g in itertools.groupby(s)), n - 1
)
counts = {i: 0 for i in range(len(ATOMS))}
idx = next(i for i, (a, _) in enumerate(ATOMS) if s == a)
counts = {i: 0 for i in range(len(atoms))}
idx = next(i for i, (a, _) in enumerate(atoms) if s == a)
counts[idx] = 1
for _ in range(n):
c2 = {i: 0 for i in range(len(ATOMS))}
c2 = {i: 0 for i in range(len(atoms))}
for i in counts:
for j in ATOMS[i][1]:
for j in atoms[i][1]:
c2[j] += counts[i]
counts = c2
return sum(counts[i] * len(a[0]) for i, a in enumerate(ATOMS))
return sum(counts[i] * len(a[0]) for i, a in enumerate(atoms))
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any] | None:
yield look_and_say_length(input, 40)
yield look_and_say_length(input, 50)
answer_1 = look_and_say_length(line, 40)
print(f"answer 1 is {answer_1}")
answer_2 = look_and_say_length(line, 50)
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,5 @@
import itertools
from typing import Any, Iterator
from ..base import BaseSolver
import sys
def is_valid(p: str) -> bool:
@ -42,8 +40,10 @@ def find_next_password(p: str) -> str:
return p
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
answer_1 = find_next_password(input)
yield answer_1
yield find_next_password(increment(answer_1))
line = sys.stdin.read().strip()
answer_1 = find_next_password(line)
print(f"answer 1 is {answer_1}")
answer_2 = find_next_password(increment(answer_1))
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,6 @@
import json
from typing import Any, Iterator, TypeAlias
from ..base import BaseSolver
import sys
from typing import TypeAlias
JsonObject: TypeAlias = dict[str, "JsonObject"] | list["JsonObject"] | int | str
@ -19,9 +18,10 @@ def json_sum(value: JsonObject, ignore: str | None = None) -> int:
return 0
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
data: JsonObject = json.loads(input)
data: JsonObject = json.load(sys.stdin)
yield json_sum(data)
yield json_sum(data, "red")
answer_1 = json_sum(data)
print(f"answer 1 is {answer_1}")
answer_2 = json_sum(data, "red")
print(f"answer 2 is {answer_2}")

View File

@ -1,11 +1,10 @@
import itertools
import sys
from collections import defaultdict
from typing import Any, Iterator, Literal, cast
from typing import Literal, cast
import parse # type: ignore
from ..base import BaseSolver
def max_change_in_happiness(happiness: dict[str, dict[str, int]]) -> int:
guests = list(happiness)
@ -18,12 +17,10 @@ def max_change_in_happiness(happiness: dict[str, dict[str, int]]) -> int:
)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().splitlines()
happiness: dict[str, dict[str, int]] = defaultdict(dict)
for line in lines:
happiness: dict[str, dict[str, int]] = defaultdict(dict)
for line in lines:
u1, gain_or_loose, hap, u2 = cast(
tuple[str, Literal["gain", "lose"], int, str],
parse.parse( # type: ignore
@ -32,9 +29,13 @@ class Solver(BaseSolver):
)
happiness[u1][u2] = hap if gain_or_loose == "gain" else -hap
yield max_change_in_happiness(happiness)
for guest in list(happiness):
answer_1 = max_change_in_happiness(happiness)
print(f"answer 1 is {answer_1}")
for guest in list(happiness):
happiness["me"][guest] = 0
happiness[guest]["me"] = 0
yield max_change_in_happiness(happiness)
answer_2 = max_change_in_happiness(happiness)
print(f"answer 2 is {answer_2}")

View File

@ -1,10 +1,9 @@
import sys
from dataclasses import dataclass
from typing import Any, Iterator, Literal, cast
from typing import Literal, cast
import parse # type: ignore
from ..base import BaseSolver
@dataclass(frozen=True)
class Reindeer:
@ -14,12 +13,10 @@ class Reindeer:
rest_time: int
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().splitlines()
reindeers: list[Reindeer] = []
for line in lines:
reindeers: list[Reindeer] = []
for line in lines:
reindeer, speed, speed_time, rest_time = cast(
tuple[str, int, int, int],
parse.parse( # type: ignore
@ -29,20 +26,18 @@ class Solver(BaseSolver):
),
)
reindeers.append(
Reindeer(
name=reindeer, speed=speed, fly_time=speed_time, rest_time=rest_time
)
Reindeer(name=reindeer, speed=speed, fly_time=speed_time, rest_time=rest_time)
)
target = 1000 if len(reindeers) <= 2 else 2503
target = 1000 if len(reindeers) <= 2 else 2503
states: dict[Reindeer, tuple[Literal["resting", "flying"], int]] = {
states: dict[Reindeer, tuple[Literal["resting", "flying"], int]] = {
reindeer: ("resting", 0) for reindeer in reindeers
}
distances: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers}
points: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers}
}
distances: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers}
points: dict[Reindeer, int] = {reindeer: 0 for reindeer in reindeers}
for time in self.progress.wrap(range(target)):
for time in range(target):
for reindeer in reindeers:
if states[reindeer][0] == "flying":
distances[reindeer] += reindeer.speed
@ -59,5 +54,9 @@ class Solver(BaseSolver):
else:
states[reindeer] = ("resting", time + reindeer.rest_time)
yield max(distances.values())
yield max(points.values()) - 1
answer_1 = max(distances.values())
print(f"answer 1 is {answer_1}")
answer_2 = max(points.values()) - 1
print(f"answer 2 is {answer_2}")

View File

@ -1,10 +1,9 @@
import math
from typing import Any, Iterator, Sequence, cast
import sys
from typing import Sequence, cast
import parse # type: ignore
from ..base import BaseSolver
def score(ingredients: list[list[int]], teaspoons: Sequence[int]) -> int:
return math.prod(
@ -19,12 +18,10 @@ def score(ingredients: list[list[int]], teaspoons: Sequence[int]) -> int:
)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().splitlines()
ingredients: list[list[int]] = []
for line in lines:
ingredients: list[list[int]] = []
for line in lines:
_, *scores = cast(
tuple[str, int, int, int, int, int],
parse.parse( # type: ignore
@ -35,11 +32,11 @@ class Solver(BaseSolver):
)
ingredients.append(scores)
total_teaspoons = 100
calories: list[int] = []
scores: list[int] = []
total_teaspoons = 100
calories: list[int] = []
scores: list[int] = []
for a in range(total_teaspoons + 1):
for a in range(total_teaspoons + 1):
for b in range(total_teaspoons + 1 - a):
for c in range(total_teaspoons + 1 - a - b):
teaspoons = (a, b, c, total_teaspoons - a - b - c)
@ -52,5 +49,9 @@ class Solver(BaseSolver):
)
)
yield max(scores)
yield max(score for score, calory in zip(scores, calories) if calory == 500)
answer_1 = max(scores)
print(f"answer 1 is {answer_1}")
answer_2 = max(score for score, calory in zip(scores, calories) if calory == 500)
print(f"answer 2 is {answer_2}")

View File

@ -1,9 +1,8 @@
import operator as op
import re
import sys
from collections import defaultdict
from typing import Any, Callable, Iterator
from ..base import BaseSolver
from typing import Callable
MFCSAM: dict[str, int] = {
"children": 3,
@ -18,10 +17,18 @@ MFCSAM: dict[str, int] = {
"perfumes": 1,
}
lines = sys.stdin.readlines()
def match(
aunts: list[dict[str, int]], operators: dict[str, Callable[[int, int], bool]]
) -> int:
aunts: list[dict[str, int]] = [
{
match[1]: int(match[2])
for match in re.findall(R"((?P<compound>[^:, ]+): (?P<quantity>\d+))", line)
}
for line in lines
]
def match(operators: dict[str, Callable[[int, int], bool]]) -> int:
return next(
i
for i, aunt in enumerate(aunts, start=1)
@ -29,29 +36,16 @@ def match(
)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
answer_1 = match(defaultdict(lambda: op.eq))
print(f"answer 1 is {answer_1}")
aunts: list[dict[str, int]] = [
{
match[1]: int(match[2])
for match in re.findall(
R"((?P<compound>[^:, ]+): (?P<quantity>\d+))", line
)
}
for line in lines
]
yield match(aunts, defaultdict(lambda: op.eq))
yield match(
aunts,
answer_2 = match(
defaultdict(
lambda: op.eq,
trees=op.gt,
cats=op.gt,
pomeranians=op.lt,
goldfish=op.lt,
),
)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,5 @@
from typing import Any, Iterator
from ..base import BaseSolver
import sys
from typing import Iterator
def iter_combinations(value: int, containers: list[int]) -> Iterator[tuple[int, ...]]:
@ -17,18 +16,15 @@ def iter_combinations(value: int, containers: list[int]) -> Iterator[tuple[int,
yield (containers[i],) + combination
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
containers = [int(c) for c in input.split()]
total = 25 if len(containers) <= 5 else 150
containers = [int(c) for c in sys.stdin.read().split()]
total = 25 if len(containers) <= 5 else 150
combinations = [
combination for combination in iter_combinations(total, containers)
]
combinations = [combination for combination in iter_combinations(total, containers)]
yield len(combinations)
answer_1 = len(combinations)
print(f"answer 1 is {answer_1}")
min_containers = min(len(combination) for combination in combinations)
yield sum(
1 for combination in combinations if len(combination) == min_containers
)
min_containers = min(len(combination) for combination in combinations)
answer_2 = sum(1 for combination in combinations if len(combination) == min_containers)
print(f"answer 2 is {answer_2}")

View File

@ -1,18 +1,13 @@
import itertools
from typing import Any, Iterator
import sys
import numpy as np
from numpy.typing import NDArray
from ..base import BaseSolver
grid0 = np.array([[c == "#" for c in line] for line in sys.stdin.read().splitlines()])
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
grid0 = np.array([[c == "#" for c in line] for line in input.splitlines()])
# add an always off circle around
grid0 = np.concatenate(
# add an always off circle around
grid0 = np.concatenate(
[
np.zeros((grid0.shape[0] + 2, 1), dtype=bool),
np.concatenate(
@ -25,21 +20,22 @@ class Solver(BaseSolver):
np.zeros((grid0.shape[0] + 2, 1), dtype=bool),
],
axis=1,
)
)
moves = list(itertools.product([-1, 0, 1], repeat=2))
moves.remove((0, 0))
moves = list(itertools.product([-1, 0, 1], repeat=2))
moves.remove((0, 0))
jjs, iis = np.meshgrid(
jjs, iis = np.meshgrid(
np.arange(1, grid0.shape[0] - 1, dtype=int),
np.arange(1, grid0.shape[1] - 1, dtype=int),
)
iis, jjs = iis.flatten(), jjs.flatten()
)
iis, jjs = iis.flatten(), jjs.flatten()
ins = iis[:, None] + np.array(moves)[:, 0]
jns = jjs[:, None] + np.array(moves)[:, 1]
ins = iis[:, None] + np.array(moves)[:, 0]
jns = jjs[:, None] + np.array(moves)[:, 1]
def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]:
def game_of_life(grid: NDArray[np.bool_]) -> NDArray[np.bool_]:
neighbors_on = grid[ins, jns].sum(axis=1)
cells_on = grid[iis, jjs]
@ -48,19 +44,23 @@ class Solver(BaseSolver):
return grid
grid = grid0
n_steps = 4 if len(grid) < 10 else 100
for _ in range(n_steps):
grid = grid0
n_steps = 4 if len(grid) < 10 else 100
for _ in range(n_steps):
grid = game_of_life(grid)
yield grid.sum()
answer_1 = grid.sum()
print(f"answer 1 is {answer_1}")
n_steps = 5 if len(grid) < 10 else 100
grid = grid0
for _ in range(n_steps):
n_steps = 5 if len(grid) < 10 else 100
grid = grid0
for _ in range(n_steps):
grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True
grid = game_of_life(grid)
grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True
grid[[1, 1, -2, -2], [1, -2, 1, -2]] = True
yield sum(cell for line in grid for cell in line)
answer_2 = sum(cell for line in grid for cell in line)
print(f"answer 2 is {answer_2}")

View File

@ -1,41 +1,37 @@
import sys
from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
replacements_s, molecule = sys.stdin.read().split("\n\n")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
replacements_s, molecule = input.split("\n\n")
REPLACEMENTS: dict[str, list[str]] = defaultdict(list)
for replacement_s in replacements_s.splitlines():
REPLACEMENTS: dict[str, list[str]] = defaultdict(list)
for replacement_s in replacements_s.splitlines():
p = replacement_s.split(" => ")
REPLACEMENTS[p[0]].append(p[1])
molecule = molecule.strip()
molecule = molecule.strip()
generated = [
generated = [
molecule[:i] + replacement + molecule[i + len(symbol) :]
for symbol, replacements in REPLACEMENTS.items()
for replacement in replacements
for i in range(len(molecule))
if molecule[i:].startswith(symbol)
]
]
yield len(set(generated))
answer_1 = len(set(generated))
print(f"answer 1 is {answer_1}")
inversion: dict[str, str] = {
inversion: dict[str, str] = {
replacement: symbol
for symbol, replacements in REPLACEMENTS.items()
for replacement in replacements
}
}
# there is actually only one way to create the molecule, and we can greedily replace
# tokens with their replacements, e.g., if H => OH then we can replace OH by H directly
# without thinking
# there is actually only one way to create the molecule, and we can greedily replace
# tokens with their replacements, e.g., if H => OH then we can replace OH by H directly
# without thinking
count = 0
while molecule != "e":
count = 0
while molecule != "e":
i = 0
m2 = ""
while i < len(molecule):
@ -52,6 +48,9 @@ class Solver(BaseSolver):
m2 += molecule[i]
i += 1
# print(m2)
molecule = m2
yield count
answer_2 = count
print(f"answer 2 is {count}")

View File

@ -1,24 +1,20 @@
from typing import Any, Iterator
import sys
import numpy as np
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
length, width, height = np.array(
[[int(c) for c in line.split("x")] for line in lines]
).T
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
length, width, height = np.array(
[[int(c) for c in line.split("x")] for line in input.splitlines()]
).T
lw, wh, hl = (length * width, width * height, height * length)
lw, wh, hl = (length * width, width * height, height * length)
answer_1 = np.sum(2 * (lw + wh + hl) + np.min(np.stack([lw, wh, hl]), axis=0))
print(f"answer 1 is {answer_1}")
yield np.sum(2 * (lw + wh + hl) + np.min(np.stack([lw, wh, hl]), axis=0))
yield np.sum(
answer_2 = np.sum(
length * width * height
+ 2
* np.min(
np.stack([length + width, length + height, height + width]), axis=0
)
)
+ 2 * np.min(np.stack([length + width, length + height, height + width]), axis=0)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,10 +1,10 @@
import itertools
from typing import Any, Iterator
import sys
from ..base import BaseSolver
target = int(sys.stdin.read())
def presents(n: int, elf: int, max: int) -> int:
def presents(n: int, elf: int, max: int = target) -> int:
count = 0
k = 1
while k * k < n:
@ -21,9 +21,8 @@ def presents(n: int, elf: int, max: int) -> int:
return count
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
target = int(input)
answer_1 = next(n for n in itertools.count(1) if presents(n, 10) >= target)
print(f"answer 1 is {answer_1}")
yield next(n for n in itertools.count(1) if presents(n, 10, target) >= target)
yield next(n for n in itertools.count(1) if presents(n, 11, 50) >= target)
answer_2 = next(n for n in itertools.count(1) if presents(n, 11, 50) >= target)
print(f"answer 2 is {answer_2}")

View File

@ -1,8 +1,7 @@
import itertools
import sys
from math import ceil
from typing import Any, Iterator, TypeAlias
from ..base import BaseSolver
from typing import TypeAlias
Modifier: TypeAlias = tuple[str, int, int, int]
@ -34,18 +33,17 @@ RINGS: list[Modifier] = [
]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().splitlines()
player_hp = 100
player_hp = 100
boss_attack = int(lines[1].split(":")[1].strip())
boss_armor = int(lines[2].split(":")[1].strip())
boss_hp = int(lines[0].split(":")[1].strip())
boss_attack = int(lines[1].split(":")[1].strip())
boss_armor = int(lines[2].split(":")[1].strip())
boss_hp = int(lines[0].split(":")[1].strip())
min_cost, max_cost = 1_000_000, 0
for equipments in itertools.product(WEAPONS, ARMORS, RINGS, RINGS):
min_cost, max_cost = 1_000_000, 0
for equipments in itertools.product(WEAPONS, ARMORS, RINGS, RINGS):
if equipments[-1][0] != "" and equipments[-2] == equipments[-1]:
continue
@ -60,5 +58,9 @@ class Solver(BaseSolver):
else:
max_cost = max(cost, max_cost)
yield min_cost
yield max_cost
answer_1 = min_cost
print(f"answer 1 is {answer_1}")
answer_2 = max_cost
print(f"answer 2 is {answer_2}")

View File

@ -1,9 +1,8 @@
from __future__ import annotations
import heapq
from typing import Any, Iterator, Literal, TypeAlias, cast
from ..base import BaseSolver
import sys
from typing import Literal, TypeAlias, cast
PlayerType: TypeAlias = Literal["player", "boss"]
SpellType: TypeAlias = Literal["magic missile", "drain", "shield", "poison", "recharge"]
@ -63,6 +62,17 @@ def play(
continue
visited.add((player, player_hp, player_mana, player_armor, boss_hp, buffs))
if hard_mode and player == "player":
player_hp = max(0, player_hp - 1)
if player_hp == 0:
continue
if boss_hp == 0:
winning_node = spells
continue
new_buffs: list[tuple[BuffType, int]] = []
for buff, length in buffs:
length = length - 1
@ -78,16 +88,6 @@ def play(
if length > 0:
new_buffs.append((buff, length))
if hard_mode and player == "player":
player_hp = player_hp - 1
if player_hp <= 0:
continue
if boss_hp <= 0:
winning_node = spells
continue
buffs = tuple(new_buffs)
if player == "boss":
@ -155,27 +155,23 @@ def play(
return winning_node
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().splitlines()
player_hp = 50
player_mana = 500
player_armor = 0
player_hp = 50
player_mana = 500
player_armor = 0
boss_hp = int(lines[0].split(":")[1].strip())
boss_attack = int(lines[1].split(":")[1].strip())
boss_hp = int(lines[0].split(":")[1].strip())
boss_attack = int(lines[1].split(":")[1].strip())
yield sum(
answer_1 = sum(
c
for _, c in play(
player_hp, player_mana, player_armor, boss_hp, boss_attack, False
)
)
for _, c in play(player_hp, player_mana, player_armor, boss_hp, boss_attack, False)
)
print(f"answer 1 is {answer_1}")
yield sum(
c
for _, c in play(
player_hp, player_mana, player_armor, boss_hp, boss_attack, True
)
)
# 1242 (not working)
answer_2 = sum(
c for _, c in play(player_hp, player_mana, player_armor, boss_hp, boss_attack, True)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,107 +0,0 @@
import inspect
from typing import Any, Callable, Final, Iterator, Mapping
from ..base import BaseSolver
class Instruction:
def __init__(self, fn: Callable[..., None]):
self._fn = fn
args = inspect.getfullargspec(fn)
self._argtypes = [args.annotations[arg] for arg in args.args[1:]]
def __call__(self, args: tuple[str, ...]):
self._fn(
*(argtype(arg) for arg, argtype in zip(args, self._argtypes, strict=True))
)
class Machine:
def __init__(
self, instructions: list[str], registers: dict[str, int] = {"a": 0, "b": 1}
):
self.instructions: Final = [
(part[0], tuple(arg.strip() for arg in " ".join(part[1:]).split(",")))
for instruction in instructions
if (part := instruction.split())
]
self._fns = {
name: Instruction(getattr(self, name))
for name in ("hlf", "tpl", "inc", "jmp", "jie", "jio")
}
self._registers = registers.copy()
self._ip = 0
@property
def registers(self) -> Mapping[str, int]:
return self._registers
@property
def ip(self) -> int:
return self._ip
def reset(self, registers: dict[str, int] = {"a": 0, "b": 0}):
self._registers = registers.copy()
self._ip = 0
def hlf(self, register: str):
self._registers[register] //= 2
self._ip += 1
def tpl(self, register: str):
self._registers[register] *= 3
self._ip += 1
def inc(self, register: str):
self._registers[register] += 1
self._ip += 1
def jmp(self, offset: int):
self._ip += offset
assert 0 <= self._ip < len(self.instructions)
def jie(self, register: str, offset: int):
if self._registers[register] % 2 == 0:
self._ip += offset
else:
self._ip += 1
def jio(self, register: str, offset: int):
if self._registers[register] == 1:
self._ip += offset
else:
self._ip += 1
def _exec(self) -> bool:
# execute next instruction
if self._ip >= len(self.instructions):
return False
ins, args = self.instructions[self._ip]
if ins not in self._fns:
return False
self._fns[ins](args)
return True
def run(self):
while self._exec():
...
return self.registers
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
machine = Machine(input.splitlines())
registers = machine.run()
yield registers["b"]
machine.reset({"a": 1, "b": 0})
registers = machine.run()
yield registers["b"]

View File

@ -1,88 +0,0 @@
from typing import Any, Iterator, TypeAlias
from ..base import BaseSolver
TupleOfInts: TypeAlias = tuple[int, ...]
def check_n_groups(
target: int, groups: tuple[TupleOfInts, ...], numbers: TupleOfInts
) -> bool:
n_groups = len(groups)
groups_s = tuple(sum(group) for group in groups)
if all(target == group_s for group_s in groups_s):
return not numbers
if not numbers:
return False
head, *tail_l = numbers
tail, tail_s = tuple(tail_l), sum(tail_l)
return any(
groups_s[i] + head <= target
and sum(groups_s[j] for j in range(len(groups)) if i != j) + tail_s
>= (n_groups - 1) * target
and check_n_groups(
target, groups[:i] + ((groups[i] + (head,)),) + groups[i + 1 :], tail
)
for i in range(len(groups))
)
def enumerate_single_subset(
target: int, numbers: TupleOfInts
) -> Iterator[tuple[int, TupleOfInts, TupleOfInts]]:
"""
Enumerate subset of numbers whose sum equals target.
Subset are enumerated in increasing order of length, then product (quantum value).
Args:
target: Target for the sum of the subset.
numbers: Tuple of integers to find the subset from.
Returns:
A generator (quantum, subset, remaining) where subset if the subset of numbers
whose sum equals target, quantum the product of the subset, and remaining the
remaining numbers.
"""
groups: list[tuple[int, TupleOfInts, TupleOfInts]] = [(1, (), numbers)]
for _ in range(len(numbers)):
new_groups: list[tuple[int, TupleOfInts, TupleOfInts]] = []
for g_quantum, group, remaining in groups:
sg = sum(group)
for i in range(len(remaining)):
if group and remaining[i] <= group[-1]:
continue
uv = remaining[:i] + remaining[i + 1 :]
kv = g_quantum * remaining[i], group + (remaining[i],), uv
if sg + remaining[i] == target:
yield kv
elif sg + remaining[i] < target:
new_groups.append(kv)
groups = new_groups
def find_min_quantum(numbers: tuple[int, ...], n_groups: int):
return next(
g_quantum
for g_quantum, group_1v2, group_234v2 in enumerate_single_subset(
sum(numbers) // n_groups, numbers
)
if check_n_groups(sum(group_1v2), ((),) * (n_groups - 1), group_234v2)
)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
numbers = tuple(map(int, input.split()))
yield find_min_quantum(numbers, 3)
yield find_min_quantum(numbers, 4)

View File

@ -1,16 +0,0 @@
import re
from typing import Any, Iterator
from ..base import BaseSolver
from ..tools.math import pow_mod
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
m = re.search(r"row\s*([0-9]+)\s*,\s*column\s*([0-9]+)", input)
assert m is not None
row, col = int(m.group(1)), int(m.group(2))
n = (row * (row - 1)) // 2 + col * (col + 1) // 2 + (row - 1) * (col - 1)
yield (20151125 * pow_mod(252533, n - 1, 33554393)) % 33554393

View File

@ -1,7 +1,7 @@
import sys
from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
line = sys.stdin.read().strip()
def process(directions: str) -> dict[tuple[int, int], int]:
@ -27,7 +27,8 @@ def process(directions: str) -> dict[tuple[int, int], int]:
return counts
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
yield len(process(input))
yield len(process(input[::2]) | process(input[1::2]))
answer_1 = len(process(line))
print(f"answer 1 is {answer_1}")
answer_2 = len(process(line[::2]) | process(line[1::2]))
print(f"answer 2 is {answer_2}")

View File

@ -1,20 +1,16 @@
import hashlib
import itertools
from typing import Any, Iterator
import sys
from ..base import BaseSolver
line = sys.stdin.read().strip()
it = iter(itertools.count(1))
answer_1 = next(
i for i in it if hashlib.md5(f"{line}{i}".encode()).hexdigest().startswith("00000")
)
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
it = iter(itertools.count(1))
yield next(
i
for i in it
if hashlib.md5(f"{input}{i}".encode()).hexdigest().startswith("00000")
)
yield next(
i
for i in it
if hashlib.md5(f"{input}{i}".encode()).hexdigest().startswith("000000")
)
answer_2 = next(
i for i in it if hashlib.md5(f"{line}{i}".encode()).hexdigest().startswith("000000")
)
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,4 @@
from typing import Any, Iterator
from ..base import BaseSolver
import sys
VOWELS = "aeiou"
FORBIDDEN = {"ab", "cd", "pq", "xy"}
@ -29,8 +27,10 @@ def is_nice_2(s: str) -> bool:
return True
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
yield sum(map(is_nice_1, lines))
yield sum(map(is_nice_2, lines))
lines = sys.stdin.read().splitlines()
answer_1 = sum(map(is_nice_1, lines))
print(f"answer 1 is {answer_1}")
answer_2 = sum(map(is_nice_2, lines))
print(f"answer 2 is {answer_2}")

View File

@ -1,16 +1,14 @@
from typing import Any, Iterator, Literal, cast
import sys
from typing import Literal, cast
import numpy as np
import parse # type: ignore
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lights_1 = np.zeros((1000, 1000), dtype=bool)
lights_2 = np.zeros((1000, 1000), dtype=int)
for line in input.splitlines():
lights_1 = np.zeros((1000, 1000), dtype=bool)
lights_2 = np.zeros((1000, 1000), dtype=int)
for line in lines:
action, sx, sy, ex, ey = cast(
tuple[Literal["turn on", "turn off", "toggle"], int, int, int, int],
parse.parse("{} {:d},{:d} through {:d},{:d}", line), # type: ignore
@ -28,5 +26,8 @@ class Solver(BaseSolver):
lights_1[sx:ex, sy:ey] = ~lights_1[sx:ex, sy:ey]
lights_2[sx:ex, sy:ey] += 2
yield lights_1.sum()
yield lights_2.sum()
answer_1 = lights_1.sum()
print(f"answer 1 is {answer_1}")
answer_2 = lights_2.sum()
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
import logging
import operator
from typing import Any, Callable, Iterator
import os
import sys
from typing import Callable
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
OPERATORS = {
"AND": operator.and_,
@ -32,27 +36,12 @@ def value_of(key: str) -> tuple[str, Callable[[dict[str, int]], int]]:
return key, lambda values: values[key]
def process(
signals: Signals,
values: dict[str, int],
) -> dict[str, int]:
while signals:
signal = next(s for s in signals if all(p in values for p in signals[s][0]))
_, deps, command = signals[signal]
values[signal] = command(deps[0](values), deps[1](values)) % 65536
del signals[signal]
lines = sys.stdin.read().splitlines()
return values
signals: Signals = {}
values: dict[str, int] = {"": 0}
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any] | None:
lines = input.splitlines()
signals: Signals = {}
values: dict[str, int] = {"": 0}
for line in lines:
for line in lines:
command, signal = line.split(" -> ")
if command.startswith("NOT"):
@ -88,9 +77,25 @@ class Solver(BaseSolver):
signals[signal] = ((lhs_s, rhs_s), (lhs_fn, rhs_fn), op)
values_1 = process(signals.copy(), values.copy())
for k in sorted(values_1):
self.logger.info(f"{k}: {values_1[k]}")
yield values_1["a"]
yield process(signals.copy(), values | {"b": values_1["a"]})["a"]
def process(
signals: Signals,
values: dict[str, int],
) -> dict[str, int]:
while signals:
signal = next(s for s in signals if all(p in values for p in signals[s][0]))
_, deps, command = signals[signal]
values[signal] = command(deps[0](values), deps[1](values)) % 65536
del signals[signal]
return values
values_1 = process(signals.copy(), values.copy())
logging.info("\n" + "\n".join(f"{k}: {values_1[k]}" for k in sorted(values_1)))
answer_1 = values_1["a"]
print(f"answer 1 is {answer_1}")
values_2 = process(signals.copy(), values | {"b": values_1["a"]})
answer_2 = values_2["a"]
print(f"answer 2 is {answer_2}")

View File

@ -1,13 +1,14 @@
from typing import Any, Iterator
import logging
import os
import sys
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().splitlines()
yield sum(
answer_1 = sum(
# left and right quotes (not in memory)
2
# each \\ adds one character in the literals (compared to memory)
@ -19,9 +20,10 @@ class Solver(BaseSolver):
# avoid \\\\x, etc., but this does not occur in the examples and the actual input
+ 3 * (line.count(R"\x") - line.count(R"\\x") + line.count(R"\\\x"))
for line in lines
)
)
print(f"answer 1 is {answer_1}")
yield sum(
answer_2 = sum(
# needs to wrap in quotes (2 characters)
2
# needs to escape every \ with an extra \
@ -29,4 +31,5 @@ class Solver(BaseSolver):
# needs to escape every " with an extra \ (including the first and last ones)
+ line.count('"')
for line in lines
)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,28 +1,27 @@
import itertools
import sys
from collections import defaultdict
from typing import Any, Iterator, cast
from typing import cast
import parse # type: ignore
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
distances: dict[str, dict[str, int]] = defaultdict(dict)
for line in lines:
distances: dict[str, dict[str, int]] = defaultdict(dict)
for line in lines:
origin, destination, length = cast(
tuple[str, str, int],
parse.parse("{} to {} = {:d}", line), # type: ignore
)
distances[origin][destination] = distances[destination][origin] = length
distance_of_routes = {
distance_of_routes = {
route: sum(distances[o][d] for o, d in zip(route[:-1], route[1:]))
for route in map(tuple, itertools.permutations(distances))
}
}
yield min(distance_of_routes.values())
yield max(distance_of_routes.values())
answer_1 = min(distance_of_routes.values())
print(f"answer 1 is {answer_1}")
answer_2 = max(distance_of_routes.values())
print(f"answer 2 is {answer_2}")

View File

@ -1,17 +1,14 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
values = [int(line) for line in lines]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# part 1
answer_1 = sum(v2 > v1 for v1, v2 in zip(values[:-1], values[1:]))
print(f"answer 1 is {answer_1}")
values = [int(line) for line in lines]
# part 1
yield sum(v2 > v1 for v1, v2 in zip(values[:-1], values[1:]))
# part 2
runnings = [sum(values[i : i + 3]) for i in range(len(values) - 2)]
yield sum(v2 > v1 for v1, v2 in zip(runnings[:-1], runnings[1:]))
# part 2
runnings = [sum(values[i : i + 3]) for i in range(len(values) - 2)]
answer_2 = sum(v2 > v1 for v1, v2 in zip(runnings[:-1], runnings[1:]))
print(f"answer 2 is {answer_2}")

View File

@ -1,47 +1,11 @@
from functools import reduce
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
BRACKETS = {"{": "}", "[": "]", "<": ">", "(": ")"}
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
CORRUPT_SCORES = {")": 3, "]": 57, "}": 1197, ">": 25137}
COMPLETE_SCORES = {")": 1, "]": 2, "}": 3, ">": 4}
def corrupted_or_incomplete(line: str) -> tuple[bool, str]:
opens: list[str] = []
for c in line:
if c in BRACKETS:
opens.append(c)
elif BRACKETS[opens[-1]] != c:
return True, c
else:
opens.pop()
return (False, "".join(opens))
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
answer_1: int = 0
incomplete_scores: list[int] = []
for line in lines:
c, r = corrupted_or_incomplete(line)
if c:
answer_1 += CORRUPT_SCORES[r]
else:
incomplete_scores.append(
reduce(
lambda s, c: s * 5 + COMPLETE_SCORES[BRACKETS[c]],
reversed(r),
0,
),
)
yield answer_1
yield sorted(incomplete_scores)[len(incomplete_scores) // 2]
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,66 +1,11 @@
import itertools as it
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
def do_step(values: list[list[int]]) -> tuple[list[list[int]], set[tuple[int, int]]]:
values = [[c + 1 for c in r] for r in values]
flashed: set[tuple[int, int]] = set()
while True:
found = False
for i_row, row in enumerate(values):
for i_col, col in enumerate(row):
if col <= 9 or (i_row, i_col) in flashed:
continue
found = True
flashed.add((i_row, i_col))
for dr, dc in it.product((-1, 0, 1), repeat=2):
if 0 <= i_row + dr < len(values) and 0 <= i_col + dc < len(
values[0]
):
values[i_row + dr][i_col + dc] += 1
if not found:
break
for i, j in flashed:
values[i][j] = 0
return values, flashed
class Solver(BaseSolver):
def print_grid(self, values: list[list[int]], flashed: set[tuple[int, int]]):
for i_row, row in enumerate(values):
s_row = ""
for i_col, col in enumerate(row):
if (i_row, i_col) in flashed:
s_row += f"\033[0;31m{col}\033[0;00m"
else:
s_row += str(col)
self.logger.info(s_row)
self.logger.info("")
def solve(self, input: str) -> Iterator[Any]:
values_0 = [[int(c) for c in r] for r in input.splitlines()]
values = values_0
total_flashed: int = 0
for _ in range(100):
values, flashed = do_step(values)
total_flashed += len(flashed)
yield total_flashed
n_cells = len(values) * len(values[0])
flashed: set[tuple[int, int]] = set()
values, step = values_0, 0
while len(flashed) != n_cells:
values, flashed = do_step(values)
step += 1
yield step
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,64 +1,11 @@
import string
from collections import defaultdict
from functools import cache
from typing import Any, Iterator, Mapping, Sequence
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
@cache
def is_small(node: str):
return all(c in string.ascii_lowercase for c in node)
def enumerate_paths(
neighbors: Mapping[str, Sequence[str]],
duplicate_smalls: int = 0,
start: str = "start",
current: tuple[str, ...] = ("start",),
) -> Iterator[tuple[str, ...]]:
if start == "end":
yield current
for neighbor in neighbors[start]:
if not is_small(neighbor):
yield from enumerate_paths(
neighbors, duplicate_smalls, neighbor, current + (neighbor,)
)
elif neighbor not in current:
yield from enumerate_paths(
neighbors, duplicate_smalls, neighbor, current + (neighbor,)
)
elif duplicate_smalls > 0:
yield from enumerate_paths(
neighbors, duplicate_smalls - 1, neighbor, current + (neighbor,)
)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
neighbors: dict[str, list[str]] = defaultdict(list)
for row in input.splitlines():
a, b = row.split("-")
if a != "end" and b != "start":
neighbors[a].append(b)
if b != "end" and a != "start":
neighbors[b].append(a)
if self.files:
graph = "graph {\n"
for node, neighbors_of in neighbors.items():
graph += (
" ".join(
f"{node} -- {neighbor};"
for neighbor in neighbors_of
if node <= neighbor or node == "start" or neighbor == "end"
)
+ "\n"
)
graph += "}\n"
self.files.create("graph.dot", graph.encode(), False)
yield len(list(enumerate_paths(neighbors)))
yield len(list(enumerate_paths(neighbors, 1)))
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,20 +1,17 @@
import sys
from math import prod
from typing import Any, Iterator, Literal, TypeAlias, cast
from typing import Literal, TypeAlias, cast
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
Command: TypeAlias = Literal["forward", "up", "down"]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
commands: list[tuple[Command, int]] = [
commands: list[tuple[Command, int]] = [
(cast(Command, (p := line.split())[0]), int(p[1])) for line in lines
]
]
def depth_and_position(use_aim: bool):
def depth_and_position(use_aim: bool):
aim, pos, depth = 0, 0, 0
for command, value in commands:
d_depth = 0
@ -34,5 +31,11 @@ class Solver(BaseSolver):
return depth, pos
yield prod(depth_and_position(False))
yield prod(depth_and_position(True))
# part 1
answer_1 = prod(depth_and_position(False))
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = prod(depth_and_position(True))
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# part 1
answer_1 = ...
print(f"answer 1 is {answer_1}")
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]: ...
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,6 @@
import sys
from collections import Counter
from typing import Any, Iterator, Literal
from ..base import BaseSolver
from typing import Literal
def generator_rating(
@ -21,23 +20,20 @@ def generator_rating(
return values[0]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().splitlines()
# part 1
most_and_least_common = [
tuple(
Counter(line[col] for line in lines).most_common(2)[m][0]
for m in range(2)
)
# part 1
most_and_least_common = [
tuple(Counter(line[col] for line in lines).most_common(2)[m][0] for m in range(2))
for col in range(len(lines[0]))
]
gamma_rate = int("".join(most for most, _ in most_and_least_common), base=2)
epsilon_rate = int("".join(least for _, least in most_and_least_common), base=2)
yield gamma_rate * epsilon_rate
]
gamma_rate = int("".join(most for most, _ in most_and_least_common), base=2)
epsilon_rate = int("".join(least for _, least in most_and_least_common), base=2)
print(f"answer 1 is {gamma_rate * epsilon_rate}")
# part 2
oxygen_generator_rating = int(generator_rating(lines, True, "1"), base=2)
co2_scrubber_rating = int(generator_rating(lines, False, "0"), base=2)
yield oxygen_generator_rating * co2_scrubber_rating
# part 2
oxygen_generator_rating = int(generator_rating(lines, True, "1"), base=2)
co2_scrubber_rating = int(generator_rating(lines, False, "0"), base=2)
answer_2 = oxygen_generator_rating * co2_scrubber_rating
print(f"answer 2 is {answer_2}")

View File

@ -1,28 +1,23 @@
from typing import Any, Iterator
import sys
import numpy as np
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
numbers = [int(c) for c in lines[0].split(",")]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
numbers = [int(c) for c in lines[0].split(",")]
boards = np.asarray(
boards = np.asarray(
[
[[int(c) for c in line.split()] for line in lines[start : start + 5]]
for start in range(2, len(lines), 6)
]
)
)
# (round, score) for each board (-1 when not found)
winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))]
marked = np.zeros_like(boards, dtype=bool)
# (round, score) for each board (-1 when not found)
winning_rounds: list[tuple[int, int]] = [(-1, -1) for _ in range(len(boards))]
marked = np.zeros_like(boards, dtype=bool)
for round, number in enumerate(numbers):
for round, number in enumerate(numbers):
# mark boards
marked[boards == number] = True
@ -31,9 +26,7 @@ class Solver(BaseSolver):
if winning_rounds[index][0] > 0:
continue
if np.any(
np.all(marked[index], axis=0) | np.all(marked[index], axis=1)
):
if np.any(np.all(marked[index], axis=0) | np.all(marked[index], axis=1)):
winning_rounds[index] = (
round,
number * int(np.sum(boards[index][~marked[index]])),
@ -43,10 +36,10 @@ class Solver(BaseSolver):
if np.all(marked.all(axis=1) | marked.all(axis=2)):
break
# part 1
(_, score) = min(winning_rounds, key=lambda w: w[0])
yield score
# part 1
(_, score) = min(winning_rounds, key=lambda w: w[0])
print(f"answer 1 is {score}")
# part 2
(_, score) = max(winning_rounds, key=lambda w: w[0])
yield score
# part 2
(_, score) = max(winning_rounds, key=lambda w: w[0])
print(f"answer 2 is {score}")

View File

@ -1,15 +1,10 @@
from typing import Any, Iterator
import sys
import numpy as np
from ..base import BaseSolver
lines: list[str] = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
sections: list[tuple[tuple[int, int], tuple[int, int]]] = [
sections: list[tuple[tuple[int, int], tuple[int, int]]] = [
(
(
int(line.split(" -> ")[0].split(",")[0]),
@ -21,19 +16,21 @@ class Solver(BaseSolver):
),
)
for line in lines
]
]
np_sections = np.array(sections).reshape(-1, 4)
np_sections = np.array(sections).reshape(-1, 4)
x_max, y_max = (
x_min, x_max, y_min, y_max = (
min(np_sections[:, 0].min(), np_sections[:, 2].min()),
max(np_sections[:, 0].max(), np_sections[:, 2].max()),
min(np_sections[:, 1].min(), np_sections[:, 3].min()),
max(np_sections[:, 1].max(), np_sections[:, 3].max()),
)
)
counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int)
counts_2 = counts_1.copy()
counts_1 = np.zeros((y_max + 1, x_max + 1), dtype=int)
counts_2 = counts_1.copy()
for (x1, y1), (x2, y2) in sections:
for (x1, y1), (x2, y2) in sections:
x_rng = range(x1, x2 + 1, 1) if x2 >= x1 else range(x1, x2 - 1, -1)
y_rng = range(y1, y2 + 1, 1) if y2 >= y1 else range(y1, y2 - 1, -1)
@ -44,5 +41,8 @@ class Solver(BaseSolver):
for i, j in zip(y_rng, x_rng):
counts_2[i, j] += 1
yield (counts_1 >= 2).sum()
yield (counts_2 >= 2).sum()
answer_1 = (counts_1 >= 2).sum()
print(f"answer 1 is {answer_1}")
answer_2 = (counts_2 >= 2).sum()
print(f"answer 2 is {answer_2}")

View File

@ -1,21 +1,21 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
values = [int(c) for c in sys.stdin.read().strip().split(",")]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
values = [int(c) for c in input.split(",")]
days = 256
lanterns = {day: 0 for day in range(days)}
for value in values:
days = 256
lanterns = {day: 0 for day in range(days)}
for value in values:
for day in range(value, days, 7):
lanterns[day] += 1
for day in range(days):
for day in range(days):
for day2 in range(day + 9, days, 7):
lanterns[day2] += lanterns[day]
yield sum(v for k, v in lanterns.items() if k < 80) + len(values)
yield sum(lanterns.values()) + len(values)
# part 1
answer_1 = sum(v for k, v in lanterns.items() if k < 80) + len(values)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = sum(lanterns.values()) + len(values)
print(f"answer 2 is {answer_2}")

View File

@ -1,22 +1,19 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
positions = [int(c) for c in sys.stdin.read().strip().split(",")]
min_position, max_position = min(positions), max(positions)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
positions = [int(c) for c in input.split(",")]
min_position, max_position = min(positions), max(positions)
# part 1
yield min(
# part 1
answer_1 = min(
sum(abs(p - position) for p in positions)
for position in range(min_position, max_position + 1)
)
)
print(f"answer 1 is {answer_1}")
# part 2
yield min(
# part 2
answer_2 = min(
sum(abs(p - position) * (abs(p - position) + 1) // 2 for p in positions)
for position in range(min_position, max_position + 1)
)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,8 @@
import itertools
from typing import Any, Iterator
import os
import sys
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
digits = {
"abcefg": 0,
@ -16,23 +17,19 @@ digits = {
"abcdfg": 9,
}
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# part 1
lengths = {len(k) for k, v in digits.items() if v in (1, 4, 7, 8)}
answer_1 = sum(
len(p) in lengths for line in lines for p in line.split("|")[1].strip().split()
)
print(f"answer 1 is {answer_1}")
# part 1
lengths = {len(k) for k, v in digits.items() if v in (1, 4, 7, 8)}
yield sum(
len(p) in lengths
for line in lines
for p in line.split("|")[1].strip().split()
)
# part 2
values: list[int] = []
# part 2
values: list[int] = []
for line in lines:
for line in lines:
parts = line.split("|")
broken_digits = sorted(parts[0].strip().split(), key=len)
@ -52,9 +49,7 @@ class Solver(BaseSolver):
bd = [u for u in per_length[4][0] if u not in cf]
# the 3 digits of length 5 have a, d and g in common
adg = [
u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])
]
adg = [u for u in per_length[5][0] if all(u in pe for pe in per_length[5][1:])]
# we can remove a
dg = [u for u in adg if u != a]
@ -82,8 +77,11 @@ class Solver(BaseSolver):
digit = "".join(sorted(mapping[c] for c in number))
value = 10 * value + digits[digit]
self.logger.info(f"value for '{line}' is {value}")
if VERBOSE:
print(value)
values.append(value)
yield sum(values)
answer_2 = sum(values)
print(f"answer 2 is {answer_2}")

View File

@ -1,18 +1,18 @@
import sys
from math import prod
from typing import Any, Iterator
from ..base import BaseSolver
values = [[int(c) for c in row] for row in sys.stdin.read().splitlines()]
n_rows, n_cols = len(values), len(values[0])
def neighbors(point: tuple[int, int], n_rows: int, n_cols: int):
def neighbors(point: tuple[int, int]):
i, j = point
for di, dj in ((-1, 0), (+1, 0), (0, -1), (0, +1)):
if 0 <= i + di < n_rows and 0 <= j + dj < n_cols:
yield (i + di, j + dj)
def basin(values: list[list[int]], start: tuple[int, int]) -> set[tuple[int, int]]:
n_rows, n_cols = len(values), len(values[0])
def basin(start: tuple[int, int]) -> set[tuple[int, int]]:
visited: set[tuple[int, int]] = set()
queue = [start]
@ -23,25 +23,22 @@ def basin(values: list[list[int]], start: tuple[int, int]) -> set[tuple[int, int
continue
visited.add((i, j))
queue.extend(neighbors((i, j), n_rows, n_cols))
queue.extend(neighbors((i, j)))
return visited
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
values = [[int(c) for c in row] for row in input.splitlines()]
n_rows, n_cols = len(values), len(values[0])
low_points = [
low_points = [
(i, j)
for i in range(n_rows)
for j in range(n_cols)
if all(
values[ti][tj] > values[i][j]
for ti, tj in neighbors((i, j), n_rows, n_cols)
)
]
if all(values[ti][tj] > values[i][j] for ti, tj in neighbors((i, j)))
]
yield sum(values[i][j] + 1 for i, j in low_points)
yield prod(sorted(len(basin(values, point)) for point in low_points)[-3:])
# part 1
answer_1 = sum(values[i][j] + 1 for i, j in low_points)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = prod(sorted(len(basin(point)) for point in low_points)[-3:])
print(f"answer 2 is {answer_2}")

View File

@ -1,12 +1,7 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
blocks = sys.stdin.read().split("\n\n")
values = sorted(sum(map(int, block.split())) for block in blocks)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
blocks = input.split("\n\n")
values = sorted(sum(map(int, block.split())) for block in blocks)
yield values[-1]
yield sum(values[-3:])
print(f"answer 1 is {values[-1]}")
print(f"answer 2 is {sum(values[-3:])}")

View File

@ -1,16 +1,13 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
cycle = 1
x = 1
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
values = {cycle: x}
cycle, x = 1, 1
values = {cycle: x}
for line in lines:
for line in lines:
cycle += 1
if line == "noop":
@ -25,19 +22,17 @@ class Solver(BaseSolver):
values[cycle] = x
answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40))
yield answer_1
answer_1 = sum(c * values[c] for c in range(20, max(values.keys()) + 1, 40))
print(f"answer 1 is {answer_1}")
yield (
"\n"
+ "\n".join(
"".join(
"#"
if j >= (v := values[1 + i * 40 + j]) - 1 and j <= v + 1
else "."
for j in range(40)
)
for i in range(6)
)
+ "\n"
)
for i in range(6):
for j in range(40):
v = values[1 + i * 40 + j]
if j >= v - 1 and j <= v + 1:
print("#", end="")
else:
print(".", end="")
print()

View File

@ -1,8 +1,7 @@
import copy
import sys
from functools import reduce
from typing import Any, Callable, Final, Iterator, Mapping, Sequence
from ..base import BaseSolver
from typing import Callable, Final, Mapping, Sequence
class Monkey:
@ -120,28 +119,24 @@ def monkey_business(inspects: dict[Monkey, int]) -> int:
return sorted_levels[-2] * sorted_levels[-1]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
monkeys = [parse_monkey(block.splitlines()) for block in input.split("\n\n")]
monkeys = [parse_monkey(block.splitlines()) for block in sys.stdin.read().split("\n\n")]
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
yield monkey_business(
# case 1: we simply divide the worry by 3 after applying the monkey worry operation
answer_1 = monkey_business(
run(copy.deepcopy(monkeys), 20, me_worry_fn=lambda w: w // 3)
)
)
print(f"answer 1 is {answer_1}")
# case 2: to keep reasonable level values, we can use a modulo operation, we need to
# use the product of all "divisible by" test so that the test remains valid
#
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
#
# (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
# we use the product of all test value
#
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
yield monkey_business(
run(
copy.deepcopy(monkeys),
10_000,
me_worry_fn=lambda w: w % total_test_value,
)
)
# case 2: to keep reasonable level values, we can use a modulo operation, we need to
# use the product of all "divisible by" test so that the test remains valid
#
# (a + b) % c == ((a % c) + (b % c)) % c --- this would work for a single test value
#
# (a + b) % c == ((a % d) + (b % d)) % c --- if d is a multiple of c, which is why here
# we use the product of all test value
#
total_test_value = reduce(lambda w, m: w * m.test_value, monkeys, 1)
answer_2 = monkey_business(
run(copy.deepcopy(monkeys), 10_000, me_worry_fn=lambda w: w % total_test_value)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,6 @@
import heapq
from typing import Any, Callable, Iterator, TypeVar
from ..base import BaseSolver
import sys
from typing import Callable, Iterator, TypeVar
Node = TypeVar("Node")
@ -69,6 +68,30 @@ def make_path(parents: dict[Node, Node], start: Node, end: Node) -> list[Node] |
return list(reversed(path))
def print_path(path: list[tuple[int, int]], n_rows: int, n_cols: int) -> None:
end = path[-1]
graph = [["." for _c in range(n_cols)] for _r in range(n_rows)]
graph[end[0]][end[1]] = "E"
for i in range(0, len(path) - 1):
cr, cc = path[i]
nr, nc = path[i + 1]
if cr == nr and nc == cc - 1:
graph[cr][cc] = "<"
elif cr == nr and nc == cc + 1:
graph[cr][cc] = ">"
elif cr == nr - 1 and nc == cc:
graph[cr][cc] = "v"
elif cr == nr + 1 and nc == cc:
graph[cr][cc] = "^"
else:
assert False, "{} -> {} infeasible".format(path[i], path[i + 1])
print("\n".join("".join(row) for row in graph))
def neighbors(
grid: list[list[int]], node: tuple[int, int], up: bool
) -> Iterator[tuple[int, int]]:
@ -95,52 +118,17 @@ def neighbors(
# === main code ===
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def print_path(
self, name: str, path: list[tuple[int, int]], n_rows: int, n_cols: int
) -> None:
if not self.files:
return
grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
end = path[-1]
start: tuple[int, int] | None = None
end: tuple[int, int] | None = None
graph = [["." for _c in range(n_cols)] for _r in range(n_rows)]
graph[end[0]][end[1]] = "E"
# for part 2
start_s: list[tuple[int, int]] = []
for i in range(0, len(path) - 1):
cr, cc = path[i]
nr, nc = path[i + 1]
if cr == nr and nc == cc - 1:
graph[cr][cc] = "<"
elif cr == nr and nc == cc + 1:
graph[cr][cc] = ">"
elif cr == nr - 1 and nc == cc:
graph[cr][cc] = "v"
elif cr == nr + 1 and nc == cc:
graph[cr][cc] = "^"
else:
assert False, "{} -> {} infeasible".format(path[i], path[i + 1])
self.files.create(
f"graph_{name}.txt",
"\n".join("".join(row) for row in graph).encode(),
text=True,
)
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
grid = [[ord(cell) - ord("a") for cell in line] for line in lines]
start: tuple[int, int] | None = None
end: tuple[int, int] | None = None
# for part 2
start_s: list[tuple[int, int]] = []
for i_row, row in enumerate(grid):
for i_row, row in enumerate(grid):
for i_col, col in enumerate(row):
if chr(col + ord("a")) == "S":
start = (i_row, i_col)
@ -150,27 +138,26 @@ class Solver(BaseSolver):
elif col == 0:
start_s.append((i_row, i_col))
assert start is not None
assert end is not None
assert start is not None
assert end is not None
# fix values
grid[start[0]][start[1]] = 0
grid[end[0]][end[1]] = ord("z") - ord("a")
# fix values
grid[start[0]][start[1]] = 0
grid[end[0]][end[1]] = ord("z") - ord("a")
lengths_1, parents_1 = dijkstra(
start=start,
neighbors=lambda n: neighbors(grid, n, True),
cost=lambda lhs, rhs: 1,
)
path_1 = make_path(parents_1, start, end)
assert path_1 is not None
self.print_path("answer1", path_1, n_rows=len(grid), n_cols=len(grid[0]))
yield lengths_1[end] - 1
lengths_1, parents_1 = dijkstra(
start=start, neighbors=lambda n: neighbors(grid, n, True), cost=lambda lhs, rhs: 1
)
path_1 = make_path(parents_1, start, end)
assert path_1 is not None
lengths_2, _ = dijkstra(
start=end,
neighbors=lambda n: neighbors(grid, n, False),
cost=lambda lhs, rhs: 1,
)
yield min(lengths_2.get(start, float("inf")) for start in start_s)
print_path(path_1, n_rows=len(grid), n_cols=len(grid[0]))
print(f"answer 1 is {lengths_1[end] - 1}")
lengths_2, parents_2 = dijkstra(
start=end, neighbors=lambda n: neighbors(grid, n, False), cost=lambda lhs, rhs: 1
)
answer_2 = min(lengths_2.get(start, float("inf")) for start in start_s)
print(f"answer 2 is {answer_2}")

View File

@ -1,8 +1,11 @@
import json
import sys
from functools import cmp_to_key
from typing import Any, Iterator, TypeAlias, cast
from typing import TypeAlias, cast
from ..base import BaseSolver
blocks = sys.stdin.read().strip().split("\n\n")
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
Packet: TypeAlias = list[int | list["Packet"]]
@ -25,18 +28,14 @@ def compare(lhs: Packet, rhs: Packet) -> int:
return len(rhs) - len(lhs)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
blocks = input.split("\n\n")
pairs = [tuple(json.loads(p) for p in block.split("\n")) for block in blocks]
answer_1 = sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
print(f"answer_1 is {answer_1}")
yield sum(i + 1 for i, (lhs, rhs) in enumerate(pairs) if compare(lhs, rhs) > 0)
dividers = [[[2]], [[6]]]
dividers = [[[2]], [[6]]]
packets = [packet for packets in pairs for packet in packets]
packets.extend(dividers)
packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
packets = [packet for packets in pairs for packet in packets]
packets.extend(dividers)
packets = list(reversed(sorted(packets, key=cmp_to_key(compare))))
d_index = [packets.index(d) + 1 for d in dividers]
yield d_index[0] * d_index[1]
d_index = [packets.index(d) + 1 for d in dividers]
print(f"answer 2 is {d_index[0] * d_index[1]}")

View File

@ -1,7 +1,6 @@
import sys
from enum import Enum, auto
from typing import Any, Callable, Iterator, cast
from ..base import BaseSolver
from typing import Callable, cast
class Cell(Enum):
@ -13,6 +12,26 @@ class Cell(Enum):
return {Cell.AIR: ".", Cell.ROCK: "#", Cell.SAND: "O"}[self]
def print_blocks(blocks: dict[tuple[int, int], Cell]):
"""
Print the given set of blocks on a grid.
Args:
blocks: Set of blocks to print.
"""
x_min, y_min, x_max, y_max = (
min(x for x, _ in blocks),
0,
max(x for x, _ in blocks),
max(y for _, y in blocks),
)
for y in range(y_min, y_max + 1):
print(
"".join(str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1))
)
def flow(
blocks: dict[tuple[int, int], Cell],
stop_fn: Callable[[int, int], bool],
@ -65,53 +84,21 @@ def flow(
# === inputs ===
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def print_blocks(self, name: str, blocks: dict[tuple[int, int], Cell]):
"""
Print the given set of blocks on a grid.
Args:
blocks: Set of blocks to print.
"""
if not self.files:
return
x_min, y_min, x_max, y_max = (
min(x for x, _ in blocks),
0,
max(x for x, _ in blocks),
max(y for _, y in blocks),
)
self.files.create(
f"blocks_{name}.txt",
"\n".join(
"".join(
str(blocks.get((x, y), Cell.AIR)) for x in range(x_min, x_max + 1)
)
for y in range(y_min, y_max + 1)
).encode(),
True,
)
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
paths: list[list[tuple[int, int]]] = []
for line in lines:
paths: list[list[tuple[int, int]]] = []
for line in lines:
parts = line.split(" -> ")
paths.append(
[
cast(
tuple[int, int], tuple(int(c.strip()) for c in part.split(","))
)
cast(tuple[int, int], tuple(int(c.strip()) for c in part.split(",")))
for part in parts
]
)
blocks: dict[tuple[int, int], Cell] = {}
for path in paths:
blocks: dict[tuple[int, int], Cell] = {}
for path in paths:
for start, end in zip(path[:-1], path[1:]):
x_start = min(start[0], end[0])
x_end = max(start[0], end[0]) + 1
@ -122,25 +109,32 @@ class Solver(BaseSolver):
for y in range(y_start, y_end):
blocks[x, y] = Cell.ROCK
self.print_blocks("start", blocks)
print_blocks(blocks)
print()
y_max = max(y for _, y in blocks)
x_min, y_min, x_max, y_max = (
min(x for x, _ in blocks),
0,
max(x for x, _ in blocks),
max(y for _, y in blocks),
)
# === part 1 ===
# === part 1 ===
blocks_1 = flow(
blocks_1 = flow(
blocks.copy(), stop_fn=lambda x, y: y > y_max, fill_fn=lambda x, y: Cell.AIR
)
self.print_blocks("part1", blocks_1)
yield sum(v == Cell.SAND for v in blocks_1.values())
)
print_blocks(blocks_1)
print(f"answer 1 is {sum(v == Cell.SAND for v in blocks_1.values())}")
print()
# === part 2 ===
# === part 2 ===
blocks_2 = flow(
blocks_2 = flow(
blocks.copy(),
stop_fn=lambda x, y: x == 500 and y == 0,
fill_fn=lambda x, y: Cell.AIR if y < y_max + 2 else Cell.ROCK,
)
blocks_2[500, 0] = Cell.SAND
self.print_blocks("part2", blocks_2)
yield sum(v == Cell.SAND for v in blocks_2.values())
)
blocks_2[500, 0] = Cell.SAND
print_blocks(blocks_2)
print(f"answer 2 is {sum(v == Cell.SAND for v in blocks_2.values())}")

View File

@ -1,17 +1,12 @@
import itertools as it
from typing import Any, Iterator
import sys
from typing import Any
import numpy as np
import parse # type: ignore
from numpy.typing import NDArray
from ..base import BaseSolver
class Solver(BaseSolver):
def part1(
self, sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], row: int
) -> int:
def part1(sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], row: int) -> int:
no_beacons_row_l: list[NDArray[np.floating[Any]]] = []
for (sx, sy), (bx, by) in sensor_to_beacon.items():
@ -21,14 +16,17 @@ class Solver(BaseSolver):
no_beacons_row_l.append(sx + np.arange(0, d - abs(sy - row) + 1)) # type: ignore
beacons_at_row = set(bx for (bx, by) in sensor_to_beacon.values() if by == row)
no_beacons_row = set(it.chain(*no_beacons_row_l)).difference(beacons_at_row) # type: ignore
no_beacons_row = set(np.concatenate(no_beacons_row_l)).difference(beacons_at_row) # type: ignore
return len(no_beacons_row)
def part2_intervals(
self, sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
) -> tuple[int, int, int]:
for y in self.progress.wrap(range(xy_max + 1)):
def part2_intervals(
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
) -> tuple[int, int, int]:
from tqdm import trange
for y in trange(xy_max + 1):
its: list[tuple[int, int]] = []
for (sx, sy), (bx, by) in sensor_to_beacon.items():
d = abs(sx - bx) + abs(sy - by)
@ -48,9 +46,10 @@ class Solver(BaseSolver):
return (0, 0, 0)
def part2_cplex(
self, sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
) -> tuple[int, int, int]:
def part2_cplex(
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]], xy_max: int
) -> tuple[int, int, int]:
from docplex.mp.model import Model
m = Model()
@ -59,10 +58,7 @@ class Solver(BaseSolver):
for (sx, sy), (bx, by) in sensor_to_beacon.items():
d = abs(sx - bx) + abs(sy - by)
m.add_constraint(
m.abs(x - sx) + m.abs(y - sy) >= d + 1, # type: ignore
ctname=f"ct_{sx}_{sy}",
)
m.add_constraint(m.abs(x - sx) + m.abs(y - sy) >= d + 1, ctname=f"ct_{sx}_{sy}") # type: ignore
m.set_objective("min", x + y)
@ -73,23 +69,22 @@ class Solver(BaseSolver):
vy = int(s.get_value(y))
return vx, vy, 4_000_000 * vx + vy
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]] = {}
lines = sys.stdin.read().splitlines()
for line in lines:
sensor_to_beacon: dict[tuple[int, int], tuple[int, int]] = {}
for line in lines:
r: dict[str, str] = parse.parse( # type: ignore
"Sensor at x={sx}, y={sy}: closest beacon is at x={bx}, y={by}", line
)
sensor_to_beacon[int(r["sx"]), int(r["sy"])] = (int(r["bx"]), int(r["by"]))
xy_max = 4_000_000 if max(sensor_to_beacon) > (1_000, 0) else 20
row = 2_000_000 if max(sensor_to_beacon) > (1_000, 0) else 10
xy_max = 4_000_000 if max(sensor_to_beacon) > (1_000, 0) else 20
row = 2_000_000 if max(sensor_to_beacon) > (1_000, 0) else 10
yield self.part1(sensor_to_beacon, row)
print(f"answer 1 is {part1(sensor_to_beacon, row)}")
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
x, y, a2 = self.part2_intervals(sensor_to_beacon, xy_max)
self.logger.info(f"answer 2 is {a2} (x={x}, y={y})")
yield a2
# x, y, a2 = part2_cplex(sensor_to_beacon, xy_max)
x, y, a2 = part2_intervals(sensor_to_beacon, xy_max)
print(f"answer 2 is {a2} (x={x}, y={y})")

View File

@ -3,10 +3,11 @@ from __future__ import annotations
import heapq
import itertools
import re
import sys
from collections import defaultdict
from typing import Any, FrozenSet, Iterator, NamedTuple
from typing import FrozenSet, NamedTuple
from ..base import BaseSolver
from tqdm import tqdm
class Pipe(NamedTuple):
@ -35,8 +36,8 @@ def breadth_first_search(pipes: dict[str, Pipe], pipe: Pipe) -> dict[Pipe, int]:
Runs a BFS from the given pipe and return the shortest distance (in term of hops)
to all other pipes.
"""
queue = [(0, pipe)]
visited: set[Pipe] = set()
queue = [(0, pipe_1)]
visited = set()
distances: dict[Pipe, int] = {}
while len(distances) < len(pipes):
@ -60,17 +61,12 @@ def update_with_better(
node_at_times[flowing] = max(node_at_times[flowing], flow)
# === MAIN ===
class Solver(BaseSolver):
def part_1(
self,
def part_1(
start_pipe: Pipe,
max_time: int,
distances: dict[tuple[Pipe, Pipe], int],
relevant_pipes: FrozenSet[Pipe],
):
):
node_at_times: dict[int, dict[Pipe, dict[FrozenSet[Pipe], int]]] = defaultdict(
lambda: defaultdict(lambda: defaultdict(lambda: 0))
)
@ -102,17 +98,15 @@ class Solver(BaseSolver):
for flow in nodes_of_pipe.values()
)
def part_2(
self,
def part_2(
start_pipe: Pipe,
max_time: int,
distances: dict[tuple[Pipe, Pipe], int],
relevant_pipes: FrozenSet[Pipe],
):
):
def compute(pipes_for_me: FrozenSet[Pipe]) -> int:
return self.part_1(
start_pipe, max_time, distances, pipes_for_me
) + self.part_1(
return part_1(start_pipe, max_time, distances, pipes_for_me) + part_1(
start_pipe, max_time, distances, relevant_pipes - pipes_for_me
)
@ -122,13 +116,17 @@ class Solver(BaseSolver):
for relevant_pipes_1 in itertools.combinations(relevant_pipes, r)
]
return max(compute(comb) for comb in self.progress.wrap(combs))
return max(compute(comb) for comb in tqdm(combs))
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
pipes: dict[str, Pipe] = {}
for line in lines:
# === MAIN ===
lines = sys.stdin.read().splitlines()
pipes: dict[str, Pipe] = {}
for line in lines:
r = re.match(
R"Valve ([A-Z]+) has flow rate=([0-9]+); tunnels? leads? to valves? (.+)",
line,
@ -139,9 +137,9 @@ class Solver(BaseSolver):
pipes[g[0]] = Pipe(g[0], int(g[1]), g[2].split(", "))
# compute distances from one valve to any other
distances: dict[tuple[Pipe, Pipe], int] = {}
for pipe_1 in pipes.values():
# compute distances from one valve to any other
distances: dict[tuple[Pipe, Pipe], int] = {}
for pipe_1 in pipes.values():
distances.update(
{
(pipe_1, pipe_2): distance
@ -149,11 +147,12 @@ class Solver(BaseSolver):
}
)
# valves with flow
relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0)
# valves with flow
relevant_pipes = frozenset(pipe for pipe in pipes.values() if pipe.flow > 0)
# 1651, 1653
yield self.part_1(pipes["AA"], 30, distances, relevant_pipes)
# 1707, 2223
yield self.part_2(pipes["AA"], 26, distances, relevant_pipes)
# 1651, 1653
print(part_1(pipes["AA"], 30, distances, relevant_pipes))
# 1707, 2223
print(part_2(pipes["AA"], 26, distances, relevant_pipes))

View File

@ -1,16 +1,23 @@
from typing import Any, Iterator, Sequence, TypeAlias, TypeVar
import sys
from typing import Sequence, TypeVar
import numpy as np
from numpy.typing import NDArray
from ..base import BaseSolver
T = TypeVar("T")
Tower: TypeAlias = NDArray[np.bool]
def print_tower(tower: np.ndarray, out: str = "#"):
print("-" * (tower.shape[1] + 2))
non_empty = False
for row in reversed(range(1, tower.shape[0])):
if not non_empty and not tower[row, :].any():
continue
non_empty = True
print("|" + "".join(out if c else "." for c in tower[row, :]) + "|")
print("+" + "-" * tower.shape[1] + "+")
def tower_height(tower: Tower) -> int:
def tower_height(tower: np.ndarray) -> int:
return int(tower.shape[0] - tower[::-1, :].argmax(axis=0).min() - 1)
@ -38,8 +45,8 @@ def build_tower(
n_rocks: int,
jets: str,
early_stop: bool = False,
init: Tower = np.ones(WIDTH, dtype=bool),
) -> tuple[Tower, int, int, dict[int, int]]:
init: np.ndarray = np.ones(WIDTH, dtype=bool),
) -> tuple[np.ndarray, int, int, dict[int, int]]:
tower = EMPTY_BLOCKS.copy()
tower[0, :] = init
@ -88,24 +95,26 @@ def build_tower(
return tower, rock_count, done_at.get((i_rock, i_jet), -1), heights
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
tower, *_ = build_tower(2022, input)
yield tower_height(tower)
line = sys.stdin.read().strip()
TOTAL_ROCKS = 1_000_000_000_000
_tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, input, True)
assert prev_1 > 0
tower, *_ = build_tower(2022, line)
answer_1 = tower_height(tower)
print(f"answer 1 is {answer_1}")
# 2767 1513
remaining_rocks = TOTAL_ROCKS - n_rocks_1
n_repeat_rocks = n_rocks_1 - prev_1
n_repeat_towers = remaining_rocks // n_repeat_rocks
TOTAL_ROCKS = 1_000_000_000_000
tower_1, n_rocks_1, prev_1, heights_1 = build_tower(TOTAL_ROCKS, line, True)
assert prev_1 > 0
base_height = heights_1[prev_1]
repeat_height = heights_1[prev_1 + n_repeat_rocks - 1] - heights_1[prev_1]
remaining_height = (
# 2767 1513
remaining_rocks = TOTAL_ROCKS - n_rocks_1
n_repeat_rocks = n_rocks_1 - prev_1
n_repeat_towers = remaining_rocks // n_repeat_rocks
base_height = heights_1[prev_1]
repeat_height = heights_1[prev_1 + n_repeat_rocks - 1] - heights_1[prev_1]
remaining_height = (
heights_1[prev_1 + remaining_rocks % n_repeat_rocks] - heights_1[prev_1]
)
)
yield base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
answer_2 = base_height + (n_repeat_towers + 1) * repeat_height + remaining_height
print(f"answer 2 is {answer_2}")

View File

@ -1,38 +1,33 @@
from typing import Any, Iterator
import sys
import numpy as np
from ..base import BaseSolver
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
xyz = np.asarray(
xyz = np.asarray(
[
tuple(int(x) for x in row.split(",")) # type: ignore
for row in input.splitlines()
for row in sys.stdin.read().splitlines()
]
)
)
xyz = xyz - xyz.min(axis=0) + 1
xyz = xyz - xyz.min(axis=0) + 1
cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool)
cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True
cubes = np.zeros(xyz.max(axis=0) + 3, dtype=bool)
cubes[xyz[:, 0], xyz[:, 1], xyz[:, 2]] = True
faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)]
n_dims = len(cubes.shape)
yield sum(
1
for x, y, z in xyz
for dx, dy, dz in faces
if not cubes[x + dx, y + dy, z + dz]
)
faces = [(-1, 0, 0), (1, 0, 0), (0, -1, 0), (0, 1, 0), (0, 0, -1), (0, 0, 1)]
visited = np.zeros_like(cubes, dtype=bool)
queue = [(0, 0, 0)]
answer_1 = sum(
1 for x, y, z in xyz for dx, dy, dz in faces if not cubes[x + dx, y + dy, z + dz]
)
print(f"answer 1 is {answer_1}")
n_faces = 0
while queue:
visited = np.zeros_like(cubes, dtype=bool)
queue = [(0, 0, 0)]
n_faces = 0
while queue:
x, y, z = queue.pop(0)
if visited[x, y, z]:
@ -42,9 +37,7 @@ class Solver(BaseSolver):
for dx, dy, dz in faces:
nx, ny, nz = x + dx, y + dy, z + dz
if not all(
n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))
):
if not all(n >= 0 and n < cubes.shape[i] for i, n in enumerate((nx, ny, nz))):
continue
if visited[nx, ny, nz]:
@ -54,5 +47,4 @@ class Solver(BaseSolver):
n_faces += 1
else:
queue.append((nx, ny, nz))
yield n_faces
print(f"answer 2 is {n_faces}")

View File

@ -1,11 +1,10 @@
from typing import Any, Iterator, Literal
import sys
from typing import Any, Literal
import numpy as np
import parse # pyright: ignore[reportMissingTypeStubs]
from numpy.typing import NDArray
from ..base import BaseSolver
Reagent = Literal["ore", "clay", "obsidian", "geode"]
REAGENTS: tuple[Reagent, ...] = (
"ore",
@ -63,6 +62,29 @@ def dominates(lhs: State, rhs: State):
)
lines = sys.stdin.read().splitlines()
blueprints: list[dict[Reagent, IntOfReagent]] = []
for line in lines:
r: list[int] = parse.parse( # type: ignore
"Blueprint {}: "
"Each ore robot costs {:d} ore. "
"Each clay robot costs {:d} ore. "
"Each obsidian robot costs {:d} ore and {:d} clay. "
"Each geode robot costs {:d} ore and {:d} obsidian.",
line,
)
blueprints.append(
{
"ore": {"ore": r[1]},
"clay": {"ore": r[2]},
"obsidian": {"ore": r[3], "clay": r[4]},
"geode": {"ore": r[5], "obsidian": r[6]},
}
)
def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
# since we can only build one robot per time, we do not need more than X robots
# of type K where X is the maximum number of K required among all robots, e.g.,
@ -151,31 +173,11 @@ def run(blueprint: dict[Reagent, dict[Reagent, int]], max_time: int) -> int:
return max(state.reagents["geode"] for state in state_after_t[max_time])
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
blueprints: list[dict[Reagent, IntOfReagent]] = []
for line in input.splitlines():
r: list[int] = parse.parse( # type: ignore
"Blueprint {}: "
"Each ore robot costs {:d} ore. "
"Each clay robot costs {:d} ore. "
"Each obsidian robot costs {:d} ore and {:d} clay. "
"Each geode robot costs {:d} ore and {:d} obsidian.",
line,
)
blueprints.append(
{
"ore": {"ore": r[1]},
"clay": {"ore": r[2]},
"obsidian": {"ore": r[3], "clay": r[4]},
"geode": {"ore": r[5], "obsidian": r[6]},
}
)
yield sum(
answer_1 = sum(
(i_blueprint + 1) * run(blueprint, 24)
for i_blueprint, blueprint in enumerate(blueprints)
)
)
print(f"answer 1 is {answer_1}")
yield (run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32))
answer_2 = run(blueprints[0], 32) * run(blueprints[1], 32) * run(blueprints[2], 32)
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,4 @@
from typing import Any, Iterator
from ..base import BaseSolver
import sys
def score_1(ux: int, vx: int) -> int:
@ -35,23 +33,21 @@ def score_2(ux: int, vx: int) -> int:
return (ux + vx - 1) % 3 + 1 + vx * 3
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.readlines()
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
# modulo-3 arithmetic
#
# in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move
# if opponent plays 0 is 1, or 0 if opponent plays 2 (0 = (2 + 1 % 3)))
#
# the solution relies on replacing rock / paper / scissor by values 0 / 1 / 2 and using
# modulo-3 arithmetic
#
# in modulo-3 arithmetic, the winning move is 1 + the opponent move (e.g., winning move
# if opponent plays 0 is 1, or 0 if opponent plays 2 (0 = (2 + 1 % 3)))
#
# we read the lines in a Nx2 in array with value 0/1/2 instead of A/B/C or X/Y/Z for
# easier manipulation
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
# we read the lines in a Nx2 in array with value 0/1/2 instead of A/B/C or X/Y/Z for
# easier manipulation
values = [(ord(row[0]) - ord("A"), ord(row[2]) - ord("X")) for row in lines]
# part 1 - 13526
yield sum(score_1(*v) for v in values)
# part 1 - 13526
print(f"answer 1 is {sum(score_1(*v) for v in values)}")
# part 2 - 14204
yield sum(score_2(*v) for v in values)
# part 2 - 14204
print(f"answer 2 is {sum(score_2(*v) for v in values)}")

View File

@ -1,8 +1,6 @@
from __future__ import annotations
from typing import Any, Iterator
from ..base import BaseSolver
import sys
class Number:
@ -67,9 +65,10 @@ def decrypt(numbers: list[Number], key: int, rounds: int) -> int:
)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
numbers = [Number(int(x)) for x in input.splitlines()]
numbers = [Number(int(x)) for i, x in enumerate(sys.stdin.readlines())]
yield decrypt(numbers, 1, 1)
yield decrypt(numbers, 811589153, 10)
answer_1 = decrypt(numbers, 1, 1)
print(f"answer 1 is {answer_1}")
answer_2 = decrypt(numbers, 811589153, 10)
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,6 @@
import operator
from typing import Any, Callable, Iterator
from ..base import BaseSolver
import sys
from typing import Callable
def compute(monkeys: dict[str, int | tuple[str, str, str]], monkey: str) -> int:
@ -78,15 +77,13 @@ def invert(
return monkeys
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
lines = sys.stdin.read().splitlines()
monkeys: dict[str, int | tuple[str, str, str]] = {}
monkeys: dict[str, int | tuple[str, str, str]] = {}
op_monkeys: set[str] = set()
op_monkeys: set[str] = set()
for line in lines:
for line in lines:
parts = line.split(":")
name = parts[0].strip()
@ -99,10 +96,12 @@ class Solver(BaseSolver):
op_monkeys.add(name)
yield compute(monkeys.copy(), "root")
# assume the second operand of 'root' can be computed, and the first one depends on
# humn, which is the case is my input and the test input
assert isinstance(monkeys["root"], tuple)
p1, _, p2 = monkeys["root"] # type: ignore
yield compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")
answer_1 = compute(monkeys.copy(), "root")
print(f"answer 1 is {answer_1}")
# assume the second operand of 'root' can be computed, and the first one depends on
# humn, which is the case is my input and the test input
p1, _, p2 = monkeys["root"] # type: ignore
answer_2 = compute(invert(monkeys, "humn", compute(monkeys.copy(), p2)), "humn")
print(f"answer 2 is {answer_2}")

View File

@ -1,57 +1,51 @@
import re
from typing import Any, Callable, Iterator
import sys
from typing import Callable
import numpy as np
from ..base import BaseSolver
VOID, EMPTY, WALL = 0, 1, 2
TILE_FROM_CHAR = {" ": VOID, ".": EMPTY, "#": WALL}
SCORES = {"E": 0, "S": 1, "W": 2, "N": 3}
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
board_map_s, direction_s = input.split("\n\n")
board_map_s, direction_s = sys.stdin.read().split("\n\n")
# board
board_lines = board_map_s.splitlines()
max_line = max(len(line) for line in board_lines)
board = np.array(
# board
board_lines = board_map_s.splitlines()
max_line = max(len(line) for line in board_lines)
board = np.array(
[
[TILE_FROM_CHAR[c] for c in row] + [VOID] * (max_line - len(row))
for row in board_map_s.splitlines()
]
)
)
directions = [
int(p1) if p2 else p1
for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s)
]
directions = [
int(p1) if p2 else p1 for p1, p2 in re.findall(R"(([0-9])+|L|R)", direction_s)
]
# find on each row and column the first and last non-void
row_first_non_void = np.argmax(board != VOID, axis=1)
row_last_non_void = (
board.shape[1] - np.argmax(board[:, ::-1] != VOID, axis=1) - 1
)
col_first_non_void = np.argmax(board != VOID, axis=0)
col_last_non_void = (
board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1
)
faces = np.zeros_like(board)
size = np.gcd(board.shape[0], board.shape[1])
for row in range(0, board.shape[0], size):
# find on each row and column the first and last non-void
row_first_non_void = np.argmax(board != VOID, axis=1)
row_last_non_void = board.shape[1] - np.argmax(board[:, ::-1] != VOID, axis=1) - 1
col_first_non_void = np.argmax(board != VOID, axis=0)
col_last_non_void = board.shape[0] - np.argmax(board[::-1, :] != VOID, axis=0) - 1
faces = np.zeros_like(board)
size = np.gcd(board.shape[0], board.shape[1])
for row in range(0, board.shape[0], size):
for col in range(row_first_non_void[row], row_last_non_void[row], size):
faces[row : row + size, col : col + size] = faces.max() + 1
SIZE = np.gcd(*board.shape)
SIZE = np.gcd(*board.shape)
# TODO: deduce this from the actual cube...
faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]]
# TODO: deduce this from the actual cube...
faces_wrap: dict[int, dict[str, Callable[[int, int], tuple[int, int, str]]]]
if board.shape == (12, 16): # example
if board.shape == (12, 16): # example
faces_wrap = {
1: {
"W": lambda y, x: (4, 4 + y, "S"), # 3N
@ -79,7 +73,7 @@ class Solver(BaseSolver):
},
}
else:
else:
faces_wrap = {
1: {
"W": lambda y, x: (3 * SIZE - y - 1, 0, "E"), # 4W
@ -109,7 +103,8 @@ class Solver(BaseSolver):
},
}
def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
def wrap_part_1(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
if r0 == "E":
return y0, row_first_non_void[y0], r0
elif r0 == "S":
@ -121,14 +116,14 @@ class Solver(BaseSolver):
assert False
def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
def wrap_part_2(y0: int, x0: int, r0: str) -> tuple[int, int, str]:
cube = faces[y0, x0]
assert r0 in faces_wrap[cube]
return faces_wrap[cube][r0](y0, x0)
def run(
wrap: Callable[[int, int, str], tuple[int, int, str]],
) -> tuple[int, int, str]:
def run(wrap: Callable[[int, int, str], tuple[int, int, str]]) -> tuple[int, int, str]:
y0 = 0
x0 = np.where(board[0] == EMPTY)[0][0]
r0 = "E"
@ -137,9 +132,7 @@ class Solver(BaseSolver):
if isinstance(direction, int):
while direction > 0:
if r0 == "E":
xi = np.where(
board[y0, x0 + 1 : x0 + direction + 1] == WALL
)[0]
xi = np.where(board[y0, x0 + 1 : x0 + direction + 1] == WALL)[0]
if len(xi):
x0 = x0 + xi[0]
direction = 0
@ -155,14 +148,10 @@ class Solver(BaseSolver):
x0 = row_last_non_void[y0]
direction = 0
else:
direction = (
direction - (row_last_non_void[y0] - x0) - 1
)
direction = direction - (row_last_non_void[y0] - x0) - 1
y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "S":
yi = np.where(
board[y0 + 1 : y0 + direction + 1, x0] == WALL
)[0]
yi = np.where(board[y0 + 1 : y0 + direction + 1, x0] == WALL)[0]
if len(yi):
y0 = y0 + yi[0]
direction = 0
@ -178,9 +167,7 @@ class Solver(BaseSolver):
y0 = col_last_non_void[x0]
direction = 0
else:
direction = (
direction - (col_last_non_void[x0] - y0) - 1
)
direction = direction - (col_last_non_void[x0] - y0) - 1
y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "W":
left = max(x0 - direction - 1, 0)
@ -188,10 +175,7 @@ class Solver(BaseSolver):
if len(xi):
x0 = left + xi[-1] + 1
direction = 0
elif (
x0 - direction >= 0
and board[y0, x0 - direction] == EMPTY
):
elif x0 - direction >= 0 and board[y0, x0 - direction] == EMPTY:
x0 = x0 - direction
direction = 0
else:
@ -200,9 +184,7 @@ class Solver(BaseSolver):
x0 = row_first_non_void[y0]
direction = 0
else:
direction = (
direction - (x0 - row_first_non_void[y0]) - 1
)
direction = direction - (x0 - row_first_non_void[y0]) - 1
y0, x0, r0 = y0_t, x0_t, r0_t
elif r0 == "N":
top = max(y0 - direction - 1, 0)
@ -210,10 +192,7 @@ class Solver(BaseSolver):
if len(yi):
y0 = top + yi[-1] + 1
direction = 0
elif (
y0 - direction >= 0
and board[y0 - direction, x0] == EMPTY
):
elif y0 - direction >= 0 and board[y0 - direction, x0] == EMPTY:
y0 = y0 - direction
direction = 0
else:
@ -222,9 +201,7 @@ class Solver(BaseSolver):
y0 = col_first_non_void[x0]
direction = 0
else:
direction = (
direction - (y0 - col_first_non_void[x0]) - 1
)
direction = direction - (y0 - col_first_non_void[x0]) - 1
y0, x0, r0 = y0_t, x0_t, r0_t
else:
r0 = {
@ -236,8 +213,11 @@ class Solver(BaseSolver):
return y0, x0, r0
y1, x1, r1 = run(wrap_part_1)
yield 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
y2, x2, r2 = run(wrap_part_2)
yield 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]
y1, x1, r1 = run(wrap_part_1)
answer_1 = 1000 * (1 + y1) + 4 * (1 + x1) + SCORES[r1]
print(f"answer 1 is {answer_1}")
y2, x2, r2 = run(wrap_part_2)
answer_2 = 1000 * (1 + y2) + 4 * (1 + x2) + SCORES[r2]
print(f"answer 2 is {answer_2}")

View File

@ -1,8 +1,6 @@
import itertools
import sys
from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
Directions = list[
tuple[
@ -20,10 +18,22 @@ DIRECTIONS: Directions = [
def min_max_yx(positions: set[tuple[int, int]]) -> tuple[int, int, int, int]:
ys, xs = {y for y, _x in positions}, {x for _y, x in positions}
ys, xs = {y for y, x in positions}, {x for y, x in positions}
return min(ys), min(xs), max(ys), max(xs)
def print_positions(positions: set[tuple[int, int]]):
min_y, min_x, max_y, max_x = min_max_yx(positions)
print(
"\n".join(
"".join(
"#" if (y, x) in positions else "." for x in range(min_x - 1, max_x + 2)
)
for y in range(min_y - 1, max_y + 2)
)
)
def round(
positions: set[tuple[int, int]],
directions: Directions,
@ -59,33 +69,30 @@ def round(
directions.append(directions.pop(0))
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
POSITIONS = {
POSITIONS = {
(i, j)
for i, row in enumerate(input.splitlines())
for i, row in enumerate(sys.stdin.read().splitlines())
for j, col in enumerate(row)
if col == "#"
}
}
# === part 1 ===
# === part 1 ===
p1, d1 = POSITIONS.copy(), DIRECTIONS.copy()
for _ in range(10):
p1, d1 = POSITIONS.copy(), DIRECTIONS.copy()
for r in range(10):
round(p1, d1)
min_y, min_x, max_y, max_x = min_max_yx(p1)
yield sum(
(y, x) not in p1
for y in range(min_y, max_y + 1)
for x in range(min_x, max_x + 1)
)
min_y, min_x, max_y, max_x = min_max_yx(p1)
answer_1 = sum(
(y, x) not in p1 for y in range(min_y, max_y + 1) for x in range(min_x, max_x + 1)
)
print(f"answer 1 is {answer_1}")
# === part 2 ===
# === part 2 ===
p2, d2 = POSITIONS.copy(), DIRECTIONS.copy()
answer_2 = 0
while True:
p2, d2 = POSITIONS.copy(), DIRECTIONS.copy()
answer_2 = 0
while True:
answer_2 += 1
backup = p2.copy()
round(p2, d2)
@ -93,4 +100,4 @@ class Solver(BaseSolver):
if backup == p2:
break
yield answer_2
print(f"answer 2 is {answer_2}")

View File

@ -1,46 +1,36 @@
import heapq
import math
import sys
from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
winds = {
winds = {
(i - 1, j - 1, lines[i][j])
for i in range(1, len(lines) - 1)
for j in range(1, len(lines[i]) - 1)
if lines[i][j] != "."
}
}
n_rows, n_cols = len(lines) - 2, len(lines[0]) - 2
CYCLE = math.lcm(n_rows, n_cols)
n_rows, n_cols = len(lines) - 2, len(lines[0]) - 2
CYCLE = math.lcm(n_rows, n_cols)
east_winds = [
{j for j in range(n_cols) if (i, j, ">") in winds} for i in range(n_rows)
]
west_winds = [
{j for j in range(n_cols) if (i, j, "<") in winds} for i in range(n_rows)
]
north_winds = [
east_winds = [{j for j in range(n_cols) if (i, j, ">") in winds} for i in range(n_rows)]
west_winds = [{j for j in range(n_cols) if (i, j, "<") in winds} for i in range(n_rows)]
north_winds = [
{i for i in range(n_rows) if (i, j, "^") in winds} for j in range(n_cols)
]
south_winds = [
]
south_winds = [
{i for i in range(n_rows) if (i, j, "v") in winds} for j in range(n_cols)
]
]
def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
def run(start: tuple[int, int], start_cycle: int, end: tuple[int, int]):
def heuristic(y: int, x: int) -> int:
return abs(end[0] - y) + abs(end[1] - x)
# (distance + heuristic, distance, (start_pos, cycle))
queue = [
(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))
]
queue = [(heuristic(start[0], start[1]), 0, ((start[0], start[1]), start_cycle))]
visited: set[tuple[tuple[int, int], int]] = set()
distances: dict[tuple[int, int], dict[int, int]] = defaultdict(lambda: {})
@ -64,17 +54,13 @@ class Solver(BaseSolver):
n_cycle = (cycle + 1) % CYCLE
if (ty, tx) == end:
heapq.heappush(
queue, (distance + 1, distance + 1, ((ty, tx), n_cycle))
)
heapq.heappush(queue, (distance + 1, distance + 1, ((ty, tx), n_cycle)))
break
if ((ty, tx), n_cycle) in visited:
continue
if (ty, tx) != start and (
ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols
):
if (ty, tx) != start and (ty < 0 or tx < 0 or ty >= n_rows or tx >= n_cols):
continue
if (ty, tx) != start:
@ -89,29 +75,24 @@ class Solver(BaseSolver):
heapq.heappush(
queue,
(
(
heuristic(ty, tx) + distance + 1,
distance + 1,
((ty, tx), n_cycle),
)
),
((heuristic(ty, tx) + distance + 1, distance + 1, ((ty, tx), n_cycle))),
)
return distances, next(iter(distances[end].values()))
start = (
start = (
-1,
next(j for j in range(1, len(lines[0]) - 1) if lines[0][j] == ".") - 1,
)
end = (
)
end = (
n_rows,
next(j for j in range(1, len(lines[-1]) - 1) if lines[-1][j] == ".") - 1,
)
)
distances_1, forward_1 = run(start, 0, end)
yield forward_1
distances_1, forward_1 = run(start, 0, end)
print(f"answer 1 is {forward_1}")
distances_2, return_1 = run(end, next(iter(distances_1[end].keys())), start)
_distances_3, forward_2 = run(start, next(iter(distances_2[start].keys())), end)
yield forward_1 + return_1 + forward_2
distances_2, return_1 = run(end, next(iter(distances_1[end].keys())), start)
distances_3, forward_2 = run(start, next(iter(distances_2[start].keys())), end)
print(f"answer 2 is {forward_1 + return_1 + forward_2}")

View File

@ -1,22 +1,19 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
coeffs = {"2": 2, "1": 1, "0": 0, "-": -1, "=": -2}
def snafu2number(number: str) -> int:
def snafu2number(number: str) -> int:
value = 0
for c in number:
value *= 5
value += coeffs[c]
return value
def number2snafu(number: int) -> str:
def number2snafu(number: int) -> str:
values = ["0", "1", "2", "=", "-"]
res = ""
while number > 0:
@ -25,4 +22,6 @@ class Solver(BaseSolver):
number = number // 5 + int(mod >= 3)
return "".join(reversed(res))
yield number2snafu(sum(map(snafu2number, lines)))
answer_1 = number2snafu(sum(map(snafu2number, lines)))
print(f"answer 1 is {answer_1}")

View File

@ -1,28 +1,23 @@
import string
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = [line.strip() for line in sys.stdin.readlines()]
# extract content of each part
parts = [(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
# priorities
priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)}
# extract content of each part
parts = [
(set(line[: len(line) // 2]), set(line[len(line) // 2 :])) for line in lines
]
# part 1
part1 = sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2))
print(f"answer 1 is {part1}")
# priorities
priorities = {c: i + 1 for i, c in enumerate(string.ascii_letters)}
# part 1
yield sum(priorities[c] for p1, p2 in parts for c in p1.intersection(p2))
# part 2
n_per_group = 3
yield sum(
# part 2
n_per_group = 3
part2 = sum(
priorities[c]
for i in range(0, len(lines), n_per_group)
for c in set(lines[i]).intersection(*lines[i + 1 : i + n_per_group])
)
)
print(f"answer 2 is {part2}")

View File

@ -1,6 +1,6 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = [line.strip() for line in sys.stdin.readlines()]
def make_range(value: str) -> set[int]:
@ -8,13 +8,10 @@ def make_range(value: str) -> set[int]:
return set(range(int(parts[0]), int(parts[1]) + 1))
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
sections = [tuple(make_range(part) for part in line.split(",")) for line in lines]
sections = [
tuple(make_range(part) for part in line.split(",")) for line in lines
]
answer_1 = sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
print(f"answer 1 is {answer_1}")
yield sum(s1.issubset(s2) or s2.issubset(s1) for s1, s2 in sections)
yield sum(bool(s1.intersection(s2)) for s1, s2 in sections)
answer_2 = sum(bool(s1.intersection(s2)) for s1, s2 in sections)
print(f"answer 1 is {answer_2}")

View File

@ -1,43 +1,41 @@
import copy
from typing import Any, Iterator
import sys
from ..base import BaseSolver
blocks_s, moves_s = (part.splitlines() for part in sys.stdin.read().split("\n\n"))
blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()}
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
blocks_s, moves_s = (part.splitlines() for part in input.split("\n\n"))
blocks: dict[str, list[str]] = {stack: [] for stack in blocks_s[-1].split()}
# this codes assumes that the lines are regular, i.e., 4 characters per "crate" in the
# form of '[X] ' (including the trailing space)
#
for block in blocks_s[-2::-1]:
# this codes assumes that the lines are regular, i.e., 4 characters per "crate" in the
# form of '[X] ' (including the trailing space)
#
for block in blocks_s[-2::-1]:
for stack, index in zip(blocks, range(0, len(block), 4)):
crate = block[index + 1 : index + 2].strip()
if crate:
blocks[stack].append(crate)
# part 1 - deep copy for part 2
blocks_1 = copy.deepcopy(blocks)
# part 1 - deep copy for part 2
blocks_1 = copy.deepcopy(blocks)
for move in moves_s:
for move in moves_s:
_, count_s, _, from_, _, to_ = move.strip().split()
for _i in range(int(count_s)):
blocks_1[to_].append(blocks_1[from_].pop())
# part 2
blocks_2 = copy.deepcopy(blocks)
# part 2
blocks_2 = copy.deepcopy(blocks)
for move in moves_s:
for move in moves_s:
_, count_s, _, from_, _, to_ = move.strip().split()
count = int(count_s)
blocks_2[to_].extend(blocks_2[from_][-count:])
del blocks_2[from_][-count:]
yield "".join(s[-1] for s in blocks_1.values())
yield "".join(s[-1] for s in blocks_2.values())
answer_1 = "".join(s[-1] for s in blocks_1.values())
print(f"answer 1 is {answer_1}")
answer_2 = "".join(s[-1] for s in blocks_2.values())
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,4 @@
from typing import Any, Iterator
from ..base import BaseSolver
import sys
def index_of_first_n_differents(data: str, n: int) -> int:
@ -10,7 +8,8 @@ def index_of_first_n_differents(data: str, n: int) -> int:
return -1
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
yield index_of_first_n_differents(input, 4)
yield index_of_first_n_differents(input, 14)
data = sys.stdin.read().strip()
print(f"answer 1 is {index_of_first_n_differents(data, 4)}")
print(f"answer 2 is {index_of_first_n_differents(data, 14)}")

View File

@ -1,35 +1,30 @@
import sys
from pathlib import Path
from typing import Any, Iterator
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
# we are going to use Path to create path and go up/down in the file tree since it
# implements everything we need
#
# we can use .resolve() to get normalized path, although this will add C:\ to all paths
# on Windows but that is not an issue since only the sizes matter
#
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
# mapping from path to list of files or directories
trees: dict[Path, list[Path]] = {}
# we are going to use Path to create path and go up/down in the file tree since it
# implements everything we need
#
# we can use .resolve() to get normalized path, although this will add C:\ to all paths
# on Windows but that is not an issue since only the sizes matter
#
# mapping from paths to either size (for file) or -1 for directory
sizes: dict[Path, int] = {}
# mapping from path to list of files or directories
trees: dict[Path, list[Path]] = {}
# first line must be a cd otherwise we have no idea where we are
assert lines[0].startswith("$ cd")
base_path = Path(lines[0].strip("$").split()[1]).resolve()
cur_path = base_path
# mapping from paths to either size (for file) or -1 for directory
sizes: dict[Path, int] = {}
trees[cur_path] = []
sizes[cur_path] = -1
# first line must be a cd otherwise we have no idea where we are
assert lines[0].startswith("$ cd")
base_path = Path(lines[0].strip("$").split()[1]).resolve()
cur_path = base_path
trees[cur_path] = []
sizes[cur_path] = -1
for line in lines[1:]:
for line in lines[1:]:
# command
if line.startswith("$"):
parts = line.strip("$").strip().split()
@ -58,7 +53,8 @@ class Solver(BaseSolver):
trees[cur_path].append(path)
sizes[path] = size
def compute_size(path: Path) -> int:
def compute_size(path: Path) -> int:
size = sizes[path]
if size >= 0:
@ -66,16 +62,19 @@ class Solver(BaseSolver):
return sum(compute_size(sub) for sub in trees[path])
acc_sizes = {path: compute_size(path) for path in trees}
# part 1
yield sum(size for size in acc_sizes.values() if size <= 100_000)
acc_sizes = {path: compute_size(path) for path in trees}
# part 2
total_space = 70_000_000
update_space = 30_000_000
free_space = total_space - acc_sizes[base_path]
# part 1
answer_1 = sum(size for size in acc_sizes.values() if size <= 100_000)
print(f"answer 1 is {answer_1}")
to_free_space = update_space - free_space
# part 2
total_space = 70_000_000
update_space = 30_000_000
free_space = total_space - acc_sizes[base_path]
yield min(size for size in acc_sizes.values() if size >= to_free_space)
to_free_space = update_space - free_space
answer_2 = min(size for size in acc_sizes.values() if size >= to_free_space)
print(f"answer 2 is {answer_2}")

View File

@ -1,20 +1,15 @@
from typing import Any, Iterator
import sys
import numpy as np
from numpy.typing import NDArray
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
trees = np.array([[int(x) for x in row] for row in lines])
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
trees = np.array([[int(x) for x in row] for row in lines])
# answer 1
highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1
highest_trees[1:-1, 1:-1] = [
# answer 1
highest_trees = np.ones(trees.shape + (4,), dtype=int) * -1
highest_trees[1:-1, 1:-1] = [
[
[
trees[:i, j].max(),
@ -25,11 +20,13 @@ class Solver(BaseSolver):
for j in range(1, trees.shape[1] - 1)
]
for i in range(1, trees.shape[0] - 1)
]
]
yield (highest_trees.min(axis=2) < trees).sum()
answer_1 = (highest_trees.min(axis=2) < trees).sum()
print(f"answer 1 is {answer_1}")
def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int:
def viewing_distance(row_of_trees: NDArray[np.int_], value: int) -> int:
w = np.where(row_of_trees >= value)[0]
if not w.size:
@ -37,9 +34,10 @@ class Solver(BaseSolver):
return w[0] + 1
# answer 2
v_distances = np.zeros(trees.shape + (4,), dtype=int)
v_distances[1:-1, 1:-1, :] = [
# answer 2
v_distances = np.zeros(trees.shape + (4,), dtype=int)
v_distances[1:-1, 1:-1, :] = [
[
[
viewing_distance(trees[i - 1 :: -1, j], trees[i, j]),
@ -50,5 +48,6 @@ class Solver(BaseSolver):
for j in range(1, trees.shape[1] - 1)
]
for i in range(1, trees.shape[0] - 1)
]
yield np.prod(v_distances, axis=2).max()
]
answer_2 = np.prod(v_distances, axis=2).max()
print(f"answer 2 is {answer_2}")

View File

@ -1,10 +1,7 @@
import itertools as it
from typing import Any, Iterator
import sys
import numpy as np
from ..base import BaseSolver
def move(head: tuple[int, int], command: str) -> tuple[int, int]:
h_col, h_row = head
@ -46,14 +43,17 @@ def run(commands: list[str], n_blocks: int) -> list[tuple[int, int]]:
return visited
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = [line.strip() for line in input.splitlines()]
lines = sys.stdin.read().splitlines()
# flatten the commands
commands = list(
it.chain(*(p[0] * int(p[1]) for line in lines if (p := line.split())))
)
# flatten the commands
commands: list[str] = []
for line in lines:
d, c = line.split()
commands.extend(d * int(c))
yield len(set(run(commands, n_blocks=2)))
yield len(set(run(commands, n_blocks=10)))
visited_1 = run(commands, n_blocks=2)
print(f"answer 1 is {len(set(visited_1))}")
visited_2 = run(commands, n_blocks=10)
print(f"answer 2 is {len(set(visited_2))}")

View File

@ -1,9 +1,27 @@
from typing import Any, Iterator
import sys
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
lookups_1 = {str(d): d for d in range(1, 10)}
lookups_2 = lookups_1 | {
d: i + 1
for i, d in enumerate(
(
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
)
)
}
def find_values(lines: list[str], lookups: dict[str, int]) -> list[int]:
def find_values(lookups: dict[str, int]) -> list[int]:
values: list[int] = []
for line in filter(bool, lines):
@ -23,27 +41,5 @@ def find_values(lines: list[str], lookups: dict[str, int]) -> list[int]:
return values
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lookups_1 = {str(d): d for d in range(1, 10)}
lookups_2 = lookups_1 | {
d: i + 1
for i, d in enumerate(
(
"one",
"two",
"three",
"four",
"five",
"six",
"seven",
"eight",
"nine",
)
)
}
lines = input.splitlines()
yield sum(find_values(lines, lookups_1))
yield sum(find_values(lines, lookups_2))
print(f"answer 1 is {sum(find_values(lookups_1))}")
print(f"answer 2 is {sum(find_values(lookups_2))}")

View File

@ -1,38 +1,37 @@
from typing import Any, Iterator, Literal, cast
import os
import sys
from typing import Literal, cast
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
Symbol = Literal["|", "-", "L", "J", "7", "F", ".", "S"]
lines: list[list[Symbol]] = [
[cast(Symbol, symbol) for symbol in line] for line in sys.stdin.read().splitlines()
]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines: list[list[Symbol]] = [
[cast(Symbol, symbol) for symbol in line] for line in input.splitlines()
]
# find starting point
si, sj = next(
# find starting point
si, sj = next(
(i, j)
for i in range(len(lines))
for j in range(len(lines[0]))
if lines[i][j] == "S"
)
)
# find one of the two outputs
ni, nj = si, sj
for ni, nj, chars in (
# find one of the two outputs
ni, nj = si, sj
for ni, nj, chars in (
(si - 1, sj, "|7F"),
(si + 1, sj, "|LJ"),
(si, sj - 1, "-LF"),
(si, sj + 1, "-J7"),
):
):
if lines[ni][nj] in chars:
break
# part 1 - find the loop (re-used in part 2)
loop = [(si, sj), (ni, nj)]
while True:
# part 1 - find the loop (re-used in part 2)
loop = [(si, sj), (ni, nj)]
while True:
pi, pj = loop[-2]
i, j = loop[-1]
@ -52,29 +51,30 @@ class Solver(BaseSolver):
loop.append((i, j))
yield len(loop) // 2
answer_1 = len(loop) // 2
print(f"answer 1 is {answer_1}")
# part 2
# part 2
# replace S by an appropriate character for the loop below
di1, dj1 = loop[1][0] - loop[0][0], loop[1][1] - loop[0][1]
di2, dj2 = loop[0][0] - loop[-1][0], loop[0][1] - loop[-1][1]
mapping: dict[tuple[int, int], dict[tuple[int, int], Symbol]] = {
# replace S by an appropriate character for the loop below
di1, dj1 = loop[1][0] - loop[0][0], loop[1][1] - loop[0][1]
di2, dj2 = loop[0][0] - loop[-1][0], loop[0][1] - loop[-1][1]
mapping: dict[tuple[int, int], dict[tuple[int, int], Symbol]] = {
(0, 1): {(0, 1): "-", (-1, 0): "F", (1, 0): "L"},
(0, -1): {(0, -1): "-", (-1, 0): "7", (1, 0): "J"},
(1, 0): {(1, 0): "|", (0, 1): "7", (0, -1): "F"},
(-1, 0): {(-1, 0): "|", (0, -1): "L", (0, 1): "J"},
}
lines[si][sj] = mapping[di1, dj1][di2, dj2]
}
lines[si][sj] = mapping[di1, dj1][di2, dj2]
# find the points inside the loop using an adaptation of ray casting for a discrete
# grid (https://stackoverflow.com/a/218081/2666289)
#
# use a set for faster '... in loop' check
#
loop_s = set(loop)
inside: set[tuple[int, int]] = set()
for i in range(len(lines)):
# find the points inside the loop using an adaptation of ray casting for a discrete
# grid (https://stackoverflow.com/a/218081/2666289)
#
# use a set for faster '... in loop' check
#
loop_s = set(loop)
inside: set[tuple[int, int]] = set()
for i in range(len(lines)):
cnt = 0
for j in range(len(lines[0])):
if (i, j) not in loop_s and cnt % 2 == 1:
@ -83,17 +83,18 @@ class Solver(BaseSolver):
if (i, j) in loop_s and lines[i][j] in "|LJ":
cnt += 1
if self.files:
rows = [["." for _j in range(len(lines[0]))] for _i in range(len(lines))]
rows[si][sj] = "\033[91mS\033[0m"
if VERBOSE:
for i in range(len(lines)):
for j in range(len(lines[0])):
if (i, j) == (si, sj):
print("\033[91mS\033[0m", end="")
elif (i, j) in loop:
print(lines[i][j], end="")
elif (i, j) in inside:
print("\033[92mI\033[0m", end="")
else:
print(".", end="")
print()
for i, j in loop:
rows[i][j] = lines[i][j]
for i, j in inside:
rows[i][j] = "\033[92mI\033[0m"
self.files.create(
"output.txt", "\n".join("".join(row) for row in rows).encode(), True
)
yield len(inside)
answer_2 = len(inside)
print(f"answer 2 is {answer_2}")

View File

@ -1,22 +1,18 @@
from typing import Any, Iterator
import sys
import numpy as np
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
data = np.array([[c == "#" for c in line] for line in lines])
rows = {c for c in range(data.shape[0]) if not data[c, :].any()}
columns = {c for c in range(data.shape[1]) if not data[:, c].any()}
galaxies_y, galaxies_x = np.where(data) # type: ignore
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
data = np.array([[c == "#" for c in line] for line in lines])
rows = {c for c in range(data.shape[0]) if not data[c, :].any()}
columns = {c for c in range(data.shape[1]) if not data[:, c].any()}
galaxies_y, galaxies_x = np.where(data) # type: ignore
def compute_total_distance(expansion: int) -> int:
def compute_total_distance(expansion: int) -> int:
distances: list[int] = []
for g1 in range(len(galaxies_y)):
x1, y1 = int(galaxies_x[g1]), int(galaxies_y[g1])
@ -35,8 +31,11 @@ class Solver(BaseSolver):
distances.append(dx + dy)
return sum(distances)
# part 1
yield compute_total_distance(2)
# part 2
yield compute_total_distance(1000000)
# part 1
answer_1 = compute_total_distance(2)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = compute_total_distance(1000000)
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,9 @@
import os
import sys
from functools import lru_cache
from typing import Any, Iterable, Iterator
from typing import Iterable
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
@lru_cache
@ -75,29 +77,31 @@ def compute_possible_arrangements(
)
class Solver(BaseSolver):
def compute_all_possible_arrangements(
self, lines: Iterable[str], repeat: int
) -> int:
def compute_all_possible_arrangements(lines: Iterable[str], repeat: int) -> int:
count = 0
for i_line, line in enumerate(lines):
self.logger.info(f"processing line {i_line}: {line}...")
if VERBOSE:
from tqdm import tqdm
lines = tqdm(lines)
for line in lines:
parts = line.split(" ")
count += compute_possible_arrangements(
tuple(
filter(len, "?".join(parts[0] for _ in range(repeat)).split("."))
),
tuple(filter(len, "?".join(parts[0] for _ in range(repeat)).split("."))),
tuple(int(c) for c in parts[1].split(",")) * repeat,
)
return count
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# part 1
yield self.compute_all_possible_arrangements(lines, 1)
lines = sys.stdin.read().splitlines()
# part 2
yield self.compute_all_possible_arrangements(lines, 5)
# part 1
answer_1 = compute_all_possible_arrangements(lines, 1)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = compute_all_possible_arrangements(lines, 5)
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,5 @@
from typing import Any, Callable, Iterator, Literal
from ..base import BaseSolver
import sys
from typing import Callable, Literal
def split(block: list[str], axis: Literal[0, 1], count: int) -> int:
@ -26,18 +25,19 @@ def split(block: list[str], axis: Literal[0, 1], count: int) -> int:
return 0
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
blocks = [block.splitlines() for block in input.split("\n\n")]
blocks = [block.splitlines() for block in sys.stdin.read().split("\n\n")]
# part 1
yield sum(
# part 1
answer_1 = sum(
split(block, axis=1, count=0) + 100 * split(block, axis=0, count=0)
for block in blocks
)
)
print(f"answer 1 is {answer_1}")
# part 2
yield sum(
# part 2
answer_2 = sum(
split(block, axis=1, count=1) + 100 * split(block, axis=0, count=1)
for block in blocks
)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,9 +1,10 @@
from typing import Any, Iterator, TypeAlias
from ..base import BaseSolver
import sys
from typing import TypeAlias
RockGrid: TypeAlias = list[list[str]]
rocks0 = [list(line) for line in sys.stdin.read().splitlines()]
def slide_rocks_top(rocks: RockGrid) -> RockGrid:
top = [0 if c == "." else 1 for c in rocks[0]]
@ -33,25 +34,21 @@ def cycle(rocks: RockGrid) -> RockGrid:
return rocks
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
rocks0 = [list(line) for line in input.splitlines()]
rocks = slide_rocks_top([[c for c in r] for r in rocks0])
rocks = slide_rocks_top([[c for c in r] for r in rocks0])
# part 1
answer_1 = sum(
(len(rocks) - i) * sum(1 for c in row if c == "O") for i, row in enumerate(rocks)
)
print(f"answer 1 is {answer_1}")
# part 1
yield sum(
(len(rocks) - i) * sum(1 for c in row if c == "O")
for i, row in enumerate(rocks)
)
# part 2
rocks = rocks0
# part 2
rocks = rocks0
N = 1000000000
cycles: list[RockGrid] = []
i_cycle: int = -1
for i_cycle in range(N):
N = 1000000000
cycles: list[RockGrid] = []
i_cycle: int = -1
for i_cycle in range(N):
rocks = cycle(rocks)
if any(rocks == c for c in cycles):
@ -59,12 +56,13 @@ class Solver(BaseSolver):
cycles.append([[c for c in r] for r in rocks])
cycle_start = next(i for i in range(len(cycles)) if (rocks == cycles[i]))
cycle_length = i_cycle - cycle_start
cycle_start = next(i for i in range(len(cycles)) if (rocks == cycles[i]))
cycle_length = i_cycle - cycle_start
ci = cycle_start + (N - cycle_start) % cycle_length - 1
ci = cycle_start + (N - cycle_start) % cycle_length - 1
yield sum(
answer_2 = sum(
(len(rocks) - i) * sum(1 for c in row if c == "O")
for i, row in enumerate(cycles[ci])
)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,24 +1,21 @@
import sys
from functools import reduce
from typing import Any, Iterator
from ..base import BaseSolver
steps = sys.stdin.read().strip().split(",")
def _hash(s: str) -> int:
return reduce(lambda v, u: ((v + ord(u)) * 17) % 256, s, 0)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
steps = input.split(",")
# part 1
answer_1 = sum(map(_hash, steps))
print(f"answer 1 is {answer_1}")
# part 1
yield sum(map(_hash, steps))
# part 2
boxes: list[dict[str, int]] = [{} for _ in range(256)]
# part 2
boxes: list[dict[str, int]] = [{} for _ in range(256)]
for step in steps:
for step in steps:
if (i := step.find("=")) >= 0:
label, length = step[:i], int(step[i + 1 :])
boxes[_hash(label)][label] = length
@ -26,8 +23,9 @@ class Solver(BaseSolver):
label = step[:-1]
boxes[_hash(label)].pop(label, None)
yield sum(
answer_2 = sum(
i_box * i_lens * length
for i_box, box in enumerate(boxes, start=1)
for i_lens, length in enumerate(box.values(), start=1)
)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,8 @@
from typing import Any, Iterator, Literal, TypeAlias, cast
import os
import sys
from typing import Literal, TypeAlias, cast
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
CellType: TypeAlias = Literal[".", "|", "-", "\\", "/"]
Direction: TypeAlias = Literal["R", "L", "U", "D"]
@ -76,38 +78,33 @@ def propagate(
return beams
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
layout: list[list[CellType]] = [
[cast(CellType, col) for col in row] for row in input.splitlines()
]
layout: list[list[CellType]] = [
[cast(CellType, col) for col in row] for row in sys.stdin.read().splitlines()
]
beams = propagate(layout, (0, 0), "R")
if self.files:
self.files.create(
"beams.txt",
"\n".join(
"".join("#" if col else "." for col in row) for row in beams
).encode(),
True,
)
beams = propagate(layout, (0, 0), "R")
# part 1
yield sum(sum(map(bool, row)) for row in beams)
if VERBOSE:
print("\n".join(["".join("#" if col else "." for col in row) for row in beams]))
# part 2
n_rows, n_cols = len(layout), len(layout[0])
cases: list[tuple[tuple[int, int], Direction]] = []
# part 1
answer_1 = sum(sum(map(bool, row)) for row in beams)
print(f"answer 1 is {answer_1}")
for row in range(n_rows):
# part 2
n_rows, n_cols = len(layout), len(layout[0])
cases: list[tuple[tuple[int, int], Direction]] = []
for row in range(n_rows):
cases.append(((row, 0), "R"))
cases.append(((row, n_cols - 1), "L"))
for col in range(n_cols):
for col in range(n_cols):
cases.append(((0, col), "D"))
cases.append(((n_rows - 1, col), "U"))
yield max(
answer_2 = max(
sum(sum(map(bool, row)) for row in propagate(layout, start, direction))
for start, direction in cases
)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,11 +1,13 @@
from __future__ import annotations
import heapq
import os
import sys
from collections import defaultdict
from dataclasses import dataclass
from typing import Any, Iterator, Literal, TypeAlias
from typing import Literal, TypeAlias
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
Direction: TypeAlias = Literal[">", "<", "^", "v"]
@ -30,17 +32,11 @@ MAPPINGS: dict[Direction, tuple[int, int, Direction]] = {
}
class Solver(BaseSolver):
def print_shortest_path(
self,
name: str,
def print_shortest_path(
grid: list[list[int]],
target: tuple[int, int],
per_cell: dict[tuple[int, int], list[tuple[Label, int]]],
):
if not self.files:
return
):
assert len(per_cell[target]) == 1
label = per_cell[target][0][0]
@ -70,19 +66,16 @@ class Solver(BaseSolver):
if (r, c) != (prev_label.row, prev_label.col):
p_grid[r][c] = f"\033[93m{grid[r][c]}\033[0m"
p_grid[label.row][label.col] = (
f"\033[91m{grid[label.row][label.col]}\033[0m"
)
p_grid[label.row][label.col] = f"\033[91m{grid[label.row][label.col]}\033[0m"
prev_label = label
p_grid[0][0] = f"\033[92m{grid[0][0]}\033[0m"
self.files.create(
name, "\n".join("".join(row) for row in p_grid).encode(), True
)
print("\n".join("".join(row) for row in p_grid))
def shortest_many_paths(self, grid: list[list[int]]) -> dict[tuple[int, int], int]:
def shortest_many_paths(grid: list[list[int]]) -> dict[tuple[int, int], int]:
n_rows, n_cols = len(grid), len(grid[0])
visited: dict[tuple[int, int], tuple[Label, int]] = {}
@ -132,14 +125,13 @@ class Solver(BaseSolver):
return {(r, c): visited[r, c][1] for r in range(n_rows) for c in range(n_cols)}
def shortest_path(
self,
name: str,
def shortest_path(
grid: list[list[int]],
min_straight: int,
max_straight: int,
lower_bounds: dict[tuple[int, int], int],
) -> int:
) -> int:
n_rows, n_cols = len(grid), len(grid[0])
target = (len(grid) - 1, len(grid[0]) - 1)
@ -223,16 +215,19 @@ class Solver(BaseSolver):
),
)
self.print_shortest_path(f"shortest-path_{name}.txt", grid, target, per_cell)
if VERBOSE:
print_shortest_path(grid, target, per_cell)
return per_cell[target][0][1]
def solve(self, input: str) -> Iterator[Any]:
data = [[int(c) for c in r] for r in input.splitlines()]
estimates = self.shortest_many_paths(data)
# part 1
yield self.shortest_path("answer_1", data, 1, 3, lower_bounds=estimates)
data = [[int(c) for c in r] for r in sys.stdin.read().splitlines()]
estimates = shortest_many_paths(data)
# part 2
yield self.shortest_path("answer_2", data, 4, 10, lower_bounds=estimates)
# part 1
answer_1 = shortest_path(data, 1, 3, lower_bounds=estimates)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = shortest_path(data, 4, 10, lower_bounds=estimates)
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,5 @@
from typing import Any, Iterator, Literal, TypeAlias, cast
from ..base import BaseSolver
import sys
from typing import Literal, TypeAlias, cast
Direction: TypeAlias = Literal["R", "L", "U", "D"]
@ -34,23 +33,22 @@ def polygon(values: list[tuple[Direction, int]]) -> tuple[list[tuple[int, int]],
return corners, perimeter
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().splitlines()
# part 1
yield area(
*polygon(
[(cast(Direction, (p := line.split())[0]), int(p[1])) for line in lines]
)
)
# part 2
yield area(
# part 1
answer_1 = area(
*polygon([(cast(Direction, (p := line.split())[0]), int(p[1])) for line in lines])
)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = area(
*polygon(
[
(DIRECTIONS[int((h := line.split()[-1])[-2])], int(h[2:-2], 16))
for line in lines
]
)
)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,8 +1,13 @@
import logging
import operator
import os
import sys
from math import prod
from typing import Any, Iterator, Literal, TypeAlias, cast
from typing import Literal, TypeAlias, cast
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
Category: TypeAlias = Literal["x", "m", "a", "s"]
Part: TypeAlias = dict[Category, int]
@ -17,8 +22,7 @@ Check: TypeAlias = tuple[Category, Literal["<", ">"], int] | None
Workflow: TypeAlias = list[tuple[Check, str]]
class Solver(BaseSolver):
def accept(self, workflows: dict[str, Workflow], part: Part) -> bool:
def accept(workflows: dict[str, Workflow], part: Part) -> bool:
workflow = "in"
decision: bool | None = None
@ -38,7 +42,8 @@ class Solver(BaseSolver):
return decision
def propagate(self, workflows: dict[str, Workflow], start: PartWithBounds) -> int:
def propagate(workflows: dict[str, Workflow], start: PartWithBounds) -> int:
def _fmt(meta: PartWithBounds) -> str:
return "{" + ", ".join(f"{k}={v}" for k, v in meta.items()) + "}"
@ -47,13 +52,13 @@ class Solver(BaseSolver):
) -> int:
count = 0
if target in workflows:
self.logger.info(f" transfer to {target}")
logging.info(f" transfer to {target}")
queue.append((meta, target))
elif target == "A":
count = prod((high - low + 1) for low, high in meta.values())
self.logger.info(f" accepted ({count})")
logging.info(f" accepted ({count})")
else:
self.logger.info(" rejected")
logging.info(" rejected")
return count
accepted = 0
@ -64,26 +69,24 @@ class Solver(BaseSolver):
while queue:
n_iterations += 1
meta, workflow = queue.pop()
self.logger.info(f"{workflow}: {_fmt(meta)}")
logging.info(f"{workflow}: {_fmt(meta)}")
for check, target in workflows[workflow]:
if check is None:
self.logger.info(" end-of-workflow")
logging.info(" end-of-workflow")
accepted += transfer_or_accept(target, meta, queue)
continue
category, sense, value = check
bounds, op = meta[category], OPERATORS[sense]
self.logger.info(
f" checking {_fmt(meta)} against {category} {sense} {value}"
)
logging.info(f" checking {_fmt(meta)} against {category} {sense} {value}")
if not op(bounds[0], value) and not op(bounds[1], value):
self.logger.info(" reject, always false")
logging.info(" reject, always false")
continue
if op(meta[category][0], value) and op(meta[category][1], value):
self.logger.info(" accept, always true")
logging.info(" accept, always true")
accepted += transfer_or_accept(target, meta, queue)
break
@ -93,18 +96,18 @@ class Solver(BaseSolver):
meta[category], meta2[category] = (value, high), (low, value - 1)
else:
meta[category], meta2[category] = (low, value), (value + 1, high)
self.logger.info(f" split {_fmt(meta2)} ({target}), {_fmt(meta)}")
logging.info(f" split {_fmt(meta2)} ({target}), {_fmt(meta)}")
accepted += transfer_or_accept(target, meta2, queue)
self.logger.info(f"run took {n_iterations} iterations")
logging.info(f"run took {n_iterations} iterations")
return accepted
def solve(self, input: str) -> Iterator[Any]:
workflows_s, parts_s = input.split("\n\n")
workflows: dict[str, Workflow] = {}
for workflow_s in workflows_s.split("\n"):
workflows_s, parts_s = sys.stdin.read().strip().split("\n\n")
workflows: dict[str, Workflow] = {}
for workflow_s in workflows_s.split("\n"):
name, block_s = workflow_s.split("{")
workflows[name] = []
@ -121,14 +124,17 @@ class Solver(BaseSolver):
check, target = None, block
workflows[name].append((check, target))
# part 1
parts: list[Part] = [
# part 1
parts: list[Part] = [
{cast(Category, s[0]): int(s[2:]) for s in part_s[1:-1].split(",")}
for part_s in parts_s.split("\n")
]
yield sum(sum(part.values()) for part in parts if self.accept(workflows, part))
]
answer_1 = sum(sum(part.values()) for part in parts if accept(workflows, part))
print(f"answer 1 is {answer_1}")
# part 2
yield self.propagate(
# part 2
answer_2 = propagate(
workflows, {cast(Category, c): (1, 4000) for c in ["x", "m", "a", "s"]}
)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,18 +1,15 @@
import math
from typing import Any, Iterator, Literal, TypeAlias, cast
from ..base import BaseSolver
import sys
from typing import Literal, TypeAlias, cast
CubeType: TypeAlias = Literal["red", "blue", "green"]
MAX_CUBES: dict[CubeType, int] = {"red": 12, "green": 13, "blue": 14}
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
games: dict[int, list[dict[CubeType, int]]] = {}
for line in filter(bool, lines):
# parse games
lines = sys.stdin.read().splitlines()
games: dict[int, list[dict[CubeType, int]]] = {}
for line in filter(bool, lines):
id_part, sets_part = line.split(":")
games[int(id_part.split(" ")[-1])] = [
@ -24,7 +21,8 @@ class Solver(BaseSolver):
for cube_set_s in sets_part.strip().split(";")
]
yield sum(
# part 1
answer_1 = sum(
id
for id, set_of_cubes in games.items()
if all(
@ -32,12 +30,14 @@ class Solver(BaseSolver):
for cube_set in set_of_cubes
for cube, n_cubes in cube_set.items()
)
)
)
print(f"answer 1 is {answer_1}")
yield sum(
# part 2
answer_2 = sum(
math.prod(
max(cube_set.get(cube, 0) for cube_set in set_of_cubes)
for cube in MAX_CUBES
max(cube_set.get(cube, 0) for cube_set in set_of_cubes) for cube in MAX_CUBES
)
for set_of_cubes in games.values()
)
)
print(f"answer 2 is {answer_2}")

View File

@ -1,41 +1,55 @@
import logging
import os
import sys
from collections import defaultdict
from math import lcm
from typing import Any, Iterator, Literal, TypeAlias
from typing import Literal, TypeAlias
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
from ..base import BaseSolver
ModuleType: TypeAlias = Literal["broadcaster", "conjunction", "flip-flop"]
PulseType: TypeAlias = Literal["high", "low"]
modules: dict[str, tuple[ModuleType, list[str]]] = {}
class Solver(BaseSolver):
_modules: dict[str, tuple[ModuleType, list[str]]]
lines = sys.stdin.read().splitlines()
def _process(
self,
for line in lines:
name, outputs_s = line.split(" -> ")
outputs = outputs_s.split(", ")
if name == "broadcaster":
modules["broadcaster"] = ("broadcaster", outputs)
else:
modules[name[1:]] = (
"conjunction" if name.startswith("&") else "flip-flop",
outputs,
)
def process(
start: tuple[str, str, PulseType],
flip_flop_states: dict[str, Literal["on", "off"]],
conjunction_states: dict[str, dict[str, PulseType]],
) -> tuple[dict[PulseType, int], dict[str, dict[PulseType, int]]]:
) -> tuple[dict[PulseType, int], dict[str, dict[PulseType, int]]]:
pulses: list[tuple[str, str, PulseType]] = [start]
counts: dict[PulseType, int] = {"low": 0, "high": 0}
inputs: dict[str, dict[PulseType, int]] = defaultdict(
lambda: {"low": 0, "high": 0}
)
inputs: dict[str, dict[PulseType, int]] = defaultdict(lambda: {"low": 0, "high": 0})
self.logger.info("starting process... ")
logging.info("starting process... ")
while pulses:
input, name, pulse = pulses.pop(0)
self.logger.info(f"{input} -{pulse}-> {name}")
logging.info(f"{input} -{pulse}-> {name}")
counts[pulse] += 1
inputs[name][pulse] += 1
if name not in self._modules:
if name not in modules:
continue
type, outputs = self._modules[name]
type, outputs = modules[name]
if type == "broadcaster":
...
@ -63,96 +77,70 @@ class Solver(BaseSolver):
return counts, inputs
def solve(self, input: str) -> Iterator[Any]:
self._modules = {}
lines = input.splitlines()
for line in lines:
name, outputs_s = line.split(" -> ")
outputs = outputs_s.split(", ")
if name == "broadcaster":
self._modules["broadcaster"] = ("broadcaster", outputs)
else:
self._modules[name[1:]] = (
"conjunction" if name.startswith("&") else "flip-flop",
outputs,
)
if self.files:
contents = "digraph G {\n"
contents += "rx [shape=circle, color=red, style=filled];\n"
for name, (type, outputs) in self._modules.items():
with open("./day20.dot", "w") as fp:
fp.write("digraph G {\n")
fp.write("rx [shape=circle, color=red, style=filled];\n")
for name, (type, outputs) in modules.items():
if type == "conjunction":
shape = "diamond"
elif type == "flip-flop":
shape = "box"
else:
shape = "circle"
contents += f"{name} [shape={shape}];\n"
for name, (type, outputs) in self._modules.items():
fp.write(f"{name} [shape={shape}];\n")
for name, (type, outputs) in modules.items():
for output in outputs:
contents += f"{name} -> {output};\n"
contents += "}\n"
fp.write(f"{name} -> {output};\n")
fp.write("}\n")
self.files.create("day20.dot", contents.encode(), False)
# part 1
flip_flop_states: dict[str, Literal["on", "off"]] = {
name: "off"
for name, (type, _) in self._modules.items()
if type == "flip-flop"
}
conjunction_states: dict[str, dict[str, PulseType]] = {
name: {
input: "low"
for input, (_, outputs) in self._modules.items()
if name in outputs
}
for name, (type, _) in self._modules.items()
# part 1
flip_flop_states: dict[str, Literal["on", "off"]] = {
name: "off" for name, (type, _) in modules.items() if type == "flip-flop"
}
conjunction_states: dict[str, dict[str, PulseType]] = {
name: {input: "low" for input, (_, outputs) in modules.items() if name in outputs}
for name, (type, _) in modules.items()
if type == "conjunction"
}
counts: dict[PulseType, int] = {"low": 0, "high": 0}
for _ in range(1000):
result, _ = self._process(
}
counts: dict[PulseType, int] = {"low": 0, "high": 0}
for _ in range(1000):
result, _ = process(
("button", "broadcaster", "low"), flip_flop_states, conjunction_states
)
for pulse in ("low", "high"):
counts[pulse] += result[pulse]
yield counts["low"] * counts["high"]
answer_1 = counts["low"] * counts["high"]
print(f"answer 1 is {answer_1}")
# part 2
# part 2
# reset states
for name in flip_flop_states:
# reset states
for name in flip_flop_states:
flip_flop_states[name] = "off"
for name in conjunction_states:
for name in conjunction_states:
for input in conjunction_states[name]:
conjunction_states[name][input] = "low"
# find the conjunction connected to rx
to_rx = [
name for name, (_, outputs) in self._modules.items() if "rx" in outputs
]
assert len(to_rx) == 1, "cannot handle multiple module inputs for rx"
assert (
self._modules[to_rx[0]][0] == "conjunction"
), "can only handle conjunction as input to rx"
# find the conjunction connected to rx
to_rx = [name for name, (_, outputs) in modules.items() if "rx" in outputs]
assert len(to_rx) == 1, "cannot handle multiple module inputs for rx"
assert (
modules[to_rx[0]][0] == "conjunction"
), "can only handle conjunction as input to rx"
to_rx_inputs = [
name for name, (_, outputs) in self._modules.items() if to_rx[0] in outputs
]
assert all(
self._modules[i][0] == "conjunction" and len(self._modules[i][1]) == 1
for i in to_rx_inputs
), "can only handle inversion as second-order inputs to rx"
to_rx_inputs = [name for name, (_, outputs) in modules.items() if to_rx[0] in outputs]
assert all(
modules[i][0] == "conjunction" and len(modules[i][1]) == 1 for i in to_rx_inputs
), "can only handle inversion as second-order inputs to rx"
count = 1
cycles: dict[str, int] = {}
second: dict[str, int] = {}
while len(second) != len(to_rx_inputs):
_, inputs = self._process(
count = 1
cycles: dict[str, int] = {}
second: dict[str, int] = {}
while len(second) != len(to_rx_inputs):
_, inputs = process(
("button", "broadcaster", "low"), flip_flop_states, conjunction_states
)
@ -165,8 +153,9 @@ class Solver(BaseSolver):
count += 1
assert all(
assert all(
second[k] == cycles[k] * 2 for k in to_rx_inputs
), "cannot only handle cycles starting at the beginning"
), "cannot only handle cycles starting at the beginning"
yield lcm(*cycles.values())
answer_2 = lcm(*cycles.values())
print(f"answer 2 is {answer_2}")

View File

@ -1,6 +1,9 @@
from typing import Any, Iterator
import logging
import os
import sys
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
def reachable(
@ -18,39 +21,35 @@ def reachable(
return tiles
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
map = input.splitlines()
start = next(
(i, j)
for i in range(len(map))
for j in range(len(map[i]))
if map[i][j] == "S"
)
map = sys.stdin.read().splitlines()
start = next(
(i, j) for i in range(len(map)) for j in range(len(map[i])) if map[i][j] == "S"
)
# part 1
yield len(reachable(map, {start}, 6 if len(map) < 20 else 64))
# part 1
answer_1 = len(reachable(map, {start}, 6 if len(map) < 20 else 64))
print(f"answer 1 is {answer_1}")
# part 2
# part 2
# the initial map is a square and contains an empty rhombus whose diameter is
# the size of the map, and has only empty cells around the middle row and column
#
# after ~n/2 steps, the first map is filled with a rhombus, after that we get a
# bigger rhombus every n steps
#
# we are going to find the number of cells reached for the initial rhombus, n
# steps after and n * 2 steps after
#
cycle = len(map)
rhombus = (len(map) - 3) // 2 + 1
# the initial map is a square and contains an empty rhombus whose diameter is the size
# of the map, and has only empty cells around the middle row and column
#
# after ~n/2 steps, the first map is filled with a rhombus, after that we get a bigger
# rhombus every n steps
#
# we are going to find the number of cells reached for the initial rhombus, n steps
# after and n * 2 steps after
#
cycle = len(map)
rhombus = (len(map) - 3) // 2 + 1
values: list[int] = []
values.append(len(tiles := reachable(map, {start}, rhombus)))
values.append(len(tiles := reachable(map, tiles, cycle)))
values.append(len(tiles := reachable(map, tiles, cycle)))
values: list[int] = []
values.append(len(tiles := reachable(map, {start}, rhombus)))
values.append(len(tiles := reachable(map, tiles, cycle)))
values.append(len(tiles := reachable(map, tiles, cycle)))
if self.files:
if logging.root.getEffectiveLevel() == logging.INFO:
n_rows, n_cols = len(map), len(map[0])
rows = [
@ -66,85 +65,85 @@ class Solver(BaseSolver):
if (i // cycle) % 2 == (j // cycle) % 2:
rows[i][j] = f"\033[91m{rows[i][j]}\033[0m"
self.files.create(
"cycle.txt", "\n".join("".join(row) for row in rows).encode(), True
print("\n".join("".join(row) for row in rows))
logging.info(f"values to fit: {values}")
# version 1:
#
# after 3 cycles, the figure looks like the following:
#
# I M D
# I J A K D
# H A F A L
# C E A K B
# C G B
#
# after 4 cycles, the figure looks like the following:
#
# I M D
# I J A K D
# I J A B A K D
# H A B A B A L
# C E A B A N F
# C E A N F
# C G F
#
# the 'radius' of the rhombus is the number of cycles minus 1
#
# the 4 'corner' (M, H, L, G) are counted once, the blocks with a corner triangle (D, I,
# C, B) are each counted radius times, the blocks with everything but one corner (J, K,
# E, N) are each counted radius - 1 times
#
# there are two versions of the whole block, A and B in the above (or odd and even),
# depending on the number of cycles, either A or B will be in the center
#
counts = [
[
sum(
(i, j) in tiles
for i in range(ci * cycle, (ci + 1) * cycle)
for j in range(cj * cycle, (cj + 1) * cycle)
)
for cj in range(-2, 3)
]
for ci in range(-2, 3)
]
self.logger.info(f"values to fit: {values}")
radius = (26501365 - rhombus) // cycle - 1
A = counts[2][2] if radius % 2 == 0 else counts[2][1]
B = counts[2][2] if radius % 2 == 1 else counts[2][1]
answer_2 = (
(radius + 1) * A
+ radius * B
+ 2 * radius * (radius + 1) // 2 * A
+ 2 * radius * (radius - 1) // 2 * B
+ sum(counts[i][j] for i, j in ((0, 2), (-1, 2), (2, 0), (2, -1)))
+ sum(counts[i][j] for i, j in ((0, 1), (0, 3), (-1, 1), (-1, 3))) * (radius + 1)
+ sum(counts[i][j] for i, j in ((1, 1), (1, 3), (-2, 1), (-2, 3))) * radius
)
print(f"answer 2 (v1) is {answer_2}")
# version 1:
#
# after 3 cycles, the figure looks like the following:
#
# I M D
# I J A K D
# H A F A L
# C E A K B
# C G B
#
# after 4 cycles, the figure looks like the following:
#
# I M D
# I J A K D
# I J A B A K D
# H A B A B A L
# C E A B A N F
# C E A N F
# C G F
#
# the 'radius' of the rhombus is the number of cycles minus 1
#
# the 4 'corner' (M, H, L, G) are counted once, the blocks with a corner triangle (D, I,
# C, B) are each counted radius times, the blocks with everything but one corner (J, K,
# E, N) are each counted radius - 1 times
#
# there are two versions of the whole block, A and B in the above (or odd and even),
# depending on the number of cycles, either A or B will be in the center
#
# version 2: fitting a polynomial
#
# the value we are interested in (26501365) can be written as R + K * C where R is the
# step at which we find the first rhombus, and K the repeat step, so instead of fitting
# for X values (R, R + K, R + 2 K), we are going to fit for (0, 1, 2), giving us much
# simpler equation for the a, b and c coefficient
#
# we get:
# - (a * 0² + b * 0 + c) = y1 => c = y1
# - (a * 1² + b * 1 + c) = y2 => a + b = y2 - y1
# => b = y2 - y1 - a
# - (a * 2² + b * 2 + c) = y3 => 4a + 2b = y3 - y1
# => 4a + 2(y2 - y1 - a) = y3 - y1
# => a = (y1 + y3) / 2 - y2
#
y1, y2, y3 = values
a, b, c = (y1 + y3) // 2 - y2, 2 * y2 - (3 * y1 + y3) // 2, y1
# counts = [
# [
# sum(
# (i, j) in tiles
# for i in range(ci * cycle, (ci + 1) * cycle)
# for j in range(cj * cycle, (cj + 1) * cycle)
# )
# for cj in range(-2, 3)
# ]
# for ci in range(-2, 3)
# ]
# radius = (26501365 - rhombus) // cycle - 1
# A = counts[2][2] if radius % 2 == 0 else counts[2][1]
# B = counts[2][2] if radius % 2 == 1 else counts[2][1]
# answer_2 = (
# (radius + 1) * A
# + radius * B
# + 2 * radius * (radius + 1) // 2 * A
# + 2 * radius * (radius - 1) // 2 * B
# + sum(counts[i][j] for i, j in ((0, 2), (-1, 2), (2, 0), (2, -1)))
# + sum(counts[i][j] for i, j in ((0, 1), (0, 3), (-1, 1), (-1, 3)))
# * (radius + 1)
# + sum(counts[i][j] for i, j in ((1, 1), (1, 3), (-2, 1), (-2, 3))) * radius
# )
# version 2: fitting a polynomial
#
# the value we are interested in (26501365) can be written as R + K * C where R is the
# step at which we find the first rhombus, and K the repeat step, so instead of fitting
# for X values (R, R + K, R + 2 K), we are going to fit for (0, 1, 2), giving us much
# simpler equation for the a, b and c coefficient
#
# we get:
# - (a * 0² + b * 0 + c) = y1 => c = y1
# - (a * 1² + b * 1 + c) = y2 => a + b = y2 - y1
# => b = y2 - y1 - a
# - (a * 2² + b * 2 + c) = y3 => 4a + 2b = y3 - y1
# => 4a + 2(y2 - y1 - a) = y3 - y1
# => a = (y1 + y3) / 2 - y2
#
y1, y2, y3 = values
a, b, c = (y1 + y3) // 2 - y2, 2 * y2 - (3 * y1 + y3) // 2, y1
n = (26501365 - rhombus) // cycle
yield a * n * n + b * n + c
n = (26501365 - rhombus) // cycle
answer_2 = a * n * n + b * n + c
print(f"answer 2 (v2) is {answer_2}")

View File

@ -1,23 +1,26 @@
import itertools
import logging
import os
import string
import sys
from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().splitlines()
def _name(i: int) -> str:
def _name(i: int) -> str:
if len(lines) < 26:
return string.ascii_uppercase[i]
return f"B{i:04d}"
def build_supports(
def build_supports(
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]],
) -> tuple[dict[int, set[int]], dict[int, set[int]]]:
) -> tuple[dict[int, set[int]], dict[int, set[int]]]:
# 1. compute locations where a brick of sand will land after falling by processing
# them in sorted order of bottom z location
levels: dict[tuple[int, int, int], int] = defaultdict(lambda: -1)
@ -39,9 +42,7 @@ class Solver(BaseSolver):
# 2. compute the bricks that supports any brick
supported_by: dict[int, set[int]] = {}
supports: dict[int, set[int]] = {
i_brick: set() for i_brick in range(len(bricks))
}
supports: dict[int, set[int]] = {i_brick: set() for i_brick in range(len(bricks))}
for i_brick, ((sx, sy, sz), (ex, ey, ez)) in enumerate(bricks):
name = _name(i_brick)
@ -50,7 +51,7 @@ class Solver(BaseSolver):
for x, y in itertools.product(range(sx, ex + 1), range(sy, ey + 1))
if (v := levels[x, y, sz - 1]) != -1
}
self.logger.info(
logging.info(
f"{name} supported by {', '.join(map(_name, supported_by[i_brick]))}"
)
@ -59,8 +60,9 @@ class Solver(BaseSolver):
return supported_by, supports
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]] = []
for line in lines:
bricks: list[tuple[tuple[int, int, int], tuple[int, int, int]]] = []
for line in lines:
bricks.append(
(
tuple(int(c) for c in line.split("~")[0].split(",")), # type: ignore
@ -68,19 +70,20 @@ class Solver(BaseSolver):
)
)
# sort bricks by bottom z position to compute supports
bricks = sorted(bricks, key=lambda b: b[0][-1])
supported_by, supports = build_supports(bricks)
# sort bricks by bottom z position to compute supports
bricks = sorted(bricks, key=lambda b: b[0][-1])
supported_by, supports = build_supports(bricks)
# part 1
yield len(bricks) - sum(
# part 1
answer_1 = len(bricks) - sum(
any(len(supported_by[supported]) == 1 for supported in supports_to)
for supports_to in supports.values()
)
)
print(f"answer 1 is {answer_1}")
# part 2
falling_in_chain: dict[int, set[int]] = {}
for i_brick in range(len(bricks)):
# part 2
falling_in_chain: dict[int, set[int]] = {}
for i_brick in range(len(bricks)):
to_disintegrate: set[int] = {
supported
for supported in supports[i_brick]
@ -97,13 +100,12 @@ class Solver(BaseSolver):
for d_brick in to_disintegrate:
for supported in supports[d_brick]:
supported_by_copy[supported] = supported_by_copy[supported] - {
d_brick
}
supported_by_copy[supported] = supported_by_copy[supported] - {d_brick}
if not supported_by_copy[supported]:
to_disintegrate_v.add(supported)
to_disintegrate = to_disintegrate_v
yield sum(len(falling) for falling in falling_in_chain.values())
answer_2 = sum(len(falling) for falling in falling_in_chain.values())
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,11 @@
import logging
import os
import sys
from collections import defaultdict
from typing import Any, Iterator, Literal, Sequence, TypeAlias, cast
from typing import Literal, Sequence, TypeAlias, cast
from ..base import BaseSolver
VERBOSE = os.getenv("AOC_VERBOSE") == "True"
logging.basicConfig(level=logging.INFO if VERBOSE else logging.WARNING)
DirectionType: TypeAlias = Literal[">", "<", "^", "v", ".", "#"]
@ -31,7 +35,6 @@ def neighbors(
Compute neighbors of the given node, ignoring the given set of nodes and considering
that you can go uphill on slopes.
"""
n_rows, n_cols = len(grid), len(grid[0])
i, j = node
for di, dj in Neighbors[grid[i][j]]:
@ -100,13 +103,11 @@ def compute_direct_links(
return direct
class Solver(BaseSolver):
def longest_path_length(
self,
def longest_path_length(
links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]],
start: tuple[int, int],
target: tuple[int, int],
) -> int:
) -> int:
max_distance: int = -1
queue: list[tuple[tuple[int, int], int, frozenset[tuple[int, int]]]] = [
(start, 0, frozenset({start}))
@ -128,38 +129,39 @@ class Solver(BaseSolver):
if reach not in path
)
self.logger.info(f"processed {nodes} nodes")
logging.info(f"processed {nodes} nodes")
return max_distance
def solve(self, input: str) -> Iterator[Any]:
lines = cast(list[Sequence[DirectionType]], input.splitlines())
start = (0, 1)
target = (len(lines) - 1, len(lines[0]) - 2)
lines = cast(list[Sequence[DirectionType]], sys.stdin.read().splitlines())
n_rows, n_cols = len(lines), len(lines[0])
start = (0, 1)
target = (len(lines) - 1, len(lines[0]) - 2)
direct_links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]] = {
direct_links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]] = {
start: [reachable(lines, start, target)]
}
direct_links.update(
compute_direct_links(lines, direct_links[start][0][0], target)
)
}
direct_links.update(compute_direct_links(lines, direct_links[start][0][0], target))
# part 1
yield self.longest_path_length(direct_links, start, target)
# part 1
answer_1 = longest_path_length(direct_links, start, target)
print(f"answer 1 is {answer_1}")
# part 2
reverse_links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]] = (
defaultdict(list)
)
for origin, links in direct_links.items():
# part 2
reverse_links: dict[tuple[int, int], list[tuple[tuple[int, int], int]]] = defaultdict(
list
)
for origin, links in direct_links.items():
for destination, distance in links:
if origin != start:
reverse_links[destination].append((origin, distance))
links = {
links = {
k: direct_links.get(k, []) + reverse_links.get(k, [])
for k in direct_links.keys() | reverse_links.keys()
}
}
yield self.longest_path_length(links, start, target)
answer_2 = longest_path_length(links, start, target)
print(f"answer 2 is {answer_2}")

View File

@ -1,29 +1,22 @@
from typing import Any, Iterator
import sys
import numpy as np
from sympy import solve, symbols
from ..base import BaseSolver
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
positions = np.array(
positions = np.array(
[[int(c) for c in line.split("@")[0].strip().split(", ")] for line in lines]
)
velocities = np.array(
)
velocities = np.array(
[[int(c) for c in line.split("@")[1].strip().split(", ")] for line in lines]
)
)
# part 1
low, high = (
[7, 27] if len(positions) <= 10 else [200000000000000, 400000000000000]
)
# part 1
low, high = [7, 27] if len(positions) <= 10 else [200000000000000, 400000000000000]
count = 0
for i1, (p1, v1) in enumerate(zip(positions, velocities)):
count = 0
for i1, (p1, v1) in enumerate(zip(positions, velocities)):
p, r = p1[:2], v1[:2]
q, s = positions[i1 + 1 :, :2], velocities[i1 + 1 :, :2]
@ -38,32 +31,33 @@ class Solver(BaseSolver):
c = p + np.expand_dims(t, 1) * r
count += np.all((low <= c) & (c <= high), axis=1).sum()
yield count
# part 2
# equation
# p1 + t1 * v1 == p0 + t1 * v0
# p2 + t2 * v2 == p0 + t2 * v0
# p3 + t3 * v3 == p0 + t3 * v0
# ...
# pn + tn * vn == p0 + tn * v0
#
answer_1 = count
print(f"answer 1 is {answer_1}")
# we can solve with only 3 lines since each lines contains 3
# equations (x / y / z), so 3 lines give 9 equations and 9
# variables: position (3), velocities (3) and times (3).
n = 3
# part 2
# equation
# p1 + t1 * v1 == p0 + t1 * v0
# p2 + t2 * v2 == p0 + t2 * v0
# p3 + t3 * v3 == p0 + t3 * v0
# ...
# pn + tn * vn == p0 + tn * v0
#
x, y, z, vx, vy, vz, *ts = symbols(
# we can solve with only 3 lines since each lines contains 3
# equations (x / y / z), so 3 lines give 9 equations and 9
# variables: position (3), velocities (3) and times (3).
n = 3
x, y, z, vx, vy, vz, *ts = symbols(
"x y z vx vy vz " + " ".join(f"t{i}" for i in range(n + 1))
)
equations = []
for i1, ti in zip(range(n), ts):
for p, d, pi, di in zip(
(x, y, z), (vx, vy, vz), positions[i1], velocities[i1]
):
)
equations = []
for i1, ti in zip(range(n), ts):
for p, d, pi, di in zip((x, y, z), (vx, vy, vz), positions[i1], velocities[i1]):
equations.append(p + ti * d - pi - ti * di)
print(equations)
r = solve(equations, [x, y, z, vx, vy, vz] + list(ts), dict=True)[0]
yield r[x] + r[y] + r[z]
r = solve(equations, [x, y, z, vx, vy, vz] + list(ts), dict=True)[0]
answer_2 = r[x] + r[y] + r[z]
print(f"answer 2 is {answer_2}")

View File

@ -1,25 +1,25 @@
# pyright: reportUnknownMemberType=false
from typing import Any, Iterator
import sys
import networkx as nx
from ..base import BaseSolver
components = {
(p := line.split(": "))[0]: p[1].split() for line in sys.stdin.read().splitlines()
}
targets = {t for c in components for t in components[c] if t not in components}
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
components = {
(p := line.split(": "))[0]: p[1].split() for line in input.splitlines()
}
graph = nx.Graph()
graph.add_edges_from((u, v) for u, vs in components.items() for v in vs)
graph: "nx.Graph[str]" = nx.Graph()
graph.add_edges_from((u, v) for u, vs in components.items() for v in vs)
cut = nx.minimum_edge_cut(graph)
graph.remove_edges_from(cut)
cut = nx.minimum_edge_cut(graph)
graph.remove_edges_from(cut)
c1, c2 = nx.connected_components(graph)
c1, c2 = nx.connected_components(graph)
# part 1
answer_1 = len(c1) * len(c2)
print(f"answer 1 is {answer_1}")
# part 1
yield len(c1) * len(c2)
# part 2
answer_2 = ...
print(f"answer 2 is {answer_2}")

View File

@ -1,20 +1,15 @@
import string
import sys
from collections import defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
NOT_A_SYMBOL = "." + string.digits
lines = sys.stdin.read().splitlines()
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
values: list[int] = []
gears: dict[tuple[int, int], list[int]] = defaultdict(list)
values: list[int] = []
gears: dict[tuple[int, int], list[int]] = defaultdict(list)
for i, line in enumerate(lines):
for i, line in enumerate(lines):
j = 0
while j < len(line):
# skip everything until a digit is found (start of a number)
@ -49,5 +44,10 @@ class Solver(BaseSolver):
# continue starting from the end of the number
j = k
yield sum(values)
yield sum(v1 * v2 for v1, v2 in filter(lambda vs: len(vs) == 2, gears.values()))
# part 1
answer_1 = sum(values)
print(f"answer 1 is {answer_1}")
# part 2
answer_2 = sum(v1 * v2 for v1, v2 in filter(lambda vs: len(vs) == 2, gears.values()))
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,5 @@
import sys
from dataclasses import dataclass
from typing import Any, Iterator
from ..base import BaseSolver
@dataclass(frozen=True)
@ -11,12 +9,10 @@ class Card:
values: list[int]
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().splitlines()
cards: list[Card] = []
for line in lines:
cards: list[Card] = []
for line in lines:
id_part, e_part = line.split(":")
numbers_s, values_s = e_part.split("|")
cards.append(
@ -27,18 +23,19 @@ class Solver(BaseSolver):
)
)
winnings = [sum(1 for n in card.values if n in card.numbers) for card in cards]
winnings = [sum(1 for n in card.values if n in card.numbers) for card in cards]
# part 1
yield sum(2 ** (winning - 1) for winning in winnings if winning > 0)
# part 1
answer_1 = sum(2 ** (winning - 1) for winning in winnings if winning > 0)
print(f"answer 1 is {answer_1}")
# part 2
card2cards = {i: list(range(i + 1, i + w + 1)) for i, w in enumerate(winnings)}
card2values = {i: 0 for i in range(len(cards))}
# part 2
card2cards = {i: list(range(i + 1, i + w + 1)) for i, w in enumerate(winnings)}
card2values = {i: 0 for i in range(len(cards))}
for i in range(len(cards)):
for i in range(len(cards)):
card2values[i] += 1
for j in card2cards[i]:
card2values[j] += card2values[i]
yield sum(card2values.values())
print(f"answer 2 is {sum(card2values.values())}")

View File

@ -1,6 +1,5 @@
from typing import Any, Iterator, Sequence
from ..base import BaseSolver
import sys
from typing import Sequence
MAP_ORDER = [
"seed",
@ -13,6 +12,55 @@ MAP_ORDER = [
"location",
]
lines = sys.stdin.read().splitlines()
# mappings from one category to another, each list contains
# ranges stored as (source, target, length), ordered by start and
# completed to have no "hole"
maps: dict[tuple[str, str], list[tuple[int, int, int]]] = {}
# parsing
index = 2
while index < len(lines):
p1, _, p2 = lines[index].split()[0].split("-")
# extract the existing ranges from the file - we store as (source, target, length)
# whereas the file is in order (target, source, length)
index += 1
values: list[tuple[int, int, int]] = []
while index < len(lines) and lines[index]:
n1, n2, n3 = lines[index].split()
values.append((int(n2), int(n1), int(n3)))
index += 1
# sort by source value
values.sort()
# add a 'fake' interval starting at 0 if missing
if values[0][0] != 0:
values.insert(0, (0, 0, values[0][0]))
# fill gaps between intervals
for i in range(len(values) - 1):
next_start = values[i + 1][0]
end = values[i][0] + values[i][2]
if next_start != end:
values.insert(
i + 1,
(end, end, next_start - end),
)
# add an interval covering values up to at least 2**32 at the end
last_start, _, last_length = values[-1]
values.append((last_start + last_length, last_start + last_length, 2**32))
assert all(v1[0] + v1[2] == v2[0] for v1, v2 in zip(values[:-1], values[1:]))
assert values[0][0] == 0
assert values[-1][0] + values[-1][-1] >= 2**32
maps[p1, p2] = values
index += 1
def find_range(
values: tuple[int, int], map: list[tuple[int, int, int]]
@ -63,71 +111,19 @@ def find_range(
return ranges
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
# mappings from one category to another, each list contains
# ranges stored as (source, target, length), ordered by start and
# completed to have no "hole"
maps: dict[tuple[str, str], list[tuple[int, int, int]]] = {}
def find_location_ranges(
seeds: Sequence[tuple[int, int]],
) -> Sequence[tuple[int, int]]:
def find_location_ranges(seeds: Sequence[tuple[int, int]]) -> Sequence[tuple[int, int]]:
for map1, map2 in zip(MAP_ORDER[:-1], MAP_ORDER[1:]):
seeds = [s2 for s1 in seeds for s2 in find_range(s1, maps[map1, map2])]
return seeds
# parsing
index = 2
while index < len(lines):
p1, _, p2 = lines[index].split()[0].split("-")
# extract the existing ranges from the file - we store as (source, target, length)
# whereas the file is in order (target, source, length)
index += 1
values: list[tuple[int, int, int]] = []
while index < len(lines) and lines[index]:
n1, n2, n3 = lines[index].split()
values.append((int(n2), int(n1), int(n3)))
index += 1
# part 1 - use find_range() with range of length 1
seeds_p1 = [(int(s), 1) for s in lines[0].split(":")[1].strip().split()]
answer_1 = min(start for start, _ in find_location_ranges(seeds_p1))
print(f"answer 1 is {answer_1}")
# sort by source value
values.sort()
# add a 'fake' interval starting at 0 if missing
if values[0][0] != 0:
values.insert(0, (0, 0, values[0][0]))
# fill gaps between intervals
for i in range(len(values) - 1):
next_start = values[i + 1][0]
end = values[i][0] + values[i][2]
if next_start != end:
values.insert(
i + 1,
(end, end, next_start - end),
)
# add an interval covering values up to at least 2**32 at the end
last_start, _, last_length = values[-1]
values.append((last_start + last_length, last_start + last_length, 2**32))
assert all(
v1[0] + v1[2] == v2[0] for v1, v2 in zip(values[:-1], values[1:])
)
assert values[0][0] == 0
assert values[-1][0] + values[-1][-1] >= 2**32
maps[p1, p2] = values
index += 1
# part 1 - use find_range() with range of length 1
seeds_p1 = [(int(s), 1) for s in lines[0].split(":")[1].strip().split()]
yield min(start for start, _ in find_location_ranges(seeds_p1))
# # part 2
parts = lines[0].split(":")[1].strip().split()
seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])]
yield min(start for start, _ in find_location_ranges(seeds_p2))
# # part 2
parts = lines[0].split(":")[1].strip().split()
seeds_p2 = [(int(s), int(e)) for s, e in zip(parts[::2], parts[1::2])]
answer_2 = min(start for start, _ in find_location_ranges(seeds_p2))
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,5 @@
import math
from typing import Any, Iterator
from ..base import BaseSolver
import sys
def extreme_times_to_beat(time: int, distance: int) -> tuple[int, int]:
@ -27,23 +25,23 @@ def extreme_times_to_beat(time: int, distance: int) -> tuple[int, int]:
return t1, t2
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
lines = sys.stdin.read().splitlines()
# part 1
times = list(map(int, lines[0].split()[1:]))
distances = list(map(int, lines[1].split()[1:]))
yield math.prod(
# part 1
times = list(map(int, lines[0].split()[1:]))
distances = list(map(int, lines[1].split()[1:]))
answer_1 = math.prod(
t2 - t1 + 1
for t1, t2 in (
extreme_times_to_beat(time, distance)
for time, distance in zip(times, distances)
)
)
)
print(f"answer 1 is {answer_1}")
# part 2
time = int(lines[0].split(":")[1].strip().replace(" ", ""))
distance = int(lines[1].split(":")[1].strip().replace(" ", ""))
t1, t2 = extreme_times_to_beat(time, distance)
yield t2 - t1 + 1
# part 2
time = int(lines[0].split(":")[1].strip().replace(" ", ""))
distance = int(lines[1].split(":")[1].strip().replace(" ", ""))
t1, t2 = extreme_times_to_beat(time, distance)
answer_2 = t2 - t1 + 1
print(f"answer 2 is {answer_2}")

View File

@ -1,7 +1,5 @@
import sys
from collections import Counter, defaultdict
from typing import Any, Iterator
from ..base import BaseSolver
class HandTypes:
@ -34,17 +32,18 @@ def extract_key(hand: str, values: dict[str, int], joker: str = "0") -> tuple[in
)
class Solver(BaseSolver):
def solve(self, input: str) -> Iterator[Any]:
lines = input.splitlines()
cards = [(t[0], int(t[1])) for line in lines if (t := line.split())]
lines = sys.stdin.read().splitlines()
cards = [(t[0], int(t[1])) for line in lines if (t := line.split())]
# part 1
values = {card: value for value, card in enumerate("23456789TJQKA")}
cards.sort(key=lambda cv: extract_key(cv[0], values=values))
yield sum(rank * value for rank, (_, value) in enumerate(cards, start=1))
# part 2
values = {card: value for value, card in enumerate("J23456789TQKA")}
cards.sort(key=lambda cv: extract_key(cv[0], values=values, joker="J"))
yield sum(rank * value for rank, (_, value) in enumerate(cards, start=1))
# part 1
values = {card: value for value, card in enumerate("23456789TJQKA")}
cards.sort(key=lambda cv: extract_key(cv[0], values=values))
answer_1 = sum(rank * value for rank, (_, value) in enumerate(cards, start=1))
print(f"answer 1 is {answer_1}")
# part 2
values = {card: value for value, card in enumerate("J23456789TQKA")}
cards.sort(key=lambda cv: extract_key(cv[0], values=values, joker="J"))
answer_2 = sum(rank * value for rank, (_, value) in enumerate(cards, start=1))
print(f"answer 2 is {answer_2}")

Some files were not shown because too many files have changed in this diff Show More