# Python Tricks - Iterable - Part 2

Nov 23, 2019 • 11 Minute Read

## Introduction

*Editor's note: This guide is part of a series on useful Python tricks. Read more about the series and find links the other guides here.*

This is the second of two guides on iterable Python tricks. In the first, we learned many useful iterable Python tricks. Check it out if you missed it.

In this guide, we will continue to add more iterable python tricks to our arsenal.

## map

The **map** function applies a function to all the items in a given iterable.

` ``map(function, iterables) -> iterables`

**map** returns a list of the results after applying the given function to each item of a given iterable.

#### Inspiring Examples

` ````
"""map to node children recursively"""
# L104: get depth in binary tree
def max_depth(root: TreeNode) -> int:
return 1 + max(map(maxDepth, (root.left, root.right))) if root else 0
"""comparison map find with map index"""
# L290: given a pattern and a string words, find if words follows the same pattern.
# i.e. pattern = 'abba', words = 'dog cat cat dog'
def valid_word_pattern(pattern: str, words: str) -> bool:
s, t = pattern, words.split()
return list(map(s.find, s)) == list(map(t.index, t))
"""map dict.get"""
# L506: given scores of N athletes, find their relative ranks and the people with the top three highest scores, who will be awarded medals: "Gold Medal", "Silver Medal" and "Bronze Medal"
def find_relative_ranks(nums: List[int]) -> List[str]:
sorted_nums = sorted(nums, reverse=True)
rank = ["Gold Medal", "Silver Medal", "Bronze Medal"] + list(map(str, range(4, len(nums) + 1)))
return list(map(dict(zip(sorted_nums, rank)).get, nums))
```

## reduce

**reduce** returns a single value constructed by applying a rolling computation to sequential pairs of values in a given iterable.

` ``reduce(function, sequence[, initial]) -> value`

#### Inspiring Examples

` ````
# L136: every numbers appears twice except for one, find that single one
def single_number(nums: List[int]) -> int:
return reduce(lambda x, y: x ^ y, nums)
# L47: generate all possible permutations
def permute(nums: List[int]) -> List[List[int]]:
return reduce(lambda P, n: [p[:i] + [n] + p[i:] for p in P for i in range(len(p)+1)], nums, [[]])
```

## zip

**zip** takes an iterator that aggregates elements based on the iterables passed and returns an iterator of tuples. Its syntax is as follows:

` ``zip(*iterables) -> iterator`

Notice the iterator stops when the shortest iterable is exhausted. If you prefer to match the longest iterable, you can try **zip_longest(*iterables, fillvalue=None)** in **itertools** module.

` ````
a = [1,2,3]
b = ['one','two','three']
zipped=zip(a,b)
list(zipped)
# output: [(1, 'one'), (2, 'two'), (3, 'three')]
```

#### Inspiring Examples

` ````
"""construct dict"""
keys = ['a', 'b', 'c']
vals = [1, 2, 3]
zipped_dict = dict(zip(keys, vals))
"""difference of neighbor pairs"""
arr, diffs = [1, 2, 3, 4, 5], []
for pre, post in zip(arr[:-1], arr[1:]): # zip(arr, arr[1:]) is ok too, zip matches the shortest
diffs.append(post - pre)
"""transpose matrix"""
# L867: The transpose of a matrix is the matrix flipped over it's main diagonal, switching the row and column indices of the matrix.
def transpose(matrix: List[List[int]]) -> List[List[int]]:
return list(map(list, zip(*matrix)))
"""zip into a set"""
# L290: given a pattern and a string words, find if words follows the same pattern.
# i.e. pattern = 'abba', words = 'dog cat cat dog'
def valid_word_pattern(pattern: str, words: str) -> bool:
s, t = pattern, words.split()
return len(set(zip(s, t))) == len(set(s)) == len(set(t)) and len(s) == len(t)
```

## Unpacking

We use the operators ***** (for tuples) and ****** (for dictionaries) to unpack arguments in Python.

` ````
def product(a, b):
return a * b
"""use * to unpack tuple, list or other iterables"""
param_tuple = (2, 3)
product(*param_tuple)
# output: 6
"""use ** to unpack dict"""
param_dict = {'a': 2, 'b': 3}
product(**param_dict)
# output: 6
```

#### Inspiring Examples

More advanced usages of unpacking:

` ````
"""get rest of all"""
a, *b, c = range(5)
# b = [1, 2, 3]
[(c, *d, [*e]), f, *g] = [[1, 2, 3, 4, [5, 5, 5]], 6, 7, 8]
# d = [2, 3, 4], e = [5, 5, 5], g = [7, 8]
"""merge dicts"""
a = {'1': 1, '2': 2}
b = {'2': 3, '4': 4}
merged_dict = {**a, **b} #{'1': 1, '2': 3, '4': 4}
"""[*a] = list(a), [*zip(*matrix)]: transpose matrix"""
# L54: traverse matrix in spiral order.
def traverse_spiral_order(matrix: List[List[int]]) -> List[int]:
return matrix and [*matrix.pop(0)] + traverse_spiral_order([*zip(*matrix)][::-1])
```

## sum

The **sum** function takes an iterable and returns the sum of items in it. Its syntax is as follows:

` ``sum(iterable[, start]) -> number`

#### Inspiring Examples

` ````
"""sum"""
# L771: S is stones you have, J is types of stones which are jewels, and you want to know how many stones are also jewels.
def num_jewels_in_stones(J: str, S: str) -> int:
return sum(map(J.count, S))
# L389: string t is generated by random shuffling string s and then add one more letter at a random position, find the letter.
def find_the_difference(s: str, t: str) -> str:
return chr(sum(map(ord, t)) - sum(map(ord, s)))
# L266: determine if a permutation of the string could form a palindrome
def can_permute_palindrome(s: str) -> bool:
"""at most one odd count character"""
return sum(v % 2 for v in Counter(s).values()) <= 1
```

## max and min

The **max** function returns the largest of the input values. Its syntax is as follows:

` ``max(iterable[, default=obj, key=func]) -> value`

The **min** function is similar to this.

#### Inspiring Examples

` ````
"""max"""
# L169: find the majority element which appears more than ⌊ n/2 ⌋ times.
def majority_element(nums: List[int]) -> int:
counts = Counter(nums)
return max(counts.keys(), key=counts.get)
"""min"""
# L14: find the longest common prefix string amongst an array of strings.
def longest_common_prefix(strs: List[str]) -> str:
shortest = min(strs, key=len)
for i, ch in enumerate(shortest):
if any(s[i] != ch for s in strs):
return shortest[:i]
return shortest
```

## any and all

The **any** function tests whether any item in the iterable evaluates to **True** to not. Its syntax is as follows:

` ``any(iterable) -> bool`

The **all** function is similar to this.

#### Inspiring Examples

` ````
"""any"""
# L36: determine if a 9x9 Sudoku board is valid, which means 1-9 in row, column, 3x3 sub-boxes
def is_valid_sudoku(board: List[List[str]]) -> bool:
seen = set()
return not any(x in seen or seen.add(x)
for i, row in enumerate(board)
for j, c in enumerate(row)
if c != '.'
for x in ((c, i), (j, c), (i/3, j/3, c)))
"""all"""
# L766: a matrix is Toeplitz if every diagonal from top-left to bottom-right has the same element.
def is_toeplitz_matrix(matrix: List[List[int]]) -> bool:
return all(r == 0 or c == 0 or matrix[r-1][c-1] == val
for r, row in enumerate(matrix)
for c, val in enumerate(row))
# L246: verify the number is strobogrammatic, strobogrammatic number looks the same when rotated 180 degrees
def is_strobogrammatic(num: str) -> bool:
return all(map('696 00 11 88'.count, map(operator.add, num, num[::-1])))
# L261: given n nodes labeled from 0 to n-1 and a list of undirected edges, check whether these edges make up a valid tree.
def valid_tree(n: int, edges: List[List[int]]) -> bool:
"""all check in union find"""
parent = range(n)
def find(x):
return x if parent[x] == x else find(parent[x])
def union(xy):
x, y = map(find, xy)
parent[x] = y
return x != y
return len(edges) == n-1 and all(map(union, edges))
```

## Customize iterable in bisect

**bisect** module provides support for maintaining a list in sorted order without having to sort the list after each insertion.

The **bisect_left**/**bisect** function locates the insertion point for **x** in **a** to maintain sorted order.

` ``bisect.bisect_left(a, x[, lo=0, hi=len(a)]) -> int`

#### Inspiring Examples

In the following examples, **bisect** usage is generalized to customized iterables. The key point is the idea of constructing an appropriate **iterable**. We need to use **__getitem__** for **bisect** to index.

` ````
"""use binary search to find the first number that's less than or equal to the last."""
# L153: find the minimum element in rotated sorted array
def find_min_in_rotated_sorted_arr(self, nums: List[int]) -> int:
"""construct iterable which distinguish the two parts of rotated array"""
self.__getitem__ = lambda i: nums[i] <= nums[-1]
return nums[bisect.bisect(self, False, 0, len(nums))]
"""construct a boolean iterable and use binary search"""
# L4: find the median of the two sorted arrays
def find_median_in_two_sorted_arrays(nums1: List[int], nums2: List[int]) -> float:
a, b = sorted((nums1, nums2), key=len)
m, n = len(a), len(b)
after = (m + n - 1) // 2
class Range:
def __getitem__(self, i):
return after-i-1 < 0 or a[i] >= b[after-i-1]
i = bisect.bisect_left(Range(), True, 0, m)
nextfew = sorted(a[i:i+2] + b[after-i:after-i+2])
return (nextfew[0] + nextfew[1 - (m+n)%2]) / 2
```

## Conclusion

In this guide, we have learned more iterable Python tricks, such as map/reduce, pack/unpack, and the advanced skills for some built-in functions. I hope some of them will be useful for you.

There are other advanced techniques not mentioned here. This guide simply offers a good starting point to travel in the Python world. You can also download an example notebook, iterables.ipynb, from Github.

This guide is one of a series of Python tricks guides:

- Python Tricks - Introduction
- Python Tricks - Basic - Part 1
- Python Tricks - Basic - Part 2
- Python Tricks - Iterable - Part 1
- Python Tricks - Iterable - Part 2
- Python Tricks - Black Magic - Part 1
- Python Tricks - Black Magic - Part 2

I hope you enjoyed it. If you have any questions, you're welcome to contact me at recnac@foxmail.com.