from numpy.random import choice, seed def get_first_random_solution(m, data): seed(42) random_indexes = choice(len(data.index), size=m, replace=False) return data.loc[random_indexes] def replace_worst_element(previous, data): solution = previous.copy() worst_index = solution["distance"].astype(float).idxmin() random_element = data.sample().squeeze() while solution.isin(random_element.values.ravel()).any().any(): random_element = data.sample().squeeze() solution.loc[worst_index] = random_element return solution, worst_index def choose_best_solution(previous, current, index): if previous.loc[index].distance >= current.loc[index].distance: return previous return current def get_random_solution(previous, data): candidates = [] candidates.append(previous) solution, worst_index = replace_worst_element(previous, data) previous_worst_distance = previous["distance"].loc[worst_index] last_solution = candidates[-1] while last_solution.distance.loc[worst_index] <= previous_worst_distance: solution, _ = replace_worst_element(previous=solution, data=data) if solution.equals(last_solution): best_solution = choose_best_solution( previous=previous, current=solution, index=worst_index ) return best_solution candidates.append(solution) last_solution = candidates[-1] return last_solution def explore_neighbourhood(element, data, max_iterations=100000): neighbourhood = [] neighbourhood.append(element) for i in range(max_iterations): print(f"Iteration {i}") previous_solution = neighbourhood[-1] neighbour = get_random_solution(previous=previous_solution, data=data) if neighbour.equals(previous_solution): break neighbourhood.append(neighbour) return neighbour def local_search(m, data): first_solution = get_first_random_solution(m=m, data=data) best_solution = explore_neighbourhood(element=first_solution, data=data) return best_solution