views:

233

answers:

3

I'm trying to find a neat way of summing a list and a list of lists in the same function, so far I've got:

import operator
"""
 Fails late for item = ['a', 'b']
"""   
def validate(item):
    try:
        return sum(item) == sum(range(1, 10))
    except TypeError:
        return sum(reduce(operator.add, item)) == sum(range(1, 10))

"""
 Not valid for item = [1,2,[3,4,5]]
"""
def validate2(item):
        if isinstance(item[0], int):
            return sum(item) == sum(range(1, 10))
        else:
            return sum(reduce(operator.add, item)) == sum(range(1, 10))


print validate([1, 2, 3, 4, 5, 6, 7, 8, 9])
print validate([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

print validate2([1, 2, 3, 4, 5, 6, 7, 8, 9])
print validate2([[1, 2, 3], [4, 5, 6], [7, 8, 9]])

but neither of these seem quite right to me (reasons in the doc strings). What I want to know is if there is a better way of summing lists and lists of lists that doesn't require me to catch exceptions or actually analyse the list before the function decides what to do.

Obviously I'm still expecting ['a', 'b'] to be invalid.

+5  A: 

Perhaps you'd find it easier to flatten the list first?

def flatten(xs):
     for x in xs:
        try:
            sub = iter(x)
        except TypeError:
            yield x
        else:
            for y in flatten(sub):
                yield y

With the above, you can do this:

In [4]: fs = flatten([1,2,[3,4,[5,6],7],8,[9,10]])

In [5]: list(fs)
Out[5]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Michał Marczyk
You can move `for x in xs` inside the `else:` (and change to `yield xs` in the except) to allow this to work even on scalars. I always like putting recursion conditions at the entrance to the function.
Beni Cherniavsky-Paskin
@Beni: True enough, that's possible when desired. The bit after the `else:` would become `for x in xs: for y in flatten(x): yield y`. Thanks for the comment!
Michał Marczyk
+3  A: 

Don't forget to describe exactly what you're trying to do. I'm assuming you mean to sum all values to a single value, and not to get eg. [[1,2],[3,4]] -> [3,7]. Here's simple recursion; five lines of code if you skip the tests:

def sums(it):
    """
    >>> sums(1)
    1
    >>> sums([1,2,3])
    6
    >>> sums([1,2,3,[4,5]])
    15
    >>> sums(['a','b'])
    Traceback (most recent call last):
    ...
    TypeError: unsupported operand type(s) for +: 'int' and 'str'
    """
    if getattr(it, "__iter__", None):
        return sum(map(sums, it))
    else:
        return it

if __name__ == "__main__":
    import doctest
    doctest.testmod()
Glenn Maynard
I'd not seen the doctest module before, thanks for introducing me to it +1
MattyW
A: 

The external numpy module has many operations (including sum()) which work similarly on scalars, vectors, matrices and even higher-dimensional arrays...

Note however that it doesn't work on mixed lists like [1,2,[3,4,5]], only square matrices! So it doesn't exactly answer your question.

Beni Cherniavsky-Paskin