简体   繁体   中英

Speeding Up a Python While Loop

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 (eg 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 (eg 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!

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)
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

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.

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])

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))

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.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM