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 some uncommon, advanced tricks in Python—what I like to refer to as "black magic." In the first, we learned many useful black magic Python tricks. Check it out if you missed it.
In this guide, we will continue to add more black magic Python tricks to our arsenal.
Sentinel can make a program simpler: it's a mechanism to distinguish useful data from placeholders, which indicate data is absent.
For example, we can put the key we search after the end of the array. This ensures that we will eventually find the element. When we find it, we only have to check whether we found a real element or just the sentinel.
Here we compare two similar built-in functions: str.index
without sentinel and str.find
with sentinel.
1"""find vs index"""
2"""index without sentinel"""
3try:
4 i = a.index(b)
5except:
6 return
7
8"""find with sentinel"""
9i = a.find(b)
10if i == -1:
11 return
As a design philosophy, the idea of sentinel is widely used in Python. We will further discuss the sentinel mechanism in a separate guide, including default value, loop terminator, sentinel object, and functional sentinel.
These examples show the sentinel idea used in both build-in functions and application scenarios.
1"""sentinel in iter"""
2blocks = ''.join(iter(partial(f.read, 32), ''))
3
4"""sentinel in dict.get"""
5sentinel = object()
6value = my_dict.get(key, sentinel)
7if value is not sentinel:
8 # Do something with value
9
10"""add a sentinel n at the end (which is the appropriate last insertion index then)"""
11# L47: given a collection of numbers that might contain duplicates, return all possible unique permutations.
12def permute_unique(nums: List[int]) -> List[List[int]]:
13 perms = [[]]
14 for n in nums:
15 perms = [p[:i] + [n] + p[i:]
16 for p in perms
17 for i in range((p + [n]).index(n) + 1)]
18 return perms
19
20"""sentinel in matrix"""
21def traverse_neighbors(matrix: List[List[int]]):
22 m, n = len(matrix), len(matrix[0])
23 """augment matrix to void length check by sentinel"""
24 matrix += [0] * n,
25 for row in matrix:
26 row.append(0)
27
28 for i in range(m):
29 for j in range(n):
30 # construct neighbor iterator
31 for I, J in (i + 1, j), (i - 1, j), (i, j + 1), (i, j - 1):
32 """no need to check boundary"""
33 process_neighbor_logic(matrix[I][J])
34
35"""functional sentinel"""
36def get_element(matrix: List[List[int]], i: int, j: int) -> int:
37 return matrix[i][j] if 0 <= i < m and 0 <= j < n else -1
1some_list += element, # actually "element," is a tuple
is equivalent to
1some_list.append(element)
+= ,
is shorter to type and more clear, even faster. But if your teammates or readers are not familiar with it, It may cause confusion.
1arr = [1, 2, 3]
2arr += 4,
3arr
4# [1, 2, 3, 4]
We know we cannot use branch logic (conditional execution) like if
/else
in list comprehension, so how to simulate break
in list comprehension?
Note: This is just a study and exploration of list comprehension. Since this trick is too hackish, with poor readability and performance, it should not be used in production code. But the techniques and ideas can be used for reference and may provide some inspiration.
Let's take this Stack Overflow question as an example:
How can I stop the iteration of list comprehension when a particular element is found?
1# pseudo code
2new_list=[a for a in origin_list break if a==break_elment]
Here is the hackish solution:
1# https://stackoverflow.com/a/56054962/11263560
2origin_list = [1, 2, 3, 3, 4, 3, 5]
3break_elment = 3
4
5new_list = [a for end in [[]] for a in origin_list
6 if not end and not (a == break_elment and end.append(42))]
7# output: [1, 2, 3]
There are many techniques used in this trick:
end
condition in list comprehension. Skip the rest of the elements when end
is not empty (actually not break out, but indeed in logic).end
) in a list comprehension? Here is a trick to wrap it in a for list: for end in [[]]
.and
/or
to divide branch logics.Here are more similar examples:
1# https://stackoverflow.com/a/55671533/11263560
2# How can I break a list comprehension based on a condition, for instance when the number 412 is found?
3# requirement in pseudo code
4even = [n for n in numbers if 0 == n % 2 and break if n == 412]
5
6"""use end condition"""
7even = [n for end in [[]] for n in numbers
8 if (False if end or n != 412 else end.append(42))
9 or not end and not n % 2]
10
11# https://stackoverflow.com/q/55646039/11263560
12"""use push & pop to record last pair"""
13res = [last.pop() and last.append(b) or b for last in [[desired_list[0]]] for a, b in
14 zip([desired_list[0]] + desired_list, desired_list) if abs(a[1] - b[1]) <= 5 and a == last[0]]
15
16"""use end condition"""
17res = [b for end in [[]] for a, b in zip([desired_list[0]] + desired_list, desired_list)
18 if (False if end or abs(a[1] - b[1]) <= 5 else end.append(42)) or not end and abs(a[1] - b[1]) <= 5]
Integrate several dimensions into one dictionary using an index with fractions.
1# L562: given a 01 matrix, find the longest line of consecutive one. the line could be horizontal, vertical, diagonal or anti-diagonal.
2def longest_line_of_consecutive_one_in_matrix(matrix: List[List[int]]) -> int:
3 max_len = 0
4 cur_len = defaultdict(int)
5 for i, row in enumerate(matrix):
6 for j, v in enumerate(row):
7 """merge row, col, analog, anti-analog into one dict by index with fractions"""
8 for key in i, j + .1, i + j + .2, i - j + .3: # analog: i+j, anti-analog: i-j
9 cur_len[key] = (cur_len[key] + 1) * v # accumulate util v turn to zero
10 max_len = max(max_len, cur_len[key])
11 return max_len
Use complex number for two-dimensional representation or visit four directions with imaginary unit calculation.
A property of imaginary number j
:
1a = 1j
2a, a * a, a ** 3, a ** 4
3# values: j, -1, -j, 1
1"""simplify two-dimension index into one-dimension by complex number"""
2# traverse neighbors in matrix
3def traverse_neighbor_by_complex(matrix: List[List[int]]) -> None:
4 matrix = {i + 1j * j: v for i, row in enumerate(matrix) for j, v in enumerate(row)}
5 for z in matrix:
6 """visit 4-directional neighbor by complex calculation"""
7 for k in range(4):
8 process_neighbor_logic(matrix.get(z + 1j ** k))
9
10# L657: given a sequence of robot 4-directional moves "LRUD", judge if this robot ends up at (0, 0) after it completes its moves.
11def judge_circle(moves: str) -> bool:
12 """D: 1j**-1=-1j, R: 1j**0=1+0j, U: 1j**1=1j, L: 1j**2=-1+0j, result in D+U=0 and L+R=0"""
13 return not sum(1j**'RUL'.find(m) for m in moves)
14
15"""use complex number to represent island(simplify 2d -> 1d and turn into a sparse representation)"""
16# L695: an island is a group of 1's connected 4-directionally, find the maximum area of an island in the given 2D array.
17def max_area_of_island(grid: List[List[int]]) -> int:
18 grid = {i + j * 1j: val for i, row in enumerate(grid) for j, val in enumerate(row)}
19 """calculate the area of paricular island by visit neigher complex calculation"""
20 def area(z):
21 return grid.pop(z, 0) and 1 + sum(area(z + 1j ** k) for k in range(4))
22 return max(map(area, set(grid)))
In this guide, we have learned more black magic Python tricks, such as sentinel, list comprehension with break, and advanced usages for fractions and complex number. 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, black_magics.ipynb, from Github.
This guide is one of a series of Python tricks guides:
I hope you enjoyed it. If you have any questions, you're welcome to contact me at [email protected]