## Table of Contents

## Open Table of Contents

## Introduction

Recently, I’ve been brushing up my coding interview’s skills by studying data structures and algorithms. This isn’t the first time (of course) I’ve done this, but this time I decided to use Python as my main programming language, given its simplicity, easy-to-read syntax, and some builtin collections and data structures it has.

## Variables

```
# Defining a variable
language = "Python"
# Set it to none (nil or null in other languages)
language = None
# Defining multiple variables at the same time
slow, fast = 0, 10
# Swapping variables (or arrays values)
slow, fast = fast, slow
```

## If statements

Most of the time for the `if`

statements we don’t need parentheses (`{`

or `}`

) to determine the scope of the conditional, this is given for the level of indentation.

```
if age < 18:
print("You are a boy 👦🏽")
elif age >= 18 and age <= 65:
print("You are an adult 👨🏽")
else:
print("You are old 👴🏽")
```

However, there’s an exception though, when we have a multi-line comparison, we must use parentheses to avoid syntax errors.

```
n, m = 1, 2
if ((n > 2 and
n != m) or n == m):
n += 1
```

Also notice we don’t use

`&&`

or`||`

for the boolean operators like in other languages, in Python we use the keywords`and`

and`or`

directly. If you want to negate a value, you can use`not`

instead of`!`

.

## While loop

```
n = 10
while n > 0:
print(n)
n -= 1
```

## For loop

```
# Looping from i = 0 to i = 9 (the 10 in the range function is not included)
for i in range(10):
print(i) # 0, 1, 2, 3, 4, 5, 6, 7, 8, 9
# Looping from i = 2 to i = 5 (again the 6 in the range function is not included)
for i in range(2, 6):
print(i) # 2, 3, 4, 5
# Looping in reverse from i = 5 to i = 2 (not including 1)
for i in range(5, 1, -1):
print(i) # 5, 4, 3, 2
```

### How the range function behaves?

As we saw the range function is very flexible, and we can pass different arguments to it, here are more details about how to use it.

```
# With one integer argument it counts from 0 to stop
range(stop)
# With two integer arguments it counts from start to stop
range(start, stop)
# With three integer arguments it counts from start to stop,
# with a defined step size
range(start, stop, step)
```

## Division

```
# Division is decimal by default ⚠️
print(5 / 2) # 2.5
# Double slash rounds down
print(5 // 2) # 2
# ⚠️ CAREFUL: most languages round towards 0 by default
# so, negative numbers will round down
print(-3 // 2) # -2
# A work around for rounding towards 0 is to use
# decimal division and then convert to int
print(int(-3 / 2)) # -1
# Modding is similar to other languages
print(10 % 3) # 1
# Except for negative values 🤦🏽♂️
print(-10 % 3) # 2
# To be consistent import the math package and use it
import math
print(math.fmod(-10, 3)) # -1.0
```

Talking about the `math`

package and math’s related values we can use the following functions:

```
import math
# More math helpers
print(math.floor(3 / 2))
print(math.ceil(3 / 2))
print(math.sqrt(2))
print(math.pow(2, 3))
# min and max int
print(float("inf"))
print(float("-inf"))
```

## Arrays

```
# Defining arrays
arr = [1, 2, 3, 4, 5, 6]
# Indexing a value from an array
n = arr[1] # 2
# Indexing the last element of the array
n = arr[-1] # 6
# Indexing the second to last element of the array
# (and so on using negative values)
n = arr[-2] # 5
# Get the length of the array
print(len(arr)) # 6
# Adding a value to the end
arr.append(7) # [1, 2, 3, 4, 5, 6, 7]
# Inserting a value at a specific index my_arr.insert(idx, value)
# it operation takes O(N) time
arr.insert(2, 8) # [1, 2, 8, 3, 4, 5, 6, 7]
# Removing a value at the end
n = arr.pop() # 7 and the new values of the arr = [1, 2, 8, 3, 4, 5, 6]
# Sub-lists aka slicing arrays, the index 3 is not included
newArr = arr[1:3] # [2, 8]
# Unpacking an array
# ⚠️ the values to unpack must match the elements in the array,
# so, in this case if you write `a, b, c = newArr` you will get an error
a, b = newArr # 2, 8
# Initializing and array of size n with default value of 1
n = 5
arr = [1] * n # [1, 1, 1, 1, 1]
# Traversing an array without the index
arr = [1, 2, 3, 4, 5]
for n in arr:
print(n) # 1, 2, 3, 4, 5
# Traversing an array with the index
for i in range(len(arr)):
print(arr[i]) # 1, 2, 3, 4, 5
# Traversing with both the index and value
for i, n in enumerate(arr):
print(i, n) # 0, 1, 2, 3, 4 (indices) and 1, 2, 3, 4, 5 (values)
# Traverse multiple arrays simultaneously
nums1 = [1, 3, 5]
nums2 = [2, 4, 6]
for n1, n2 in zip(nums1, nums2):
print(n1, n2) # 1, 2 \n 3, 4 \n 5, 6
```

## Sorting and reversing an array

```
# Reverse
arr = [1, 2, 3, 4]
arr.reverse() # [4, 3, 2, 1]
# Sorting (in ascending order which is the default behavior)
arr = [4, 3, -2, -1]
arr.sort() # [-2, -1, 3, 4]
# Sorting (in descending order)
arr = [4, 3, -2, -1]
arr.sort(reverse=True) # [4, 3, -1, -2]
# Sorting an array of strings by alphabetical order
names = ["Bob", "Alice", "Zoe", "Kevin"]
names.sort() # ["Alice", "Bob", "Kevin", "Zoe"]
# Sorting an array of strings by length
names.sort(key=lambda x: len(x)) # ["Bob", "Zoe", "Alice", "Kevin"]
```

## List comprehension

```
# Initialize an array from 0 to 4 values
arr = [i for i in range(5)] # [0, 1, 2, 3, 4]
# Initialize an array from 0 to 4 values and adding i to the i
arr = [i+i for i in range(5)] # [0, 2, 4, 6, 8]
# 2D list (3x3 matrix with 0 values in this case)
arr = [[0] * 3 for i in range(3)] # [[0, 0, 0], [0, 0, 0], [0, 0, 0]]
```

## Strings

```
# As arrays we can slice a string
s = "Hello Pythonist!"
print(s[0:5]) # "Hello"
# Strings are immutable
s[0] = "h" # TypeError: 'str' object does not support item assignment
# We can "update" a string, but in reality what we are doing is
# creating a new one (it's considered an O(N) time operation)
s += ", nice to meet you."
print(s) # "Hello Pythonist!, nice to meet you."
# Valid numeric strings can be converted to integers
print(int("123") + int("10")) # 133
# Numbers can be converted to strings and concatenate them
print(str(123) + str(10)) # "12310"
# Getting the ASCII value from a char
print(ord("A")) # 65
# Combine a list of strings with an empty space delimiter
s = ["Hello", "Pythonist" "!"]
print("".join(s)) # "HelloPythonist!"
```

## Queues (double ended queues)

```
from collections import deque
# Defining the queue
queue = deque()
# Adding values to the end of the queue
queue.append(1)
queue.append(2)
print(queue) # deque([1, 2])
# Pop an element from the left O(1) time complexity
item = queue.popleft()
print(queue) # deque([2])
print(item) # 1
# Add values to the left of the queue O(1) time complexity
queue.appendleft(1)
print(queue) # deque([1, 2])
# Pop an element from the right of the queue O(1) time complexity
item = queue.pop()
print(queue) # deque([1])
print(item) # 2
```

## HashSet

Sometimes, in coding interview questions we need to keep a **unique list of values** and a set is the perfect data structure to achieve it.

```
# Defining a set
mySet = set()
# Adding values
mySet.add(1)
mySet.add(2)
print(mySet) # {1, 2}
# Getting the length of the set
print(len(mySet)) # 2
# Checking if a value exists in the set
print(1 in mySet) # True
print(2 in mySet) # True
print(3 in mySet) # False
# Removing a value from a set
mySet.remove(2)
print(2 in mySet) # False
# List to set
numbers = set([1, 2, 3, 4, 5])
print(numbers) # {1, 2, 3, 4, 5}
# Set comprehension
mySet = { i for i in range(5) }
print(mySet) # {0, 1, 2, 3, 4}
```

## HashMap (aka dict)

One the most (if not the most) common data structures while doing coding interviews is a `map`

, this data structure allows us to keep a list of values in a `key: value`

pair format.

```
# Defining a map
myMap = {}
myMap["Alice"] = 23
myMap["Bob"] = 34
print(myMap) # {"Alice": 23, "Bob": 34}
# Getting the length of the map
print(len(myMap)) # 2
# Updating a key from the map
myMap["Alice"] = 45
print(myMap) # {"Alice": 45, "Bob": 34}
# Checking a value exists in the map
print("Alice" in myMap) # True
print("Gabriel" in myMap) # False
# Deleting a key from a map
item = myMap.pop("Alice")
print(item) # 45
print(myMap) # {"Bob": 34}
# Initializing a map with values
myMap = {"Carlos": 10, "Gustavo": 30, "Juan": 97}
print(myMap) # {"Carlos": 10, "Gustavo": 30, "Juan": 97}
# Dict comprehension
myMap = { i: i*2 for i in range(4) }
print(myMap) # {0: 0, 1: 2, 2: 4, 3: 6}
# Looping through a map with both the keys and values
myMap = {"Carlos": 10, "Gustavo": 30, "Juan": 97}
for key in myMap:
print(key, myMap[key]) # Carlos 10 \n Gustavo 30 \n Juan 97
# Looping through a map with both the keys and values and unpacking
myMap = {"Carlos": 10, "Gustavo": 30, "Juan": 97}
for key, val in myMap.items():
print(key, val) # Carlos 10 \n Gustavo 30 \n Juan 97
# Looping only the values
for val in myMap.values():
print(val) # 10 \n 30 \n 97
```

In Python we call it

`dict`

, but you can also see them as`maps`

,`associative arrays`

,`objects`

, etc.

## Tuples

Tuples are like arrays, but they are immutable.

```
# Defining a tuple
tup = (1, 2, 3)
print(tup) # (1, 2, 3)
# Getting a value
print(tup[0]) # 1
# Using a tuple as map key
myMap = { (1, 2): 3 }
print(myMap) # {(1, 2): 3}
# Using a tuple as set value
mySet = set()
mySet.add((1, 2))
print((1, 2) in mySet) # True
# ⚠️ You can't modify them
tup[0] = 5 # TypeError: 'tuple' object does not support item assignment
```

## Heaps

Heaps are another really common data structure to find the `min`

and `max`

values from a set of elements, under the hood they are implemented using arrays. By default, in Python they are min heaps.

```
import heapq
# Defining a heap
minHeap = []
heapq.heappush(minHeap, 3)
heapq.heappush(minHeap, 2)
heapq.heappush(minHeap, 4)
# Getting the min values which is always at index 0
print(minHeap[0]) # 2
# Looping a heap
while len(minHeap):
print(heapq.heappop(minHeap)) # 2 \n 3 \n 4
```

Python

doesn’t have max heapsby default, the work around is to use a min heap and multiply by -1 when`push`

and`pop`

.

```
import heapq
# Defining a min heap with negative values when push
maxHeap = []
heapq.heappush(maxHeap, -3)
heapq.heappush(maxHeap, -2)
heapq.heappush(maxHeap, -4)
# Max is always at index 0
print(-1 * maxHeap[0]) # 4
# Looping a heap
while len(maxHeap):
print(heapq.heappop(maxHeap) * -1) # 4 \n 3 \n 2
```

If you need to initialize a heap from a list of values, you can do it as follows:

```
import heapq
arr = [2, 1, 8, 4, 5]
heapq.heapify(arr)
while arr:
print(heapq.heappop(arr)) # 1 \n 2 \n 4 \n 5 \n 8
```

## Functions

```
# Defining a function
def add(a: int, b: int) -> int:
return a + b
print(add(10, 20)) # 30
```

## Nested functions

This kind of functions are useful when dealing with graphs and algorithms that uses recursion.

```
# Defining nested functions
def outer(a: str, b: str):
c = "c"
def inner():
return a + b + c
return inner()
print(outer("a", "b")) # "abc"
```

If you want to modify the value of an array inside a nested function you have to use the `nonlocal`

keyword in front of the value you want to modify

```
# Can modify objects but not reassign, unless using nonlocal keyword
def double(arr: list[int], val: int):
def helper():
# Modifying array works:
for i, n in enumerate(arr):
arr[i] *= 2
# Will only modify val in the helper scope
# val *= 2
# this will modify val outside helper scope
nonlocal val
val *= 2
helper()
print(arr, val)
nums = [1, 2]
val = 3
double(nums, val) # [2, 4] 6
```

## Classes

```
class Student:
# Constructor
def __init__(self, names):
# Create member variables
self.names = names
self.size = len(names)
# Set keyword required as param
def printNames(self):
for name in self.names:
print(name)
# Accessing a member variable
def getLength(self) -> int:
return self.size
# Calling a member function from another one
def getDoubleSize(self) -> int:
return 2 * self.getLength()
s = Student(["Alice", "Bob", "Martha"])
s.printNames() # "Alice" \n "Bob" \n "Martha"
print(s.getDoubleSize()) # 6
```

## Resources

Happy learning!