【问题标题】:How to test the membership of multiple values in a list?如何测试列表中多个值的成员资格?
【发布时间】:2021-04-06 02:29:45
【问题描述】:

我想测试两个或多个值是否在一个列表中具有成员资格,但我得到了一个意外的结果:

>>> 'a','b' in ['b', 'a', 'foo', 'bar']
('a', True)

那么,Python 可以同时测试一个列表中多个值的成员资格吗? 这个结果是什么意思?

【问题讨论】:

    标签: python


    【解决方案1】:

    这可以满足您的需求,并且几乎适用于所有情况:

    >>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
    True
    

    表达式 'a','b' in ['b', 'a', 'foo', 'bar'] 无法按预期工作,因为 Python 将其解释为元组:

    >>> 'a', 'b'
    ('a', 'b')
    >>> 'a', 5 + 2
    ('a', 7)
    >>> 'a', 'x' in 'xerxes'
    ('a', True)
    

    其他选项

    还有其他方法可以执行此测试,但它们不适用于多种不同类型的输入。正如Kabie 指出的那样,您可以使用集合来解决这个问题......

    >>> set(['a', 'b']).issubset(set(['a', 'b', 'foo', 'bar']))
    True
    >>> {'a', 'b'} <= {'a', 'b', 'foo', 'bar'}
    True
    

    ...有时:

    >>> {'a', ['b']} <= {'a', ['b'], 'foo', 'bar'}
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unhashable type: 'list'
    

    只能使用可散列元素创建集合。但是生成器表达式all(x in container for x in items) 几乎可以处理任何容器类型。唯一的要求是 container 是可重复的(即不是生成器)。 items 可以是任何可迭代的。

    >>> container = [['b'], 'a', 'foo', 'bar']
    >>> items = (i for i in ('a', ['b']))
    >>> all(x in [['b'], 'a', 'foo', 'bar'] for x in items)
    True
    

    速度测试

    在许多情况下,子集测试会比all 更快,但差异并不令人震惊——除非问题不相关,因为集合不是一个选项。仅仅为了这样的测试而将列表转换为集合并不总是值得麻烦的。将生成器转换为集合有时会非常浪费,会使程序速度降低许多数量级。

    以下是一些用于说明的基准。当containeritems 都相对较小时,最大的区别就出现了。在这种情况下,子集方法大约快一个数量级:

    >>> smallset = set(range(10))
    >>> smallsubset = set(range(5))
    >>> %timeit smallset >= smallsubset
    110 ns ± 0.702 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
    >>> %timeit all(x in smallset for x in smallsubset)
    951 ns ± 11.5 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)
    

    这看起来有很大的不同。但只要 container 是一个集合,all 仍然可以在更大的范围内完美使用:

    >>> bigset = set(range(100000))
    >>> bigsubset = set(range(50000))
    >>> %timeit bigset >= bigsubset
    1.14 ms ± 13.9 µs per loop (mean ± std. dev. of 7 runs, 1000 loops each)
    >>> %timeit all(x in bigset for x in bigsubset)
    5.96 ms ± 37 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    使用子集测试仍然更快,但在此规模下仅快 5 倍。速度的提升是由于 Python 对set 的快速c 支持实现,但两种情况下的基本算法是相同的。

    如果您的items 由于其他原因已经存储在列表中,那么您必须在使用子集测试方法之前将它们转换为集合。然后加速下降到大约 2.5 倍:

    >>> %timeit bigset >= set(bigsubseq)
    2.1 ms ± 49.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    如果你的container是一个序列,需要先转换,那么加速就更小了:

    >>> %timeit set(bigseq) >= set(bigsubseq)
    4.36 ms ± 31.4 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    只有当我们将container 作为序列保留时,才会得到极其缓慢的结果:

    >>> %timeit all(x in bigseq for x in bigsubseq)
    184 ms ± 994 µs per loop (mean ± std. dev. of 7 runs, 10 loops each)
    

    当然,我们只会在必要时这样做。如果bigseq 中的所有项目都是可哈希的,那么我们将改为这样做:

    >>> %timeit bigset = set(bigseq); all(x in bigset for x in bigsubseq)
    7.24 ms ± 78 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    这仅比替代方案快 1.66 倍(set(bigseq) &gt;= set(bigsubseq),以上时间为 4.36)。

    因此子集测试通常更快,但幅度不大。另一方面,让我们看看all 何时更快。如果items 有一千万个值,并且很可能包含不在container 中的值怎么办?

    >>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); set(bigset) >= set(hugeiter)
    13.1 s ± 167 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
    >>> %timeit hugeiter = (x * 10 for bss in [bigsubseq] * 2000 for x in bss); all(x in bigset for x in hugeiter)
    2.33 ms ± 65.2 µs per loop (mean ± std. dev. of 7 runs, 100 loops each)
    

    在这种情况下,将生成器转换为集合被证明是非常浪费的。 set 构造函数必须消耗整个生成器。但是all 的短路行为确保只需要消耗生成器的一小部分,因此它比子集测试快四个数量级

    诚然,这是一个极端的例子。但正如它所显示的那样,您不能假设一种方法或另一种方法在所有情况下都会更快。

    结果

    大多数时候,将container 转换为一个集合是值得的,至少如果它的所有元素都是可散列的。这是因为 in 对于集合是 O(1),而 in 对于序列是 O(n)。

    另一方面,使用子集测试可能只是有时值得。如果您的测试项目已经存储在一组中,请务必这样做。否则,all 只会慢一点,并且不需要任何额外的存储空间。它也可以与大型项目生成器一起使用,有时在这种情况下会提供巨大的加速。

    【讨论】:

      【解决方案2】:

      另一种方法:

      >>> set(['a','b']).issubset( ['b','a','foo','bar'] )
      True
      

      【讨论】:

      • 有趣的事实:set(['a', 'b']) &lt;= set(['b','a','foo','bar']) 是同一事物的另一种拼写方式,看起来“更数学”。
      • 从 Python 2.7 开始,您可以使用 {'a', 'b'} &lt;= {'b','a','foo','bar'}
      【解决方案3】:

      如果您想检查您输入的所有匹配项

      >>> all(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
      

      如果您想检查至少一个匹配项

      >>> any(x in ['b', 'a', 'foo', 'bar'] for x in ['a', 'b'])
      

      【讨论】:

        【解决方案4】:

        我很确定in 的优先级高于,,因此您的语句被解释为'a', ('b' in ['b' ...]),然后评估为'a', True,因为'b' 在数组中。

        有关如何做你想做的事,请参阅上一个答案。

        【讨论】:

          【解决方案5】:

          Python 解析器将该语句评估为一个元组,其中第一个值为 'a',第二个值为表达式 'b' in ['b', 'a', 'foo', 'bar'](其计算结果为 True)。

          你可以编写一个简单的函数来做你想做的事:

          def all_in(candidates, sequence):
              for element in candidates:
                  if element not in sequence:
                      return False
              return True
          

          然后这样称呼它:

          >>> all_in(('a', 'b'), ['b', 'a', 'foo', 'bar'])
          True
          

          【讨论】:

            【解决方案6】:

            我想说我们甚至可以不使用方括号。

            array = ['b', 'a', 'foo', 'bar']
            all([i in array for i in 'a', 'b'])
            

            【讨论】:

              【解决方案7】:
              [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]
              

              我认为这比选择的答案更好的原因是您确实不需要调用“all()”函数。在 IF 语句中,空列表评估为 False,非空列表评估为 True。

              if [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]:
                  ...Do something...
              

              例子:

              >>> [x for x in ['a','b'] if x in ['b', 'a', 'foo', 'bar']]
              ['a', 'b']
              >>> [x for x in ['G','F'] if x in ['b', 'a', 'foo', 'bar']]
              []
              

              【讨论】:

                【解决方案8】:

                这里提供的两个答案都不会处理重复的元素。例如,如果您正在测试 [1,2,2] 是否是 [1,2,3,4] 的子列表,则两者都将返回 True。这可能是你的意思,但我只是想澄清一下。 如果要为 [1,2,3,4] 中的 [1,2,2] 返回 false,则需要对两个列表进行排序,并使用每个列表上的移动索引检查每个项目。只是稍微复杂一点的 for 循环。

                【讨论】:

                • '两者'?答案不止两个。您的意思是所有答案都存在这个问题,还是只有两个答案(如果是,是哪些)?
                【解决方案9】:

                我是这样做的:

                A = ['a','b','c']
                B = ['c']
                logic = [(x in B) for x in A]
                if True in logic:
                    do something
                

                【讨论】:

                  【解决方案10】:

                  如果没有 lambda,你怎么能成为 Pythonic! ..不要被当真..但这种方式也有效:

                  orig_array = [ ..... ]
                  test_array = [ ... ]
                  
                  filter(lambda x:x in test_array, orig_array) == test_array
                  

                  如果您想测试 任何 值是否在数组中,请省略结尾部分:

                  filter(lambda x:x in test_array, orig_array)
                  

                  【讨论】:

                  • 请注意,这在filter 是生成器的 Python 3 中无法按预期工作。如果您想实际获得可以使用== 或在布尔上下文中测试的结果(以查看它是否为空),则需要将其包装在list 中。在anyall 中使用列表推导式或生成器表达式更为可取。
                  猜你喜欢
                  • 1970-01-01
                  • 2015-09-12
                  • 2013-05-31
                  • 2011-07-10
                  • 1970-01-01
                  • 2012-03-02
                  • 2017-07-05
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多