- Iterate simultaneously over multiple lists
- Mixed list of iterables
- Shifted values from iterable
- Multiple distinct values from an iterator
- Zip as transpose
A for loop operates more like foreach in Python. Iterating over elements from one iterable, like list is simple. What to do if elements are needed to be picked from two or more lists per cycle? This is where the zip method comes in. It allows multiple iterables to be looped over at the same time. For every cycle, zip picks one element from each of the iterables provided.
- two independent iterables
- zip simultaneous packages elements from each
- stops when one iterable is expended
Py3: zip multiple lists
# two lists lsta = [1,2,3,4] lstb = list('hello') # pair holds one value from each list for pair in zip(lsta, lstb): print(pair) #= (1, 'h') # (2, 'e') # (3, 'l') # (4, 'l')
Notes: Using the zip explained
The zip method packages multiple iterables provided to it. They can be a mixed type of iterables. During each loop, zip queries the next value from each iterable. All the elements extracted are packaged into a single tuple. If a single variable is provided, the tuple is assigned to it. If multiple variables are present in the statement, then the tuple is unpacked and appropriately assigned to the variables. Check out tuple unpacking for more details. The iteration continues while each iterable is able to supply a value. When one is exhausted, the whole loop exits.
A mixed set of iterables like lists, tuples, dictionaries, range, or generators can all be provided to a single zip statement.
- multiple iterables packaged into a container list
- unpack values from zip into separate variables
- stop at the end of shortest iterable
Py3: Mixed bag of iterables
itra = [1,2,3] itrb = range(5,8) itrc = 'iterate' # iterate with 3 iterables # unpack values into separate variables for a,b,c in zip(itra,itrb,itrc): print(a,b,c) #= 1 5 i # 2 6 t # 3 7 e
Instead of providing each iterable individually, they can be packaged into a list.
Py3: Container of iterables
itra = (1,2,3,4) txtb = 'abc' rngc = range(10,14) # pack iterables as a list container = [itra, txtb, rngc] for a, b, c in zip(*container): print(a, c, b) #= 1 10 a # 2 11 b # 3 12 c
Notes: Packaging iterables can be powerful
Multiple iterables can be packaged into a list and unpacked inside a zip statement. This is a very powerful feature, as complex sets of iterables can be created to be iterated together.
Often we need to get multiple values from the same iterable. Say a pair of values are needed per iteration.
- multiple values from same iterable
- shift is fixed and set at start
Py3: Pair of values
nums = [1,2,3,4,5] # offset second iterable for pair in zip(nums, nums[1:]): print(pair) #= (1, 2) # (2, 3) # (3, 4) # (4, 5)
The pair of values example can be extended and generalized to extract triplets or multiple values from an iterable.
Py3: Recode pair to triplet
nums = [1,2,3,4,5] # create container of iterables container =  for s in range(3): container.append(nums[s:]) # co-iterate contents for triplet in zip(*container): print(triplet) #= (1, 2, 3) # (2, 3, 4) # (3, 4, 5)
Notes: Generalized multi value iteration
Creating and unpacking a list of staggered iterables allows us to generalize iterating with multiple values derived from same iterable. It is to be noted that this works for iterables and not iterators.
If multiple values are needed from a list of numbers, where the next iteration moves the pointer to next new value - then we can use an iterator. An iterator keeps track of the last value it has released.
Py3: Multiple values using iterator
# create an iterator nums = [1,2,3,4,5,6,7,8,9] itr = iter(nums) # request 3 values from iterator each loop # same as: for triplet in zip(itr, itr, itr): for triplet in zip(*[itr]*3): print(triplet) #= (1, 2, 3) # (4, 5, 6) # (7, 8, 9)
Notes: Iterator instead of iterables
The difference is stark when we convert an iterable into an iterator. The pointer for an iterable being queried by a zip per cycle moves by a single value. When zip requests multiple values from an iterator, the pointer moves by multiple positions. So the iterator version provides multiple values without repetition of the values.
We can think of zip as a way to change the ordering of a two dimensional data structure. The combinations are reversible by multiple zip operations.
Py3: Reversible zip transpositions
a = (1,2,3,4) b = (6,7,8,9) # zip two iterables and expand into list z = list(zip(a,b)) #= [(1,6), (2,7), (3,8), (4,9)] # create initial list of two iterables c = [a,b] #= [(1,2,3,4), (6,7,8,9)] # apply zip; same as zip(a,b) z1 = list(zip(*c)) #= [(1,6), (2,7), (3,8), (4,9)] # reapply zip, to get back initial list z2 = list(zip(*z1)) #= [(1,2,3,4), (6,7,8,9)]
Notes: Transposing using zip
Zip rearranges elements within a two dimensional data structure. We can think about this as rows and columns. Zip converts between rows and columns. When we finally look at each row as a group, we notice that the operation reverts back to original when zip is applied twice sequentially.