19

In Python 2, a common (old, legacy) idiom is to use map to join iterators of uneven length using the form map(None,iter,iter,...) like so:

>>> map(None,xrange(5),xrange(10,12))
[(0, 10), (1, 11), (2, None), (3, None), (4, None)]

In Python 2, it is extended so that the longest iterator is the length of the returned list and if one is shorter than the other it is padded with None.

In Python 3, this is different. First, you cannot use None as an argument for the callable in position 1:

>>> list(map(None, range(5),range(10,12)))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'NoneType' object is not callable

OK -- I can fix that like so:

>>> def f(*x): return x    
... 
>>> list(map(f, *(range(5),range(10,12))))
[(0, 10), (1, 11)]

But now, I have a different problem: map returns the shortest iterator's length -- no longer padded with None.

As I port Python 2 code to Python 3, this is not a terrible rare idiom and I have not figured out an easy in place solution.

Unfortunately, the 2to3 tools does not pick this up -- unhelpfully suggesting:

-map(None,xrange(5),xrange(10,18))
+list(map(None,list(range(5)),list(range(10,18)))) 

Suggestions?


Edit

There is some discussion of how common this idiom is. See this SO post.

I am updating legacy code written when I was still in high school. Look at the 2003 Python tutorials being written and discussed by Raymond Hettinger with this specific behavior of map being pointed out...

11
  • 3
    map(None,*(iter,iter)) is not a common idiom. Have you heard of itertools.izip_longest()? Does exactly what you described, but without gimmicks. Commented Aug 18, 2012 at 3:17
  • 4
    @Tadeck: Since the behavior map described is straight from the Python 2 documents, yeah -- it is not uncommon... Commented Aug 18, 2012 at 3:21
  • 3
    @drewk: I am familiar with how map() works, but using map(None, *(iter, iter)) in this case seems to be very unpythonic for me. Also, I also wonder, why map(None,*(xrange(5),xrange(10,12))) and not more direct map(None, xrange(5), xrange(10,12))? Does exactly the same. Any reference that could prove it is pythonic? Guido's confirmation would be enough, or Raymond Hettinger's, or part of Python's docs would do. Commented Aug 18, 2012 at 3:27
  • 2
    I did not state it was Pythonic or even good practice; I said it was common because that is the behavior that is documented in map in Python 2. Google 'python join list of different lengths' Some of the older answers use map. Commented Aug 18, 2012 at 3:41
  • 2
    Well, "common" and "supported" are different things; I've certainly never seen it before. In any case, this functionality has been supplanted by zip and zip_longest, which are far more understandable IMO. Commented Aug 18, 2012 at 4:01

4 Answers 4

18

itertools.zip_longest does what you want, with a more comprehensible name. :)

Sign up to request clarification or add additional context in comments.

3 Comments

Exactly, and in Python 2.x it is called itertools.izip_longest(), so there is no need to do map(None, *(iter, iter)). +1
you'll still need to stick a list() around it, though.
@andrewcooke Well, depending on if you actually need a list or just want to iterate over it....
2

I'll answer my own question this time.

With Python 3x, you can use itertools.zip_longest like so:

>>> list(map(lambda *a: a,*zip(*itertools.zip_longest(range(5),range(10,17)))))
[(0, 10), (1, 11), (2, 12), (3, 13), (4, 14), (None, 15), (None, 16)]

You can also roll ur own I suppose:

>>> def oldMapNone(*ells):
...     '''replace for map(None, ....), invalid in 3.0 :-( '''
...     lgst = max([len(e) for e in ells])
...     return list(zip(* [list(e) + [None] * (lgst - len(e)) for e in ells]))
... 
>>> oldMapNone(range(5),range(10,12),range(30,38))
[(0, 10, 30), (1, 11, 31), (2, None, 32), (3, None, 33), (4, None, 34), (None, None, 35), (None, None, 36), (None, None, 37)]

1 Comment

I do not understand the complexity of your answer. Will the following just work: list(itertools.zip_longest(range(5),range(10,17)))
1

One way if you need some obsolete functionality in from the Python 2 then one way - write it yourself. Of course, it's not built-in functionality, but at least it is something.

The code snippet below takes 27 lines

#!/usr/bin/env python3

def fetch(sequence, index):
  return None if len(sequence) <= index else sequence[index]

def mymap(f, *args):
  max_len = 0
  for i in range(len(args)): 
      max_len = max(max_len, len(args[i]))
  out = []
  for i in range(max_len):
      t = []
      for j in range(len(args)): 
          t.append(fetch(args[j],i))      

      if f != None:
          # Use * for unpack arguments from Arbitarily argument list
          # Use ** for unpack arguments from Keyword argument list
          out.append(f(*t))
      else:
          out.append(tuple(t))
  return out 

if __name__ == '__main__':
    print(mymap(None, [1,2,3,4,5],[2,1,3,4],[3,4]))
    print(mymap(None,range(5),range(10,12)))

Comments

-1

you can solve the problem like this: list(map(lambda x, y: (x, y),[1, 2, 3 ,4, 5], [6, 7, 8, 9, 10]))

1 Comment

Your solution only works if both lists are of the same size.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.