views:

461

answers:

7

How to double a number of binary digits in an integer? For example, if bin(x)="1001" then bin(y) must be "11000011". Is there any smart and fast algorithm ?

UPDATE: Here is an elegant solution:

''.join([''.join(i) for i in zip(X,X)])

where X is bin(int_x)[2:]

However, I am interested in a more faster way and for the integers of any size. Maybe an arithmetical transformation should help.

+4  A: 

any_number - int

str(n) - produces string from int.

str::replace(pattern, replaced_value) - replaces all patterns in string to replaced_value.

int(str) - makes int from string.

n=any_number
result_number = int(str(n).replace("0","00").replace("1","11"))
Max
you call it fast?
Andrey
At least it is a solution.
Xavier Ho
I am not sure it is fast, but it is ellegant. didn't notice it in question, but I commonly use python when I don't mind about speed. Anyway, why this shouldn't be fast? str::replace method replaces all entries in one traversal. So here are two traversals, 2 is only constant, the complexity is still O(N)
Max
@Max: O-complexity doesn't tell about speed, it basically measures how your algorithm scales.
0xA3
Oh, sorry, didn't see Peter Milley's solution.
Max
So if `n = 2335`... did you mean to write `bin(n)[2:]`?
badp
Yes, I mean it, but here is written: "a number of binary digits". My English says that number contains only binary digits, not so?
Max
I quote: «if `bin(x)="1001"` then `bin(y)` must be `"11000011"`», where `0b1001` is `9`. (Oh, you'll need also to use the base argument for the `int` call...)
badp
you could double any string like this `''.join([c*2 for c in str(n)])`
Nick D
IMO, Max's solution is the most elegant. I don't see a need to use complicated function for this, unless the use case warrants.
Xavier Ho
@Nick D, I will take it into account, tnx.
Max
@Xavier Ho: I totally agree. All the other solutions take a non-trivial amount of brainpower to understand, for the saving of a trivial amount of CPU performance. Don't get me wrong, the "clever" solutions are very interesting to read (for the theory of it), but I'd probably end up using this solution in practice, unless microsecond performance was *truly* an issue.
Jeriko
+10  A: 

The straightforward solution just using integer arithmetic would be:

def doubledigits(n):
    result = 0
    power = 1
    while n > 0:
        if n%2==1:
            result += 3*power
        power *= 4
        n //= 2
    return result
Peter Milley
It's much less elegant than using string (more code), but would also probably be much faster (especially as it's easily portable to c).
Matthieu M.
@Matthieu: It is faster only if it is really written in C.
KennyTM
Probably (I haven't benchmarked it) since hand-loops are notoriously slow in python in comparison to the built-in operations.
Matthieu M.
I have say, I dispute the idea that "elegant" equals "less code". That's a dangerous way to think. Conciseness is not necessarily the same thing as clarity. (That said, Mark Dickinson's answer is excellent.)
Peter Milley
A: 
y = 0;
for(i = 15; i  >= 0; i--) {
    if((1 << i) & x) {
       y |= 3;
    }
    y <<= 2;
}
Jim Tshr
A: 
def doubledigits(x):
    from math import log
    print (bin(x))
    numdigits = x.bit_length()
    result = 1 << (numdigits*2)
    for i in range(numdigits, -1, -1):
        mask = 1 << i
        if (x & mask > 0):
            rmask = 0b11 << (2*i)
            result = result | rmask
    return result

should do it.

phimuemue
Compare this solution to Peter Milley's solution above. Which one is nicer?
unbeli
Doesn't work for x = 0. Just use `x.bit_length()` instead of log.
KennyTM
@unbeli compare your comment to the others on this question - which is nicer? :P hey, as far as i'm concerned, at least he gave a working solution!
Jeriko
+17  A: 

Here's one way that should be reasonably fast: convert your number to a binary string, then reinterpret the result as being in base 4. Now to make sure that all the '1's are doubled properly, multiply the result by 3.

>>> x = 9
>>> bin(x)
'0b1001'
>>> y = int(bin(x)[2:], 4)*3
>>> bin(y)
'0b11000011'
Mark Dickinson
Very cool method!
psihodelia
Nice one! Took me some time to get it.
Xavier Ho
+1 for very cool method.
Max
+1  A: 
$ python2.6
Python 2.6.5 (r265:79063, Mar 25 2010, 14:13:28)
>>> def dd(n): return eval("0b" + "".join(d * 2 for d in str(bin(n))[2:]))
...
>>> dd(9)
195
grokus
I know eval is evil, if you don't need to convert the result to decimal, you don't need it.
grokus
+15  A: 

(Reference http://graphics.stanford.edu/~seander/bithacks.html#Interleave64bitOps):

If your number is below 256, you may use

@magic
def double_digits_holger8(x):
    m = (x * 0x0101010101010101 & 0x8040201008040201) * 0x0102040810204081
    return ((m >> 49) & 0x5555) | ((m >> 48) & 0xAAAA)

and if it is below 65536,

@more_magic
def double_digits_binmag16(x):
    x = (x | x << 8) & 0x00FF00FF
    x = (x | x << 4) & 0x0F0F0F0F
    x = (x | x << 2) & 0x33333333
    x = (x | x << 1) & 0x55555555
    return x | x << 1

Comparison with other solutions (the function must take an integer and return an integer for fair comparison):

Method        Time per 256 calls
--------------------------------
Do nothing        46.2 usec 
Holger8          256   usec
BinMag16         360   usec
Mark             367   usec # http://stackoverflow.com/questions/2928886/doubling-binary-digits/2929198#2929198
Max              720   usec # http://stackoverflow.com/questions/2928886/doubling-binary-digits/2928938#2928938
Peter          1.08    msec # http://stackoverflow.com/questions/2928886/doubling-binary-digits/2928973#2928973
Phiµµ w/o Log  1.11    msec # http://stackoverflow.com/questions/2928886/doubling-binary-digits/2929106#2929106
Jim16          1.26    msec # http://stackoverflow.com/questions/2928886/doubling-binary-digits/2929038#2929038
Elegant        1.66    msec # int(''.join([''.join(i) for i in zip(X,X)]),2)
More Elegant   2.05    msec # int(''.join(chain(*zip(X, X))), 2)

Benchmark source code can be found in http://gist.github.com/417172.

KennyTM
+1 for more magic. You can get rid of some of the semi-colons. `;p`
Xavier Ho
I am interested more in an universal method for the long integers of any size.
psihodelia
Is there any specific reason to use the "magic" and "more_magic" decorators? Looking at your code, they seem to just pass the function (value) along without actually doing anything to it. Am I wrong in thinking that you might as well just have "magic" and "more_magic" be comments?
JAB
@JAB: I just ported http://code.google.com/p/gag/ to Python :p
KennyTM
@KennyTM: Ah, I see. Thanks for the link. I think I'll keep it bookmarked.
JAB
+1 for testing.
Peter Milley