包含+添加用于设置的函数,仅对项目进行一次哈希处理



我正在寻找一种向set添加value的方法,但我还需要知道该值是否在添加之前set中。附加限制是该值的hash仅计算一次(如果在我添加它之前未包含它)。

如果没有限制,这将很容易:

def contains_add(aset, value):
    contains = value in aset
    if not contains:
        aset.add(value)
    return contains

但不幸的是,该值的hash方法非常昂贵,我无法(轻松)更改该类。

一种方法是创建一个包含value的临时set,然后使用set.isdisjointset.update - 两者都不需要重新计算hash

def set_contains_add(aset, value):
    anotherset = {value}
    containsnot = anotherset.isdisjoint(aset)
    if containsnot:
        aset.update(anotherset)
    return not containsnot

要验证这是否仅计算一次hash,请执行以下操作:

class MyClass(object):
    def __init__(self, value):
        self._value = value
    def __hash__(self):
        print('hashing')
        return hash(self._value)
    def __eq__(self, other):
        return self._value == other._value
    def __repr__(self):
        return '{}({})'.format(self.__class__.__name__, self._value)
>>> myset = set()
>>> set_contains_add(myset, MyClass(1))
hashing
False
>>> set_contains_add(myset, MyClass(1))
hashing
True

但是,如果hash函数不昂贵,这种方法肯定比问题中提出的函数慢!


另一种方法(不真正推荐,因为它使用私有函数)是使用dict的 Python-C-API(3.5+)(如果假设dictset 的可接受替代品)。这里用Cython模拟:

%load_ext cython
%%cython
from cpython.object cimport PyObject, PyObject_Hash
cdef extern from "Python.h":
    PyObject* _PyDict_GetItem_KnownHash(object mp, object key, Py_hash_t hash)
    int _PyDict_SetItem_KnownHash(object mp, object key, object item, Py_hash_t hash) except -1
    int _PyDict_Contains(object mp, object key, Py_hash_t hash) except -1
def dict_contains_add(object mydict, object key):
    cdef Py_hash_t keyhash = PyObject_Hash(key)
    cdef bint contains = _PyDict_Contains(mydict, key, keyhash)
    if not contains:
        _PyDict_SetItem_KnownHash(mydict, key, 0, keyhash)
    return contains
>>> mydict = dict()
>>> dict_contains_add(mydict, MyClass(1))
hashing
False
>>> dict_contains_add(mydict, MyClass(1))
hashing
True

从好的方面来说,dict_contains_add确实很快(比问题中介绍的函数更快),但由于它(错误地)使用仅适用于python 3.5及更高版本的私有函数,因此它可能不是一个好的选择。

最新更新