Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why does map work like izip_longest with fill=None?

When map has different-length inputs, a fill value of None is used for the missing inputs:

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

This is the same behavior as:

>>> import itertools
>>> list(itertools.izip_longest(*x))
[(1, 5), (2, 6), (3, None), (4, None)]

What's the reason map provides this behavior, and not the following?

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

…and is there an easy way to get the latter behavior either with some flavor of zip or map?

like image 658
Mike McKerns Avatar asked May 08 '15 16:05

Mike McKerns


2 Answers

I think this a design decision that the core devs opted for at the time they implemented map. There's no universally defined behavior for map when it is used with multiple iterables, quoting from Map (higher-order function):

Map with 2 or more lists encounters the issue of handling when the lists are of different lengths. Various languages differ on this; some raise an exception, some stop after the length of the shortest list and ignore extra items on the other lists; some continue on to the length of the longest list, and for the lists that have already ended, pass some placeholder value to the function indicating no value.

So, Python core devs opted for None as placeholder for shorter iterables at the time map was introduced to Python in 1993.

But in case of itertools.imap it short-circuits with the shortest iterable because its design is heavily inspired from languages like Standard ML, Haskell and APL. In Standard ML and Haskell map ends with shortest iterable(I am not sure about APL though).

Python 3 also removed the map(None, ...)(or we should say itertools.imap, Python 3's map is actually almost itertools.imap: Move map() from itertools to builtins) construct because it was present in Python 2 only because at the time map was added to Python there was no zip() function in Python.

From Issue2186: map and filter shouldn't support None as first argument (in Py3k only):

I concur with Guido that we never would have created map(None, ...) if zip() had existed. The primary use case is obsolete.


To get the result you want I would suggest using itertools.izip_longest with a sentinel value(object()) rather than default None, None will break things if the iterables itself contain None:

from itertools import izip_longest


def solve(seq):
    sentinel = object()
    return [tuple(x for x in item if x is not sentinel) for item in 
            izip_longest(*seq, fillvalue=sentinel)]


print solve([[1,2,3,4],[5,6]])
# [(1, 5), (2, 6), (3,), (4,)] 
like image 184
Ashwini Chaudhary Avatar answered Oct 31 '22 23:10

Ashwini Chaudhary


Given that the first list is always longer and that there are only two lists, you would do something like this:

x = [1,2,3,4,5]
y = ['a','b']
zip(x,y) + [(i,) for i in x[len(y):]]
[(1, 'a'), (2, 'b'), (3,), (4,), (5,)]
like image 39
Josep Valls Avatar answered Nov 01 '22 01:11

Josep Valls