在Python中,有什么简洁的方法可以根据自定义比较过滤短列表只包含唯一值?

1 投票
2 回答
604 浏览
提问于 2025-04-18 04:16

我有一个短短的(1到5个)自定义类的实例列表,我想过滤一下,让列表中只保留独特的值,这里的“独特”是根据我自己定义的比较方式来判断的。

有没有简单、整洁、符合Python风格、又快的方法来做到这一点?我看到的常见方法是用set()来去重,但这对我来说不太适用,因为set是通过__hash__来比较的,这样就不能逐个对象进行比较了。

额外说明:

我对“独特”的理解其实是值之间的差异要小于某个特定的值。实际上,这更像是“接近度”而不是“独特性”。这就是为什么我不能仅仅检查一个值是否已经被使用,我需要检查这个值是否与其他值太接近。

2 个回答

0

因为 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 包装一下,提供一个可以自动进行这种包装的版本。

2

如果你把问题重新理解为为每个对象生成一个独特的值,这个值是根据一组被认为相等的对象来决定的,你仍然可以使用集合,但要用它来存储你认为对象相等的值,方法是:

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]

撰写回答