I was asked to solve the following task:Given two methods for hashing (division and multiplication) and three  methods for collision handling (chaining, open addressing w/ quadratic  probing, and open addressing w/ double hashing), contruct classes for  all six combinations.All classes have a parameter \$m\$, classes using the multiplication  method require a parameter \$A\$, and classes using double hashing  require a second parameter, like \$m_2\$ and/or \$A_2\$.I took this task purely as a design exercise, so the actual implementation of the algorithms is not important to me.After lots of thinking, I produced the following design:from abc import ABCMeta, abstractmethodclass HashTable(object):    Abstract base class for hash table.    Attributes    ----------    m : int        Size of underlying array.    m_2 : int, optional        Parameter for second hash function.    A : float, optional        Parameter for multiplication method.    A_2 : float, optional        Parameter for second multiplication hash function.        __metaclass__ = ABCMeta    def __init__(self, m, m_2=None, A=None, A_2=None):        self._m = m        self._m2 = m_2        self._A = A        self._A2 = A_2        self._table = [None] * m    @abstractmethod    def hash_function(self, key):        Return `h(key)`.        Parameters        ----------        key : int            Value to hash.        Returns        -------        int            Value between 0 and `m` - 1.                pass    @abstractmethod    def insert(self, key):        Insert `key` to hash table.        Parameters        ----------        key            The key to insert.        Returns        -------        int            Number of operations needed.                pass    @abstractmethod    def delete(self, key):        Delete `key` from the hash table.        Parameters        ----------        key            The key to delete.        Returns        -------        int            Number of operations needed.                pass    @abstractmethod    def member(self, key):        Check if `key` is in the hash table.        Parameters        ----------        key            The key to search.        Returns        -------        bool            True if `key` was found, False otherwise.        int            Number of operations needed.                passclass DivisionMethodMixin(object):    def hash_function(self, key):        return key % self._mclass MultiplicationMethodMixin(object):    def hash_function(self, key):        kA = key * self._A        return int(self._m * (kA - int(kA)))class ChainingMixin(object):    def _add_linked_lists(self):        for i in xrange(len(self._table)):            self._table[i] = []        self._add_linked_lists = lambda: None    def insert(self, key):        self._add_linked_lists()        self._table[self.hash_function(key)].append(key)        return 1    def delete(self, key):        self._add_linked_lists()        self._table[self.hash_function(key)].remove(key)        return 1    def member(self, key):        self._add_linked_lists()        return (key in self._table[self.hash_function(key)], 1)class OpenAddressingMixin(object):    _deleted = object()  # Use a special object to mark deleted keys    def insert(self, key):        num_ops = 0        for i in xrange(self._m):            num_ops += 1            hk = self._h(key, i)            if self._table[hk] in (None, self._deleted):                self._table[hk] = key                break        return num_ops    def delete(self, key):        num_ops = 0        for i in xrange(self._m):            num_ops += 1            hk = self._h(key, i)            if self._table[hk] == key:                self._table[hk] = self._deleted                break        return num_ops    def member(self, key):        num_ops = 0        for i in xrange(self._m):            num_ops += 1            hk = self._h(key, i)            if self._table[hk] is None:                return (False, num_ops)            if self._table[hk] == key:                return (True, num_ops)        return (False, num_ops)class OpenAddressingQuadraticProbingMixin(OpenAddressingMixin):    _h = lambda self, key, i: (self.hash_function(key) + i * i) % self._mclass OpenAddressingDoubleHashingMixin(OpenAddressingMixin):    _h = lambda self, key, i: (self.hash_function(key) + i * self.hash_function_2(key)) % self._m2The six required classes are now implemented like so:class DivisionChainingHashTable(DivisionMethodMixin, ChainingMixin, HashTable):    passclass DivisionOAQPHashTable(DivisionMethodMixin, OpenAddressingQuadraticProbingMixin, HashTable):    passclass DivisionOADHHashTable(OpenAddressingDoubleHashingMixin, DivisionMethodMixin, HashTable):    def hash_function_2(self, key):        return key % self._m2class MultiplicationChainingHashTable(MultiplicationMethodMixin, ChainingMixin, HashTable):    passclass MultiplicationOAQPHashTable(MultiplicationMethodMixin, OpenAddressingQuadraticProbingMixin, HashTable):    passclass MultiplicationOADHHashTable(OpenAddressingDoubleHashingMixin, MultiplicationMethodMixin, HashTable):    def hash_function_2(self, key):        kA = key * self._A2        return int(self._m2 * (kA - int(kA)))The code was written with Python 2.7 in mind, and that is why I don't use meta=ABCMeta in HashTable's declaration, and do explicitly inherit from object. Other than that, I think this code should be compatible with Python 3.6, too.This design solves the problem, and I think I managed to avoid code duplication and maximize reuse to the best of my abilities. Still, I believe this code is smelly, and something about it bothers me.I would love to read your opinions on the design, and how would you tackle the problem.
Create six hash tables - 2 hashing schemes x 3 collision handling schemes
python;object oriented;design patterns;mixins