在Python中,有什么简洁的方法可以根据自定义比较过滤短列表只包含唯一值?
我有一个短短的(1到5个)自定义类的实例列表,我想过滤一下,让列表中只保留独特的值,这里的“独特”是根据我自己定义的比较方式来判断的。
有没有简单、整洁、符合Python风格、又快的方法来做到这一点?我看到的常见方法是用set()来去重,但这对我来说不太适用,因为set是通过__hash__
来比较的,这样就不能逐个对象进行比较了。
额外说明:
我对“独特”的理解其实是值之间的差异要小于某个特定的值。实际上,这更像是“接近度”而不是“独特性”。这就是为什么我不能仅仅检查一个值是否已经被使用,我需要检查这个值是否与其他值太接近。
2 个回答
因为 set
这个东西不支持自定义的比较方式,所以我会把对象包裹起来,让它们符合你选择的比较方式:
class Wrapped:
def __init__(self, value):
self.value = value
def __hash__(self):
# …
def __eq__(self, other):
# …
values = set(Wrapped(x) for x in your_list)
还有一种更优雅的办法,就是把 set
包装一下,提供一个可以自动进行这种包装的版本。
如果你把问题重新理解为为每个对象生成一个独特的值,这个值是根据一组被认为相等的对象来决定的,你仍然可以使用集合,但要用它来存储你认为对象相等的值,方法是:
def unique_value(obj):
return some_calculation_over(obj)
seen = set()
filtered = [obj for obj in yourlist if unique_value(obj) not in seen and not seen.add(unique_value(obj)]
另一种方法是无论如何都实现 __hash__
方法,并确保它根据你的比较标准返回 相同的值;比如说,对于被认为相等的对象,它们的哈希值应该是一样的。你还需要实现一个 __eq__
方法。
如果两个对象在两个属性上是相等的,那么这个值就是这两个属性的元组。如果你能计算出一个数字,并且相等的数字意味着对象相等,那就这样做,等等。
当你想要移除一组对象时,方法并没有太大不同;只需计算对象落入的桶。那些“接近”的对象会被放入同一个桶里,这样你就得到了独特的值(桶的标识符)。
为了将一系列足够接近的对象聚类,也许你想要先根据某个属性对对象进行 排序,然后挑选出那些足够接近的元素:
def unique_close(lst, tolerance, key=None):
if key is None:
# identity
key = lambda o: o
items = iter(sorted(lst, key=key))
first = next(items)
prev = key(first)
yield first
for item in items:
val = key(item)
if abs(prev - val) > tolerance:
yield item
prev = val
这个生成器会根据你选择的关键字对元素进行排序(默认是元素本身),然后只返回那些超出容忍度的元素。
演示:
>>> list(unique_close([1, 4, 5, 2, 3], 2))
[1]
>>> list(unique_close([1, 4, 5, 2, 22, 24, 3], 2))
[1, 22]
>>> list(unique_close([1, 4, 5, 2, 22, 24, 3], 3, key=lambda v: v * 2))
[1, 22, 24]