【问题标题】:List slicing vs indexing in Python?Python中的列表切片与索引?
【发布时间】:2021-12-02 06:58:23
【问题描述】:

我试图在 Python 中处理一个文件。长话短说,这是我写的代码的两个版本:

for line in file:
    if line[0:2] == ".I":
        #do something
    elif line[0:2] == ".T":
        #do something else
    elif line[0:2] == ".A":
         ......

文件中有大约 21000 行。但是,当我将代码更改为以下内容时:

for line in file:
        if line[0] == ".":
            if line[1] == "I":
                #do something                   
            elif line[1] == "T":
                #do something
            elif line[1] == "A":
                ...

运行时间发生了巨大变化,我的意思是从 40 分钟到 30 秒。我知道列表切片是 O(N),但在这种情况下,我们只切片字符串中的前两个字符。那么是什么导致它发生了如此巨大的变化呢?

【问题讨论】:

  • 你的大部分文件不是以'.'开头的吗?
  • 这可能会有所帮助:stackoverflow.com/a/35181399/12229158
  • 如果文件不是以"." 开头,那么在第一种方法中,您进行 3 次比较(涉及 3 个切片),而在第二种方法中,您只进行一次不涉及切片的比较跨度>
  • 只是好奇:你用line.startswith(...)测量时间了吗?
  • 第一个问题很好——有多少行以“.”开头?这就是区别。你有多少这些条件,要采取什么行动。您可能会发现具有实现“做某事”的功能的字典是最快的。

标签: python python-3.x string list indexing


【解决方案1】:

我正在研究更多细节,但根据Python Wiki,索引具有恒定的时间复杂度 (O(1)),而切片的复杂度取决于切片,O(k)。

【讨论】:

  • 对,但是这里的切片都很小,大小为 2。
  • 真;我的猜测是它是时间复杂度以及通过 OP 的第一个条件的行数的组合。
  • 我认为时间复杂度与这里无关。从技术上讲,时间复杂度是恒定时间,因为切片总是相同的大小。在我的机器上,索引到一个字符串之间的差异大约是 33 纳秒,而对长度为 2 的字符串进行切片大约是 90。所以这大约慢了 3 倍,而且它没有缩放。并不能真正解释 80 倍的差异
【解决方案2】:

索引的速度是切片的两倍,但这是对非常小的数字的比较。运行一百万次时,差异约为 0.04 秒。这不是您在代码中看到的差异。

>>> timeit("s[0:2]=='aa'", setup="s = '12345'")
0.08988943499571178
>>> timeit("s[0]=='a'", setup="s = '12345'")
0.05322081400663592
>>> timeit("val=='aa'", setup="val='aa'")
0.03722755100170616

您可以通过将切片或索引值分配给变量一次并将其用于将来的比较来稍微加快这两种情况。您也可以在引用局部变量的速度稍快的函数中执行此操作。

现在是更大的问题。假设您有 10,000 行,其中 1000 行以“.”开头。这些线均匀分布在“.A 和 .Z”之间。您将平均检查 23 个不同的值。在第一种情况下,这是 10000 * 23 或 230,000 总检查。在第二种情况下,您只用一次检查就淘汰了大多数候选人,然后用平均 23 次检查淘汰其余候选人。那是 (9000) + (1000 * 23) 或总共 32,000 次检查。检查的条件减少了 86%。

让我们走得更远。假设你有你不感兴趣的“.whatever”值。每一个都必须经过所有 26 次检查,然后你才能意识到它是一个无用的。如果是这种情况,您可以将所有比较器分组并首先检查。

wanted = {".A", ".B", etc...)
for line in file:
    check = line[:2]
    if check in wanted:
        val = check[1]
        if ...
        

如果您可以将“do_something”代码编写为函数,您可以走得更远。

def do_thing_A():
    pass
    
def do_thing_B():
    pass
    
def do_nothing():
    pass
    
do_all_the_things = {".A":do_thing_A, ".B":do_thing_B}

for line in file:
    do_all_the_things.get(line[:2], do_nothing)()

【讨论】:

    猜你喜欢
    • 2012-12-16
    • 1970-01-01
    • 1970-01-01
    • 2016-10-27
    • 2012-06-14
    • 2012-02-24
    • 1970-01-01
    • 2018-07-29
    • 2019-01-29
    相关资源
    最近更新 更多