diff --git a/exam-sample_02/Makefile b/exam-sample_02/Makefile
new file mode 100644
index 0000000000000000000000000000000000000000..9cca8cc1f703b3fbe69d9367e0fbdffacc6f60b7
--- /dev/null
+++ b/exam-sample_02/Makefile
@@ -0,0 +1,19 @@
+CFLAGS += -Wall -Wextra -std=c11 -pedantic
+CFLAGS += -g
+CFLAGS += -I./include
+LDFLAGS += -L./lib
+LDLIBS += -lupoalglib -lm
+
+
+.PHONY: all clean
+
+all: test/bst_subtree_count_even test/ht_sepchain_odelete
+
+test/bst_subtree_count_even: test/bst_subtree_count_even.o exam.o
+
+test/ht_sepchain_odelete: test/ht_sepchain_odelete.o exam.o
+
+clean:
+	$(RM) test/bst_subtree_count_even
+	$(RM) test/ht_sepchain_odelete
+	$(RM) *.o test/*.o
diff --git a/exam-sample_02/exam.c b/exam-sample_02/exam.c
new file mode 100644
index 0000000000000000000000000000000000000000..3b30e3245ec941c06ff4edf49409d2f415e6572a
--- /dev/null
+++ b/exam-sample_02/exam.c
@@ -0,0 +1,111 @@
+/* vim: set tabstop=4 expandtab shiftwidth=4 softtabstop=4: */
+
+
+/******************************************************************************/
+/*** NOME:                                                                  ***/
+/*** COGNOME:                                                               ***/
+/*** MATRICOLA:                                                             ***/
+/******************************************************************************/
+
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <upo/bst.h>
+#include <upo/hashtable.h>
+
+
+/**** BEGIN of EXERCISE #1 ****/
+
+size_t upo_bst_subtree_count_skip_2_impl(upo_bst_node_t *node)
+{
+    if (!node) { return 0; }
+    size_t count = 1;
+    if (node->left) {
+        count += upo_bst_subtree_count_skip_2_impl(node->left->left)
+            + upo_bst_subtree_count_skip_2_impl(node->left->right);
+    }
+    if (node->right) {
+        count += upo_bst_subtree_count_skip_2_impl(node->right->left)
+            + upo_bst_subtree_count_skip_2_impl(node->right->right);
+    }
+    return count;
+}
+
+size_t upo_bst_subtree_count_even(const upo_bst_t bst, const void *key)
+{
+    if (!bst) {
+        return 0;
+    }
+    if (upo_bst_is_empty(bst)) {
+        return 0;
+    }
+
+    upo_bst_node_t *node = bst->root;
+    upo_bst_comparator_t cmp = upo_bst_get_comparator(bst);
+    int res;
+    int even = 1;
+    while (node && (res = cmp(key, node->key)) != 0)
+    {
+        node = res < 0 ? node->left : node->right;
+        even = !even;
+    }
+    if (!node)
+    {
+        return 0;
+    }
+    if (even)
+    {
+        return upo_bst_subtree_count_skip_2_impl(node);
+    }
+
+    size_t count = 0;
+    if (node->left) {
+        count += upo_bst_subtree_count_skip_2_impl(node->left);
+    }
+    if (node->right) {
+        count += upo_bst_subtree_count_skip_2_impl(node->right);
+    }
+    return count;
+}
+
+/**** END of EXERCISE #1 ****/
+
+
+/**** BEGIN of EXERCISE #2 ****/
+
+void upo_ht_sepchain_odelete(upo_ht_sepchain_t ht, const void *key, int destroy_data)
+{
+    if (!ht)
+    {
+        perror("Uninitialized hash table");
+        return;
+    }
+    upo_ht_hasher_t h = upo_ht_sepchain_get_hasher(ht);
+    size_t i = h(key, upo_ht_sepchain_capacity(ht));
+    if (ht->slots[i].head)
+    {
+        upo_ht_comparator_t cmp = upo_ht_sepchain_get_comparator(ht);
+        upo_ht_sepchain_list_node_t **before = &ht->slots[i].head;
+        while (*before && cmp((*before)->key, key) != 0)
+        {
+            before = &(*before)->next;
+        }
+        if (*before)
+        {
+            upo_ht_sepchain_list_node_t *next = (*before)->next;
+            if (destroy_data)
+            {
+                free((*before)->key);
+                free((*before)->value);
+            }
+            free(*before);
+            *before = next;
+            --ht->size;
+        }
+    }
+}
+
+/**** END of EXERCISE #2 ****/
diff --git a/exam-sample_02/exam.pdf b/exam-sample_02/exam.pdf
new file mode 100644
index 0000000000000000000000000000000000000000..ee9c491104f983b847614d4bad4b03c06c73c7ec
Binary files /dev/null and b/exam-sample_02/exam.pdf differ
diff --git a/exam-sample_02/include/upo/bst.h b/exam-sample_02/include/upo/bst.h
new file mode 100644
index 0000000000000000000000000000000000000000..53919e27714e1935b704afb845969b8a862935a8
--- /dev/null
+++ b/exam-sample_02/include/upo/bst.h
@@ -0,0 +1,161 @@
+/* vim: set tabstop=4 expandtab shiftwidth=4 softtabstop=4: */
+
+/**
+ * \file upo/bst.h
+ *
+ * \brief The Binary Search Tree (BST) abstract data type.
+ *
+ * Trees are nonlinear containers where data is structured according to
+ * hierarchical organization.
+ * Trees are made of nodes that are connected to each other according to a
+ * parent-child relationship.
+ * Binary Search Trees are Binary Trees where:
+ * - Each node has a key and an associated value (possibly, the
+ *   key itself),
+ * - The key in node v is greater than the keys in all nodes in the
+ *   left subtree of v, and
+ * - The key in node v is less than the keys in all nodes in the right
+ *   subtree of v.
+ * .
+ *
+ * \author Marco Guazzone (marco.guazzone@uniupo.it)
+ *
+ * \copyright 2015 University of Piemonte Orientale, Computer Science Institute
+ *
+ *  This file is part of UPOalglib.
+ *
+ *  UPOalglib is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  UPOalglib is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with UPOalglib.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UPO_BST_H
+#define UPO_BST_H
+
+
+#include <stddef.h>
+
+
+/**
+ * \brief The type for key comparison functions.
+ *
+ * Declares the type for key comparison functions that are used to compare keys
+ * stored in the binary search tree.
+ * A comparison function takes two parameters:
+ * - The first parameter is a pointer to the first key to compare.
+ * - The second parameter is a pointer to the second key to compare.
+ * A comparison function returns a number less than, equal to, or greater than
+ * zero if the first key (first argument) is less than, equal to, or greater
+ * than the second key (second argument), respectively.
+ */
+typedef int (*upo_bst_comparator_t)(const void*, const void*);
+
+/** \brief Alias for binary search tree node type. */
+typedef struct upo_bst_node_s upo_bst_node_t;
+
+/** \brief Type for nodes of a binary search tree. */
+struct upo_bst_node_s
+{
+    void* key; /**< Pointer to user-provided key. */
+    void* value; /**< Pointer to user-provided value. */
+    upo_bst_node_t* left; /**< Pointer to the left child node. */
+    upo_bst_node_t* right; /**< Pointer to the right child node. */
+};
+
+/** \brief Defines a binary tree. */
+struct upo_bst_s
+{
+    upo_bst_node_t* root; /**< The root of the binary tree. */
+    upo_bst_comparator_t key_cmp; /**< Pointer to the key comparison function. */
+};
+
+/** \brief Declares the Binary Search Tree type. */
+typedef struct upo_bst_s* upo_bst_t;
+
+
+/**
+ * \brief Creates a new empty binary search tree.
+ *
+ * \param key_cmp A pointer to the function used to compare keys.
+ * \return An empty binary search tree.
+ *
+ * Worst-case complexity: constant, `O(1)`.
+ */
+upo_bst_t upo_bst_create(upo_bst_comparator_t key_cmp);
+
+/**
+ * \brief Destroys the given binary search tree together with data stored on it.
+ *
+ * \param tree The binary search tree to destroy.
+ * \param destroy_data Tells whether the previously allocated memory for keys
+ *  and values stored in this binary search tree must be freed (value `1`) or
+ *  not (value `0`).
+ *
+ * Memory deallocation (if requested) is performed by means of the `free()`
+ * standard C function.
+ *
+ * Worst-case complexity: linear in the number `n` of elements, `O(n)`.
+ */
+void upo_bst_destroy(upo_bst_t tree, int destroy_data);
+
+/**
+ * \brief Removes all elements from the given binary search tree and destroys
+ *  all data stored on it.
+ *
+ * \param tree The binary search tree to clear.
+ * \param destroy_data Tells whether the previously allocated memory for keys
+ *  and values stored in this binary search tree must be freed (value `1`) or
+ *  not (value `0`).
+ *
+ * Memory deallocation (if requested) is performed by means of the `free()`
+ * standard C function.
+ *
+ * Worst-case complexity: linear in the number `n` of elements, `O(n)`.
+ */
+void upo_bst_clear(upo_bst_t tree, int destroy_data);
+
+/**
+ * \brief Returns the comparison function stored in the binary search tree.
+ *
+ * \param tree The binary search tree.
+ * \return The comparison function.
+ */
+upo_bst_comparator_t upo_bst_get_comparator(const upo_bst_t tree);
+
+/**
+ * \brief Tells if the given binary search tree is empty.
+ *
+ * \param tree The binary search tree.
+ * \return `1` if the binary search tree is empty or `0` otherwise.
+ *
+ * A binary search tree is empty if it doesn't contain any node.
+ *
+ * Worst-case complexity: constant, `O(1)`.
+ */
+int upo_bst_is_empty(const upo_bst_t tree);
+
+/**
+ * \brief Counts the number of nodes of the subtree rooted at the node of the
+ *  binary search tree containing the given key and that are located at an
+ *  even depth.
+ *
+ * \param tree The binary search tree.
+ * \param key The key for which this function must return the number of nodes of
+ *  the subtree rooted at the associated node and located at an even depth.
+ * \return The number of nodes of the subtree rooted at the node containing the
+ *  given key and located at an even depth, or `0` if the tree is empty or if
+ *  the given key does not belong to the tree.
+ */
+size_t upo_bst_subtree_count_even(const upo_bst_t tree, const void* key);
+
+
+#endif /* UPO_BST_H */
diff --git a/exam-sample_02/include/upo/hashtable.h b/exam-sample_02/include/upo/hashtable.h
new file mode 100644
index 0000000000000000000000000000000000000000..0d95aa44b138cad424d3138ffe3c39d323c6ae5e
--- /dev/null
+++ b/exam-sample_02/include/upo/hashtable.h
@@ -0,0 +1,353 @@
+/* vim: set tabstop=4 expandtab shiftwidth=4 softtabstop=4: */
+
+/**
+ * \file upo/hashtable.h
+ *
+ * \brief The Hash Table (HT) abstract data type.
+ *
+ * Hash Tables are containers composed of unique keys (containing at most one of
+ * each key value) that associates values of another type with the keys.
+ *
+ * \author Marco Guazzone (marco.guazzone@uniupo.it)
+ *
+ * \copyright 2015 University of Piemonte Orientale, Computer Science Institute
+ *
+ *  This file is part of UPOalglib.
+ *
+ *  UPOalglib is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  UPOalglib is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with UPOalglib.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UPO_HASHTABLE_H
+#define UPO_HASHTABLE_H
+
+
+#include <stddef.h>
+
+
+/*** BEGIN of COMMON TYPES ***/
+
+
+/** \brief The type for hash functions.
+ *
+ * Declares the type for key hash functions that are used to map key
+ * space into the index space to use for indexing the hash table.
+ * A hash function takes two parameters:
+ * - The first parameter is a pointer to the key to hash.
+ * - The second parameter is the capacity of the hash table.
+ * A hash function returns a nonnegative number which represents a position
+ * (index) in the hash table.
+ */
+typedef size_t (*upo_ht_hasher_t)(const void*, size_t);
+
+/**
+ * \brief The type for key comparison functions.
+ *
+ * Declares the type for key comparison functions that are used to compare keys
+ * stored in the hash table.
+ * A comparison function takes two parameters:
+ * - The first parameter is a pointer to the first key to compare.
+ * - The second parameter is a pointer to the second key to compare.
+ * A comparison function returns a number less than, equal to, or greater than
+ * zero if the first key (first argument) is less than, equal to, or greater
+ * than the second key (second argument), respectively.
+ */
+typedef int (*upo_ht_comparator_t)(const void*, const void*);
+
+
+/*** END of COMMON TYPES ***/
+
+
+/*** BEGIN of HASH TABLE with SEPARATE CHAINING ***/
+
+
+/** \brief Default capacity of hash tables with separate chaining. */
+#define UPO_HT_SEPCHAIN_DEFAULT_CAPACITY 997U
+
+
+/** \brief Type for nodes of the list of collisions. */
+struct upo_ht_sepchain_list_node_s
+{
+    void *key; /**< Pointer to the user-provided key. */
+    void *value; /**< Pointer to the value associated to the key. */
+    struct upo_ht_sepchain_list_node_s *next; /**< Pointer to the next node in the list. */
+};
+/** \brief Alias for the type for nodes of the list of collisions. */
+typedef struct upo_ht_sepchain_list_node_s upo_ht_sepchain_list_node_t;
+
+/** \brief Type for slots of hash tables with separate chaining. */
+struct upo_ht_sepchain_slot_s
+{
+    upo_ht_sepchain_list_node_t *head; /**< Pointer to the head of the list of collisions. */
+};
+/** \brief Alias for the type for slots of hash tables with separate chaining. */
+typedef struct upo_ht_sepchain_slot_s upo_ht_sepchain_slot_t;
+
+/** \brief Type for hash tables with separate chaining. */
+struct upo_ht_sepchain_s
+{
+    upo_ht_sepchain_slot_t *slots; /**< The hash table as array of slots. */
+    size_t capacity; /**< The capacity of the hash table. */
+    size_t size; /**< The number of elements stored in the hash table. */
+    upo_ht_hasher_t key_hash; /**< The key hash function. */
+    upo_ht_comparator_t key_cmp; /**< The key comparison function. */
+};
+
+/** \brief Type for hash tables with separate chaining. */
+typedef struct upo_ht_sepchain_s* upo_ht_sepchain_t;
+
+
+/**
+ * \brief Creates a new empty hash table.
+ *
+ * \param m The initial capacity of the hash table.
+ * \param key_hash A pointer to the function used to hash keys.
+ * \param key_cmp A pointer to the function used to compare keys.
+ * \return An empty hash table.
+ *
+ * Worst-case complexity: linear in the capacity `m` of the hash table, `O(m)`.
+ */
+upo_ht_sepchain_t upo_ht_sepchain_create(size_t m, upo_ht_hasher_t key_hash, upo_ht_comparator_t key_cmp);
+
+/**
+ * \brief Destroys the given hash table.
+ *
+ * \param ht The hash table to destroy.
+ * \param destroy_data Tells whether the previously allocated memory for data
+ *  stored in the hash table must be freed (value `1`) or not (value `0`).
+ *
+ * Memory deallocation (if requested) is performed by means of the `free()`
+ * standard C function.
+ *
+ * Worst-case complexity: linear in the capacity `m` of the hash table, `O(m)`.
+ */
+void upo_ht_sepchain_destroy(upo_ht_sepchain_t ht, int destroy_data);
+
+/**
+ * \brief Removes all key-value pairs from the given hash table.
+ *
+ * \param ht The hash table to clear.
+ * \param destroy_data Tells whether the previously allocated memory for data
+ *  stored in the hash table must be freed (value `1`) or not (value `0`).
+ *
+ * Memory deallocation (if requested) is performed by means of the `free()`
+ * standard C function.
+ *
+ * Worst-case complexity: linear in the capacity `m` of the hash table, `O(m)`.
+ */
+void upo_ht_sepchain_clear(upo_ht_sepchain_t ht, int destroy_data);
+
+/**
+ * \brief Tells if the given hash table is empty.
+ *
+ * \param ht The hash table.
+ * \return `1` if the hash table is empty or `0` otherwise.
+ *
+ * A hash table is empty if it doesn't contain any node.
+ *
+ * Worst-case complexity: constant, `O(1)`.
+ */
+int upo_ht_sepchain_is_empty(const upo_ht_sepchain_t ht);
+
+/**
+ * \brief Returns the capacity of the hash table.
+ *
+ * \param ht The hash table.
+ * \return The total number of slots of the hash tables.
+ *
+ * Worst-case complexity: constant, `O(1)`.
+ */
+size_t upo_ht_sepchain_capacity(const upo_ht_sepchain_t ht);
+
+/**
+ * \brief Returns the size of the hash table.
+ *
+ * \param ht The hash table.
+ * \return The number of keys stored in the hash tables.
+ *
+ * Worst-case complexity: linear in the capacity `m` of the hash table, `O(m)`.
+ */
+size_t upo_ht_sepchain_size(const upo_ht_sepchain_t ht);
+
+/**
+ * \brief Returns the load factor of the hash table.
+ *
+ * \param ht The hash table.
+ * \return The load factor which is defined as the ratio between the number of
+ *  stored keys (i.e., the keys) and the number of slots (i.e., the capacity).
+ *
+ * Worst-case complexity: constant, `O(1)`.
+ */
+double upo_ht_sepchain_load_factor(const upo_ht_sepchain_t ht);
+
+/**
+ * \brief Returns the key comparator function.
+ *
+ * \param ht The hash table.
+ * \return The key comparator function.
+ */
+upo_ht_comparator_t upo_ht_sepchain_get_comparator(const upo_ht_sepchain_t ht);
+
+/**
+ * \brief Returns the key hasher function.
+ *
+ * \param ht The hash table.
+ * \return The key hasher function.
+ */
+upo_ht_hasher_t upo_ht_sepchain_get_hasher(const upo_ht_sepchain_t ht);
+
+/**
+ * \brief Removes the value identified by the provided key in the given
+ *  hash table.
+ *
+ * \param ht The hash table.
+ * \param key The key.
+ * \param destroy_data Tells whether the previously allocated memory for data,
+ *  that is to be removed, must be freed (value `1`) or not (value `0`).
+ *
+ * Memory deallocation (if requested) is performed by means of the `free()`
+ * standard C function.
+ *
+ * Worst-case complexity: linear in the number `n` of elements, `O(n)`.
+ */
+void upo_ht_sepchain_odelete(upo_ht_sepchain_t ht, const void *key, int destroy_data);
+
+
+/*** END of HASH TABLE with SEPARATE CHAINING ***/
+
+
+/*** BEGIN of HASH FUNCTIONS ***/
+
+
+/**
+ * \brief Hash function for integers that uses the division method.
+ *
+ * \param x The integer to be hashed.
+ * \param m The number of possible hash values.
+ * \return The hash value which is an integer number in \f$\{0,\ldots,m-1\}\f$.
+ *
+ * The division method is defined as:
+ * \f[
+ *   h(x) = x \bmod m
+ * \f]
+ * where:
+ * - \f$y \bmod z\f$ means the remainder of the division \f$y / z\f$.
+ * .
+ */
+size_t upo_ht_hash_int_div(const void *x, size_t m);
+
+/**
+ * \brief Hash function for integers that uses the multiplication method.
+ *
+ * \param x The integer to be hashed.
+ * \param a A multiplicative constant in the range of (0,1).
+ * \param m The number of possible hash values.
+ * \return The hash value which is an integer number in \f$\{0,\ldots,m-1\}\f$.
+ *
+ * The multiplication method is defined as:
+ * \f[
+ *   h(x) = m \lfloor a x \bmod 1 \rfloor
+ * \f]
+ * where:
+ * - \f$\lfloor y \rfloor\f$ means the floor of \f$y\f$, that is the greatest
+ *   integer less than or equal to \f$y\f$.
+ * - \f$y \bmod z\f$ means the remainder of the division \f$y / z\f$.
+ * - \f$y \bmod 1\f$ is the fractional part of \f$y\f$, that is
+ *   the result of \f$y - \lfloor y \rfloor\f$.
+ * .
+ */
+size_t upo_ht_hash_int_mult(const void *x, double a, size_t m);
+
+/**
+ * \brief Hash function for integers that uses the multiplication method and
+ *  the value of the multiplicative constant as proposed by Knuth.
+ *
+ * \param x The integer to be hashed.
+ * \param m The number of possible hash values.
+ * \return The hash value which is an integer number in \f$\{0,\ldots,m-1\}\f$.
+ */
+size_t upo_ht_hash_int_mult_knuth(const void *x, size_t m);
+
+/**
+ * \brief Hash function for strings.
+ *
+ * \param s The string to be hashed.
+ * \param h0 The initial value for the hash value that is usually chosen
+ *  randomly from a universal family mapping integer domain
+ *  \f$\{0,\ldots,p-1\} \mapsto \{0,\ldots,m-1\}\f$.
+ * \param a A multiplicative factor such that \f$a \in \{0,\ldots,p-1\}\f$ which
+ *  is usually uniformly random.
+ * \param m The number of possible hash values.
+ * \return The hash value which is an integer number in \f$\{0,\ldots,m-1\}\f$.
+ *
+ * The implemented hash function is the following:
+ * \f[
+ *     h(k) = h_0 \left( \big(\sum_{i=0}^{\ell-1} k_i\cdot a^i \big) \bmod m \right), 
+ * \f]
+ * where:
+ * - \f$k=(k_0,\ldots,k_{\ell-1})\f$ is an array of characters of size \$\ell\$
+ */
+size_t upo_ht_hash_str(const void *s, size_t h0, size_t a, size_t m);
+
+/**
+ * \brief The Bernstein's hash function `djb2`.
+ *
+ * This hash function was first reported by Daniel J. Bernstein in comp.lang.c on 1991.
+ *
+ * See:
+ * - https://groups.google.com/forum/#!msg/comp.lang.c/lSKWXiuNOAk/zstZ3SRhCjgJ
+ * - http://www.cse.yorku.ca/~oz/hash.html
+ * .
+ */
+size_t upo_ht_hash_str_djb2(const void *s, size_t m);
+
+/**
+ * \brief The Bernstein's hash function `djb2a`.
+ *
+ * This hash function is an alternative to the 'djb2' hash function that was
+ * first reported by Daniel J. Bernstein in comp.lang.c on 1991.
+ * Instead of using the sum operator it uses the XOR operator.
+ *
+ * See:
+ * - https://groups.google.com/forum/#!msg/comp.lang.c/lSKWXiuNOAk/zstZ3SRhCjgJ
+ * - http://www.cse.yorku.ca/~oz/hash.html
+ * .
+ */
+size_t upo_ht_hash_str_djb2a(const void *s, size_t m);
+
+/**
+ * \brief The Java's hash function `hashCode`.
+ *
+ * See:
+ * - https://docs.oracle.com/javase/8/docs/api/java/lang/String.html#hashCode--
+ * .
+ */
+size_t upo_ht_hash_str_java(const void *s, size_t m);
+
+/**
+ * \brief The Kernighan and Ritchie's hash function proposed in the second
+ *  edition of their C book.
+ */
+size_t upo_ht_hash_str_kr2e(const void *s, size_t m);
+
+/**
+ * \brief The SGI STL's hash function `hash_fun`.
+ *
+ */
+size_t upo_ht_hash_str_sgistl(const void *s, size_t m);
+
+
+/*** END of HASH FUNCTIONS ***/
+
+
+#endif /* UPO_HASHTABLE_H */
diff --git a/exam-sample_02/include/upo/test.h b/exam-sample_02/include/upo/test.h
new file mode 100644
index 0000000000000000000000000000000000000000..f373ab96266cd4d23f5530e2a916d187cc26039f
--- /dev/null
+++ b/exam-sample_02/include/upo/test.h
@@ -0,0 +1,43 @@
+/* vim: set tabstop=4 expandtab shiftwidth=4 softtabstop=4: */
+
+/**
+ * \file upo/test/utility.h
+ *
+ * \brief Collection of useful functions for testing.
+ *
+ * \author Marco Guazzone (marco.guazzone@uniupo.it)
+ *
+ * \copyright 2015 University of Piemonte Orientale, Computer Science Institute
+ *
+ *  This file is part of UPOalglib.
+ *
+ *  UPOalglib is free software: you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation, either version 3 of the License, or
+ *  (at your option) any later version.
+ *
+ *  UPOalglib is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with UPOalglib.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#ifndef UPO_TEST_H
+#define UPO_TEST_H
+
+
+int upo_test_int_cmp(const void* a, const void* b);
+
+int upo_test_str_cmp(const void* a, const void* b);
+
+char* upo_test_int_to_string(const void *pval);
+
+char* upo_test_str_to_string(const void *pval);
+
+char* upo_test_array_to_string(const void *base, size_t nelem, size_t elem_sz, char* (*to_string)(const void*));
+
+
+#endif /* UPO_TEST_H */
diff --git a/exam-sample_02/lib/libupoalglib.a b/exam-sample_02/lib/libupoalglib.a
new file mode 100644
index 0000000000000000000000000000000000000000..44a6ea57f9cd514063d4c67ef153a4bcd9e0ef85
Binary files /dev/null and b/exam-sample_02/lib/libupoalglib.a differ
diff --git a/exam-sample_02/lib/libupoalglib.a-fedora b/exam-sample_02/lib/libupoalglib.a-fedora
new file mode 100644
index 0000000000000000000000000000000000000000..44a6ea57f9cd514063d4c67ef153a4bcd9e0ef85
Binary files /dev/null and b/exam-sample_02/lib/libupoalglib.a-fedora differ
diff --git a/exam-sample_02/lib/libupoalglib.a-ubuntu b/exam-sample_02/lib/libupoalglib.a-ubuntu
new file mode 100644
index 0000000000000000000000000000000000000000..7855963573abce868e6c5349e28c73726e6d98be
Binary files /dev/null and b/exam-sample_02/lib/libupoalglib.a-ubuntu differ
diff --git a/exam-sample_02/test/bst_subtree_count_even.c b/exam-sample_02/test/bst_subtree_count_even.c
new file mode 100644
index 0000000000000000000000000000000000000000..4a4e58f287e28bc9f34a9b03dc9cc11a34b7ef3a
--- /dev/null
+++ b/exam-sample_02/test/bst_subtree_count_even.c
@@ -0,0 +1,361 @@
+/* vim: set tabstop=4 expandtab shiftwidth=4 softtabstop=4: */
+
+/*
+ * Copyright 2016 University of Piemonte Orientale, Computer Science Institute
+ *
+ * This file is part of UPOalglib.
+ *
+ * UPOalglib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * UPOalglib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with UPOalglib.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <limits.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <upo/bst.h>
+#include <upo/test.h>
+
+
+#define TEST_OK 1
+#define TEST_FAIL 0
+
+
+static int test_bst1();
+static int test_bst2();
+static int test_bst3();
+static int test_bst4();
+static int test_bst5();
+static int test_empty_bst();
+static int test_int_bst(int *keys, int *values, size_t n, int *no_keys, size_t m);
+static int test_str_bst(char **keys, int *values, size_t n, char **no_keys, size_t m);
+static int test_bst(void *keys, size_t key_sz, void *values, size_t value_sz, size_t n, void *no_keys, size_t m, upo_bst_comparator_t key_cmp, char* (*key_to_string)(const void*), char* (*value_to_string)(const void*));
+extern size_t upo_bst_check(const upo_bst_t x, const void *y);
+extern void upo_bst_dump(const upo_bst_t, FILE *, char* (*)(const void*), int, char* (*)(const void*), int);
+extern void* upo_bst_put(upo_bst_t, void*, void*);
+
+
+int main()
+{
+    size_t n = 5;
+    size_t i = 0;
+    int ret = 0;
+
+    printf("*** [DISCLAIMER] ***************************************************************\n");
+    printf("The following tests are provided as is, to allow you to quickly test your code.\n");
+    printf("However, passing these tests is a necessary but not sufficient condition,\n");
+    printf("meaning that they do not guarantee that your code is correct.\n");
+    printf("In fact, your code may be wrong even it passes all these tests\n");
+    printf("********************************************************************************\n\n");
+
+    for (i = 1; i <= n; ++i)
+    {
+        switch (i)
+        {
+            case 1:
+                ret = test_bst1();
+                break;
+            case 2:
+                ret = test_bst2();
+                break;
+            case 3:
+                ret = test_bst3();
+                break;
+            case 4:
+                ret = test_bst4();
+                break;
+            case 5:
+                ret = test_bst5();
+                break;
+            default:
+                fprintf(stderr, "ERROR: Unexpected test case number");
+                abort();
+        }
+        printf("Test BST #%lu => %s\n", i, (ret == TEST_OK) ? "[OK]" : "[FAIL]");
+    }
+
+    ret = test_empty_bst();
+    printf("Test Empty BST => %s\n", (ret == TEST_OK) ? "[OK]" : "[FAIL]");
+
+    return 0;
+}
+
+
+int test_bst1()
+{
+    /*
+     * BST:
+     *       8
+     *     /   \
+     *    3     10
+     *   / \     \
+     *  1   6     14
+     *     / \   /
+     *    4   7 13
+     */
+
+    int keys[] = {8, 3, 1, 6, 4, 7, 10, 14, 13};
+    int values[] = {0, 1, 2, 3, 4, 5, 6, 7, 8};
+    int no_keys[] = {0, 2, 5, 9, 11, 16};
+    size_t n = sizeof keys/sizeof keys[0];
+    size_t m = sizeof no_keys/sizeof no_keys[0];
+
+    assert( n == sizeof keys/sizeof keys[0] );
+    assert( n == sizeof values/sizeof values[0] );
+    assert( m == sizeof no_keys/sizeof no_keys[0] );
+
+    return test_int_bst(keys, values, n, no_keys, m);
+}
+
+int test_bst2()
+{
+    /*
+     * BST:
+     *      11
+     *     / \
+     *    3   13
+     *   / \   \
+     *  1   9   19
+     *     /   /
+     *    7   15
+     *   /     \
+     *  5       17
+     */
+
+    int keys[] = {11, 3, 1, 9, 7, 5, 13, 19, 15, 17};
+    int values[] = {-11, -3, -1, -9, -7, -5, -13, -19, -15, -17};
+    int no_keys[] = {0, 6, 10, 12, 14, 20};
+    size_t n = sizeof keys/sizeof keys[0];
+    size_t m = sizeof no_keys/sizeof no_keys[0];
+
+    assert( n == sizeof keys/sizeof keys[0] );
+    assert( n == sizeof values/sizeof values[0] );
+    assert( m == sizeof no_keys/sizeof no_keys[0] );
+
+    return test_int_bst(keys, values, n, no_keys, m);
+}
+
+int test_bst3()
+{
+    /*
+     * BST:
+     *            11
+     *           /
+     *          9
+     *         /
+     *        7
+     *       /
+     *      5
+     *     /
+     *    3
+     *   /
+     *  1
+     */
+
+    int keys[] = {11, 9, 7, 5, 3, 1};
+    int values[] = {0, 1, 2, 3, 4, 5};
+    int no_keys[] = {0, 2, 4, 6, 8, 10, 12};
+    size_t n = sizeof keys/sizeof keys[0];
+    size_t m = sizeof no_keys/sizeof no_keys[0];
+
+    assert( n == sizeof keys/sizeof keys[0] );
+    assert( n == sizeof values/sizeof values[0] );
+    assert( m == sizeof no_keys/sizeof no_keys[0] );
+
+    return test_int_bst(keys, values, n, no_keys, m);
+}
+
+int test_bst4()
+{
+    /*
+     * BST:
+     * 1
+     *  \
+     *   3
+     *    \
+     *     5
+     *      \
+     *       7
+     *        \
+     *         9
+     *          \
+     *           11
+     */
+
+    int keys[] = {1, 3, 5, 7, 9, 11};
+    int values[] = {0, 1, 2, 3, 4, 5};
+    int no_keys[] = {0, 2, 4, 6, 8, 10, 12};
+    size_t n = sizeof keys/sizeof keys[0];
+    size_t m = sizeof no_keys/sizeof no_keys[0];
+
+    assert( n == sizeof keys/sizeof keys[0] );
+    assert( n == sizeof values/sizeof values[0] );
+    assert( m == sizeof no_keys/sizeof no_keys[0] );
+
+    return test_int_bst(keys, values, n, no_keys, m);
+}
+
+int test_bst5()
+{
+    /*
+     * BST:
+     *         "aae"
+     *          / \
+     *      "aac" "aaj"
+     *       / \      \
+     *   "aaa" "aad" "aak"
+     */
+
+    char *keys[] = {"aae", "aaj", "aac", "aad", "aaa", "aak"};
+    int values[] = {-5, -10, -3, -4, -1, -11};
+    char *no_keys[] = {"AAA", "xyz", "aaf"};
+    size_t n = sizeof keys/sizeof keys[0];
+    size_t m = sizeof no_keys/sizeof no_keys[0];
+
+    assert( n == sizeof keys/sizeof keys[0] );
+    assert( n == sizeof values/sizeof values[0] );
+    assert( m == sizeof no_keys/sizeof no_keys[0] );
+
+    return test_str_bst(keys, values, n, no_keys, m);
+}
+
+int test_empty_bst()
+{
+    return test_int_bst(NULL, NULL, 0, NULL, 0);
+}
+
+int test_int_bst(int *keys, int *values, size_t n, int *no_keys, size_t m)
+{
+    return test_bst(keys, sizeof keys[0], values, sizeof values[0], n, no_keys, m, upo_test_int_cmp, upo_test_int_to_string, upo_test_int_to_string);
+}
+
+int test_str_bst(char **keys, int *values, size_t n, char **no_keys, size_t m)
+{
+    return test_bst(keys, sizeof keys[0], values, sizeof values[0], n, no_keys, m, upo_test_str_cmp, upo_test_str_to_string, upo_test_int_to_string);
+}
+
+int test_bst(void *keys, size_t key_sz, void *values, size_t value_sz, size_t n, void *no_keys, size_t m, upo_bst_comparator_t key_cmp, char* (*key_to_string)(const void*), char* (*value_to_string)(const void*))
+{
+    upo_bst_t bst = NULL;
+    unsigned char *pck = keys;
+    unsigned char *pcv = values;
+    unsigned char *pcnk = no_keys;
+    int ret = TEST_OK;
+
+    /* Creates BST */
+
+    bst = upo_bst_create(key_cmp);
+
+    assert( bst != NULL );
+
+    if (n > 0)
+    {
+        // Non-empty BST
+
+        for (size_t i = 0; i < n; ++i)
+        {
+            upo_bst_put(bst, pck + i*key_sz, pcv + i*value_sz);
+        }
+
+        // Keys
+        for (size_t i = 0; i < n && ret == TEST_OK; ++i)
+        {
+            void *pkey = NULL;
+            size_t count = 0;
+            size_t count_check = 0;
+
+            pkey = malloc(key_sz);
+            if (pkey == NULL)
+            {
+                perror("[ERROR] Unable to allocate memory for key");
+                abort();
+            }
+
+            memcpy(pkey, pck + i*key_sz, key_sz);
+
+            count = upo_bst_subtree_count_even(bst, pkey);
+
+            count_check = upo_bst_check(bst, pkey);
+
+            if (count != count_check)
+            {
+                char *key_str = key_to_string(pkey);
+                fprintf(stderr, "[file: %s, line: %d] ERROR: Key: '%s' -> Expected count %lu, got %lu.\n", __FILE__, __LINE__, key_str, count_check, count);
+                free(key_str);
+                fprintf(stderr, "[file: %s, line: %d] <BST>:\n", __FILE__, __LINE__);
+                upo_bst_dump(bst, stderr, key_to_string, 1, value_to_string, 1);
+                fprintf(stderr, "[file: %s, line: %d] </BST>:\n", __FILE__, __LINE__);
+                ret = TEST_FAIL;
+            }
+
+            free(pkey);
+        }
+
+        // No keys
+        for (size_t i = 0; i < m && ret == TEST_OK; ++i)
+        {
+            void *pkey = NULL;
+            size_t count = 0;
+            size_t count_check = 0;
+
+            pkey = malloc(key_sz);
+            if (pkey == NULL)
+            {
+                perror("[ERROR] Unable to allocate memory for key");
+                abort();
+            }
+
+            memcpy(pkey, pcnk + i*key_sz, key_sz);
+
+            count = upo_bst_subtree_count_even(bst, pkey);
+
+            count_check = upo_bst_check(bst, pkey);
+
+            if (count != count_check)
+            {
+                char *key_str = key_to_string(pkey);
+                fprintf(stderr, "[file: %s, line: %d] ERROR: Key: '%s' -> Expected count '%lu', got %lu.\n", __FILE__, __LINE__, key_str, count_check, count);
+                free(key_str);
+                fprintf(stderr, "[file: %s, line: %d] <BST>:\n", __FILE__, __LINE__);
+                upo_bst_dump(bst, stderr, key_to_string, 1, value_to_string, 1);
+                fprintf(stderr, "[file: %s, line: %d] </BST>:\n", __FILE__, __LINE__);
+                ret = TEST_FAIL;
+            }
+
+            free(pkey);
+        }
+    }
+    else
+    {
+        // Empty BST
+
+        int dummy_key = 0;
+        size_t count = 0;
+
+        count = upo_bst_subtree_count_even(bst, &dummy_key);
+
+        if (count != 0)
+        {
+            fprintf(stderr, "[file: %s, line: %d] ERROR: Empty BST -> Expected count 0, got %lu.\n", __FILE__, __LINE__, count);
+            ret = TEST_FAIL;
+        }
+    }
+
+    /* Destroy */
+ 
+    upo_bst_destroy(bst, 0);
+
+    return ret;
+}
diff --git a/exam-sample_02/test/ht_sepchain_odelete.c b/exam-sample_02/test/ht_sepchain_odelete.c
new file mode 100644
index 0000000000000000000000000000000000000000..d3762046627ffe19c12efb028c876c0bc267055f
--- /dev/null
+++ b/exam-sample_02/test/ht_sepchain_odelete.c
@@ -0,0 +1,506 @@
+/* vim: set tabstop=4 expandtab shiftwidth=4 softtabstop=4: */
+
+/*
+ * Copyright 2015 University of Piemonte Orientale, Computer Science Institute
+ *
+ * This file is part of UPOalglib.
+ *
+ * UPOalglib is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * UPOalglib is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with UPOalglib.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <assert.h>
+#include <stddef.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <upo/hashtable.h>
+#include <upo/test.h>
+
+
+#define TEST_OK 1
+#define TEST_FAIL 0
+
+
+static int test_ht1();
+static int test_ht2();
+static int test_ht3();
+static int test_ht4();
+static int test_ht5();
+static int test_ht6();
+static int test_empty_ht();
+static int test_int_ht(int *keys, int *values, size_t n, int *no_keys, size_t m);
+static int test_str_ht(char **keys, int *values, size_t n, char **no_keys, size_t m);
+static int test_ht(void *keys, size_t key_sz, void *values, size_t value_sz, size_t n, void *no_keys, size_t m, upo_ht_hasher_t key_hasher, upo_ht_comparator_t key_cmp, char* (*key_to_string)(const void*), char* (*value_to_string)(const void*));
+extern upo_ht_sepchain_list_node_t* upo_ht_sepchain_check_order(const upo_ht_sepchain_t ht);
+extern int upo_ht_sepchain_contains(const upo_ht_sepchain_t ht, const void *key);
+extern void upo_ht_sepchain_dump(const upo_ht_sepchain_t, FILE *, char* (*)(const void*), int, char* (*)(const void*), int);
+extern void* upo_ht_sepchain_put(upo_ht_sepchain_t ht, void *key, void *value);
+
+
+int main()
+{
+    size_t n = 6;
+    size_t i = 0;
+    int ret = 0;
+
+    printf("*** [DISCLAIMER] ***************************************************************\n");
+    printf("The following tests are provided as is, to allow you to quickly test your code.\n");
+    printf("However, passing these tests is a necessary but not sufficient condition,\n");
+    printf("meaning that they do not guarantee that your code is correct.\n");
+    printf("In fact, your code may be wrong even it passes all these tests\n");
+    printf("********************************************************************************\n\n");
+
+    for (i = 1; i <= n; ++i)
+    {
+        switch (i)
+        {
+            case 1:
+                ret = test_ht1();
+                break;
+            case 2:
+                ret = test_ht2();
+                break;
+            case 3:
+                ret = test_ht3();
+                break;
+            case 4:
+                ret = test_ht4();
+                break;
+            case 5:
+                ret = test_ht5();
+                break;
+            case 6:
+                ret = test_ht6();
+                break;
+            default:
+                fprintf(stderr, "ERROR: Unexpected test case number");
+                abort();
+        }
+        printf("Test HT #%zu => %s\n", i, (ret == TEST_OK) ? "[OK]" : "[FAIL]");
+    }
+
+    ret = test_empty_ht();
+    printf("Test Empty HT => %s\n", (ret == TEST_OK) ? "[OK]" : "[FAIL]");
+
+    return 0;
+}
+
+
+int test_ht1()
+{
+    int keys[] = {0,1,2,3,4,5,6,7,8,9,1000,2000,3000,4000,5000,6000,7000,8000,9000};
+    int values[] = {0,1,2,3,4,5,6,7,8,9,1000,2000,3000,4000,5000,6000,7000,8000,9000};
+    int no_keys[] = {10,11,12,13,14,15,16,19,1010,2010,3010,4010,5010};
+    size_t n = sizeof keys/sizeof keys[0];
+    size_t m = sizeof no_keys/sizeof no_keys[0];
+
+    assert( n == (sizeof keys/sizeof keys[0]) );
+    assert( n == (sizeof values/sizeof values[0]) );
+    assert( m == (sizeof no_keys/sizeof no_keys[0]) );
+
+    return test_int_ht(keys, values, n, no_keys, m);
+}
+
+int test_ht2()
+{
+    int keys[] = {0,10,20,30,40,50,60,70,80,90,100,200,300,400,500,600,700,800,900};
+    int values[] = {0,1,2,3,4,5,6,7,8,9,1000,2000,3000,4000,5000,6000,7000,8000,9000};
+    int no_keys[] = {1,11,21,31,41,51,61,71,81,91,101,201,301,401,501,601,701,801,901,1001,1101,1201,1301,1401,1501};
+    size_t n = sizeof keys/sizeof keys[0];
+    size_t m = sizeof no_keys/sizeof no_keys[0];
+
+    assert( n == (sizeof keys/sizeof keys[0]) );
+    assert( n == (sizeof values/sizeof values[0]) );
+    assert( m == (sizeof no_keys/sizeof no_keys[0]) );
+
+    return test_int_ht(keys, values, n, no_keys, m);
+}
+
+int test_ht3()
+{
+    int keys[] = {0,1,2,3,4,10,11,12,13,14,1000,2000,3000,4000,5000,6000,7000,8000,9000};
+    int values[] = {0,1,2,3,4,5,6,7,8,9,1000,2000,3000,4000,5000,6000,7000,8000,9000};
+    int no_keys[] = {5,6,7,8,9,15,16,17,18,19,1500,1600,1700,1800,1900};
+    size_t n = sizeof keys/sizeof keys[0];
+    size_t m = sizeof no_keys/sizeof no_keys[0];
+
+    assert( n == (sizeof keys/sizeof keys[0]) );
+    assert( n == (sizeof values/sizeof values[0]) );
+    assert( m == (sizeof no_keys/sizeof no_keys[0]) );
+
+    return test_int_ht(keys, values, n, no_keys, m);
+}
+
+int test_ht4()
+{
+    char *keys[] = {"AA","AB","AQ","Aa","Aq","Ba","Ca","Da","Ea","Fa","AAA", "Ab"};
+    int values[] = {0,1,2,3,4,5,6,7,8,9,10,11};
+    char *no_keys[] = {"xAA","xAB","xAQ","xAa","xAq","xBa","xCa","xDa","xEa","xFa","xAAA", "xAb", "yAA", "yAB"};
+    size_t n = sizeof keys/sizeof keys[0];
+    size_t m = sizeof no_keys/sizeof no_keys[0];
+
+    assert( n == (sizeof keys/sizeof keys[0]) );
+    assert( n == (sizeof values/sizeof values[0]) );
+    assert( m == (sizeof no_keys/sizeof no_keys[0]) );
+
+    return test_str_ht(keys, values, n, no_keys, m);
+}
+
+int test_ht5()
+{
+    char *keys[] = {"aaa","aba","aca","ada","aea","afa","aga","aha","aia","aja"};
+    int values[] = {0,1,2,3,4,5,6,7,8,9};
+    char *no_keys[] = {"a1a","a2a","a3a","a4a","a5a","a6a","a7a","a9a","a9a","a10a"};
+    size_t n = sizeof keys/sizeof keys[0];
+    size_t m = sizeof no_keys/sizeof no_keys[0];
+
+    assert( n == (sizeof keys/sizeof keys[0]) );
+    assert( n == (sizeof values/sizeof values[0]) );
+    assert( m == (sizeof no_keys/sizeof no_keys[0]) );
+
+    return test_str_ht(keys, values, n, no_keys, m);
+}
+
+int test_ht6()
+{
+    char *keys[] = {"aaa","aab","aac","aad","aba","abb","abc","abd","abe","abf"};
+    int values[] = {0,1,2,3,4,5,6,7,8,9};
+    char *no_keys[] = {"aax","abx","acx","adx","aex","afx"};
+    size_t n = sizeof keys/sizeof keys[0];
+    size_t m = sizeof no_keys/sizeof no_keys[0];
+
+    assert( n == (sizeof keys/sizeof keys[0]) );
+    assert( n == (sizeof values/sizeof values[0]) );
+    assert( m == (sizeof no_keys/sizeof no_keys[0]) );
+
+    return test_str_ht(keys, values, n, no_keys, m);
+}
+
+int test_empty_ht()
+{
+    int dummy1 = 0;
+    int dummy2 = 1;
+
+    return test_int_ht(&dummy1, &dummy1, 0, &dummy2, 0);
+}
+
+int test_int_ht(int *keys, int *values, size_t n, int *no_keys, size_t m)
+{
+    assert( keys );
+    assert( values );
+    assert( no_keys );
+
+    return test_ht(keys, sizeof keys[0], values, sizeof values[0], n, no_keys, m, upo_ht_hash_int_div, upo_test_int_cmp, upo_test_int_to_string, upo_test_int_to_string);
+}
+
+int test_str_ht(char **keys, int *values, size_t n, char **no_keys, size_t m)
+{
+    assert( keys );
+    assert( values );
+    assert( no_keys );
+
+    return test_ht(keys, sizeof keys[0], values, sizeof values[0], n, no_keys, m, upo_ht_hash_str_kr2e, upo_test_str_cmp, upo_test_str_to_string, upo_test_int_to_string);
+}
+
+int test_ht(void *keys, size_t key_sz, void *values, size_t value_sz, size_t n, void *no_keys, size_t m, upo_ht_hasher_t key_hasher, upo_ht_comparator_t key_cmp, char* (*key_to_string)(const void*), char* (*value_to_string)(const void*))
+{
+    upo_ht_sepchain_t ht = NULL;
+    size_t base_cap = 3;
+    size_t caps[4];
+    size_t nc;
+    unsigned char *pck = keys;
+    unsigned char *pcv = values;
+    unsigned char *pcnk = no_keys;
+    int ret = TEST_OK;
+
+/*
+    caps[0] = 2*n+UPO_HT_SEPCHAIN_DEFAULT_CAPACITY;
+    caps[1] = n*UPO_HT_SEPCHAIN_DEFAULT_CAPACITY;
+    caps[2] = UPO_HT_SEPCHAIN_DEFAULT_CAPACITY;
+*/
+    caps[0] = 3*n+base_cap;
+    caps[1] = 2*n+base_cap;
+    caps[2] = n*base_cap;
+    caps[3] = base_cap;
+    nc = sizeof caps/sizeof caps[0];
+
+    for (size_t k = 0; k < nc && ret == TEST_OK; ++k)
+    {
+        ht = upo_ht_sepchain_create(caps[k], key_hasher, key_cmp);
+
+        assert( ht != NULL );
+
+        if (n > 0)
+        {
+            // Non-empty HT
+
+            // Populate the HT
+            for (size_t i = 0; i < n; ++i)
+            {
+                upo_ht_sepchain_put(ht, pck + i*key_sz, pcv + i*value_sz);
+            }
+
+            // Remove from HT
+            for (size_t i = 0; i < n && ret == TEST_OK; ++i)
+            {
+                size_t old_size = 0;
+                size_t new_size = 0;
+                int found = 0;
+
+                void *key = NULL;
+
+                key = malloc(key_sz);
+                if (key == NULL)
+                {
+                    perror("Unable to allocate memory for key");
+                    abort();
+                }
+
+                memcpy(key, pck + i*key_sz, key_sz);
+
+                old_size = upo_ht_sepchain_size(ht);
+
+                upo_ht_sepchain_odelete(ht, key, 0);
+
+                new_size = upo_ht_sepchain_size(ht);
+
+                found = upo_ht_sepchain_contains(ht, key);
+
+                if (found == 1)
+                {
+                    char *key_str = key_to_string(key);
+                    fprintf(stderr, "[file: %s, line: %d] ERROR: HT Alternative #: %lu, Key: '%s' -> Found key after its deletion.\n", __FILE__, __LINE__, k, key_str);
+                    fprintf(stderr, "[file: %s, line: %d]: <HT>\n", __FILE__, __LINE__);
+                    upo_ht_sepchain_dump(ht, stderr, key_to_string, 1, value_to_string, 1);
+                    fprintf(stderr, "[file: %s, line: %d]: </HT>\n", __FILE__, __LINE__);
+                    fflush(stderr);
+                    free(key_str);
+                    ret = TEST_FAIL;
+                }
+                else if (new_size != (old_size-1))
+                {
+                    char *key_str = key_to_string(key);
+                    fprintf(stderr, "[file: %s, line: %d] ERROR: HT Alternative #: %lu, Key: '%s' -> Expected size: %lu, got %lu.\n", __FILE__, __LINE__, k, key_str, (old_size-1), new_size);
+                    fprintf(stderr, "[file: %s, line: %d]: <HT>\n", __FILE__, __LINE__);
+                    upo_ht_sepchain_dump(ht, stderr, key_to_string, 1, value_to_string, 1);
+                    fprintf(stderr, "[file: %s, line: %d]: </HT>\n", __FILE__, __LINE__);
+                    fflush(stderr);
+                    free(key_str);
+                    ret = TEST_FAIL;
+                }
+
+                free(key);
+
+                // Check order of stored values 
+
+                upo_ht_sepchain_list_node_t *node = upo_ht_sepchain_check_order(ht);
+                if (node != NULL)
+                {
+                    char *key_str1 = key_to_string(node->key);
+                    char *key_str2 = key_to_string(node->next->key);
+                    fprintf(stderr, "[file: %s, line: %d] ERROR: HT Alternative #: %zu, Keys: '%s' and '%s' are out of order.\n", __FILE__, __LINE__, k, key_str1, key_str2);
+                    free(key_str1);
+                    free(key_str2);
+                    fprintf(stderr, "[file: %s, line: %d]: <HT>\n", __FILE__, __LINE__);
+                    upo_ht_sepchain_dump(ht, stderr, key_to_string, 1, value_to_string, 1);
+                    fprintf(stderr, "[file: %s, line: %d]: </HT>\n", __FILE__, __LINE__);
+                    fflush(stderr);
+                    ret = TEST_FAIL;
+                }
+            }
+
+            upo_ht_sepchain_clear(ht, 0);
+
+            // Now for each key to be removed, fill the HT with all keys, remove the given key and then check that the other keys are still present in the HT
+
+            for (size_t j = 0; j < n && ret == TEST_OK; ++j)
+            {
+                // Populate the HT
+                for (size_t i = 0; i < n && ret == TEST_OK; ++i)
+                {
+                    upo_ht_sepchain_put(ht, pck + i*key_sz, pcv + i*value_sz);
+                }
+
+                // Remove from the HT
+
+                void *key = NULL;
+
+                key = malloc(key_sz);
+                if (key == NULL)
+                {
+                    perror("Unable to allocate memory for key");
+                    abort();
+                }
+
+                memcpy(key, pck + j*key_sz, key_sz);
+
+                size_t old_size = 0;
+                size_t new_size = 0;
+                int found = 0;
+
+                old_size = upo_ht_sepchain_size(ht);
+
+                upo_ht_sepchain_odelete(ht, key, 0);
+
+                new_size = upo_ht_sepchain_size(ht);
+
+                found = upo_ht_sepchain_contains(ht, key);
+
+                if (found == 1)
+                {
+                    char *key_str = key_to_string(key);
+                    fprintf(stderr, "[file: %s, line: %d] ERROR: HT Alternative #: %lu, Key: '%s' -> Found key after its deletion.\n", __FILE__, __LINE__, k, key_str);
+                    fprintf(stderr, "[file: %s, line: %d]: <HT>\n", __FILE__, __LINE__);
+                    upo_ht_sepchain_dump(ht, stderr, key_to_string, 1, value_to_string, 1);
+                    fprintf(stderr, "[file: %s, line: %d]: </HT>\n", __FILE__, __LINE__);
+                    fflush(stderr);
+                    free(key_str);
+                    ret = TEST_FAIL;
+                }
+                else if (new_size != (old_size-1))
+                {
+                    char *key_str = key_to_string(key);
+                    fprintf(stderr, "[file: %s, line: %d] ERROR: HT Alternative #: %lu, Key: '%s' -> Expected size: %lu, got %lu.\n", __FILE__, __LINE__, k, key_str, (old_size-1), new_size);
+                    fprintf(stderr, "[file: %s, line: %d]: <HT>\n", __FILE__, __LINE__);
+                    upo_ht_sepchain_dump(ht, stderr, key_to_string, 1, value_to_string, 1);
+                    fprintf(stderr, "[file: %s, line: %d]: </HT>\n", __FILE__, __LINE__);
+                    fflush(stderr);
+                    free(key_str);
+                    ret = TEST_FAIL;
+                }
+
+                for (size_t i = 0; i < n && ret == TEST_OK; ++i)
+                {
+                    if (i == j)
+                    {
+                        // Skip the remove key
+                        continue;
+                    }
+
+                    found = upo_ht_sepchain_contains(ht, pck + i*key_sz);
+
+                    if (found == 0)
+                    {
+                        ret = TEST_FAIL;
+                        char *key_str = key_to_string(key);
+                        char *key2_str = key_to_string(pck + i*key_sz);
+                        fprintf(stderr, "[file: %s, line: %d] ERROR: HT Alternative #: %lu, Key: '%s' -> Key '%s' not found after the deletion of another key.\n", __FILE__, __LINE__, k, key_str, key2_str);
+                        fprintf(stderr, "[file: %s, line: %d]: <HT>\n", __FILE__, __LINE__);
+                        upo_ht_sepchain_dump(ht, stderr, key_to_string, 1, value_to_string, 1);
+                        fprintf(stderr, "[file: %s, line: %d]: </HT>\n", __FILE__, __LINE__);
+                        fflush(stderr);
+                        free(key_str);
+                        free(key2_str);
+                        ret = TEST_FAIL;
+                    }
+                }
+
+                /* Clean-up */
+                free(key);
+
+                // Clear the HT
+                upo_ht_sepchain_clear(ht, 0);
+            }
+
+            // Now populate again the HT and try to remove a key not stored in it.
+
+            // Populate the HT
+            for (size_t i = 0; i < n; ++i)
+            {
+                upo_ht_sepchain_put(ht, pck + i*key_sz, pcv + i*value_sz);
+            }
+
+            // Remove from HT
+            for (size_t i = 0; i < m && ret == TEST_OK; ++i)
+            {
+                size_t old_size = 0;
+                size_t new_size = 0;
+                int found = 0;
+
+                // Use a different pointer for key to catch comparison-by-pointers issues
+
+                void *key = NULL;
+
+                key = malloc(key_sz);
+                if (key == NULL)
+                {
+                    perror("Unable to allocate memory for key");
+                    abort();
+                }
+
+                memcpy(key, pcnk + i*key_sz, key_sz);
+
+                old_size = upo_ht_sepchain_size(ht);
+
+                upo_ht_sepchain_odelete(ht, key, 0);
+
+                new_size = upo_ht_sepchain_size(ht);
+
+                found = upo_ht_sepchain_contains(ht, key);
+
+                if (found == 1)
+                {
+                    char *key_str = key_to_string(key);
+                    fprintf(stderr, "[file: %s, line: %d] ERROR: HT Alternative #: %lu, Key: '%s' -> Found key not belonging to the HT.\n", __FILE__, __LINE__, k, key_str);
+                    fprintf(stderr, "[file: %s, line: %d]: <HT>\n", __FILE__, __LINE__);
+                    upo_ht_sepchain_dump(ht, stderr, key_to_string, 1, value_to_string, 1);
+                    fprintf(stderr, "[file: %s, line: %d]: </HT>\n", __FILE__, __LINE__);
+                    fflush(stderr);
+                    free(key_str);
+                    ret = TEST_FAIL;
+                }
+                else if (new_size != old_size)
+                {
+                    char *key_str = key_to_string(key);
+                    fprintf(stderr, "[file: %s, line: %d] ERROR: HT Alternative #: %lu, Key: '%s' -> Expected size: %lu, got %lu.\n", __FILE__, __LINE__, k, key_str, old_size, new_size);
+                    fprintf(stderr, "[file: %s, line: %d]: <HT>\n", __FILE__, __LINE__);
+                    upo_ht_sepchain_dump(ht, stderr, key_to_string, 1, value_to_string, 1);
+                    fprintf(stderr, "[file: %s, line: %d]: </HT>\n", __FILE__, __LINE__);
+                    fflush(stderr);
+                    free(key_str);
+                    ret = TEST_FAIL;
+                }
+
+                free(key);
+
+                // Check order of stored values 
+
+                upo_ht_sepchain_list_node_t *node = upo_ht_sepchain_check_order(ht);
+                if (node != NULL)
+                {
+                    char *key_str1 = key_to_string(node->key);
+                    char *key_str2 = key_to_string(node->next->key);
+                    fprintf(stderr, "[file: %s, line: %d] ERROR: HT Alternative #: %zu, Keys: '%s' and '%s' are out of order.\n", __FILE__, __LINE__, k, key_str1, key_str2);
+                    free(key_str1);
+                    free(key_str2);
+                    fprintf(stderr, "[file: %s, line: %d]: <HT>\n", __FILE__, __LINE__);
+                    upo_ht_sepchain_dump(ht, stderr, key_to_string, 1, value_to_string, 1);
+                    fprintf(stderr, "[file: %s, line: %d]: </HT>\n", __FILE__, __LINE__);
+                    fflush(stderr);
+                    ret = TEST_FAIL;
+                }
+            }
+
+            upo_ht_sepchain_clear(ht, 0);
+        }
+        else
+        {
+            // Empty HT: no test
+        }
+
+        upo_ht_sepchain_destroy(ht, 0);
+    }
+
+    return ret;
+}