如何快速比较列表和集合?

How can I quickly compare a list and a set?

假设我有一个列表

l = [1, 1 , 1, 2, 3, 4, 5, 5]

和两个不相交组等长

a = (1, 3)b = (2, 5)

我想分别获取 abl 中的元素,例如

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

我尝试了像 [x for x in l if x in a] 这样的列表理解,但是如果 lab 的长度是 10^5[=29,那需要很长时间=]

编辑:集合是等长的不相交集合。

编辑:我需要做的是计算 l 中常见的 a 中的元素(重复项)减去 [=15= 中的 l 中的元素](也有重复项)。所以上面的例子应该输出1。问题是列表和集合是否与 10E5 一样长。使用过滤器和 itertools 仍然需要很长时间。

编辑:我现在明白了!显然我必须用 set() 包装输入集!一开始我不知道(我只是通过 input().split() 得到的)因为输入已经是唯一的但不知道列表和集合有很大的不同而且集合更快!好吧,直到我。

您可以使用 itertools 模块中的 chainrepeat 函数:

>>> from itertools import repeat,chain
>>> a={1,3}
>>> list(chain.from_iterable((repeat(i,l.count(i)) for i in a)))
[1, 1, 1, 3]

注意:作为一种更有效的方法,您可以为 a 使用 set 容器,它的成员检查复杂度为 O(1),并且您不需要调用 list 如果您不需要结果作为列表,chain.from_iterable returns 一个 iterator.

或者作为一种非常优化的方法,您可以使用 numpy,这在处理大型列表时特别强大:

>>> import numpy as np
>>> l = np.array([1, 1 , 1, 2, 3, 4, 5, 5])
>>> a = (1, 3)
>>> l[np.in1d(l,a)]
array([1, 1, 1, 3])
l = [1, 1 , 1, 2, 3, 4, 5, 5]
a = (1, 3)
b = (3, 5)
l2 = list(filter(a.__contains__, l)) # [1, 1, 1, 3]
l3 = list(filter(b.__contains__, l)) # [3, 5, 5]

我很想知道这对您的数据集有何影响。

from pandas import Series
l = Series([1, 1 , 1, 2, 3, 4, 5, 5])
a = Series((1, 3))
b = Series((3, 5))
a_answer, b_answer = list(l[l.isin(a)]), list(l[l.isin(b)])

原因:Pandas 的调用实现级别低于 Python,因此根据数字的数据类型,它可能比纯粹的 python 解决方案更适合您。

快吗?

Kasramvd 提出了一个聪明的 np.in1d() 方法和 Wesley 的强大 pandas 框架还建议,让我设置定量标准 以便能够处理个别解决方案的质量。

让我们既公平又量化:

越多,一旦10E+5个物品进入游戏。 处理效率和速度内存处理,矢量化潜力和(也许)隐藏不利的副作用,CPU-缓存延迟掩蔽更差或更好的关闭-CPU数据访问时间(以及更多的巨魔)-这些都是敌人,我们必须在生产中忍受:


摘要:

Nathan 基于 set 的方法对于所有测试的量表都更快。

Nathan 的方法处理小型 1E+41E+6 缩放集的速度相当快,利用隐藏在 python 中独特元素的可哈希集合中的有利搜索功能的成果 set-s(因为 set 类型正是为 引入的)。

然而,O( m*n ) / O( n^2 )无法证明。

这些假设的复杂性模型应该意味着,随着 mn 规模的增长,与 [=20] 相比,基于 numpy 的方法的不利性能损失=] 基于相同 list/set-dataSets 的方法应增长并加速更大的 m,n-s.

增加 set 尺度表明初始边缘在更大的尺度上变得更小。

1:31E+4 规模上的速度优势降级到仍然尊重,但一些[=109 的优势较小=]1:2 1E+6 规模上的速度优势。

真正的代码执行已经做出了任务 O(m*n)/O(n^2) 复杂度的先验假设,无法在体内确认。


如何测试?

>>> import numpy as np
>>> from zmq import Stopwatch
>>> aStopWATCH = Stopwatch()
>>> l = [1, 1 , 1, 2, 3, 4, 5, 5]
>>> a = (1, 3)
>>> b = (3, 5)
>>> npL = np.array( l )
>>> npL
array([1, 1, 1, 2, 3, 4, 5, 5])

>>> npA = np.array( a )
>>> npA
array([1, 3])

>>> import numba # ______________________________was not me who has said QUICKLY
>>> @numba.jit   #                                                       QUICKLY
... def getLinA( aListAsNumpyARRAY, aSetAsNumpyARRAY ):             # << Kasramvd
...    return    aListAsNumpyARRAY[ np.in1d( aListAsNumpyARRAY,
                                              aSetAsNumpyARRAY
                                              )
                                    ]
>>>
>>> aStopWATCH.start();getLinA( npL, npA );aStopWATCH.stop()
array([1, 1, 1, 3])
113513L                 # runs a JIT-compiler on a first call ... pay 113,51 [ms]

>>> aStopWATCH.start();getLinA( npL, npA );aStopWATCH.stop()
array([1, 1, 1, 3])
653L                    # runs a pre-compiled code on 2nd+ ...... wow   0,65 [ms]

855L                    # runs a pre-compiled code on 2nd+ ...... wow   0,86 [ms]
857L                    # runs a pre-compiled code on 2nd+ ...... wow   0,86 [ms]
698L                    # runs a pre-compiled code on 2nd+ ...... wow   0,7  [ms]
690L                    # runs a pre-compiled code on 2nd+ ...... wow   0,7  [ms]

提示:

是的,没有免费的晚餐。
如您所见,一旦我们不得不为 JIT 编译付出代价。 但是,多亏了 Travis OLIPHANT 出色的 numba 工具,没有人可以阻止我们 "pre-mini-call"(让 JIT 编译器完成它的职责)

getLinA( npL[:2], npA[:2] )

然后重新 运行 已编译的全尺寸

getLinA( npL[:1E+9], npA[:1E+9] )

Ex post 受与 Nathan Davis

思想交流启发的讨论
>>> def NathanListPROCESSOR( aList = [1,1,3,2,5,7,8,3,2,1],
                             setA  = set( ( 1, 3 ) ),
                             setB  = set( ( 2, 3 ) )
                             ):
...     accumulator = 0
...     for item in aList:
...         if item in setA:
...            accumulator += 1
...         elif item in setB:
...            accumulator -= 1
...     print accumulator
...     return
...
>>> NathanListPROCESSOR()   #_______________________EXPECTED:    == 2
3                           #_______________________O/P DEFINED: == 1

时间:请参考。随着实际代码执行工件的出现,计时结果发生变化(缓存脏度发生变化,)

>>> aStopWATCH.start();NathanListPROCESSOR();aStopWATCH.stop()
3
  1207L
   491L
   279L
   350L
  1478L
  1172L
  1698L
  1488L
  1449L
  9688L
  1466L

对于缩放到 1E+41E+6 大小的对象:

>>> aStopWATCH.start();NathanListPROCESSOR( aLIST, setA, setB );aStopWATCH.stop()
-1
  2582L
  2673L
  2529L
  2524L
  2888L
  2693L

>>> aStopWATCH.start();getLISTinSET( npLIST, npSetA, npSetB );aStopWATCH.stop()
0
129983L
 12068L
 10699L
 10930L
 10857L
 10999L
 10954L
 10994L

numba 的帮助下:

>>> @numba.jit
... def numba_getLISTinSET( npList, npSetA, npSetB ):
...     return ( len(       npList[ np.in1d( npList, npSetA ) ] ) -
                 len(       npList[ np.in1d( npList, npSetB ) ] )
                 )
>>> aStopWATCH.start();numba_getLISTinSET( npLIST, npSetA, npSetB );aStopWATCH.stop()
0
165320L
  7047L
  7328L
  7378L
  7898L
  7519L
  7556L
  7277L
  7296L
  7292L
  7303L
  7302L
  7426L
  7369L
  7307L

终于几乎-1E+6天平:

>>> setA = generateSet( 1000000 )
>>> len( setA )                                      # the cost of uniqueness
235836
>>> setB = generateSet( 1000000 )

>>> npSetA = np.array( list( setA ), dtype = np.int )
>>> npSetB = np.array( list( setB ), dtype = np.int )

>>> aLIST = list( ( np.random.random( 1000000 * 1.1 + 10 ) * 1000000 * 10000 ).astype( np.int ) )[:1000000]

>>> len( aLIST )
1000000
>>> npLIST = np.array( aLIST, dtype = np.int )

#----------------------vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv-------------
#---------------------|                                           |-------------
>>> aStopWATCH.start();numba_getLISTinSET( npLIST, npSetA, npSetB );aStopWATCH.stop()
6
406061L
403946L
409831L
409329L
408920L

#----------------------vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv-------------
#---------------------|                                       |-------------
>>> aStopWATCH.start();NathanListPROCESSOR( aLIST, setA, setB );aStopWATCH.stop()
785334
200755L
196791L
195540L
196606L
202483L
197094L
199481L
196651L
200969L
198856L
202039L
200152L
202364L

根本问题是您没有为工作使用适当的数据结构。 在这种情况下,对于 small 集合,使用元组表示集合可能 ok, 但是对于 large 集,您可以期望搜索平均 列表中每个元素的集合组合大小的一半 那实际上是在其中一个集合中。 对于列表中每个集合中 not 的元素, 我们必须搜索两个集合的所有个元素以确定。

所以任何基于这些数据结构的算法 (即,使用元组表示集合) 最好是 O(m*n),其中 m 是列表的大小 n 是集合的大小。

我们确实没有任何方法可以减少 m 组件 — 我们必须检查列表的每个元素以确定哪个集合 (如果有的话)它属于。

但是,我们可以减少 n 分量。 如何?通过为我们的集合使用更高效的数据结构。

幸运的是,这并不难,因为 Python 包含一个内置的 set 类型。 所以第一步是构建两个集合:

a = set((1, 3))
b = set((2, 5))

现在,我们可以轻松(高效)确定元素 e 是否在其中一个集合中:

e = 1
e in a # => True
e in b # => False

现在,我们只需要遍历输入列表并累加结果:

l = [1, 1, 3, 2, 5, 7, 8, 3, 2, 1]
result = 0 # accumulator for result
for e in l:
  if e in a:
    result += 1
  elif e in b:
    result -= 1

print result # prints "2"