Converting hex to binary in the form of string

This code returns a string of binary in 4-digit forms for a given hex in the form of string

def str_bin_in_4digits(aString):
retStr = ''
for i in aString:
retStr = retStr+"{0:04b}".format(int(i, 16))+" "
return retStr.strip()


for example,

>>> str_bin_in_4digits("20AC")
0010 0000 1010 1100


The code works as expected and my concern is could it be more elegant, like faster or less memory consumption?

• this question better fit to portal stackoverflow.com – furas May 1 at 6:39
• You should probably be using the inbuilt binascii module for this. (stackoverflow.com/a/1425500/10534470) – Eshaan7 May 1 at 15:00
• @furas what makes you say that? The code is as complete as can be expected, and works. – Reinderien May 2 at 4:16
• @Reinderien I send a lot of time on Stackoverflow and I see many questions which shows only part of code - like in question - and for me it fit to Stackoverflow. – furas May 2 at 4:49

Review

You don't have a lot of code here to review, so this will necessarily be short.

• PEP-8: The Style Guide for Python Code recommends:
• snake_case for functions, variables, and parameters. So aString should be a_string, and retVal should be ret_val.
• Better parameter names
• What is aString? "Hello World" is a string, but we can't use it, because you are actually expecting a hexadecimal string. Perhaps hex_string would be a better parameter name.
• Similarly, binary_string would be more descriptive than retStr.
• A '''docstring''' would be useful for the function.
• Type hints would also be useful.

Alternate Implementation

Doing things character-by-character is inefficient. It is usually much faster to let Python do the work itself with its efficient, optimized, native code functions.

Python strings formatting supports adding a comma separator between thousand groups.

>>> f"{123456789:,d}"
'123,456,789'


It also supports adding underscores between groups of 4 digits when using the binary or hexadecimal format codes:

>>> f"{548151468:_x}"
'20ac_20ac'
>>> f"{0x20AC:_b}"
'10_0000_1010_1100'


That is most of the way to what you're looking for. Just need to turn underscores to spaces, with .replace(...) and fill with leading zeros by adding the width and 0-fill flag to the format string.

>>> f"{0x20AC:019_b}".replace('_', ' ')
'0010 0000 1010 1100'


A function using this technique could look like:

def str_bin_in_4digits(hex_string: str) -> str:
"""
Turn a hex string into a binary string.
In the output string, binary digits are space separated in groups of 4.

>>> str_bin_in_4digits('20AC')
'0010 0000 1010 1100'
"""

value = int(hex_string, 16)
width = len(hex_string) * 5 - 1
bin_string = f"{value:0{width}_b}"
return bin_string.replace('_', ' ')

if __name__ == '__main__':
import doctest
doctest.testmod(verbose=True)


Depending on your definition of elegant, you can one-line this:

def str_bin_in_4digits(hex_string: str) -> str:
"""
Turn a hex string into a binary string.
In the output string, binary digits are space separated in groups of 4.

>>> str_bin_in_4digits('20AC')
'0010 0000 1010 1100'
"""

return f"{int(hex_string,16):0{len(hex_string)*5-1}_b}".replace('_', ' ')

• Nice review. Is there a reason to mix double and single quotes? – Marc 2 days ago
• Python makes no distinction between single and double quotes, or triple-quoted single/double quote strings, once they've been created. Single quotes don't need to be escaped in double quote strings, and double quotes don't need to be escaped in single quote string; newlines and quotes don't need escaping in triple-quoted strings. My habit of using single quotes for single characters and double quotes for strings comes from C/C++/Java, and has no other significance. @Marc – AJNeufeld 2 days ago

I don't know if there is faster or less memory consumption method but you can write it more elegant using list

def str_bin_in_4digits(aString):

data = []

for i in aString:
data.append( "{0:04b}".format(int(i, 16)) )

retStr = " ".join(data)

return retStr  # doesn't need strip()


and then you could write it also as list comprehension

def str_bin_in_4digits(aString):

data = ["{0:04b}".format(int(i, 16)) for i in aString]
retStr = " ".join(data)

return retStr  # doesn't need strip()


and then you could even reduce to one line

def str_bin_in_4digits(aString):

return " ".join(["{0:04b}".format(int(i, 16)) for i in aString])


Maybe even using list comprehension it can be little faster but for small string you may not see it.

Now you have to only choose version which is the most readable for you.

Problem is that you have 4-bits values (single hex char).

For full 8-bits you could convert from hex to bytes using

 bytes_data = bytes.fromhex('0A 0B 0C')

bytes_data = bytes.fromhex('0A0B0C')


and later you could again use list comprehension - using f-string it could even shorter

 data = [f'{x:04b}' for x in bytes_data]


But with with 8-bits you would have to split it to 4-bits using something like this

bytes_data[0] >> 4,  bytes_data[0] & 0x0f

• If you believe this question to be off topic, why answer it? – Reinderien May 2 at 4:14
• @Reinderien because I like to resolve problems. And this problem seems interesting for me. But usually I do it on Stackoverflow. – furas May 2 at 4:48
• please avoid using camelCasing for variables in python :) – hjpotter92 May 2 at 7:25
• @hjpotter92 OP used aString so I decided to keep it and not add in answer PEP 8 -- Style Guide forPython Code – furas May 2 at 7:29
• If you believe this question to be off topic, why answer it? "off topic" means the question does not meet the forum entry criteria and/or exit goals, so to speak. A context of code improvement vis-a-vis a debugging session, let's say. In the wrong forum-category the question is not necessarily categorically unanswerable (although that is the case at times). So far, I've not seen Code Review petagogy suddenly become anti-helpful or plain wrong upon moving to another forum. – radarbob May 3 at 1:57

The problem is to take a hex string and convert it to the equivalent binary string. Each character in the input string maps to a one or more characters in the output string, e.g., '0' -> '0000', ... 'A' -> '1010', ... 'F' -> '1111'. This is a perfect fit for string.translate()

table = ''.maketrans({'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 '})

def str_bin_in_4digits(hex_string):
return hex_string.upper().translate(table)


It's 6-7 times faster.

• This does not work. It is missing the required str.maketrans(...) call, so the translate table is not correct. Instead of calling .upper(), creating an unnecessary temporary result, it would be more efficient to include both A-F and a-f translations in the table. Finally, this does not add spaces in the resulting groups of 4 digits. – AJNeufeld May 2 at 23:05
• Please fix this so I can upvote it; str.translate(...) is my favourite function and this can be a great solution. The translate table needs ordinal number keys, as in table = {ord('0'): '0000', ... or table = {48: '0000', ... to work, but it would be simpler to just use table = str.maketrans({'0': '0000', ...) to do this one-time initialisation. To get space-separated 4-digit groups, add a space at the end of each table value and strip the final space from the result. – AJNeufeld May 3 at 15:27
• @AJNeufeld, Fixed. I left out a Jupyter cell when copying the code over. – RootTwo May 3 at 21:32
• Much better. But it could still use a .strip() or [:-1] to removed the trailing space, like the OP's code originally did. – AJNeufeld 2 days ago