我正在寻找一种向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.isdisjoint
和set.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+)(如果假设dict
是 set
的可接受替代品)。这里用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及更高版本的私有函数,因此它可能不是一个好的选择。