• 0 Posts
  • 12 Comments
Joined 1 year ago
cake
Cake day: August 22nd, 2023

help-circle

  • Python

    Had to rely on an external polygon library for this one. Part 1 could have been easily done without it but part 2 would be diffucult (you can even use the simplify function to count the number of straight edges in internal and external boundaries modulo checking the collinearity of the start and end of the boundary)

    
    import numpy as np
    from pathlib import Path
    from shapely import box, union, MultiPolygon, Polygon, MultiLineString
    cwd = Path(__file__).parent
    
    def parse_input(file_path):
      with file_path.open("r") as fp:
        garden = list(map(list, fp.read().splitlines()))
    
      return np.array(garden)
    
    def get_polygon(plant, garden):
      coords = list(map(tuple, list(np.argwhere(garden==plant))))
      for indc,coord in enumerate(coords):
    
        box_next = box(xmin=coord[0], ymin=coord[1], xmax=coord[0]+1,
                       ymax=coord[1]+1)
    
        if indc==0:
          poly = box_next
        else:
          poly = union(poly, box_next)
    
      if isinstance(poly, Polygon):
        poly = MultiPolygon([poly])
    
      return poly
    
    def are_collinear(coords, tol=None):
        coords = np.array(coords, dtype=float)
        coords -= coords[0]
        return np.linalg.matrix_rank(coords, tol=tol)==1
    
    def simplify_boundary(boundary):
    
      # if the object has internal and external boundaries then split them
      # and recurse
      if isinstance(boundary, MultiLineString):
        coordinates = []
        for b in boundary.geoms:
          coordinates.append(simplify_boundary(b))
        return list(np.concat(coordinates, axis=0))
    
      simple_boundary = boundary.simplify(0)
      coords = [np.array(x) for x in list(simple_boundary.coords)[:-1]]
      resolved = False
    
      while not resolved:
    
        end_side=\
        np.concat([x[:,None] for x in [coords[-1], coords[0], coords[1]]], axis=1)
    
        if  are_collinear(end_side.T):
          coords = coords[1:]
        else:
          resolved = True
    
      return coords
    
    def solve_problem(file_name):
    
      garden = parse_input(Path(cwd, file_name))
      unique_plants = set(garden.flatten())
      total_price = 0
      discounted_total_price = 0
    
      for plant in unique_plants:
    
        polygon = get_polygon(plant, garden)
    
        for geom in polygon.geoms:
          coordinates = simplify_boundary(geom.boundary)
          total_price += geom.area*geom.length
          discounted_total_price += geom.area*len(coordinates)
    
      return int(total_price), int(discounted_total_price)
    
    

  • Python

    I initially cached the calculate_next function but honestly number of unique numbers don’t grow that much (couple thousands) so I did not feel a difference when I removed the cache. Using a dict just blazes through the problem.

    from pathlib import Path
    from collections import defaultdict
    cwd = Path(__file__).parent
    
    def parse_input(file_path):
      with file_path.open("r") as fp:
        numbers = list(map(int, fp.read().splitlines()[0].split(' ')))
    
      return numbers
    
    def calculate_next(val):
    
      if val == 0:
        return [1]
      if (l:=len(str(val)))%2==0:
        return [int(str(val)[:int(l/2)]), int(str(val)[int(l/2):])]
      else:
        return [2024*val]
    
    def solve_problem(file_name, nblinks):
    
      numbers = parse_input(Path(cwd, file_name))
      nvals = 0
    
      for indt, node in enumerate(numbers):
    
        last_nodes = {node:1}
        counter = 0
    
        while counter<nblinks:
          new_nodes = defaultdict(int)
    
          for val,count in last_nodes.items():
            val_next_nodes = calculate_next(val)
    
            for node in val_next_nodes:
              new_nodes[node] += count
    
          last_nodes = new_nodes
          counter += 1
        nvals += sum(last_nodes.values())
    
      return nvals
    
    




  • Python

    Not surprisingly, trees

    import numpy as np
    from pathlib import Path
    
    cwd = Path(__file__).parent
    
    cross = np.array([[-1,0],[1,0],[0,-1],[0,1]])
    
    class Node():
      def __init__(self, coord, parent):
        self.coord = coord
        self.parent = parent
    
      def __repr__(self):
        return f"{self.coord}"
    
    def parse_input(file_path):
    
      with file_path.open("r") as fp:
        data = list(map(list, fp.read().splitlines()))
    
      return np.array(data, dtype=int)
    
    def find_neighbours(node_pos, grid):
    
      I = list(filter(lambda x: all([c>=0 and o-c>0 for c,o in zip(x,grid.shape)]),
                      list(cross + node_pos)))
    
      candidates = grid[tuple(np.array(I).T)]
      J = np.argwhere(candidates-grid[tuple(node_pos)]==1).flatten()
    
      return list(np.array(I).T[:, J].T)
    
    def construct_tree_paths(grid):
    
      roots = list(np.argwhere(grid==0))
      trees = []
    
      for root in roots:
    
        levels = [[Node(root, None)]]
        while len(levels[-1])>0 or len(levels)==1:
          levels.append([Node(node, root) for root in levels[-1] for node in
                         find_neighbours(root.coord, grid)])
        trees.append(levels)
    
      return trees
    
    def trace_back(trees, grid):
    
      paths = []
    
      for levels in trees:
        for node in levels[-2]:
    
          path = ""
          while node is not None:
            coord = ",".join(node.coord.astype(str))
            path += f"{coord} "
            node = node.parent
          paths.append(path)
    
      return paths
    
    def solve_problem(file_name):
    
      grid = parse_input(Path(cwd, file_name))
      trees = construct_tree_paths(grid)
      trails = trace_back(trees, grid)
      ntrails = len(set(trails))
      nreached = sum([len(set([tuple(x.coord) for x in levels[-2]])) for levels in trees])
    
      return nreached, ntrails
    


  • Wow I got thrashed by chatgpt. Strictly speaking that is correct, it is more akin to Tree Search. But even then not strictly because in tree search you are searching through a list of objects that is known, you build a tree out of it and based on some conditions eliminate half of the remaining tree each time. Here I have some state space (as chatgpt claims!) and again based on applying certain conditions, I eliminate some portion of the search space successively (so I dont have to evaluate that part of the tree anymore). To me both are very similar in spirit as both methods avoid evaluating some function on all the possible inputs and successively chops off a fraction of the search space. To be more correct I will atleast replace it with tree search though, thanks. And thanks for taking a close look at my solution and improving it.




  • Python

    It is a tree search

    def parse_input(path):
    
      with path.open("r") as fp:
        lines = fp.read().splitlines()
    
      roots = [int(line.split(':')[0]) for line in lines]
      node_lists = [[int(x)  for x in line.split(':')[1][1:].split(' ')] for line in lines]
    
      return roots, node_lists
    
    def construct_tree(root, nodes, include_concat):
    
      levels = [[] for _ in range(len(nodes)+1)]
      levels[0] = [(str(root), "")]
      # level nodes are tuples of the form (val, operation) where both are str
      # val can be numerical or empty string
      # operation can be *, +, || or empty string
    
      for indl, level in enumerate(levels[1:], start=1):
    
        node = nodes[indl-1]
    
        for elem in levels[indl-1]:
    
          if elem[0]=='':
            continue
    
          if elem[0][-len(str(node)):] == str(node) and include_concat:
            levels[indl].append((elem[0][:-len(str(node))], "||"))
          if (a:=int(elem[0]))%(b:=int(node))==0:
            levels[indl].append((str(int(a/b)), '*'))
          if (a:=int(elem[0])) - (b:=int(node))>0:
            levels[indl].append((str(a - b), "+"))
    
      return levels[-1]
    
    def solve_problem(file_name, include_concat):
    
      roots, node_lists = parse_input(Path(cwd, file_name))
      valid_roots = []
    
      for root, nodes in zip(roots, node_lists):
    
        top = construct_tree(root, nodes[::-1], include_concat)
    
        if any((x[0]=='1' and x[1]=='*') or (x[0]=='0' and x[1]=='+') or
               (x[0]=='' and x[1]=='||') for x in top):
    
          valid_roots.append(root)
    
      return sum(valid_roots)