7

I need an algorithm which given a list L and a number N, returns a list of N smaller lists where the sublists are "balanced". Examples:

algo(range(1, 8), 3)  -> [[1,2,3], [4,5], [6,7]]
algo(range(1, 6), 4)  -> [[1,2], [3], [4], [5]]
algo(range(1, 12), 5) -> [[1,2,3], [4,5], [6,7], [8,9], [10, 11]]

As you can see, the algorithm should "prefer" the first list in the output.

I've been trying for hours, but I can't figure out a nice and terse algorithm for it. This will be implemented in Python, by the way, but it's really the algorithm that I'm after here. This is not homework, this is for a website which will display contents in a list in three columns (Django).


I got the best answer from #python on freenode and it is as follows:

def split_up(l, n):
    q, r = divmod(len(l), n)
    def division_point(i):
        return i * q + min(i, r)
    return [l[division_point(i):division_point(i+1)] for i in range(n)]

Don't ask me why it works though. :) I'll give the correct answer to the one with most votes though.

Deniz Dogan
  • 25,711
  • 35
  • 110
  • 162

5 Answers5

5

This is the code I came up with, without the sorting. Just slap on a lst.sort() if the input is not sorted.

I think this came out nicely, using iterators and using islice to cut off the next piece.

import itertools

def partlst(lst, n):
    """Partition @lst in @n balanced parts, in given order"""
    parts, rest = divmod(len(lst), n)
    lstiter = iter(lst)
    for j in xrange(n):
        plen = len(lst)/n + (1 if rest > 0 else 0)
        rest -= 1
        yield list(itertools.islice(lstiter, plen))

parts =  list(partlst(range(1, 15), 5))
print len(parts)
print parts
Crescent Fresh
  • 115,249
  • 25
  • 154
  • 140
u0b34a0f6ae
  • 48,117
  • 14
  • 92
  • 101
1

Assuming you want output to contain lists of equal length when possible, otherwise give preference to lists in the beginning. Difference between lengths of sub-lists no more than one.

>>> l = [0, 1, 2, 3, 4, 5, 6]
>>> def algo(li, n):
        a, b = divmod(len(li), n)
        c = [a + 1] * b + [a] * (n-b)
        s = 0
        for i, j in enumerate(c):
            c[i] = li[s:s+j]
            s += j
        return c

>>> algo(l, 3)
[[0, 1, 2], [3, 4], [5, 6]]
>>> algo(l, 4)
[[0, 1], [2, 3], [4, 5], [6]]
SilentGhost
  • 307,395
  • 66
  • 306
  • 293
0

If I understand your problem... you would only have to add one item for each list under mod(n), where you have algo (range(a,b), n)

So you should:

  1. Have b-a > n
  2. Calculate b-a = n*x + y (I dont really know if the operator % exists on python, so you should get y)
  3. The first y lists will have (b-a/n + 1) elements and the other lists will have (b-a/n)
Diego Dias
  • 21,634
  • 6
  • 33
  • 36
0

Here's a tribute to functional lovers:

def algo(l, n):
    if n == 1: return [l]
    q, r = divmod(len(l),n)
    if r: q += 1
    return [l[:q]] + algo(l[q:], n - 1)

This one is a little bit smaller:

def algo(l, n):
    k = l[:]
    q, r = divmod(len(l),n)
    return [[k.pop(0) for _ in [0] * m]
            for m in [q + 1] * r + [q] * (n - r)]
vaab
  • 9,685
  • 7
  • 55
  • 60
0

Bit late to the party, but...

def algo(l, n):
  return [l[-(-len(l)*i//n):-(-len(l)*(i+1)//n)] for i in range(n)]

Use / instead of // in older versions of Python.

Alice Purcell
  • 12,622
  • 6
  • 51
  • 57
  • I haven't tested it, but if it works, then I'll be damned! :) – Deniz Dogan Nov 04 '09 at 19:52
  • Those double negatives seem to be useless. I redefined it as: `[lst[len(lst) * i // n : len(lst) * (i + 1) // n] for i in range(n)]`. – Mahyar Mirrashed Jun 15 '22 at 07:46
  • 1
    @MahyarMirrashed Removing the double-negatives means the algorithm puts more items in later lists rather than earlier ones, which the OP did not want. Try it with e.g. algo([1,2,3,4], 3) to see the difference – Alice Purcell Jun 16 '22 at 11:24