visited = [1, 5, 6, 8, 10]
def is_visited(visited, city):
# O(n)
for visited_city in visited:
if visited_city == city:
return True
return False
print(is_visited(visited, 6))
print(is_visited(visited, 7))True
False
Let’s recall why we do need set.
Assuming we have 30 cities (identified by number/id from 0 to 29). We want to keep track of the cities we have visited
True
False
Ok, but that’s O(n)
We then optimized it using boolean array
True
False
Our code is O(1) but our storage complexity is O(n)
It’s not a big deal, but we can do better.
Let’s check how much memory we need to store a boolean array of size 30:
import sys
# Integer
int_value = 1
print(f"Integer: {sys.getsizeof(int_value)} bytes")
# Boolean
bool_value = True
print(f"Boolean: {sys.getsizeof(bool_value)} bytes")
# Array of boolean
bool_array = [True] * 30
print(f"Array of booleans: {sys.getsizeof(bool_array) + sum(map(sys.getsizeof, bool_array))} bytes")Integer: 28 bytes
Boolean: 28 bytes
Array of booleans: 1136 bytes
1136 bytes
Can we do better?
Yes, it’s possible to only use 28 bytes!
Let’s step back for a while…
Every integer is represented as a sequence of bits (binary digits), and every bit is either 0 or 1.
For example, the decimal number five has a binary representation of 101:
The basic binary operations are shifting left, shifting right, bitwise AND, bitwise OR, bitwise NOT, and bitwise XOR.
Shifting left
1 in binary is 1
2 in binary is 10
4 in binary is 100
Shifting right
11 in binary is 1011
5 in binary is 101
2 in binary is 10
Shifting also has the effect of multiplying or dividing the number by a power of two.
10
2
Or more generally, shifting left by n bits is equivalent to multiplying by 2^n
AND
5 in binary is 101
7 in binary is 111
5 in binary is 101
OR
5 in binary is 101
4 in binary is 100
5 in binary is 101
XOR
5 in binary is 101
4 in binary is 100
1 in binary is 1
To check if the i-th (0 based) bit is set, we can just AND the number with 1 << i
The result will be 0 if the bit is not set, and non-zero otherwise.
To turn on the i-th bit, we can OR the number with 1 << i
To turn off the i-th bit, we can AND the number with ~(1 << i)
~ is the bitwise NOT operator
Hint:
| dec | bin |
|---|---|
1 << 4 |
0010000 |
~(1 << 4) |
1101111 |
To flip the i-th bit, we can XOR the number with 1 << i
Let’s see how we can use bitmaps to store the visited cities
True
False
It only uses 28 bytes! While the boolean array uses 1136 bytes.
Let’s say we have a feature toggle of 100 features, and we want to enable/disable them.
class FeatureFlags:
def __init__(self):
self.flags = 0
def enable_feature(self, feature_id):
self.flags |= (1 << feature_id)
def disable_feature(self, feature_id):
self.flags &= ~(1 << feature_id)
def is_feature_enabled(self, feature_id):
mask = 1 << feature_id
return self.flags & mask != 0
def __str__(self):
return bin(self.flags)
ff = FeatureFlags()
print("Enable feature 1")
ff.enable_feature(1)
print(f"feature_flags: {ff}")
print(ff.is_feature_enabled(1))
print()
print("Disable feature 1")
ff.disable_feature(1)
print(f"feature_flags: {ff}")
print(ff.is_feature_enabled(1))
print()
print("Enable feature 10 and 20")
ff.enable_feature(10)
ff.enable_feature(20)
print(f"feature_flags: {ff}")
print(ff.is_feature_enabled(10))
print(ff.is_feature_enabled(20))
print(ff.is_feature_enabled(30))
Enable feature 1
feature_flags: 0b10
True
Disable feature 1
feature_flags: 0b0
False
Enable feature 10 and 20
feature_flags: 0b100000000010000000000
True
True
False
Are you familiar with chmod?
chmod 755 file.txt
What’s the meaning of that 755? It’s a bitmap representation of file permission!
Let’s try here
The unique sequence of bits can be used to represent a subset of a set.
Let’s do a simple counting:
If we label each bit:
So, given a set, to return all subsets, we can just iterate from 0 to 2^n - 1 and check which bit is set.
In a chess board, we want to place 8 rooks such that no rook can attack another rook.
How to check if a rook is occupying a column? The following is the slow way:
from typing import List
boards: List[List[bool]] = []
def is_valid(board: List[List[bool]], row: int, col: int) -> bool:
# check whether there is a rook in the same row
for c in range(col):
if board[row][c]:
return False
# check whether there is a rook in the same column
for r in range(row):
if board[r][col]:
return False
return TrueThat’s O(n)
Using bitmap, we can do it in O(1)
row_visited: int = 0
col_visited: int = 0
def is_valid(row_visited: int, col_visited: int, row: int, col: int) -> bool:
# check whether there is a rook in the same row
if row_visited & (1 << row) != 0:
return False
# check whether there is a rook in the same column
if col_visited & (1 << col) != 0:
return False
return TrueWhen we have visited the col/row, we just mark it as visited.
Complete solution:
8-Queens is similar to 8-Rooks, but we need to check the diagonal as well: