diff --git a/challenges/2024/07-bridgeRepair/README.md b/challenges/2024/07-bridgeRepair/README.md index 40a02ff..434d0fc 100644 --- a/challenges/2024/07-bridgeRepair/README.md +++ b/challenges/2024/07-bridgeRepair/README.md @@ -3,7 +3,7 @@ Part 1 is: * greater than 450054910499 -Before optimisation (pregenerating then testing operator combiantions using `itertools.product("*+|", length=n)`), runtime looks something like this: +Before optimisation (pregenerating then testing operator combiantions using `itertools.product("*+|", length=n)`), run time looks something like this: ``` Part 1: min 0.2671 seconds, max 0.2818 seconds, avg 0.2755 @@ -12,3 +12,11 @@ Part 2: min 23.2387 seconds, max 24.8753 seconds, avg 23.8805 It also appeared that using string concatenation as opposed to pure mathematical functions for the concatenation operator was marginally faster in Python. +After optimisation (recursive solves working backwards through the numbers), the run time looks something like this: + +``` +Part 1: min 0.0214 seconds, max 0.041 seconds, avg 0.0233 +Part 2: min 0.0215 seconds, max 0.0273 seconds, avg 0.0229 +``` + +The intial version of this solve is in commit `b2fa4b7`. \ No newline at end of file diff --git a/challenges/2024/07-bridgeRepair/main.py b/challenges/2024/07-bridgeRepair/main.py index d1fd3c2..2be6431 100644 --- a/challenges/2024/07-bridgeRepair/main.py +++ b/challenges/2024/07-bridgeRepair/main.py @@ -14,58 +14,51 @@ def parse(instr: str) -> list[tuple[int, list[int]]]: return res -def evaluate(ns: list[int], ops: Iterable[str]): - acc = ns[0] - for i, (v, op) in enumerate(zip(ns[1:], ops)): - if op == "*": - acc *= v - elif op == "+": - acc += v - elif op == "|": - acc = (acc * (10 ** int(math.log10(v) + 1))) + v - else: - raise ValueError(f"unknown operation {op}") - return acc +def ends_with(x: int, y: int) -> bool: + ycard = int(math.log10(y)) + 1 + return (x - y) * (10**-ycard) == int(x * (10**-ycard)) + + +def trim_int(x: int, y: int) -> int: + ycard = int(math.log10(y)) + 1 + return int((x - y) * (10**-ycard)) + + +def solve(target: int, ns: list[int], use_concat: bool = False) -> bool: + v = ns[-1] + rest = ns[:-1] + + if len(rest) == 0: + return target == v + + if target % v == 0: + # this represents a possible multiplication + if solve(int(target / v), rest, use_concat): + return True + + if use_concat and ends_with(target, v): + # this is a possible concatenation + if solve(trim_int(target, v), rest, use_concat): + return True + + # last resort, addition + return solve(target - v, rest, use_concat) def one(instr: str): cases = parse(instr) - - cached_ops = {} - - n = 0 - for (target, numbers) in cases: - num_ops = len(numbers) - 1 - if num_ops not in cached_ops: - cached_ops[num_ops] = tuple(itertools.product("+*", repeat=num_ops)) - - for ops in cached_ops[num_ops]: - v = evaluate(numbers, ops) - if v == target: - n += v - break - - return n + return itertools.accumulate( + target if solve(target, numbers, use_concat=False) else 0 + for (target, numbers) in cases + ) def two(instr: str): cases = parse(instr) - - cached_ops = {} - - n = 0 - for (target, numbers) in cases: - num_ops = len(numbers) - 1 - if num_ops not in cached_ops: - cached_ops[num_ops] = tuple(itertools.product("+*|", repeat=num_ops)) - - for ops in cached_ops[num_ops]: - v = evaluate(numbers, ops) - if v == target: - n += v - break - - return n + return itertools.accumulate( + target if solve(target, numbers, use_concat=True) else 0 + for (target, numbers) in cases + ) def _debug(*args, **kwargs): diff --git a/challenges/2024/README.md b/challenges/2024/README.md index b3d569c..1252231 100644 --- a/challenges/2024/README.md +++ b/challenges/2024/README.md @@ -19,4 +19,5 @@ A day denoted with an asterisk means it has a visualisation. | 03 - Mull It Over | ★ ★ | Python | The first instance of Advent of Parsing this year! | | 04* - Ceres Search | ★ ★ | Python | When it says a cross, it does not mean a plus. | | 05 - Print Queue | ★ ★ | Python | Before you dismiss and idea as being "too simple", make sure you check that it doesn't work. | -| 06 - Guard Gallivant | ★ ★ | Python | oh dear runtime (also I knew what I wanted to do for so long it just took me 3 hours to implement it properly) | \ No newline at end of file +| 06 - Guard Gallivant | ★ ★ | Python | oh dear runtime (also I knew what I wanted to do for so long it just took me 3 hours to implement it properly) | +| 07 - Bridge Repair | ★ ★ | Python | Maths? Backwards?? | \ No newline at end of file diff --git a/challenges/2024/benchmark-graph.png b/challenges/2024/benchmark-graph.png index e834874..5c06d8d 100644 Binary files a/challenges/2024/benchmark-graph.png and b/challenges/2024/benchmark-graph.png differ diff --git a/challenges/2024/benchmarks.jsonl b/challenges/2024/benchmarks.jsonl index 7e8c66a..308361c 100644 --- a/challenges/2024/benchmarks.jsonl +++ b/challenges/2024/benchmarks.jsonl @@ -12,3 +12,5 @@ {"day": 6, "part": 2, "runner": "py", "min": 15.881408452987671, "max": 17.086341857910156, "avg": 16.64130985736847, "n": 6} {"day": 7, "part": 1, "runner": "py", "min": 0.26709485054016113, "max": 0.28178858757019043, "avg": 0.2754525661468506, "n": 5} {"day": 7, "part": 2, "runner": "py", "min": 23.23872661590576, "max": 24.87530255317688, "avg": 23.880544805526732, "n": 5} +{"day": 7, "part": 1, "runner": "py", "min": 0.02138209342956543, "max": 0.04097461700439453, "avg": 0.023260540962219238, "n": 100} +{"day": 7, "part": 2, "runner": "py", "min": 0.021509647369384766, "max": 0.027263402938842773, "avg": 0.022869422435760497, "n": 100}