generated from public/aoc_template
Day 12 - Part 2
This commit is contained in:
parent
55d885f416
commit
2d5553e1c3
207
day12.py
207
day12.py
@ -1,174 +1,29 @@
|
|||||||
import re
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from tools.aoc import AOCDay
|
from tools.aoc import AOCDay
|
||||||
from tools.itertools import combinations_of_sum, len_combinations_of_sum
|
from typing import Any
|
||||||
from typing import Any, Iterator
|
|
||||||
from tqdm.auto import tqdm
|
|
||||||
|
|
||||||
|
|
||||||
def clean_springs(springs: str, groups: tuple[int, ...]) -> str:
|
def get_min_len(groups: tuple[int, ...]) -> int:
|
||||||
hashes = []
|
return sum(groups) + len(groups) - 1
|
||||||
start = None
|
|
||||||
c_size = 0
|
|
||||||
for i, c in enumerate(springs):
|
|
||||||
if c == "#":
|
|
||||||
if start is None:
|
|
||||||
start = i
|
|
||||||
c_size += 1
|
|
||||||
else:
|
|
||||||
if start is not None:
|
|
||||||
hashes.append((start, c_size))
|
|
||||||
start = None
|
|
||||||
c_size = 0
|
|
||||||
|
|
||||||
if start is not None:
|
|
||||||
hashes.append((start, c_size))
|
|
||||||
|
|
||||||
# print("cleaning", springs, "with hashes", hashes, "and groups", groups)
|
|
||||||
found = {}
|
|
||||||
for i, s in enumerate(groups):
|
|
||||||
for hash_start, hash_len in hashes:
|
|
||||||
if sum(groups[: i + 1]) + i + 1 < hash_start < len(springs) - 1 - (sum(groups[i:]) + len(groups) - i):
|
|
||||||
if hash_len == s:
|
|
||||||
# print(
|
|
||||||
# f"PLACING HASH {i=} {groups[:i+1]} -> {sum(groups[:i + 1]) + i} <= {hash_start} <= {len(springs)=} - ({groups[i:]} -> {sum(groups[i:]) + len(groups) - i})"
|
|
||||||
# )
|
|
||||||
found[i] = (hash_start, s)
|
|
||||||
if hash_start == 0:
|
|
||||||
if len(springs) > hash_len:
|
|
||||||
springs = "#" * hash_len + "." + springs[hash_start + hash_len + 1 :]
|
|
||||||
else:
|
|
||||||
springs = "#" * hash_len
|
|
||||||
elif hash_start + hash_len == len(springs) - 1:
|
|
||||||
springs = springs[: -(hash_len + 1)] + "." + "#" * hash_len
|
|
||||||
else:
|
|
||||||
springs = (
|
|
||||||
springs[: hash_start - 1]
|
|
||||||
+ "."
|
|
||||||
+ "#" * hash_len
|
|
||||||
+ "."
|
|
||||||
+ springs[hash_start + hash_len + 1 :]
|
|
||||||
)
|
|
||||||
# print("found index", i, "value", groups[i], "at", hash_start, "new springs", springs,)
|
|
||||||
break
|
|
||||||
|
|
||||||
# print("cleaned springs:", springs)
|
|
||||||
return springs
|
|
||||||
|
|
||||||
|
|
||||||
def get_open_groups(springs: str) -> list[str]:
|
|
||||||
open_groups = re.split(r"\.+", springs)
|
|
||||||
while "" in open_groups:
|
|
||||||
open_groups.remove("")
|
|
||||||
return open_groups
|
|
||||||
|
|
||||||
|
|
||||||
def get_placement_options(groups: tuple[int, ...], open_groups: list[str]) -> Iterator[tuple]:
|
|
||||||
print(
|
|
||||||
f"get_placement_options({len(groups)}, {len(open_groups)}) returns",
|
|
||||||
len_combinations_of_sum(len(groups), len(open_groups)),
|
|
||||||
)
|
|
||||||
t = tqdm(total=len_combinations_of_sum(len(groups), len(open_groups)), leave=False, file=sys.stdout)
|
|
||||||
for options in combinations_of_sum(total_sum=len(groups), length=len(open_groups)):
|
|
||||||
t.update(1)
|
|
||||||
idx = 0
|
|
||||||
possible = True
|
|
||||||
for i, x in enumerate(options):
|
|
||||||
group_str = open_groups[i]
|
|
||||||
if sum(groups[idx : idx + x]) + x - 1 > len(group_str) or ("#" in group_str and x == 0):
|
|
||||||
possible = False
|
|
||||||
break # not possible
|
|
||||||
idx += x
|
|
||||||
|
|
||||||
if possible:
|
|
||||||
yield options
|
|
||||||
|
|
||||||
t.close()
|
|
||||||
|
|
||||||
|
|
||||||
def brute_force_group(group_str: str, groups: tuple[int, ...]) -> int:
|
|
||||||
count = 0
|
|
||||||
p = re.compile(group_str.replace("?", "."))
|
|
||||||
g_len = len(groups)
|
|
||||||
t = tqdm(
|
|
||||||
total=len_combinations_of_sum(len(group_str) - sum(groups), len(groups)),
|
|
||||||
leave=False,
|
|
||||||
file=sys.stdout,
|
|
||||||
postfix="brute_force",
|
|
||||||
)
|
|
||||||
for x in combinations_of_sum(total_sum=len(group_str) - sum(groups), length=len(groups) + 1):
|
|
||||||
t.update()
|
|
||||||
test_str = ""
|
|
||||||
valid = True
|
|
||||||
for i, c in enumerate(x):
|
|
||||||
test_str += " " * c
|
|
||||||
if 0 < i < g_len and c == 0:
|
|
||||||
valid = False
|
|
||||||
break
|
|
||||||
|
|
||||||
if i < len(groups):
|
|
||||||
test_str += groups[i] * "#"
|
|
||||||
|
|
||||||
if valid and p.match(test_str):
|
|
||||||
count += 1
|
|
||||||
|
|
||||||
t.close()
|
|
||||||
return count
|
|
||||||
|
|
||||||
|
|
||||||
def get_arrangements(springs: str, groups: tuple[int, ...]) -> int:
|
|
||||||
if sum(groups) + len(groups) - 1 == len(springs):
|
|
||||||
return 1
|
|
||||||
|
|
||||||
open_groups = get_open_groups(springs)
|
|
||||||
# print(f"{groups} => {springs} =>", list((x, len(x)) for x in open_groups))
|
|
||||||
|
|
||||||
arrangements = 0
|
|
||||||
for placement_option in get_placement_options(groups, open_groups):
|
|
||||||
# print("PLACEMENT OPTION", placement_option)
|
|
||||||
sub_arrangements = 1
|
|
||||||
group_idx = 0
|
|
||||||
for i, x in enumerate(placement_option):
|
|
||||||
if x == 0:
|
|
||||||
continue
|
|
||||||
numbers_to_place = groups[group_idx : group_idx + x]
|
|
||||||
group_idx += x
|
|
||||||
group_str = open_groups[i]
|
|
||||||
cleaned_group_str = clean_springs(group_str, numbers_to_place)
|
|
||||||
# print(f"CLEAN ({i}, {x}, {group_idx})", group_str, "=>", cleaned_group_str, "from", numbers_to_place)
|
|
||||||
if cleaned_group_str != group_str:
|
|
||||||
f = get_arrangements(cleaned_group_str, numbers_to_place)
|
|
||||||
# print("RECURSE CALL", cleaned_group_str, numbers_to_place, "=>", f)
|
|
||||||
sub_arrangements *= f
|
|
||||||
else:
|
|
||||||
f = brute_force_group(group_str, numbers_to_place)
|
|
||||||
# print("MISSING CASE:", group_str, numbers_to_place, "=>", f)
|
|
||||||
sub_arrangements *= f
|
|
||||||
# print("SUB", placement_option, "=>", sub_arrangements)
|
|
||||||
arrangements += sub_arrangements
|
|
||||||
|
|
||||||
return arrangements
|
|
||||||
|
|
||||||
|
|
||||||
class Day(AOCDay):
|
class Day(AOCDay):
|
||||||
inputs = [
|
inputs = [
|
||||||
[
|
[
|
||||||
(101, "input12_debug"),
|
|
||||||
(21, "input12_test"),
|
(21, "input12_test"),
|
||||||
|
(101, "input12_debug"),
|
||||||
(6981, "input12"),
|
(6981, "input12"),
|
||||||
],
|
],
|
||||||
[
|
[
|
||||||
(525152, "input12_test"),
|
(525152, "input12_test"),
|
||||||
(None, "input12"),
|
(4546215031609, "input12"),
|
||||||
],
|
],
|
||||||
]
|
]
|
||||||
|
|
||||||
def parse_input(self, part2: bool = False) -> list[tuple[str, tuple[int, ...]]]:
|
def parse_input(self, part2: bool = False) -> list[tuple[str, tuple[int, ...]]]:
|
||||||
records = []
|
records = []
|
||||||
for line in self.getInput():
|
for line in self.getInput():
|
||||||
springs, groupd = line.split()
|
springs, groups = line.split()
|
||||||
groups = tuple(map(int, groupd.split(",")))
|
groups = tuple(map(int, groups.split(",")))
|
||||||
if part2:
|
if part2:
|
||||||
springs = "?".join([springs] * 5)
|
springs = "?".join([springs] * 5)
|
||||||
groups *= 5
|
groups *= 5
|
||||||
@ -176,24 +31,42 @@ class Day(AOCDay):
|
|||||||
|
|
||||||
return records
|
return records
|
||||||
|
|
||||||
def part1(self) -> Any:
|
def get_arrangements(self, springs: str, groups: tuple[int, ...]) -> int:
|
||||||
ans = 0
|
if (springs, groups) in self.DP:
|
||||||
for springs, groups in self.parse_input():
|
return self.DP[(springs, groups)]
|
||||||
f = get_arrangements(springs, groups)
|
|
||||||
print("RESULT", springs, "=>", f)
|
|
||||||
ans += f
|
|
||||||
|
|
||||||
return ans
|
if len(groups) == 0:
|
||||||
|
return 0
|
||||||
|
|
||||||
|
count = 0
|
||||||
|
for i in range(len(springs) - get_min_len(groups) + 1):
|
||||||
|
sub_springs = springs[i:]
|
||||||
|
hash_len = groups[0]
|
||||||
|
if (
|
||||||
|
(i > 0 and springs[i - 1] == "#")
|
||||||
|
or "." in sub_springs[:hash_len]
|
||||||
|
or (len(sub_springs) > hash_len and sub_springs[hash_len] == "#")
|
||||||
|
):
|
||||||
|
if "#" in springs[:i]:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if len(groups) > 1:
|
||||||
|
count += self.get_arrangements(springs[i + groups[0] + 1 :], groups[1:])
|
||||||
|
else:
|
||||||
|
if "#" in springs[i + groups[0] :]:
|
||||||
|
continue
|
||||||
|
count += 1
|
||||||
|
|
||||||
|
self.DP[(springs, groups)] = count
|
||||||
|
return self.DP[(springs, groups)]
|
||||||
|
|
||||||
|
def part1(self) -> Any:
|
||||||
|
return sum(self.get_arrangements(springs, groups) for springs, groups in self.parse_input())
|
||||||
|
|
||||||
def part2(self) -> Any:
|
def part2(self) -> Any:
|
||||||
ans = 0
|
return sum(self.get_arrangements(springs, groups) for springs, groups in self.parse_input(part2=True))
|
||||||
# explore moving windows; or some other way to make use of DP
|
|
||||||
for i, (springs, groups) in enumerate(self.parse_input(part2=True)):
|
|
||||||
f = get_arrangements(springs, groups)
|
|
||||||
print(i + 1, "/", len(self.input), "=>", f)
|
|
||||||
ans += f
|
|
||||||
|
|
||||||
return ans
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
6
inputs/input12_debug
Normal file
6
inputs/input12_debug
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
??????#??.??.? 2,1,1
|
||||||
|
.??#?#???????.??#? 1,5,2,1,2
|
||||||
|
????##.?????#???? 1,3,1,2,1
|
||||||
|
?.???#?????#?#?? 1,1,2,4
|
||||||
|
??#??????????## 1,2,2
|
||||||
|
?.?????#??? 1,2
|
||||||
Loading…
Reference in New Issue
Block a user