1

What would be the proper conversion of the following map statement into a for loop?

map(lambda x: map(lambda y: map(lambda z: x*y*z, [5,6]), [3,4]), [1,2])
# [[[15, 18], [20, 24]], [[30, 36], [40, 48]]]

My first attempt was something like:

x_tmp = []
for x in [1,2]:
    y_tmp = []
    for y in [3,4]:
        z_tmp = []
        for z in [5,6]:
            z_tmp.append(x*y*z)
        y_tmp.append(z_tmp)
    x_tmp.append(y_tmp)

x_tmp
# [[[15, 18], [20, 24]], [[30, 36], [40, 48]]]

But this seems so odd that I can't imagine that would be the actual translation of it. Is there something closer to how map works that can be shown with a for loop?

I thought maybe it would be close to:

[x*y*z for x in [1,2] for y in [3,4] for z in [5,6]]
# [15, 18, 20, 24, 30, 36, 40, 48]

But that seems to lose all the nesting and do a flat mapping instead.

To do the list-comprehension way it seems not only does it need to be nested, but the args also need to be reversed, for example:

>>> [[[x*y*z for z in [5,6]] for y in [3,4]] for x in [1,2]]
# [[[15, 18], [20, 24]], [[30, 36], [40, 48]]]
Will Ness
  • 70,110
  • 9
  • 98
  • 181
David542
  • 104,438
  • 178
  • 489
  • 842
  • 1
    A list comprehension always returns a single list. If you want nested lists, you use nested list comprehensions. – Barmar Jun 24 '21 at 23:53
  • In terms of the order of operations, nothing is actually "reversed": in all the snippets that give the correct result, the inner loop is always the one for the `z` variable. – Pierre D Jun 25 '21 at 00:05
  • "But this seems so odd that I can't imagine that would be the actual translation of it. Is there something closer to how map works that can be shown with a for loop?" why does it seem "odd"? That's it. – juanpa.arrivillaga Jun 25 '21 at 00:18
  • The (correct) list comprehension is nested, but that's normal given that both the regular loops are also nested, as is the nested `map` statement. About the parameter order: The "reading direction" is different, but again, the actual ordering of the parameters is the same as in the `map` or nested loop blocks. – tobias_k Jun 25 '21 at 21:05

4 Answers4

3

Your first attempt is, indeed, an appropriate transformation to nested for loops. Your final attempt is an appropriate transformation to a nested list comprehension.

I'm not sure why you find these unsatisfying; unrolling loops is often a messy business. You've just provided a lovely primer in the equivalent forms. :-)

Prune
  • 76,765
  • 14
  • 60
  • 81
1

Not sure which REPL you are using, but map returns an iterator in Python 3.

x = map(lambda x: map(lambda y: map(lambda z: x*y*z, [5,6]), [3,4]), [1,2])

print(x)
<map object at 0x7fd6f70d1670>

You can use list to convert the iterator to a list -

print(list(x))
[<map object at 0x7fd6f7034f70>, <map object at 0x7fd6f70837f0>]

As you can see, the nested map calls produce iterators as well, so you would need to call list on the nested elements to get the actual output in your question

x = list(map(lambda x: list(map(lambda y: list(map(lambda z: x*y*z, [5,6])), [3,4])), [1,2]))

print(x)
[[[15, 18], [20, 24]], [[30, 36], [40, 48]]]

You could write a nested_for that takes a proc and a variable count of iterables, its -

def nested_for(proc, *its):
  def loop (acc, its):
    if not its:
      yield proc(*acc)
    else:
      for x in its[0]:
        yield list(loop((*acc, x), its[1:]))
  yield from loop(tuple(), its)
out = list(nested_for(lambda x,y,z: x*y*z, [1,2], [3,4], [5,6]))
print(out)
[[[[15], [18]], [[20], [24]]], [[[30], [36]], [[40], [48]]]]

This implementation produces one additional level of [...] nesting. If that is undesirable, you can make the loop a bit more sophisticated -

def nested_for(proc, *its):
  def loop (acc, it = None, *its):
    if not it:
      yield proc(*acc)
    elif not its:
      for v in it:
        yield proc(*acc, v) # don't nest deepest iteration
    else:
      for v in it:
        yield list(loop((*acc, v), *its))
  yield from loop(tuple(), *its)
out = list(nested_for(lambda x,y,z: x*y*z, [1,2], [3,4], [5,6]))
print(out)
[[[15, 18], [20, 24]], [[30, 36], [40, 48]]]

Seeing as though you are familiar with currying, you could simplify nested_for by making it accept a curried proc -

def nested_for (proc, it = None, *its):
  if not it:
    return
  elif not its:
    for v in it:
      yield proc(v)
  else:
    for v in it:
      yield list(nested_for(proc(v), *its))
out = list(nested_for(lambda x: lambda y: lambda z: x*y*z, [1,2], [3,4], [5,6]))
print(out)
[[[15, 18], [20, 24]], [[30, 36], [40, 48]]]

Aside from that I don't really know what question you're trying to answer. As Prune points out, you've already identified the equivalent for and list comprehension forms.

Mulan
  • 129,518
  • 31
  • 228
  • 259
  • 1
    there is an option when posting/editing an answer. i typically mark an answer as cw when the question is somewhat nebulous or when providing info that does not fit in a comment. cw-type answers are not awarded points and can be freely edited by anyone. – Mulan Jun 25 '21 at 22:34
1

you write

"seems to lose all the nesting and do a flat mapping instead."

and that's precisely the answer to your recent closed question about Scheme code translated to Python.

(apply append (map foo xs)) == (flatmap foo xs) and flatmap is how nested for loops are translated / implemented.

to be clear: it's nested in the sense of being [result(x,y) for x in xs for y in ys]. the nested list comprehension in this question, [[result for y in ys] for x in xs] is not nested loops, it's one loop (for x in xs) that yields the whole inner list comprehension's value ([result for y in ys]) as its result for each x in xs.

nested loops are combined loops which yield their result as one sequence, from the deepest loop only. to emulate this with lists, flatmap is performed, to achieve the same effect.

so, the nested loops are -- in pseudocode, --

for x in xs:
   for y in ys:
      yield result(x, y)

= [result(x, y) for x in xs for y in ys]

= (flatMapOn xs (lambda (x) =>
    (flatMapOn ys (lambda (y) => (list (result x y))))))

= (flatMapOn xs (lambda (x) =>
        (mapOn ys (lambda (y) =>       (result x y) ))))

and the "map-like" loop you ask about is

for x in xs: 
    yield (list (for y in ys: 
                   yield result(x,y) ))

= [[result(x, y) for y in ys] for x in xs]

= (mapOn xs (lambda (x) =>
    (mapOn ys (lambda (y) => (result x y)))))

NB it's yield, not yield from, in the outer loop here.

In Racket we observe

> (define (flatMapOn xs foo) (flatmap foo xs))

> (define (mapOn xs foo) (map foo xs))

> (display (flatMapOn (list 1 2 3) (lambda (x) 
     (flatMapOn (list x (+ x 10) (+ x 100)) (lambda (y) 
        (list (cons x y)))))))
((1 . 1) (1 . 11) (1 . 101) (2 . 2) (2 . 12) (2 . 102) 
 (3 . 3) (3 . 13) (3 . 103))

> (display (flatMapOn (list 1 2 3) (lambda (x) 
     (mapOn (list x (+ x 10) (+ x 100)) (lambda (y) 
              (cons x y))))))
((1 . 1) (1 . 11) (1 . 101) (2 . 2) (2 . 12) (2 . 102) 
 (3 . 3) (3 . 13) (3 . 103))

> (display (mapOn (list 1 2 3) (lambda (x) 
     (mapOn (list x (+ x 10) (+ x 100)) (lambda (y) 
              (cons x y))))))
( ((1 . 1) (1 . 11) (1 . 101)) 
  ((2 . 2) (2 . 12) (2 . 102)) 
  ((3 . 3) (3 . 13) (3 . 103)) )
Will Ness
  • 70,110
  • 9
  • 98
  • 181
0

You can use recursion:

def nest_op(d, p = 1):
   return [nest_op(d[:-1], p*i) if d[:-1] else p*i for i in d[-1]]

print(nest_op([[5,6], [3,4], [1,2]]))

Output:

[[[15, 18], [20, 24]], [[30, 36], [40, 48]]]
Ajax1234
  • 69,937
  • 8
  • 61
  • 102