Estoy buscando una función para hacer que la lista esté lo más desordenada posible. Preferiblemente en Python.
Trasfondo:
Quiero verificar los estados de las URL y ver si las URL dan un 404 o no. Solo uso asyncio y módulos de requests . Nada sofisticado.
Ahora no quiero sobrecargar los servidores, así que quiero minimizar la verificación de las URL que están en el mismo dominio al mismo tiempo. Tengo esta idea de ordenar las URL de manera que los elementos que están cerca uno del otro (que tienen la misma clave de clasificación = nombre de dominio) se colocan lo más separados posible en la lista.
Un ejemplo con números:
a=[1,1,2,3,3] # <== sorted list, sortness score = 2 0,1,2,3,4 # <== positionspodría desclasificarse como:
b=[1,3,2,1,3] # <== unsorted list, sortness score = 6 0,1,2,3,4 # <== positionsDiría que podemos calcular una puntuación de clasificación sumando las distancias entre elementos iguales (que tienen la misma clave = nombre de dominio). Mayor clasificación significa mejor sin clasificar. Tal vez haya una mejor manera de probar la falta de orden.
El puntaje de clasificación para la lista a es 2. La suma de las distancias para 1 es (1-0)=1, para 2 es 0, para 3 es (4-3)=1.
El puntaje de clasificación para la lista b es 6. La suma de las distancias para 1 es (3-0)=3, para 2 es 0, para 3 es (4-1)=3.
La lista de URL se parecería a una lista de tuplas (dominio, URL):
[ ('example.com', 'http://example.com/404'), ('test.com', 'http://test.com/404'), ('test.com', 'http://test.com/405'), ('example.com', 'http://example.com/405'), ... ]Estoy trabajando en un prototipo que funciona bien, pero no de manera óptima, ya que puedo encontrar algunas variantes que es mejor ordenarlas a mano.
¿Alguien quiere darle una oportunidad?
Este es mi código , pero no es genial :):
from collections import Counter from collections import defaultdict import math def test_unsortness(lst:list) -> float: pos = defaultdict(list) score = 0 # Store positions for each key # input = [1,3,2,3,1] => {1: [0, 4], 3: [1, 3], 2: [2]} for c,l in enumerate(lst): pos[l].append(c) for k,poslst in pos.items(): for i in range(len(poslst)-1): score += math.sqrt(poslst[i+1] - poslst[i]) return score def unsort(lst:list) -> list: free_positions = list(range(0,len(lst))) output_list = [None] * len(free_positions) for val, count in Counter(lst).most_common(): pos = 0 step = len(free_positions) / count for i in range(count): output_list[free_positions[int(pos)]] = val free_positions[int(pos)] = None # Remove position later pos = pos + step free_positions = [p for p in free_positions if p] return output_list lsts = list() lsts.append( [1,1,2,3,3] ) lsts.append( [1,3,2,3,1] ) # This has the worst score after unsort() lsts.append( [1,2,3,0,1,2,3] ) # This has the worst score after unsort() lsts.append( [3,2,1,0,1,2,3] ) # This has the worst score after unsort() lsts.append( [3,2,1,3,1,2,3] ) # This has the worst score after unsort() lsts.append( [1,2,3,4,5] ) for lst in lsts: ulst = unsort(lst) print( ( lst, '%.2f'%test_unsortness(lst), '====>', ulst, '%.2f'%test_unsortness(ulst), ) ) # Original score Unsorted score # ------- ----- -------- ----- # ([1, 1, 2, 3, 3], '2.00', '====>', [1, 3, 1, 3, 2], '2.83') # ([1, 3, 2, 3, 1], '3.41', '====>', [1, 3, 1, 3, 2], '2.83') # ([1, 2, 3, 0, 1, 2, 3], '6.00', '====>', [1, 2, 3, 1, 2, 3, 0], '5.20') # ([3, 2, 1, 0, 1, 2, 3], '5.86', '====>', [3, 2, 1, 3, 2, 1, 0], '5.20') # ([3, 2, 1, 3, 1, 2, 3], '6.88', '====>', [3, 2, 3, 1, 3, 2, 1], '6.56') # ([1, 2, 3, 4, 5], '0.00', '====>', [1, 2, 3, 4, 5], '0.00')PD. No estoy buscando solo una función aleatoria y sé que hay rastreadores que pueden administrar cargas de dominio, pero esto es por el bien del ejercicio.
Una manera fácil de codificar una lista es maximizar su puntuación de "clasificación" utilizando un algoritmo genético con un cromosoma de permutación. Pude piratear rápidamente una versión en R usando el paquete GA. No soy un usuario de Python, pero estoy seguro de que hay bibliotecas GA para Python que incluyen cromosomas de permutación. Si no, se puede adaptar una biblioteca GA general con cromosomas vectoriales de valor real. Simplemente usa un vector con valores en [0, 1] como un cromosoma y convierte cada vector a su índice de clasificación.
Cuando enfrenté un problema similar, así es como lo resolví:
MAX_INT - LevenshteinDistance(s1,s2)Podría implementar una búsqueda binaria invertida.
from typing import Union, List sorted_int_list = [1, 1, 2, 3, 3] unsorted_int_list = [1, 3, 2, 1, 3] sorted_str_list = [ "example.com", "example.com", "test.com", "stackoverflow.com", "stackoverflow.com", ] unsorted_str_list = [ "example.com", "stackoverflow.com", "test.com", "example.com", "stackoverflow.com", ] def inverted_binary_search( input_list: List[Union[str, int]], search_elem: Union[int, str], list_selector_start: int, list_selector_end: int, ) -> int: if list_selector_end - list_selector_start <= 1: if search_elem < input_list[list_selector_start]: return list_selector_start - 1 else: return list_selector_start list_selector_mid = (list_selector_start + list_selector_end) // 2 if input_list[list_selector_mid] > search_elem: return inverted_binary_search( input_list=input_list, search_elem=search_elem, list_selector_start=list_selector_mid, list_selector_end=list_selector_end, ) elif input_list[list_selector_mid] < search_elem: return inverted_binary_search( input_list=input_list, search_elem=search_elem, list_selector_start=list_selector_start, list_selector_end=list_selector_mid, ) else: return list_selector_mid def inverted_binary_insertion_sort(your_list: List[Union[str, int]]): for idx in range(1, len(your_list)): selected_elem = your_list[idx] inverted_binary_search_position = ( inverted_binary_search( input_list=your_list, search_elem=selected_elem, list_selector_start=0, list_selector_end=idx, ) + 1 ) for idk in range(idx, inverted_binary_search_position, -1): your_list[idk] = your_list[idk - 1] your_list[inverted_binary_search_position] = selected_elem return your_listProducción
inverted_sorted_int_list = inverted_binary_insertion_sort(sorted_int_list) print(inverted_sorted_int_list) >> [1, 3, 3, 2, 1] inverted_sorted_str_list = inverted_binary_insertion_sort(sorted_str_list) print(inverted_sorted_str_list) >> ['example.com', 'stackoverflow.com', 'stackoverflow.com', 'test.com', 'example.com']Actualizar:
Teniendo en cuenta los comentarios, también podría ejecutar la función dos veces. Esto desenredará los duplicados.
inverted_sorted_int_list = inverted_binary_insertion_sort( inverted_binary_insertion_sort(sorted_int_list) ) >> [1, 3, 2, 1, 3]No existe una definición obvia de desorden que funcione mejor para usted, pero aquí hay algo que al menos funciona bien:
En orden ordenado, los índices de los elementos que están juntos suelen diferir solo en los bits más pequeños. Al invertir el orden de los bits, los nuevos índices para los elementos que están muy juntos difieren en los bits más grandes , por lo que terminarán muy separados.
def bitreverse(x, bits): # reverse the lower 32 bits x = ((x & 0x55555555) << 1) | ((x & 0xAAAAAAAA) >> 1) x = ((x & 0x33333333) << 2) | ((x & 0xCCCCCCCC) >> 2) x = ((x & 0x0F0F0F0F) << 4) | ((x & 0xF0F0F0F0) >> 4) x = ((x & 0x00FF00FF) << 8) | ((x & 0xFF00FF00) >> 8) x = ((x & 0x0000FFFF) << 16) | ((x & 0xFFFF0000) >> 16) # take only the appropriate length return (x>>(32-bits)) & ((1<<bits)-1) def antisort(inlist): if len(inlist) < 3: return inlist inlist = sorted(inlist) #get the next power of 2 list length p2len = 2 bits = 1 while p2len < len(inlist): p2len *= 2 bits += 1 templist = [None] * p2len for i in range(len(inlist)): newi = i * p2len // len(inlist) newi = bitreverse(newi, bits) templist[newi] = inlist[i] return [item for item in templist if item != None] print(antisort(["a","b","c","d","e","f","g", "h","i","j","k","l","m","n","o","p","q","r", "s","t","u","v","w","x","y","z"]))Producción:
['a', 'n', 'h', 'u', 'e', 'r', 'k', 'x', 'c', 'p', 'f', 's', 'm', 'z', 'b', 'o', 'i', 'v', 'l', 'y', 'd', 'q', 'j', 'w', 'g', 't']Espero que este algoritmo funcione correctamente:
unsorted_list = ['c', 'a', 'a', 'a', 'a', 'b', 'b'] d = {i: unsorted_list.count(i) for i in unsorted_list} print(d) # {'c': 1, 'a': 4, 'b': 2} d = {k: v for k, v in sorted(d.items(), key=lambda item: item[1], reverse=True)} print(d) # {'a': 4, 'b': 2, 'c': 1} result = [None] * len(unsorted_list) border_index_left = 0 border_index_right = len(unsorted_list) - 1 it = iter(d) def set_recursively(k, nk): set_borders(k) set_borders(nk) if d[k]: set_recursively(k, nk) def set_borders(key): global border_index_left, border_index_right if key is not None and d[key]: result[border_index_left] = key d[key] = d[key] - 1 border_index_left = border_index_left + 1 if key is not None and d[key]: result[border_index_right] = key d[key] = d[key] - 1 border_index_right = border_index_right - 1 next_element = next(it, None) for k, v in d.items(): next_element = next(it, None) set_recursively(k, next_element) print(result) # ['a', 'b', 'a', 'c', 'a', 'b', 'a']Visualmente, parece caminar desde el borde hasta el medio:
[2, 3, 3, 3, 1, 1, 0] [None, None, None, None, None, None, None] [3, None, None, None, None, None, None] [3, None, None, None, None, None, 3] [3, 1, None, None, None, None, 3] [3, 1, None, None, None, 1, 3] [3, 1, 3, None, None, 1, 3] [3, 1, 3, 2, None, 1, 3] [3, 1, 3, 2, 0, 1, 3]Usé Google OR Tools para resolver este problema. Lo enmarqué como un problema de optimización de restricciones y lo modelé de esa manera.
from collections import defaultdict from itertools import chain, combinations from ortools.sat.python import cp_model model = cp_model.CpModel() data = [ ('example.com', 'http://example.com/404'), ('test.com', 'http://test.com/404'), ('test.com', 'http://test.com/405'), ('example.com', 'http://example.com/405'), ('google.com', 'http://google.com/404'), ('example.com', 'http://example.com/406'), ('stackoverflow.com', 'http://stackoverflow.com/404'), ('test.com', 'http://test.com/406'), ('example.com', 'http://example.com/407') ] tmp = defaultdict(list) for (domain, url) in sorted(data): var = model.NewIntVar(0, len(data) - 1, url) tmp[domain].append(var) # store URLs as model variables where the key is the domain vals = list(chain.from_iterable(tmp.values())) # create a single list of all variables model.AddAllDifferent(vals) # all variables must occupy a unique spot in the output constraint = [] for urls in tmp.values(): if len(urls) == 1: # a single domain does not need a specific constraint constraint.append(urls[0]) continue combos = combinations(urls, 2) for (x, y) in combos: # create combinations between each URL of a specific domain constraint.append((x - y)) model.Maximize(sum(constraint)) # maximize the distance between similar URLs from our constraint list solver = cp_model.CpSolver() status = solver.Solve(model) output = [None for _ in range(len(data))] if status == cp_model.OPTIMAL or status == cp_model.FEASIBLE: for val in vals: idx = solver.Value(val) output[idx] = val.Name() print(output) ['http://example.com/407', 'http://test.com/406', 'http://example.com/406', 'http://test.com/405', 'http://example.com/405', 'http://stackoverflow.com/404', 'http://google.com/404', 'http://test.com/404', 'http://example.com/404']En lugar de desordenar su lista de URL, ¿por qué no agruparlas por dominio, cada una en una cola, y luego procesarlas de forma asíncrona con un retraso (¿aleatorio?) en el medio?
Me parece menos complejo que lo que está tratando de hacer para lograr lo mismo y si tiene mucho dominio, siempre puede acelerar el número para ejecutarlo simultáneamente en ese punto.
Solo digo que poner un retraso de tiempo corto funcionaría bien. Creo que alguien ya lo mencionó. Es muy sencillo y muy fiable. Podrías hacer algo como:
from random import sample from time import sleep import requests intervalList = list(range(0.1, 0.5)) error404 = [] connectionError = [] for i in your_URL_list: ststusCode = req.get(str(i)).status_code if ststusCode == 404: error404.append(i) sleep(sample(intervalList,1))Salud