Day 7: Bridge Repair

Megathread guidelines

  • Keep top level comments as only solutions, if you want to say something other than a solution put it in a new post. (replies to comments can be whatever)
  • You can send code in code blocks by using three backticks, the code, and then three backticks or use something such as https://topaz.github.io/paste/ if you prefer sending it through a URL

FAQ

  • stevenviola@programming.dev
    link
    fedilink
    English
    arrow-up
    2
    ·
    20 days ago

    Python

    Takes ~5.3s on my machine to get both outputs. Not sure how to optimize it any further other than running the math in threads? Took me longer than it should have to realize a lot of unnecessary math could be cut if the running total becomes greater than the target while doing the math. Also very happy to see that none of the inputs caused the recursive function to hit Python’s max stack depth.

    Code
    import argparse
    import os
    
    
    class Calibrations:
        def __init__(self, target, operators) -> None:
            self.operators = operators
            self.target = target
            self.target_found = False
    
        def do_math(self, numbers, operation) -> int:
            if operation == "+":
                return numbers[0] + numbers[1]
            elif operation == "*":
                return numbers[0] * numbers[1]
            elif operation == "||":
                return int(str(numbers[0]) + str(numbers[1]))
    
        def all_options(self, numbers, last) -> int:
            if len(numbers) < 1:
                return last
            for j in self.operators:
                # If we found our target already, abort
                # If the last value is greater than the target, abort
                if self.target_found or last > self.target:
                    return
                total = self.all_options(
                    numbers[1:], self.do_math((last, numbers[0]), j)
                )
                if total == self.target:
                    self.target_found = True
    
        def process_line(self, line) -> int:
            numbers = [int(x) for x in line.split(":")[1].strip().split()]
            self.all_options(numbers[1:], numbers[0])
            if self.target_found:
                return self.target
            return 0
    
    
    def main() -> None:
        path = os.path.dirname(os.path.abspath(__file__))
        parser = argparse.ArgumentParser(description="Bridge Repair")
        parser.add_argument("filename", help="Path to the input file")
        args = parser.parse_args()
        sum_of_valid = [0, 0]
        with open(f"{path}/{args.filename}", "r") as f:
            for line in f:
                line = line.strip()
                target = int(line.split(":")[0])
                for idx, ops in enumerate([["+", "*"], ["+", "*", "||"]]):
                    c = Calibrations(target, ops)
                    found = c.process_line(line)
                    sum_of_valid[idx] += found
                    if found:
                        break
        for i in range(0, 2):
            part = i + 1
            print(
                "The sum of valid calibrations for Part "
                + f"{part} is {sum(sum_of_valid[:part])}"
            )
    
    
    if __name__ == "__main__":
        main()
    

    https://github.com/stevenviola/advent-of-code-2024

    • iAvicenna@lemmy.world
      link
      fedilink
      arrow-up
      2
      ·
      edit-2
      18 days ago

      If you havent already done so, doing it in the form of “tree search”, the code completes in the blink of an eye (though on a high end cpu 11th Gen Intel® Core™ i7-11800H @ 2.30GHz). posted the code below

      • stevenviola@programming.dev
        link
        fedilink
        arrow-up
        1
        ·
        19 days ago

        Thanks! yup, I figured there would be a way. You’re right, much faster, on my machine with your code, this is the speed:

        $ time python3 day7.py 
        4555081946288
        227921760109726
        
        real    0m0.171s
        

        I’ll have to take a look to understand how that works to be better.

        • Acters@lemmy.world
          link
          fedilink
          arrow-up
          1
          ·
          edit-2
          18 days ago

          I posted my solution here and found my way to finish 30 milliseconds faster.(~100ms for his, and ~66 ms for mine) However, as I noted I stop prematurely sometimes. Which seems to work with my given input. but here is the one that makes sure it gets to the end of the list of integers:

          code
          def main(input_data):
              input_data = input_data.replace('\r', '')
              parsed_data = {int(line[0]): [int(i) for i in line[1].split()[::-1]] for line in [l.split(': ') for l in input_data.splitlines()]}
              part1 = 0
              part2 = 0
              for item in parsed_data.items():
                  root, num_array = item
                  part_1_branches = [set() for _ in range(len(num_array)+1)]
                  part_2_branches = [set() for _ in range(len(num_array)+1)]
                  part_1_branches[0].add(root)
                  part_2_branches[0].add(root)
                  for level,i in enumerate(num_array):
                      if len(part_1_branches[level]) == 0 and len(part_2_branches[level]) == 0:
                          break
          
                      for branch in part_1_branches[level]:
                          if level==len(num_array)-1:
                              if branch == i:
                                  part1 += root
                                  break
                          if branch % i == 0:
                              part_1_branches[level+1].add(branch//i)
                          if branch - i > 0:
                              part_1_branches[level+1].add(branch-i)
          
                      for branch in part_2_branches[level]:
                          if level==len(num_array)-1:
                              if (branch == i or str(branch) == str(i)):
                                  part2 += root
                                  break
                          if branch % i == 0:
                              part_2_branches[level+1].add(branch//i)
                          if branch - i > 0:
                              part_2_branches[level+1].add(branch-i)
                          if str(i) == str(branch)[-len(str(i)):]:
                              part_2_branches[level+1].add(int(str(branch)[:-len(str(i))].rjust(1,'0')))
              
              print("Part 1:", part1, "\nPart 2:", part2)
              return [part1, part2]
          
          if __name__ == "__main__":
              with open('input', 'r') as f:
                  main(f.read())