2
\$\begingroup\$

I have a dictionary which I use to lookup my binary and ASCII values (from A - P). It works, but my ascii2bin function just doesn't really feel right (and beautiful). Is there is a better way to write it?

The ascii2bin function splits a 2-letter string (i.e. 'LA') and looks up the keys in the dictionary. Then concatenates the 2 keys to each other to get an 8bit string.

Check the comment for the assignment which was given.

import sdes

"""
Using S-DES, decrypt the string (10100010) using the key (0111111101) by hand.
Then decode the 4 bits of the plaintext string to a letter and the second 4 bits to another letter where
we encode A through P in base 2 (i.e., A = 0000, B = 0001, ..., P = 1111).
"""

KEY = '0111111101'
CIPHERTEXT = '10100010'

ALPHABET = {'0000': 'A',
            '0001': 'B',
            '0010': 'C',
            '0011': 'D',
            '0100': 'E',
            '0101': 'F',
            '0110': 'G',
            '0111': 'H',
            '1000': 'I',
            '1001': 'J',
            '1010': 'K',
            '1011': 'L',
            '1100': 'M',
            '1101': 'N',
            '1110': 'O',
            '1111': 'P'}


def bin2ascii(bin):
    return ALPHABET[bin[:4]] + ALPHABET[bin[4:]]


def ascii2bin(string_of_2_letters):
    bits = []
    for i in range(0, 2):
        bits += ([key for key, val in ALPHABET.items() if val == string_of_2_letters[i]])
    return bits[0] + bits[1]


print bin2ascii(sdes.decrypt(CIPHERTEXT, KEY))
\$\endgroup\$

3 Answers 3

3
\$\begingroup\$

Give more sensible names

ALPHABET = {'0000': 'A',
            '0001': 'B',

This is plain wrong, that variable really is not only an alphabet. I usually call dicts x_to_y and I think it is a sensible idea to do so, my suggestion is BINARY_TO_LETTER

Just reverse the dict

def reverse_dict(d):
    return {d[i]:i for i in d}

LETTER_TO_BINARY = reverse_dict(BINARY_TO_LETTER)
\$\endgroup\$
1
  • \$\begingroup\$ Thank you, this is a lot better! And you are 100% right about the naming, thanks for the tip! \$\endgroup\$
    – ßaron
    Oct 20, 2015 at 20:06
3
\$\begingroup\$

An alternate version, not using dict's, can be made when noticing that the binary version is simply the letter position in the alphabet. If one in addition makes it a little more generic, so it can handle longer text you could end up with something like this:

ORD_A = ord('A')

def from_binary(binary):
    """Convert binary string of length 4, to corresponding ascii character."""
    return  chr(int(binary, 2) + ORD_A)

def to_binary(character):
    """Convert ascii character to a binary string."""
    return format(ord(character) - ORD_A, '04b')


def ascii_to_binary(text):
    """Convert a text to it's binary string version, 4bits pr character."""
    # For the case of two characters
    #return  to_binary(text[0]) + to_binary(text[1])

    # Generic version
    return ''.join(to_binary(letter) for letter in text)


def binary_to_ascii(binary_text):
    """Convert a binary text back to the ascii text."""
    # For the case of two characters in binary form
    # return from_binary(encrypted_text[:4]) + from_binary(encrypted_text[4:])

    # Generic version
    return ''.join(from_binary(binary_text[i:i+4]) for i in range(0, len(binary_text), 4))


def main():

    for test in [ 'AB', 'CD', 'PACMAN', 'COOL' ]:
        encrypted = ascii_to_binary(test)
        decrypted = binary_to_ascii(encrypted)
        print('{} -> {} -> {}'.format(test, encrypted, decrypted))

if __name__ == '__main__':
    main()

Output from this run is:

AB -> 00000001 -> AB
CD -> 00100011 -> CD
PACMAN -> 111100000010110000001101 -> PACMAN
COOL -> 0010111011101011 -> COOL

One could/should also add in some error checks to verify that the encryption doesn't try to encrypt letters outside of 'A'–'P', or lower case character, but that is left as an exercise for the reader.

\$\endgroup\$
1
  • \$\begingroup\$ It is interesting to see other approaches to this, thanks! \$\endgroup\$
    – ßaron
    Oct 20, 2015 at 21:40
1
\$\begingroup\$

The others have good answers about the methods, but there's also some notes about your execution.

bin2ascii doesn't catch any errors at all. It just assumes that bin contains exactly 8 characters. However if you have any other number you'll get an unhelpful KeyError. I would catch that and raise it as a more relevant ValueError.

def bin2ascii(bin):
    try:
        return ALPHABET[bin[:4]] + ALPHABET[bin[4:]]
    except KeyError:
        raise ValueError("{} is not a valid pair of binary values.".format(bin))

It would be good to add a docstring though, clearing up the requirements. I know you have one at the top but it's good practice to have one here too.

Instead of return bits[0] + bits[1] you can just return ''.join(bits). That will concatenate all elements of the list for you and look neater. Also you can just pass a single number to range, so you can do range(2). In which case 0 is the default starting parameter.

The fact that the two is hardcoded without explanation isn't great here either. Another docstring would be good, and you should decide how to handle strings that are longer than two characters. Ignore them and just convert the first two characters? I suggest at leasting raising another ValueError if the string is under 2 letters (as otherwise you'll just get an IndexError) but I think you'd be better of explicitly failing longer strings too.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

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