Python:从给定属性的对象列表中提取子列表的最快方法

时间:2018-03-20 06:49:10

标签: python performance list

说我有这个简单的课程:

class Foo(object):
    def __init__(self, number, name):
        self.number = number
        self.name = name

和Foo实例列表:

l = [Foo(10, 'a'), Foo(9, 'a'), Foo(8, 'a'), Foo(7,'a'), Foo (5, 'b'), Foo (4, 'b') ,Foo (3, 'b')]

假设'name'属性只能是'a'或'b'。

以最快的方式提取“名称”为“a”(或“b”)的所有对象的子列表是什么?请注意,此操作可能会被调用数百万次,这就是为什么我要尽可能地优化它。

注意列表的构建方式使得所有元素在列表的第一个或第二个半部分中“组合在一起”。该列表是对称的,并按递减属性'number'排序。 编辑:不一定有相同数量的'a'和'b'。

我是怎么做到的:

一开始我只是做了一个for循环:

sublist = []
for o in l:
  if o.name == 'a'
  sublist.append(o)

然后我尝试了列表理解:

sublist = [o for o in l if o.name=='a']

但是,如果不是有点慢,这似乎差不多。

无论哪种方式,这些都没有利用所有属性已经在原始(排序)列表中“组合在一起”的假设。即使不再需要它也会保持循环。速度非常重要,所以我需要它尽可能高效。

3 个答案:

答案 0 :(得分:2)

在匹配

后遇到不匹配时,只需退出循环
sublist = []
for o in l:
    if o.name == 'a'
        sublist.append(o)
    elif sublist:
        break

如果您想使用生成器,可以使用itertools函数

from itertools import takewhile, dropwhile

sublist = list(takewhile(lambda o: o.name == 'a', dropwhile(lambda o: o.name != 'a', l))

这两个都利用列表已排序的事实,并在项目停止匹配后停止处理列表。

答案 1 :(得分:2)

由于name属性只能是'a'或'b',它们是有序的,并且你有相同数量的'a'和'b',最简单的方法是找到中间点和切片列表:

mid = int(len(aList)/2)
sublist = l[:mid]

以上将为您提供所有'a'而l[mid:]给出所有'b'。

修改:由于问题已经改变,'a'和'b'的元素数量相同,上述答案不再适用。

根据列表的长度,我的猜测是二元搜索(对于更长的列表)或者像Brendan建议的那样(对于较短的列表)突破循环将是最快的方法。

答案 2 :(得分:2)

使用二进制搜索在O(logN)中找到中间点:

In [19]: class Foo(object):
    ...:     def __init__(self, number, name):
    ...:         self.number = number
    ...:         self.name = name
    ...:         
    ...:     def __repr__(self):
    ...:         return 'Foo(number={self.number}, name={self.name})'.format(self=self)
    ...:     

In [20]: def binary_search(lst, predicate):
    ...:     """
    ...:     Finds the first element for which predicate(x) == True
    ...:     """
    ...:     lo, hi = 0, len(lst)
    ...:     while lo < hi:
    ...:         mid = (lo + hi) // 2
    ...:         if predicate(lst[mid]):
    ...:             hi = mid
    ...:         else:
    ...:             lo = mid + 1
    ...:     return lo
    ...: 

In [21]: l = [Foo(10, 'a'), Foo(9, 'a'), Foo(8, 'a'), Foo(7,'a'), Foo (5, 'b'), Foo (4, 'b'
    ...: ) ,Foo (3, 'b')]

In [22]: binary_search(l, lambda x: x.name == 'b')
Out[22]: 4

In [23]: l[:binary_search(l, lambda x: x.name == 'b')]
Out[23]: 
[Foo(number=10, name=a),
 Foo(number=9, name=a),
 Foo(number=8, name=a),
 Foo(number=7, name=a)]

In [24]: l[binary_search(l, lambda x: x.name == 'b'):]
Out[24]: [Foo(number=5, name=b), Foo(number=4, name=b), Foo(number=3, name=b)]

然而,请注意:

  1. 对于10 4 元素,完成O(N)复杂度的朴素方法应该花费不到1秒的时间。
  2. 在制作副本时,您仍然需要遍历数组,这会导致O(N)
  3. 如果您遇到性能问题,最好使用分析器来查找程序中的瓶颈。迭代超过10个 4 元素通常不是瓶颈(除非你在10个 4 元素上迭代10 4 次 - 这导致10 < SUP> 8 )。但是,从db查询10 4 可能是一个瓶颈,因为它也使用网络,可能会查询其他项目等等。如有疑问 - 请使用profiler