Search code examples
pythonlazy-evaluation

Iteration over multiple maps


I have a question about how Python(3) internally loops when computing multiple maps. Here's a nonsense example:

from random import randint

A = [randint(0,20) for _ in range(100)]
map1 = map(lambda a: a+1, A)
map2 = map(lambda a: a-1, map1)
B = list(map2)

Because map() produces a lazy expression, nothing is actually computed until list(map2) is called, correct?

When it does finally do this computation, which of these methods is it more akin to?

Loop method 1:

A = [randint(0,20) for _ in range(100)]
temp1 = []
for a in A:
    temp1.append(a+1)

B = []
for t in temp1:
    B.append(t-1)

Loop method 2:

A = [randint(0,20) for _ in range(100)]
B = []
for a in A:
    temp = a+1
    B.append(temp-1)

Or does it compute in some entirely different manner?


Solution

  • In general, the map() function produces a generator, which in turn doesn't produce any output or calculate anything until it's explicitly asked to. Converting a generator to a list is essentially akin to asking it for the next element until there is no next element.

    We can do some experiments on the command line in order to find out more:

    >>> B = [i for i in range(5)]
    >>> map2 = map(lambda b:2*b, B)
    >>> B[2] = 50
    >>> list(map2)
    [0, 2, 100, 6, 8]
    

    We can see that, even though we modify B after creating the generator, our change is still reflected in the generator's output. Thus, it seems that map holds onto a reference to the original iterable from which it was created, and calculates one value at a time only when it's asked to.


    In your example, that means the process goes something like this:

    A = [2, 4, 6, 8, 10]
    b = list(map2)
        b[0] --> next(map2) = (lambda a: a-1)(next(map1))
                 --> next(map1) = (lambda a: a+1)(next(A)) 
                     --> next(A) = A[0] = 2
                 --> next(map1) = 2+1 = 3
             --> next(map2) = 3-1 = 2
        ...
    

    In human terms, the next value of map2 is calculated by asking for the next value of map1. That, in turn, is calculated from A that you originally set.