2

I have the following code:

import math

print "Hey, lets solve Task 4 :)"

number1 = input ("How many digits do you want to look at? ")
number2 = input ("What would you like the digits to add up to? ")
final = []

if number1 == 1:
    cow = range(1,10)
elif number1 == 2:
    cow = range(10,100)
elif number1 == 3:
    cow = range(001,1000)
elif number1 == 4:
    cow = range(1000,10000)
elif number1 == 5:
    cow = range(10000,100000)
elif number1 == 6:
    cow = range(100000,1000000)
elif number1 == 7:
    cow = range(1000000,10000000)
elif number1 == 8:
    cow = range(10000000,100000000)
elif number1 == 9:
    cow = range(100000000,1000000000)
elif number1 == 10:
    cow = range(1000000000,10000000000)

number3 = cow[-1] + 1
number10 = number3
number8 = number3 - 1

if number1 == 1:
    test = range(1,number10)
elif number1 == 2:
    test = range(00,number10)
elif number1 == 3:
    test = range(000,number10)
elif number1 == 4:
    test = range(0000,number10)
elif number1 == 5:
    test = range(00000,number10)
elif number1 == 6:
    test = range(000000,number10)
elif number1 == 7:
    test = range(0000000,number10)
elif number1 == 8:
    test = range(00000000,number10)
elif number1 == 9:
    test = range(000000000,number10)
elif number1 == 10:
    test = range(0000000000,number10)

if number1 == 1:
    number7 = number8
elif number1 == 2:
    number7 = number8 + 1
elif number1 == 3:
    number7 = number8 + 1
elif number1 == 4:
    number7 = number8 + 1
elif number1 == 5:
    number7 = number8 + 1
elif number1 == 6:
    number7 = number8 + 1
elif number1 == 7:
    number7 = number8 + 1
elif number1 == 8:
    number7 = number8 + 1
elif number1 == 9:
    number7 = number8 + 1
elif number1 == 10:
    number7 = number8 + 1



n = 0
while n < number7:

    a = test[n]
    a = str(a)
    print a

    number4 = sum(int(x) for x in a)

    if number4 == number2:
        final.append(number4)

    n = n + 1



print len(final)

Basically, this code works out how many digits of numbers contain integers which add up to certain numbers. When it is run, it asks how many digits you would like (e.g. 4) and what number you would like them to add up to. For example, you could pick 4 and 18 and it would calculate how many numbers from 1000 - 9999 have integers that add up to 18 (e.g. 4545).

The problem is, the last question asks how many 10 digit numbers add up to 39. Using this code, it would take my computer ages because it has to count from 1 all the way up to the largest 10 digit number. When I tried it, it crashed my computer!

Is there a way to speed up the loop?

Thank you!

2
  • Are you aware of cython? Commented Oct 23, 2013 at 8:27
  • 3
    Your code is crashing because range(1000000000,10000000000) is generating 9 billion 64 bit integers in memory, which is 72 GB of memory. Commented Oct 23, 2013 at 8:42

6 Answers 6

2

You're pushing the computer too hard, dude :) Calculating trillions of numbers like that.

Looks like xrange is more suitable for your case.

cow = xrange(1000, 10000)
Sign up to request clarification or add additional context in comments.

2 Comments

No 2 digit number could add up to 45, it's the integers I'm interested in. E.g. 45 add's up to 9 and 17 add's up to 8. I would need to look at all the 2 digit numbers to get an answer.
Does xrange still work out every digit? For every 10 digit number it would still take ages. I just tried it then and it crashed haha. Thanks for answering so fast though!
2
import math

print "Hey, lets solve Task 4 :)"

number1, number2, answer = 4, 18, 0

for num in xrange(10 ** (number1 - 1), 10 ** number1):
    total = 0
    while num:
        total += num % 10
        num /= 10
    if total == number2: answer += 1

print answer

2 Comments

Awesome, but cruel :)
num //= 10 maybe slightly better, case any python3 users?
1

The fastest solution when you have large numbers isn't going to look at the individual numbers at all. If you want to find out how many 10 digit numbers sum to 39, first find all the unique combinations of 10 digits that sum to 39 (2630 of them).

The way you can do that is for each digit d, find all the unique combinations of 9 digit numbers summing to 39-d where no digit is less than d.

def unique_combos(digits, total, smallest=0):
    if digits*9 < total or digits*smallest > total:
        return
    if digits==1:
        yield [total]
        return

    for i in range(smallest, 10):
        for combo in unique_combos(digits-1, total-i, i):
            yield [i]+combo

Then for each unique combination of digits find out how many ways it can be arranged. That's just simple math, only you have to avoid counting any that begin with leading zeros.

import operator
from collections import Counter
from math import factorial
def npermutations(l):
    """From stackoverflow 16453188"""
    num = factorial(len(l))
    mults = Counter(l).values()
    den = reduce(operator.mul, (factorial(v) for v in mults), 1)
    return num / den

Now just add up that figure for each combination and you have your answer. Given that we're no longer worried too much about speed I just subtracted the number with fewer digits.

def answer(digits, total):
    n_or_fewer = sum(npermutations(l) for l in unique_combos(digits, total))
    fewer = sum(npermutations(l) for l in unique_combos(digits-1, total))
    print("There are {} {}-digit numbers with digits summing to {}".format(
        n_or_fewer - fewer,
        digits, total))

if __name__=='__main__':
    answer(4,18)
    answer(10,39)

Output takes less than 1 second:

C:\Temp>digitsum2.py
There are 615 4-digit numbers with digits summing to 18
There are 307100365 10-digit numbers with digits summing to 39

For completeness, Sudipta suggested optimising how you step through the numbers. Here's some code that does that: it goes directly from one number to the next, and gets the same answer for 4 digit numbers but I still got bored running it for the 307 million 10 digit values. It could probably be optimised quite a bit, but brute force is the wrong way to go here.

def smallest_sum(n, digits, first=1):
    """Find the smallest number with 'digits' digits that sum to 'n'
    Returns the value as a list of digits because that's what we need next anyway"""
    k = max(first,n + 9 - 9*digits)
    assert k <= 9, "Impossible values"
    if digits > 1:
            return [k]+smallest_sum(n-k, digits-1, 0)
    return [k]

def all_sums(digits):
    """Find the next string of digits with the same sum.
    We can do this by finding the last non-zero digit and decrementing it
    then incrementing the previous non-nine, and sorting any digits that
    follow it."""
    yield digits
    while True:
        was = list(digits)
        for i in range(len(digits)-1,-1,-1):
            if digits[i] != 0:
                decrement = i
                break
        else:
            return
        for i in range(decrement-1,-1,-1):
            if digits[i] != 9:
                increment = i
                break
        else:
            return
        digits[decrement] -= 1
        digits[increment] += 1
        if increment < len(digits)-2:
            digits[increment+1:] = digits[increment+1:][::-1]
        assert digits > was
        yield digits

def count(num_digits, total):
    try:
        digits = smallest_sum(total, num_digits)
    except AssertionError:
        return 0
    return sum(1 for _ in all_sums(digits))

if __name__=='__main__':
    print(count(4,18))
    print(count(10,39))

3 Comments

Wow, this is amazing! Thank you so much. I need to include the values from 0 (e.g. 0000000001 is a 10 digit number). To do this, would I need to change something in your second block of code? Thanks again :)
In that case just use 'n_or_fewer' directly, don't subtract (or even calculate) the 'fewer' value.
Thanks for your help, you've been very kind!
1

You seem to be doing this with some brute force method, you should find a way to skip some iterations.

For example you choose 4 and 18 and you are on number 1809 :

1809 = 18  # match
1810 = 10
1811 = 11
1812 = 12

There is a pattern, you need to optimize it to skip some iterations.

Comments

1

Your brute force algorithm is exponential, so it will be slow no matter how you implement it. aIKid's advice will help you avoid flooding the memory, but it'll still take ages to run.

I would suggest a different, incremental algorithm:

number1 = 4
number2 = 18

options={}
for i in range(1,10):
    options[i]=1 # for single digit numbers there is one option for each target total

for _ in range(1,number1):
    new_options={}
    for i in range(0,10): # for each new digit we can add
        for option in options: # go through all the options
            cur_total=i+option
            if cur_total not in new_options:
                new_options[cur_total]=0
            new_options[cur_total]+=options[option] # and calc how many options this digit add for each target total
    options=new_options

print(options[number2])

Comments

0

Firstly, as many other answers have indicated: you need to use xrange , which acts like a generator and gives you each number one by one and saves a lot of memory.

You can also improve your algorithm by incorporating some optimizations:

  1. Check for min and max numbers in the given range whose integers add up to given sum.

    Example - Your numbers are 4 and 18. Instead of checking all numbers, you can run the loop for 1089 to 9900.

  2. If and when you find a match, the immediately next number can never be a match (because its sum of digits will be either 1 more than the present number, or 8,17...less than the present no.). Similarly, even the next to next cannot be used...and so on, you can find a pattern to safely skip such numbers (A similar strategy has been suggested in another answer)

  3. At the very beginning, you can check for some edge cases like if the input of sum is less than 9.

    Example- Your numbers are 4(no. of digits) and 5(sum of digits). Then we can safely rule out all the numbers which have digits 6,7,8,9 in them.

3 Comments

I don't think you need your edge case example. Find the smallest number that meets the condition and find the rule for going from one matching number to the next lower, and then you can just iterate through all the answers and you'll never see those edge cases. Mind you, the rule for going from one to the next is not entirely trivial.
@Duncan yes sure, if we can figure out a way of going from one matching number to the next one, then we need not iterate over the whole list at all! If you can suggest something about how to do that, please add it as an answer. I'll also think about this approach.
I posted an answer that has code to skip directly from one matching number to the next. Also code that works out the answer without generating any matching numbers (and therefore runs a lot, lot faster).

Your Answer

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