5

Let's say I have a list:

list=['plu;ean;price;quantity','plu1;ean1;price1;quantity1']

I want to iterate over the list + split the list by ";" and put an if clause, like this:

for item in list:
    split_item=item.split(";")
    if split_item[0] == "string_value" or split_item[1] == "string_value":
        do something.....

I was wondering, if this is the fastest way possible? Let's say my initial list is a lot bigger (has a lot more list items). I tried with list comprehensions:

item=[item.split(";") for item in list if item.split(";")[0] == "string_value" or item.split(";")[1] == "string_value"]

But this is actually giving me slower results. The first case is giving me an average of 90ms, while the second one is giving me an average of 130ms. Am I doing the list comprehension wrong? Is there a faster solution?

4

4 回答 4

3

我想知道,这是否是最快的方法?

不,当然不是。与 Python 相比,您可以在手工编码的汇编中更快地实现它。所以呢?

如果“做某事...”不是微不足道的,并且有很多匹配项,那么做某事 100000 次的成本将比循环 500000 次的成本贵很多,所以找到最快的循环方式并没有一点都不重要。

事实上,split每个循环只调用两到三个而不是记住和重用结果会淹没迭代的成本,并且当你只关心两个结果时不传递 maxsplit 参数也可能。


所以,你试图优化错误的东西。但是,如果在你修复了所有其他东西之后,结果证明迭代成本在这里真的很重要呢?

好吧,您不能直接使用推导来加快速度,因为推导适用于返回值的表达式,而不是用于执行操作的语句。

但是,如果您查看您的代码,您会意识到您实际上正在做三件事:拆分每个字符串,然后过滤掉不匹配的字符串,然后执行“做某事”。因此,您可以对前两部分使用理解,然后您只for对通过过滤器的更小的值列表使用慢速循环。

看起来你尝试过这个,但你犯了两个错误。

首先,你最好使用生成器表达式而不是列表推导式——这里不需要列表,只需要迭代的东西,所以不要花钱构建一个。

其次,你不想split串三次。您可能会找到一些复杂的方法来split一次完成一次理解,但是为什么要麻烦呢?只需将每个步骤写成自己的步骤即可。

所以:

split_items = (item.split(';') for item in items)
filtered_items = (item for item in split_items 
                  if item[0] == "string_value" or item[1] == "string_value")
for item in filtered_items:
    do something...

这实际上会更快吗?如果您可以获得一些真实的测试数据和“做某事......”代码,这表明迭代是一个瓶颈,您可以在这些真实数据和代码上进行测试。在那之前,没有什么可以测试的。

于 2013-09-25T18:57:31.250 回答
2

仅当检索到的前两项str.split(';', 2)满足条件时才拆分整个字符串:

>>> strs = 'plu;ean;price;quantity'
>>> strs.split(';', 2)
['plu', 'ean', 'price;quantity']

'price;quantity'此处仅当前两项满足条件时才拆分第三项( ):

>>> lis = ['plu;ean;price;quantity'*1000, 'plu1;ean1;price1;quantity1'*1000]*1000

正常的 for 循环,列表中每个项目的整个字符串的单个拆分。

>>> %%timeit
for item in lis:
    split_item=item.split(";")
    if split_item[0] == "plu" or split_item[1] == "ean":pass
... 
1 loops, best of 3: 952 ms per loop

列表理解等价于上面的 for 循环:

>>> %timeit [x for x in (item.split(';') for item in lis) if x[0]== "plu" or x[1]=="ean"]
1 loops, best of 3: 961 ms per loop

按需拆分:

>>> %timeit [[x] + [y] + z.split(';') for x, y, z in (item.split(';', 2) for item in lis) if x== "plu" or y=="ean"]
1 loops, best of 3: 508 ms per loop

当然,如果列表和字符串很小,那么这种优化就无关紧要了。

于 2013-09-25T18:53:33.603 回答
1

编辑:事实证明,正则表达式缓存对竞争有点不公平。我的错。正则表达式只快了一小部分。

如果您正在寻找速度,hcwhsa 的答案应该足够好。如果您需要更多,请查看re.

import re
from itertools import chain

lis = ['plu;ean;price;quantity'*1000, 'plu1;ean1;price1;quantity1'*100]*1000

matcher = re.compile('^(?:plu(?:;|$)|[^;]*;ean(?:;|$))').match
[l.split(';') for l in lis if matcher(l)]

时间安排,主要是积极的结果(也split就是缓慢的主要原因):

SETUP="
import re
from itertools import chain
matcher = re.compile('^(?:plu(?:;|$)|[^;]*;ean(?:;|$))').match

lis = ['plu1;ean1;price1;quantity1'+chr(i) for i in range(10000)] + ['plu;ean;price;quantity' for i in range(10000)]
"

python -m timeit -s "$SETUP" "[[x] + [y] + z.split(';') for x, y, z in (item.split(';', 2) for item in lis) if x== 'plu' or y=='ean']"
python -m timeit -s "$SETUP" "[l.split(';') for l in lis if matcher(l)]"

我们看到我的快一点。

10 loops, best of 3: 55 msec per loop
10 loops, best of 3: 49.5 msec per loop

对于大多数负面结果(大多数东西都被过滤了):

SETUP="
import re
from itertools import chain
matcher = re.compile('^(?:plu(?:;|$)|[^;]*;ean(?:;|$))').match

lis = ['plu1;ean1;price1;quantity1'+chr(i) for i in range(1000)] + ['plu;ean;price;quantity' for i in range(10000)]
"

python -m timeit -s "$SETUP" "[[x] + [y] + z.split(';') for x, y, z in (item.split(';', 2) for item in lis) if x== 'plu' or y=='ean']"
python -m timeit -s "$SETUP" "[l.split(';') for l in lis if matcher(l)]"

领先一点

10 loops, best of 3: 40.9 msec per loop
10 loops, best of 3: 35.7 msec per loop

如果结果始终是唯一的,请使用

next([x] + [y] + z.split(';') for x, y, z in (item.split(';', 2) for item in lis) if x== 'plu' or y=='ean')

或更快的正则表达式版本

next(filter(matcher, lis)).split(';')

itertools.ifilter在 Python 2 上使用)。

时间:

SETUP="
import re
from itertools import chain
matcher = re.compile('^(?:plu(?:;|$)|[^;]*;ean(?:;|$))').match

lis = ['plu1;ean1;price1;quantity1'+chr(i) for i in range(10000)] + ['plu;ean;price;quantity'] + ['plu1;ean1;price1;quantity1'+chr(i) for i in range(10000)]
"

python -m timeit -s "$SETUP" "[[x] + [y] + z.split(';') for x, y, z in (item.split(';', 2) for item in lis) if x== 'plu' or y=='ean']"
python -m timeit -s "$SETUP" "next([x] + [y] + z.split(';') for x, y, z in (item.split(';', 2) for item in lis) if x== 'plu' or y=='ean')"

python -m timeit -s "$SETUP" "[l.split(';') for l in lis if matcher(l)]"
python -m timeit -s "$SETUP" "next(filter(matcher, lis)).split(';')"

结果:

10 loops, best of 3: 31.3 msec per loop
100 loops, best of 3: 15.2 msec per loop
10 loops, best of 3: 28.8 msec per loop
100 loops, best of 3: 14.1 msec per loop

因此,这极大地促进了这两种方法。

于 2013-09-25T20:24:51.930 回答
1

我在这里找到了一个不错的选择。

您可以结合使用地图和过滤器。尝试这个:

>>>import itertools
>>>splited_list = itertools.imap(lambda x: x.split(";"), your_list)
>>>result = filter(lambda x: filter(lambda x: x[0] == "plu" or x[1] == "string_value", lista)

第一项将创建元素的迭代器。第二个将过滤它。我在 IPython Notebook shell 中运行了一个小型基准测试,得到了以下结果:

第一次测试:

在此处输入图像描述

体积小,单线方案效果更好

第二次测试:

在此处输入图像描述

使用更大的列表,地图/过滤器解决方案略好

第三次测试:

在此处输入图像描述

有了大列表和更大的元素,地图/过滤器解决方案就更好了。

我猜随着列表大小的增加,性能差异会继续增加,直到在 66% 的时间内达到峰值(在 10000 个元素列表试验中)。

map/filter 解决方案和列表理解解决方案之间的区别在于对 .split() 的调用次数。Ones 为每个项目调用 3 次,另一个只调用一次,因为列表推导式只是一种将映射/过滤器结合在一起的 Python 方式。我过去经常使用列表推导,并认为我不知道 lambda 是什么。直到我发现地图和列表理解是一回事。

如果您不关心内存使用情况,可以使用常规 map 代替 imap。它将立即创建带有拆分的列表。它将使用更多内存来存储它,但它会稍微快一些。

实际上,如果您不关心内存使用情况,您可以使用 2 个列表推导来编写映射/过滤器解决方案,并获得相同的确切结果。查看:

在此处输入图像描述

于 2013-09-25T19:49:34.367 回答