hajimuz
hajimuz

Reputation: 412

Elegant way to slice a list with a condition

Given a list [2,8,13,15,24,30], all the elements of which are supposed to be in range(31). Now I want to slice it into 3 lists, first list with numbers from 0 to 10, the second one with numbers from 11 to 20, and the others into the rest.

Here is my ugly code :

numbers = [2,8,13,15,24,30]
mylist = [[],[],[]] # I hate this the most...
for i in numbers:
    if i <= 10 :
        mylist[0].append(i)
    elif i > 10 and i <= 20:
        mylist[1].append(i)
    else:
        mylist[2].append(i)

print mylist

I don't think this is a good way to do it. Any suggestions please?

Upvotes: 5

Views: 1354

Answers (6)

roippi
roippi

Reputation: 25974

Since your input is sorted you can do this in one pass with itertools.groupby:

from itertools import groupby

[list(g) for _,g in groupby(numbers, lambda x: x//10)]
Out[3]: [[2, 8], [13, 15], [24, 26]]

No need to initialize a bunch of lists this way, groupby yields them on the fly.

This may be off-by-one in terms of how you wanted to treat the modulo-10 boundaries; if it's unclear you can always define your own grouper function:

def grouper(x):
    '''bins matching the semantics:
    [0,10] (10,20] (20, 30]'''
    return (x-1)//10 if x > 0 else 0

and use it thusly:

numbers = [2,8,13,15,24,30]

[list(g) for _,g in groupby(numbers, grouper)]
Out[5]: [[2, 8], [13, 15], [24, 30]]

Upvotes: 3

Chris Johnson
Chris Johnson

Reputation: 21986

One nicer way to do this uses default dicts:

from collections import defaultdict
output = defaultdict(list)
for n in numbers:
    output[n//10].append(n)

This creates a dict with a default element (if you access a key that hasn't been created) being an empty list. You don't need to create the empty list-of-lists you didn't like in your original code sample.

You can then access the output by decade, i.e. while output is a dict, output[0] is a list.

If you need to preserve your original output logic, converting this dict to a list-of-lists is simple.

Upvotes: 1

Sylvain Leroux
Sylvain Leroux

Reputation: 52030

What about using reduce ?

numbers = [2,8,13,15,24,26]

def part(acc, x):
    #  first list with numbers from 0 to 10, 
    #  the second one with numbers from 11 to 20,
    #  and the others into the rest.
    #
    #  This is *not* the same as:
    #    acc[x/10].append(x)
    #
    if x < 10:
        acc[0].append(x)
    elif x > 20:
        acc[2].append(x)
    else:
        acc[1].append(x)
    return acc

print reduce(part, numbers, [[],[],[]])

And to remove the hated [[],[],[]], if you can live with a dictionary instead of a list:

from collections import defaultdict
numbers = [2,8,13,15,24,26]

def part(acc, x):
    if x < 10:
        acc[0].append(x)
    elif x > 20:
        acc[2].append(x)
    else:
        acc[1].append(x)
    return acc

print reduce(part, numbers, defaultdict(list))

Producing:

defaultdict(<type 'list'>, {0: [2, 8], 1: [13, 15], 2: [24, 26]})

Upvotes: 1

Chris Johnson
Chris Johnson

Reputation: 21986

Without rethinking your basic approach, you can do:

for n in numbers:
    mylist[n//10].append(n)

This takes advantage of integer division, e.g. 19//10 = 1.

There are much more elegant ways to do this using other Python constructs; I will make a second answer for those. But for now this is a quick, easy and not too nauseating way to do what you want.

Upvotes: 1

vch
vch

Reputation: 211

Not sure what counts for elegant, but list comprehensions shoudl work:

numbers = [2,8,13,15,24,26]
mylist = []
mylist.append([x for x in numbers if x <=10])
mylist.append([x for x in numbers if x > 10 and x <= 20])
mylist.append([x for x in numbers if x > 2 ])
print mylist

Upvotes: 0

Cory Kramer
Cory Kramer

Reputation: 117941

def filterList(original, lower, upper):
    return filter(lambda i : i > lower and i <= upper, original)

This can be called like so

firstSlice = filterList(numbers, 0, 10)
>>> firstSlice
[2, 8]

Making your range list

ranges = [0, 10, 20, 30]

Then making the 2D list in list comprehension

>>> [filterList(numbers, ranges[i], ranges[i+1]) for i in range(len(ranges)-1)]
[[2, 8], [13, 15], [24, 26]]

Upvotes: 1

Related Questions