在对列表中查找唯一对

时间:2016-07-04 14:38:42

标签: python list

我有一个(大)整数列表列表,例如,

a = [
    [1, 2],
    [3, 6],
    [2, 1],
    [3, 5],
    [3, 6]
    ]

大多数对将出现两次,其中整数的顺序无关紧要(即[1, 2]等同于[2, 1])。我现在想找到只出现一次的对,并得到一个布尔列表来表示。对于上面的例子,

b = [False, False, False, True, False]

由于a通常很大,我想避免使用显式循环。可能会建议映射到frozenset,但我不确定这是否过度。

5 个答案:

答案 0 :(得分:14)

ctr = Counter(frozenset(x) for x in a)
b = [ctr[frozenset(x)] == 1 for x in a]

我们可以使用Counter来获取每个列表的计数(将列表转到冻结集以忽略顺序),然​​后对每个列表检查它是否只出现一次。

答案 1 :(得分:6)

以下是使用NumPy的解决方案比建议的frozenset解决方案快10倍:

a = numpy.array(a)
a.sort(axis=1)
b = numpy.ascontiguousarray(a).view(
        numpy.dtype((numpy.void, a.dtype.itemsize * a.shape[1]))
        )
_, inv, ct = numpy.unique(b, return_inverse=True, return_counts=True)
print(ct[inv] == 1)

不同阵列尺寸的速度比较:

enter image description here

情节是用

创建的
from collections import Counter
import numpy
import perfplot


def fs(a):
    ctr = Counter(frozenset(x) for x in a)
    b = [ctr[frozenset(x)] == 1 for x in a]
    return b


def with_numpy(a):
    a = numpy.array(a)
    a.sort(axis=1)
    b = numpy.ascontiguousarray(a).view(
            numpy.dtype((numpy.void, a.dtype.itemsize * a.shape[1]))
            )
    _, inv, ct = numpy.unique(b, return_inverse=True, return_counts=True)
    res = ct[inv] == 1
    return res


perfplot.show(
        setup=lambda n: numpy.random.randint(0, 10, size=(n, 2)),
        kernels=[fs, with_numpy],
        labels=['frozenset', 'numpy'],
        n_range=[2**k for k in range(12)],
        xlabel='len(a)',
        logx=True,
        logy=True,
        )

答案 2 :(得分:2)

您可以从头到尾扫描列表,同时将map个遇到的对保留到第一个位置。无论何时处理一对,都要检查以前是否遇到过它。如果是这种情况,则b中的第一个遭遇索引和当前遭遇索引都必须设置为False。否则,我们只是将当前索引添加到遇到的对的映射中,并且不对b进行任何更改。 b最初将开始全部True。为了保持与[1,2][2,1]相当的东西,我首先简单地对该对进行排序,以获得稳定的表示。代码看起来像这样:

def proc(a):
  b = [True] * len(a) # Better way to allocate this
  filter = {}
  idx = 0
  for p in a:
    m = min(p)
    M = max(p)
    pp = (m, M)
    if pp in filter:
      # We've found the element once previously
      # Need to mark both it and the current value as "False"
      # If we encounter pp multiple times, we'll set the initial
      # value to False multiple times, but that's not an issue
      b[filter[pp]] = False
      b[idx] = False
    else:
      # This is the first time we encounter pp, so we just add it
      # to the filter for possible later encounters, but don't affect
      # b at all.
      filter[pp] = idx
    idx++
  return b

时间复杂度为O(len(a))这很好,但空间复杂度也是O(len(a))(对于filter),所以这可能不是那么好。根据您的灵活程度,您可以使用近似过滤器,例如布隆过滤器。

答案 3 :(得分:2)

#-*- coding : utf-8 -*-
a = [[1, 2], [3, 6], [2, 1], [3, 5], [3, 6]]
result = filter(lambda el:(a.count([el[0],el[1]]) + a.count([el[1],el[0]]) == 1),a)
bool_res = [ (a.count([el[0],el[1]]) + a.count([el[1],el[0]]) == 1) for el in a]
print result
print bool_res

给出:

[[3, 5]]
[False, False, False, True, False]

答案 4 :(得分:-1)

使用字典表示O(n)解决方案。

a = [ [1, 2], [3, 6], [2, 1], [3, 5], [3, 6] ]

dict = {}
boolList = []

# Iterate through a
for i in range (len(a)):

    # Assume that this element is not a duplicate
    # This 'True' is added to the corresponding index i of boolList
    boolList += [True]

    # Set elem to the current pair in the list
    elem = a[i]

    # If elem is in ascending order, it will be entered into the map as is
    if elem[0] <= elem[1]:
        key = repr(elem)
    # If not, change it into ascending order so keys can easily be compared
    else:
        key = repr( [ elem[1] ] + [ elem[0] ])

    # If this pair has not yet been seen, add it as a key to the dictionary
    # with the value a list containing its index in a.
    if key not in dict:
        dict[key] = [i]
    # If this pair is a duploicate, add the new index to the dict. The value
    # of the key will contain a list containing the indeces of that pair in a.
    else:
        # Change the value to contain the new index
        dict[key] += [i]

        # Change boolList for this to True for this index
        boolList[i] = False

        # If this is the first duplicate for the pair, make the first
        # occurrence of the pair into a duplicate as well.
        if len(dict[key]) <= 2:
            boolList[ dict[key][0] ] = False

print a
print boolList