diff --git a/pretext/AlgorithmAnalysis/HashTableAnalysis.ptx b/pretext/AlgorithmAnalysis/HashTableAnalysis.ptx index 08d32d92..ff14fcc9 100755 --- a/pretext/AlgorithmAnalysis/HashTableAnalysis.ptx +++ b/pretext/AlgorithmAnalysis/HashTableAnalysis.ptx @@ -91,70 +91,93 @@ advantage of the hash table is the constant speed of the contains operation.

For our last performance experiment we will compare the performance of - the contains operation between vectors and hash tables. In the process we - will confirm that the contains operation for vectors is O(n) and - the contains operation for hash tables is O(1). The experiment - we will use to compare the two is simple. We'll make a vector with a range - of numbers in it. Then we will pick numbers at random and check to see - if the numbers are in the vector. If our performance tables are correct - the bigger the vector the longer it should take to determine if any one - number is contained in the vector.

+ searching for a value between vectors and hash tables. In C++, vectors do + not have a contains method, so we will use std::find to + search the vector and find (or contains) to search the hash + table. In the process we will confirm that searching a vector is + O(n) and searching a hash table is O(1). The experiment we + will use to compare the two is simple. We'll fill a vector with a range of + numbers, then search for every one of those numbers in turn and time how + long the search takes. If our performance tables are correct, the bigger + the vector the longer it should take to find a number, even though the + values themselves are already in sorted order.

We will repeat the same experiment for a hash table that contains - numbers as the keys. In this experiment we should see that determining + numbers as the keys. In this experiment we should see that finding whether or not a number is in the hash table is not only much faster, - but the time it takes to check should remain constant even as the - hash table grows larger.

+ but the time it takes to search should remain roughly constant even as + the hash table grows larger. Note that in both cases we only time the + search itself — filling each container is done first and is not included + in the measurement, since we want to isolate the cost of the search + operation.

implements this comparison. Notice that we are - performing exactly the same operation, number in container. The - difference is that on line 7 x is vector, and on line 9 x is a - hash table.

+ performing an equivalent search operation in each container: std::find + against the vector, and find against the hash table in the C++ + version, or the in operator against a list and a dict in the + Python version. In each case only the search loop is timed, not the + loop that builds the container.

Hash Table Analysis C++ Implementation +#include <algorithm> +#include <chrono> +#include <iomanip> #include <iostream> -#include <ctime> -#include <vector> +#include <numeric> #include <unordered_map> +#include <vector> using namespace std; int main() { - for(int a = 10000; a < 1000001; a = a + 20000) { - vector<int> avector; - unordered_map<int, int> amap; + cout << setw(10) << "size" + << setw(15) << "vector (s)" + << setw(15) << "hash table (s)" << endl; - // create the vector and map - for( int i = 0; i < a; i++){ - avector.push_back(i); - amap[i] = -1; + for(int a = 10000; a < 1000001; a = a + 20000) { + // build and fill the vector (not timed) + vector<int> avector(a); + iota(avector.begin(), avector.end(), 0); + + // time searching the vector for every value + auto begin = chrono::steady_clock::now(); + for(int i = 0; i < a; i++){ + if (find(avector.begin(), avector.end(), i) == avector.end()) { + cerr << "value not found in vector!" << endl; + return 1; + } } + auto end = chrono::steady_clock::now(); + chrono::duration<double> elapsed_secs = end - begin; + double avg_vector_time = elapsed_secs.count() / a; - // timing for vector access - srand(1); - clock_t begin = clock(); - for( int i = 0; i < a; i++){ - find(avector.begin(), avector.end(), rand() % a); + // build and fill the hash table (not timed) + unordered_map<int, int> amap; + for(int i = 0; i < a; i++){ + amap[i] = i; } - clock_t end = clock(); - double elapsed_secs = double(end - begin) / CLOCKS_PER_SEC; - - // timing for hash table - srand(1); - clock_t begin_ht = clock(); - for( int i = 0; i < a; i++ ){ - amap.contains(rand() % a); + + // time searching the hash table for every value + auto begin_ht = chrono::steady_clock::now(); + for(int i = 0; i < a; i++){ + if (amap.find(i) == amap.end()) { + cerr << "value not found in hash table!" << endl; + return 2; + } } - clock_t end_ht = clock(); - double elapsed_secs_ht = double(end_ht - begin_ht) / CLOCKS_PER_SEC; + auto end_ht = chrono::steady_clock::now(); + chrono::duration<double> elapsed_secs_ht = end_ht - begin_ht; + double avg_hashtable_time = elapsed_secs_ht.count() / a; // Printing final output - cout << a << "\t" << elapsed_secs << "\t" << elapsed_secs_ht << endl; + cout << setw(10) << a + << setw(15) << avg_vector_time + << setw(15) << avg_hashtable_time << endl; } return 0; } - + Python Implementation @@ -173,20 +196,18 @@ for i in range(10000,1000001,20000): -

summarizes the results of running - . You can see that the hash table is consistently - faster. For the smallest vector size of 10,000 elements a hash table is - 89.4 times faster than a vector. For the largest vector size of 990,000 - elements the hash table is 11,603 times faster! You can also see that - the time it takes for the contains operator on the vector grows linearly - with the size of the vector. This verifies the assertion that the contains - operator on a vector is O(n). It can also be seen that the time - for the contains operator on a hash table is constant even as the - hash table size grows. In fact for a hash table size of 10,000 the - contains operation took 0.004 milliseconds and for the hash table size - of 990,000 it also took 0.004 milliseconds.

- -
+

Running confirms that the hash table is + consistently faster. For the smallest vector size of 10,000 elements a hash table + search is approximately 320 times faster than a vector search. For the largest vector + size of 990,000 elements the hash table is approximately 27,500 times faster! You can also + see that the time it takes to search the vector grows linearly with the + size of the vector. This verifies the assertion that searching a vector + is O(n). It can also be seen that the time to search a hash table + stays roughly constant even as the hash table grows larger. In fact for a + hash table size of 10,000 the search took approximately 2 nanoseconds + and for the hash table size of 990,000 it also took approximately 2 nanoseconds.

+ +

Since C++ is an evolving language, there are always changes going on behind the scenes. The latest information on the performance of C++