是否可以将此循环转换为python中的列表理解

Posted

技术标签:

【中文标题】是否可以将此循环转换为python中的列表理解【英文标题】:is it possible to convert this loop into a list comprehension in python 【发布时间】:2014-02-14 19:37:08 【问题描述】:

我有一小部分code,我想知道它是否可以写成list comprehensionwhile loop 部分是我对浓缩感兴趣的部分。

>>> sum=33
>>> final_list=[]
>>> LastCoin=[0, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5, 1, 2, 
           1, 2, 5, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5, 1, 2, 1]
>>> while sum>0:
...    final_list.append(LastCoin[sum])
...    sum-=LastCoin[sum]
... 
>>> print final_list
[1, 2, 5, 5, 5, 5, 5, 5]
>>> 

【问题讨论】:

您使用的迭代模式非常不规则,您似乎将迭代器用作累加器和索引。你能给我们更多关于这个问题的背景信息吗? 可能 xy problem? 将任何while 循环转换为列表理解的第一步是将while 循环转换为for 循环。如果没有自然的方式来做到这一点,那么就不会有任何自然的方式将其转化为列表理解。 (总有一些方法可以做到这一点——最坏的情况,你可以将循环提取到一个生成器函数中,然后final_list = [sum for sum in my_generator()]...但这几乎没有有用。)跨度> 我想值得一提的是sum是一个内置函数的名称,所以你应该为你的变量选择其他名称。 另一种方法(您可能不想这样做):listcomp 中的表达式必须是常量值和循环迭代值。您需要包含最后附加的值。如果您查看functools.reduceitertools.accumulate,您会发现这始终是可能的。我想不出任何方法可以使用该技巧使任何内容在这里都可以远程读取,但也许你可以。 【参考方案1】:

您尝试使用列表推导式是否有充分的理由?

我个人看到很多人试图在他们不属于的地方插入列表推导,因为,你知道,'列表推导更快 - 他们在原生 C 中!而你无聊的循环是在解释的 Python 中。这并不总是正确的。

作为参考,如果我们将简洁易读的原始解决方案与两个建议的答案进行比较,您可能会发现您的假设被违反:

In [5]: %%timeit
   ...: sum=33
   ...: while sum > 0:
   ...:     final_list.append(LastCoin[sum])
   ...:     sum -= LastCoin[sum]
   ...:
100000 loops, best of 3: 1.96 µs per loop

In [6]: %%timeit
   ...: sum=33
   ...: susu = [sum]
   ...: susu.extend(x for x in xrange(sum,-1,-1)
   ...:             if x==(susu[-1]-LastCoin[susu[-1]])!=0)
   ...: fifi = [LastCoin[x] for x in susu]
   ...:
100000 loops, best of 3: 10.4 µs per loop
# 5x slower

In [10]: %timeit final_list = [LastCoin[reduce(lambda x, y: x - LastCoin[x], range(counter, i, -1))] for i in range(counter -1, 0, -1) if reduce(lambda x, y: x - LastCoin[x], range(counter, i, -1))]
10000 loops, best of 3: 128 µs per loop
# More than 60x slower!!

如果您尝试对列表中的每个元素执行某些操作 - 过滤(测试每个元素的真/假)、翻译等,其中每个元素的操作都是独立的(并且,理论上,通常可以并行化)。它不太擅长在循环期间进行处理和更改状态的循环,并且当您尝试时它们通常看起来很难看。在这种特殊情况下,您在浏览列表时只查看 8 个项目,因为您正在手动计算要查看的索引。在列表理解的情况下,您至少必须查看所有 33 个。

我不知道这是否是您的动机,但如果是,请将其保留为循环。毕竟 Python 循环并没有那么糟糕!

【讨论】:

我个人不喜欢用timeit,我从来不提醒怎么用(我不假装用它是坏事)。通过使用time.clock 进行的测试,我没有发现我的解决方案的执行时间与 Omid Raha 的执行时间有很大差异:执行 Omid 的解决方案的时间比我的第一个我的解决方案长大约 13 倍,而不是 60 倍。 - 但我找到了另一个解决方案,请看,它快 3 倍,更简单! -- 我赞成你比较执行时间的工作。 我使用 Ipython (ipython.org);在那里,它就像 %timeit 命令一样简单。我也不记得这些论点,但我不必:) 我针对您的解决方案进行了测试 - 它比原始的 while 循环更简单,但仍不简单。怎么会这样?这是相同的算法,但有额外的函数调用。时间仍然很快,但在我的测试中慢了大约 70% - while 循环为 1.66uS,而递归解决方案为 2.94uS(我的时间只是st=33; fifi3 = nekst(st, LastCoin, []),所以我已经重构了函数 def out循环)。 我刚刚浏览了 IPython 的网站,它给了我学习和使用它的愿望。看起来非常有趣。谢谢你的信息。 - 我想知道的事情:IPython 是用哪种语言实现的?它是基于 C 作为 Python 本身,还是基于 Python 或其他? 嗯,谢谢您的反映。我现在想我已经用尽了这个主题。祝你下午愉快。【参考方案2】:

是的,有可能:

counter = 33 # your sum variable
LastCoin = [0, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5, 1, 2,
            1, 2, 5, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5, 1, 2, 1]

final_list = [LastCoin[reduce(lambda x, y: x - LastCoin[x],
                              range(counter, i, -1))]

              for i in range(counter -1, 0, -1)

              if reduce(lambda x, y: x - LastCoin[x],
                        range(counter, i, -1))
              ]

print(final_list)
# [1, 2, 5, 5, 5, 5, 5, 5]

但是,这不是import this 的方式!

【讨论】:

我几乎不了解您解决方案中的过程,并且它的执行时间比我的解决方案更长,如 Corley 所示。无论如何,这是一个令人印象深刻的解决方案,祝贺这个 "tour de force" -- 您可能会对我在编辑我的答案时的新解决方案感兴趣 如果您不尝试将所有内容都写在一个 182 个字符长的行中,这将更加清晰。无论如何,看起来您正在对每个值的整个范围进行归约,这使其成为二次而不是线性的,这解释了为什么它如此缓慢。许多核心开发者喜欢说“在每个reduce 内部都有一个循环在哭着出去”,我并不总是同意……但是当有 两个 循环在哭着出去并变平时合而为一,那是另一回事。无论如何,仍然是聪明的解决方案【参考方案3】:

编辑

更好的解决方案

不尝试使用列表推导。改为使用递归。 与我以前的解决方案相比,更简单,执行时间除以 3。

LastCoin=[0, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5, 1, 2,
          1, 2, 5, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5,
          1, 2, 1, 2, 5, 1, 2, 1]

def nekst(x,L,rec=None):
    if rec is None: rec = []
    rec.append(L[x])
    if x-L[x]>0: nekst(x-L[x],L,rec)
    return rec

print nekst(33,LastCoin)

结果

[1, 2, 5, 5, 5, 5, 5, 5]

比较执行时间

注意:以下测试是使用没有行的递归函数完成的if rec is None: rec = []. 这条线的存在稍微增加了 (+12%) 具有递归函数的解决方案的执行时间。

from time import clock

iterat = 10000
N = 100

LastCoin=[0, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5, 1, 2,
          1, 2, 5, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5,
          1, 2, 1, 2, 5, 1, 2, 1]

counter = 33
te = clock()
for i in xrange(iterat):
    final_list = [LastCoin[reduce(lambda x, y: x - LastCoin[x],
                                   range(counter, i, -1))]
                   for i in range(counter -1, 0, -1)
                   if reduce(lambda x, y: x - LastCoin[x],
                             range(counter, i, -1))]
print clock()-te,'Omid Raha'

st=33
E1 = []
for n in xrange(N):
    te = clock()
    for i in xrange(iterat):
        susu2 = [st]
        susu2.extend(x for x in xrange(st,0,-1)
                     if x==(susu2[-1]-LastCoin[susu2[-1]]))
        fifi2 = [LastCoin[x] for x in susu2]
        del fifi2
    E1.append(clock()-te)
t1 = min(E1)
print t1,'eyquem 1'

E2 = []
for n in xrange(N):
    te = clock()
    for i in xrange(iterat):
        def nekst(x,L,rec):
            rec.append(L[x])
            if x-L[x]>0: nekst(x-L[x],L,rec)
            return rec
        fifi3 = nekst(st,LastCoin,[])
        del fifi3,nekst
    E2.append(clock()-te)
t2 = min(E2)
print t2,'eyquem 2, nekst redefined at each turn of the measurement loop'

def nekst(x,L,rec):
    rec.append(L[x])
    if x-L[x]>0: nekst(x-L[x],L,rec)
    return rec

E22 = []
for n in xrange(N):
    te = clock()
    for i in xrange(iterat):
        fifi3 = nekst(st,LastCoin,[])
        del fifi3
    E22.append(clock()-te)
t22 = min(E22)
print t22,'eyquem 2, nekst defined outside of the measurement loop'

W = []
for n in xrange(N):
    te = clock()
    for i in xrange(iterat):
        y = 33
        final_list=[]
        while y>0:
            final_list.append(LastCoin[y])
            y-=LastCoin[y]
        del final_list,y
    W.append(clock()-te)
tw = min(W)
print tw,'while-loop == %.1f %% of %s' % (100*min(W)/min(E22),min(E22))

结果

4.10056836833 Omid Raha
0.29426393578 eyquem 1
0.114381576429 eyquem 2, nekst redefined at each turn of the measurement loop
0.107410299354 eyquem 2, nekst defined outside of the measurement loop
0.0820501882362 while-loop == 76.4 % of 0.107410299354

如果函数nekst()的定义在时序循环之外执行,会快一点。

.

原始答案略有编辑

我没有比这更好的了:

sum=33
LastCoin=[0, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5, 1, 2,
          1, 2, 5, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5,
          1, 2, 1, 2, 5, 1, 2, 1]

susu = [sum]
susu.extend(x for x in xrange(sum,0,-1)
            if x==(susu[-1]-LastCoin[susu[-1]]))
fifi = [LastCoin[x] for x in susu]

print 'susu  == %r\n'\
      'fifi  == %r\n'\
      'wanted : %r' % (susu,fifi,[1, 2, 5, 5, 5, 5, 5, 5])

结果

susu  == [33, 32, 30, 25, 20, 15, 10, 5]
fifi  == [1, 2, 5, 5, 5, 5, 5, 5]
wanted : [1, 2, 5, 5, 5, 5, 5, 5]

编辑是

x for x in xrange(sum,0,-1) if x==(susu[-1]-LastCoin[susu[-1]])代替原来的x for x in xrange(sum,-1,-1) if x==(susu[-1]-LastCoin[susu[-1]])!=0

【讨论】:

【参考方案4】:

我参加聚会可能有点晚了。在 python 3.9 中,我们可以使用海象运算符 (:=)

如果我们使用它,我们可以执行以下操作并将 while 语句减少到一行。

LastCoin=[0, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5, 1, 2,
          1, 2, 5, 1, 2, 1, 2, 5, 1, 2, 1, 2, 5,
          1, 2, 1, 2, 5, 1, 2, 1]

s = 33

final_list = [LastCoin[s]]
while (s:=s-LastCoin[s]) > 0: final_list.append(LastCoin[s])

print (final_list)

这个输出将是:

[1, 2, 5, 5, 5, 5, 5, 5]

【讨论】:

以上是关于是否可以将此循环转换为python中的列表理解的主要内容,如果未能解决你的问题,请参考以下文章

如何将此列表理解转换为 for 循环,但我不能 [重复]

如何将字节对象转换为 python 3 中的元组列表?

使用for循环遍历python中的列表

在 Python 中将列表理解转换为 For 循环

如何将此列表视图中可以添加的最大项目数限制为 10?

Python 3.3 如何将此递归函数转换为纯 yield 循环版本?