【问题标题】:Complex List Comprehension slower than straight loop复杂列表理解比直接循环慢
【发布时间】:2017-08-27 06:19:01
【问题描述】:

我在leetcode 上看到了一个有趣的编程练习解决方案,它甚至与问题/解决方案本身无关,因此如果您愿意,可以在提供的链接上阅读它。然而,一个高票的解决方案是这一个班轮:

片段 1

def fizzBuzz2(n):
    return  ["Fizz" * (not i%3) + "Buzz" * (not i%5) or str(i) for i in range(1, n+1)]

片段 2

def fizzBuzz(n):
    out = []
    for i in range(1, n+1):
        if i % 3 == 0 and i % 5 == 0:
            out.append("FizzBuzz")
        elif i % 3 == 0:
            out.append("Fizz")
        elif i % 5 == 0:
            out.append("Buzz")
        else:
            out.append(str(i))
            
    return out

但是,我预计列表推导会击败公共循环,但是当我对其进行计时时,情况并非如此。即使做一个 dis,Snippet 2 也有更多的指令。

是什么让 Snippet 1 变慢了?

【问题讨论】:

  • sn-p 2 在我的测试中较慢。顺便说一句,里面有一个流浪的self
  • 字符串连接与文字字符串,但 Snippet 2 也根据需要执行两倍的 % 操作。
  • 我自己无法在 atm 上检查它,但列表理解是否可能检查所有语句,而 if elif 结构只检查直到一个条件成立?
  • 感谢迷途的自己,你是不是用了很大的n?像 1.000.000 ?
  • 我过去曾为 Comparing list comprehensions and explicit loops (3 array generators faster than 1 for loop) 做过一些分析。您可能会发现它很有用

标签: python


【解决方案1】:

你的sn-p

def fizzBuzz2(n):
    return  ["Fizz" * (not i%3) + "Buzz" * (not i%5) or str(i) for i in range(1, n+1)]

执行很多字符串连接(即使是空字符串)。我用一个额外的模运算来交换它,它可以保存连接并且它已经更快了。

def fizzBuzz3(n):
    return  ["FizzBuzz" if not i%15 else "Fizz" if not i%3 else "Buzz" if not i%5 else str(i) for i in range(1, n+1)]

顺便说一句,在我的机器上,这两种理解都比“经典”方法更快,所以我得到的结果与你所说的不同:

your comp: 4.927702903747559
my listcomp: 4.343341112136841
classical: 6.015967845916748

所以我优化的 listcomp 赢了(而且似乎在你的机器上也赢了),即使我对 listcomp 的流控制引起的额外模运算不满意)

我的测试协议是用n=1000执行10000次操作:

import time
start_time = time.time()
for i in range(10000):
    fizzBuzz2(1000)
print("your comp:",time.time()-start_time)
start_time = time.time()
for i in range(10000):
    fizzBuzz3(1000)
print("my listcomp:",time.time()-start_time)
start_time = time.time()
for i in range(10000):
    fizzBuzz(1000)
print("classical:",time.time()-start_time)

请注意,即使在“经典”方法中预先计算模数,它也会下降到 5.375272035598755 秒(这很好)但仍然比 listcomps 差,因为所有指令(您还通过调用一种保存模计算的方法)。我猜在这种情况下,python 不是获得最快速度的合适语言。

【讨论】:

  • 这很奇怪,我的意思是奇怪的是,当你运行经典时它会变慢,我的结果:经典:3.7922539711,列表比较:3.95321893692,你的列表比较; 2.85592794418
  • 所以我优化的 listcomp 是 3 个中最快的?这仍然是个好消息:)
  • 哈哈,是的,请注意,你的机器很慢,这是我运行你的测试协议:('your comp:', 3.3554258346557617) ('my listcomp:', 2.2999539375305176) ('经典:',3.1437509059906006)
  • 是的。在快速的机器上编码太容易了:) 让测试变得奇怪的原因可能是你的机器不是很平衡:我的意思是你可能有非常快的 RAM 或非常快的 CPU,而我们这些凡人对每个人都有“标准”比率关键资源。
【解决方案2】:

很多人已经回答了您的问题,但我想补充一点,还有更快的解决方案。对我来说,事后测试这些数字似乎是错误的,尽管您已经知道“Fizz”、“Buzz”和“FizzBu​​zz”会出现在哪些地方。试试这个:

def fizz_buzz(n):
    result = []
    for i in range(1, n + 1, 15):
        result += [str(i), str(i + 1), 'Fizz',
                   str(i + 3), 'Buzz', 'Fizz',
                   str(i + 6), str(i + 7), 'Fizz', 'Buzz',
                   str(i + 10), 'Fizz', str(i + 12), str(i + 13),
                   'FizzBuzz']
    return result[:n - len(result)]

start_time = time()
for i in range(10000):
    fizz_buzz(1000)
print("fizz_buzz:", time() - start_time)

与之前的答案相比:

your comp: 3.3942723274230957
my listcomp: 2.586350202560425
classical: 3.879168748855591
fizz_buzz: 1.6374053955078125

【讨论】:

  • 顺便说一句,将您的解决方案与 listcomp & itertools.chain.from_iterable:result = list(itertools.chain.from_iterable([str(i), str(i + 1), 'Fizz', str(i + 3), 'Buzz', 'Fizz', str(i + 6), str(i + 7), 'Fizz', 'Buzz', str(i + 10), 'Fizz', str(i + 12), str(i + 13), 'FizzBuzz'] for i in range(1, n + 1, 15))) return result[:n - len(result)]结合使用可以更快
  • 不是[:n - len(result)] 只是[:n]吗?
  • @Jean-FrançoisFabre 你确定吗?我已经用你的替换了结果行,完成的时间要长几个数量级。
  • 是的,我已经把它放在了工作台上,在我的机器上它甚至更快(哦,我希望你确实删除了循环!!)
【解决方案3】:

是的,因为在理解中,所有指令都在每个循环中进行评估。

Snippet 2 中,最多评估1 个if 分支。因此,减少了计算,并使其成为更快的替代方案。

【讨论】:

    【解决方案4】:

    虽然我们正在探索实现,但这里有一个有趣的切片,它比单个列表理解更快,但不如 @seenorth 的解决方案快:

    from math import ceil
    
    
    def fizz_buzz(n):
        result = list(range(n))
        result[::3] = ["Fizz"] * ceil(n / 3)
        result[::5] = ["Buzz"] * ceil(n / 5)
        result[::15] = ["FizzBuzz"] * ceil(n / 15)
    
        return list(map(str, result))
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-12-24
      • 1970-01-01
      • 2022-01-25
      • 1970-01-01
      • 1970-01-01
      • 2022-11-30
      • 1970-01-01
      相关资源
      最近更新 更多