Hello everyone, welcome back! today, I wanted to talk about understanding the importance of efficiency with data structure and algorithms and how to use Big-O notation to measure the complexity of an algorithm.

In my last post, we talked about how to solve computational problems with python but we didn't dive into whether our solution was efficient or not. That's what we will be looking at in this section.

When we refer to the efficiency of a program, we don't only look at the time it takes to run, but at the space required in the computer's memory as well. Often, there will be a trade-off between the two, where you can design a program that runs faster by selecting a data structure that takes up more space—or vice versa.

In order to quantify the time and space an algorithm takes, let's first understand what an algorithm is.

## Algorithm

An algorithm is a series of well-defined steps for solving a problem. Usually, an algorithm takes some kind of input (such as a list) and then produces the desired output (such as a reversed list).

For any problem, there is likely more than one algorithm that can solve the problem, but there are some algorithms that are more efficient than others. However, computers are so fast! that in some problems, we can't tell the difference, so how can we know what algorithm is more efficient than another? and if we can't tell the difference, why try to make our code efficient?.

Well, those are some great questions, and in some cases it's true, one version of a program may take 5 times longer than another, but they both still run so quickly that it has no real impact. However, in other cases, a very small change can make the difference between a program that takes milliseconds to run and a program that takes hours!.

Sometimes, you will hear programmers say:

"This algorithm is better than that algorithm"

But how can we be more specific than that? How do we quantify efficiency?.

Let's take a look at some code examples.

```
def add200(n):
for i in range(2):
n += 100
return n
```

```
def add200_2(n):
for i in range(100):
n += 2
return n
```

Both of the functions above have no real-world use, they are just dummy functions that will help us understand efficiency. Both of them add 200 to whatever the input(n) is. Take a good look at them and tell me, which one is more efficient?

The answer is the `add200`

function. Why?

Although both functions output the exact same result, the `add200_2`

function makes too many iterations, while the `add200()`

function only iterates twice.

With the example above, what we basically did was to estimate which code had more lines to run, let's take a look again at both functions

```
def add200(n):
for i in range(2):
n += 100
return n
def add200_2(n):
for i in range(100):
n += 2
return n
```

The first function has a total of 4 lines but because of the for loop that gets called twice, there is a total of 5 lines (the for loop line doesn't get counted).

Now, let's take a look at the second function. the total of lines is 4 but the for loop gets called 100 times! so running this code will involve running 103 lines of code!.

Counting lines it's not a perfect way of quantifying the efficiency of a program but it's an easy way for us to **approximate** the difference in efficiency between two solutions.

In both examples above, no matter what number we passed as an argument, the number of lines executed will remain the same.

Here's a new code example:

```
def print_to_num(n):
for i in range(n+1): # +1 because python counts from 0
print(i)
print_to_num(10)
```

The code above prints all numbers from 0 to N, meaning the bigger the input N is, the number of lines executed will increase, and that means a longer time to run.

The highlighted idea it's that:

As the input to an algorithm increases, the time required to run the algorithm

mayalso increase.

This **may** happen in some cases, in the last example it does increase the time, but in the first two, it does not.

Let's keep working with the last function and let's try several function calls examples to see the number of lines each one gets to run.

```
print_to_num(2) # 4 lines
print_to_num(3) # 5 lines
print_to_num(4) # 6 lines
print_to_num(5) # 7 lines
```

As you can see, when N goes up by 1 the number of lines will also go up by 1. We can also say that the number of lines executed increases by a **proportional** amount. This type of relationship is called a **Linear relationship** if we graph the relationship we can see why it's called that.

The x-axis represents the input size, in this case, a number, and the y-axis represents the number of operations that will be performed in this case we're thinking of an "operation" as a line of Python code which is not the most accurate but will do for now.

Let's take a look at another function example where the operations increase at a **none** constant rate.

```
def print_square(n):
for i in range(1, n+1):
for j in range(1, n+1):
print("*", end=" ") # end=" " for printing all "*" in a single line separated by two white spaces
print("") # this is used to mimic an "enter"
print_square(5)
```

This code prints a square made out of "*" that it's exactly N x N in size

```
output:
* * * * *
* * * * *
* * * * *
* * * * *
* * * * *
```

Notice that this function has a **nested** loop, that means, a loop inside a loop, and take a good look at the fact that both loops have a linear rate of increase but they are **nested** that makes the rate of increase **quadratic** that means that when the input goes up by a certain amount, the number of operations goes up by the **square** of that amount.

```
print_square(1) # 1 line
print_square(2) # 4 lines
print_square(3) # 9 lines
```

Let's add the quadratic rate of increase to our graph:

Our `print_square`

function exhibits a quadratic rate of increase which as you can see is a much faster rate of increase, this means that as we pass larger numbers, the number of operations the computer has to perform shoots up very quickly making the program far less efficient
These are only two examples of the rate of increase but there are many more. Here are the most common:

Note. when people refer to the rate of increase, they will often use the term "order". For example, instead of saying: "This algorithm has a linear rate of increase" they will say: "The order of this algorithm is linear"

Big-O notation is a "simplified analysis of an algorithm's efficiency". Big-O gives us an algorithm's complexity in terms of the input size (N) and it's independent of the machine we run the algorithms on. Big-O can give us the time but also space complexity of an algorithm

There are three types of ways we can look at an algorithm's efficiency.

- Worst-case
- Best-case
- Average-case

Normally, when talking about Big-O notation we will typically look at the worst-case scenario, this doesn't mean the others are not used, but normally we want to know what the worst case is.

Big-O notation ignores constants. Example: let's say you have a function that has a running time of O(5n), in this case, we say that it runs on the order of O(n) because as N gets larger, the 5 no longer matters.

Terms priority. This means that if a function has a part that has an O(1) order but another part of that same function has an order of O(n) we will say that the whole function has an order of O(n). This includes sections that might not run, for example, in an if-else statement.

`O(1) < O(logn) < O(n) < O(nlogn) < O(n2) < O(2n) < O(n!)`

(see graph above)

In this section, we will take a look at example lines of code and see how O(n) is used.

`O(1)`

Take a look at the line below

```
print((5*10)/2) # outputs 25
```

As you can see, this code simply computes a simple math operation and it's not dependent on any input. This is an example of O(1) order or constant time.

What if we have not one but three simple math operations.

```
n = 5 + 2 # O(1)
y = 3 * 5 # O(1)
x = n + y # O(1)
```

Well, in this case, each line of code has an O(1) order, so what we need to do is add all of them like this: `O(1)+O(1)+O(1)`

BUT let's remember the first rule and ignore constants, making the final result be `O(1)`

`O(n)`

Let's try another example:

```
for i in range(n): # n * O(1) = O(n)
print(i) # O(1)
```

The clearest example of Linear time code, it's a simple for loop that iterates from 0 to N. In the code shown above, the print statement has an order of `O(1)`

but because it's inside a loop, we need to multiply `O(1) * N`

which would give us a result of `O(N)`

If, for example, we had both examples above together like this:

```
n = 5 + 2 # O(1)
y = 3 * 5 # O(1)
x = n + y # O(1)
for i in range(n): # n * O(1) = O(n)
print(i) # O(1)
```

We would have the `O(1)`

of the simple math and print lines, and `O(n)`

of the for loop, so, to calculate the total time we need to add both of them BUT let's remember our second rule of terms priority and make `O(N)`

the final result for this code

`O(n²)`

The easiest way to get a code to have a Quadratic order it's to simply have a nested loop with both of them iterating from 0 to N. Let's see the code example that we saw earlier:

```
def print_square(n):
for i in range(1, n+1):
for j in range(1, n+1):
print("*", end=" ") # end=" " for printing all "*" in a single line separated by two white spaces
print("") # this is used to mimic an "enter"
print_square(5)
```

The first for loop has a time complexity of `O(N)`

, the second for loop also has a time complexity of `O(N)`

, and finally, the print statements have a complexity of `O(1)`

. And hopefully, you can see pretty clearly that the print statement will be executed `N * N`

times, making this whole code have a time complexity of `O(N²)`

If, we put all previous codes together like this:

```
n = 5 + 2 # O(1)
y = 3 * 5 # O(1)
x = n + y # O(1)
for i in range(n): # n * O(1) = O(n)
print(i) # O(1)
for i in range(1, n+1): # O(n) * O(n) = O(n²)
for j in range(1, n+1):
print("*", end=" ")
print("") # this is used to mimic an "enter"
```

we know the first code has a complexity of `O(1)`

the second code has a complexity of `O(n)`

and the third code has a complexity of `O(n²)`

and because of our second rule, the complexity of the whole code it's `O(n²)`

`O(log n)`

An algorithm with a Logarithmic run time, it's that code that reduces the size of the input data in each step (it doesn't need to look at all values of the input data), for example:

```
input_array = [0, 2, 5, 6, 8, 9, 1, 4, 3, 10]
# 3 means it's going to iterate in steps of three
for index in range(0, len(input_array), 3): # <== O(log n)
print(input_array[index]) # 0 6 1 10
```

This code receives an array and iterates through it, which would make the code time complexity `O(n)`

BUT because it's not going through every element, and it's going in steps of 3 instead, the time complexity becomes `O(log n)`

`(O(n log n))`

An algorithm is said to have a log-linear time complexity when each operation in the input data has a `logarithm`

time complexity. It is commonly seen in sorting algorithms

```
codes = set()
for phone in phones_arr:
codes.add(phone[:4])
print('\n'.join(sorted(codes))) # <== O(n log n)
```

The code above iterates through an array of phone numbers and adds the first four digits of each phone to a "codes" set(a set doesn't allow duplicates) and finally the code prints the codes but are sorted.

`O(2ⁿ)`

Algorithms have an exponential time complexity when the growth doubles with each addition to the input data set. This kind of time complexity is usually seen in brute-force algorithms.

```
def fibonacci(n):
if n <= 1:
return n
return fibonacci(n-1) + fibonacci(n-2)
print(fibonacci(8)) # outputs 21
```

A great example of an exponential time algorithm is the recursive calculation of Fibonacci numbers, the code above receives a number and prints the N number in the Fibonacci sequence:
`0, 1, 1, 2, 3, 5, 8, 13, 21`

An algorithm where the operational execution complexity increases factorially with the increase in the input size. As you can see from the graph above, this is the most inefficient order that an algorithm can have.

```
def factorial(n):
if n == 1:
return n
else:
return n * factorial(n-1)
print(factorial(4))
```

The function above receives a number and uses recursion to print the result of multiplying all numbers from 1 to N
`4*3*2*1 = 24`

You've reached the end of this post on complexity and Big-O notation to quantify time efficiency, I really hope some of the concepts we saw today have helped you and hopefully you will be thinking not only about solving a problem but to think weather your code is efficient or not. If you liked this post, make sure to share it with another person that might find this interesting, and let me know in the comments your thoughts, suggestions, and what you will like to see next.

See you in the next post, stay tuned for more!

]]>Hello everyone, welcome back!, today I wanted to give a beginners overview of Linked Lists, we will take a good look at what are they? what are they for? and we will look through some examples together.

A linked list is an extension of a list but it's definitely NOT an array. By definition a linked list is a:

Dynamic data structure where each element (called a node) is made up of two items: the data and a reference (or pointer), which points to the next node. A linked list is a collection of nodes where each node is connected to the next node through a pointer.

Here it's an image to give a better understanding of a linked list

With a linked list, there are still some things that have order but there are no indices. Instead, a linked list is characterized by its `Links`

. Each element has a notion of what the next element is since it's connected to it, but not necessarily how long the list is or where it is in the list.
An array's different. There is nothing in one element of the array that says "here's your next element" like a linked list would, instead an array knows what the next element is by what the next `index`

is.

Let's walk through the differences together.

- A data structure consisting of a collection of elements each identified by the array index.
- Supports random access, the programmer can directly access an element in the array using an index
- Elements are stored in contiguous memory locations
- Programmer has to specify the size of the array when declaring the array
- Elements are independent of each other

- A linear collection of data elements whose order is not given by their location in memory.
- Supports sequential access, the programmer has to go sequentially through each element until reaching the searched element
- Elements can be stored anywhere in memory
- There is no need in specifying the length of the linked list
- An element has to point to the next or previous element

When talking about linked lists you will often hear "Node" instead of "Element"

When I was studying linked lists I was already pretty familiar with arrays and felt pretty comfortable using them, and if you are a beginner you are probably feeling the same way and are wondering "Hey, arrays are easy to access, have a set length, the elements are independent of each other, why make my life harder with this new linked list stuff?", if you thought of that, congratulations! you are on your way to becoming a great programmer since you have to always ask yourself the why of things.

The answer to the question "Why use linked lists?" can be a complex one, but for now, the simple and short answer is Efficiency. Turns out, that adding or removing an element from a linked list is so much easier in comparison to an array. And I want to give a quick reminder that we are working with python here.

Let's learn more about linked lists while we work through some examples and start writing some code.

Because of the definition we saw earlier, we are going to approach this, by creating a `Node`

, a node it's going to have, two attributes, the value, and the `next`

attribute that's going to be a pointer pointing to the next node. From the image above, we can see how each node points to the next one until the list it's finished and the last node points to `None`

. this collection of nodes pointing to the next one is going to be the Linked List.

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
```

this node has a "value" property that can be passed as an argument, and a "next" attribute that points to the next `Node`

, in this case, it's pointing to None by default.

Using our newly created Node class, it's time to create our `Head`

, the "Head" it's basically the first Node.

```
head = Node(8)
```

Creating a new Node with the value of 8 and saving it in a new head variable.

Like we saw in the previous step, to create a new Node simply code `Node(value)`

, and to link it to the head what we need to code is:

```
head.next = Node(4)
```

In the code above, `head.next`

had the value of None, but now we are replacing that with a new Node with the value of 4.

Right now, we have a linked list that has a Head with the value of 8 then the next node with the value of 4 like this:

Here's our progress:

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
head = Node(2)
head.next = Node(1)
```

To print out the values in the linked list we have to code:

```
print(head.value)
print(head.next.value)
```

```
Output:
8
4
```

Let's remember that this is what we want to accomplish:

To do this, we need to create three more nodes, and we need to attach each one to the next attribute of the node that comes before it. Notice that we don't have a direct reference to any of the nodes other than the head.

```
head.next.next = Node(2)
head.next.next.next = Node(5)
```

Let's print all the values:

```
print(head.value)
print(head.next.value)
print(head.next.next.value)
print(head.next.next.next.value)
```

This process isn't very efficient, let's write a function that receives the head and traverses the linked list to print out all of the values.

```
def print_linked(head):
current = head # current node set to the head
while current: # loop that will run as long as current exists
print(current.value) # printing the current node value
current = current.next # moving to the next node
print_linked(head)
```

Ahh, so much better, now, no matter how long the linked list is, with `print_linked(head)`

we no longer need to code `print(head.next.next.next.next)`

you get the point.

Up until now, we have to create a variable with the head and pass that to our functions in order to interact with it, but an alternative it's to create a Linked_List class and code methods to it. Let's take a look at some code to understand what I mean.

```
# Linked_List class with a default head value of None meaning empty list
class Linked_List:
def __init__(self):
self.head = None
```

Let's add our print function as a method to this class.

```
def print_linked(self):
current = self.head
while current:
print(current.value)
current = current.next
```

In the code above, we made tiny changes to fit in as a method, in this case instead of receiving the head, we receive the `self`

attribute, and instead of setting current to the head, we set it to the `self.head`

, other than that, everything stays the same.

Now, we have our Linked list class with a print method and we have to create the actual object like this:

```
my_linked_list = Linked_List()
```

and if we wanted to print the values of that linked list, all we have to do is:

```
my_linked_list.print_linked()
```

If you tested the lines above you should have seen no output since the linked list is completely empty, let's change that by adding a method that allows us to append items into the list.

```
def append(self, value):
# checking if the linked list is empty
if self.head is None:
# If so, set the head to a new node with the passed value
self.head = Node(value)
return
# Traverse the linked list until reaching the end of the list
current = self.head
while current.next:
current = current.next
# Append the new node with the passed value
current.next = Node(value)
```

Here's the full code:

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
class Linked_List:
def __init__(self):
self.head = None
def print_linked(self):
current = self.head
while current:
print(current.value)
current = current.next
def append(self, value):
if self.head is None:
self.head = Node(value)
return
current = self.head
while current.next:
current = current.next
current.next = Node(value)
my_linked_list = Linked_List() # this is creating a Linked_List object
my_linked_list.append(8) # appending 8 to the list
my_linked_list.append(4) # appending 4 to the list
my_linked_list.append(2) # appending 2 to the list
my_linked_list.append(5) # appending 5 to the list
my_linked_list.print_linked() # printing the list
```

If you test the code above you shall now see an output of `8 4 2 5`

like the image below

I hope you're getting the hang of this, let's try adding more methods that will make working with linked lists better.

Below you will find the exercises for this section, I highly encourage you to try to code them yourself before watching the solutions that can be found at the end of this section. And I also recommend you try each of the methods individually first before trying to use multiple of them together.

`to_list()`

methodWhen the method is called on the linked list it should convert the linked list back into a normal python list.

```
output:
[8,4,2,5]
```

`prepend()`

methodMethod similar to the `append()`

method but instead of appending at the end of the linked list it will append at the beginning of the list

```
my_linked_list.prepend(7)
my_linked_list.print_linked()
```

```
output:
7
8
4
2
5
```

`search()`

method`print(my_linked_list.search(4))`

will return the Node if there is one with a matching value, if not, it will return a ValueError indicating that the searched value is not found in the list. Because it returns the searched Node you can access its value like this:

```
# if there is a node with the value of 4, 4 will be printed
print(my_linked_list.search(4).value)
```

```
output:
4
```

`remove()`

methodMethod traverses the linked list and removes the Node with the passed value

```
my_linked_list.remove(4)
my_linked_list.print_linked()
```

```
output:
7
8
2
5
```

`pop()`

methodMethod that removes the first node of the linked list

```
my_linked_list.pop()
my_linked_list.print_linked()
```

```
output:
8
2
5
```

`insert()`

methodThis method receives a position and a value and inserts a new node with the passed value in the passed position

```
my_linked_list.insert(10,1)
my_linked_list.print_linked()
```

```
output:
8
10
2
5
```

`size()`

methodThis method shall return the length of the linked list like the method `len()`

would on a normal python list

```
print(my_linked_list.size())
```

```
output:
4
```

`reverse()`

methodReversing a linked list is a really common interview problem and I highly recommend that you put extra effort into understanding this method. This method shall, like the name suggests, reverse the linked list nodes order.

```
my_linked_list.reverse()
my_linked_list.print_linked()
```

```
output:
5
2
10
8
```

Feel free to test out combinations of the methods and try each one individually and do your best in understanding them. You can also try adding your own methods, and remember that the solutions shown below are not the only correct ways of coding them.

```
class Node:
def __init__(self, value):
self.value = value
self.next = None
class Linked_List:
def __init__(self):
self.head = None
# ------------------------------------------ #
def print_linked(self):
current = self.head
while current:
print(current.value)
current = current.next
# ------------------------------------------ #
def append(self, value):
if self.head is None:
self.head = Node(value)
return
current = self.head
while current.next:
current = current.next
current.next = Node(value)
# ------------------------------------------ #
def to_list(self):
out = []
node = self.head
while node:
out.append(node.value)
node = node.next
return out
# ------------------------------------------ #
def prepend(self, value):
if self.head is None:
self.head = Node(value)
return
new_head = Node(value)
new_head.next = self.head
self.head = new_head
# ------------------------------------------ #
def search(self, value):
if self.head is None:
return None
node = self.head
while node:
if node.value == value:
return node
node = node.next
raise ValueError("Value not found in the list.")
# ------------------------------------------ #
def remove(self, value):
if self.head is None:
return
if self.head.value == value:
self.head = self.head.next
return
node = self.head
while node.next:
if node.next.value == value:
node.next = node.next.next
return
node = node.next
raise ValueError("Value not found in the list.")
# ------------------------------------------ #
def pop(self):
if self.head is None:
return None
node = self.head
self.head = self.head.next
return node.value
# ------------------------------------------ #
def insert(self, value, pos):
if self.head is None:
self.head = Node(value)
return
if pos == 0:
self.prepend(value)
return
index = 0
node = self.head
while node.next and index <= pos:
if (pos - 1) == index:
new_node = Node(value)
new_node.next = node.next
node.next = new_node
return
index += 1
node = node.next
else:
self.append(value)
# ------------------------------------------ #
def size(self):
size = 0
node = self.head
while node:
size += 1
node = node.next
return size
# ------------------------------------------ #
def reverse(self):
previous = None
current = self.head
while current:
next = current.next
current.next = previous
previous = current
current = next
self.head = previous
# ------------------------------------------ #
my_linked_list = Linked_List() # creating linked list obj
my_linked_list.append(8) # appending 8
my_linked_list.append(4) # appending 4
my_linked_list.append(2) # appending 2
my_linked_list.append(5) # appending 5
my_linked_list.print_linked() # output: 8 4 2 5
print(my_linked_list.to_list()) # output: [8,4,2,5]
my_linked_list.prepend(7) # appending 7 to the start of the linked list
my_linked_list.print_linked() # output: 7 8 4 2 5
print(my_linked_list.search(4).value) # output: 4
my_linked_list.remove(4) # remove the node with value 4 from the list
my_linked_list.print_linked() # output: 7 8 2 5
my_linked_list.pop() # remove the first node
my_linked_list.print_linked() # output: 8 2 5
my_linked_list.insert(10, 1) # insert node with value 10 in position 1
my_linked_list.print_linked() # output: 8 10 2 5
print(my_linked_list.size()) # output: 4
my_linked_list.reverse() # reverse the order of the nodes
my_linked_list.print_linked() # output: 5 2 10 8
```

Up until this point, we have been working with a type of linked list called: "singly linked list", in this kind of linked list, each node is connected only to the next node in the list.

This connection is typically implemented by setting the `next`

attribute on a node object itself. But there are more types of linked lists.

This type of list is the exact same as the singly linked list with the addition of also having a connection backward.

To implement a Doubly linked list simply change your node to have a previous property like this:

```
class DoubleNode:
def __init__(self, value):
self.value = value
self.next = None
self.previous = None # new property
```

using doubly linked lists can give us advantages that a singly linked list can't, for example, now that we track the tail, we can do things like appending a node to a list's tail in constant time.

```
def append(self, value):
if self.head is None:
self.head = DoubleNode(value)
self.tail = self.head
return
self.tail.next = DoubleNode(value)
self.tail.next.previous = self.tail
self.tail = self.tail.next
return
```

Linked circular lists occur when the chain of nodes links to itself somewhere. For example, NodeA -> NodeB -> NodeC -> NodeD -> NodeB is a circular list because NodeD points to NodeB creating a NodeB -> NodeC -> NodeD -> NodeB loop.

A circular linked list in simple words, it's a singly or doubly list where all nodes are connected to form a loop or "circle" meaning there is no NULL at the end, this can be a problem since if we try to iterate through it we will never find the end. We usually want to detect if there's a loop in our linked list to avoid an infinite iteration.

In this section, we'll see a way we can detect loops in a linked list with python. The way we'll do this is by using two pointers called "runners", both of them will traverse through the linked list but one of them it's going to iterate twice as fast, meaning it will advance two nodes at a time. If a loop exists, the fast runner will eventually catch up with the slow runner and both pointers will be pointing at the same node at the same time, if this happens you will know for sure there's a loop, see the image below to understand this technique.

`hasLoop()`

methodI'm confident you can do this on your own, see the image above thoroughly to understand the algorithm you're about to code, and also take another look at the past exercises, you should find everything you need in this post. Make your best effort! and don't worry if you don't get it right away we'll see the solution below.

`hasLoop()`

methodThe first thing we need to do is to define our `hasLoop()`

method:

```
def hasLoop(self):
# your code goes here
```

Next, we'll check if the list is empty, in the case that the condition is True we'll return False meaning there is no loop

```
if self.head is None:
return False
```

Then, we need to create our two pointers as we talked about earlier

```
slow = self.head
fast = self.head
```

Now the fun part! while the fast node and the fast.next node exists, let's advance the slow node by one and the fast by two

```
while fast and fast.next:
slow = slow.next
fast = fast.next.next
```

and for each iteration, we need to check if the slow node is the same as the fast one, if that is the case then we just found a loop and the code should return True, if the while loops finishes, it means there was an end to the list and the code should return False.

```
if slow == fast:
return True
return False
```

Full code:

```
def hasLoop(self):
if self.head is None: # check if the linked list is empty
return False # return False, meaning there is no loop
slow = self.head # create the slow node
fast = self.head # create the fast node
while fast and fast.next: # while fast and fast.next exist:
slow = slow.next # advance the slow node by one
fast = fast.next.next # advance the fast node by two
if slow == fast: # if the nodes are the same
return True # return True, meaning there is a loop
# if the loop finishes, that means there was an end to the list
# and there was no loop
return False
```

Let's suppose we have a **nested** linked list, this means that the value of each node in the linked list is another ordered linked list. The image below illustrates this.

In this section, we'll **flatten** this linked list, which means to combine all nested lists into one **ordered** single linked list like this:

`NestedLinkedList`

classThis class should inherit the Linked_List class and inside of the new class let's add our flatten method.

```
class NestedLinkedList(Linked_List):
def flatten(self):
# code goes here
```

`merge()`

Helper FunctionThis function will be useful for merging two linked lists and having them into a single **ordered** linked list, this function it's not going to be a `NestedLinkedList`

method and should return an instance of LinkedList.

```
def merge(list1, list2):
# your code goes here
return
```

`merge()`

walkthroughNote, this solution expects the passed lists to be ordered.

First, we need to create a new LinkedList object, that will be named `merged`

```
merged = Linked_List()
```

Next, let's check whether any of the passed lists are None. If list1 is None, immediately return list2, if list2 is None, immediately return list1.

```
if list1 is None:
return list2
if list2 is None:
return list1
```

Then, we'll create two traversers, one called list1_node and the second one called list2_node. And using both of them, we'll traverse both lists while they exist.

```
list1_node = list1.head
list2_node = list2.head
while list1_node or list2_node: # pay attention to the OR
```

If the first node does not exist, meaning the first list is traversed, append the list2_node to the "result list"(merged) and advance the list2_node to the next node in that list.

```
if list1_node is None: # if list1 is traversed
merged.append(list2_node.value) # append the second list node to the "result"
list2_node = list2_node.next # and advance the second node to the next one
```

Repeat the step above but with the list2

```
elif list2_node is None: # if list2 is traversed
merged.append(list1_node.value) # append the first node to the "result"
list1_node = list1_node.next # advance the first node to the next one
```

Now we can work in the cases that none of the lists are done traversing. Which would always be the first iteration.

Because we want the output to be an ordered linked list but with both lists merged, we need to check which node has the smallest value and append it.

```
# if the first node value is smaller than the second node value
elif list1_node.value <= list2_node.value:
merged.append(list1_node.value) # append the first node
list1_node = list1_node.next # and advance to the next node
else:
merged.append(list2_node.value) # append the second node
list2_node = list2_node.next # advance to the next node
```

Finally, after the while loop exits indicating that both lists have been traversed, return the result, (merge) linked list.

```
return merged
```

Here's the full `merge()`

procedure:

```
def merge(list1, list2):
merged = Linked_List()
if list1 is None:
return list2
if list2 is None:
return list1
list1_node = list1.head # [5,6,7] list one
list2_node = list2.head # [1,2,3] list two
while list1_node or list2_node: # 5 - 1
if list1_node is None: # if list1 is traversed
# append the second list node to the "result"
merged.append(list2_node.value)
list2_node = list2_node.next # and advance the second node to the next one
elif list2_node is None: # if list2 is traversed
# append the first node to the "result"
merged.append(list1_node.value)
list1_node = list1_node.next # advance the first node to the next one
# if the first node value is smaller than the second node value
elif list1_node.value <= list2_node.value:
merged.append(list1_node.value) # append the first node
list1_node = list1_node.next # and advance to the next node
else:
merged.append(list2_node.value) # append the second node
list2_node = list2_node.next # advance to the next node
return merged
```

`Flatten()`

MethodWith the help of our `merge()`

helper function, there is an easy solution to finish our `Flatten()`

method by using **recursion**.

We won't talk about recursion in detail in this post, but for those that are not familiar with the concept, recursion is basically invoking a function inside itself, here's a code example that returns the factorial of any number.

```
def factorial(x): # function definition
if x == 1: # break point, important to exit the "loop"
return 1
else:
return (x*factorial(x-1)) # invoking itself
print(factorial(4)) # 4 * 3 * 2 * 1 = 24
```

As you can see from the code above, the `factorial()`

function it's being invoked inside of itself, that's a recursive function.

Back to our `Flatten()`

method. Let's apply recursion!

```
class NestedLinkedList(Linked_List):
def flatten(self):
return self._flatten(self.head)
def _flatten(self, node): # a recursive function
# A termination condition
if node.next is None:
# if there is no next node in the list
# merge the last, current node
return merge(node.value, None)
# _flatten() is calling itself untill a termination condition is achieved
# <-- Both arguments are a simple LinkedList each
return merge(node.value, self._flatten(node.next))
```

`flatten()`

FunctionFirst, let's create three simple, normal,ordered, linked lists.

```
''' Create a simple LinkedList'''
linked_list = Linked_List()
linked_list.append(0)
linked_list.append(4)
linked_list.append(8)
linked_list.print_linked()
# 0 => 4 => 8
''' Create another simple LinkedList'''
second_linked_list = Linked_List()
second_linked_list.append(1)
second_linked_list.append(2)
second_linked_list.append(3)
second_linked_list.print_linked()
# 1 => 2 => 3
''' Create another simple LinkedList'''
third_linked_list = Linked_List()
third_linked_list.append(6)
third_linked_list.append(7)
third_linked_list.append(9)
third_linked_list.print_linked()
# 6 => 7 => 9
```

Next, let's create a **nested** linked list object using our class

```
nested_linked_list = NestedLinkedList()
```

Something really cool, it's that when we defined the `NestedLinkedList`

class, we inherited the `Linked_List`

class, which means that all of the previous methods we wrote for the `Linked_list`

class are going to be available to use in the `NestedLinkedList`

class.

```
# Here we inherited the Linked_List class
class NestedLinkedList(Linked_List): # making all of its methods available here too!
```

Now, let's append the three simple linked lists into this `nested_linked_list`

using the `append()`

method.

```
nested_linked_list.append(linked_list)
nested_linked_list.append(second_linked_list)
nested_linked_list.append(third_linked_list)
```

Lastly, create the flattened list like this:

```
flattened = nested_linked_list.flatten()
```

and print it out!

```
flattened.print_linked()
```

You've reached the end of this lesson on Linked Lists, we saw what are they? how do they differentiate from a normal python list? what are some pros and cons? and we also saw code examples of the most common ways to interact with Linked lists.

I really hope some of the guidelines we saw today were helpful and the basic foundations on this topic were well understood, remember, there is still a lot to learn and we definitively didn't cover everything on linked lists, but I hope this was a good beginner overview and that you grasped the concepts and feel more confident on your programming journey.

Let me know in the comments what you thought about this post and let me know what you will like to see next. See you in the next post, stay tuned!

]]>Hello everyone, welcome back! today I want to talk about a very important topic: how to solve problems?. Learning how to solve problems is one of the most important skills you can learn, and improving as a problem solver is a lifelong challenge that cannot be learned through this short lesson, however, I want to show you important tips on how to tackle more complex programming problems, and hopefully, this post will teach you the essentials to problem-solving.

In this post, we will tackle a specific problem, and talk about how I would go about solving it, and the goal of this exercise is not just to solve the problem but to draw general ideas on how to solve any type of programming problem.

Given your birthday and the current date, calculate your age in days. Compensate for leap years. Assume that the birthday and current date are correct (no time travel).

It's often tempting to start writing code early, the problem with this is that we are likely to write the wrong code and get frustrated, it's also likely that you think that you "solved the problem" but in reality, you might end up doing something completely different to what it was asked.

We need to emphasize that we are working on a `computational problem`

, and what all computational problems have in common is that they have inputs and desired outputs, so, a problem is defined by the set of possible inputs (this is usually an infinite set) and the relationships between those inputs and the desired outputs. And a solution is a procedure that can take any input in that set and produces the desired output that satisfies the relationship, in this case, the relationship we want is that the output is the number of days between the birthday and the current date.

So, the first step to understanding a problem is to understand what the possible inputs are.

If we take another look at the problem we realized that it's clearly stated that we're given two dates as inputs.

Given your birthday and the current date, calculate your age in days. Compensate for leap years. Assume that the birthday and current date are correct (no time travel).

We always need to ask ourselves this question, for the moment we know the type of the inputs, but if we take another look at our problem we can find a good clue on what to expect.

Given your birthday and the current date, calculate your age in days. Compensate for leap years. Assume that the birthday and current date are correct (no time travel).

Thanks to that statement we know that the second date needs to be after the first one. Assumptions like this one make life easier for programmers since our code has to work for fewer possible inputs, however, we are going to be good defensive programmers and check if the second date is after the first date in our code. Checking if the requirement is correct is a good practice since other people or even ourselves can make mistakes.

The other assumption we might need to think about is the range of dates. Calendars are very complicated and they've changed over history, so, we are going to require that the dates are valid dates in the Gregorian calendar, which started in October 1582.

For most real-world problems, it's up to you to figure out how to encode the inputs and this is one of the most important decisions that you can make in solving a problem. In this case, the problem template below indicates how the inputs are represented

```
# function that returns the dates difference in days
def daysBetweenDates( year1, month1, day1, year2, month2, day2 ):
# You're code goes here
```

As we can see there are six parameters to the daysBetweenDates procedure, which means we're going to be passing in six different values to represent those two dates. Some of you might see that there are better ways to pass the date, as an object for example but for the sake of clarity we're going to be passing those six values.

The statement of the question gives us some idea of what the output should be in the calculate your age part.

But it doesn't specify really explicitly what we want the output to be

In this case, after reading the problem, it's clear that the output should be a **number ** representing the days of the difference between date1 and date2 assuming date1 is before date2.

Now that we know what the inputs are and what the outputs are it's time to understand the relationship between the two by working out some examples.

Take a good look at the problems below and try to figure out what the output for each call should be.

```
daysBetweenDates(2020,11,7, 2020,11,7)
daysBetweenDates(2015,12,7, 2015,12,8)
daysBetweenDates(2010,12,8, 2010,12,7)
daysBetweenDates(2019,5,15, 2021,5,15)
daysBetweenDates(2012,6,29,2013,6,31)
```

For problem one, the output should be 0 since both dates are the same. For problem two the output should be 1 since there is only a one-day difference. Output for problem three should be an error since the second date is before the first date. Problem four was hard since, there was a two-year difference and 2020 is a leap year, so, the output should be 731. The last one was tricky since there is no day 31 in June!, so we would like to have an error indicating the invalid input.

At this point, you might be ready to start coding, but if the problem is challenging one you're probably not.

The first step to solving a problem as a human is to look at an example and in this case let's say we wanted to find the difference between the dates (2013,01,24) and (2013,06,29), as a human the first thing that we might do is to look at a calendar and find the first date, then see how many days are left for that month, in this case, that is 7 days, we would probably grab a piece of paper and write that number down. Then we would count how many days has every month until we reach the second date.
`7+28+31+30+31+29 = 156`

We now have a starting point and it's time to write down an algorithm that systematizes how we solve it, in this case, we're going to write this as `Pseudocode`

meaning we aren't focusing on coding real python code but the ideas instead.

```
# find the days between the dates (2013,01,24) and (2013,06,29)
#(year1,month1,day1,year2,month2,day2)
days = # days in month1(31) - starting day(24) = 7
while month1 < month2:
days += # days in current month1
month1 += 1 # moving to the next month
days += day2 # add the remaining days from the second date month(29)
# add the years days
while year1 < year2:
days += # days in year1
```

In this case, I don't think we should, there are several cases that this code does not consider, like:

- input dates in same month
`(2013,06,24 and 2013,06,29) # valid input`

- month2 < month1
`(2012,07,24 and 2013,06,29) # valid input`

- year2 < year1
`(2013,01,24 and 2012,06,29) # invalid input`

- accounting for leap years.

Let's think of a more simple mechanical algorithm. As humans, it's very inefficient to manually count day by day, but not for computers, and I want to emphasize optimization, don't optimize prematurely! focus on solving the problem first.

```
days = 0
while date1 < date2:
date1 += # advance to next day
days += 1
return days
```

This is the most simple approach possible, it's adding 1 day until we reach the second date.

I want to take a look at line three.

```
date1 += # advance to next day
```

This is clearly the most important part, and because of it, we should start by coding a `nextDay(year,month,day)`

procedure. The function should receive a year, month, and day and return the year, month, and day of the next day.

```
def nextDay(year, month, day):
"""
For simplicity, assume every month has 30 days.
"""
if day < 30:
return year, month, day + 1
elif month < 12:
return year, month + 1, 1
else:
return year + 1, 1 ,1
print(nextDay(2021,3,11)) # expected output: 2021,3,12
print(nextDay(2021,3,29)) # expected output: 2021,3,30
print(nextDay(2021,3,30)) # expected output: 2021,4,1
print(nextDay(2021,11,30)) # expected output: 2021,12,1
print(nextDay(2021,12,30)) # expected output: 2022,1,1
```

As we can see, the code above accounts for the case that it's the end of the month and for the end of the year, the only problem with it it's that it's assuming all months have 30 days, so, what should we do next?

In this case, we can make the `nextDay`

procedure to work with real months but we can actually do that later and start coding the `daysBetweenDates`

procedure first, the advantage is that we'll be more confident if we're on the right track and most importantly, we'll be closer to having an answer, then we can correct the `nextDay`

procedure since that will be a significantly smaller detail that shouldn't affect our `daysBetweenDates`

procedure.

```
days = 0
while date1 < date2:
date1 += # advance to next day
days += 1
return days
```

with our new `nextDay`

procedure we can now code this right? well, not quite, if you have already tried you realized what the problem is. The comparison between dates.

```
while date1 < date2:
```

Let's understand exactly what the helper function shall do, in this case, we only need to return a boolean, True if the first date is before the second, and False if it is not. Let's code it!.

```
def date1BeforeDate2(year1,month1,day1,year2,month2,day2):
"""
returns True if the first date is before the second date
"""
if year1 < year2:
return True
if month1 < month2:
return True
if day1 < day2:
return True
return False
```

The solution for this helper function was really easy, we only needed to compare if the year1 was before year2, if that condition it's True, simply return True, then repeat the condition with the months and days, if none of the conditions were True simply return False.

Now that we have our helper function, it's time to try to code the pseudocode again with our newly created function.

Pseudocode:

```
days = 0
while date1 < date2:
date1 += # advance to next day
days += 1
return days
```

DaysBetweenDates procedure:

```
def daysBetweenDates(year1, month1, day1, year2, month2, day2):
"""
returns the difference of two dates in days
"""
days = 0
while date1BeforeDate2(year1, month1, day1, year2, month2, day2):
year1, month1, day1 = nextDay(year1, month1, day1)
days += 1
return days
print(daysBetweenDates(2019, 5, 15, 2021, 5, 15)) # output should have been 731
print(daysBetweenDates(2023, 5, 15, 2020, 5, 15)) # invalid inputs
```

So much progress! however, if you run the code above you will realize that the output is 720 when it should have been 731, why?. Let's not forget that the `nextDay`

procedure is incorrectly assuming all months have 30 days!!. However, it's very clear that we're on the right track. For the second print, we purposely passed invalid inputs to see what happens, in this case, the code output 0, but that is not really what we want.

Like we talked about before, we are going to follow a good defensive programming practice and check for invalid inputs inside our `daysBetweenDates`

procedure.

Python `assert()`

is perfect for what we want. `assert()`

allows us to perform comparisons. If the expression contained within it is False, an exception will be thrown, specifically an `AssertionError`

.

Example:

```
assert(5 < 3) # False, an AssertionError will be shown
assert(2 < 4) # True, nothing will happen
```

This fits perfectly for what we want, that is, checking if the first date is before the second date. Here is my implementation:

```
def daysBetweenDates(year1, month1, day1, year2, month2, day2):
"""
returns the difference of two dates in days
"""
assert(date1BeforeDate2(year1, month1, day1, year2, month2, day2))
days = 0
while date1BeforeDate2(year1, month1, day1, year2, month2, day2):
year1, month1, day1 = nextDay(year1, month1, day1)
days += 1
return days
```

Now if we test an invalid case where the second date is before the first date we should see an `AssertionError`

pop up in the terminal like the image below:

Right now, we're 70% of the way, some of the things left to do are to make sure that the `daysBetweenDates`

procedure accounts for leap years and months with the correct amount of days.

The way I'm going to approach this is that I'm going to write a `daysInMonth(year,month)`

procedure that returns the correct number of days of the specified month.

```
def daysInMonth(year, month):
return # the number of days of the specified month
```

First things first, I'm going to do some googling to figure out what months have 31 days.

After a quick search, I found that the months with 31 days are months: 01, 03, 05, 07, 08, 10, and 12. With this information we can now add a condition in our code, like this:

```
if month in (1, 3, 5, 7, 8, 10, 12):
return 31
```

Let's repeat the process with the month of February, normally it has 28 days except on leap years where February has 29. For this first stage, we are going to check if the month is the number two and return 28 without accounting for leap years yet.

```
def daysInMonth(year, month):
"""
Function that receives a month and a year
and based on that, it will return the number
of days the specific month has.
WARNING: This code does not account for leap years yet
"""
if month in (1, 3, 5, 7, 8, 10, 12):
return 31
elif month == 2:
return 28
else:
return 30
```

As you can see from the code above, in the case that none of the conditions are true, it will return the normal 30 days.

Let's go back to our `nextDay`

procedure and change it so that it now uses the correct number of days.

Before:

```
if day < 30:
```

After:

```
if day < daysInMonth(year, month):
```

Just as we coded our `daysInMonth`

helper procedure, we should also create a procedure to check if a specified year is a leap year, but before we code, we should understand what a leap year is.

## Leap year

a year, occurring once every four years, that has 366 days including February 29 as an intercalary day. - https://en.wikipedia.org/wiki/Leap_year

If you scroll below the Wikipedia article, you will find an algorithm section that is incredibly useful for programmers.

Here is my implementation of the algorithm:

```
def isLeapYear(year):
"""
Function that receives a year and returns
a boolean, True if the year is a leap year
and False if it's not
"""
if not (year % 4 == 0):
return False
elif not (year % 100 == 0):
return True
elif not (year % 400 == 0):
return False
else:
return True
```

After we are done writing our function it's important to test it to make sure it works, in this case, the code is giving the expected output so now it's time to integrate it in our `daysInMonth`

procedure.

For the moment, the `daysInMonth`

procedure does not account for leap years and it's returning 28 days if the month is February, let's change that with our newly created `isLeapYear`

procedure.

```
def daysInMonth(year, month):
"""
Function that receives a month and a year
and based on that, it will return the number
of days the specific month has, accounting for
leap years.
"""
if month in (1, 3, 5, 7, 8, 10, 12):
return 31
elif month == 2:
if isLeapYear(year):
return 29
else:
return 28
else:
return 30
```

Now, if the month is February, the code will check if the year is a leap year, if that it's true it will return 29 days, and if it's not it will return the normal 28 days. At this point, it looks like we are done, now it's time to test the full code with several examples.

After testing we realized that everything looks to be working except for the case where the dates are the same.
`print(daysBetweenDates(2021, 8, 24, 2021, 8, 24))`

If you run this test case you will see an AssertionError when the output should be 0, to fix this I'm going to simply change the assertion line in the `daysBetweenDates`

procedure.

Before:

```
assert(date1BeforeDate2(year1, month1, day1, year2, month2, day2))
```

After:

```
assert not (date1BeforeDate2(year2, month2, day2, year1, month1, day1))
```

Now everything looks to be finished, it's time to do final testing to be confident our code it's working as expected, and in this case, it looks like we can consider our code done!.

Full python code:

```
def isLeapYear(year):
"""
Function that receives a year and returns
a boolean, True if the year is a leap year
and False if it's not
"""
if not (year % 4 == 0):
return False
elif not (year % 100 == 0):
return True
elif not (year % 400 == 0):
return False
else:
return True
def daysInMonth(year, month):
"""
Function that receives a month and a year
and based on that, it will return the number
of days the specific month has, accounting for
leap years.
"""
if month in (1, 3, 5, 7, 8, 10, 12):
return 31
elif month == 2:
if isLeapYear(year):
return 29
else:
return 28
else:
return 30
def nextDay(year, month, day):
"""
The function receives a year, month, and day and returns
the year, month, and day of the next day.
"""
if day < daysInMonth(year, month):
return year, month, day + 1
elif month < 12:
return year, month + 1, 1
else:
return year + 1, 1, 1
def date1BeforeDate2(year1, month1, day1, year2, month2, day2):
"""
The function receives two dates and returns a boolean, True
if the first date is before the second, and False if it is not.
"""
if year1 < year2:
return True
if month1 < month2:
return True
if day1 < day2:
return True
return False
def daysBetweenDates(year1, month1, day1, year2, month2, day2):
"""
returns the difference between two dates in days accounting for
leap years and no time travel.
"""
assert not (date1BeforeDate2(year2, month2, day2, year1, month1, day1))
days = 0
while date1BeforeDate2(year1, month1, day1, year2, month2, day2):
year1, month1, day1 = nextDay(year1, month1, day1)
days += 1
return days
print(daysBetweenDates(2019, 5, 24, 2020, 5, 24))
```

You've reached the end of this lesson on problem-solving for programmers, remember that this is a skill that takes a lifetime to master so don't feel frustrated if you don't get it right away because this isn't easy, but I hope that some of the guidelines I've shared today can help you with any problem you might encounter in the future.

That's all for today guys, I really hope this was helpful, and let me know in the comments any thoughts, suggestions and I will see you in the next post, stay tuned!.

]]>Hello everyone, today's post is going to be a bit shorter than usual but it's a very important topic non the less, I want to talk about adding Fonts to Figma.

Okay, so, when I was making a cover design in Figma I came up with the really common pain of not finding the right font for my project, normally you can find LOT's of fonts to try your design with, but I wasn't finding the font I wanted, `Sans Pro Display`

, I was really surprised this font didn't come included in the really big Figma library, and for the first time ever I had to add a font to Figma, in the end, it wasn't complicated but it took me way longer than it should have and that's why I wanted to share this with you.

The first step is going to find your wanted font, in my case, I simply googled "Sans Pro Display free Font Download" and easily found it here: https://www.cufonfonts.com/font/sf-pro-display, make sure you are downloading from a secure website.

Download your font and you should get a zip file, extract the elements from the zip file and you should get a folder with OpenType Font files like this:

Open your Figma project and if you select your desired text and go to ==> Design ==> Text and search for your newly downloaded Font you will realize that it cannot be found in the library.

The reason why you can't find the font it's because it's not installed in your system yet. To install the font you can simply double click the OpenType Font file and a screen like the one shown below should appear then you simply click on the install button.

This step is SUPER IMPORTANT and I really hoped someone would have told me to RESTART Figma, I lost a lot of time trying to figure out why the font was still not appearing and the solution was really simple, simply close the app and open again.

After restarting the app you should be able to select any text element, go to ==> Design ==> Text and search for the name of your font, Figma will likely auto-complete the search, and now we are ready to start using our new font.

In today's post, we learned how to add any font to Figma, I want to mention that this method is for the Desktop version only, and I really hope this post helps anyone that was looking to expand their font library, here it's the cover design I was working on:

Comment below any thoughts, suggestions, and i will see you in the next post.

]]>Hello everyone, in today's post I wanted to show you how to use the google cloud hosting service. using that service, we can host static web pages easily, free, and securely.

Firebase hosting will provide you impressive good speed without the need for separate CDN and it's FREE, easy to use, and provides the SSL certificate so that your website will be hosted securely with HTTPS.

- Google account for Firebase setup in console
- Domain: you can purchase a domain from any provider
- Firebase-CLI (installation process showed later in this post)

Open Firebase Console ==> Sign in with your Google account ==> Create project ==> Add title ==> Enable Google analyitics for your country.

See the images below for guidance.

Simply execute the following command:

```
npm install -g firebase-tools
```

or you can use:

```
yarn global add firebase-tools
```

- Create a new folder on your computer
- Open a new CMD(command prompt) and cd into the newly created folder
- Now execute the command:

```
firebase login
```

This will redirect you to the sign-in page in the browser, where you just need to simply sign in.

After successfully given access, you should get the below message on the browser

On your command prompt, simply execute the following command:

```
firebase init
```

After executing this command you should see the following screen:

After pressing enter to the question

are you ready to proceed?

you should select the option:

Hosting: Configure files for Firebase Hosting and (optionally) set up GitHub Action deploys

Use arrows to change selection and select using the `spacebar`

, then press `enter`

.

Once the Hosting option is selected, they'll provide the option to choose an existing project or create a new one. As we have already created a sample static project, you need to just choose the existing option and press `enter`

.

After pressing `enter`

you should see your Firebase project, select it, and press `enter`

.
Then it will ask you for a name for the public directory, you can put the same name as your project, and after pressing `enter`

the following question should appear

If your page is a single page like mine you should answer yes.

After we are done with the configuration we need to run the following command:

```
firebase serve
```

If we followed all of the steps correctly, in the folder created at the beginning we will find another folder and inside an index.html file that will be shown on `localhost:5000`

After watching the screen above without showing problems then we can exit with `Ctrl+C`

We're almost done!, now it's time to delete the index.html file and replace it with our wanted index.html, index.css, images, and icon files.

Back at the CMD terminal we can repeat step 5 and now we should see our website on `localhost:5000`

, it's likely that we see the old file instead of the new one so to fix that make sure to refresh the browser with `Ctrl+Shift+R`

After making sure everything works properly on `localhost:5000`

, we can now exit and proceed with the deploy

```
firebase deploy
```

As we can see from the image above, after the deploy it's complete, we should get a Hosting URL, if we paste that into our web browser we should see our website.

We are Done!

In the likely case that you don't want your website URL to look like this: https://sample-9f6cf.web.app/ and want to use a custom domain instead, we can do just that from the Project dashboard, just follow the images below.

In my case, I already had a domain from https://domains.google/ and after following the three steps from the image above we should get a register type, host, and value (image below).

Now we need to go to Google Domains ==> Select your domain ==>DNS ==> Manage custom records ==> Create new record and enter the values there ==> Save. See the images below for guidance.

Now we should wait some time, normally it's a few minutes, and after that, we are done hosting our website with firebase!.

I really hope this post has taught you how to host your website with Firebase and if you have any suggestions or ideas, let me know in the comments below, see you in the next post!.

]]>`PCA9685`

driver and thought I shared my experience with anyone interested in this topic.
- Raspberry Pi (mine is the 3B+)
- PCA9685 driver
- Servos (the driver supports up to 16)
- 4 female to female jumper cables.
- External 5v power for the driver
- Python3

5v-6v recommended power and you can connect as many servos as you like

Turn on your Raspberry Pi and open a new terminal, then run the following commands:

```
sudo apt-get install git build-essential python-dev
git clone https://github.com/adafruit/Adafruit_Python_PCA9685.git
cd Adafruit_Python_PCA9685
sudo python setup.py install
cd examples
```

Inside the examples folder, you should find the `simplest.py`

example code, to run it use the command

`python3 simplest.py`

However, if we execute this program we get an error

No such file or directory: '/dev/i2c-1'

To fix this we need to open the raspberry pi software configuration

`sudo raspi-config`

Use the down arrow to select interfacing options and press enter, then select P5 I2C and enable the interface ==> ok ==> finish.

Now, if we execute the `simplest.py`

file we shouldn't get any errors and our servo should start moving, Nice!, however the code it's unnecessarily complex for what I need. I would like to call a function that passes the target degree as a parameter and nothing else, this way the code would be easier to read and with fewer bugs, to accomplish this I'm going to use the Adafruit Servo Kit library for python

https://github.com/adafruit/Adafruit_CircuitPython_ServoKit

To use this library, open a new terminal on your Raspberry Pi and execute the following command:
`pip3 install adafruit-circuitpython-servokit`

Here we can see a full example:

```
from time import sleep
from adafruit_servokit import ServoKit
# Set channels to the number of servo channels on your kit.
# 8 for FeatherWing, 16 for Shield/HAT/Bonnet.
kit = ServoKit(channels=8)
kit.servo[0].angle = 175
sleep(1)
kit.servo[0].angle = 45
sleep(1)
```

We can specify the servo in the`kit.servo[0-15].`

I really hope this post helped you out and feel free to share this with someone that might need it, see you next time!.

]]>