Is this how you paginate, or is there a better algorithm?

orokusaki picture orokusaki · Sep 19, 2010 · Viewed 19.1k times · Source

I want to be able to take a sequence like:

my_sequence = ['foo', 'bar', 'baz', 'spam', 'eggs', 'cheese', 'yogurt']

Use a function like:

my_paginated_sequence = get_rows(my_sequence, 3)

To get:

[['foo', 'bar', 'baz'], ['spam', 'eggs', 'cheese'], ['yogurt']]

This is what I came up with by just thinking through it:

def get_rows(sequence, num):
    count = 1
    rows = list()
    cols = list()
    for item in sequence:
        if count == num:
            cols.append(item)
            rows.append(cols)
            cols = list()
            count = 1
        else:
            cols.append(item)
            count += 1
    if count > 0:
        rows.append(cols)
    return rows

Answer

Alex Martelli picture Alex Martelli · Sep 19, 2010

If you know you have a sliceable sequence (list or tuple),

def getrows_byslice(seq, rowlen):
    for start in xrange(0, len(seq), rowlen):
        yield seq[start:start+rowlen]

This of course is a generator, so if you absolutely need a list as the result, you'll use list(getrows_byslice(seq, 3)) or the like, of course.

If what you start with is a generic iterable, the itertools recipes offer help with the grouper recipe...:

import itertools

def grouper(n, iterable, fillvalue=None):
    "grouper(3, 'ABCDEFG', 'x') --> ABC DEF Gxx"
    args = [iter(iterable)] * n
    return itertools.izip_longest(fillvalue=fillvalue, *args)

(again, you'll need to call list on this if a list is what you want, of course).

Since you actually want the last tuple to be truncated rather than filled up, you'll need to "trim" the trailing fill-values from the very last tuple.