以 pythonic 方式对具有复杂数据对象的多个列表执行 AND、OR、NOT

Do AND, OR, NOT for multiple lists with complex data objects the pythonic way

我有多个包含复杂对象的列表。我想对它们进行布尔运算 ANDORNOT

AND:结果列表将包含 all 使用的源列表中存在的所有对象。不能重复。

OR:结果列表应包含来自所有使用的源列表的所有对象。不能重复。

NOT:结果列表应仅包含源列表中不存在于非列表中的现有对象。

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# the "complex data"
class Person:
    def __init__(self, name):
        # assume the 'name' as unique
        self.name = name

# create example data
mylistA = [Person('Anna'),
           Person('Bob'),
           Person('Jane'),
           Person('Alfred')]

mylistB = [Person('Simon'),
           Person('Anna'),
           Person('Doris'),
           Person('Bob')]

mylistC = [Person('Bob'),
           Person('Rosi'),
           Person('Becky'),
           Person('Anna')]

mylistD = [Person('Alfred'),
           Person('Bob'),
           Person('Chris'),
           Person('Susi')]

def doAND(some_lists):
    pass

def doOR(some_lists):
    pass

def doNOT(one_list, not_list):
    pass

# should result in 'Anna', 'Bob'
resultAND = doAND([mylistA, mylistB, mylistC])
print(resultAND)

# should result in 'Anna', 'Bob', 'Jane', 'Alfred', 'Simon', 'Doris', 'Rosi',
# 'Becky'
resultOR = doOR([mylistA, mylistB, mylistC])
print(resultOR)

# 'Anna'
resultNOT = doNOT(resultAND, mylistD)
print(resultNOT)

背景信息:"complex objects"在真实场景中是sqlalchemy对象。他们的 "identity" 在我这里的例子的上下文中不是主键。他们的 "identity" 是根据他们成员的组合形成的(简单示例:"firstname"、"lastname"、"birthdate")。

您应该使用 set,而不是列表。 这避免了重复并以方便的方式提供您的所有操作:

a=[1,2,3,4,5]
b=[1,2,3]

a=set(a)
b=set(b)

# OR
a | b # [1,2,3,4,5]

# AND
a & b # [1,2,3]

# NOT
a - b # [4,5]

您甚至可以将其用于复杂的数据类型。他们需要满足两个条件:

  • __eq__需要实施
  • __hash__需要实施

集合需要 __eq__ 来查找重复项。但是,如果您只实现 __eq__,默认的 __hash__ 实现将被删除。

那是因为 __eq____hash__ 需要保持一致。 所以你需要重新实现 __hash__

你对内置 hash() 函数的使用实际上比我使用 hashlib 的版本要好得多。所以我更新了那个。 令人惊讶的是,__hash__ 的实现不提供 __eq__ 的隐式实现,即使具有相同散列的对象必须相等是不变量。因此,__eq____hash__都需要实现。在此答案的先前版本中,这是错误的。

也许 __eq__ 运算符需要再次实现,因为性能原因。我不知道 hash() 函数有多快,但如果你的集合变大,直接比较名称而不是先散列它们可能是一个有用的优化。

class Person:
    def __init__(self, name):
        # assume the 'name' as unique
        self.name = name

    def __hash__(self):
        return hash(self.name)

    def __eq__(self, other):
        return self.name == other.name
        # return hash(self) == hash(other)

    def __repr__(self):
        return self.name


persons = [Person("a"), Person("b"), Person("a")]

print(persons)  # [a, b, a]

persons_set= set(persons)

print(persons_set) # [a, b]

感谢@criket_007给了我正确的提示。 Python就是这么简单!只需为 复杂数据对象 创建运算符。然后你可以把它们当作set.

这是更新后的例子

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

# the "complex data"
class Person:
    def __init__(self, name):
        # assume the 'name' as unique
        self.name = name
    def __str__(self):
        return self.name
    def __repr__(self):
        return '{}:{}'.format(id(self), self.__str__())
    def __hash__(self):
        return hash(self.name)

# create example data
mylistA = [Person('Anna'),
           Person('Bob'),
           Person('Jane'),
           Person('Alfred')]
sa = set(mylistA)

mylistB = [Person('Simon'),
           Person('Anna'),
           Person('Doris'),
           Person('Bob')]
sb = set(mylistB)

mylistC = [Person('Bob'),
           Person('Rosi'),
           Person('Becky'),
           Person('Anna')]
sc = set(mylistC)

mylistD = [Person('Alfred'),
           Person('Bob'),
           Person('Chris'),
           Person('Susi')]
sd = set(mylistD)

# should result in 'Anna', 'Bob'
resultAND = sa.intersection(sb, sc)
print('AND: {}\n'.format(resultAND))

# should result in 'Anna', 'Bob', 'Jane', 'Alfred', 'Simon', 'Doris', 'Rosi',
# 'Becky'
resultOR = sa.union(sb, sc)
print('OR: {}\n'.format(resultOR))

# 'Anna'
resultNOT = resultAND.difference(sd)
print('NOT: {}\n'.format(resultNOT))