加快在字符串列表上运行的功能

时间:2012-12-30 09:56:21

标签: python python-3.x

我想从我的代码加速这个功能,这个代码经常被调用。该函数接收字符串的输入列表(通常长度为4)并产生字符串列表,其中对应的条目被替换为大写字母,其顺序对应于输入字符串的字母数字顺序。然后将此列表合并为一个字符串。 示例:输入列表['wwTv', 'NzkT', 'wwTv', 'JhXc'],输出字符串'C,B,C,A'。在实际示例中,每个列表中都有许多重复项。

您能否提出更有效的解决方案?或者我直截了当的算法足够好并且不能得到显着改善?

以下是我的代码示例(Python 3.2)。这里输入数据的样本是随机创建的,并传递给函数f

import timeit
import string, random

dumb_label_set = ['A', 'B', 'C', 'D', 'E']

def a(labels):
    uniq_labels = sorted(set(labels))
    dumb_labels = [dumb_label_set[uniq_labels.index(a)] for a in labels]
    s_name = ','.join(dumb_labels)
    return(s_name)

def b(labels):
    uniq_labels = {l: i for i, l in enumerate(sorted(set(labels)))}
    dumb_labels = [dumb_label_set[uniq_labels[a]] for a in labels]
    s_name = ','.join(dumb_labels)
    return(s_name)

labels = []
for i1 in range(100000):
    labels.append([''.join(random.choice(string.ascii_letters) for ii in range(random.randint(1,4))) for i2 in range(4)])

start = timeit.default_timer()
res_a = [a(l) for l in labels]
print(timeit.default_timer() - start)

start = timeit.default_timer()
res_b = [b(l) for l in labels]
print(timeit.default_timer() - start)

print(res_a == res_b)

结果:

0.41835449560994675
0.4420497451417873
True

我的功能a稍快一些,然后由Martijn Pieters提出b

2 个答案:

答案 0 :(得分:3)

我使用字典将标签映射到索引:

uniq_labels = {l: i for i, l in enumerate(sorted(set(labels)))}
dumb_labels = [dumb_label_set[uniq_labels[a]] for a in labels]

使用较小的labels集合以便在更加可管理的时间内进行多次传递,这样做:

>>> import timeit
>>> import string, random
>>> dumb_label_set = ['A', 'B', 'C', 'D', 'E']
>>> def f(labels):
...     uniq_labels = sorted(set(labels))
...     dumb_labels = [dumb_label_set[uniq_labels.index(a)] for a in labels]
...     s_name = ','.join(dumb_labels)
...     return(s_name)
... 
>>> def f_dict(labels):
...     uniq_labels = {l: i for i, l in enumerate(sorted(set(labels)))}
...     dumb_labels = [dumb_label_set[uniq_labels[a]] for a in labels]
...     s_name = ','.join(dumb_labels)
...     return s_name
... 
>>> labels = []
>>> for i1 in range(100):
...     labels.append([''.join(random.choice(string.ascii_letters) for ii in range(random.randint(1,4))) for i2 in range(4)])
... 
>>> timeit.timeit('[f(l) for l in labels]', 'from __main__ import f, labels', number=10000)
6.586822032928467
>>> timeit.timeit('[f(l) for l in labels]', 'from __main__ import f_dict as f, labels', number=10000)
7.633307933807373

但正如您所看到的,对于您的小型输入集,您的方法更快。看起来设置映射所需的时间比最多4 .index()次查找要多。

如果您的标签序列包含(更多)元素,我的方法将获胜:

>>> dumb_label_set = string.ascii_uppercase
>>> labels = []
>>> for i1 in range(100):
...     labels.append([''.join(random.choice(string.ascii_letters) for ii in range(random.randint(1,4))) for i2 in range(26)])
... 
>>> timeit.timeit('[f(l) for l in labels]', 'from __main__ import f, labels', number=1000)
3.069930076599121
>>> timeit.timeit('[f(l) for l in labels]', 'from __main__ import f_dict as f, labels', number=1000)
2.404794931411743

这里最重要的一课是使用timeit module来比较不同的方法。 timeit模块为您的平台使用最佳计时器,并比较许多运行的被测代码以消除外部调度影响(磁盘I / O,其他进程等)。

即使只计时一次,使用timeit.default_timer优于使用time.time; 可能仍然是同一个计时器,但它将是您平台上最准确的时钟。

答案 1 :(得分:1)

如果你真的希望这个能够快速发挥作用,那么也要看看cython。当然,在这里看看其他提出的算法,但是一旦你选择了最快的算法,cython仍然可以给它一个很好的推动力。

目前正在使用原始方法ab,我只是将它们移动而不更改为另一个模块,并使用cython和gcc(-O3)进行编译。没有类型信息,我得到以下时间:

a:          0.4449379859997862
b:          0.48829928699888114
a (cython): 0.29741462399942975
b (cython): 0.2461447869991389

我确信标记变量的类型可能会给它带来另一种提升。