1
import sys

def keepsumming(number):
    numberlist = []
    for digit in str(number):
        numberlist.append(int(digit))
    total = reduce(add, numberlist)
    if total > 9:
        keepsumming(total)
    if total <= 9:
        return total

def add(x,y):
    return x+y

keepsumming(sys.argv[1])

I want to create a function that adds the individual digits of any number, and to keep summing digits until the result is only one digit. (e.g. 1048576 = 1+0+4+8+5+7+6 = 31 = 3+1 = 4). The function seems to work in some laces but not in others. for example:

$python csp39.py 29

returns None, but:

$python csp39.py 30

returns 3, as it should...

Any help would be appreciated!

9 Answers 9

7

As others have mentioned, the problem seems to be with the part

if total > 9:
    keepsumming(total)  # you need return here!

Just for completeness, I want to present you some examples how this task could be solved a bit more elegantly (if you are interested). The first also uses strings:

while number >= 10:
  number = sum(int(c) for c in str(number))

The second uses modulo so that no string operations are needed at all (which should be quite a lot faster):

while number >= 10:
  total = 0
  while number:
    number, digit = divmod(number, 10)
    total += digit
  number = total

You can also use an iterator if you want to do different things with the digits:

def digits(number, base = 10):
  while number:
    yield number % base
    number //= base

number = 12345

# sum digits
print sum(digits(number))
# multiply digits
from operator import mul
print reduce(mul, digits(number), 1)

This last one is very nice and idiomatic Python, IMHO. You can use it to implement your original function:

def keepsumming(number, base = 10):
  if number < base:
    return number
  return keepsumming(sum(digits(number, base)), base)

Or iteratively:

def keepsumming(number, base = 10):
  while number >= base:
    number = sum(digits(number, base))

UPDATE: Thanks to Karl Knechtel for the hint that this actually is a very trivial problem. It can be solved in one line if the underlying mathematics are exploited properly:

def keepsumming(number, base = 10):
  return 1 + (number - 1) % (b - 1)
Sign up to request clarification or add additional context in comments.

10 Comments

Neither of these will iterate until there's only one digit left, though.
@DSM: Thanks, I must have missed that O.o I changed the samples.
def recursivelysumdigits(num): return num if num < 10 else recursivelysumdigits(sum(map(int, str(num))))
@agf: Yeah, that's basically my first example written using tail recursion (which is quite slow because of string manipulation and Python not optimizing tail calls). Also it uses map instead of list comprehensions. What about it?
If you use //= instead of /= you'll unambiguously call the floor division, therefore making the code to work even on python-3.x :)
|
5

There's a very simple solution:

while number >= 10:
    number = sum(divmod(number, 10))

5 Comments

Nice trick indeed! This is definitely the best solution so far.
+1 for originality, for making a clearly written iterative version, for conciseness, and for the flexibility to work with alternate bases :-)
This doesn't really follow the same algorithm; the number isn't fully broken into digits each time. By more or less the same logic that allows this to work, we might equally well just do number %= 9.
@KarlKnechtel: I don't understand what you want to say. While the algorithm is a little different, the result is the same. Your suggestion, number %= 9, gives a different result; why do you call that equally well?
@KarlKnechtel: I now see what you mean, but would'nt that be number = number % 9 if number % 9 else 9?
4

I am fairly sure you need to change

if total > 9:
        keepsumming(total)

into

if total > 9:
        return keepsumming(total)

As with most recursive algorithms, you need to pass results down through returning the next call.

Comments

2

and what about simply converting to string and summing?

res = 1234567
while len(str(res)) > 1 :
    res = sum(int(val) for val in str(res))

return res

Thats's what I use to do :)

Comments

2

Here is a clean tail-recursive example with code that is designed to be easy to understand:

def keepsumming(n):
    'Recursively sum digits until a single digit remains:  881 -> 17 -> 8'
    return n if n < 10 else keepsumming(sum(map(int, str(n))))

2 Comments

Very nice. I don't often use map, preferring generators and list comprehensions, but it clearly adds to the conciseness here.
@hughdbrown map (with lambda) became much less popular once list comprehensions were introduced (the latter being much more flexibile), but now map() is making a comeback as a vehicle for parallelizing computation (see multiprocessing.map for example or look a map/reduce implementation).
1

Here you go:

>>> sumdig = (lambda recurse: (lambda fix: fix(lambda n: sum(int(c) for c in str(n)))) (recurse(lambda f, g: (lambda x: (lambda d, lg: d if d == lg else f(f,g)(d))(g(x),x)))))(lambda f: lambda x: f(f,x))
>>> sumdig(889977)
3

You are sure to get full, if not extra, credit for this solution.

1 Comment

Re: "You are sure to get full, if not extra, credit for this solution." Of all the solutions here, this is far and away the most indirect and most recursive. Accordingly, I'd say it is the least likely to be easily explainable to a professor and the most likely to be flagged as not original work. If I were betting, I'd say that the OP would certainly get extra attention but not necessarily full marks.
0

Your code as currently written need to replace the recursive call to keepsumming(total) with return keepsumming(total); python does not automatically return the value of the last evaluated statement.

However, you should note that your code has redundancies.

for digit in str(number):
    numberlist.append(int(digit))
total = reduce(add, numberlist)

should become

from operator import add
total = reduce(add, (int(digit) for digit in str(number)))

4 Comments

Where reduce(add, (..)) should become sum(..)?
@NiklasB.: Although sum may be more idiomatic for python, reduce(add, ...) is more recognisable by functional programmers from other languages :)
I absolutely agree. Still, it can be a useful shortcut, and you don't need to import add.
@NiklasB. I think importing add is a useful demonstration for a questioner defining his own add.
0

A code golf-able version that doesn't require a while loop, or recursion.

>>> import math
>>> (lambda x: sum(int((x * 10 ** -p) % 10) for p in range(math.ceil(math.log(x, 10)))))(1048576)
31

This is probably what I'd use though.

def sum_digits(number):
    return sum(
        int(number * (10 ** -place) % 10)
        for place in range(math.ceil(math.log(number, 10)))
    )

It's easier to see what's going on if you append to a list instead of summing the integer values.

def show_digits(number):
    magnitude = int(math.log(number, 10))
    forms = []
    for digit_place in range(magnitude + 1):
        form = number * (10 ** -digit_place)  # force to one's place
        forms.append(form)
    return forms
>>> show_digits(1048576)
[1048576, 104857.6, 10485.76, 1048.576, 104.8576, 10.48576, 1.048576]

Comments

-1

For keepsumming that takes a string:

def keepsumming(number):
    return number if len(number) < 2 \
        else keepsumming(str(sum(int(c) for c in number)))

For keepsumming that takes a number:

def keepsumming(number):
    return number if number < 10 \
        else keepsumming(sum(int(c) for c in str(number)))

5 Comments

I think it would be a bit more logical for keepsumming to take a number, instead of a string.
In [271]: keepsumming('1234') Out[271]: '1'
@Marcin: '1234' --> str(1 + 2 + 3 + 4) == '10'; '10' --> str(1 + 0) == '1'; That looks right to me. What do you think it should be? I've provided a version that takes a number as input (as opposed to a string composed of digits) and it returns 1.
@Niklas B.: Yes, the doctests are long and unreadable. I was mostly responding to Marcin who claimed the results are wrong. I don't think they are, and my doctests say what I think is supposed to happen.
@hughdbrown: I think Marcin didn't read the question properly, otherwise he wouldn't have posted those comments.

Your Answer

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.