4
\$\begingroup\$

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?

\$\endgroup\$
4
  • \$\begingroup\$ this question better fit to portal stackoverflow.com \$\endgroup\$
    – furas
    May 1 at 6:39
  • \$\begingroup\$ You should probably be using the inbuilt binascii module for this. (stackoverflow.com/a/1425500/10534470) \$\endgroup\$
    – Eshaan7
    May 1 at 15:00
  • 1
    \$\begingroup\$ @furas what makes you say that? The code is as complete as can be expected, and works. \$\endgroup\$
    – Reinderien
    May 2 at 4:16
  • \$\begingroup\$ @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. \$\endgroup\$
    – furas
    May 2 at 4:49
5
\$\begingroup\$

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('_', ' ')
\$\endgroup\$
2
  • \$\begingroup\$ Nice review. Is there a reason to mix double and single quotes? \$\endgroup\$
    – Marc
    May 5 at 0:03
  • 1
    \$\begingroup\$ 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 \$\endgroup\$
    – AJNeufeld
    May 5 at 0:58
3
\$\begingroup\$

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.

\$\endgroup\$
4
  • 2
    \$\begingroup\$ 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. \$\endgroup\$
    – AJNeufeld
    May 2 at 23:05
  • \$\begingroup\$ 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. \$\endgroup\$
    – AJNeufeld
    May 3 at 15:27
  • \$\begingroup\$ @AJNeufeld, Fixed. I left out a Jupyter cell when copying the code over. \$\endgroup\$
    – RootTwo
    May 3 at 21:32
  • \$\begingroup\$ Much better. But it could still use a .strip() or [:-1] to removed the trailing space, like the OP's code originally did. \$\endgroup\$
    – AJNeufeld
    May 4 at 13:41
1
\$\begingroup\$

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
\$\endgroup\$
5
  • 1
    \$\begingroup\$ If you believe this question to be off topic, why answer it? \$\endgroup\$
    – Reinderien
    May 2 at 4:14
  • \$\begingroup\$ @Reinderien because I like to resolve problems. And this problem seems interesting for me. But usually I do it on Stackoverflow. \$\endgroup\$
    – furas
    May 2 at 4:48
  • 1
    \$\begingroup\$ please avoid using camelCasing for variables in python :) \$\endgroup\$
    – hjpotter92
    May 2 at 7:25
  • \$\begingroup\$ @hjpotter92 OP used aString so I decided to keep it and not add in answer PEP 8 -- Style Guide forPython Code \$\endgroup\$
    – furas
    May 2 at 7:29
  • \$\begingroup\$ 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. \$\endgroup\$
    – radarbob
    May 3 at 1:57

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.