Dividir una lista en partes en función de un conjunto de índices en Python

¿Cuál es la mejor manera de dividir una lista en partes según un número arbitrario de índices? Por ejemplo, dado el siguiente código

indexes = [5, 12, 17] list = range(20) 

devolver algo como esto

 part1 = list[:5] part2 = list[5:12] part3 = list[12:17] part4 = list[17:] 

Si no hay índices debe devolver la lista completa.

Esta es la solución más simple y más python que se me ocurre:

 def partition(alist, indices): return [alist[i:j] for i, j in zip([0]+indices, indices+[None])] 

Si las entradas son muy grandes, entonces la solución de iteradores debería ser más conveniente:

 from itertools import izip, chain def partition(alist, indices): pairs = izip(chain([0], indices), chain(indices, [None])) return (alist[i:j] for i, j in pairs) 

y, por supuesto, la solución muy, muy perezosa (si no te importa obtener matrices en lugar de listas, pero de todos modos siempre puedes revertirlas a listas):

 import numpy partition = numpy.split 

Me interesaría ver una forma más pythonica de hacer esto también. Pero esta es una solución de mierda. Debe agregar una comprobación para una lista de índice vacía.

Algo a lo largo de las líneas de:

 indexes = [5, 12, 17] list = range(20) output = [] prev = 0 for index in indexes: output.append(list[prev:index]) prev = index output.append(list[indexes[-1]:]) print output 

produce

 [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16], [17, 18, 19]] 

Mi solución es similar a la de Il-Bhima.

 >>> def parts(list_, indices): ... indices = [0]+indices+[len(list_)] ... return [list_[v:indices[k+1]] for k, v in enumerate(indices[:-1])] 

Enfoque alternativo

Si está dispuesto a cambiar ligeramente la forma en que ingresa los índices, de índices absolutos a relativos (es decir, de [5, 12, 17] a [5, 7, 5] , lo siguiente también le dará el resultado deseado, Si bien no crea listas intermedias.

 >>> from itertools import islice >>> def parts(list_, indices): ... i = iter(list_) ... return [list(islice(i, n)) for n in chain(indices, [None])] 
 >>> def burst_seq(seq, indices): ... startpos = 0 ... for index in indices: ... yield seq[startpos:index] ... startpos = index ... yield seq[startpos:] ... >>> list(burst_seq(range(20), [5, 12, 17])) [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9, 10, 11], [12, 13, 14, 15, 16], [17, 18, 19]] >>> list(burst_seq(range(20), [])) [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19]] >>> list(burst_seq(range(0), [5, 12, 17])) [[], [], [], []] >>> 

Maxima mea culpa: usa una statement for , y no usa cosas de whizzbang como itertools, zip (), None como centinela, lista de comprensión, …

😉

 indices = [5, 12, 17] input = range(20) output = [] reduce(lambda x, y: output.append(input[x:y]) or y, indices + [len(input)], 0) print output 

Esto es todo lo que pude pensar

 def partition(list_, indexes): if indexes[0] != 0: indexes = [0] + indexes if indexes[-1] != len(list_): indexes = indexes + [len(list_)] return [ list_[a:b] for (a,b) in zip(indexes[:-1], indexes[1:])] 

Cide hace tres copias de la matriz: [0] + índices de copias, ([0] + índices) + [] copias de nuevo, e índices [: – 1] se copiarán por tercera vez. Il-Bhima hace cinco copias. (No estoy contando el valor de retorno, por supuesto).

Se podrían reducir (izip, islice), pero aquí hay una versión de copia cero:

 def iterate_pairs(lst, indexes): prev = 0 for i in indexes: yield prev, i prev = i yield prev, len(lst) def partition(lst, indexes): for first, last in iterate_pairs(lst, indexes): yield lst[first:last] indexes = [5, 12, 17] lst = range(20) print [l for l in partition(lst, indexes)] 

Por supuesto, las copias de la matriz son bastante baratas (código nativo) en comparación con Python interpretado, pero esto tiene otra ventaja: es fácil reutilizar, mutar los datos directamente:

 for first, last in iterate_pairs(lst, indexes): for i in range(first, last): lst[i] = first print lst # [0, 0, 0, 0, 0, 5, 5, 5, 5, 5, 5, 5, 12, 12, 12, 12, 12, 17, 17, 17] 

(Es por eso que pasé los índices a iterate_pairs. Si no te importa eso, puedes eliminar ese parámetro y solo hacer que la última línea sea “yield prev, None”, que es todo lo que necesita la partición ()).

Aquí hay otra respuesta.

 def partition(l, indexes): result, indexes = [], indexes+[len(l)] reduce(lambda x, y: result.append(l[x:y]) or y, indexes, 0) return result 

Es compatible con los índices negativos y tal.

 >>> partition([1,2,3,4,5], [1, -1]) [[1], [2, 3, 4], [5]] >>> 

El plural de index es indices. Yendo por la simplicidad / legibilidad.

 indices = [5, 12, 17] input = range(20) output = [] for i in reversed(indices): output.append(input[i:]) input[i:] = [] output.append(input) while len(output): print output.pop()