5
\$\begingroup\$

I encountered this question as I am preparing for my code interview. I was implementing a base function that can decode and encode any bases from base 2 to base 36. I think my decode and encode functions work fine. It can convert digits from binary (base 2 - 36).

My task:

Decode digits from hexadecimal (base 16). Decode given digits in given base to number in base 10.

Also:

Decode digits from any base (2 up to 36).

I created a helper function that handles hexadecimal digit_from_letter, as it checks if the digit is a digit else it's a letter.

import string

def letter_from_num(num):
    """ Convert letter from number:
    input: int -- integer representation of number (in base 10)
    return: strings of letters """
    letters  ='abcdefghijklmnopqrstuvwxyz'
    return letters[num - 10]

def digit_from_letter(letter):
    """ 
    input: any letter 
    return: int -- number representation of number (in base 10)
    handles digits from hexadecimal (base 16)
    handles digits from any base (2 up to 36)
    math function that calculate the number based on math conversion
    this calculate from the 36 letters -97 + 1
    """
    num = ord(letter) - 97 + 10
    return num

def decode(digits, base):
    """Decode given digits in given base to number in base 10.
    digits: str -- string representation of number (in given base)
    base: int -- base of given number
    return: result is int -- integer representation of number (in base 10)"""
    # Handle up to base 36 [0-9a-z]
    assert 2 <= base <= 36, 'base is out of range: {}'.format(base)
    result = 0
    # Loop through the enumeration 
    # index and digit
    # create a helper function that handles hexadecimal digit_from_letter
    for index, digit in enumerate(digits):
        if digit.isdigit(): 
            digit_to_add = int(digit) 
        else: 
            digit_to_add = digit_from_letter(digit)
        # add them together on the result with the digital_to_add to the result
        result += digit_to_add
        if index is not len(digits) - 1: 
            result *= base 
        else: 
            1
    # Return the result decimal digit
    return result

def encode(number, base):
    """Encode given number in base 10 to digits in given base.
    number: int -- integer representation of number (in base 10)
    base: int -- base to convert to
    return: str -- string representation of number (in given base)"""
    # Handle up to base 36 [0-9a-z]
    assert 2 <= base <= 36, 'base is out of range: {}'.format(base)
    new_base_number = ''
    # encode helps me to make sure that we will work 
    while number != 0:
        remainder = number % base
        number = number // base
        if (remainder >= 10 and base > 10):
            remainder = letter_from_num(remainder) 
        else: 
            remainder
        new_base_number += str(remainder)
    # Reverse
    new_base_number = new_base_number[::-1]
    return new_base_number


def convert(digits, base1, base2):
    """Convert given digits in base1 to digits in base2.
    digits: str -- string representation of number (in base1)
    base1: int -- base of given number
    base2: int -- base to convert to
    return: str -- string representation of number (in base2)"""
    assert 2 <= base1 <= 36, 'base1 is out of range: {}'.format(base1)
    assert 2 <= base2 <= 36, 'base2 is out of range: {}'.format(base2)
    # result = convert(digits, base1, base2)
    # if 
    if base1 == 10:
        new_base_number = encode(int(decimal_digit), base2)
    else:
        decimal_digit = decode(digits, base1)
        new_base_number = encode(decimal_digit, base2)
    # Return
    return new_base_number



def main():
    """Read command-line arguments and convert given digits between bases."""
    import sys
    args = sys.argv[1:]  # Ignore script file name
    if len(args) == 3:
        digits = args[0]
        base1 = int(args[1])
        base2 = int(args[2])
        # Convert given digits between bases
        result = convert(digits, base1, base2)
        print('{} in base {} is {} in base {}'.format(digits, base1, result, base2))
    else:
        print('Usage: {} digits base1 base2'.format(sys.argv[0]))
        print('Converts digits from base1 to base2')



if __name__ == '__main__':
    main()
    print(decode('123456', 8))
    print(decode('123456', 9))
    print(decode('123456', 10))
    print(decode('123456', 12))
    print(decode('123456', 15))
    print(decode('123456', 20))
    print(decode('123456', 25))
    print(decode('123456', 30))
    print(decode('123456', 35))
    print(decode('123456', 36))

I think my code is pretty robust because it passes all these unittest cases too.

#!python

from bases import decode, encode, convert
import unittest


class BasesDecodeTest(unittest.TestCase):

    def test_decode_binary(self):
        assert decode('0', 2) == 0
        assert decode('1', 2) == 1
        assert decode('10', 2) == 2
        assert decode('11', 2) == 3
        assert decode('100', 2) == 4
        assert decode('101', 2) == 5
        assert decode('110', 2) == 6
        assert decode('111', 2) == 7
        assert decode('1000', 2) == 8
        assert decode('1001', 2) == 9
        assert decode('1010', 2) == 10
        assert decode('1011', 2) == 11
        assert decode('1100', 2) == 12
        assert decode('1101', 2) == 13
        assert decode('1110', 2) == 14
        assert decode('1111', 2) == 15

    def test_decode_decimal(self):
        assert decode('5', 10) == 5
        assert decode('9', 10) == 9
        assert decode('10', 10) == 10
        assert decode('25', 10) == 25
        assert decode('64', 10) == 64
        assert decode('99', 10) == 99
        assert decode('123', 10) == 123
        assert decode('789', 10) == 789
        assert decode('2345', 10) == 2345
        assert decode('6789', 10) == 6789
        assert decode('13579', 10) == 13579
        assert decode('24680', 10) == 24680

    def test_decode_123456(self):
        assert decode('123456', 8) ==42798
        assert decode('123456', 9)  == 74733
        assert decode('123456', 10) == 123456
        assert decode('123456', 12) == 296130
        assert decode('123456', 15) == 871731
        assert decode('123456', 20) == 3545706
        assert decode('123456', 25) == 10596381
        assert decode('123456', 30) == 26004756
        assert decode('123456', 35) == 55656831
        assert decode('123456', 36) == 63970746

    def test_decode_hexadecimal(self):
        assert decode('a', 16) == 10
        assert decode('f', 16) == 15
        assert decode('99', 16) == 153
        assert decode('ff', 16) == 255
        assert decode('ace', 16) == 2766
        assert decode('cab', 16) == 3243
        assert decode('bead', 16) == 48813
        assert decode('face', 16) == 64206
        assert decode('c0ffee', 16) == 12648430
        assert decode('facade', 16) == 16435934
        assert decode('deadbeef', 16) == 3735928559
        assert decode('f007ba11', 16) == 4027038225

    def test_decode_10(self):
        assert decode('10', 2) == 2
        assert decode('10', 4) == 4
        assert decode('10', 8) == 8
        assert decode('10', 10) == 10
        assert decode('10', 16) == 16
        assert decode('10', 25) == 25
        assert decode('10', 32) == 32
        assert decode('10', 36) == 36

    def test_decode_1010(self):
        assert decode('1010', 2) == 10
        assert decode('1010', 4) == 68
        assert decode('1010', 8) == 520
        assert decode('1010', 10) == 1010
        assert decode('1010', 16) == 4112
        assert decode('1010', 25) == 15650
        assert decode('1010', 32) == 32800
        assert decode('1010', 36) == 46692

    def test_decode_101101(self):
        assert decode('101101', 2) == 45
        assert decode('101101', 4) == 1105
        assert decode('101101', 8) == 33345
        assert decode('101101', 10) == 101101
        assert decode('101101', 16) == 1052929
        assert decode('101101', 25) == 9781876
        assert decode('101101', 32) == 33588225
        assert decode('101101', 36) == 60514129


class BasesEncodeTest(unittest.TestCase):

    def test_encode_binary(self):
        # assert encode(0, 2) == '0'  # Should '' be valid?
        assert encode(1, 2) == '1'
        assert encode(2, 2) == '10'
        assert encode(3, 2) == '11'
        assert encode(4, 2) == '100'
        assert encode(5, 2) == '101'
        assert encode(6, 2) == '110'
        assert encode(7, 2) == '111'
        assert encode(8, 2) == '1000'
        assert encode(9, 2) == '1001'
        assert encode(10, 2) == '1010'
        assert encode(11, 2) == '1011'
        assert encode(12, 2) == '1100'
        assert encode(13, 2) == '1101'
        assert encode(14, 2) == '1110'
        assert encode(15, 2) == '1111'

    def test_encode_decimal(self):
        # assert encode(0, 10) == '0'  # Should '' be valid?
        assert encode(5, 10) == '5'
        assert encode(10, 10) == '10'
        assert encode(25, 10) == '25'
        assert encode(64, 10) == '64'
        assert encode(99, 10) == '99'
        assert encode(123, 10) == '123'
        assert encode(789, 10) == '789'
        assert encode(2345, 10) == '2345'
        assert encode(6789, 10) == '6789'
        assert encode(13579, 10) == '13579'
        assert encode(24680, 10) == '24680'

    def test_encode_hexadecimal(self):
        assert encode(10, 16) == 'a'
        assert encode(15, 16) == 'f'
        assert encode(153, 16) == '99'
        assert encode(255, 16) == 'ff'
        assert encode(2766, 16) == 'ace'
        assert encode(3243, 16) == 'cab'
        assert encode(48813, 16) == 'bead'
        assert encode(64206, 16) == 'face'
        assert encode(12648430, 16) == 'c0ffee'
        assert encode(16435934, 16) == 'facade'
        assert encode(3735928559, 16) == 'deadbeef'
        assert encode(4027038225, 16) == 'f007ba11'

    def test_encode_1234(self):
        assert encode(1234, 2) == '10011010010'
        assert encode(1234, 3) == '1200201'
        assert encode(1234, 4) == '103102'
        assert encode(1234, 5) == '14414'
        assert encode(1234, 8) == '2322'
        assert encode(1234, 10) == '1234'
        assert encode(1234, 16) == '4d2'
        assert encode(1234, 32) == '16i'

    def test_encode_248975(self):
        assert encode(248975, 2) == '111100110010001111'
        assert encode(248975, 4) == '330302033'
        assert encode(248975, 8) == '746217'
        assert encode(248975, 10) == '248975'
        assert encode(248975, 16) == '3cc8f'
        assert encode(248975, 25) == 'fn90'
        assert encode(248975, 32) == '7j4f'
        assert encode(248975, 36) == '5c3z'

    def test_encode_into_10(self):
        assert encode(2, 2) == '10'
        assert encode(4, 4) == '10'
        assert encode(8, 8) == '10'
        assert encode(10, 10) == '10'
        assert encode(16, 16) == '10'
        assert encode(25, 25) == '10'
        assert encode(32, 32) == '10'
        assert encode(36, 36) == '10'

    def test_encode_into_1010(self):
        assert encode(10, 2) == '1010'
        assert encode(68, 4) == '1010'
        assert encode(520, 8) == '1010'
        assert encode(1010, 10) == '1010'
        assert encode(4112, 16) == '1010'
        assert encode(15650, 25) == '1010'
        assert encode(32800, 32) == '1010'
        assert encode(46692, 36) == '1010'

    def test_encode_into_101101(self):
        assert encode(45, 2) == '101101'
        assert encode(1105, 4) == '101101'
        assert encode(33345, 8) == '101101'
        assert encode(101101, 10) == '101101'
        assert encode(1052929, 16) == '101101'
        assert encode(9781876, 25) == '101101'
        assert encode(33588225, 32) == '101101'
        assert encode(60514129, 36) == '101101'


class BasesConvertTest(unittest.TestCase):

    def test_convert_from_binary(self):
        assert convert('1101', 2, 3) == '111'
        assert convert('1101', 2, 4) == '31'
        assert convert('1101', 2, 8) == '15'
        assert convert('1101', 2, 10) == '13'
        assert convert('101010', 2, 3) == '1120'
        assert convert('101010', 2, 4) == '222'
        assert convert('101010', 2, 8) == '52'
        assert convert('101010', 2, 10) == '42'
        assert convert('101010', 2, 16) == '2a'
        assert convert('101010', 2, 25) == '1h'
        assert convert('101010', 2, 32) == '1a'
        assert convert('101010', 2, 36) == '16'

    def test_convert_to_binary(self):
        assert convert('111', 3, 2) == '1101'
        assert convert('31', 4, 2) == '1101'
        assert convert('15', 8, 2) == '1101'
        assert convert('13', 10, 2) == '1101'
        assert convert('101', 3, 2) == '1010'
        assert convert('101', 4, 2) == '10001'
        assert convert('101', 8, 2) == '1000001'
        assert convert('101', 10, 2) == '1100101'
        assert convert('101', 16, 2) == '100000001'
        assert convert('101', 25, 2) == '1001110010'
        assert convert('101', 32, 2) == '10000000001'
        assert convert('101', 36, 2) == '10100010001'

    def test_convert_hexadecimal_to_decimal(self):
        assert convert('a', 16, 10) == '10'
        assert convert('f', 16, 10) == '15'
        assert convert('99', 16, 10) == '153'
        assert convert('ff', 16, 10) == '255'
        assert convert('ace', 16, 10) == '2766'
        assert convert('cab', 16, 10) == '3243'
        assert convert('bead', 16, 10) == '48813'
        assert convert('face', 16, 10) == '64206'
        assert convert('c0ffee', 16, 10) == '12648430'
        assert convert('facade', 16, 10) == '16435934'
        assert convert('deadbeef', 16, 10) == '3735928559'
        assert convert('f007ba11', 16, 10) == '4027038225'

    def test_convert_decimal_to_hexadecimal(self):
        assert convert('10', 10, 16) == 'a'
        assert convert('15', 10, 16) == 'f'
        assert convert('153', 10, 16) == '99'
        assert convert('255', 10, 16) == 'ff'
        assert convert('2766', 10, 16) == 'ace'
        assert convert('3243', 10, 16) == 'cab'
        assert convert('48813', 10, 16) == 'bead'
        assert convert('64206', 10, 16) == 'face'
        assert convert('12648430', 10, 16) == 'c0ffee'
        assert convert('16435934', 10, 16) == 'facade'
        assert convert('3735928559', 10, 16) == 'deadbeef'
        assert convert('4027038225', 10, 16) == 'f007ba11'

    def test_convert_hexadecimal_to_binary(self):
        assert convert('a', 16, 2) == '1010'
        assert convert('b', 16, 2) == '1011'
        assert convert('c', 16, 2) == '1100'
        assert convert('d', 16, 2) == '1101'
        assert convert('e', 16, 2) == '1110'
        assert convert('f', 16, 2) == '1111'
        assert convert('c840', 16, 2) == '1100100001000000'
        assert convert('d951', 16, 2) == '1101100101010001'
        assert convert('ea62', 16, 2) == '1110101001100010'
        assert convert('fb73', 16, 2) == '1111101101110011'

    def test_convert_binary_to_hexadecimal(self):
        assert convert('1010', 2, 16) == 'a'
        assert convert('1011', 2, 16) == 'b'
        assert convert('1100', 2, 16) == 'c'
        assert convert('1101', 2, 16) == 'd'
        assert convert('1110', 2, 16) == 'e'
        assert convert('1111', 2, 16) == 'f'
        assert convert('1100100001000000', 2, 16) == 'c840'
        assert convert('1101100101010001', 2, 16) == 'd951'
        assert convert('1110101001100010', 2, 16) == 'ea62'
        assert convert('1111101101110011', 2, 16) == 'fb73'


if __name__ == '__main__':
    unittest.main()
\$\endgroup\$
  • 6
    \$\begingroup\$ You do know that the built-in int can already do the job of your decode, right? Try int("abcdefghijklmnopqrstuvwxyz0123456789", 36). Re-inventing existing functionality is fine (especially for interviews or for learning purposes), but you should say that you are. We even have a tag for it (reinventing-the-wheel). \$\endgroup\$ – Graipher Nov 5 '17 at 9:28
1
\$\begingroup\$
  • Much as I like the presence of docstrings for the functions, I miss one for the module.
  • several docstrings mention int (in base 10) - I hold ints to be without base, represented base 2 if any (→ Bitwise Operations) (and to have a default base to use in conversions from/to str)
  • letter_from_num(num)/digit_from_letter(letter):
    in my book, a digit is (an encoding of) a glyph in a string representation of a number:
    I'd choose digit_for_num(num) and num_for_digit(digit)
    (and hide all "decimal digit special casing" here)
    - These functions are not as defensive as decode()/encode()
    - the literal 97 seems uncalled for
  • decode(digits, base):
    - the comment containing helper function that handles hexadecimal seems incorrect
    (& your IDE or you seem to have missed a variable renamed in the next one)
    - after remainder = number % base, I'd be annoyed if remainder >= 10 and base <= 10
    - the else:-branch in "the non-final-digit handling" seems pointless
    - it is cleaner and conventional to just start digit handling with result *= base unconditionally, obsoleting the enumerate
  • encode(number, base)
    - helps me to make sure that we will work just before digit handling is cryptic, at best
    - assigning new_base_number = new_base_number[::-1]to just return it looks clumsy
  • convert(digits, base1, base2)
    - none of the internal comments looks helpful
    - why special-case base1 == 10?
    - decimal_digit looks a misnomer - value? number, integer?

I miss the (trivial) Decode digits from hexadecimal (base 16). part of the task quoted in the code with embedded documentation presented.

\$\endgroup\$

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.