【问题标题】:Filter out everything before a condition is met, keep all elements after在满足条件之前过滤掉所有元素,保留之后的所有元素
【发布时间】:2022-02-03 01:32:24
【问题描述】:

我想知道以下问题是否有简单的解决方案。这里的问题是,在初始条件为真之后,我想保留此列表中出现的每个元素。这里的条件是我想在值大于 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 的值后停止此操作,然后将其余值包含在内,无论它们是否满足条件。这似乎是一个简单的问题,但我还没有找到解决方案。

【问题讨论】:

    标签: python list list-comprehension


    【解决方案1】:

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

    在我看来,这可以说是最容易阅读的版本。这也对应于其他函数式编程语言中的常见模式,例如 Haskell 中的 dropWhile (&lt;=18) p 和 Scala 中的 p.dropWhile(_ &lt;= 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
    

    【讨论】:

    • 在这里很好地使用了warlus。值得一提的是它只存在于 Python 3.8+
    • 我感觉到你所说的海象。出于某种原因,最后一个看起来仍然有点烦人(可能只是我)。由于我们在列表的尾随部分,使用p[i:] 绕过临时变量似乎很诱人。 for i, x in enumerate(p): if x &gt; 18: out = p[i:]; break 之类的东西(带有适当的空白)。
    • @ilkkachu 好点!我已经包括了。
    • @ilkkachu 据我了解,这似乎与下面接受的答案非常相似。它比那个有什么优势吗?
    • @NewbieAF,好吧,大多数人都可以立即阅读显式循环,而紧凑的单行可能需要更多的解码。我并不是说另一个答案中的那个是错误的,如果我这样做了,我可能会被它是 Pythonic 方式或其他方式所困扰。也不知道性能。只是我觉得这里的显式循环可以简化,同时仍然保持可读性。
    【解决方案2】:

    您可以使用enumerate 并在生成器表达式和next 中列出切片:

    out = next((p[i:] for i, item in enumerate(p) if item > 18), [])
    

    输出:

    [20, 13, 29, 3, 39]
    

    就运行时间而言,取决于数据结构。

    下图显示了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'
    )
    

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

    【讨论】:

    • 如此漂亮、优雅地使用内置插件,它不会超出它的运行范围!生成器表达式摇滚!
    • @Graipher 你确定吗? ideone.com/UAVscC
    • @Unmitigated true,if 条件一开始就阻止了语句被执行,它只对项目进行操作。
    • 能否将my solutions 包含在您的基准测试中?我认为它们将是您的第一个基准测试中最快的,没有尝试另一个。
    • 感谢您的更新。由于函数调用,itertools.dropwhile 很慢。看起来使用 18 .__ge__ 代替 lambda 会显着加快速度,但根据我的经验,人们出于某种原因往往不喜欢它。
    【解决方案3】:

    这里有很好的解决方案,只是想演示如何使用 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])。第二个使用随机生成的长度为 20000 的数组,其中前半部分介于 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(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 是一个蛞蝓,不需要。但是在大的情况下,numpy 几乎快 100 倍,而且还有很长的路要走! :)

    【讨论】:

    • 非常感谢您的回答!我确实注意到实际用例(这是一个相当大的 pandas 数据帧)的输出时间与发布的其他解决方案相比存在很大差异。
    • @cinderashes 对于您的实际用例来说,这是最快的吗? :)
    • @cinderashes 如果您的实际用例是 DataFrame,那么如果您询问有关 DataFrame 而不是列表的问题会更好(对您而言,尽管对其他人而言可能不是)像你一样。正如您从这个答案中看到的那样,它们之间的性能可能会有很大差异!
    【解决方案4】:

    我注意到在 p 实际上是 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)
    

    输出:

        0
    4  20
    5  13
    6  29
    7   3
    8  39
    

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

    【讨论】:

      【解决方案5】:

      根据我的经验,至少对于整数列表,使用 enumerate 只是为了找到 一个 索引并丢弃所有其他索引非常浪费,因此首先只找到元素然后再查找会更快使用list.index 查找其索引。根据一些测试,我预计它比@enke's benchmark 中的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 []
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2021-06-07
        • 2020-05-10
        • 2013-03-06
        • 2021-07-02
        • 1970-01-01
        • 2012-01-07
        • 2017-08-23
        相关资源
        最近更新 更多