Good, just ... hash tabs usually have collisions probability.
Like I said in
#16, optimum size is about twice the input array size. This assumes you use the common scheme of probing
[H%N] and if it is occupied but non-matching,
[(H+D)%N],
[(H+2*D)%N],
[(H+3*D)%N], and so on, until either an unused slot or the matching value is found. (
H being the hash of the value,
N the hash table size, and
D the probe step size, often 1.)
Obviously, to avoid the costly modulo operation per probe, one should make
N a power of two about twice as large as the array size.
Really, a generic function to count the number of occurrences in an array, needs all three: nested-loop
O(N²), range histogram, and a hash table.
If the array is short, the nested loop one makes most sense, because the iterations are fast, and the total number of iterations for small N, say up to a dozen perhaps, is faster to do than the alternatives. Otherwise, do a pass over the array entries, and find out the continuous range of values. If it is smaller than say 4× the number of entries in the array, you allocate an array of counters large enough for each possible value, and do the range histogram. Otherwise, you allocate a hash table of about 2× the number of entries in the array (noting that each hash table entry contains both the original value, and the count), and do the hash table approach.
Is it worth it? I dunno. I don't think so. But knowing the three approaches
is useful, because ones use cases tend to fall into one of the three.
It's like with radix-sorting IEEE 754 double-precision numbers. It is quite straightforward: you just need to XOR-mask the high bit if unset, and all bits if set, so that when interpreted as an unsigned 64-bit integer, the values sort exactly like their original finite double-precision values would. Redo afterwards to return the original values. The optimal pass sizes do depend on the cache architecture, and although it does scale as
O(N), you need bloody huge arrays, tens of millions to billions of doubles, before you beat the traditional
O(N log
N) sort algorithms. In many cases, by doing the XOR-mask pass before and after, and treating the doubles as 64-bit integers, you can speed up the sort enough that the amount of data at which the difference would matter to a human, is too large to worry about: the code maintenance cost is more important in practice.