7
\$\begingroup\$

It is my solution from a challange from edabit.com named "Drunken Python". Thats the task: You need to create two functions to substitute str() and int(). A function called int_to_str() that converts integers into strings and a function called str_to_int() that converts strings into integers.

I hope you can tell me things I could do better and if the algorithms I used (e.g. to calculate the length of an int) are optimal.

# A function called int_to_str() that converts integers into strings and a function called str_to_int() that
# converts strings into integers without using the in built functions "int()" and str().


def str_to_int(num_str):
    dec_places = {11: 10000000000, 10: 1000000000, 9: 100000000, 8: 10000000, 7: 1000000, 6: 100000, 5: 10000, 4: 1000,
                  3: 100, 2: 10, 1: 1}
    char_digit = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    num = 0
    length = len(num_str)
    for i in range(length):
        x = char_digit[num_str[0 - (i + 1)]] * dec_places[i + 1]
        num = num + x
    return num


def calculate_length(num):
    div = 10
    i = 1
    while num / div >= 1:
        div = div * 10
        i = i + 1
    return i


def int_to_str(num_int):
    word = ""
    div = 10
    char_digit = {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9'}
    length = calculate_length(num_int)
    for i in range(length):
        x = (num_int % div) // (div // 10)
        word = char_digit[x] + word
        num_int = num_int - x
        div = div * 10
    return word
```
\$\endgroup\$
3
  • 1
    \$\begingroup\$ dec_places = {11: 10000000000, 10: 1000000000, 9: 100000000, 8: 10000000, 7: 1000000, 6: 100000, 5: 10000, 4: 1000, 3: 100, 2: 10, 1: 1} : Consider 10 raised to power (k - 1)'s python equivalent. \$\endgroup\$ – aki Aug 6 '20 at 11:30
  • 2
    \$\begingroup\$ Simpler: str_to_int = eval; int_to_str = repr :-P \$\endgroup\$ – superb rain Aug 6 '20 at 12:14
  • 1
    \$\begingroup\$ "You need to create two functions to substitute str() and int()." That should be "You need to create two functions to substitute for str() and int()." Given how important wording is in programming, it's disappointing to see people being so cavalier. \$\endgroup\$ – Acccumulation Aug 7 '20 at 5:35
4
\$\begingroup\$

Some minor comments to get started

  • As str_to_int is intended to be an implementation of int you can name it as such. Either int_ or plain int would be fine as names.
  • The 0 in num_str[0 - (i + 1)] is not needed.
  • Appending and prepending to a string in a loop has not entirely obvious quadratic time complexity. It won't matter much for short strings and adding single characters at a time, but be careful in choosing the right data structure.
  • num = num + x is more commonly written as num += x
  • If you don't use the loop variable, you can leave it out by replacing it with an underscore. for _ in range(length):

The code to convert a string to an int is unnecessarily limited. A KeyError is raised on a test like x = str(10**16); str_to_int(x).

Replacing the dictionary with pow will fix this.

10 ** (k - 1)  # or you can write pow(10, k - 1)

Which in the code becomes

def str_to_int(num_str):
    # Deleted dec_places
    char_digit = {'0': 0, '1': 1, '2': 2, '3': 3, '4': 4, '5': 5, '6': 6, '7': 7, '8': 8, '9': 9}
    num = 0
    length = len(num_str)
    for i in range(length):
        x = char_digit[num_str[-(i + 1)]] * 10 ** i  # simplified from 10 ** ((i + 1) - 1)
        num += x
    return num

int_to_str does a bit more work than it needs to. If you remove the line num = num - x you'll find the results are still the same.

There is a slightly better approach you can take. Instead of multiplying up div, you could instead divide down num_int. The advantage of the suggested approach is that you will always be dividing and and modding by 10, rather than increasing powers of 10. This (should) be a bit faster overall, since it is easier to do arithmetic with small numbers.

Modifying your current code to use this method might look like:

def str_(num_int):
    word = ""
    char_digit = {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9'}
    length = calculate_length(num_int)
    for i in range(length):
        x = num_int % 10
        num_int //= 10
        word = char_digit[x] + word
    return word

You can use the same idea to simplify calculate_length. But even better, you don't need to calculate the length anymore, since you can stop when num_int has had all its digits divided out. You will need a special case for 0 though.

def str_(num_int):
    if num_int == 0:
        return '0'
    word = ""
    char_digit = {0: '0', 1: '1', 2: '2', 3: '3', 4: '4', 5: '5', 6: '6', 7: '7', 8: '8', 9: '9'}
    while num_int:
        x = num_int % 10
        num_int //= 10
        word = char_digit[x] + word
    return word

int has had a good bit more time spent on it, and as such has a little more robustness and functionality. You might consider whether you want to handle any of these cases.

  • Negative numbers.
  • Bases other than base 10.
  • Whitespace around the number (' 777 \t').
  • Underscore as a separator ('10_000').
  • A leading '+' ('+42')
\$\endgroup\$
3
  • \$\begingroup\$ Wow! I am so thankful for your help. I already learned so much from this! I think I'll try recoding the whole program to make sure I remember everything correctly and I'll try to implement the extra features(espacially the negative numbers). But again, thank you very much! \$\endgroup\$ – Jonas Aug 6 '20 at 15:49
  • 1
    \$\begingroup\$ About the quadratic runtime warning for strings: I just measured how long the string concats and divisions-by-10 take for a number with 30000 digits. The concats took about 0.02 seconds, the divisions about 1.5 seconds: repl.it/repls/GrumpyBuzzingSoftware#main.py \$\endgroup\$ – superb rain Aug 6 '20 at 18:54
  • 1
    \$\begingroup\$ That is surprising. I will leave my comment on quadratic behaviour in, on the off chance someone reading has never heard of it before. \$\endgroup\$ – spyr03 Aug 6 '20 at 19:37
4
\$\begingroup\$

Keep it simple

Your functions are way too complicated. First, you can convert a single digit from integer to character by using ord() and the fact that the ordinals of the characters '0' to '9' are consecutive. Furthermore, you don't need to calculate the length of an integer if you reverse the way you build up the string. Here are example implementations that are simpler:

def str_to_int(num_str):
    num = 0

    for char in num_str:
        num *= 10
        num += ord(char) - ord('0')

    return num

def int_to_str(num_int):
    word = ""

    while num_int:
        word = chr(ord('0') + num_int % 10) + word
        num_int //= 10

    return word or "0"

Integers can be negative

Your code (and my example simplification above) does not handle negative integers.

\$\endgroup\$
2
  • 2
    \$\begingroup\$ ord('0') is a constant value. Can be put outside loop \$\endgroup\$ – hjpotter92 Aug 6 '20 at 13:09
  • \$\begingroup\$ Thank you! Your idea to use unicode numbers is surely way more efficient and very clever. I didn't even know about ord() and chr() before. \$\endgroup\$ – Jonas Aug 6 '20 at 15:52

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.