input: Write a function to implement a Q-learning algorithm for the 'GridWorld-v0' environment.
  assert len(q_learning(env, 0.8, 0.95, 2000)) == 2000
  assert isinstance(q_learning(env, 0.8, 0.95, 2000), list)
  assert all(isinstance(reward, (int, float)) for reward in q_learning(env, 0.8, 0.95, 2000))
output: import numpy as np
  import gym
  
  def q_learning(env, learning_rate, discount_factor, num_episodes):
      # Initialize Q-table with zeros
      q_table = np.zeros([env.observation_space.n, env.action_space.n])
      rewards_per_episode = []
  
      for episode in range(num_episodes):
          state = env.reset()
          total_rewards = 0
          done = False
  
          while not done:
              # Choose an action using an epsilon-greedy strategy
              if np.random.uniform(0, 1) < (1 / (episode + 1)):
                  action = env.action_space.sample()
              else:
                  action = np.argmax(q_table[state, :])
  
              # Take the action and observe the outcome
              new_state, reward, done, _ = env.step(action)
  
              # Update the Q-table using the Q-learning update rule
              q_table[state, action] = q_table[state, action] + learning_rate * (reward + discount_factor * np.max(q_table[new_state, :]) - q_table[state, action])
  
              state = new_state
              total_rewards += reward
  
          rewards_per_episode.append(total_rewards)
  
      return rewards_per_episode
  
  # Example usage (uncomment to run if GridWorld-v0 is available)
  # env = gym.make('GridWorld-v0')
  # rewards = q_learning(env, 0.8, 0.95, 2000)
  # print(rewards)