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()
asked Nov 4, 2017 at 21:49
\$\endgroup\$
1
  • 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\$ Commented Nov 5, 2017 at 9:28

1 Answer 1

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.

answered Jan 3, 2019 at 23:13
\$\endgroup\$

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.