Python:两个列表中的列表的交集

3
我有一个列表的列表A和一个列表的列表B,其中A和B有许多相同的子列表。
最好的方法是将B中唯一的子列表提取出来,放进A中。
A = [['foo', 123], ['bar', np.array(range(10))], ['baz', 345]]
B = [['foo', 123], ['bar', np.array(range(10))], ['meow', 456]]

=> A = [['foo', 123], ['bar', np.array(range(10))], ['baz', 345], ['meow', 456]]

我尝试过:
A += [b for b in B if b not in A]

但是这会产生一个 ValueError,提示使用 any()all()。我真的必须逐个测试 B 中每个子列表在 A 中的每个子列表吗?

ERROR: ValueError: The truth value of an array with more than one element is ambiguous. Use a.any() or a.all()

2
请提供完整的错误回溯信息。 - jonrsharpe
np.array 实例是否完全可比较? - salezica
1
错误的原因是你实际上在执行 np.arange(10) == np.arange(10),这会导致一个布尔数组而不是单个值(这是非常明确的设计)。通常你会使用集合来解决这个问题,但是numpy数组不可哈希。因此,最简单的方法是将数组转换为不可变元组,并将每个子列表转换为元组或集合。然后你就可以进行“常规”的集合交集操作了。 - Joe Kington
你需要维护列表顺序吗? - dawg
2个回答

1
通常,你可以使用多种方法对一个或多个列表进行去重,无论是否按顺序。
以下是一种不保持顺序的去重两个列表的方法:
>>> A=[1,3,5,'a','c',7]
>>> B=[1,2,3,'c','b','a',6]
>>> set(A+B)
set(['a', 1, 'c', 3, 5, 6, 7, 2, 'b'])

这里有一种方法可以保持顺序:
>>> seen=set()
>>> [e for e in A+B if e not in seen and (seen.add(e) or True)]
[1, 3, 5, 'a', 'c', 7, 2, 'b', 6]

问题在于,为了使用这些方法,所有元素都必须是可哈希的:
>>> set([np.array(range(10)), 22])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: unhashable type: 'numpy.ndarray'

解决这个问题的一种方法是使用每个元素的repr

>>> set([repr(e) for e in [np.array(range(10)), 22]])
set(['22', 'array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])'])

或者使用frozenset
>>> set(frozenset(e) for e in [np.array(range(10)), np.array(range(2))])
set([frozenset([0, 1]), frozenset([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])])

在您的情况下,frozenset方法不适用于列表的列表:
>>> set(frozenset(e) for e in [[np.array(range(10)), np.array(range(2))],[np.array(range(5))
]])
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 1, in <genexpr>
TypeError: unhashable type: 'numpy.ndarray'

所以您需要使用扁平化的列表。
如果子列表的repr是其唯一性的明确证明,您可以这样做:
from collections import OrderedDict
import numpy as np

A = [['foo', 123], ['bar', np.array(range(10))], ['baz', 345]]
B = [['foo', 123], ['bar', np.array(range(10))], ['meow', 456]]

seen=OrderedDict((repr(e),0) for e in B)

newA=[]
for e in A+B:
    key=repr(e)
    if key in seen:
        if seen[key]==0:
            newA.append(e)
            seen[key]=1
    else:
        seen[key]=1
        newA.append(e)

print newA
# [['foo', 123], ['bar', array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])], ['baz', 345], ['meow', 456]]

由于repr函数返回一个字符串,可以被eval函数用于重新创建列表,这是相当明确的测试,但我不能完全确定。它取决于列表中有什么。

例如,lambda的repr无法重新创建lambda:

>>> repr(lambda x:x)
'<function <lambda> at 0x10710ec08>'

但是,'<function <lambda> at 0x10710ec08>' 的字符串值仍然是明确唯一的,因为 0x10710ec08 部分是 lambda 在内存中的地址(在 cPython 中是这样的)。

您还可以像我上面提到的那样--使用 frozenset 中的扁平列表作为已看到或未看到内容的签名:

def flatten(LoL):
    for el in LoL:
        if isinstance(el, collections.Iterable) and not isinstance(el, basestring):
            for sub in flatten(el):
                yield sub
        else:
            yield el      
newA=[]    
seen=set()
for e in A+B:
    fset=frozenset(flatten(e))
    if fset not in seen:
        newA.append(e)
        seen.add(fset)

print newA        
# [['foo', 123], ['bar', array([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])], ['baz', 345], ['meow', 456]]

所以如果你有奇怪的对象既不可哈希又不唯一的repr字符串对象在A和B中,那么你就没办法了。根据你的示例,其中一种方法应该有效。

0

你可以这样做

import numpy as np

A = [['foo', 123], ['bar', np.array(range(10))], ['baz', 345]]
B = [['foo', 123], ['bar', np.array(range(10))], ['meow', 456]]

res = set().update(tuple(x) for x in A).update(tuple(x) for x in B)

除了 np.array 项目之外,它们是不可哈希的……不确定该怎么处理。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接