docs: def _local_search(problem, fringe_expander, iterations_limit=0, fringe_size=1,
                    random_initial_states=False, stop_when_no_better=True,
                    viewer=None):
      '''
      Basic algorithm for all local search algorithms.
      '''
      if viewer:
          viewer.event('started')
  
      fringe = BoundedPriorityQueue(fringe_size)
      if random_initial_states:
          for _ in range(fringe_size):
              s = problem.generate_random_state()
              fringe.append(SearchNodeValueOrdered(state=s, problem=problem))
      else:
          fringe.append(SearchNodeValueOrdered(state=problem.initial_state,
                                               problem=problem))
  
      finish_reason = ''
      iteration = 0
      run = True
      best = None
  
      while run:
          if viewer:
              viewer.event('new_iteration', list(fringe))
  
          old_best = fringe[0]
          fringe_expander(fringe, iteration, viewer)
          best = fringe[0]
  
          iteration += 1
  
          if iterations_limit and iteration >= iterations_limit:
              run = False
              finish_reason = 'reaching iteration limit'
          elif old_best.value >= best.value and stop_when_no_better:
              run = False
              finish_reason = 'not being able to improve solution'
  
      if viewer:
          viewer.event('finished', fringe, best, 'returned after %s' % finish_reason)
  
      return best