Login

A couple of template filters for partitioning lists

Author:
jacobian
Posted:
February 25, 2007
Language:
Python
Version:
Pre .96
Tags:
template filter lists
Score:
19 (after 21 ratings)

People -- and by "people" I mean Jeff Croft -- often ask about how to split a list into multiple lists (usually for presenting as columns in a template).

These template tags provide two different ways of splitting lists -- on "vertically", and the other "horizontally".

 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
"""
Template tags for working with lists.

You'll use these in templates thusly::

    {% load listutil %}
    {% for sublist in mylist|parition:"3" %}
        {% for item in mylist %}
            do something with {{ item }}
        {% endfor %}
    {% endfor %}
"""

from django import template

register = template.Library()

@register.filter
def partition(thelist, n):
    """
    Break a list into ``n`` pieces. The last list may be larger than the rest if
    the list doesn't break cleanly. That is::

        >>> l = range(10)

        >>> partition(l, 2)
        [[0, 1, 2, 3, 4], [5, 6, 7, 8, 9]]

        >>> partition(l, 3)
        [[0, 1, 2], [3, 4, 5], [6, 7, 8, 9]]

        >>> partition(l, 4)
        [[0, 1], [2, 3], [4, 5], [6, 7, 8, 9]]

        >>> partition(l, 5)
        [[0, 1], [2, 3], [4, 5], [6, 7], [8, 9]]

    """
    try:
        n = int(n)
        thelist = list(thelist)
    except (ValueError, TypeError):
        return [thelist]
    p = len(thelist) / n
    return [thelist[p*i:p*(i+1)] for i in range(n - 1)] + [thelist[p*(i+1):]]

@register.filter
def partition_horizontal(thelist, n):
    """
    Break a list into ``n`` peices, but "horizontally." That is, 
    ``partition_horizontal(range(10), 3)`` gives::
    
        [[1, 2, 3],
         [4, 5, 6],
         [7, 8, 9],
         [10]]
        
    Clear as mud?
    """
    try:
        n = int(n)
        thelist = list(thelist)
    except (ValueError, TypeError):
        return [thelist]
    newlists = [list() for i in range(n)]
    for i, val in enumerate(thelist):
        newlists[i%n].append(val)
    return newlists

More like this

Comments

jcroft (on February 25, 2007):
<p>Thanks so much for this, Jacob!</p> <p>Just a small error in the docstring up top...</p> <pre>{% load listutil %} {% for sublist in mylist|parition:"3" %} {% for item in mylist %} do something with {{ item }} {% endfor %} {% endfor %} </pre> <p>I think that third line should be:</p> <pre> {% for item in sublistlist %} </pre> <p>Right?</p>

#

jcroft (on February 26, 2007):
<p>Err, I mean:</p> <pre>{% for item in sublist %} </pre>

#

spencermah (on February 27, 2007):
<p>partition_horizontal does not do what its docstring suggests:</p> <pre>>>> partition_horizontal(range(10), 3) [[0, 3, 6, 9], [1, 4, 7], [2, 5, 8]] </pre> <p>Assuming the implementation is correct, a more efficient implementation uses 'extended slices' i.e. (without error-checking):</p> <pre>>>> def partition_horizontal_new(thelist, n): ... return [thelist[i::n] for i in range(n)] ... >>> partition_horizontal_new(range(10),3) [[0, 3, 6, 9], [1, 4, 7], [2, 5, 8]] >>> </pre>

#

ludvig.ericson (on February 27, 2007):
<p>If what spencermah said is true, one could collapse this into one line:</p> <pre>register.filter("partition_horizontal", lambda l,n: [l[i::n] for i in range(n)]) </pre> <p>Firstly, we have to pass a name since it is an anonymous function, secondly we pass the function with lambda style defining. Simple as pie.</p>

#

brosner (on February 27, 2007):
<p>As noted above the docstring for partition_horizontal is not what the actually implementation is generating. I have corrected the implementation to work as noted.</p> <p>By the way range(10) will never give you a 10 ;)</p> <p>@register.filter def partition_horizontal(thelist, n): """ Break a list into n peices, but "horizontally." That is, partition_horizontal(range(10), 3) gives::</p> <pre> [[0, 1, 2], [3, 4, 5], [6, 7, 8], [9]] Clear as mud? """ from math import ceil try: n = int(n) thelist = list(thelist) except (ValueError, TypeError): return [thelist] newlists = [list() for i in range(int(ceil(len(thelist) / float(n))))] for i, val in enumerate(thelist): newlists[i/n].append(val) return newlists </pre> <p>Eh, the highlighting didn't seem to work too well, lol.</p>

#

klohrenz (on March 1, 2007):
<p>I modified the vertical filter so that if the list doesn't divide evenly, the last list would be the shortest (rather than longest). I thought this might be helpful to others.</p> <p>I took line 44:</p> <pre>p = len(thelist) / n </pre> <p>And replaced it with this:</p> <pre>if (len(thelist) % n == 0): p = len(thelist) / n else: p = (len(thelist) / n) + 1 </pre>

#

akaihola (on April 10, 2007):
<p>Typo on line 7: "parition" > "partition"</p>

#

theetderks (on May 21, 2007):
<p>How about this:</p> <pre>@register.filter def partition_horizontal(thelist, n): """ Break a list into ``n`` peices, but "horizontally." That is, ``partition_horizontal(range(10), 3)`` gives:: [[1, 2, 3], [4, 5, 6], [7, 8, 9], [10]] Clear as mud? """ try: n = int(n) thelist = list(thelist) except (ValueError, TypeError): return [thelist] newlists=[] for x in range((len(thelist)-1)/n+1): newlists.append(thelist[x*n:x*n+n]) return newlists </pre>

#

Please login first before commenting.