0

From input 0x1234 I need to output: 0b0001_0010_0011_0100.

I can think of all the bad ways to do this, but what's the most pythonic way to achieve this.

I can easily convert to bin string (bin(int('0x1234', 16)), but that only gives me 0001001000110100. I need a _ between nibbles.

Thank you.

Hamms
  • 5,016
  • 21
  • 28
Romeo
  • 147
  • 1
  • 3
  • 11

3 Answers3

3

hex maps one digit to one nibble ... so you can do it quite easily with a lookup table

hex2bin_map = {
   "0":"0000",
   "1":"0001",
   "2":"0010",
   "3":"0011",
   "4":"0100",
   "5":"0101",
   "6":"0110",    
   "7":"0111",
   "8":"1000",
   "9":"1001",
   "A":"1010",
   "B":"1011",
   "C":"1100",
   "D":"1101",
   "E":"1110",
   "F":"1111",
}
hex_num="1234"
print "_".join(hex2bin_map[i] for i in hex_num)
# 0001_0010_0011_0100

this isnt as terse as the other answer(s) im sure ... but its probably the most performant ... and its pretty dang easy to read (ie you dont need to worry about the grouper pattern of zip, nor regex's, which are bound to confuse newer programmers)

Joran Beasley
  • 110,522
  • 12
  • 160
  • 179
  • I like that this avoids the roundabout conversion from string -> number -> back to string. –  Aug 24 '16 at 21:48
  • plus dict lookups are O(1) ... thats really the benefit.... str->num->str is really a pretty quick operation – Joran Beasley Aug 24 '16 at 21:49
  • Thanks!. that makes sense. – Romeo Aug 24 '16 at 22:04
  • Nice approach! Inspired by this, I added a second approach to my answer which is quite similar to this but avoids the lookup table. (Also, you're missing the handling of the "0x" at the beginning of the input and the "0b" expected at the beginning of the output.) – user94559 Aug 25 '16 at 02:22
2

As an alternative to generic sequence chunking, for strings, you can use a regex to group and replace:

>>> '0b' + re.sub(r'(\d{4})(?!$)', r'\1_', format(int('0x1234', 16), '016b'))
'0b0001_0010_0011_0100'

In Python 3.6, you will be able to do this with a format specifier (see PEP 515)

'0b' + format(int('0x1234', 16), '016_b')

or, if you prefer:

'0b{:016_b}'.format(int('0x1234', 16))
Community
  • 1
  • 1
1

Not sure if there's a better way, but this works (assuming the numbers are always 16-bit):

# Takes a hex-encoded 16-bit number and returns a binary representation with
# leading zeros and underscores as a nibble separator.
def binary_representation(string):
    return '0b' + '_'.join(
        ''.join(nibble) for nibble in zip(*([iter('{:>016b}'.format(int(string, 16)))]*4)))

assert binary_representation('0x1234') == '0b0001_0010_0011_0100'

The zip trick is a good one that comes from the grouper example in the Python docs. That call essentially emits [('0', '0', '0', '1'), ('0', '0', '1', '0'), ...]. From there, you just need to turn each nibble into a single string and then join them all with underscores.

The other interesting bit here is the {:>016b} format, which gives you a right-aligned, zero-padded binary representation of a number. If you need to support different sizes of numbers, this is the part you'll need to change.

UPDATE

Given that your number starts out as a string, this approach is even simpler, inspired by @Joran's answer but without the lookup table.

def binary_representation(string):
    return '0b' + '_'.join(['{:>04b}'.format(int(d, 16)) for d in string[2:]])
user94559
  • 59,196
  • 6
  • 103
  • 103