It seems like the range is known. Let's assume it goes up to 1<<20, just to make it more interesting:
max_log2=20
So make a list that (in effect) maps an integer to its base 2 logarithm. The following will do the trick:
log2s_table=[0]*((1<<max_log2)+1)
for i in range(max_log2+1):
log2s_table[1<<i]=i
(This doesn't do anything useful for numbers that aren't powers of two; the problem statement suggests they don't need to be handled. Would be easy enough to fix that though.)
The function to get the logarithm is very simple, and could easily be inlined:
def table(v):
return log2s_table[v]
I can't guarantee that the test code I wrote is exactly the same as the one being used to get the example timings, but this is rather quicker than the stringcount
code:
stringcount: 0.43 s.
table: 0.16 s.
Since all the values in the table are less than 256, I wondered whether using a string instead of a list would be quicker, or maybe an array.array
of bytes, but no dice:
string: 0.25 s.
arr: 0.21 s.
Using a dict
to do the lookup is another possibility, taking advantage of the way only powers of two are being checked:
log2s_map=dict([(1<<x,x) for x in range(max_log2+1)])
def map(v):
return log2s_map[v]
The results for this weren't as good, though:
map: 0.20 s.
And just for fun one could also use the hex
method on float objects to get a string that includes (as its last part) the base 2 exponent of the number. This is a bit slow to extract in general, but if the exponent is only ever going to be one digit it could be done straightforwardly enough:
def floathex(v):
return ord(float(v).hex()[-1])-48
This is purely for entertainment value though as it was uncompetetive -- though, amazingly, still quicker than the bitwise approach.
So it looks like using a list is the way to go.
(This approach won't scale indefinitely, due to limited memory, but to make up for that the execution speed won't depend on max_log2
, or the input values, in any way that you'll notice when running python code. Regarding the memory consumption, if I remember my python internals correctly, the table will take up about (1<<max_log2)*4
bytes, because the contents are all small integers that the interpreter will intern automatically. SO, when max_log2
is 20, that's about 4MB.)