Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to calculate a cumulative product of a list using list comprehension

I'm trying my hand at converting the following loop to a comprehension.

Problem is given an input_list = [1, 2, 3, 4, 5] return a list with each element as multiple of all elements till that index starting from left to right.

Hence return list would be [1, 2, 6, 24, 120].

The normal loop I have (and it's working):

l2r = list()
for i in range(lst_len):
    if i == 0:
        l2r.append(lst_num[i])
    else:
        l2r.append(lst_num[i] * l2r[i-1])
like image 554
ridua Avatar asked Jun 07 '20 00:06

ridua


People also ask

How do you find the cumulative product?

B = cumprod( A ) returns the cumulative product of A starting at the beginning of the first array dimension in A whose size does not equal 1. If A is a vector, then cumprod(A) returns a vector containing the cumulative product of the elements of A .

How do you find the cumulative product in Python?

Pandas Series: cumprod() function The cumprod() function is used to get cumulative product over a DataFrame or Series axis. Returns a DataFrame or Series of the same size containing the cumulative product. The index or the name of the axis. 0 is equivalent to None or 'index'.

How do you do a cumulative sum list in Python?

We declare an empty list cum_list to which we will append elements to form the cumulative sum list. Initialize a sum variable sm=0. Start iterating over the input list, with each iteration we increment the sum value to previous value+ the current element. On each iteration, the sum value is appended to the cum_list.

What is a cumulative product?

A cumulative product is a sequence of partial products of a given sequence.


4 Answers

Python 3.8+ solution:

  • := Assignment Expressions
lst = [1, 2, 3, 4, 5]

curr = 1
out = [(curr:=curr*v) for v in lst]
print(out)

Prints:

[1, 2, 6, 24, 120]

Other solution (with itertools.accumulate):

from itertools import accumulate

out = [*accumulate(lst, lambda a, b: a*b)]
print(out)
like image 126
Andrej Kesely Avatar answered Oct 29 '22 04:10

Andrej Kesely


A recursive function could help.

input_list = [ 1, 2, 3, 4, 5]

def cumprod(ls, i=None):
    i = len(ls)-1 if i is None else i
    if i == 0:
        return 1
    return ls[i] * cumprod(ls, i-1)

output_list = [cumprod(input_list, i) for i in range(len(input_list))]

output_list has value [1, 2, 6, 24, 120]


This method can be compressed in python3.8 using the walrus operator

input_list = [ 1, 2, 3, 4, 5]

def cumprod_inline(ls, i=None):
    return 1 if (i := len(ls)-1 if i is None else i) == 0 else ls[i] * cumprod_inline(ls, i-1)

output_list = [cumprod_inline(input_list, i) for i in range(len(input_list))]

output_list has value [1, 2, 6, 24, 120]


Because you plan to use this in list comprehension, there's no need to provide a default for the i argument. This removes the need to check if i is None.

input_list = [ 1, 2, 3, 4, 5]

def cumprod_inline_nodefault(ls, i):
    return 1 if i == 0 else ls[i] * cumprod_inline_nodefault(ls, i-1)

output_list = [cumprod_inline_nodefault(input_list, i) for i in range(len(input_list))]

output_list has value [1, 2, 6, 24, 120]


Finally, if you really wanted to keep it to a single , self-contained list comprehension line, you can follow the approach note here to use recursive lambda calls

input_list = [ 1, 2, 3, 4, 5]

output_list = [(lambda func, x, y: func(func,x,y))(lambda func, ls, i: 1 if i == 0 else ls[i] * func(func, ls, i-1),input_list,i) for i in range(len(input_list))]

output_list has value [1, 2, 6, 24, 120]

It's entirely over-engineered, and barely legible, but hey! it works and its just for fun.

like image 37
Ron Avatar answered Oct 29 '22 03:10

Ron


Well, you could do it like this(a):

import math

orig = [1, 2, 3, 4, 5]
print([math.prod(orig[:pos]) for pos in range(1, len(orig) + 1)])

This generates what you wanted:

[1, 2, 6, 24, 120]

and basically works by running a counter from 1 to the size of the list, at each point working out the product of all terms before that position:

pos   values    prod
===  =========  ====
 1   1             1
 2   1,2           2
 3   1,2,3         6
 4   1,2,3,4      24
 5   1,2,3,4,5   120

(a) Just keep in mind that's less efficient at runtime since it calculates the full product for every single element (rather than caching the most recently obtained product). You can avoid that while still making your code more compact (often the reason for using list comprehensions), with something like:

def listToListOfProds(orig):
    curr = 1
    newList = []
    for item in orig:
        curr *= item
        newList.append(curr)
    return newList

print(listToListOfProds([1, 2, 3, 4, 5]))

That's obviously not a list comprehension but still has the advantages in that it doesn't clutter up your code where you need to calculate it.

People seem to often discount the function solution in Python, simply because the language is so expressive and allows things like list comprehensions to do a lot of work in minimal source code.

But, other than the function itself, this solution has the same advantages of a one-line list comprehension in that it, well, takes up one line :-)

In addition, you're free to change the function whenever you want (if you find a better way in a later Python version, for example), without having to change all the different places in the code that call it.

like image 42
paxdiablo Avatar answered Oct 29 '22 04:10

paxdiablo


This should not be made into a list comprehension if one iteration depends on the state of an earlier one!

If the goal is a one-liner, then there are lots of solutions with @AndrejKesely's itertools.accumulate() being an excellent one (+1). Here's mine that abuses functools.reduce():

from functools import reduce

lst = [1, 2, 3, 4, 5]

print(reduce(lambda x, y: x + [x[-1] * y], lst, [lst.pop(0)]))

But as far as list comprehensions go, @AndrejKesely's assignment-expression-based solution is the wrong thing to do (-1). Here's a more self contained comprehension that doesn't leak into the surrounding scope:

lst = [1, 2, 3, 4, 5]

seq = [a.append(a[-1] * b) or a.pop(0) for a in [[lst.pop(0)]] for b in [*lst, 1]]

print(seq)

But it's still the wrong thing to do! This is based on a similar problem that also got upvoted for the wrong reasons.

like image 36
cdlane Avatar answered Oct 29 '22 04:10

cdlane