input: Write a python function to solve the 8-puzzle problem using the A* search algorithm.
  assert a_star_search([1, 2, 3, 4, 5, 6, 7, 8, 0], Puzzle([1, 2, 3, 4, 5, 6, 7, 0, 8], 0, 0)).steps == 1
  assert a_star_search([1, 2, 3, 4, 5, 6, 0, 7, 8], Puzzle([1, 2, 3, 4, 5, 6, 7, 0, 8], 0, 0)).steps == 2
  assert a_star_search([1, 2, 3, 4, 5, 6, 7, 0, 8], Puzzle([1, 2, 3, 4, 5, 6, 7, 8, 0], 0, 0)).steps == 1
output: from heapq import heappop, heappush
  from itertools import count
  
  class Puzzle:
      def __init__(self, board, steps, heuristic):
          self.board = board
          self.steps = steps
          self.heuristic = heuristic
  
      def __lt__(self, other):
          return (self.steps + self.heuristic) < (other.steps + other.heuristic)
  
      def possible_moves(self):
          moves = []
          zero_index = self.board.index(0)
          zero_row, zero_col = divmod(zero_index, 3)
          directions = [(-1, 0), (1, 0), (0, -1), (0, 1)]
  
          for dr, dc in directions:
              new_row, new_col = zero_row + dr, zero_col + dc
              if 0 <= new_row < 3 and 0 <= new_col < 3:
                  new_index = new_row * 3 + new_col
                  new_board = self.board[:]
                  new_board[zero_index], new_board[new_index] = new_board[new_index], new_board[zero_index]
                  moves.append(Puzzle(new_board, self.steps + 1, 0))
  
          return moves
  
  def heuristic(board, goal):
      return sum(abs(b // 3 - g // 3) + abs(b % 3 - g % 3)
                 for b, g in ((board.index(i), goal.index(i)) for i in range(1, 9)))
  
  def a_star_search(goal, start_puzzle):
      start_puzzle.heuristic = heuristic(start_puzzle.board, goal)
      open_set = []
      entry_count = count()
      heappush(open_set, (start_puzzle.steps + start_puzzle.heuristic, next(entry_count), start_puzzle))
      closed_set = set()
  
      while open_set:
          _, _, current = heappop(open_set)
  
          if current.board == goal:
              return current
  
          closed_set.add(tuple(current.board))
  
          for move in current.possible_moves():
              if tuple(move.board) in closed_set:
                  continue
  
              move.heuristic = heuristic(move.board, goal)
              heappush(open_set, (move.steps + move.heuristic, next(entry_count), move))
  
      return None