diff --git a/2023/python/_sample.py b/2023/python/_sample.py index c3aea1e..f34bf85 100644 --- a/2023/python/_sample.py +++ b/2023/python/_sample.py @@ -3,6 +3,7 @@ import shared import itertools import functools + # @shared.profile def part1(rows): pass @@ -19,7 +20,7 @@ def main(): part1(rows) print("🕒", elapsed()) - rows = [row for row in shared.load_rows(1,True)] + rows = [row for row in shared.load_rows(1, True)] with shared.elapsed_timer() as elapsed: part2(rows) print("🕒", elapsed()) diff --git a/2023/python/day01.py b/2023/python/day01.py index e089c3a..95076b2 100644 --- a/2023/python/day01.py +++ b/2023/python/day01.py @@ -7,37 +7,37 @@ NUMS = { "one": 1, "two": 2, "six": 6, - "four": 4, "five": 5, "nine": 9, - "seven": 7, "eight": 8, "three": 3, } - + # @shared.profile def part1(rows): total = 0 for row in rows: - numbers = ''.join(filter(str.isdigit, row)) + numbers = "".join(filter(str.isdigit, row)) total += get_total(numbers) print(total) + def get_total(numbers): - tens, ones= int(numbers[0]),int(numbers[-1]) + tens, ones = int(numbers[0]), int(numbers[-1]) return (tens * 10) + ones + def loop_row(row): digits = [] for idx, _ in enumerate(row): if str.isdigit(row[idx]): digits.append(row[idx]) continue - for x in [3,4,5]: - next = row[idx:idx+x] + for x in [3, 4, 5]: + next = row[idx : idx + x] if next in NUMS.keys(): digits.append(str(NUMS[next])) break @@ -52,6 +52,7 @@ def part2(rows): total += get_total(nums) print(total) + def main(): rows = [row for row in shared.load_rows(1)] with shared.elapsed_timer() as elapsed: diff --git a/2023/python/day02.py b/2023/python/day02.py index d007d02..95e5f6c 100644 --- a/2023/python/day02.py +++ b/2023/python/day02.py @@ -1,6 +1,7 @@ import shared from collections import defaultdict + def split_games(row): game, draws = row.split(":") game = int(game.split(" ")[-1]) @@ -10,14 +11,16 @@ def split_games(row): cube = cube.split() count, color = cube colors[color].append(int(count)) - return game,colors - + return game, colors + + MAXES = { "red": 12, - "green":13, + "green": 13, "blue": 14, } + # @shared.profile def part1(rows): applicable = [] @@ -39,14 +42,11 @@ def part2(rows): total = 0 for row in rows: _, colors = split_games(row) - power=1 + power = 1 for vals in colors.values(): power *= max(vals) total += power print(total) - - - def main(): diff --git a/2023/python/day03.py b/2023/python/day03.py index 645f1b6..c39c73f 100644 --- a/2023/python/day03.py +++ b/2023/python/day03.py @@ -5,43 +5,49 @@ import functools from pprint import pprint from collections import defaultdict -SYMBOLS="*%@#+-/$=&" +SYMBOLS = "*%@#+-/$=&" + def get_all_numbers_and_starting_coords(mat): """ - for a coordinate, walks right until it encounters a non digit, - when it does, it adds that number string to a collection, - and starts over, walking right until it continues to next row + for a coordinate, walks right until it encounters a non digit, + when it does, it adds that number string to a collection, + and starts over, walking right until it continues to next row """ nums = [] for y, row in enumerate(mat): x = 0 while x < len(row): - num = matrix.number_starting_at(mat, x,y) + num = matrix.number_starting_at(mat, x, y) if num is None: x += 1 continue - nums.append(((x,y), num)) + nums.append(((x, y), num)) x += len(num) return nums + def get_row_coords(x1, y, length): """ for a given x,y point, it just creates the rest of the x/y coords to the right of this coord for a run """ coords = [] - for x in range(x1, x1+length): - coords.append((x,y)) + for x in range(x1, x1 + length): + coords.append((x, y)) return coords + def process_number(mx, coords, num): line_coords = get_row_coords(*coords, len(num)) for coord in line_coords: - any_symbols = [(cs, v) for cs,v in matrix.get_neighbor_coords(mx, *coord) if v in SYMBOLS] + any_symbols = [ + (cs, v) for cs, v in matrix.get_neighbor_coords(mx, *coord) if v in SYMBOLS + ] if any_symbols: return True, any_symbols[0] return False, None + # @shared.profile def part1(mat): choose = [] @@ -66,8 +72,8 @@ def part2(mat): for coords, num in nums: valid, sc = process_number(mat, coords, num) if valid: - symbols[(sc[0]['r'],sc[0]['c'])].append(num) - + symbols[(sc[0]["r"], sc[0]["c"])].append(num) + total = 0 for splat in splats: neighbors = symbols[splat] @@ -78,7 +84,6 @@ def part2(mat): print(total) - def main(): mat = shared.load_file_char_matrix(shared.get_fname(3)) with shared.elapsed_timer() as elapsed: diff --git a/2023/python/day04.py b/2023/python/day04.py index 297ed5d..9383f7b 100644 --- a/2023/python/day04.py +++ b/2023/python/day04.py @@ -4,6 +4,7 @@ from pprint import pprint pattern = r"Card\s+(\d+): ([\d\s]+) \| ([\d\s]+)" + def load_cards(rows): cards = {} for row in rows: @@ -18,37 +19,38 @@ def load_cards(rows): have=have, have_count=len(have), points=0, - refs=0 + refs=0, ) if have: points = 1 - for _ in range(len(have)-1): + for _ in range(len(have) - 1): points *= 2 - cards[card_number]['points'] = points + cards[card_number]["points"] = points return cards + # @shared.profile def part1(cards): - print(sum(card['points'] for _, card in cards.items())) + print(sum(card["points"] for _, card in cards.items())) # @shared.profile def part2(cards): - card_ids = list(range(len(cards)+1)) + card_ids = list(range(len(cards) + 1)) for card_id, card in cards.items(): - nn = [x+1+card_id for x in range(card['have_count'])] + nn = [x + 1 + card_id for x in range(card["have_count"])] if not nn: continue # One point for initially having the card for idx in nn: - cards[idx]['refs'] += 1 + cards[idx]["refs"] += 1 # X more points for how many times this card is referenced - for _ in range(card['refs']): + for _ in range(card["refs"]): for idx in nn: - cards[idx]['refs'] += 1 + cards[idx]["refs"] += 1 # Sum the ref counts, and then add the total cards in - print(sum(card['refs'] for _, card in cards.items()) +len(cards)) + print(sum(card["refs"] for _, card in cards.items()) + len(cards)) def main(): diff --git a/2023/python/day05.py b/2023/python/day05.py index a3fa796..20eb325 100644 --- a/2023/python/day05.py +++ b/2023/python/day05.py @@ -2,11 +2,12 @@ import shared from pprint import pprint from dataclasses import dataclass + @dataclass class Mapping: dst: int src: int - ran: int # RANGE + ran: int # RANGE maps_parts = [ @@ -19,21 +20,24 @@ maps_parts = [ "humidity-to-location", ] -group = lambda l,s: list(zip(*(iter(l),) * s)) +group = lambda l, s: list(zip(*(iter(l),) * s)) + def line_ints(s): return [int(x) for x in s.split() if s] + def parse_line(line): return Mapping(*line) + class Mapper: def __init__(self, rows): self.parse(rows) def parse(self, rows): seeds = rows.pop(0) - rows.pop(0) # Discard empty to start at a clean slate + rows.pop(0) # Discard empty to start at a clean slate seeds = line_ints(seeds.split(":")[1]) maps = {} current_map = None @@ -52,22 +56,23 @@ class Mapper: possibilities = self.maps[step] for p in possibilities: if source < p.src: - #print(f"\t{source} less than {p.src}") + # print(f"\t{source} less than {p.src}") continue - if source > p.src+ p.ran: - #print(f"\t{source} greater than {p.src}+{p.ran}={p.src+p.ran}") + if source > p.src + p.ran: + # print(f"\t{source} greater than {p.src}+{p.ran}={p.src+p.ran}") continue - #print(f"\t{source} in range") - #print(f"\t{source} is {p.dst + (source - p.src)}") + # print(f"\t{source} in range") + # print(f"\t{source} is {p.dst + (source - p.src)}") return p.dst + (source - p.src) break return source + # @shared.profile def part1(rows): mapper = Mapper(rows[:]) locations = [] - + for seed in mapper.seeds: next = seed for part in maps_parts: @@ -78,16 +83,14 @@ def part1(rows): print(min(locations)) - # @shared.profile def part2(rows): mapper = Mapper(rows[:]) locations = [] seeds = group(mapper.seeds, 2) - for seed_group in seeds: - for seed in range(seed_group[0], seed_group[0]+seed_group[1]): + for seed in range(seed_group[0], seed_group[0] + seed_group[1]): next = seed for part in maps_parts: _part = next @@ -96,7 +99,6 @@ def part2(rows): print(min(locations)) - def main(): rows = [row for row in shared.load_rows(5)] with shared.elapsed_timer() as elapsed: diff --git a/2023/python/day06.py b/2023/python/day06.py index 61600d4..c584af2 100644 --- a/2023/python/day06.py +++ b/2023/python/day06.py @@ -1,16 +1,16 @@ import matrix import shared from dataclasses import dataclass -from functools import reduce # Valid in Python 2.6+, required in Python 3 +from functools import reduce # Valid in Python 2.6+, required in Python 3 import operator - @dataclass class Game: duration: int highscore: int + # @shared.profile def part1(rows): times = [int(r) for r in rows[0].split(":")[1].split(" ") if r] @@ -32,18 +32,21 @@ def part2(rows): ways.append(calculate(game)) print(reduce(operator.mul, ways, 1)) + def search(game, _range): for held in _range: remaining = game.duration - held score = held * remaining if score > game.highscore: - return held + return held + def calculate(game): - starting_win = search(game, range(game.duration+1)) - ending_win = search(game, reversed(range(game.duration+1))) + starting_win = search(game, range(game.duration + 1)) + ending_win = search(game, reversed(range(game.duration + 1))) print(game, starting_win, ending_win) - return ending_win+1-starting_win + return ending_win + 1 - starting_win + def main(): rows = [row for row in shared.load_rows(6)] @@ -51,7 +54,7 @@ def main(): part1(rows) print("🕒", elapsed()) - rows = [row for row in shared.load_rows(6,True)] + rows = [row for row in shared.load_rows(6, True)] with shared.elapsed_timer() as elapsed: part2(rows) print("🕒", elapsed()) diff --git a/2023/python/day07.py b/2023/python/day07.py index 45b9ae2..ae5d701 100644 --- a/2023/python/day07.py +++ b/2023/python/day07.py @@ -5,9 +5,9 @@ from collections import defaultdict from pprint import pprint from typing import List -#STRENGTHS = list(reversed("A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, 2".split(", "))) +# STRENGTHS = list(reversed("A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, 2".split(", "))) STRENGTHS = "A, K, Q, J, T, 9, 8, 7, 6, 5, 4, 3, 2".split(", ") -STRENGTHS_TO_WEIGHT = {k:idx for idx, k in enumerate(STRENGTHS)} +STRENGTHS_TO_WEIGHT = {k: idx for idx, k in enumerate(STRENGTHS)} STRENGTH_HANDS = [ "Five of a Kind", @@ -19,20 +19,20 @@ STRENGTH_HANDS = [ "High Card", ] + def determine_hand(cards): card_set = set(cards) if len(card_set) == 1: return "Five of a Kind" - same_cards = sorted([cards.count(a) for a in card_set]) if len(same_cards) == 2: - if max(same_cards) == 4: + if max(same_cards) == 4: return "Four of a Kind" elif max(same_cards) == 3: return "Full House" elif len(same_cards) == 3: - if max(same_cards) == 3: + if max(same_cards) == 3: return "Three of a Kind" else: return "Two Pair" @@ -40,6 +40,7 @@ def determine_hand(cards): return "One Pair" return "High Card" + @dataclass class Hand: cards: str @@ -72,8 +73,8 @@ def part1(rows): in_order.extend(hands[kind]) total = 0 for rank, card in enumerate(in_order): - total += (rank+1)*card.bid - + total += (rank + 1) * card.bid + print(total) diff --git a/2023/python/day08.py b/2023/python/day08.py index 81af6e4..3603622 100644 --- a/2023/python/day08.py +++ b/2023/python/day08.py @@ -1,85 +1,55 @@ import shared from scanf import scanf +from pprint import pprint +from math import lcm -def check(d): - return all(x[-1] == "Z" for x in d.values()) - -# @shared.profile -def part2(rows): +def setup(rows): MAP = {} instructions = rows.pop(0) rows.pop(0) instructions = instructions.replace("L", "0") instructions = instructions.replace("R", "1") instructions = [int(x) for x in instructions] - print(instructions) for row in rows: entry, left, right = scanf("%s = (%s, %s)", row) - MAP[entry] = [left,right] + MAP[entry] = [left, right] + return MAP, instructions - starting_entries = [] - ending_entries = [] - for key in MAP.keys(): - if key[-1] == "A": - starting_entries.append(key) - if key[-1] == "Z": - ending_entries.append(key) - - - endpoints = {k: k for k in starting_entries} - print(endpoints) - print(check(endpoints)) - print(starting_entries) - print() - count = 0 - while True: - print(count, endpoints, check(endpoints)) - for start in starting_entries: - v = endpoints[start] - idx = count % len(instructions) - l_r = instructions[idx] - _next = MAP[v][l_r] - endpoints[start] = _next - count += 1 - if check(endpoints): - break - print(count) # @shared.profile def part1(rows): - MAP = {} - instructions = rows.pop(0) - rows.pop(0) - instructions = instructions.replace("L", "0") - instructions = instructions.replace("R", "1") - instructions = [int(x) for x in instructions] + _map, _instructions = setup(rows) + count, _ = find("AAA", _instructions, _map) + print(count) - for row in rows: - entry, left, right = scanf("%s = (%s, %s)", row) - MAP[entry] = [left,right] +# @shared.profile +def part2(rows): + _map, _instructions = setup(rows) + starting_points = [k for k in _map.keys() if k[-1] == "A"] + ending_points = [k for k in _map.keys() if k[-1] == "Z"] + ends = [] + for s in starting_points: + count, end = find(s, _instructions, _map, None) + ends.append(count) + print(lcm(*ends)) + + +def find(start, instructions, MAP, target="ZZZ"): count = 0 - current = "AAA" - target = "ZZZ" - steps = [] + current = start while True: - steps.append(current) idx = count % len(instructions) l_r = instructions[idx] current = MAP[current][l_r] count += 1 - if current == target: - steps.append(target) - break - - print(steps) - print(count) - - - - + if target is None: + if current.endswith("Z"): + return count, current + elif current == target: + return count, current def main(): diff --git a/2023/python/matrix.py b/2023/python/matrix.py index 1f76cc0..94d2fbb 100644 --- a/2023/python/matrix.py +++ b/2023/python/matrix.py @@ -67,7 +67,7 @@ def number_starting_at(mx, x, y): return None current += d - next = number_starting_at(mx, x+1, y) + next = number_starting_at(mx, x + 1, y) if next is None: return current return current + next @@ -340,22 +340,22 @@ def highlight(matrix, red=[], green=[], blue=[], blink_green=[]): print a matrix of anything, Falsy values turns to `.` for clarity """ mx = deepcopy(matrix) - for (y, x) in red: + for y, x in red: if (y, x) in blue or (y, x) in green or (y, x) in blink_green: continue new = f"{colors.RED}{mx[y][x]}{colors.ENDC}" mx[y][x] = new - for (y, x) in green: + for y, x in green: if (y, x) in blue or (y, x) in blink_green: continue new = f"{colors.GREEN}{mx[y][x]}{colors.ENDC}" mx[y][x] = new - for (y, x) in blue: + for y, x in blue: if (y, x) in blink_green: continue new = f"{colors.BLUE}{mx[y][x]}{colors.ENDC}" mx[y][x] = new - for (y, x) in blink_green: + for y, x in blink_green: new = f"{colors.BLINK}{colors.GREEN}{mx[y][x]}{colors.ENDC}" mx[y][x] = new print(ppmx(mx, pad=False, space=True, zero="0")) @@ -364,29 +364,29 @@ def highlight(matrix, red=[], green=[], blue=[], blink_green=[]): def draw_shape_at(mx, row, col, shape=None, value=1): if shape is None: raise Exception("Please provide a list of coordinate offsets from Y,X to draw") - for y,x in shape: - mx[row+y][col+x] = value - + for y, x in shape: + mx[row + y][col + x] = value + def collision_at(mx, row, col, shape=None): if shape is None: raise Exception("Please provide a list of coordinate offsets from Y,X to draw") - for y,x in shape: - if mx[row+y][col+x] != 0: + for y, x in shape: + if mx[row + y][col + x] != 0: return True return False + def out_of_bounds(mx, row, col, shape=None): - if shape is None: raise Exception("Please provide a list of coordinate offsets from Y,X to draw") height, width = get_size(mx) - for y,x in shape: - if row+y > height-1: + for y, x in shape: + if row + y > height - 1: return True - if col+x >= width: + if col + x >= width: return True - if col+x < 0: + if col + x < 0: return True return False @@ -396,47 +396,44 @@ def spiral_generator(width, height): l = 0 m = height n = width - - ''' k - starting row index + + """ k - starting row index m - ending row index l - starting column index n - ending column index - i - iterator ''' - - while (k < m and l < n): - + i - iterator """ + + while k < m and l < n: # Print the first row from # the remaining rows for i in range(l, n): - yield (i,k) - #print(a[k][i], end=" ") - + yield (i, k) + # print(a[k][i], end=" ") + k += 1 - + # Print the last column from # the remaining columns for i in range(k, m): - yield (n-1,i) - #print(a[i][n - 1], end=" ") - + yield (n - 1, i) + # print(a[i][n - 1], end=" ") + n -= 1 - + # Print the last row from # the remaining rows - if (k < m): - + if k < m: for i in range(n - 1, (l - 1), -1): - #print(a[m - 1][i], end=" ") - yield (i, m-1) - + # print(a[m - 1][i], end=" ") + yield (i, m - 1) + m -= 1 - + # Print the first column from # the remaining columns - if (l < n): + if l < n: for i in range(m - 1, k - 1, -1): - #print(a[i][l], end=" ") - yield (l,i) - - l += 1 + # print(a[i][l], end=" ") + yield (l, i) + l += 1 diff --git a/2023/python/shared.py b/2023/python/shared.py index 7317895..81cc486 100644 --- a/2023/python/shared.py +++ b/2023/python/shared.py @@ -31,12 +31,12 @@ def minmax(l): def load_rows(day, part2=False): - return [row for row in load(day,part2)] + return [row for row in load(day, part2)] def load(day, part2=False): if part2: - path = Path(get_fname(day)+".part2") + path = Path(get_fname(day) + ".part2") try: return path.read_text().rstrip().split("\n") except FileNotFoundError: @@ -111,58 +111,59 @@ def elapsed_timer(): elapser = lambda: end - start - - -def render_cubes(maxX,maxY,maxZ, my_cubes): +def render_cubes(maxX, maxY, maxZ, my_cubes): from mpl_toolkits.mplot3d import Axes3D import numpy as np import matplotlib.pyplot as plt from mpl_toolkits.mplot3d.art3d import Poly3DCollection - - def cuboid_data(o, size=(1,1,1)): - X = [[[0, 1, 0], [0, 0, 0], [1, 0, 0], [1, 1, 0]], - [[0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0]], - [[1, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]], - [[0, 0, 1], [0, 0, 0], [0, 1, 0], [0, 1, 1]], - [[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]], - [[0, 1, 1], [0, 0, 1], [1, 0, 1], [1, 1, 1]]] + + def cuboid_data(o, size=(1, 1, 1)): + X = [ + [[0, 1, 0], [0, 0, 0], [1, 0, 0], [1, 1, 0]], + [[0, 0, 0], [0, 0, 1], [1, 0, 1], [1, 0, 0]], + [[1, 0, 1], [1, 0, 0], [1, 1, 0], [1, 1, 1]], + [[0, 0, 1], [0, 0, 0], [0, 1, 0], [0, 1, 1]], + [[0, 1, 0], [0, 1, 1], [1, 1, 1], [1, 1, 0]], + [[0, 1, 1], [0, 0, 1], [1, 0, 1], [1, 1, 1]], + ] X = np.array(X).astype(float) for i in range(3): - X[:,:,i] *= size[i] + X[:, :, i] *= size[i] X += np.array(o) return X - - def plotCubeAt(positions,sizes=None,colors=None, **kwargs): - if not isinstance(colors,(list,np.ndarray)): colors=["C0"]*len(positions) - if not isinstance(sizes,(list,np.ndarray)): sizes=[(1,1,1)]*len(positions) + + def plotCubeAt(positions, sizes=None, colors=None, **kwargs): + if not isinstance(colors, (list, np.ndarray)): + colors = ["C0"] * len(positions) + if not isinstance(sizes, (list, np.ndarray)): + sizes = [(1, 1, 1)] * len(positions) g = [] - for p,s,c in zip(positions,sizes,colors): - g.append( cuboid_data(p, size=s) ) - return Poly3DCollection(np.concatenate(g), - facecolors=np.repeat(colors,6, axis=0), **kwargs) - + for p, s, c in zip(positions, sizes, colors): + g.append(cuboid_data(p, size=s)) + return Poly3DCollection( + np.concatenate(g), facecolors=np.repeat(colors, 6, axis=0), **kwargs + ) + N1 = maxX N2 = maxY N3 = maxZ - ma = np.random.choice([0,1], size=(N1,N2,N3), p=[0.99, 0.01]) - x,y,z = np.indices((N1,N2,N3))-.5 - #positions = np.c_[x[ma==1],y[ma==1],z[ma==1]] + ma = np.random.choice([0, 1], size=(N1, N2, N3), p=[0.99, 0.01]) + x, y, z = np.indices((N1, N2, N3)) - 0.5 + # positions = np.c_[x[ma==1],y[ma==1],z[ma==1]] positions = np.c_[my_cubes] - colors= np.random.rand(len(positions),3) - + colors = np.random.rand(len(positions), 3) + fig = plt.figure() - ax = fig.add_subplot(projection='3d') - ax.set_aspect('equal') - - pc = plotCubeAt(positions, colors=colors,edgecolor="k") + ax = fig.add_subplot(projection="3d") + ax.set_aspect("equal") + + pc = plotCubeAt(positions, colors=colors, edgecolor="k") ax.add_collection3d(pc) - - ax.set_xlim([0,maxX]) - ax.set_ylim([0,maxY]) - ax.set_zlim([0,maxZ]) - #plotMatrix(ax, ma) - #ax.voxels(ma, edgecolor="k") - + + ax.set_xlim([0, maxX]) + ax.set_ylim([0, maxY]) + ax.set_zlim([0, maxZ]) + # plotMatrix(ax, ma) + # ax.voxels(ma, edgecolor="k") + plt.show() - -