# Advent of Code 2025 https://adventofcode.com/2025 Why do I make these separate repos every year?
Vous ne pouvez pas sélectionner plus de 25 sujets Les noms de sujets doivent commencer par une lettre ou un nombre, peuvent contenir des tirets ('-') et peuvent comporter jusqu'à 35 caractères.

day1-2.py 7.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190
  1. # encoding: utf-8
  2. ###########################
  3. # Advent of Code Day 1-2 #
  4. ###########################
  5. # Combination lock - rotary dial
  6. # Instructions come in the form `DN+`,
  7. # where D is a direction L or R, and N+ is a series of integers of
  8. # arbitrary length. (e.g. L32, R947)
  9. # The dial is 0-indexed and has 100 positions (0-99).
  10. # Rotating left decrements the index; rotating right increments it.
  11. # Rotating left from 0 goes to 99, and rotating right from 99 goes to 0
  12. # (i.e. it wraps around).
  13. # Count the number of times the dial ever sees 0.
  14. # Instructions are in day1_input.txt.
  15. # I'm gonna overengineer this.
  16. import argparse
  17. import logging
  18. import os
  19. logger = logging.getLogger(__name__)
  20. sh = logging.StreamHandler()
  21. logger.addHandler(sh)
  22. logger.setLevel(logging.INFO)
  23. DEFAULTS = {
  24. "starting_position": 50,
  25. "num_positions": 100,
  26. "filename": "day1_input.txt"
  27. }
  28. class RotaryLock:
  29. def __init__(self, starting_position:int|None=None, num_positions:int|None=None):
  30. try:
  31. num_positions = int(num_positions) if num_positions is not None else DEFAULTS["num_positions"]
  32. except ValueError:
  33. logger.warning(f"Number of positions {num_positions} is not an integer. Setting to default ({DEFAULTS['num_positions']}).")
  34. num_positions = DEFAULTS["num_positions"]
  35. if num_positions <= 0:
  36. logger.warning(f"Number of positions {num_positions} is not positive. Setting to default ({DEFAULTS['num_positions']}).")
  37. num_positions = DEFAULTS["num_positions"]
  38. self.last_position = num_positions - 1
  39. self.first_position = 0
  40. try:
  41. starting_position = int(starting_position) if starting_position is not None else DEFAULTS["starting_position"]
  42. except ValueError:
  43. logger.warning(f"Starting position {starting_position} is not an integer. Setting to default ({DEFAULTS['starting_position']}).")
  44. starting_position = DEFAULTS["starting_position"]
  45. if self.first_position <= int(starting_position) <= self.last_position:
  46. self.current_position = (starting_position)
  47. else:
  48. logger.warning(
  49. f"Starting position {starting_position} falls outside of possible positions {self.first_position}-{self.last_position}. Setting to 0."
  50. )
  51. self.current_position = 0
  52. self.position_history = []
  53. self.position_history.append(self.current_position)
  54. self.instructions = []
  55. self.num_zeroes = 0
  56. logger.debug(f"RotaryLock created!")
  57. logger.debug(self.__dict__)
  58. def read_instructions(self, filename:str|None=None) -> None:
  59. filename = filename if filename is not None else DEFAULTS["filename"]
  60. if not filename.startswith("/"):
  61. full_path = os.path.join(os.curdir, filename)
  62. else:
  63. full_path = filename
  64. if os.path.exists(full_path) and os.path.isfile(full_path):
  65. with open(full_path, "r") as f:
  66. lines = f.readlines()
  67. elif not os.path.exists(full_path):
  68. logger.error(f"File {full_path} does not exist.")
  69. return
  70. elif not os.path.isfile(full_path):
  71. logger.error(f"File {full_path} is not a file.")
  72. return
  73. self.instructions = [l.strip() for l in lines]
  74. def rotate_right(self) -> None:
  75. if self.current_position == self.last_position:
  76. self.current_position = self.first_position
  77. else:
  78. self.current_position += 1
  79. def rotate_left(self) -> None:
  80. if self.current_position == self.first_position:
  81. self.current_position = self.last_position
  82. else:
  83. self.current_position -= 1
  84. def rotate_lock(self, direction:str, magnitude:int) -> None:
  85. try:
  86. d = direction.lower()
  87. except AttributeError:
  88. logger.error(f"Supplied direction {direction} is not usable (must be a string).")
  89. return
  90. if d not in ("l","r"):
  91. logger.error(f"Supplied direction {direction} is not usable (must be L or R).")
  92. return
  93. try:
  94. n = int(magnitude)
  95. except ValueError:
  96. logger.error(f"Supplied magnitude {magnitude} is not an integer.")
  97. return
  98. for i in range(n):
  99. match d:
  100. case "r":
  101. self.rotate_right()
  102. case "l":
  103. self.rotate_left()
  104. case default:
  105. logger.error(f"Direction {d} was not recognized. (How did we get here?)")
  106. if self.current_position == self.first_position:
  107. logger.debug(f"Saw a zero.")
  108. self.num_zeroes += 1
  109. # if d == "r":
  110. # new_pos = self.current_position + n
  111. # if new_pos > self.last_position:
  112. # overflow = new_pos - (self.last_position + 1)
  113. # new_pos = overflow
  114. # else:
  115. # new_pos = self.current_position - n
  116. # if new_pos < 0:
  117. # overflow = n - (self.current_position + 1)
  118. # new_pos = self.last_position - overflow
  119. # self.current_position = new_pos
  120. logger.debug(f"After rotating {d.upper()} {n} places, current position is {self.current_position}.")
  121. def follow_instruction(self, instruction:str) -> None:
  122. try:
  123. d, n = instruction[0].lower(), int(instruction[1:])
  124. except (AttributeError, ValueError, IndexError):
  125. logger.error(f"Instruction {instruction} is unreadable (format must be DN+).")
  126. return
  127. logger.debug(f"Following instruction to rotate {d} by {n} positions.")
  128. self.rotate_lock(direction=d, magnitude=n)
  129. # if self.current_position == 0:
  130. # logger.debug(f"Landed on 0.")
  131. # self.num_zeroes += 1
  132. def follow_instructions(self, instruction_list:list[str]|None=None) -> None:
  133. if instruction_list is None:
  134. self.read_instructions()
  135. instruction_list = self.instructions
  136. else:
  137. if len(self.instructions) == 0:
  138. logger.error(f"Instructions were not passed and have not been initialized.")
  139. return
  140. instruction_list = self.instructions
  141. try:
  142. instructions = [x for x in instruction_list]
  143. except TypeError:
  144. logger.error(f"Instruction list {instruction_list} is not an iterable.")
  145. tenths = len(instructions) // 10
  146. logger.debug(f"Processing {len(instructions)} instructions.")
  147. logger.debug(f"Reporting every {tenths} instructions.")
  148. for i, instr in enumerate(instructions):
  149. if tenths > 0 and i % tenths == 0:
  150. logger.debug(f"Processing instruction {i}.")
  151. self.follow_instruction(instr)
  152. logger.info(f"Completed all instructions.")
  153. logger.info(f"Final position is {self.current_position}.")
  154. logger.info(f"Number of zeroes is {self.num_zeroes}.")
  155. def process_instructions(filename:str, start:int, num_pos:int):
  156. rlock = RotaryLock(starting_position=start, num_positions=num_pos)
  157. rlock.read_instructions(filename)
  158. rlock.follow_instructions()
  159. if __name__ == "__main__":
  160. parser = argparse.ArgumentParser()
  161. parser.add_argument("--filename", type=str, default=DEFAULTS["filename"], help="Full or local path to instruction file")
  162. parser.add_argument("--start", type=int, default=DEFAULTS["starting_position"], help="Starting position on the dial")
  163. parser.add_argument("--num_pos", type=int, default=DEFAULTS["num_positions"], help="Number of positions on the dial")
  164. parser.add_argument("--verbose", "-v", action="store_true", help="Enable debug logging")
  165. args = parser.parse_args()
  166. if args.verbose:
  167. logger.setLevel(logging.DEBUG)
  168. process_instructions(args.filename, args.start, args.num_pos)