# Advent of Code 2025 https://adventofcode.com/2025 Why do I make these separate repos every year?
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

2 周前
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)