Some very quick notes.
- The algorithm in
remove
is incorrect — it can leave keys unfindable.
This is a common mistake! In The Art of Computer Programming, Knuth comments, "Many computer programmers have great faith in algorithms, and they are surprised to find that the obvious way to delete records from a hash table doesn't work." (Vol. III, p. 533.)
If you want to get this right, Knuth gives an algorithm (pp. 533–4) for deletion in a open hash table with linear probing.
But a simpler approach is to replace the deleted key with a special key meaning "there was a key here but it was deleted" which you later remove when growing or shrinking the table.
Update: To create a unique key that you can be sure won't collide with any key passed by the user, just call object
:
_DELETED = object() # slot had a key but it was deleted
The use of
copy.deepcopy
in thegrow
andshrink
methods is wrong — this will make deep copies of the keys and values in the hash table, which isn't what you want (the caller will expect to be able to retrieve the actual item they stored in the table, not some copy of it) and might invalidate the hashes of the keys. You need a shallow copy instead.It looks as though I can't use
None
as a key. This seems unsatisfactory.
Update: You asked for use cases. Well, consider using a hash table to memoize a function that might take None
as an argument (like functools.lru_cache
). Or using a hash table to count the number of occurrences of each element of an iterable (like collections.Counter
). Also it's nice to be able to document that any hashable object can be used as a key, without having to mention any special cases.
It's quite straightforward to implement. Wherever the code has None
to mean an empty slot, use some other unique object instead:
_EMPTY = object() # slot is empty
The
rehash
method is always called withlen(self.slots)
as the second argument, so there's no need for that argument — the method can just as well compute the size.The
get
andput
methods are redundant: you could just use__getitem__
and__setitem__
.The
grow
andshrink
methods are almost identical. This common code could be shared.del newHashTable
is unnecessary — when the method returns, the last reference tonewHashTable
disappears and it will be collected in any case.There's lots of duplicated code between the
__init__
andclearTable
methods — why not have__init__
callclearTable
?