在条件满足之前过滤掉所有元素,在条件满足之后保留所有元素

Filter out everything before a condition is met, keep all elements after

我想知道是否有解决以下问题的简单方法。这里的问题是我想在初始条件为真后保留此列表中出现的每个元素。这里的条件是我想在值大于 18 为真之前删除所有内容,但保留所有内容。例子

输入:

p = [4,9,10,4,20,13,29,3,39]

预期输出:

p = [20,13,29,3,39]

我知道您可以通过

过滤整个列表
[x for x in p if x>18] 

但我想在找到第一个大于 18 的值后停止此操作,然后包括其余值,无论它们是否满足条件。这似乎是一个简单的问题,但我还没有找到解决方法。

您可以使用 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

在我看来,这可以说是 easiest-to-read 版本。这也对应于其他函数式编程语言中的常见模式,例如Haskell中的dropWhile (<=18) p和Scala中的p.dropWhile(_ <= 18)


或者,使用海象运算符(仅在 python 3.8+ 中可用):

exceeded = False
p = [x for x in p if (exceeded := exceeded or x > 18)]
print(p) # [20, 13, 29, 3, 39]

但我猜有些人不喜欢这种风格。在那种情况下,可以做一个明确的 for 循环(ilkkachu 的建议):

for i, x in enumerate(p):
    if x > 18:
        output = p[i:]
        break
else:
    output = [] # alternatively just put output = [] before for

您可以在生成器表达式和 next:

中使用 enumerate 和列表切片
out = next((p[i:] for i, item in enumerate(p) if item > 18), [])

输出:

[20, 13, 29, 3, 39]

在运行时方面,取决于数据结构。

下图显示了此处答案在 p 的各种长度下的运行时间差异。

如果原始数据是列表,那么使用 提出的惰性迭代器显然是赢家:

但是如果初始数据是一个 ndarray 对象,那么 and (对于大数组)提出的向量化操作会更快。特别是,无论数组大小如何,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'
)

用于生成第二个图的代码(请注意,我必须修改 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'
)

这里有很棒的解决方案;只是想演示如何使用 numpy:

>>> import numpy as np
>>> p[(np.array(p) > 18).argmax():]
[20, 13, 29, 3, 39]

由于这里有很多不错的答案,我决定 运行 一些简单的基准测试。第一个使用长度为 9 的 OP 样本数组 ([4,9,10,4,20,13,29,3,39])。第二个使用随机生成的长度为 20,000 的数组,其中前半部分介于 0 和 15 之间,后半部分介于 -20 和 30 之间(这样分裂就不会发生在中间)。

使用 OP 的数据(长度为 9 的数组):

%timeit enke()
650 ns ± 15.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit j1lee1()
546 ns ± 4.22 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit j1lee2()
551 ns ± 19 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit j2lee3()
536 ns ± 12.9 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

%timeit richardec()
2.08 µs ± 16 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

使用长度为20,000(两万)的数组:

%timeit enke()
1.5 ms ± 34.5 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit j1lee1()
1.95 ms ± 43 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)

%timeit j1lee2()
2.1 ms ± 53.7 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit j2lee3()
2.33 ms ± 96.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)

%timeit richardec()
13.3 µs ± 461 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

生成第二个数组的代码:

p = np.hstack([np.random.randint(0,15,10000),np.random.randint(-20,30,10000)])

所以,对于小案例,numpy 是一个 slug,不需要。但是在大的情况下,numpy 几乎快 100 倍,并且要走的路! :)

我注意到 OP 在回答下提到 p 实际上是一个 Pandas DataFrame。这是一种使用 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)

输出:

    0
4  20
5  13
6  29
7   3
8  39

注意:我对您的 DataFrame 的实际结构视而不见,所以我只是使用了给定的内容。

根据我的经验,至少对于整数列表,使用 enumerate 只是为了找到 一个 索引并丢弃所有其他索引是如此浪费以至于它更快首先只找到元素,然后使用 list.index 找到它的索引。根据一些测试,我预计它比 .

中的 explicit_loop 解决方案(使用 enumerate)快大约 1.44 倍
def start_over_18(p):
    for x in p:
        if x > 18:
            return p[p.index(x):]
    return []

另一种解决方案,使用迭代器,因此我根本不必为索引操心。似乎是两倍 explicit_loop 解决方案:

def start_over_18(p):
    it = iter(p)
    for x in it:
        if x > 18:
            return [x, *it]
    return []