做集合差异时忽略最后一个元素的Python方法


11

假设我有两个set()s:

a = {('1', '2', '3', 'a'), ('1', '2', '4', 'a'), ('1', '2', '5', 'b')}
b = {('1', '2', '3', 'b'), ('1', '2', '4', 'b'), ('1', '2', '6', 'b')}

现在,我要做的是找到集合差异,b \ a但忽略每个元组中的最后一个元素。所以就像做这样的事情:

a = {('1', '2', '3'), ('1', '2', '4'), ('1', '2', '5')}
b = {('1', '2', '3'), ('1', '2', '4'), ('1', '2', '6')}

In[1]: b - a
Out[1]: {('1', '2', '6')}

预期产量:

b \ a = {('1', '2', '6', 'b')}

是否有任何明显的/ pythonic的方法可以实现这一目标,而不必手动遍历每个集合并对照每个集合进行检查tuple[:3]


3
我最初的想法是使它们成为类,定义比较运算符
肯尼·奥斯特罗姆

2
子类化set并覆盖差异操作。我没有一个开箱即用的解决方案,我怀疑是否存在。
Ev。Kounis

集合没有“ key = ...”或类似的东西(例如sort(..))。元组是不可变数和可哈希的,并根据其哈希进行比较。删除一个元素将使哈希无效。所以不-不可能。如果不需要该值,则可以创建3部分集:aa = { t[:3] for t in a }
Patrick Artner,

2
@ AK47两个集合S和T之间的(集合)差写为S∖T,表示由不属于T的元素S组成的集合:x∈S∖T⟺x∈S∧x∉T
Grajdeanu Alex。

子类化tuple并覆盖差异运算符
Pynchia

Answers:


10

这是您编写自己的类以覆盖元组的常规哈希行为的方式:

a_data = [('1', '2', '3', 'a'), ('1', '2', '4', 'a'), ('1', '2', '5', 'b')]
b_data = [('1', '2', '3', 'b'), ('1', '2', '4', 'b'), ('1', '2', '6', 'b')]

class HashableIgnoresLastElement(tuple):
    def __eq__(self, other):
        return self[:-1] == other[:-1]

    def __hash__(self):
        return hash(self[:-1])

a = set(map(HashableIgnoresLastElement, a_data))
b = set(map(HashableIgnoresLastElement, b_data))

print(b - a)

带输出

{('1', '2', '6', 'b')}

要修改元组集的行为方式,我们必须修改元组的哈希方式。

这里

如果对象的哈希值在其生命周期内始终不变(需要一个__hash__()方法),并且可以与其他对象(需要一个__eq__()方法)进行比较,则该对象是可哈希的。比较相等的可哈希对象必须具有相同的哈希值。

散列性使对象可用作字典键和set成员,因为这些数据结构在内部使用散列值。

因此,为了使散列忽略最后一个元素,我们不得不超负荷dunder方法__eq____hash__适当的。但这并没有那么困难,因为我们要做的就是将最后一个元素切下来,然后委派给normal的适当方法tuple

进一步阅读:


1
井井有条!您还能描述一下这是如何工作的吗?对于那些阅读此解决方案的人来说可能是值得的。
Grajdeanu Alex。

@GrajdeanuAlex。我加了简短的解释:)。实际上,它只是结合了一点点的运算符重载以及哈希在Python中的工作方式。
Izaak van Dongen

2

这是定义ab使用列表而不是集合的一种方法,因为在我看来,最直接的解决方案意味着建立索引b

a = [('1', '2', '3', 'a'), ('1', '2', '4', 'a'), ('1', '2', '5', 'b')]
b = [('1', '2', '3', 'b'), ('1', '2', '4', 'b'), ('1', '2', '6', 'b')]

# reconstruct the sets of tuples removing the last elements
a_ = {tuple(t) for *t, _ in a}
b_ = [tuple(t) for *t, _ in b]

# index b based on whether an element in a_
[b[ix] for ix, j in enumerate(b_) if j not in a_]
# [('1', '2', '6', 'b')]

1
如果我没有记错的话,这是O(n),因为我确实使用了一个集合进行查找。尽管我确实认为Izaak van Dongen的回答更加优雅@konrad
yatu

1
您完全正确,列表的使用(和对列表进行枚举)使我失望,但是当然也需要在第一个集合上进行迭代。
康拉德·鲁道夫

1

设置工作正常。是您的数据无法正常工作。如果它们看起来不同,但实际上是相同的,则定义一种行为就像您想要的数据类型。然后,设置本身就很棒。

class thing:
    def __init__(self, a, b, c, d):
        self.a, self.b, self.c, self.d = a, b, c, d

    def __repr__(self):
        return (str((self.a, self.b, self.c, self.d)))

    def __hash__(self):
        return hash((self.a, self.b, self.c))

    def __eq__(self, other):
        return self.a == other.a and self.b == other.b and self.c == other.c       

a = {thing('1', '2', '3', 'a'), thing('1', '2', '4', 'a'), thing('1', '2', '5', 'b')}
b = {thing('1', '2', '3', 'b'), thing('1', '2', '4', 'b'), thing('1', '2', '6', 'b')}
print (b - a)

{('1','2','6','b')}


3
您定义__repr____hash__在元组的条件,但不是__eq__。在这里使用元组也不会更短吗?实际上,您可以在此处和其中使用切片来__hash__进一步缩短代码。
康拉德·鲁道夫

是的,只是将元组子类化是对所问问题的重大改进。
肯尼·奥斯特罗姆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.