From 5a5e8431292b0a53367dbc3a84db6a1542399932 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Anders=20Engl=C3=B6f=20Ytterstr=C3=B6m?= Date: Tue, 19 Dec 2023 14:24:28 +0100 Subject: [PATCH] 2015, day 16-25 (first completed calendar!) (#11) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Solve 2015:16 "Aunt Sue" * Make 2023:08 future compatible Code used to work with another version of python. * Solve 2015:17 "No such Thing as Too much" * Solve 2015:18 "Like a GIF For Your Yard" Also solve 2015:06 just in case, was just a ref in the end. * Solve 2015:19 "Medicine for Rudolph" * Solve 2015:20 "Infinite Elves and Infinite Houses" * Solve 2023:21 "RPG Simulator 20XX" * Solve 2015:22 "Wizard Simulator 20XX" * Solve 2015:23 "Opening the Turing Lock" * Solve 2015:25 "Let it Snow" Wrote p2rc and rc2p just for academic purposes. Puzzles could be solved anyway. * Solve 2015:24 "Hangs in the Balance" --------- Co-authored-by: Anders Englöf Ytterström --- 2015-python/aoc.py | 66 ++++---------- 2015-python/solutions/day_06.py | 50 +++++++++++ 2015-python/solutions/day_08.py | 5 +- 2015-python/solutions/day_16.py | 56 ++++++++++++ 2015-python/solutions/day_17.py | 36 ++++++++ 2015-python/solutions/day_18.py | 79 +++++++++++++++++ 2015-python/solutions/day_19.py | 51 +++++++++++ 2015-python/solutions/day_20.py | 51 +++++++++++ 2015-python/solutions/day_21.py | 100 +++++++++++++++++++++ 2015-python/solutions/day_22.py | 151 ++++++++++++++++++++++++++++++++ 2015-python/solutions/day_23.py | 65 ++++++++++++++ 2015-python/solutions/day_24.py | 50 +++++++++++ 2015-python/solutions/day_25.py | 150 +++++++++++++++++++++++++++++++ 13 files changed, 857 insertions(+), 53 deletions(-) create mode 100644 2015-python/solutions/day_06.py create mode 100644 2015-python/solutions/day_16.py create mode 100644 2015-python/solutions/day_17.py create mode 100644 2015-python/solutions/day_18.py create mode 100644 2015-python/solutions/day_19.py create mode 100644 2015-python/solutions/day_20.py create mode 100644 2015-python/solutions/day_21.py create mode 100644 2015-python/solutions/day_22.py create mode 100644 2015-python/solutions/day_23.py create mode 100644 2015-python/solutions/day_24.py create mode 100644 2015-python/solutions/day_25.py diff --git a/2015-python/aoc.py b/2015-python/aoc.py index ade61ff..32a78ae 100644 --- a/2015-python/aoc.py +++ b/2015-python/aoc.py @@ -26,65 +26,29 @@ class Solution(BaseSolution): def __str__(self): return "Day {day}: {name}" + def solve(self, pi): + return pi + + def solve_again(self, pi): + return pi + def parse_input(self, data): return data.strip() - def solve(self, puzzle_input): - return True - - def solve_again(self, puzzle_input): - return True - if __name__ == "__main__": solution = Solution() - solution.show_results() -""".strip().format( - day=day_no, day_no=day_no.zfill(2), name=name - ) + # solution.show_results() + + dummy = \"\"\" + replace me + \"\"\".strip() + + solution.solve(dummy) + # solution.solve_again(dummy) +""".strip().format(day=day_no, day_no=day_no.zfill(2), name=name) + "\n" ) - print(f"- creating tests/test_day_{day_no.zfill(2)}.py") - with open("tests/test_day_{}.py".format(day_no.zfill(2)), "w") as t: - t.write( - """ -import unittest - -from solutions.day_{day_no} import Solution - - -class Day{day_no}TestCase(unittest.TestCase): - def setUp(self): - self.solution = Solution() - self.puzzle_input = self.solution.parse_input( - \"\"\" - - \"\"\" - ) - - def test_parse_puzzle_input(self): - data = \"\"\" - - \"\"\" - assert self.solution.parse_input(data) == "" - - # def test_solve_first_part(self): - # assert self.solution.solve(self.puzzle_input) == True - - # def test_solve_second_part(self): - # assert self.solution.solve_again(self.puzzle_input) == True - - -if __name__ == "__main__": - unittest.main() -""".strip().format( - day_no=day_no.zfill(2) - ) - + "\n" - ) - print(f"- creating empty inputs/{day_no.zfill(2)}.txt") - with open("inputs/{}.txt".format(day_no.zfill(2)), "w") as i: - i.write("") print( f""" diff --git a/2015-python/solutions/day_06.py b/2015-python/solutions/day_06.py new file mode 100644 index 0000000..2252fd9 --- /dev/null +++ b/2015-python/solutions/day_06.py @@ -0,0 +1,50 @@ +import re +from solutions import BaseSolution +from collections import defaultdict + + +class Solution(BaseSolution): + input_file = "06.txt" + + def __str__(self): + return "Day 6: Probably a Fire Hazard" + + def parse_input(self, data): + return data.strip() + + def solve(self, puzzle_input): + m = defaultdict(bool) + for ase in puzzle_input.splitlines(): + p = re.match(r"^(.+) (\d+),(\d+)\D+(\d+),(\d+)", ase).groups() + a, sx, sy, ex, ey = p + for r in range(int(sy), int(ey) + 1): + for c in range(int(sx), int(ex) + 1): + match a: + case "toggle": + m[(r, c)] = not m[(r, c)] + case "turn on": + m[(r, c)] = True + case "turn off": + m[(r, c)] = False + return sum(m.values()) + + def solve_again(self, puzzle_input): + m = defaultdict(int) + for ase in puzzle_input.splitlines(): + p = re.match(r"^(.+) (\d+),(\d+)\D+(\d+),(\d+)", ase).groups() + a, sx, sy, ex, ey = p + for r in range(int(sy), int(ey) + 1): + for c in range(int(sx), int(ex) + 1): + match a: + case "toggle": + m[(r, c)] += 2 + case "turn on": + m[(r, c)] += 1 + case "turn off": + m[(r, c)] = max(m[(r, c)] - 1, 0) + return sum(m.values()) + + +if __name__ == "__main__": + solution = Solution() + solution.show_results() diff --git a/2015-python/solutions/day_08.py b/2015-python/solutions/day_08.py index 60f5109..b0a1ed2 100644 --- a/2015-python/solutions/day_08.py +++ b/2015-python/solutions/day_08.py @@ -1,4 +1,5 @@ import re + from solutions import BaseSolution @@ -20,8 +21,8 @@ class Solution(BaseSolution): def solve_again(self, puzzle_input): def lendiff(line): - encoded = f'"{line.replace("\\", "\\\\").replace('"', '\\"')}"' - return len(encoded) - len(line) + encoded = line.replace("\\", "\\\\").replace('"', '\\"') + return len(encoded) + 2 - len(line) return sum(map(lendiff, puzzle_input)) diff --git a/2015-python/solutions/day_16.py b/2015-python/solutions/day_16.py new file mode 100644 index 0000000..b8ef486 --- /dev/null +++ b/2015-python/solutions/day_16.py @@ -0,0 +1,56 @@ +import re +from collections import defaultdict + +from solutions import BaseSolution + +S = { + "children": 3, + "cats": 7, + "samoyeds": 2, + "pomeranians": 3, + "akitas": 0, + "vizslas": 0, + "goldfish": 5, + "trees": 3, + "cars": 2, + "perfumes": 1, +} + + +class Solution(BaseSolution): + input_file = "16.txt" + + def __str__(self): + return "Day 16: Aunt Sue" + + def parse_input(self, data): + return data.strip() + + def solve(self, puzzle_input): + s = defaultdict(int) + for i, l in enumerate(puzzle_input.splitlines(), 1): + for kv in re.findall(r"\w+: \d+", l): + k, v = kv.split(": ") + v = int(v) + s[i] += 1 if k in S and S[k] == v else 0 + return max(s.items(), key=lambda x: x[1])[0] + + def solve_again(self, puzzle_input): + s = defaultdict(int) + for i, l in enumerate(puzzle_input.splitlines(), 1): + for kv in re.findall(r"\w+: \d+", l): + k, v = kv.split(": ") + v = int(v) + match k: + case "cats" | "trees": + s[i] += 1 if k in S and S[k] < v else 0 + case "pomeranians" | "goldfish": + s[i] += 1 if k in S and S[k] > v else 0 + case _: + s[i] += 1 if k in S and S[k] == v else 0 + return max(s.items(), key=lambda x: x[1])[0] + + +if __name__ == "__main__": + solution = Solution() + solution.show_results() diff --git a/2015-python/solutions/day_17.py b/2015-python/solutions/day_17.py new file mode 100644 index 0000000..15eb57a --- /dev/null +++ b/2015-python/solutions/day_17.py @@ -0,0 +1,36 @@ +from collections import defaultdict +from itertools import combinations + +from solutions import BaseSolution + + +class Solution(BaseSolution): + input_file = "17.txt" + + def __str__(self): + return "Day 17: No Such Thing as Too Much" + + def parse_input(self, data): + return data.strip() + + def solve(self, puzzle_input, g=150): + return self._solve(puzzle_input, g)[0] + + def solve_again(self, puzzle_input, g=150): + return self._solve(puzzle_input, g)[1] + + def _solve(self, puzzle_input, g=150): + buckets = sorted(list(map(int, puzzle_input.split())), reverse=True) + t = 0 + r = defaultdict(int) + for i in range(len(buckets)): + for c in combinations(buckets, i): + if sum(c) == g: + t += 1 + r[i] += 1 + return t, min(r.items(), key=lambda kv: kv[0])[1] + + +if __name__ == "__main__": + solution = Solution() + solution.show_results() diff --git a/2015-python/solutions/day_18.py b/2015-python/solutions/day_18.py new file mode 100644 index 0000000..0a71c05 --- /dev/null +++ b/2015-python/solutions/day_18.py @@ -0,0 +1,79 @@ +from collections import defaultdict +from solutions import BaseSolution + + +N = [ + (-1, -1), + (-1, 0), + (-1, 1), + (0, -1), + (0, 1), + (1, -1), + (1, 0), + (1, 1), +] + + +class Solution(BaseSolution): + input_file = "18.txt" + + def __str__(self): + return "Day 18: Like a GIF For Your Yard" + + def parse_input(self, data): + return data.strip() + + def solve(self, data): + m = defaultdict(bool) + rows = data.split() + h = len(rows) + w = len(rows[0]) + for r in range(h): + for c in range(w): + m[(r, c)] = 1 if rows[r][c] == "#" else 0 + for _ in range(100): + nm = defaultdict(bool) + for r in range(h): + for c in range(w): + n = sum(m[(r + nr, c + nc)] for nr, nc in N) + match m[(r, c)]: + case 1: + nm[(r, c)] = n in (2, 3) + case 0: + nm[(r, c)] = n == 3 + m = nm + return sum(m.values()) + + def solve_again(self, data): + m = defaultdict(bool) + rows = data.split() + h = len(rows) + w = len(rows[0]) + for r in range(h): + for c in range(w): + m[(r, c)] = 1 if rows[r][c] == "#" else 0 + m[(0, 0)] = True + m[(99, 0)] = True + m[(99, 99)] = True + m[(0, 99)] = True + for _ in range(100): + nm = defaultdict(bool) + for r in range(h): + for c in range(w): + if (r, c) in [(0, 0), (99, 0), (99, 99), (0, 99)]: + nm[(r, c)] = True + continue + n = sum(m[(r + nr, c + nc)] for nr, nc in N) + match m[(r, c)]: + case 1: + nm[(r, c)] = n in (2, 3) + case 0: + nm[(r, c)] = n == 3 + m = nm + return sum(m.values()) + return True + + +if __name__ == "__main__": + solution = Solution() + solution.show_results() diff --git a/2015-python/solutions/day_19.py b/2015-python/solutions/day_19.py new file mode 100644 index 0000000..119b990 --- /dev/null +++ b/2015-python/solutions/day_19.py @@ -0,0 +1,51 @@ +from collections import defaultdict +from solutions import BaseSolution +import re +from random import shuffle, choice + + +class Solution(BaseSolution): + input_file = "19.txt" + + def __str__(self): + return "Day 19: Medicine for Rudolph" + + def solve(self, pi): + ml, s = pi.split("\n\n") + o = set() + for l in ml.splitlines(): + k, v = l.split(" => ") + for mo in re.finditer(k, s): + a, b = mo.span() + o.add(s[:a] + v + s[b:]) + return len(o) + + def solve_again(self, pi): + m = {} + ml, e = pi.split("\n\n") + for l in ml.splitlines(): + k, v = l.split(" => ") + m[v] = k + p2 = 0 + while p2 == 0: + s = e + c = 0 + os = "" + keys = list(m.keys()) + shuffle(keys) + while os != s: + os = s + for k in keys: + while k in s: + c += s.count(k) + s = s.replace(k, m[k]) + p2 = int(s == "e") * c + return p2 + + def parse_input(self, data): + return data.strip() + + +if __name__ == "__main__": + solution = Solution() + solution.show_results() diff --git a/2015-python/solutions/day_20.py b/2015-python/solutions/day_20.py new file mode 100644 index 0000000..ca97d7c --- /dev/null +++ b/2015-python/solutions/day_20.py @@ -0,0 +1,51 @@ +from collections import defaultdict +from solutions import BaseSolution + + +class Solution(BaseSolution): + input_file = "20.txt" + + def __str__(self): + return "Day 20: Infinite Elves and Infinite Houses" + + def solve(self, pi): + S = 36_000_000 + # hn = 831601 + # x1 = defaultdict(int) + # for i in range(1, hn): + # for n in range(0, hn, i): + # if n == 0: + # continue + # x1[n] += i * 10 + # for k, v in x1.items(): + # if v >= S: + # print(k, v) + hn = 885000 + hc = 50 + x2 = defaultdict(int) + for i in range(1, hn): + for n in range(0, hc * i + 1, i): + if n == 0: + continue + x2[n] += i * 11 + # p1 = max(x1.items(), key=lambda y: y[1])[0] + p2 = min(filter(lambda x: x[1] >= S, x2.items()), key=lambda y: y[0])[0] + return 0, p2 + + def solve_again(self, pi): + return pi + + def parse_input(self, data): + return data.strip() + + +if __name__ == "__main__": + solution = Solution() + # solution.show_results() + + dummy = """ + replace me + """.strip() + + print(solution.solve(dummy)) + # solution.solve_again(dummy) diff --git a/2015-python/solutions/day_21.py b/2015-python/solutions/day_21.py new file mode 100644 index 0000000..a4c4df2 --- /dev/null +++ b/2015-python/solutions/day_21.py @@ -0,0 +1,100 @@ +import re +from itertools import product +from math import inf + +from solutions import BaseSolution + +SHOP = [ + [ + ("Dagger", 8, 4, 0), + ("Shortsword", 10, 5, 0), + ("Warhammer", 25, 6, 0), + ("Longsword", 40, 7, 0), + ("Greataxe", 74, 8, 0), + ], + [ + ("No armor", 0, 0, 0), + ("Leather", 13, 0, 1), + ("Chainmail", 31, 0, 2), + ("Splintmail", 53, 0, 3), + ("Bandedmail", 75, 0, 4), + ("Platemail", 102, 0, 5), + ], + [ + ("No ring", 0, 0, 0), + ("Damage +1", 25, 1, 0), + ("Damage +2", 50, 2, 0), + ("Damage +3", 100, 3, 0), + ("Defense +1", 20, 0, 1), + ("Defense +2", 40, 0, 2), + ("Defense +3", 80, 0, 3), + ("Damage +1 & Damage +2", 75, 3, 0), + ("Damage +1 & Damage +3", 125, 4, 0), + ("Damage +1 & Defense +1", 45, 1, 1), + ("Damage +1 & Defense +2", 65, 1, 2), + ("Damage +1 & Defense +3", 105, 1, 3), + ("Damage +2 & Damage +3", 150, 5, 0), + ("Damage +2 & Defense +1", 70, 2, 1), + ("Damage +2 & Defense +2", 90, 2, 2), + ("Damage +2 & Defense +3", 130, 2, 3), + ("Damage +3 & Defense +1", 120, 3, 1), + ("Damage +3 & Defense +2", 140, 3, 2), + ("Damage +3 & Defense +3", 180, 3, 3), + ("Defense +1 & Defense +2", 60, 0, 3), + ("Defense +1 & Defense +3", 100, 0, 4), + ("Defense +2 & Defense +3", 120, 0, 5), + ], +] + + +class Solution(BaseSolution): + input_file = "21.txt" + + def __str__(self): + return "Day 21: RPG Simulator 20XX" + + def solve(self, pi): + o = self._solve(pi) + return o[0] + + def solve_again(self, pi): + o = self._solve(pi) + return o[1] + + def parse_input(self, data): + return data.strip() + + def _solve(self, pi): + boss = re.findall(r"\d+", pi) + l = inf + m = 0 + for ph, pd, pa, c in self._gearup(): + bh, bd, ba = [int(v) for v in boss] + while bh > 0 and ph > 0: + bh -= max(1, pd - ba) + if bh <= 0: + break + ph -= max(1, bd - pa) + if ph > 0: + l = min(l, c) + else: + m = max(m, c) + return l, m + + def _gearup(self): + g = [] + for w, a, r in product(*SHOP): + g.append( + ( + 100, + sum([w[2], a[2], r[2]]), + sum([w[3], a[3], r[3]]), + sum([w[1], a[1], r[1]]), + ) + ) + return g + + +if __name__ == "__main__": + solution = Solution() + solution.show_results() diff --git a/2015-python/solutions/day_22.py b/2015-python/solutions/day_22.py new file mode 100644 index 0000000..6fc654d --- /dev/null +++ b/2015-python/solutions/day_22.py @@ -0,0 +1,151 @@ +import random +import re +from math import inf + +from solutions import BaseSolution + +C = { + "M": 53, + "D": 73, + "S": 113, + "P": 173, + "R": 229, +} + +D = { + "S": 6, + "P": 6, + "R": 5, +} + +E = { + "P": 3, + "R": 101, + "S": 7, +} + + +class Solution(BaseSolution): + input_file = "22.txt" + + def __str__(self): + return "Day 22: Wizard Simulator 20XX" + + def solve(self, pi): + o = self._solve(pi) + return o[0] + + def solve_again(self, pi): + o = self._solve(pi) + return o[1] + + def parse_input(self, data): + return data.strip() + + def _solve(self, data): + p12 = [] + boss = re.findall(r"\d+", data) + shp, smp = 50, 500 + for hd in [False, True]: + x = 100_000 + l = inf + for _i in range(x): + bhp, bd = [int(v) for v in boss] + t, ms = 0, 0 + hp, mp = shp, smp + e = {"S": 0, "P": 0, "R": 0} + while hp > 0 and bhp > 0 and mp > 0: + if hd and t % 2 == 0: + hp -= 1 + if hp == 0: + break + b = {"d": bd, "e": 0} + p = {"e": 0} + if e["P"] > 0: + e["P"] -= 1 + bhp -= E["P"] + if bhp <= 0: + break + if e["R"] > 0: + e["R"] -= 1 + mp += E["R"] + if e["S"] > 0: + e["S"] -= 1 + p["e"] += E["S"] + if t % 2 == 0: + a = self._guess(mp, e) + if a in "RSP": + e[a] = D[a] + dp, db = self._turn(t, p, b, a) + mp -= C[a] + ms += C[a] + else: + dp, db = self._turn(t, p, b) + hp += dp + bhp += db + t += 1 + if bhp <= 0 and mp >= 0: + l = min(l, ms) + p12.append(l) + assert p12[0] == 953 + assert p12[1] == 1289 + return p12 + + @staticmethod + def _guess(mp, e): + v = False + while not v: + a = random.choice("MDPRS") + match a: + case "S" | "P" | "R": + v = e[a] == 0 + case _: + v = True + return a + + @staticmethod + def _turn(t, p, b, a=None): + """Take turns + + >>> Solution._turn(1, {}, {"d": 5}) + (-5, 0) + >>> Solution._turn(1, {}, {"d": 5, "e": -2}) + (-5, -2) + >>> Solution._turn(1, {"e": 7}, {"d": 5}) + (-1, 0) + >>> Solution._turn(2, {}, {}, "M") + (0, -4) + >>> Solution._turn(2, {}, {}, "D") + (2, -2) + >>> Solution._turn(2, {}, {}, "S") + (0, 0) + >>> Solution._turn(2, {}, {}, "P") + (0, 0) + >>> Solution._turn(2, {}, {}, "R") + (0, 0) + """ + ped = p.get("e", 0) + bed = b.get("e", 0) + if t % 2 == 1: + return min(ped - b["d"], -1), 0 + bed + else: + match a: + case "D": + return 2, -2 + case "M": + return 0, -4 + case "S": + return 0, 0 + case "R": + return 0, 0 + case "P": + return 0, 0 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + solution = Solution() + solution.show_results() diff --git a/2015-python/solutions/day_23.py b/2015-python/solutions/day_23.py new file mode 100644 index 0000000..a2b63f3 --- /dev/null +++ b/2015-python/solutions/day_23.py @@ -0,0 +1,65 @@ +from solutions import BaseSolution + + +class Solution(BaseSolution): + input_file = "23.txt" + + def __str__(self): + return "Day 23: Opening the Turing Lock" + + def _solve(self, pi): + p12 = [] + for a in [0, 1]: + m = { + "a": a, + "b": 0, + } + p = [l.split() for l in pi.splitlines()] + i = 0 + while i < len(p): + v, rv, *a = p[i] + rv = rv.split(",")[0] + match v: + case "hlf": + m[rv] //= 2 + i += 1 + case "tpl": + m[rv] *= 3 + i += 1 + case "inc": + m[rv] += 1 + i += 1 + case "jmp": + i += int(rv) + case "jie": + i += int(a[0]) if int(m[rv]) % 2 == 0 else 1 + case "jio": + i += int(a[0]) if int(m[rv]) == 1 else 1 + p12.append(m["b"]) + return p12 + + def solve(self, pi): + o = self._solve(pi) + return o[0] + + def solve_again(self, pi): + o = self._solve(pi) + return o[1] + + def parse_input(self, data): + return data.strip() + + +if __name__ == "__main__": + solution = Solution() + solution.show_results() + + dummy = """ +inc a +jio a, +2 +tpl a +inc a +""".strip() + + # solution.solve(dummy) + # solution.solve_again(dummy) diff --git a/2015-python/solutions/day_24.py b/2015-python/solutions/day_24.py new file mode 100644 index 0000000..7a4a008 --- /dev/null +++ b/2015-python/solutions/day_24.py @@ -0,0 +1,50 @@ +import re +from itertools import combinations +from math import prod + +from solutions import BaseSolution + + +class Solution(BaseSolution): + input_file = "24.txt" + + def __str__(self): + return "Day 24: Hangs in the Balance" + + def solve(self, pi): + o = self._solve(pi) + return o[0] + + def solve_again(self, pi): + o = self._solve(pi) + return o[1] + + def _solve(self, pi): + p12 = [] + for g in [3, 4]: + w = [int(s) for s in re.findall(r"\d+", pi)] + gs = sum(w) // g + c = [] + for r in range(len(w)): + for n in combinations(w, r=r): + if sum(n) == gs: + c.append(n) + if c: + break + p12.append(min(map(prod, c))) + return p12 + + def parse_input(self, data): + return data.strip() + + +if __name__ == "__main__": + solution = Solution() + solution.show_results() + + dummy = """ + 1,2,3,4,5,7,8,9,10,11 + """.strip() + + solution.solve(dummy) + # solution.solve_again(dummy) diff --git a/2015-python/solutions/day_25.py b/2015-python/solutions/day_25.py new file mode 100644 index 0000000..2baba87 --- /dev/null +++ b/2015-python/solutions/day_25.py @@ -0,0 +1,150 @@ +import re + +from solutions import BaseSolution + + +class Solution(BaseSolution): + input_file = "25.txt" + + def __str__(self): + return "Day 25: Let It Snow" + + def solve(self, pi): + r, c = [int(s) - 1 for s in re.findall(r"\d+", pi)] + v = 20151125 + seen = set() + e = rc2p(r, c) + t = 0 + while v not in seen: + seen.add(v) + v = v * 252533 % 33554393 + t += 1 + for _ in range(e % t - 1): + v = v * 252533 % 33554393 + assert v == 9132360 + return v + + def solve_again(self, pi): + return "God jul!" + + def parse_input(self, data): + return data.strip() + + +def p2rc(p): + """ + Get row and column for a storage position + + >>> p2rc(1) + (0, 0) + >>> p2rc(2) + (1, 0) + >>> p2rc(3) + (0, 1) + >>> p2rc(4) + (2, 0) + >>> p2rc(5) + (1, 1) + >>> p2rc(6) + (0, 2) + >>> p2rc(7) + (3, 0) + >>> p2rc(8) + (2, 1) + >>> p2rc(9) + (1, 2) + >>> p2rc(10) + (0, 3) + >>> p2rc(11) + (4, 0) + >>> p2rc(12) + (3, 1) + >>> p2rc(13) + (2, 2) + >>> p2rc(14) + (1, 3) + >>> p2rc(15) + (0, 4) + >>> p2rc(16) + (5, 0) + >>> p2rc(17) + (4, 1) + >>> p2rc(18) + (3, 2) + >>> p2rc(19) + (2, 3) + >>> p2rc(20) + (1, 4) + >>> p2rc(21) + (0, 5) + """ + v = 0 + i = 0 + while v < p: + i += 1 + v = sum(range(i + 1)) + + r, c = 0, i - 1 + for _ in range(v - p): + r += 1 + c -= 1 + return (r, c) + + +def rc2p(r, c): + """ + Get storage position for coordinate row R, column C + + >>> rc2p(0, 0) + 1 + >>> rc2p(1, 0) + 2 + >>> rc2p(0, 1) + 3 + >>> rc2p(2, 0) + 4 + >>> rc2p(1, 1) + 5 + >>> rc2p(0, 2) + 6 + >>> rc2p(3, 0) + 7 + >>> rc2p(2, 1) + 8 + >>> rc2p(1, 2) + 9 + >>> rc2p(0, 3) + 10 + >>> rc2p(4, 0) + 11 + >>> rc2p(3, 1) + 12 + >>> rc2p(2, 2) + 13 + >>> rc2p(1, 3) + 14 + >>> rc2p(0, 4) + 15 + >>> rc2p(5, 0) + 16 + >>> rc2p(4, 1) + 17 + >>> rc2p(3, 2) + 18 + >>> rc2p(2, 3) + 19 + >>> rc2p(1, 4) + 20 + >>> rc2p(0, 5) + 21 + """ + return sum(range(r + c + 1)) + c + 1 + + +if __name__ == "__main__": + import doctest + + doctest.testmod() + + solution = Solution() + solution.show_results()