cin*_*hes 26 python list-comprehension list
我想知道是否有一个简单的解决方案可以解决以下问题。这里的问题是,我想在初始条件为真后保留此列表中出现的每个元素。这里的条件是我想删除值大于 18 的条件为真之前的所有内容,但保留之后的所有内容。例子
输入:
p = [4,9,10,4,20,13,29,3,39]
Run Code Online (Sandbox Code Playgroud)
预期输出:
p = [20,13,29,3,39]
Run Code Online (Sandbox Code Playgroud)
我知道您可以通过以下方式过滤整个列表
[x for x in p if x>18]
Run Code Online (Sandbox Code Playgroud)
但我想在找到第一个大于 18 的值后停止此操作,然后包含其余的值,无论它们是否满足条件。这似乎是一个简单的问题,但我还没有找到解决方案。
j1-*_*lee 49
您可以使用itertools.dropwhile:
from itertools import dropwhile
p = [4,9,10,4,20,13,29,3,39]
p = dropwhile(lambda x: x <= 18, p)
print(*p) # 20 13 29 3 39
Run Code Online (Sandbox Code Playgroud)
在我看来,这可以说是最容易阅读的版本。dropWhile (<=18) p这也对应于其他函数式编程语言(例如Haskell 和p.dropWhile(_ <= 18)Scala)中的常见模式。
或者,使用 walrus 运算符(仅在 python 3.8+ 中可用):
exceeded = False
p = [x for x in p if (exceeded := exceeded or x > 18)]
print(p) # [20, 13, 29, 3, 39]
Run Code Online (Sandbox Code Playgroud)
但我的猜测是有些人不喜欢这种风格。在这种情况下,可以执行显式for循环(ilkkachu 的建议):
for i, x in enumerate(p):
if x > 18:
output = p[i:]
break
else:
output = [] # alternatively just put output = [] before for
Run Code Online (Sandbox Code Playgroud)
小智 23
enumerate您可以在生成器表达式中使用并列出切片和next:
out = next((p[i:] for i, item in enumerate(p) if item > 18), [])
Run Code Online (Sandbox Code Playgroud)
输出:
[20, 13, 29, 3, 39]
Run Code Online (Sandbox Code Playgroud)
就运行时间而言,取决于数据结构。
下图显示了此处针对不同长度的答案之间的运行时差异p。
如果原始数据是一个列表,那么使用@Kelly Bundy提出的惰性迭代器显然是赢家:
但如果初始数据是 ndarray 对象,那么@richardec和@0x263A(对于大型数组)提出的向量化操作会更快。特别是,无论数组大小如何,numpy 都优于列表方法。但对于非常大的数组,pandas 开始比 numpy 表现得更好(我不知道为什么,如果有人能解释的话,我(并且我确信其他人)会很感激)。
用于生成第一个图的代码:
import perfplot
import numpy as np
import pandas as pd
import random
from itertools import dropwhile
def it_dropwhile(p):
return list(dropwhile(lambda x: x <= 18, p))
def walrus(p):
exceeded = False
return [x for x in p if (exceeded := exceeded or x > 18)]
def explicit_loop(p):
for i, x in enumerate(p):
if x > 18:
output = p[i:]
break
else:
output = []
return output
def genexpr_next(p):
return next((p[i:] for i, item in enumerate(p) if item > 18), [])
def np_argmax(p):
return p[(np.array(p) > 18).argmax():]
def pd_idxmax(p):
s = pd.Series(p)
return s[s.gt(18).idxmax():]
def list_index(p):
for x in p:
if x > 18:
return p[p.index(x):]
return []
def lazy_iter(p):
it = iter(p)
for x in it:
if x > 18:
return [x, *it]
return []
perfplot.show(
setup=lambda n: random.choices(range(0, 15), k=10*n) + random.choices(range(-20,30), k=10*n),
kernels=[it_dropwhile, walrus, explicit_loop, genexpr_next, np_argmax, pd_idxmax, list_index, lazy_iter],
labels=['it_dropwhile','walrus','explicit_loop','genexpr_next','np_argmax','pd_idxmax', 'list_index', 'lazy_iter'],
n_range=[2 ** k for k in range(18)],
equality_check=np.allclose,
xlabel='~n/20'
)
Run Code Online (Sandbox Code Playgroud)
用于生成第二个图的代码(请注意,我必须修改,list_index因为 numpy 没有index方法):
def list_index(p):
for x in p:
if x > 18:
return p[np.where(p==x)[0][0]:]
return []
perfplot.show(
setup=lambda n: np.hstack([np.random.randint(0,15,10*n), np.random.randint(-20,30,10*n)]),
kernels=[it_dropwhile, walrus, explicit_loop, genexpr_next, np_argmax, pd_idxmax, list_index, lazy_iter],
labels=['it_dropwhile','walrus','explicit_loop','genexpr_next','np_argmax','pd_idxmax', 'list_index', 'lazy_iter'],
n_range=[2 ** k for k in range(18)],
equality_check=np.allclose,
xlabel='~n/20'
)
Run Code Online (Sandbox Code Playgroud)
小智 8
这里有很好的解决方案;只是想演示如何使用 numpy 做到这一点:
\n>>> import numpy as np\n>>> p[(np.array(p) > 18).argmax():]\n[20, 13, 29, 3, 39]\nRun Code Online (Sandbox Code Playgroud)\n由于这里有很多很好的答案,我决定运行一些简单的基准测试。第一个使用长度为 9 的 OP 样本数组 ( [4,9,10,4,20,13,29,3,39])。第二个使用随机生成的长度为 20,000 的数组,其中前半部分在 0 到 15 之间,后半部分在 -20 到 30 之间(以便分割不会发生在中心)。
使用OP的数据(长度为9的数组):
\n%timeit enke()\n650 ns \xc2\xb1 15.9 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000000 loops each)\n\n%timeit j1lee1()\n546 ns \xc2\xb1 4.22 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000000 loops each)\n\n%timeit j1lee2()\n551 ns \xc2\xb1 19 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000000 loops each)\n\n%timeit j2lee3()\n536 ns \xc2\xb1 12.9 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000000 loops each)\n\n%timeit richardec()\n2.08 \xc2\xb5s \xc2\xb1 16 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 100000 loops each)\nRun Code Online (Sandbox Code Playgroud)\n使用长度为 20,000(20,000)的数组:
\n%timeit enke()\n1.5 ms \xc2\xb1 34.5 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000 loops each)\n\n%timeit j1lee1()\n1.95 ms \xc2\xb1 43 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 1000 loops each)\n\n%timeit j1lee2()\n2.1 ms \xc2\xb1 53.7 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 100 loops each)\n\n%timeit j2lee3()\n2.33 ms \xc2\xb1 96.2 \xc2\xb5s per loop (mean \xc2\xb1 std. dev. of 7 runs, 100 loops each)\n\n%timeit richardec()\n13.3 \xc2\xb5s \xc2\xb1 461 ns per loop (mean \xc2\xb1 std. dev. of 7 runs, 100000 loops each)\nRun Code Online (Sandbox Code Playgroud)\n生成第二个数组的代码:
\np = np.hstack([np.random.randint(0,15,10000),np.random.randint(-20,30,10000)])\nRun Code Online (Sandbox Code Playgroud)\n因此,对于小情况,numpy 是一个 slug,不需要。但在大型案例中,numpy 的速度几乎快了 100 倍,而且是最佳选择!:)
\np我注意到实际上是 Pandas DataFrame 的答案下提到的 OP 。以下是使用 Pandas 过滤直到第一个大于 18 的数字的所有元素的方法:
import pandas as pd
df = pd.DataFrame([4,9,10,4,20,13,29,3,39])
df = df[df[0].gt(18).idxmax():]
print(df)
Run Code Online (Sandbox Code Playgroud)
输出:
0
4 20
5 13
6 29
7 3
8 39
Run Code Online (Sandbox Code Playgroud)
注意:我对 DataFrame 的实际结构一无所知,所以我只是准确地使用了给出的结构。