Difference between zip(list) and zip(*list)
PythonPython 3.xPython Problem Overview
I am using a list p = [[1,2,3],[4,5,6]]
If I do :
>>>d=zip(p)
>>>list(d)
[([1, 2, 3],), ([4, 5, 6],)]
Though, what I actually want is obtained using this:
>>>d=zip(*p)
>>>list(d)
[(1, 4), (2, 5), (3, 6)]
I have found out that adding a '*' before the list name gives my required output, but I can't make out the difference in their operation. Can you please explain the difference?
Python Solutions
Solution 1 - Python
zip
wants a bunch of arguments to zip together, but what you have is a single argument (a list, whose elements are also lists). The *
in a function call "unpacks" a list (or other iterable), making each of its elements a separate argument. So without the *
, you're doing zip( [[1,2,3],[4,5,6]] )
. With the *
, you're doing zip([1,2,3], [4,5,6])
.
Solution 2 - Python
The *
operator unpacks arguments in a function invocation statement.
Consider this
def add(x, y):
return x + y
if you have a list t = [1,2]
, you can either say add(t[0], t[1])
which is needlessly verbose or you can "unpack" t
into separate arguments using the *
operator like so add(*t)
.
This is what's going on in your example.
zip(p)
is like running zip([[1,2,3],[4,5,6]])
. Zip has a single argument here so it trivially just returns it as a tuple.
zip(*p)
is like running zip([1,2,3], [4,5,6])
. This is similar to running zip(p[0], p[1])
and you get the expected output.
Solution 3 - Python
The *
character is known as the unpacking operator. when it appears behind an iterable object, what it does is passing the items inside the iterable to the function's caller one by one. In this case, since the zip
function accepts a list of iterables in order to return their aligned columns, zip(*p)
passes all the items inside the p
as arguments to zip
function:
Therefore, in this case, zip(*p)
is equal to:
zip([1,2,3],[4,5,6])
Also, note that since Python-3.5 you can use unpacking operators in a few other cases than in function callers. One of which is called in-place unpacking that lets you use unpacking within another iterable.
In [4]: a = [1, 2, 3]
In [5]: b = [8, 9, *a, 0, 0]
In [6]: b
Out[6]: [8, 9, 1, 2, 3, 0, 0]
Solution 4 - Python
In a nutshell, with x = [1,2,3]
, when calling f(x)
, x receives 1 argument [1, 2, 3]
. When you use the star operator f(*x)
, f receives three arguments, it is equivalent to the call f(1,2,3)
.
This is why, in Python's documentation, you will often see some_function(*args, **kwargs)
. Here the double star operator does the same, but for a dictionary: with d={"some_arg":2, "some_other_arg":3}
, calling f(**d)
is the same as f(some_arg=2, some_other_arg=3)
.
Now when you use zip, effectively you want to zip [1,2,3] with [4,5,6], so you want to pass 2 args to zip, therefore you need a star operator. Without it, you're passing only a single argument.
Solution 5 - Python
While this isn't the answer the question you asked, it should help. Since zip is used to combine two lists, you should do something like this list(zip(p[0], p[1]))
to accomplish what you'd expect.
Solution 6 - Python
The "*" operator unpacks a list and applies it to a function. The zip function takes n lists and creates n-tuple pairs from each element from both lists:
> zip([iterable, ...]) > > This function returns a list of tuples, where the i-th tuple contains > the i-th element from each of the argument sequences or iterables. The > returned list is truncated in length to the length of the shortest > argument sequence. When there are multiple arguments which are all of > the same length, zip() is similar to map() with an initial argument of > None. With a single sequence argument, it returns a list of 1-tuples. > With no arguments, it returns an empty list.
Basically, by using *
with [[1,2,3],[4,5,6]]
, you are passing [1,2,3]
and [4,5,6]
as arguments to zip.