Login

A couple of template filters for partitioning lists

Author:
jacobian
Posted:
February 25, 2007
Language:
Python
Version:
Pre .96
Score:
20 (after 22 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

  1. Template tag - list punctuation for a list of items by shapiromatron 10 months, 1 week ago
  2. JSONRequestMiddleware adds a .json() method to your HttpRequests by cdcarter 10 months, 2 weeks ago
  3. Serializer factory with Django Rest Framework by julio 1 year, 5 months ago
  4. Image compression before saving the new model / work with JPG, PNG by Schleidens 1 year, 6 months ago
  5. Help text hyperlinks by sa2812 1 year, 6 months ago

Comments

jcroft (on February 25, 2007):

Thanks so much for this, Jacob!

Just a small error in the docstring up top...

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

I think that third line should be:

    {% for item in sublistlist %}

Right?

#

jcroft (on February 26, 2007):

Err, I mean:

{% for item in sublist %}

#

spencermah (on February 27, 2007):

partition_horizontal does not do what its docstring suggests:

>>> partition_horizontal(range(10), 3)
[[0, 3, 6, 9], [1, 4, 7], [2, 5, 8]]

Assuming the implementation is correct, a more efficient implementation uses 'extended slices' i.e. (without error-checking):

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

#

ludvig.ericson (on February 27, 2007):

If what spencermah said is true, one could collapse this into one line:

register.filter("partition_horizontal", lambda l,n: [l[i::n] for i in range(n)])

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.

#

brosner (on February 27, 2007):

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.

By the way range(10) will never give you a 10 ;)

@register.filter def partition_horizontal(thelist, n): """ Break a list into n peices, but "horizontally." That is, partition_horizontal(range(10), 3) gives::

        [[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

Eh, the highlighting didn't seem to work too well, lol.

#

klohrenz (on March 1, 2007):

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.

I took line 44:

p = len(thelist) / n

And replaced it with this:

if (len(thelist) % n == 0):
    p = len(thelist) / n
else:
    p = (len(thelist) / n) + 1

#

akaihola (on April 10, 2007):

Typo on line 7: "parition" > "partition"

#

theetderks (on May 21, 2007):

How about this:

@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

#

Please login first before commenting.