tags:

views:

219

answers:

4

I'm looking for a fast, clean, pythonic way to divide a list into exactly n nearly-equal partitions.

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)

There are several answers in here http://stackoverflow.com/questions/1335392/iteration-over-list-slices that run very close to what I want, except they are focused on the size of the list, and I care about the number of the lists (some of them also pad with None). These are trivially converted, obviously, but I'm looking for a best practice.

Similarly, people have pointed out great solutions here http://stackoverflow.com/questions/312443/how-do-you-split-a-list-into-evenly-sized-chunks-in-python for a very similar problem, but I'm more interested in the number of partitions than the specific size, as long as it's within 1. Again, this is trivially convertible, but I'm looking for a best practice.

A: 

What do you mean by "the number of the lists"?

gnp
the number of the partitions. e.g. len(partition(...))Also the second argument to partition.
Drew
+3  A: 
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]]
JG
That doesn't work for non-trivial examples. For `partition(range(105), 10)`, the last sublist will have only 6 elements.
Daniel Stutzbach
How would you split that list?
JG
@JG: 5 sublists of 10 items and 5 sublists of 11 items.
Daniel Stutzbach
@Daniel: Fair enough, although that isn't very clear in the original question. It is "fixed" now.
JG
@JG: I believe the “exactly n nearly equal” was meant to be “exactly n of nearly equal length”; the “as long as it's within 1” also is a strong hint, even if vague and unclear.
ΤΖΩΤΖΙΟΥ
+1  A: 

Below is one way.

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

If len(lst) cannot be evenly divided by n, this version will distribute the extra items at roughly equal intervals. For example:

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

The code could be simpler if you don't mind all of the 11s being at the beginning or the end.

Daniel Stutzbach
The use of floating-point could result in machine-dependent results: when increment*i is (mathematically) exactly halfway between two integers, the rounding could go either way depending on what numerical errors have been introduced.How about using something like `idx = len(lst)*i//n` instead? Or perhaps `idx = (len(lst)*i + n//2)//n` to get results similar to the current code.
Mark Dickinson
+4  A: 

Here's a version that's similar to Daniel's: it divides as evenly as possible, but puts all the larger partitions at the start:

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)]

It also avoids the use of float arithmetic, since that always makes me uncomfortable. :)

Edit: an example, just to show the contrast with Daniel Stutzbach's solution

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