from tools.aoc import AOCDay from tools.int_seq import triangular from typing import Any 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[2]] for x in not_found]) assigned = [] for min_x, max_x, idx_need in list(not_found): print("check", min_x, "<", start, "<", max_x, idx_need, groups[idx_need]) if groups[idx_need] > len(group_str): continue elif (not assigned or sum(assigned) + len(assigned) + groups[idx_need] <= len(group_str)) and ( min_x <= start <= max_x ): assigned.append(groups[idx_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[i] = (h[0], s) if h[0] == 0: springs = "#" * h[1] + "." + springs[h[0] + h[1] + 1 :] elif h[0] + h[1] == len(springs): springs = springs[: -(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) not_found = [] # not_found entry = (min_x, max_x, index) nf = [] last_min = 0 for i in range(len(groups)): if i in found: if nf: for x in nf: not_found.append((last_min, found[i][0], x)) nf = [] last_min = sum(found[i]) else: nf.append(i) if nf: for x in nf: not_found.append((last_min, len(springs), x)) return springs, not_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)