优雅地根据条件切片列表的方法
给定一个列表 [2,8,13,15,24,30],这个列表里的所有数字都应该在 0 到 30 的范围内。现在我想把这个列表分成三个小列表,第一个小列表包含 0 到 10 之间的数字,第二个小列表包含 11 到 20 之间的数字,剩下的数字放在第三个小列表里。
这是我写的代码,感觉很糟糕:
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
我觉得这样做不是个好办法。有没有什么建议呢?
6 个回答
1
那用 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, [[],[],[]])
如果你能接受用字典代替列表的话,可以去掉那个让人讨厌的 [[],[],[]]
:
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))
结果是:
defaultdict(<type 'list'>, {0: [2, 8], 1: [13, 15], 2: [24, 26]})
1
有一种更好的方法可以做到这一点,就是使用默认字典:
from collections import defaultdict
output = defaultdict(list)
for n in numbers:
output[n//10].append(n)
这样做会创建一个字典,如果你访问一个还没有创建的键,它的默认值就是一个空列表。这样你就不需要在原来的代码中创建那个你不喜欢的空列表了。
你可以按十年访问输出结果,也就是说,虽然 output
是一个字典,但 output[0]
是一个列表。
如果你需要保留原来的输出逻辑,把这个字典转换成列表的列表也很简单。
1
>>> [filterList(numbers, ranges[i], ranges[i+1]) for i in range(len(ranges)-1)]
[[2, 8], [13, 15], [24, 26]]
你可以这样调用它
def filterList(original, lower, upper):
return filter(lambda i : i > lower and i <= upper, original)
创建你的范围列表
firstSlice = filterList(numbers, 0, 10)
>>> firstSlice
[2, 8]
然后用列表推导式来创建二维列表
ranges = [0, 10, 20, 30]
1
如果不改变你基本的方法,你可以这样做:
for n in numbers:
mylist[n//10].append(n)
这里利用了整数除法,比如 19//10
的结果是 1。
其实还有更优雅的方法可以用其他 Python 的特性来实现;我会再写一个答案来介绍那些方法。不过现在,这种方式简单、快速,而且不会让人觉得太难受。
3
因为你的输入数据是排好序的,所以你可以用 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]]
这样就不需要提前准备一堆列表,groupby
会自动生成这些列表。
不过在处理模10的边界时,可能会有一点偏差;如果不太清楚,你可以自己定义一个 grouper
函数:
def grouper(x):
'''bins matching the semantics:
[0,10] (10,20] (20, 30]'''
return (x-1)//10 if x > 0 else 0
然后这样使用它:
numbers = [2,8,13,15,24,30]
[list(g) for _,g in groupby(numbers, grouper)]
Out[5]: [[2, 8], [13, 15], [24, 30]]