pnutzh4x0r's recent activity
-
Comment on Day 9: Mirage Maintenance in ~comp.advent_of_code
-
Comment on Day 8: Haunted Wasteland in ~comp.advent_of_code
pnutzh4x0r Response Honestly, I'm not sure if there is a mathematical reason (at least I didn't have one in mind). I sort have just assumed that once it hits a Z-node that it cycles through the same...Response
Honestly, I'm not sure if there is a mathematical reason (at least I didn't have one in mind). I sort have just assumed that once it hits a Z-node that it cycles through the same sequence. If this assumption didn't hold, then you are right... my solution would not always work.
So perhaps better lucky than good :]
-
Comment on Day 8: Haunted Wasteland in ~comp.advent_of_code
pnutzh4x0r Language: Python Part 1 First part was very straight-forward: read the input in and simulate the navigation. Taking advantage of itertools.cycle made cycling through the instructions very easy :]...Language: Python
Part 1
First part was very straight-forward: read the input in and simulate the navigation. Taking advantage of itertools.cycle made cycling through the instructions very easy :]
Network = dict[str, dict[str, str]] def read_instructions(stream=sys.stdin) -> Iterator[str]: return itertools.cycle(stream.readline().strip()) def read_network(stream=sys.stdin) -> Network: network = defaultdict(dict) for line in map(str.strip, stream): if not line: continue source, target_l, target_r = re.findall('[A-Z]{3}', line) network[source] = { 'L': target_l, 'R': target_r, } return network def navigate(instructions: Iterator[str], network: Network) -> int: count = 0 source = 'AAA' target = 'ZZZ' while source != target: source = network[source][next(instructions)] count += 1 return count def main(stream=sys.stdin) -> None: instructions = read_instructions(stream) network = read_network(stream) print(navigate(instructions, network))
Part 2
The second part was also straight-forward: locate the ghosts, and then navigate from each ghost until you hit an element that ends with a 'Z'. The trick to avoid brute-forcing is to realize that the ghosts will cycle (ie. repeat the same paths) if they all don't land on an element that ends with a 'Z' at the same time. To solve the problem, you just ened to calculate the steps for each ghost to reach an endpoint and then compute the lowest common multiple of those counts. Fortunately, Python has
math.lcm
, so not much code was needed!def navigate(instructions: Iterator[str], network: Network, element: str) -> int: count = 0 while not element.endswith('Z'): element = network[element][next(instructions)] count += 1 return count def locate_ghosts(network: Network) -> list[str]: return [element for element in network if element.endswith('A')] def main(stream=sys.stdin) -> None: instructions = read_instructions(stream) network = read_network(stream) ghosts = locate_ghosts(network) counts = [navigate(cycle(instructions), network, ghost) for ghost in ghosts] print(math.lcm(*counts))
-
Comment on Day 7: Camel Cards in ~comp.advent_of_code
pnutzh4x0r Language: Python This was fun. More enjoyable than I initially thought (though I've done card sorting code before). Part 1 This was pretty straightforward: create a histogram of the cards in each...Language: Python
This was fun. More enjoyable than I initially thought (though I've done card sorting code before).
Part 1
This was pretty straightforward: create a histogram of the cards in each hand to determine their type, and if there is a tie-breaker, compare each card pairwise. I use the Counter class from collections to do the counting, and then had a dictionary/table to convert labels to numeric values for comparison. I used a very OOP approach and wrote a magic method for comparing hands and used that with Python's builtin sort. I even got to use
Enum
!LABELS = {l: v for v, l in enumerate('23456789TJQKA', 2)} class HandType(IntEnum): FIVE_OF_A_KIND = 6 FOUR_OF_A_KIND = 5 FULL_HOUSE = 4 THREE_OF_A_KIND = 3 TWO_PAIR = 2 ONE_PAIR = 1 HIGH_CARD = 0 class Hand: def __init__(self, cards=str, bid=str): self.cards = cards self.bid = int(bid) counts = Counter(self.cards) self.type = ( HandType.FIVE_OF_A_KIND if len(counts) == 1 else HandType.FOUR_OF_A_KIND if len(counts) == 2 and any(l for l, count in counts.items() if count == 4) else HandType.FULL_HOUSE if len(counts) == 2 and any(l for l, count in counts.items() if count == 3) else HandType.THREE_OF_A_KIND if len(counts) == 3 and any(l for l, count in counts.items() if count == 3) else HandType.TWO_PAIR if len(counts) == 3 and any(l for l, count in counts.items() if count == 2) else HandType.ONE_PAIR if len(counts) == 4 and any(l for l, count in counts.items() if count == 2) else HandType.HIGH_CARD ) def __lt__(self, other): if self.type == other.type: for s_label, o_label in zip(self.cards, other.cards): if LABELS[s_label] == LABELS[o_label]: continue return LABELS[s_label] < LABELS[o_label] return False return self.type < other.type def __repr__(self): return f'Hand(cards={self.cards},bid={self.bid},type={self.type})' def read_hands(stream=sys.stdin) -> list[Hand]: return [Hand(*line.split()) for line in stream] def main(stream=sys.stdin) -> None: hands = sorted(read_hands(stream)) winnings = sum(rank * hand.bid for rank, hand in enumerate(hands, 1)) print(winnings)
Part 2
For the second part, I just had to add some post-processing code to convert the jokers into actual cards. The key insight is to find the highest and most numerous non-Joker card and convert all the Jokers to that card label.
This had two edge cases that tripped me up:
-
'JJJJJ': There is no other non-Joker here, so I messed up and ranked this the lowest because I ended up removing all counts.
-
'JJJ12': This also messed me up b/c the Joker was the most numerous card, and I didn't handle that properly.
Once I fixed the post-processing code though, everything else remained the same. Below, I only show the parts that changed from Part A.
LABELS = {l: v for v, l in enumerate('J23456789TQKA', 1)} ... class Hand: def __init__(self, cards=str, bid=str): self.cards = cards self.bid = int(bid) counts = Counter(self.cards) if 'J' in counts and len(counts) > 1: max_label = max(set(counts) - {'J'}, key=lambda l: (counts[l], LABELS[l])) counts[max_label] += counts['J'] del counts['J'] self.type = (...)
-
-
Comment on Day 6: Wait For It in ~comp.advent_of_code
pnutzh4x0r Language: Python Part 1 Not much to say... this was pretty straightforward. def race(charge_time: int, total_time: int) -> int: return charge_time * (total_time - charge_time) def...Language: Python
Part 1
Not much to say... this was pretty straightforward.
def race(charge_time: int, total_time: int) -> int: return charge_time * (total_time - charge_time) def main(stream=sys.stdin) -> None: times = [int(t) for t in stream.readline().split(':')[-1].split()] distances = [int(d) for d in stream.readline().split(':')[-1].split()] product = 1 for time, distance in zip(times, distances): ways = [c for c in range(1, time + 1) if race(c, time) > distance] product *= len(ways) print(product)
Part 2
Probably could have done some math, but didn't need to :]
def race(charge_time: int, total_time: int): return charge_time * (total_time - charge_time) def main(stream=sys.stdin) -> None: time = int(''.join(stream.readline().split(':')[-1].split())) distance = int(''.join(stream.readline().split(':')[-1].split())) ways = [c for c in range(1, time + 1) if race(c, time) > distance] print(len(ways))
-
Comment on Day 5: If You Give A Seed A Fertilizer in ~comp.advent_of_code
pnutzh4x0r Language: Python: Part 1 The first part wasn't too bad... once I realized you should store the ranges and not actually generate all the numbers o_O. Seeds = list[int] Map = tuple[int, int, int]...Language: Python:
Part 1
The first part wasn't too bad... once I realized you should store the ranges and not actually generate all the numbers o_O.
Seeds = list[int] Map = tuple[int, int, int] Maps = list[Map] def read_almanac(stream=sys.stdin) -> tuple[Seeds, list[Map]]: seeds: Seeds = [int(seed) for seed in stream.readline().split(':')[-1].split()] maps: Maps = [] for line in map(str.strip, stream): if line.endswith('map:'): maps.append([]) continue try: dst, src, length = map(int, line.split()) except ValueError: continue maps[-1].append((dst, src, length)) return seeds, maps def locate_seed(seed: int, maps: list[Map]) -> int: location = seed for amap in maps: for dst, src, length in amap: if src <= location < src + length: location = dst + (location - src) break return location def main(stream=sys.stdin) -> None: seeds, maps = read_almanac(stream) locations = [locate_seed(seed, maps) for seed in seeds] print(min(locations))
Part 2
This part took me forever, mostly to actually run, but also to fix a few off-by-one errors :|
Fortunately, I was able to re-use most of my code in Part A and just add a new function to search the range of seeds.
Even with
concurrent.futures
and a 24-core machine, it still took me about 30 - 45 minutes to complete with Python (that said, there were only 10 seed range s in the input, so I could only use 10 cores, and of those 5 of the ranges appeared to be really large, leading to a long tail effect).def locate_seeds(srange: Range, maps: list[Map]={}) -> int: seeds = range(srange[0], srange[0] + srange[1]) locator = functools.partial(locate_seed, maps=maps) return min(map(locator, seeds)) # Main Execution def main(stream=sys.stdin) -> None: seeds, maps = read_almanac(stream) locator = functools.partial(locate_seeds, maps=maps) with concurrent.futures.ProcessPoolExecutor() as executor: locations = executor.map(locator, batched(seeds, 2)) print(min(locations))
-
Comment on Day 4: Scratchcards in ~comp.advent_of_code
pnutzh4x0r Language: Python Part 1 Sets really came in handy for this challenge, as did recognizing that you can use powers of two to compute the points for each card. I tried using a regular expression to...Language: Python
Part 1
Sets really came in handy for this challenge, as did recognizing that you can use powers of two to compute the points for each card. I tried using a regular expression to parse each card, but ended up just doing it manually with split :|
Numbers = set[int] Card = list[Numbers] def read_cards(stream=sys.stdin) -> Iterator[Card]: for line in stream: yield [set(map(int, n.split())) for n in line.split(':')[-1].split('|')] def main(stream=sys.stdin) -> None: cards = [numbers & winning for winning, numbers in read_cards(stream)] points = sum(2**(len(card)-1) for card in cards if card) print(points)
Part 2
This took me longer than I wished... I had to think about it carefully before seeing how you can just keep track of the counts of each card, and then when you get to that card, you add to its copies your current count.
def main(stream=sys.stdin) -> None: cards = [numbers & winning for winning, numbers in read_cards(stream)] counts = defaultdict(int) for index, card in enumerate(cards, 1): counts[index] += 1 for copy in range(index + 1, index + len(card) + 1): counts[copy] += counts[index] print(sum(counts.values()))
-
Comment on Day 3: Gear Ratios in ~comp.advent_of_code
pnutzh4x0r (edited )LinkLanguage: Python Classic AoC grid problem... Tedious as usual, but very doable. Took my time and I'm pretty happy with the result. :] Part 1 For the first part, I decided to break the problem...Language: Python
Classic AoC grid problem... Tedious as usual, but very doable. Took my time and I'm pretty happy with the result. :]
Part 1
For the first part, I decided to break the problem into: 1. Reading the schematic, 2. Finding the numbers, 3. Finding the parts. This was useful for Part 2 as I could re-use my
read_schematic
andfind_numbers
functions.Two things I typically do for grid problems:
- Pad the grid so you can avoid annoying boundary checks.
- I have a
DIRECTIONS
list I loop through so I can check easily check the neighbors.
Schematic = list[str] Number = tuple[int, int, int] DIRECTIONS = ( (-1, -1), (-1, 0), (-1, 1), ( 0, -1), ( 0, 1), ( 1, -1), ( 1, 0), ( 1, 1), ) def read_schematic(stream=sys.stdin) -> Schematic: schematic = [line.strip() for line in stream] columns = len(schematic[0]) + 2 return [ '.'*columns, *['.' + line + '.' for line in schematic], '.'*columns, ] def is_symbol(s: str) -> bool: return not (s.isdigit() or s == '.') def find_numbers(schematic: Schematic) -> Iterator[Number]: rows = len(schematic) columns = len(schematic[0]) for r in range(1, rows): for number in re.finditer(r'[0-9]+', schematic[r]): yield (r, *number.span()) def find_parts(schematic: Schematic, numbers: Iterator[Number]) -> Iterator[int]: for r, c_head, c_tail in numbers: part = int(schematic[r][c_head:c_tail]) for c in range(c_head, c_tail): neighbors = (schematic[r + dr][c + dc] for dr, dc in DIRECTIONS) if any(is_symbol(neighbor) for neighbor in neighbors): yield part break def main(stream=sys.stdin) -> None: schematic = read_schematic(stream) numbers = find_numbers(schematic) parts = find_parts(schematic, numbers) print(sum(parts))
Part 2
For the second part, I just found the stars, and then I found the gears by checking if the stars are next to two numbers (which I had found previously).
def find_stars(schematic: Schematic) -> Iterator[Star]: rows = len(schematic) columns = len(schematic[0]) for r in range(1, rows): for c in range(1, columns): token = schematic[r][c] if token == '*': yield (r, c) def find_gears(schematic: Schematic, stars: Iterator[Star], numbers: list[Number]) -> Iterator[int]: for star_r, star_c in stars: gears = [ int(schematic[number_r][number_c_head:number_c_tail]) for number_r, number_c_head, number_c_tail in numbers if any(star_r + dr == number_r and number_c_head <= (star_c + dc) < number_c_tail for dr, dc in DIRECTIONS) ] if len(gears) == 2: yield gears[0] * gears[1] def main(stream=sys.stdin) -> None: schematic = read_schematic(stream) numbers = find_numbers(schematic) stars = find_stars(schematic) gears = find_gears(schematic, stars, list(numbers)) print(sum(gears))
-
Comment on Day 2: Cube Conundrum in ~comp.advent_of_code
pnutzh4x0r This was mostly straightforward... basically just parsing input. Here are my condensed solutions in Python Part 1 Game = dict[str, int] RED_MAX = 12 GREEN_MAX = 13 BLUE_MAX = 14 def...This was mostly straightforward... basically just parsing input. Here are my condensed solutions in Python
Part 1
Game = dict[str, int] RED_MAX = 12 GREEN_MAX = 13 BLUE_MAX = 14 def read_game(stream=sys.stdin) -> Game: try: game_string, cubes_string = stream.readline().split(':') except ValueError: return {} game: Game = defaultdict(int) game['id'] = int(game_string.split()[-1]) for cubes in cubes_string.split(';'): for cube in cubes.split(','): count, color = cube.split() game[color] = max(game[color], int(count)) return game def read_games(stream=sys.stdin) -> Iterator[Game]: while game := read_game(stream): yield game def is_valid_game(game: Game) -> bool: return all([ game['red'] <= RED_MAX, game['green'] <= GREEN_MAX, game['blue'] <= BLUE_MAX, ]) def main(stream=sys.stdin) -> None: valid_games = filter(is_valid_game, read_games(stream)) sum_of_ids = sum(game['id'] for game in valid_games) print(sum_of_ids)
Part 2
For the second part, the main parsing remainded the same. I just had to change what I did with the games I read.
def power(game: Game) -> int: return game['red'] * game['green'] * game['blue'] def main(stream=sys.stdin) -> None: sum_of_sets = sum(power(game) for game in read_games(stream)) print(sum_of_sets)
-
Comment on Day 1: Trebuchet?! in ~comp.advent_of_code
pnutzh4x0r (edited )LinkOnce again, I'm doing it in Python: Repo Link Part 1 This was pretty straightforward: just used a list comprehension to extract the digits. def read_values(stream=sys.stdin) -> list[list[str]]:...Once again, I'm doing it in Python: Repo Link
Part 1
This was pretty straightforward: just used a list comprehension to extract the digits.
def read_values(stream=sys.stdin) -> list[list[str]]: return [ list(filter(str.isdigit, line)) for line in stream ] def main(stream=sys.stdin) -> None: values = read_values(stream) total = sum( int(digits[0] + digits[-1]) for digits in values ) print(total)
Part 2
This was trickier than expected as you had to account for overlapping words such as
eightwo
. Fortunately, I only had to change myread_values
function.def read_values(stream=sys.stdin) -> list[list[str]]: values = [] for line in stream: letters = line.strip() digits = [] for index, letter in enumerate(letters): if letter.isdigit(): digits.append(letter) for value, digit in enumerate(DIGITS, 1): if letters[index:].startswith(digit): digits.append(str(value)) break values.append(digits) return values
Part 2 (Update)
While showering, I thought of a way to make Part 2 more functional by using
map
to convert the words to digits before filtering as I did in Part 1:def word_to_digit(s: str, index: int) -> str: for value, digit in enumerate(DIGITS, 1): if s[index:].startswith(digit): return str(value) return s[index] def read_values(stream=sys.stdin) -> list[list[str]]: return [ list(filter(str.isdigit, map(lambda p: word_to_digit(line, p[0]), enumerate(line)))) for line in stream ]
-
Comment on Linux gamers? If so, what games? in ~games
pnutzh4x0r As others have mentioned, Linux is my only OS (Pop!_OS for laptops and desktops, Ubuntu for servers). I haven't played games recently, but with Steam/Proton, I've been able to play Age of Empires...As others have mentioned, Linux is my only OS (Pop!_OS for laptops and desktops, Ubuntu for servers).
I haven't played games recently, but with Steam/Proton, I've been able to play Age of Empires II (HD/DE), Bloons TD 6, Moonlighter, Wizard of Legend, Kingdom Rush... so non-AAA games without much trouble.
My son also uses Linux on his laptop, and usually plays Minecraft (Java), Kerbal Space Program, Kingdoms and Castles, and up until recently... Roblox (RIP).
For the most part, gaming on Linux works... but I wouldn't say it's always smooth or reliable. It's much better now than say a decade ago (or even 5 years ago)... but it's very dependent on your game library or what you are interested in.
-
Comment on Day 10: Syntax Scoring in ~comp
pnutzh4x0r JavaScript Repo Link Part 1 This was pretty straightforward, especially since in one of the classes I teach, I use a very similar example to have students practice with using a stack :). const...JavaScript
Part 1
This was pretty straightforward, especially since in one of the classes I teach, I use a very similar example to have students practice with using a stack :).
const OPENING_BRACKETS = { '(': ')', '[': ']', '{': '}', '<': '>', }; const CLOSING_BRACKETS = { ')': 3, ']': 57, '}': 1197, '>': 25137, }; function parse_line(line) { let stack = []; for (let bracket of line) { if (bracket in OPENING_BRACKETS) { stack.push(bracket); } else { if (!stack.length) { return [0, stack]; } let previous = stack.pop(); if (bracket != OPENING_BRACKETS[previous]) { return [CLOSING_BRACKETS[bracket], null]; } } } return [0, stack]; } let lines = IO.readlines(line => line.split('')); let part_a = lines.reduce((s, l) => s + parse_line(l)[0], 0); IO.print(`Part A: ${part_a}`);
Part 2
For this part, I had a lot of fun using JavaScripts functional programming capabilities.
const COMPLETE_POINTS = { '(': 1, '[': 2, '{': 3, '<': 4, }; function complete_line(line) { return line.reverse().reduce((s, b) => 5*s + COMPLETE_POINTS[b], 0); } let scores = lines.map(parse_line) .filter(p => !p[0]) .map(p => complete_line(p[1])) .sort((a, b) => (a - b)); let part_b = scores[Math.floor(scores.length / 2)]; IO.print(`Part B: ${part_b}`);
-
Comment on Day 9: Smoke Basin in ~comp
pnutzh4x0r JavaScript Repo Link Part 1 This was pretty straightforward. The only trick I used was to pad the heatmap to avoid bounds checking. const MAX = 9; const DIRECTIONS = [ [ 0, -1], // Left [ 0, 1],...JavaScript
Part 1
This was pretty straightforward. The only trick I used was to pad the heatmap to avoid bounds checking.
const MAX = 9; const DIRECTIONS = [ [ 0, -1], // Left [ 0, 1], // Right [-1, 0], // Up [ 1, 0] // Down ]; function padmap(map) { let padrow = new Array(map[0].length + 2).fill(MAX); return [padrow, ...map.map(row => [MAX, ...row, MAX]), padrow]; } function find_low_points(heatmap) { let low_points = []; for (let row = 1; row < heatmap.length - 1; row++) { for (let col = 1; col < heatmap[0].length - 1; col++) { let is_lower = DIRECTIONS.filter(([dr, dc]) => heatmap[row][col] < heatmap[row + dr][col + dc] ); if (is_lower.length == DIRECTIONS.length) { low_points.push([heatmap[row][col], row, col]); } } } return low_points; } let heatmap = padmap(IO.readlines(line => line.split('').map(n => parseInt(n, 10)))); let low_points = find_low_points(heatmap); let part_a = low_points.reduce((t, p) => t + p[0] + 1, 0); IO.print(`Part A: ${part_a}`);
Part 2
For this part, once you have the low points, all you need to do is perform a walk or traversal from each low point until you run out of locations (ie. no more
9
s). For this, I used a traditional iterative frontier based approach.In terms of JavaScript there were still a few gotchas that I am still learning. For instance, adding an Array to a Set works, but it compares the reference/pointer instead of the contents, so you can never find the Array again. To work around this, I gave each location or node an integer identifier based on its row and column.
function find_basins(heatmap, low_points) { return low_points.map(([_, row, col]) => walk_heatmap(heatmap, [row, col])); } function walk_heatmap(heatmap, start) { let frontier = [start]; let visited = new Set(); while (frontier.length) { let [row, col] = frontier.shift(); let node_id = row*heatmap[0].length + col; if (visited.has(node_id)) { continue; } visited.add(node_id); DIRECTIONS.forEach(([dr, dc]) => { if (heatmap[row + dr][col + dc] < MAX) { frontier.push([row + dr, col + dc]); } }); } return visited.size; } let basins = find_basins(heatmap, low_points); let part_b = basins.sort((a, b) => b - a).slice(0, 3).reduce((s, n) => s * n, 1); IO.print(`Part B: ${part_b}`);
-
Comment on Day 8: Seven Segment Search in ~comp
pnutzh4x0r JavaScript Repo Link This one (well, mostly Part 2) made my head hurt. I wrote my first version in Python and then translated into JavaScript. Part 1 First part was straightforward, just compute...JavaScript
This one (well, mostly Part 2) made my head hurt. I wrote my first version in Python and then translated into JavaScript.
Part 1
First part was straightforward, just compute the length of each output string and check it against the known lengths.
const KNOWN_LENGTHS = [2, 4, 3, 7]; let entries = IO.readlines(line => line.split(' | ')); let part_a = entries.reduce((total, entry) => { let [_, outputs] = entry; outputs = outputs.split(' '); matches = outputs.filter(output => KNOWN_LENGTHS.includes(output.length)).length; return total + matches; }, 0); IO.print(`Part A: ${part_a}`);
Part 2
This part stumped me for a while, but I eventually saw the need to use sets for the patterns. In Python, sets are well supported and include many useful operations such as
&
,|
,-
. While JavaScript has a set, it lacks this operators, which makes them not very useful. Because of this, I decided to use integer bitsets in JavaScript with bitwise logical operators.function ctoi(c) { // Character to ASCII integer return c.charCodeAt(0); } function stoi(s) { // Signal string to integer bitset return s.split('').reduce((t, c) => t + (1<<(ctoi(c) - ctoi('a'))), 0); } let entries = IO.readlines(line => line.split(' | ')) let part_b = entries.reduce((total, entry) => { let [patterns, outputs] = entry; let bitsets = new Array(10).fill(0); patterns = patterns.split(' '); outputs = outputs.split(' ').map(stoi); // Base sets bitsets[1] = stoi(patterns.filter(pattern => pattern.length == 2)[0]); bitsets[4] = stoi(patterns.filter(pattern => pattern.length == 4)[0]); bitsets[7] = stoi(patterns.filter(pattern => pattern.length == 3)[0]); bitsets[8] = stoi(patterns.filter(pattern => pattern.length == 7)[0]); // Sets of length 5: 2, 3, 5 let fives = patterns.filter(pattern => pattern.length == 5).map(stoi); bitsets[2] = fives.filter(bitset => (bitset | bitsets[4]) == bitsets[8])[0]; bitsets[5] = fives.filter(bitset => (bitset | bitsets[2]) == bitsets[8])[0]; bitsets[3] = fives.filter(bitset => (bitset != bitsets[2] && bitset != bitsets[5])); // Sets of length 6: 0, 6, 9 let sixes = patterns.filter(pattern => pattern.length == 6).map(stoi); bitsets[0] = sixes.filter(bitset => (bitset | bitsets[5]) == bitsets[8])[0]; bitsets[6] = sixes.filter(bitset => (bitset | bitsets[7]) == bitsets[8])[0]; bitsets[9] = sixes.filter(bitset => (bitset != bitsets[0] && bitset != bitsets[6])); // Construct mapping let mapping = bitsets.reduce((d, b, i) => (d[b] = i, d), {}); // Construct digit let display = outputs.reduce((total, output) => total + mapping[output].toString(), ''); // Reduction return total + parseInt(display, 10); }, 0); IO.print(`Part B: ${part_b}`);
-
Comment on Day 7: The Treachery of Whales in ~comp
pnutzh4x0r JavaScript Repo Link Decided to start with JavaScript first today, rather than doing it in Python and then translating. Part 1 I had done a similar problem in the past, so I knew that the optimal...JavaScript
Decided to start with JavaScript first today, rather than doing it in Python and then translating.
Part 1
I had done a similar problem in the past, so I knew that the optimal position would just be the median of the given positions. To compute this, I just sorted the numbers and took the item in the middle. From there, it was just a matter of computing the fuel costs. The main thing that tripped me up was that JavaScript sorts everything alphabetically... even an array of numbers... unless you specify a custom comparison function. Pretty terrible.
let crabs = IO.readline(line => line.split(',').map(n => parseInt(n, 10))); let sorted = crabs.sort((a, b) => a - b); let optimal = sorted[crabs.length / 2]; let movements = crabs.map(crab => Math.abs(optimal - crab)); let fuel_costs = movements.reduce((a, b) => a + b, 0); IO.print(`Part A: ${fuel_costs}`);
Part 2
For the second part, I couldn't think of anything clever, so I just brute-forced it (ie. try every possible position and see which one yielded the minimum fuel cost). My
distance
function initially used a while loop to compute the sum from1
tod
, but then I remembered there was a formula for this (and looked it up online).With this, I learned about JavaScripts
Math.max
andMath.min
functions, along with theMath.SAFE_MAX_INTEGER
constant. It has its quirks, but JavaScript is growing on me.function distance(d) { return d * (d + 1) / 2; } let crabs = IO.readline(line => line.split(',').map(n => parseInt(n, 10))); let min_position = Math.min(...crabs); let max_position = Math.max(...crabs); let min_fuel_costs = Number.MAX_SAFE_INTEGER; for (let position = min_position; position < max_position; position++) { let movements = crabs.map(crab => distance(Math.abs(position - crab))); let fuel_costs = movements.reduce((a, b) => a + b, 0); min_fuel_costs = Math.min(min_fuel_costs, fuel_costs); } IO.print(`Part B: ${min_fuel_costs}`);
-
Comment on Day 6: Lanternfish in ~comp
pnutzh4x0r JavaScript (Both Days) I wrote my first solution in Python using a dictionary (I figured the brute-force method would not work right away). Once I got that to work, I re-wrote my solution in...JavaScript (Both Days)
I wrote my first solution in Python using a dictionary (I figured the brute-force method would not work right away). Once I got that to work, I re-wrote my solution in JavaScript using a fixed sized array (thanks to @deckard for the inspiration).
One cool thing is learning about JavaScript's spread operator... hopefully I'll get more comfortable with JavaScript and can opt into trying that first (rather than doing it in Python and then translating it to JavaScript).
// Constants const MAX_DAYS = 256; const RESET_DAY = 6; const BIRTH_DAY = 8; // Functions function simulate_day(old_table) { newborns = old_table[BIRTH_DAY]; resets = old_table[0] + old_table[RESET_DAY + 1]; births = old_table[0]; return [...old_table.splice(1, RESET_DAY), resets, newborns, births]; } function simulate_days(table, days = MAX_DAYS) { for (let day = 0; day < days; day++) { table = simulate_day(table); } return table.reduce((a, b) => a + b, 0); } // Main Execution let fish_table = new Array(BIRTH_DAY + 1).fill(0); IO.readline(line => line.split(',')).forEach(number => fish_table[parseInt(number)]++); IO.print(`Sample: ${simulate_days(fish_table.slice(0), 18)}`); IO.print(`Part A: ${simulate_days(fish_table.slice(0), 80)}`); IO.print(`Part B: ${simulate_days(fish_table.slice(0), 256)}`);
-
Comment on Day 4: Giant Squid in ~comp
pnutzh4x0r Python Repo Link Part 1 Solution was straightforward... it was just a matter of reading the board and performing the appropriate operations. One trick I used to make checking for BINGO easier was...Python
Part 1
Solution was straightforward... it was just a matter of reading the board and performing the appropriate operations. One trick I used to make checking for BINGO easier was to mark cells with
-1
and then I could just sum up the row or column and check if it was-5
to see if there was BINGO.# Constants ROWS = 5 COLS = 5 MARK = -1 # Functions def read_boards(): while sys.stdin.readline(): try: yield [list(map(int, sys.stdin.readline().split())) for _ in range(ROWS)] except (IOError, ValueError): break def mark_board(board, number): for rindex, row in enumerate(board): for cindex, cell in enumerate(row): if cell == number: board[rindex][cindex] = MARK def check_board(board): return any(sum(row) == ROWS * MARK for row in board) or \ any(sum(board[row][col] for row in range(ROWS)) == COLS * MARK for col in range(COLS)) def dump_board(board): for row in board: print(' '.join(map(str, row))) def sum_board(board): return sum( sum(number for number in row if number >= 0) for row in board ) # Main Execution def main(): numbers = [int(n) for n in sys.stdin.readline().split(',')] boards = list(read_boards()) winner = None while numbers and winner is None: number = numbers.pop(0) for board in boards: mark_board(board, number) if check_board(board): winner = board total = sum_board(winner) print(number*total)
Part 2
Only my main function changes here. Instead of looking for the first board, I simply remove boards as they win and just keep track of the last winner.
def main(): numbers = [int(n) for n in sys.stdin.readline().split(',')] boards = list(read_boards()) winner = None while numbers and boards: number = numbers.pop(0) active = [] for board in boards: mark_board(board, number) if check_board(board): winner = board else: active.append(board) boards = active total = sum_board(winner) print(number*total)
-
Comment on Day 3: Binary Diagnostic in ~comp
pnutzh4x0r JavaScript Repo Link Python is my primary language, but in order to get some practice, I've also been trying out some JavaScript. Part 1 function count_columns(report) { let counts = new...JavaScript
Python is my primary language, but in order to get some practice, I've also been trying out some JavaScript.
Part 1
function count_columns(report) { let counts = new Array(report[0].length).fill(0); report.forEach(number => { for (let index in number) { counts[index] += parseInt(number[index]); } }); return counts; } function most_common(counts, total) { return counts.map(count => 2*count >= total ? '1' : '0') } function least_common(counts, total) { return counts.map(count => 2*count < total ? '1' : '0') } let report = IO.readlines(); let counts = count_columns(report) let gamma = parseInt(most_common(counts, report.length).join(''), 2); let epsilon = parseInt(least_common(counts, report.length).join(''), 2); IO.print(gamma * epsilon);
Part 2
Below, I'm only including the parts that are different from Part 1.
function filter_numbers(report, aggregator) { for (let index = 0; report.length > 1; index++) { let counts = count_columns(report); let common = aggregator(counts, report.length); report = report.filter(number => number[index] == common[index]); } return parseInt(report[0], 2); } let report = IO.readlines(); let oxygen = filter_numbers(report, most_common); let carbon = filter_numbers(report, least_common); IO.print(oxygen * carbon);
-
Comment on Day 1: Sonar Sweep in ~comp
pnutzh4x0r Python Repo Link Part 1 List comprehensions, yay! report = [int(depth) for depth in sys.stdin] increases = sum(1 for i in range(1, len(report)) if report[i] > report[i - 1]) print(increases) Part...Python
Part 1
List comprehensions, yay!
report = [int(depth) for depth in sys.stdin] increases = sum(1 for i in range(1, len(report)) if report[i] > report[i - 1]) print(increases)
Part 2
This took me longer than it should have, but once I realized that if I could just form the windows separately, then I could re-use the method for counting the increases from Part A.
report = [int(depth) for depth in sys.stdin] windows = [sum(report[i:i+3]) for i in range(0, len(report) - 2)] increases = sum(1 for i in range(1, len(windows)) if windows[i] > windows[i - 1]) print(increases)
-
Comment on Day 25: Combo Breaker in ~comp
pnutzh4x0r Python Repo Link Part 1 Pretty straightforward today (just brute-force). Merry Christmas everyone! # Constants SUBJECT_NUMBER = 7 # Functions def transform(subject_number, loop_size): value = 1...Python
Part 1
Pretty straightforward today (just brute-force). Merry Christmas everyone!
# Constants SUBJECT_NUMBER = 7 # Functions def transform(subject_number, loop_size): value = 1 for _ in range(loop_size): value *= subject_number value %= 20201227 return value def find_loop_size(target): value = 1 loop_size = 0 while value != target: value *= SUBJECT_NUMBER value %= 20201227 loop_size += 1 return loop_size # Main Execution def main(): card_public_key = 10705932 door_public_key = 12301431 card_loop_size = find_loop_size(card_public_key) door_loop_size = find_loop_size(door_public_key) encryption_key = transform(card_public_key, door_loop_size) print(encryption_key) if __name__ == '__main__': main()
Language: Python
Part 1
Pretty straightforward. Took advantage of
itertools.pairwise
.Part 2
Only thing that changed from the first part was that I used
functools.reduce
to take the differences of the first elements of the generated sequences (rather than the sum of the last elements for Part 1).GitHub Repo