Dada una lista de listas (es decir, una lista anidada), quiero eliminar todas las listas internas con longitud 1 desde el principio y el final de la lista externa. Por ejemplo
d = [[3], [1,5], [3], [3,2,4], [8], [3]]debe convertirse en:[1,5], [3], [3,2,4].
Para el comienzo de la lista, estoy usando el siguiente código:
i = 0 while len(d[i]) == 1: d.remove(d[i]) n = n -1y para el final, uso este código:
while len(d[n-1]) == 1: d.remove(d[n-1]) n = n - 1donde n = número de listas.
Cuando ejecuto esto me sale:
[[1, 5], [3, 2, 4]]Entonces también elimina las listas en el medio con longitud 1. ¿Cómo puedo cambiar el código para que no haga eso?
Entonces, para un número n de listas, quiero eliminar todas las listas del principio que tengan una longitud de 1 hasta que haya una lista que no tenga una longitud de 1. Quiero hacer lo mismo para el final de la lista.
La mayoría de las respuestas actuales implican aparecer repetidamente desde el principio de una lista, lo cual es lento.
Usando las respuestas aquí para encontrar el primer y último índice donde una condición no se cumple, podemos encontrar el inicio y el final de la sublista deseada en tiempo lineal. Luego, construimos la rebanada usando los índices inicial y final encontrados. Si la lista contiene solo sublistas de longitud 1, entonces nuestros generadores generarán una StopIteration ; para evitar esto, verificamos con anticipación si todas las listas tienen longitud 1:
if all(len(item) == 1 for item in data): result = [] else: start = next(i for i, v in enumerate(data) if len(v) != 1) end = len(data) - next(i for i, v in enumerate(reversed(data)) if len(v) != 1) result = data[start:end] print(result)Esto da como resultado:
[[1, 5], [3], [3, 2, 4]]Si realmente necesita velocidad, puede omitir los generadores y la verificación de primer paso (este código ha sido mejorado a partir de una sugerencia de juanpa.arrivillaga ):
start = -1 for idx in range(len(data)): if len(data[idx]) != 1: start = idx break end = len(data) for idx in reversed(range(len(data))): if len(data[idx]) != 1: end = idx break result = data[start:end + 1] print(result)que parece funcionar mejor en mi máquina.
Código de referencia:
import timeit def bb1(data): if all(len(item) == 1 for item in data): result = [] else: start = next(i for i, v in enumerate(data) if len(v) != 1) end = len(data) - next(i for i, v in enumerate(reversed(data)) if len(v) != 1) result = data[start:end] return result def bb2(data): start = -1 for idx in range(len(data)): if len(data[idx]) != 1: start = idx break end = len(data) for idx in reversed(range(len(data))): if len(data[idx]) != 1: end = idx break result = data[start:end + 1] return result def fb(d): while len(d[0]) == 1: d.pop(0) # remove from head while len(d[-1]) == 1: d.pop() # remove from tailEntonces,
data = ([[3] for _ in range(100000)]) + [[3], [1,5], [3], [3,2,4], [8], [3]] %timeit -n7 bb1(data[:]) %timeit -n7 bb2(data[:]) %timeit -n7 fb(data[:])Producción:
7 loops, best of 5: 23.7 ms per loop 7 loops, best of 5: 13.1 ms per loop 7 loops, best of 5: 900 ms per loopSimplemente puede realizar un seguimiento de los índices donde se cumple la condición y luego dividir su lista en consecuencia, en lugar de modificar la lista.
def filter_list(list_of_list, len_to_filter): l, r = 0, -1 # left, right indices while len(list_of_list[l]) == len_to_filter: l += 1 while len(list_of_list[r]) == len_to_filter: r -= 1 if r == -1: # avoid case where r + 1 is 0 return list_of_list[l:] else: return list_of_list[l : r + 1] filter_list(d, 1)Esto no modifica la lista original y no requiere ninguna ordenación o inversión. Simplemente recorre la lista de cada lado hasta que se alcanza un tamaño diferente al esperado.
Aquí hay un punto de referencia que lo compara con pop , si hay pocas listas con la longitud requerida para eliminar, la version y las versiones de índices son prácticamente las mismas. Si hay muchas listas con la longitud requerida para filtrar, las operaciones pop se vuelven no despreciables y la versión de índices es mejor.
import random small = [[3], [1, 5], [3], [3, 2, 4], [8], [3]] # create 10_000 lists of length 1 or 2 ll = [ [random.randint(0, 1000) for _ in range(random.randint(1, 2))] for _ in range(10_000) ] worst_case = [ [random.randint(0, 1000) for _ in range(random.randint(1, 1))] for _ in range(10_000) ] worst_case[5000] = [1, 2, 3] def filter_pop(list_of_list, len_to_filter): while len(list_of_list[0]) == len_to_filter: list_of_list.pop(0) while len(list_of_list[-1]) == len_to_filter: list_of_list.pop() return list_of_list def filter_list(list_of_list, len_to_filter): l, r = 0, -1 # left, right indices while len(list_of_list[l]) == len_to_filter: l += 1 while len(list_of_list[r]) == len_to_filter: r -= 1 if r == -1: # avoid case where r + 1 is 0 return list_of_list[l:] else: return list_of_list[l : r + 1] In [208]: %timeit -n100 filter_pop(ll[:], 1) 47.6 µs ± 9.77 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) In [209]: %timeit -n100 filter_pop(worst_case[:], 1) 7.46 ms ± 71.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) In [210]: %timeit -n100 filter_list(ll, 1) 43.7 µs ± 11.8 µs per loop (mean ± std. dev. of 7 runs, 100 loops each) In [211]: %timeit -n100 filter_list(worst_case, 1) 1 ms ± 59 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)aquí hay una solución simple:
def removeFunction(d): l = 0 r = len(d) - 1 # remove length 1 elements from beginning for i in range(len(d)): if len(d[i]) > 1: l = i break # remove length 1 elements from the end for i in range(len(d)-1, -1, -1): if len(d[i]) > 1: r = i break return d[l:r+1]