简体   繁体   中英

Understanding reduce with enumerate for a list of lists

I am trying to multiply the first number, 1, of the first list, the second number of the second list, 5, and so on for a list of lists. For example, for [[1, 2, 3], [4, 5, 6], [7, 8, 9]], I'd like to get 1*5*9.

While there are many possible ways to do this, I wondered how reduce does with enumerate:

def test(m):
    return reduce(lambda a, b: a[1][a[0]]*b[1][b[0]], enumerate(m))

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

I would think that a in the beginning is (0, [1,2,3]) so that a[1] is [1,2,3], and a[0] is 0, and so a[1][a[0]] is 1.

However, I get the following exception:

return reduce(lambda a, b: a[1][a[0]]*b[1][b[0]], enumerate(mat))
TypeError: 'int' object has no attribute '__getitem__'

Why is a integer?

Your final and intermediate values are simple integers. So you should start with 1 , and then lambda will always get an integer as a , namely the product so far. And b will be the next enumerated item. So here's how to do it:

>>> reduce(lambda a, b: a * b[1][b[0]],
           enumerate([[1, 2, 3], [4, 5, 6], [7,8,9]]), 1)
45

Python 2 still allows this, btw:

>>> reduce(lambda a, (i, b): a * b[i],
           enumerate([[1, 2, 3], [4, 5, 6], [7,8,9]]), 1)
45

So since you're trying to reach a final value of the diagonal numbers multiplied together this is how O'd do it:

Example:

def idiagonal(xs_of_ys):
    for i, x in enumerate(xs_of_ys):
        for j, y in enumerate(x):
            if i == j:
                yield y


print reduce(lambda x, y: x * y, idiagonal(xs), 1)  # prints 45

From what you explained in one of the comments - you are trying to multiply the diagonal elements of a matrix and now I understand why you want to 'enumerate' - that's indeed correct 'cos you want to get that index. So following code would do that for you.

First map gets all the required elements in a list and the reduce then multiplies them to the desired value. Note: a couple of caveats, enumerate gives you out a tuple, so you've to add it as (x,y) in the map lambda.

a = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
reduce(lambda p,q: p*q, map(lambda (x,y): y[x], enumerate(a)), 1)

or

reduce(lambda p,q: p*q, [y[x] for (x,y) in enumerate(a)], 1)

Edit: Added a list comprehension version instead of map-lambda.

Note the list comprehension version is just about as fast as the above answer (only about 10% slower), for readability, I'd trade that.

Even though Stefan's answer is perfect in functionality, I'd like to point out that ... we are not the compiler, you know. There is no preventing us from writing something more readable that even illustrates what you are trying to do without explaining a thing:

from collections import namedtuple
Row = namedtuple('Row', ['num', 'columns'])

m = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
reduce(lambda result, row: result * row.columns[row.num], 
       (Row(*data) for data in enumerate(m)), 1)

Looking at the reduce function, now we know that you want to accumulate on the item at column number = row number. If that doesn't spell diagonal, I don't know what does :)

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