优雅地根据条件切片列表的方法

5 投票
6 回答
1082 浏览
提问于 2025-04-18 18:05

给定一个列表 [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]]

撰写回答