import re from tools.aoc import AOCDay from tools.int_seq import triangular from typing import Any, Iterable def is_Valid(springs: str, groups: list[int]) -> bool: foo = [] h_count = 0 for c in springs: if c == "#": h_count += 1 elif h_count > 0: foo.append(h_count) h_count = 0 if h_count > 0: foo.append(h_count) return foo == groups def sums(length: int, total_sum: int) -> Iterable[tuple]: if length == 1: yield (total_sum,) else: for v in range(total_sum + 1): for p in sums(length - 1, total_sum - v): yield (v,) + p def get_options(groups: list[int], fill: list[int]) -> Iterable[str]: missing_len = sum(fill) print("fill_len", len(fill), "miss_len", missing_len) for opt in sums(len(fill), missing_len): ret = "" for i, c in enumerate(opt): if 0 < i < len(fill) - 1 and c == 0: break ret += " " * c if i < len(groups): ret += "#" * groups[i] else: yield ret def _get_arrangements(springs: str, groups: list[int]) -> int: arrangements = 0 springs = springs.replace(".", " ") after_fill = len(springs) - sum(groups) - len(groups) + 1 if after_fill == 0: return 1 fill = [0] + [1] * (len(groups) - 1) + [after_fill] p = re.compile(springs.replace("?", ".")) for s in get_options(groups, fill): if p.match(s): arrangements += 1 return arrangements def get_arrangements(springs: str, groups: list[int]): springs, not_found = clean_springs(springs, groups) print("not_found", not_found) open_groups = get_open_groups(springs) to_determine = [] for start, group_str in open_groups: print("look in", group_str, "for", [groups[x] for x in not_found]) assigned = [] for need in list(not_found): print("check", need, groups[need]) if groups[need] > len(group_str): continue elif not assigned or sum(assigned) + len(assigned) + groups[need] <= len(group_str): assigned.append(groups[need]) not_found = not_found[1:] else: break print("append", assigned) if assigned: to_determine.append((group_str, assigned)) arrangements = 1 for group_str, values in to_determine: if len(values) == 1 and len(group_str) == values[0]: continue elif sum(values) + len(values) - 1 == len(group_str): continue elif len(values) == 1: arrangements *= len(group_str) - values[0] + 1 else: arrangements *= triangular(len(group_str) - (sum(values) + len(values) - 1) + 1) # print( # group_str, # "with", # values, # "has multiple options:", # triangular(len(group_str) - (sum(values) + len(values) - 1) + 1), # ) if arrangements < 0: print(springs, groups, to_determine) exit(1) return arrangements def clean_springs(springs: str, groups: list[int]) -> (str, list[int]): hashes = [] 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 h in hashes: if h[0] >= sum(groups[:i]) + i and h[0] <= len(springs) - (sum(groups[i:]) + len(groups) - i) + 1: print(h[0], ">", sum(groups[:i]) + i) print(h[0], "<", len(springs) - (sum(groups[i:]) + len(groups) - i) + 1) if h[1] == s: found.append(i) if i == 0: springs = "#" * h[1] + "." + springs[h[0] + h[1] + 1 :] elif i == len(springs) - 1: springs = springs[: -(h[0] + h[1] + 1)] + "." + "#" * h[1] else: springs = springs[: h[0] - 1] + "." + "#" * h[1] + "." + springs[h[0] + h[1] + 1 :] print("found index", i, "value", groups[i], "at", h[0], "new springs", springs) break print("cleaned springs", springs, "found", found) return springs, [x for x in range(len(groups)) if x not in found] def get_open_groups(springs: str) -> list[tuple[int, str]]: open_groups = [] start = None group_str = "" for i, c in enumerate(springs): if c == ".": if "?" in group_str: open_groups.append((start, group_str)) start = None group_str = "" else: if start is None: start = i group_str += c if "?" in group_str: open_groups.append((start, group_str)) return open_groups class Day(AOCDay): inputs = [ [ (21, "input12_test"), (6981, "input12"), ], [ (525152, "input12_test"), (None, "input12"), ], ] def part1(self) -> Any: ans = 0 for line in self.getInput(): springs, groups = line.split() groups = list(map(int, groups.split(","))) f = get_arrangements(springs, groups) print("RESULT", springs, "=>", f) ans += f return ans def part2(self) -> Any: return "" ans = 0 for i, line in enumerate(self.getInput()): springs, groups = line.split() groups = list(map(int, groups.split(","))) springs = "?".join([springs] * 5) groups = groups * 5 f = get_arrangements(springs, groups) print(i + 1, "/", len(self.input), "=>", f) ans += f return ans if __name__ == "__main__": day = Day(2023, 12) day.run(verbose=True)