From a972b775d59d54070cf194380fc523ae16f5832f Mon Sep 17 00:00:00 2001 From: Luc Rubio Date: Wed, 4 Dec 2024 23:04:09 +0100 Subject: [PATCH] Indent AoC 2021 with 2 spaces --- aoc2021/src/day01/solution.py | 58 +++--- aoc2021/src/day02/solution.py | 164 ++++++++--------- aoc2021/src/day03/solution.py | 154 ++++++++-------- aoc2021/src/day04/solution.py | 192 ++++++++++---------- aoc2021/src/day05/solution.py | 254 +++++++++++++-------------- aoc2021/src/day06/solution.py | 140 +++++++-------- aoc2021/src/day07/solution.py | 86 ++++----- aoc2022/src/day02/python/solution.py | 4 +- 8 files changed, 526 insertions(+), 526 deletions(-) diff --git a/aoc2021/src/day01/solution.py b/aoc2021/src/day01/solution.py index 73e8a3c..c240eb1 100644 --- a/aoc2021/src/day01/solution.py +++ b/aoc2021/src/day01/solution.py @@ -15,37 +15,37 @@ def part_one(depth_report: Sequence[int]) -> int: - """AOC 2021 Day 1 Part 1. + """AOC 2021 Day 1 Part 1. - Args: - depth_report: A sonar sweep report which consist of depth measurements. - Returns: - Number of times a depth measurement increases. - """ - count_depth_increases = 0 - last_depth = None - for depth in depth_report: - count_depth_increases += 1 if (last_depth and depth > last_depth) else 0 - last_depth = depth - return count_depth_increases + Args: + depth_report: A sonar sweep report which consist of depth measurements. + Returns: + Number of times a depth measurement increases. + """ + count_depth_increases = 0 + last_depth = None + for depth in depth_report: + count_depth_increases += 1 if (last_depth and depth > last_depth) else 0 + last_depth = depth + return count_depth_increases def part_two(depths: Sequence[int]) -> int: - """AOC 2021 Day 1 Part 2. + """AOC 2021 Day 1 Part 2. - Args: - depths: A sonar sweep report which consist of depth measurements. - Returns: - Number of times a depth measurement increases in sums of a - three-measurement sliding window. - """ - count_depth_increases = 0 - last_window_sum = None - for pos, depth in enumerate(depths): - if pos == 0 or pos == len(depths) - 1: - continue # Sliding window is out of bounds. - window_sum = depths[pos - 1] + depths[pos] + depths[pos + 1] - if last_window_sum and window_sum > last_window_sum: - count_depth_increases += 1 - last_window_sum = window_sum - return count_depth_increases + Args: + depths: A sonar sweep report which consist of depth measurements. + Returns: + Number of times a depth measurement increases in sums of a + three-measurement sliding window. + """ + count_depth_increases = 0 + last_window_sum = None + for pos, depth in enumerate(depths): + if pos == 0 or pos == len(depths) - 1: + continue # Sliding window is out of bounds. + window_sum = depths[pos - 1] + depths[pos] + depths[pos + 1] + if last_window_sum and window_sum > last_window_sum: + count_depth_increases += 1 + last_window_sum = window_sum + return count_depth_increases diff --git a/aoc2021/src/day02/solution.py b/aoc2021/src/day02/solution.py index 2e6a823..25b3330 100644 --- a/aoc2021/src/day02/solution.py +++ b/aoc2021/src/day02/solution.py @@ -17,109 +17,109 @@ def _parse(instruction: str) -> Tuple[str, int]: - """Parses an instruction. - Args: - instruction: A raw input instruction. - Returns: - command and command value. - """ - command, value = instruction.split(' ') - # TODO: handle incorrect instructions. - if not command: - raise ValueError('Missing command!') - if not value: - raise ValueError('Missing value!') - return command, int(value) + """Parses an instruction. + Args: + instruction: A raw input instruction. + Returns: + command and command value. + """ + command, value = instruction.split(' ') + # TODO: handle incorrect instructions. + if not command: + raise ValueError('Missing command!') + if not value: + raise ValueError('Missing value!') + return command, int(value) @dataclass class Submarine(ABC): - """Represents a generic submarine.""" - horizontal_position: int = 0 - depth: int = 0 + """Represents a generic submarine.""" + horizontal_position: int = 0 + depth: int = 0 - @abstractmethod - def execute(self, instruction: str) -> None: - pass + @abstractmethod + def execute(self, instruction: str) -> None: + pass @dataclass class PartOneSubmarine(Submarine): - """Submarine that follows specs from part two .""" - - def execute(self, instruction: str) -> None: - """Executes an instruction. - Args: - instruction: A raw input instruction. - """ - command, value = _parse(instruction=instruction) - if command == 'forward': - self.horizontal_position += value - elif command == 'down': - self.depth += value - elif command == 'up': - self.depth -= value - else: - raise ValueError(f'Unrecognized command {command}.') + """Submarine that follows specs from part two .""" + + def execute(self, instruction: str) -> None: + """Executes an instruction. + Args: + instruction: A raw input instruction. + """ + command, value = _parse(instruction=instruction) + if command == 'forward': + self.horizontal_position += value + elif command == 'down': + self.depth += value + elif command == 'up': + self.depth -= value + else: + raise ValueError(f'Unrecognized command {command}.') @dataclass class PartTwoSubmarine(Submarine): - """Submarine that follows specs from part one.""" - aim: int = 0 - - def execute(self, instruction: str) -> None: - """Executes an instruction. - Args: - instruction: A raw input instruction. - """ - command, value = _parse(instruction=instruction) - if command == 'forward': - self.horizontal_position += value - self.depth += self.aim * value - elif command == 'down': - self.aim += value - elif command == 'up': - self.aim -= value - else: - raise ValueError(f'Unrecognized command {command}.') + """Submarine that follows specs from part one.""" + aim: int = 0 + + def execute(self, instruction: str) -> None: + """Executes an instruction. + Args: + instruction: A raw input instruction. + """ + command, value = _parse(instruction=instruction) + if command == 'forward': + self.horizontal_position += value + self.depth += self.aim * value + elif command == 'down': + self.aim += value + elif command == 'up': + self.aim -= value + else: + raise ValueError(f'Unrecognized command {command}.') def _run(planned_course: Sequence[str], submarine: Submarine) -> int: - """Runs the planned course and computes result. + """Runs the planned course and computes result. - Returns: - Final horizontal position multiplied by final depth. - """ - for instruction in planned_course: - submarine.execute(instruction=instruction) - return submarine.horizontal_position * submarine.depth + Returns: + Final horizontal position multiplied by final depth. + """ + for instruction in planned_course: + submarine.execute(instruction=instruction) + return submarine.horizontal_position * submarine.depth def part_one(planned_course: Sequence[str]) -> int: - """AOC 2021 Day 2 Part 1. + """AOC 2021 Day 2 Part 1. - Args: - planned_course: sequence of instructions: - - 'forward X' increases the horizontal position by X units. - - 'down X' increases the depth by X units. - - 'up X' decreases the depth by X units. - Returns: - Final horizontal position multiplied by final depth. - """ - return _run(planned_course=planned_course, submarine=PartOneSubmarine()) + Args: + planned_course: sequence of instructions: + - 'forward X' increases the horizontal position by X units. + - 'down X' increases the depth by X units. + - 'up X' decreases the depth by X units. + Returns: + Final horizontal position multiplied by final depth. + """ + return _run(planned_course=planned_course, submarine=PartOneSubmarine()) def part_two(planned_course: Sequence[str]) -> int: - """AOC 2021 Day 2 Part 2. - - Args: - planned_course: sequence of instructions: - - 'forward X' increases horizontal position by X units and increases depth - by the aim multiplied by X. - - 'down X' increases aim by X units. - - 'up X' decreases aim by X units. - Returns: - Final horizontal position multiplied by final depth. - """ - return _run(planned_course=planned_course, submarine=PartTwoSubmarine()) + """AOC 2021 Day 2 Part 2. + + Args: + planned_course: sequence of instructions: + - 'forward X' increases horizontal position by X units and increases depth + by the aim multiplied by X. + - 'down X' increases aim by X units. + - 'up X' decreases aim by X units. + Returns: + Final horizontal position multiplied by final depth. + """ + return _run(planned_course=planned_course, submarine=PartTwoSubmarine()) diff --git a/aoc2021/src/day03/solution.py b/aoc2021/src/day03/solution.py index 173c4ec..60c3d0c 100644 --- a/aoc2021/src/day03/solution.py +++ b/aoc2021/src/day03/solution.py @@ -15,98 +15,98 @@ def _calculate_offset(report: Sequence[str], position: int) -> int: - """Calculates offset in a given position of a report. + """Calculates offset in a given position of a report. - Args: - report: A sequence of binary numbers. - position: Position to calculate offset. - Returns: - Positive value indicates that there are more ones than zeroes, negative - value that thare are more zeroes than ones, and zero means equal number - of ones and zeroes. - """ - offset = 0 - for binary_str in report: - offset += 1 if binary_str[position] == '1' else -1 - return offset + Args: + report: A sequence of binary numbers. + position: Position to calculate offset. + Returns: + Positive value indicates that there are more ones than zeroes, negative + value that thare are more zeroes than ones, and zero means equal number + of ones and zeroes. + """ + offset = 0 + for binary_str in report: + offset += 1 if binary_str[position] == '1' else -1 + return offset def _reduce(report: Sequence[str], position: int, match: int) -> Sequence[str]: - """Reduces a list of binary numbers based on position and match. + """Reduces a list of binary numbers based on position and match. - Args: - report: A sequence of binary numbers. - position: Digit position to evaluate. - match: Binary value to match, 0 or 1. - Returns: - A reduced copy of the report containing the matching binary numbers. - """ - reduced_report = [] - for binary_str in report: - if int(binary_str[position]) == match: - reduced_report.append(binary_str) - return reduced_report + Args: + report: A sequence of binary numbers. + position: Digit position to evaluate. + match: Binary value to match, 0 or 1. + Returns: + A reduced copy of the report containing the matching binary numbers. + """ + reduced_report = [] + for binary_str in report: + if int(binary_str[position]) == match: + reduced_report.append(binary_str) + return reduced_report def _find_rating(report: Sequence[str], most_common: bool) -> int: - """Finds rating in report. + """Finds rating in report. - Args: - report: A sequence of binary numbers. - most_common: Whether to keep numbers with the most common value if True, - or keep numbers with the least common value if False. - Returns: - A decimal number representing a rating. - """ - num_bits = len(report[0]) - position = 0 - working_report = list(report) - while len(working_report) > 1: - if position >= num_bits: - raise Exception('Could not find number.') - offset = _calculate_offset(report=working_report, position=position) - if most_common: - criteria = 1 if offset >= 0 else 0 - else: - criteria = 0 if offset >= 0 else 1 - working_report = _reduce(report=working_report, - position=position, - match=criteria) - position += 1 - return int(working_report[0], 2) + Args: + report: A sequence of binary numbers. + most_common: Whether to keep numbers with the most common value if True, + or keep numbers with the least common value if False. + Returns: + A decimal number representing a rating. + """ + num_bits = len(report[0]) + position = 0 + working_report = list(report) + while len(working_report) > 1: + if position >= num_bits: + raise Exception('Could not find number.') + offset = _calculate_offset(report=working_report, position=position) + if most_common: + criteria = 1 if offset >= 0 else 0 + else: + criteria = 0 if offset >= 0 else 1 + working_report = _reduce(report=working_report, + position=position, + match=criteria) + position += 1 + return int(working_report[0], 2) def part_one(diagnostic_report: Sequence[str]) -> int: - """AOC 2021 Day 3 Part 1. + """AOC 2021 Day 3 Part 1. - Args: - diagnostic_report: A sequence of binary numbers. - Returns: - Gamma rate multiplied by the epsilon rate. - """ - if not diagnostic_report: - raise ValueError('Missing diagnostic report!') - num_bits = len(diagnostic_report[0]) - most_common_bits = [ - '1' if _calculate_offset(diagnostic_report, position) > 0 else '0' for - position in range(0, num_bits)] - gamma_rate = ''.join(most_common_bits) - epsilon_rate = ''.join( - '0' if bit == '1' else '1' for bit in most_common_bits) - return int(gamma_rate, 2) * int(epsilon_rate, 2) + Args: + diagnostic_report: A sequence of binary numbers. + Returns: + Gamma rate multiplied by the epsilon rate. + """ + if not diagnostic_report: + raise ValueError('Missing diagnostic report!') + num_bits = len(diagnostic_report[0]) + most_common_bits = [ + '1' if _calculate_offset(diagnostic_report, position) > 0 else '0' for + position in range(0, num_bits)] + gamma_rate = ''.join(most_common_bits) + epsilon_rate = ''.join( + '0' if bit == '1' else '1' for bit in most_common_bits) + return int(gamma_rate, 2) * int(epsilon_rate, 2) def part_two(report: Sequence[str]) -> int: - """AOC 2021 Day 3 Part 2. + """AOC 2021 Day 3 Part 2. - Args: - report: A sequence of binary numbers. - Returns: - Oxygen generator rating multiplied by the CO2 scrubber rating. - """ - if not report: - raise ValueError('Missing diagnostic report!') - oxygen_generator_rating = _find_rating(report=report, most_common=True) - co2_scrubber_rating = _find_rating(report=report, most_common=False) - return oxygen_generator_rating * co2_scrubber_rating + Args: + report: A sequence of binary numbers. + Returns: + Oxygen generator rating multiplied by the CO2 scrubber rating. + """ + if not report: + raise ValueError('Missing diagnostic report!') + oxygen_generator_rating = _find_rating(report=report, most_common=True) + co2_scrubber_rating = _find_rating(report=report, most_common=False) + return oxygen_generator_rating * co2_scrubber_rating diff --git a/aoc2021/src/day04/solution.py b/aoc2021/src/day04/solution.py index d7555af..aa46ef0 100644 --- a/aoc2021/src/day04/solution.py +++ b/aoc2021/src/day04/solution.py @@ -17,111 +17,111 @@ class BingoBoard: - """Represents a bingo board of 5x5 numbers.""" - numbers: Sequence[Sequence[int]] - marked: List[List[bool]] # Mutable. - - def __init__(self, numbers: Sequence[Sequence[int]]) -> None: - """Initializes a bingo board. - Args: - numbers: A 5x5 board of numbers. - """ - self.numbers = numbers - self.marked = [[False] * _BOARD_SIZE for _ in range(0, _BOARD_SIZE)] - - def _has_marked_row(self) -> bool: - """Returns True if the board has one full marked row.""" - for row in self.marked: - if row.count(False) == 0: - return True - return False - - def _has_marked_column(self) -> bool: - """Returns True if the board has one full marked column.""" - for column_pos in range(0, _BOARD_SIZE): - column = [row[column_pos] for row in self.marked] - if column.count(False) == 0: - return True - return False - - def _is_winning(self) -> bool: - """True if the board won.""" - return self._has_marked_row() or self._has_marked_column() - - def mark(self, drawing_number: int) -> bool: - """Marks a number, if it exists, in the board. - - Args: - drawing_number: A number. It may not appear in the board. - Returns - True if the board wins. - """ - for i, row in enumerate(self.numbers): - for j, board_number in enumerate(row): - if board_number == drawing_number: - self.marked[i][j] = True - return self._is_winning() - - def sum_unmarked(self) -> int: - """Returns the sum of all numbers that have not been marked.""" - sum_unmarked = 0 - for i, row in enumerate(self.marked): - for j, is_marked in enumerate(row): - if not is_marked: - sum_unmarked += self.numbers[i][j] - return sum_unmarked + """Represents a bingo board of 5x5 numbers.""" + numbers: Sequence[Sequence[int]] + marked: List[List[bool]] # Mutable. + + def __init__(self, numbers: Sequence[Sequence[int]]) -> None: + """Initializes a bingo board. + Args: + numbers: A 5x5 board of numbers. + """ + self.numbers = numbers + self.marked = [[False] * _BOARD_SIZE for _ in range(0, _BOARD_SIZE)] + + def _has_marked_row(self) -> bool: + """Returns True if the board has one full marked row.""" + for row in self.marked: + if row.count(False) == 0: + return True + return False + + def _has_marked_column(self) -> bool: + """Returns True if the board has one full marked column.""" + for column_pos in range(0, _BOARD_SIZE): + column = [row[column_pos] for row in self.marked] + if column.count(False) == 0: + return True + return False + + def _is_winning(self) -> bool: + """True if the board won.""" + return self._has_marked_row() or self._has_marked_column() + + def mark(self, drawing_number: int) -> bool: + """Marks a number, if it exists, in the board. + + Args: + drawing_number: A number. It may not appear in the board. + Returns + True if the board wins. + """ + for i, row in enumerate(self.numbers): + for j, board_number in enumerate(row): + if board_number == drawing_number: + self.marked[i][j] = True + return self._is_winning() + + def sum_unmarked(self) -> int: + """Returns the sum of all numbers that have not been marked.""" + sum_unmarked = 0 + for i, row in enumerate(self.marked): + for j, is_marked in enumerate(row): + if not is_marked: + sum_unmarked += self.numbers[i][j] + return sum_unmarked def _parse_line(board_line: str) -> Sequence[int]: - return tuple(int(x) for x in board_line.split(' ') if x) + return tuple(int(x) for x in board_line.split(' ') if x) def _parse_bingo(bingo: str) -> Tuple[Sequence[int], Sequence[BingoBoard]]: - numbers = tuple(map(int, bingo[0].split(','))) - boards = [] - cursor = 2 # Skip first line and separator. - while cursor < len(bingo) - _BOARD_SIZE + 1: - board_numbers = tuple(_parse_line(bingo[i]) for i in - range(cursor, cursor + _BOARD_SIZE)) - boards.append(BingoBoard(board_numbers)) - cursor += _BOARD_SIZE + 1 # Skip empty line separating boards. - return numbers, tuple(boards) + numbers = tuple(map(int, bingo[0].split(','))) + boards = [] + cursor = 2 # Skip first line and separator. + while cursor < len(bingo) - _BOARD_SIZE + 1: + board_numbers = tuple(_parse_line(bingo[i]) for i in + range(cursor, cursor + _BOARD_SIZE)) + boards.append(BingoBoard(board_numbers)) + cursor += _BOARD_SIZE + 1 # Skip empty line separating boards. + return numbers, tuple(boards) def part_one(bingo: str) -> int: - """AOC 2021 Day 4 Part 1. - - Args: - bingo: The bingo input. First line contains comma-separated numbers to - be drawn, next lines contain 5x5 bingo boards. - Returns: - Score of the winning board. It is the sum of all unmarked numbers on that - board multiplied by the number last called. - """ - drawing_numbers, boards = _parse_bingo(bingo) - for drawing_number in drawing_numbers: - for board in boards: - if board.mark(drawing_number=drawing_number): - return drawing_number * board.sum_unmarked() - raise Exception('No board won!') + """AOC 2021 Day 4 Part 1. + + Args: + bingo: The bingo input. First line contains comma-separated numbers to + be drawn, next lines contain 5x5 bingo boards. + Returns: + Score of the winning board. It is the sum of all unmarked numbers on that + board multiplied by the number last called. + """ + drawing_numbers, boards = _parse_bingo(bingo) + for drawing_number in drawing_numbers: + for board in boards: + if board.mark(drawing_number=drawing_number): + return drawing_number * board.sum_unmarked() + raise Exception('No board won!') def part_two(bingo: str) -> int: - """AOC 2021 Day 4 Part 2. - - Args: - bingo: The bingo input. First line contains comma-separated numbers to - be drawn, next lines contain 5x5 bingo boards. - Returns: - Score of the last winning board. It is the sum of all unmarked numbers on - that board multiplied by the number last called. - """ - drawing_numbers, boards = _parse_bingo(bingo) - winning_boards = [False] * len(boards) - for drawing_number in drawing_numbers: - for board_no, board in enumerate(boards): - if board.mark(drawing_number=drawing_number): - winning_boards[board_no] = True - if winning_boards.count(False) == 0: # Last winning board. - return drawing_number * board.sum_unmarked() - raise Exception('No board won!') + """AOC 2021 Day 4 Part 2. + + Args: + bingo: The bingo input. First line contains comma-separated numbers to + be drawn, next lines contain 5x5 bingo boards. + Returns: + Score of the last winning board. It is the sum of all unmarked numbers on + that board multiplied by the number last called. + """ + drawing_numbers, boards = _parse_bingo(bingo) + winning_boards = [False] * len(boards) + for drawing_number in drawing_numbers: + for board_no, board in enumerate(boards): + if board.mark(drawing_number=drawing_number): + winning_boards[board_no] = True + if winning_boards.count(False) == 0: # Last winning board. + return drawing_number * board.sum_unmarked() + raise Exception('No board won!') diff --git a/aoc2021/src/day05/solution.py b/aoc2021/src/day05/solution.py index f8894a8..e17c148 100644 --- a/aoc2021/src/day05/solution.py +++ b/aoc2021/src/day05/solution.py @@ -15,152 +15,152 @@ class Segment: - x1: int - y1: int - x2: int - y2: int + x1: int + y1: int + x2: int + y2: int - def __init__(self, x1: int, y1: int, x2: int, y2: int) -> None: - """Initializes segment.""" - self.x1 = x1 - self.y1 = y1 - self.x2 = x2 - self.y2 = y2 + def __init__(self, x1: int, y1: int, x2: int, y2: int) -> None: + """Initializes segment.""" + self.x1 = x1 + self.y1 = y1 + self.x2 = x2 + self.y2 = y2 - def is_horizontal(self) -> bool: - return self.y1 == self.y2 + def is_horizontal(self) -> bool: + return self.y1 == self.y2 - def is_vertical(self) -> bool: - return self.x1 == self.x2 + def is_vertical(self) -> bool: + return self.x1 == self.x2 - def is_ascending_diagonal(self): - return not self.is_vertical() and not self.is_horizontal() and self.x1 < self.x2 + def is_ascending_diagonal(self): + return not self.is_vertical() and not self.is_horizontal() and self.x1 < self.x2 - def is_descending_diagonal(self): - return not self.is_vertical() and not self.is_horizontal() and self.x2 > self.x1 + def is_descending_diagonal(self): + return not self.is_vertical() and not self.is_horizontal() and self.x2 > self.x1 class Grid: - grid: List[List[int]] - - def __init__(self, width: int): - """Initializes the grid.""" - self.grid = [[0] * width for _ in range(0, width)] - - def populate_horizontal(self, x_start: int, x_end: int, - y: int) -> None: - for x in range(x_start, x_end + 1): - self.grid[y][x] += 1 - - def populate_vertical(self, y_start: int, y_end: int, - x: int) -> None: - for y in range(y_start, y_end + 1): - self.grid[y][x] += 1 - - def populate_diagonal(self, x_start: int, x_end: int, y_start: int, - y_end: int): - if x_start < x_end and y_start < y_end: - # Descending onwards. - y = y_start - for x in range(x_start, x_end + 1): - self.grid[y][x] += 1 - y += 1 - elif x_start > x_end and y_end < y_start: - # Descending backwards. - y = y_end - for x in range(x_end, x_start + 1): - self.grid[y][x] += 1 - y += 1 - elif x_start < x_end and y_end < y_start: - # Ascending onwards. - y = y_start - for x in range(x_start, x_end + 1): - self.grid[y][x] += 1 - y -= 1 - elif x_start > x_end and y_start < y_end: - # Ascending backwards. - y = y_end - for x in range(x_end, x_start + 1): - self.grid[y][x] += 1 - y -= 1 - else: - raise ValueError('Not a diagonal?') - - def count_overlap_points(self) -> int: - total = 0 - for row in self.grid: - total += sum(value > 1 for value in row) - return total + grid: List[List[int]] + + def __init__(self, width: int): + """Initializes the grid.""" + self.grid = [[0] * width for _ in range(0, width)] + + def populate_horizontal(self, x_start: int, x_end: int, + y: int) -> None: + for x in range(x_start, x_end + 1): + self.grid[y][x] += 1 + + def populate_vertical(self, y_start: int, y_end: int, + x: int) -> None: + for y in range(y_start, y_end + 1): + self.grid[y][x] += 1 + + def populate_diagonal(self, x_start: int, x_end: int, y_start: int, + y_end: int): + if x_start < x_end and y_start < y_end: + # Descending onwards. + y = y_start + for x in range(x_start, x_end + 1): + self.grid[y][x] += 1 + y += 1 + elif x_start > x_end and y_end < y_start: + # Descending backwards. + y = y_end + for x in range(x_end, x_start + 1): + self.grid[y][x] += 1 + y += 1 + elif x_start < x_end and y_end < y_start: + # Ascending onwards. + y = y_start + for x in range(x_start, x_end + 1): + self.grid[y][x] += 1 + y -= 1 + elif x_start > x_end and y_start < y_end: + # Ascending backwards. + y = y_end + for x in range(x_end, x_start + 1): + self.grid[y][x] += 1 + y -= 1 + else: + raise ValueError('Not a diagonal?') + + def count_overlap_points(self) -> int: + total = 0 + for row in self.grid: + total += sum(value > 1 for value in row) + return total def _process_input(segment_lines: str) -> Tuple[Sequence[Segment], int]: - segments = [] - max_position = -1 - # Process input. - for line in segment_lines: - start, end = line.split(' -> ') - x1, y1 = map(int, start.split(',')) - x2, y2 = map(int, end.split(',')) - segments.append(Segment(x1=x1, y1=y1, x2=x2, y2=y2)) - # Determine grid width. - if x1 > max_position: - max_position = x1 - if y1 > max_position: - max_position = y1 - if x2 > max_position: - max_position = x2 - if y2 > max_position: - max_position = y2 - return tuple(segments), max_position + segments = [] + max_position = -1 + # Process input. + for line in segment_lines: + start, end = line.split(' -> ') + x1, y1 = map(int, start.split(',')) + x2, y2 = map(int, end.split(',')) + segments.append(Segment(x1=x1, y1=y1, x2=x2, y2=y2)) + # Determine grid width. + if x1 > max_position: + max_position = x1 + if y1 > max_position: + max_position = y1 + if x2 > max_position: + max_position = x2 + if y2 > max_position: + max_position = y2 + return tuple(segments), max_position def _populate_lines(segment_lines: str, ignore_diagonals: bool = True) -> int: - """Populates lines in grid. - - Args: - segment_lines: List of segments with format x1,y1 -> x2,y2. - ignore_diagonals: Whether to ignore diagonal segments. - Returns: - Number of points where at least 2 segments overlap. - """ - segments, max_position = _process_input(segment_lines=segment_lines) - grid = Grid(width=max_position + 1) # Index between 0 and max_position. - for segment in segments: - if segment.is_horizontal(): - grid.populate_horizontal(x_start=min(segment.x1, segment.x2), - x_end=max(segment.x1, segment.x2), - y=segment.y1) - elif segment.is_vertical(): - grid.populate_vertical(y_start=min(segment.y1, segment.y2), - y_end=max(segment.y1, segment.y2), - x=segment.x1) - else: - # Must be diagonal. - if not ignore_diagonals: - grid.populate_diagonal(x_start=segment.x1, x_end=segment.x2, - y_start=segment.y1, y_end=segment.y2) - return grid.count_overlap_points() + """Populates lines in grid. + + Args: + segment_lines: List of segments with format x1,y1 -> x2,y2. + ignore_diagonals: Whether to ignore diagonal segments. + Returns: + Number of points where at least 2 segments overlap. + """ + segments, max_position = _process_input(segment_lines=segment_lines) + grid = Grid(width=max_position + 1) # Index between 0 and max_position. + for segment in segments: + if segment.is_horizontal(): + grid.populate_horizontal(x_start=min(segment.x1, segment.x2), + x_end=max(segment.x1, segment.x2), + y=segment.y1) + elif segment.is_vertical(): + grid.populate_vertical(y_start=min(segment.y1, segment.y2), + y_end=max(segment.y1, segment.y2), + x=segment.x1) + else: + # Must be diagonal. + if not ignore_diagonals: + grid.populate_diagonal(x_start=segment.x1, x_end=segment.x2, + y_start=segment.y1, y_end=segment.y2) + return grid.count_overlap_points() def part_one(segment_lines: str) -> int: - """AOC 2021 Day 5 Part 1. + """AOC 2021 Day 5 Part 1. - Part 1 only considers horizontal and vertical segments. + Part 1 only considers horizontal and vertical segments. - Args: - segment_lines: List of segments with format x1,y1 -> x2,y2. - Returns: - Number of points where at least 2 segments overlap. - """ - return _populate_lines(segment_lines=segment_lines) + Args: + segment_lines: List of segments with format x1,y1 -> x2,y2. + Returns: + Number of points where at least 2 segments overlap. + """ + return _populate_lines(segment_lines=segment_lines) def part_two(segment_lines: str) -> int: - """AOC 2021 Day 5 Part 2. - - Args: - segment_lines: List of segments with format x1,y1 -> x2,y2. - Returns: - Number of points where at least 2 segments overlap. - """ - return _populate_lines(segment_lines=segment_lines, ignore_diagonals=False) + """AOC 2021 Day 5 Part 2. + + Args: + segment_lines: List of segments with format x1,y1 -> x2,y2. + Returns: + Number of points where at least 2 segments overlap. + """ + return _populate_lines(segment_lines=segment_lines, ignore_diagonals=False) diff --git a/aoc2021/src/day06/solution.py b/aoc2021/src/day06/solution.py index b27ce6b..1911376 100644 --- a/aoc2021/src/day06/solution.py +++ b/aoc2021/src/day06/solution.py @@ -14,85 +14,85 @@ class LanternFish: - """Represents a lanternfish.""" - _NEWBORN_TIMER = 8 - _SPAWN_TIMER = 6 + """Represents a lanternfish.""" + _NEWBORN_TIMER = 8 + _SPAWN_TIMER = 6 - timer: int + timer: int - def __init__(self, timer: int = _NEWBORN_TIMER) -> None: - self.timer = timer + def __init__(self, timer: int = _NEWBORN_TIMER) -> None: + self.timer = timer - def run(self) -> bool: - """Runs timer on lanternfish. + def run(self) -> bool: + """Runs timer on lanternfish. - Returns: - True if the lanternfish spawns a new one, False otherwise. - """ - if self.timer == 0: - self.timer = self._SPAWN_TIMER - return True - self.timer -= 1 - return False + Returns: + True if the lanternfish spawns a new one, False otherwise. + """ + if self.timer == 0: + self.timer = self._SPAWN_TIMER + return True + self.timer -= 1 + return False def part_one(lanternfish_ages: str, days: int) -> int: - """AOC 2021 Day 6 Part 1. + """AOC 2021 Day 6 Part 1. - Args: - lanternfish_ages: Comma-separated string of lanternfish ages. - days: Number of elapsed days to calculate population of lanternfish. - Returns: - Number of lanternfish after elapsed days. - """ - initial_state = tuple(map(int, lanternfish_ages[0].split(','))) - population = [LanternFish(timer=timer) for timer in initial_state] - today = 0 - while today < days: - spawn_count = 0 - for fish in population: - spawn_count += 1 if fish.run() else 0 - population += [LanternFish() for _ in range(0, spawn_count)] - today += 1 - return len(population) + Args: + lanternfish_ages: Comma-separated string of lanternfish ages. + days: Number of elapsed days to calculate population of lanternfish. + Returns: + Number of lanternfish after elapsed days. + """ + initial_state = tuple(map(int, lanternfish_ages[0].split(','))) + population = [LanternFish(timer=timer) for timer in initial_state] + today = 0 + while today < days: + spawn_count = 0 + for fish in population: + spawn_count += 1 if fish.run() else 0 + population += [LanternFish() for _ in range(0, spawn_count)] + today += 1 + return len(population) def part_two(lanternfish_ages: str, days: int) -> int: - """AOC 2021 Day 6 Part 2. + """AOC 2021 Day 6 Part 2. - Args: - lanternfish_ages: Comma-separated string of lanternfish ages. - days: Number of elapsed days to calculate population of lanternfish. - Returns: - Number of lanternfish after elapsed days. - """ - population = list(map(int, lanternfish_ages[0].split(','))) - # Object-oriented and array approach does not scale. - # Inspired approach from redditor /u/mariothedogMC: - # Maintain a map of fish timers and frequencies: - fish_timer_frequencies = { - 0: 0, - 1: 0, - 2: 0, - 3: 0, - 4: 0, - 5: 0, - 6: 0, - 7: 0, - 8: 0, - } - for fish in population: - fish_timer_frequencies[fish] += 1 - for _ in range(days): - new_fish_count = fish_timer_frequencies[0] - for timer, freq in fish_timer_frequencies.copy().items(): - # Reset frequency. - fish_timer_frequencies[timer] = 0 - if timer > 0: - # Make tick. Shift frequencies to lower timer. - fish_timer_frequencies[timer - 1] = freq - # Spawn new fish. - fish_timer_frequencies[8] += new_fish_count - # Mother timers return to 6! - fish_timer_frequencies[6] += new_fish_count - return sum(fish_timer_frequencies.values()) # New population. + Args: + lanternfish_ages: Comma-separated string of lanternfish ages. + days: Number of elapsed days to calculate population of lanternfish. + Returns: + Number of lanternfish after elapsed days. + """ + population = list(map(int, lanternfish_ages[0].split(','))) + # Object-oriented and array approach does not scale. + # Inspired approach from redditor /u/mariothedogMC: + # Maintain a map of fish timers and frequencies: + fish_timer_frequencies = { + 0: 0, + 1: 0, + 2: 0, + 3: 0, + 4: 0, + 5: 0, + 6: 0, + 7: 0, + 8: 0, + } + for fish in population: + fish_timer_frequencies[fish] += 1 + for _ in range(days): + new_fish_count = fish_timer_frequencies[0] + for timer, freq in fish_timer_frequencies.copy().items(): + # Reset frequency. + fish_timer_frequencies[timer] = 0 + if timer > 0: + # Make tick. Shift frequencies to lower timer. + fish_timer_frequencies[timer - 1] = freq + # Spawn new fish. + fish_timer_frequencies[8] += new_fish_count + # Mother timers return to 6! + fish_timer_frequencies[6] += new_fish_count + return sum(fish_timer_frequencies.values()) # New population. diff --git a/aoc2021/src/day07/solution.py b/aoc2021/src/day07/solution.py index 6db0a2f..17d4fbb 100644 --- a/aoc2021/src/day07/solution.py +++ b/aoc2021/src/day07/solution.py @@ -16,60 +16,60 @@ def _calculate_fuel(positions: Sequence[int], align_position: int, constant_rate: bool = True) -> int: - """Calculates fuel to align all positions to one position. + """Calculates fuel to align all positions to one position. - Args - positions: Sequence of horizontal positions. - align_position: Position to align to. - constant_rate: Whether the fuel cost increases at constant rate. - Returns: - Fuel cost. - """ - if constant_rate: - return sum([abs(p - align_position) for p in positions]) - else: - total = 0 - for p in positions: - distance = abs(p - align_position) - # Sum of sequence of natural numbers: 1 + 2 + 3... - total += int(distance * (distance + 1) / 2) - return total + Args + positions: Sequence of horizontal positions. + align_position: Position to align to. + constant_rate: Whether the fuel cost increases at constant rate. + Returns: + Fuel cost. + """ + if constant_rate: + return sum([abs(p - align_position) for p in positions]) + else: + total = 0 + for p in positions: + distance = abs(p - align_position) + # Sum of sequence of natural numbers: 1 + 2 + 3... + total += int(distance * (distance + 1) / 2) + return total def _calculate_min_fuel(positions: Sequence[int], constant_rate: bool = True) -> int: - valid_positions = range(0, max(positions)) - min_fuel = 100000000 - for p in valid_positions: - fuel = _calculate_fuel(positions=positions, align_position=p, - constant_rate=constant_rate) - if fuel < min_fuel: - min_fuel = fuel - return min_fuel + valid_positions = range(0, max(positions)) + min_fuel = 100000000 + for p in valid_positions: + fuel = _calculate_fuel(positions=positions, align_position=p, + constant_rate=constant_rate) + if fuel < min_fuel: + min_fuel = fuel + return min_fuel def part_one(positions: Sequence[int]) -> int: - """AOC 2021 Day 7 Part 1. + """AOC 2021 Day 7 Part 1. - Args: - positions: Sequence of crabs horizontal positions. - Returns: - The least amount of fuel needed to align. - """ - return int(_calculate_min_fuel(positions=positions)) + Args: + positions: Sequence of crabs horizontal positions. + Returns: + The least amount of fuel needed to align. + """ + return int(_calculate_min_fuel(positions=positions)) def part_two(positions: Sequence[int]) -> int: - """AOC 2021 Day 7 Part 2. + """AOC 2021 Day 7 Part 2. - Crab submarine engines don't burn fuel at a constant rate. Instead, each - change of 1 step in horizontal position costs 1 more unit of fuel than the - last: the first step costs 1, the second step costs 2, the third step costs - 3, and so on. + Crab submarine engines don't burn fuel at a constant rate. Instead, each + change of 1 step in horizontal position costs 1 more unit of fuel than the + last: the first step costs 1, the second step costs 2, the third step costs + 3, and so on. - Args: - positions: Sequence of crabs horizontal positions. - Returns: - The least amount of fuel needed to align. - """ - return _calculate_min_fuel(positions=positions, constant_rate=False) + Args: + positions: Sequence of crabs horizontal positions. + Returns: + The least amount of fuel needed to align. + """ + return _calculate_min_fuel(positions=positions, constant_rate=False) diff --git a/aoc2022/src/day02/python/solution.py b/aoc2022/src/day02/python/solution.py index 1174d44..d3c32f2 100644 --- a/aoc2022/src/day02/python/solution.py +++ b/aoc2022/src/day02/python/solution.py @@ -42,8 +42,8 @@ def _get_round_score(score_table: Sequence[Sequence[int]], move_score: Dict[str, int], opponent: str, you: str) -> int: return ( - score_table[ord(opponent) - ord('A')][ord(you) - ord('X')] - + move_score[you] + score_table[ord(opponent) - ord('A')][ord(you) - ord('X')] + + move_score[you] )