Finding the intersection of sets would involve something like a hash join. While much of the setup could be pre-computed and kept in memory, you still need to do all of the following:
- Assume we are searching list
N
for digits a
and b
.
- Iterate through N, which has ~one million numbers
- For each number in the list:
- Search the hash table for digit
a
to see if N[i]
exists
- Scan the hash pointers for the right hash bucket
- Scan the hash bucket to see if
N[i]
exists
- Search the hash table for digit
b
to see if N[i]
exists
- Scan the hash pointers for the right hash bucket
- Scan the hash bucket to see if
N[i]
exists
On the other hand, if you skipped the tables and wrote an efficient algorithm for checking on the spot, you could:
- Assume we are searching list
N
for digits a
and b
.
- Iterate through N, which has ~one million numbers
- For each number in the list:
- For each digit in the number
- Divide by 10
- Compare the remainder to
a
and b
So for the hash join, you have several complicated search and location functions, per individual N[i]
, all of which require memory read, increment, and comparison operations. This means {number of numbers} x {number of hashtables} x {rows in hash lookup + rows in hash bucket}. They all multiply! Meanwhile, for a straight scan, you have two low-level operations (a division+modulus operation and a comparison) per number. That is much, much less processing.
It is hard to assess performance in isolation, but I'm guessing you're not going to gain much performance, if any, from a hash join solution. In fact it may be worse due to the low selectivity-- given a number between 1 and 1,000,000, roughly 50% of them will have any given digit. If that number were much smaller, a hash table would boost performance quite a bit, but if you're pulling back half or more of the data, a scan starts looking better and better. When you consider the increased memory utilization (and therefore increased working set) required to support the hash tables, I'm going to make a bet that the performance of the hash/intersection design would be worse.
Here is some simple code that efficiently checks for the presence of a set of digits. I used this algorithm on my Dell Precision laptop and was able to scan 1,000,000 numbers in 0.0120 seconds. I would just run this function, per number in the list, when needed.
int ContainsDigits(int numberToCheck, int digitsToFind[], int digitCount)
{
int result;
int digits[10];
memcpy(digits, digitsToFind, digitCount * sizeof(int));
while (numberToCheck > 0)
{
std::div_t result = std::div(numberToCheck, 10);
for (int i = digitCount -1; i >= 0; i--)
{
if (result.rem == digits[i])
{
if (!--digitCount) return 1;
digits[i] = digits[digitCount];
}
}
numberToCheck = result.quot;
}
return 0;
}
The algorithm checks the least significant digit (given by n % 10
) in a loop then shifts the number right (equivalent to n / 10
). We can get the modulus and quotient in a single operation (std::div
).
Digits sought are stored in an array. When a digit is found, the array is shortened by one element, with the end element moved into the place of the discovered digit, thus keeping the list compacted. When the array is empty all digits have been found. If the loop exits and not all digits have been found, the function returns false.