3

Here's a somewhat different approach to generating the sequence of Hamming numbers (aka regular numbers, 5-smooth numbers) based on the interval from one number in the sequence to the next. Here's an example plot of said intervals:

enter image description here

So there is a relatively limited number of discrete intervals separating one number from the next, and the intervals get smaller as H increases. It's often noted that Hamming numbers get sparser as they increase in size, which they do in absolute terms, but in another sense (proportionally) they get closer together.

Basically, as H goes up there is greater opportunity for 2^i*3^j*5^k where i,j,k are positive or negative integers to result in a fraction near 1.0.

Turns out that a table of just 119 intervals (i,j,k triples) covers Hamming numbers up to about 10^10000. That's about the first 1.59 trillion Hamming numbers. Such a table (C header file), sorted by the interval size from small to large, is here. Given a Hamming number, to find the next one all that's required is to find the first entry in the table where multiplication (addition of respective exponents) would yield a result with positive powers for i,j and k.

E.g., the millionth Hamming number is 2^55*3^47*5^64 which is about 5.1931278e83. The next Hamming number after that is 2^38*3^109*5^29 or about 5.1938179e83. The first appropriate table entry is:

{-17,62,-35}, // 1.000132901540844

So while those numbers are separated by about 7e79, their ratio is 1.000132901540844. To find the next number required just trying up to 119 entries in the worst case, involving just additions and comparisons (no multiplications). Also, the table of just 3 short ints per entry requires under 1kb memory. The algorithm is basically O(1) in memory and O(n) in time, where n is the length of the sequence.

One way to speed it up would be to rather than searching the table from the 0th index every time, constrain the list of table entries to search to just those entries that empirically are known to succeed the given entry in the given range (n < 1.59e12). Those lists are given in the header file above in the succtab[] struct, e.g.:

{11,{47,55,58,65,66,68,70,72,73,75,76}},

So that particular index is empirically found to only be followed by 11 different indices as listed, so those are the only ones searched.

Doing that speeds up the algorithm by a factor of 4 or so, implemented here (C code) along with the header file above. Here's a plot of the execution time on an i7-2600 3.4GHz machine:

enter image description here

I believe that compares favorably with the state of the art--is that so?

The Hamming problem is sometimes reduced to just finding the nth Hamming number without generating all the intermediate values. Adapting the above technique to a well-known scheme of just enumerating the Hamming numbers in a band around the desired range gives this plot of execution time: enter image description here

So that takes less than 2 seconds to find the 1.59 trillionth Hamming number. The C code for that is here. Does this also compare favorably with the state of the art, at least in the given bounds?

EDIT: the bounds for n (1.59e12, Hamming numbers up to about 10^10000) were chosen based on a specific machine, where it was desired that i,j,k be short ints and also reasonable expectation on execution speed. Larger tables could be generated, e.g. a table of 200 entries would allow n to be as high as about 1e18 (Hamming numbers up to about 10^85000).

Another question would be how to speed it up further. One potential area: it turns out that some table entries are hit much more often than others, and they have a correspondingly larger list of successors to check. For example, when generating the first 1.59e12 numbers, this entry is hit by fully 46% of the iterates:

{-7470,2791,1312}

It has 23 possible different successors. Perhaps some way of narrowing that down based on other parameters (e.g., history of the previous entries traversed) would help, although there wouldn't be much room for an expensive operation.

EDIT #2:

For some info about generating the table, there are basically six classes of fractions 2^i*3^j*5^k where i,j,k are positive or negative integers: fractions with only 2,3 or 5 in the numerator, and fractions with only 2,3, or 5 in the denominator. E.g., for the class with only 2 in the numerator:

f = 2^i/(3^j*5^k), i > 0 and j,k >= 0

A C program to compute the intervals for this class of fraction is here. For Hamming numbers up to about 10^10000 it runs in a few seconds. It could probably be made more efficient.

Repeating a similar process for the other 5 classes of fractions yields six lists. Sorting them all together by the interval size and removing duplicates yields the complete table.

Joe Knapp
  • 302
  • 2
  • 9
  • 1
    The memory requirements are `O(1)` only when `n` is constant - they depend on `n`, but in a very slow manner, like `O(log n)` or `O(log³ n)` or maybe even `O(log log n)`. If you only ever need the first 1e12 numbers, fine. Otherwise, you have to generate your table first, and it may be a difficult task. Please specify whether you are really interested in a constant `n` or arbitrary `n` - I think this is a very important part of your question! – anatolyg Oct 23 '17 at 15:56
  • @anatolyg As I said at the end, just given the stated bounds. I think for any given machine, n would be limited by execution speed in any case. Generating the table is a time-consuming task, but could be done on a fast machine to a degree that would bring any ordinary machine to its knees. Just extrapolating the size of the table for n beyond 1e12, it looks like a table of 200 entries would cover up to Hamming numbers of 1e18. So the memory requirement is really minimal. – Joe Knapp Oct 23 '17 at 16:23
  • 1
    Joe this is really cool - maybe it would be better appreciated as a codeproject article? While I wouldn't boot this question others might be strict and do so; maybe leave it here, too, and link from here to increase its visibility (I didn't say that). – FastAl Oct 23 '17 at 16:53
  • a. measuring the power law coefficient on your log-log plots *visually*, indeed the first looks like 1 and the second like 2/3, so that fits the known complexities. as for the constant factors, the comparison should really be done on the same machine, with the same language/compiler, no? :) b. you could re-format this to better fit the SO agenda, and post this as an *answer* here, editing the quesiton to something that would fit the answer. – Will Ness Oct 25 '17 at 12:43
  • when you say that the band algorithm is "well known", to what are you referring? could you please give some links, or what to google? What I'm curious to know is whether there's something outside SO on this? – Will Ness Oct 25 '17 at 12:53
  • the real problem now is how to generate those ratios, so we are sure we're not skipping any elements in the sequence. – Will Ness Oct 25 '17 at 15:33
  • @Will The ratios were generated with exhaustive search, so they are complete. The results match other algorithms for the millionth, billionth, trillionth number, so it checks out. I think the band algorithm was first mentioned in the Dr. Dobbs article, not sure about that, but on the Rosetta code page I see a lot of people including you have used variants of same. Seems like the name Louis Klauder was mentioned. See: http://www.drdobbs.com/architecture-and-design/hamming-problem/228700538 – Joe Knapp Oct 25 '17 at 19:54
  • hi. re: Dr Dobbs: that's my post that link points to :) (even if my name isn't mentioned anywhere there). DrDobbs changed their blog software at some point, and lots of stuff disappeared; this is what I could recover via Google. It was I also that added that link to RosettaCode. I participated in that thread back then, never was in direct correspondence with Louis Klauder though. He showed a code that he'd run with some arbitrary input, and then calculate back the indices of the resulting band. I read some Wikipedia and added the estimation code so a specific *n* could be targeted. :) – Will Ness Oct 25 '17 at 20:10
  • (continuing) so, you meant RosettaCode; I hoped maybe there were something more somewhere out there... :) --- re: exhaustive search: that's no good, sorry. Not that I doubt that it's exhaustive; I've just ran it myself through the first 12 million hamming numbers, got 63 distinct ratios. But, what I mean is, to get these numbers we must first generate the sequence, so it's no shortcut to *generating the sequence* (even less so its *n-th member*) by a long shot! What's needed is some way to generate the sequence of these ratios independently, yes? – Will Ness Oct 25 '17 at 20:16
  • It's a shortcut in any case because once you generate the sequence you have a handy table that anyone else can use on their machine without further ado. FWIW, I did generate the ratios without generating the sequence specifically--I can post the algorithm later. For a comparison on a given machine, this code finds the trillionth number in about 0.5 seconds on ideone: https://ideone.com/r5lCLS – Joe Knapp Oct 25 '17 at 20:19
  • btw the Hamming numbers for new ratios are special: they all have one or two zeroes in the triples. this must mean something, but what? :) -- oh, if you could post that algorithm, it'd be great! and thanks for the ideone link. – Will Ness Oct 25 '17 at 20:21
  • The 0.5 sec result above compares to "about two seconds" for the new improved Haskell version on RosettaCode. – Joe Knapp Oct 25 '17 at 20:22
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/157516/discussion-between-will-ness-and-joe-knapp). – Will Ness Oct 25 '17 at 20:23

1 Answers1

0

The triples enumeration is ~ n2/3 but the sorting of the band is ~ n2/3 log (n2/3) i.e. ~ n2/3 log n. This obviously doesn't change even with ~ n1/3 band space scheme.

Indeed the empirical complexities are seen in practice as ~ n0.7.

I am yet to understand your algorithm fully, but the evidence you presented strongly suggests the pure ~ n2/3 operation, which would constitute a clear and significant improvement over the previous state of the art, absolutely.

enter image description here

This would be not so, in my opinion, if it was needed to generate the whole sequence in order to find the "intervals" (ratios) your algorithm is based on. But since you generate them independently, as your later edit seems to suggest, it's no impediment at all.

Correction: if we're only interested in the nth member of the sequence, then full sort of the band is not needed; O(n) select-kth-largest algorithms do exist.

Will Ness
  • 62,652
  • 8
  • 86
  • 167