Python: Cortar una lista en n particiones de longitud casi igual

Estoy buscando una forma rápida, limpia y pirónica de dividir una lista en exactamente n particiones casi iguales.

partition([1,2,3,4,5],5)->[[1],[2],[3],[4],[5]] partition([1,2,3,4,5],2)->[[1,2],[3,4,5]] (or [[1,2,3],[4,5]]) partition([1,2,3,4,5],3)->[[1,2],[3,4],[5]] (there are other ways to slice this one too) 

Hay varias respuestas aquí. Iteración sobre los segmentos de la lista que se ejecutan muy cerca de lo que quiero, excepto que están enfocados en el tamaño de la lista, y me importa el número de las listas (algunas de ellas también se rellenan con Ninguna). Estos son trivialmente convertidos, obviamente, pero estoy buscando una mejor práctica.

De manera similar, las personas han señalado excelentes soluciones aquí. ¿Cómo se divide una lista en partes de tamaño uniforme? para un problema muy similar, pero estoy más interesado en el número de particiones que en el tamaño específico, siempre que esté dentro de 1. Una vez más, esto es trivialmente convertible, pero estoy buscando una mejor práctica.

 def partition(lst, n): division = len(lst) / float(n) return [ lst[int(round(division * i)): int(round(division * (i + 1)))] for i in xrange(n) ] >>> partition([1,2,3,4,5],5) [[1], [2], [3], [4], [5]] >>> partition([1,2,3,4,5],2) [[1, 2, 3], [4, 5]] >>> partition([1,2,3,4,5],3) [[1, 2], [3, 4], [5]] >>> partition(range(105), 10) [[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10], [11, 12, 13, 14, 15, 16, 17, 18, 19, 20], [21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31], [32, 33, 34, 35, 36, 37, 38, 39, 40, 41], [42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52], [53, 54, 55, 56, 57, 58, 59, 60, 61, 62], [63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73], [74, 75, 76, 77, 78, 79, 80, 81, 82, 83], [84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94], [95, 96, 97, 98, 99, 100, 101, 102, 103, 104]] 

Versión de Python 3:

 def partition(lst, n): division = len(lst) / n return [lst[round(division * i):round(division * (i + 1))] for i in range(n)] 

Solo una toma diferente, eso solo funciona si [[1,3,5],[2,4]] es una partición aceptable, en tu ejemplo.

 def partition ( lst, n ): return [ lst[i::n] for i in xrange(n) ] 

Esto satisface el ejemplo mencionado en el ejemplo de @Daniel Stutzbach:

 partition(range(105),10) # [[0, 10, 20, 30, 40, 50, 60, 70, 80, 90, 100], # [1, 11, 21, 31, 41, 51, 61, 71, 81, 91, 101], # [2, 12, 22, 32, 42, 52, 62, 72, 82, 92, 102], # [3, 13, 23, 33, 43, 53, 63, 73, 83, 93, 103], # [4, 14, 24, 34, 44, 54, 64, 74, 84, 94, 104], # [5, 15, 25, 35, 45, 55, 65, 75, 85, 95], # [6, 16, 26, 36, 46, 56, 66, 76, 86, 96], # [7, 17, 27, 37, 47, 57, 67, 77, 87, 97], # [8, 18, 28, 38, 48, 58, 68, 78, 88, 98], # [9, 19, 29, 39, 49, 59, 69, 79, 89, 99]] 

Aquí hay una versión que es similar a la de Daniel: se divide lo más uniformemente posible, pero coloca todas las particiones más grandes al comienzo:

 def partition(lst, n): q, r = divmod(len(lst), n) indices = [q*i + min(i, r) for i in xrange(n+1)] return [lst[indices[i]:indices[i+1]] for i in xrange(n)] 

También evita el uso de aritmética flotante, ya que eso siempre me hace sentir incómodo. 🙂

Edición: un ejemplo, solo para mostrar el contraste con la solución de Daniel Stutzbach

 >>> print [len(x) for x in partition(range(105), 10)] [11, 11, 11, 11, 11, 10, 10, 10, 10, 10] 

A continuación se muestra una forma.

 def partition(lst, n): increment = len(lst) / float(n) last = 0 i = 1 results = [] while last < len(lst): idx = int(round(increment * i)) results.append(lst[last:idx]) last = idx i += 1 return results 

Si len (lst) no se puede dividir en partes iguales por n, esta versión distribuirá los elementos adicionales en intervalos aproximadamente iguales. Por ejemplo:

 >>> print [len(x) for x in partition(range(105), 10)] [11, 10, 11, 10, 11, 10, 11, 10, 11, 10] 

El código podría ser más simple si no te importa que todos los 11s estén al principio o al final.

Esta respuesta proporciona una función split(list_, n, max_ratio) , para las personas que desean dividir su lista en n piezas con la relación máxima de max_ratio en la longitud de la pieza. Permite más variación que el interrogador “a lo sumo 1 diferencia en la longitud de la pieza”.

Funciona muestreando n longitudes de pieza dentro del rango de relación deseado [1, max_ratio) , colocándolas una detrás de la otra para formar un ‘stick roto’ con las distancias correctas entre los ‘puntos de rotura’ pero la longitud total incorrecta. Al escalar el stick roto a la longitud deseada, obtenemos las posiciones aproximadas de los puntos de quiebre que deseamos. Para obtener puntos de ruptura enteros se requiere un redondeo posterior.

Desafortunadamente, los redondeos pueden conspirar para hacer piezas demasiado cortas, y le permiten superar el max_ratio. Vea la parte inferior de esta respuesta para un ejemplo.

 import random def splitting_points(length, n, max_ratio): """n+1 slice points [0, ..., length] for n random-sized slices. max_ratio is the largest allowable ratio between the largest and the smallest part. """ ratios = [random.uniform(1, max_ratio) for _ in range(n)] normalized_ratios = [r / sum(ratios) for r in ratios] cumulative_ratios = [ sum(normalized_ratios[0:i]) for i in range(n+1) ] scaled_distances = [ int(round(r * length)) for r in cumulative_ratios ] return scaled_distances def split(list_, n, max_ratio): """Slice a list into n randomly-sized parts. max_ratio is the largest allowable ratio between the largest and the smallest part. """ points = splitting_points(len(list_), n, ratio) return [ list_[ points[i] : points[i+1] ] for i in range(n) ] 

Puedes probarlo así:

 for _ in range(10): parts = split('abcdefghijklmnopqrstuvwxyz', 4, 2) print([(len(part), part) for part in parts]) 

Ejemplo de un mal resultado:

 parts = split('abcdefghijklmnopqrstuvwxyz', 10, 2) # lengths range from 1 to 4, not 2 to 4 [(3, 'abc'), (3, 'def'), (1, 'g'), (4, 'hijk'), (3, 'lmn'), (2, 'op'), (2, 'qr'), (3, 'stu'), (2, 'vw'), (3, 'xyz')]