# Working With Python List

## 1. LOOPING THROUGH AN ENTIRE LIST

> When you want to apply the same function on every element in a list, you can use Python’s `for` loop

### 1.1. Defining a for Loop

Let’s go through the basic example of how to work with a `for` loop in a list

```python
clients = ['dexter','axe','bobby']
for client in clients:
    print(client)
```

```
dexter
axe
bobby
```

* we store the list inside the variable `clients`
* then we define a `for` loop by telling Python to store *each item* from list of `clients` and store it in the variable `client`
* remember, `for` loop ends with semicolon `:`
* then ask Python to `print` each instance of `client`
* Python repeat this loop until all items are printed Remember that the set of steps is repeated once for each item in the list, no matter how many items are in the list **More Examples of defining a for Loop** Here are few examples of how to initiate a `for` loop.:

```python
for alien in aliens:
for pet in pets:
for item in list_of_items:
```

### 1.2. Doing More Work *Within* a for Loop

You *can do just about anything* with each item in a `for` loop. Let’s send a welcome message to all the `clients`, defined in earlier example

```python
clients = ['dexter','axe','bobby']
for client in clients:
    print(f"{client.title()}, welcome to the club.")
```

```
Dexter, welcome to the club.
Axe, welcome to the club.
Bobby, welcome to the club.
```

Instead of writing just one line, we can print out as many statement for each item in the loop. Every indented line following the `:` is considered *inside the loop*, and each indented line is executed once for each value in the list.

```python
clients = ['dexter','axe','bobby']
for client in clients:
    print(f"{client.title()}, welcome to the club.")
    print(f"We are looking forward to meet you next week, {client.title()}\n")
```

```
Dexter, welcome to the club.
We are looking forward to meet you next week, Dexter

Axe, welcome to the club.
We are looking forward to meet you next week, Axe

Bobby, welcome to the club.
We are looking forward to meet you next week, Bobby
```

### 1.3. Doing Something *After* a for Loop

Let suppose we need to write a general statement after the loop ends. All lines of code after the `for` loop that are not indented are executed once without repetition.

```python
clients = ['dexter','axe','bobby']
for client in clients:
    print(f"{client.title()}, welcome to the club.")
print("\nYour presence is highly appreciated")
```

```
Dexter, welcome to the club.
Axe, welcome to the club.
Bobby, welcome to the club.

Your presence is highly appreciated
```

## 2. COMMON ERRORS IN for LOOP

We need to avoid common indentation errors - that is, coders sometimes

* indent blocks of code that don’t need to be indented, or
* forget to indent blocks that need to be indented. To add an indent, just use the `tab` key on your keyboard

### 2.1. Forgetting to Indent

Always indent the line after the for statement in a loop. If you forget, Python will remind you with `IndentationError`. In most text editors, the indent is inserted automatically after the `for` loop colon `:`

```python
clients = ['dexter','axe','bobby']
for client in clients:
#making error here by not using indent
print(client)
```

```
IndentationError: expected an indented block
```

### 2.2. Forgetting to Indent Additional Lines

Earlier, we printed two statements for each item in the loop. What if we forgot to place indent after the second `print ()` statement

```python
clients = ['dexter','axe','bobby']
for client in clients:
    print(f"{client.title()}, welcome to the club.")
#making error here
print(f"We are looking forward to meet you next week, {client.title()}\n")
```

```
Dexter, welcome to the club.
Axe, welcome to the club.
Bobby, welcome to the club.
We are looking forward to meet you next week, Bobby
```

NOTE: This is a **logical error.** The syntax is valid Python code, but the code does not produce the desired result because a problem occurs in its logic. These kind of errors can be difficult to spot.

### 2.3. Unnecessary Indent

Let suppose we need to print a simple string without a `for` loop but we did indent the `print()` statement

```python
message = "Welcome to Toronto"
#making error here by unnecessary indent
    print(message)
```

```
IndentationError: unexpected indent
```

### 2.4. Indenting Unnecessarily After the Loop

If you accidentally indent code that should run *after* a loop has finished, that code will be repeated once for each item in the list. These errors don’t give traceback, most of the time and hence difficult to spot.

```python
clients = ['dexter','axe','bobby']
for client in clients:
    print(f"{client.title()}, welcome to the club.")
# making error here by unwanted indent
    print("\nYour presence is highly appreciated")
```

```
Dexter, welcome to the club.

Your presence is highly appreciated
Axe, welcome to the club.

Your presence is highly appreciated
Bobby, welcome to the club.

Your presence is highly appreciated
```

### 2.5 Forgetting the Colon

The `for` loop ends with a colon `:` If you accidentally forget the colon , you’ll get a `syntax error` because Python doesn’t know what you’re trying to do. These errors are easier to fix than find.

```python
clients = ['dexter','axe','bobby']
for client in clients
    print(client)
```

```
SyntaxError: invalid syntax
```

## 3. MAKING LISTS OF NUMBERS

### 3.1. Defining the range() Function

Let’s go one step further and use Python’s `range()` function to generate a series of numbers Here is an example of generating and printing integers from 1 to 5.

```python
for value in range(1,6):
    print(value)
```

```
1
2
3
4
5
```

As you can see that in `range(1,n)`, `1` is included and `n` is excluded . Because the loop stops before the second value, `n` the output never contains the end value `n`, which would have been 6 in this case.

### 3.2. Using range() to Make a List of Numbers

We can wrap the `range()` inside the `list()` function to make a list of numbers. An example will make the concept clearer:

```python
numbers = list(range(1,6))
print(numbers)
```

```
[1, 2, 3, 4, 5]
```

#### a. Skip numbers in a range()

We can also use the `range()` function to tell Python to skip numbers in a given range. This is done by mentioning the step size in the `range(start,stop, step-size)` For example, here’s how we would construct a list of even numbers between 1 and 20:

```python
numbers = list(range(2,21,2))
print(numbers)
```

```
[2, 4, 6, 8, 10, 12, 14, 16, 18, 20]
```

#### b. Making list of squares(exponents)

You can create almost any set of numbers you want using the `range()` function. For example, consider how you might make a list of the first 10 square numbers (that is, the square of each integer from 1 through 10): Recall that in Python, two asterisks `**` represent exponents

```python
# defining an emoty list where we will store the numbers
squares = []
print(squares)
# for loop
for value in range(1,11):
    square = value**2
    squares.append(square)
# print the list
print(squares)
```

```
[]
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
```

You can see that the list of square was empty before the `for` loop and then we `append` it with square of each value in the given `range()`

We can achieve the same result with this shortcut approach:

```python
squares = []
for value in range(1,11):
    squares.append(value**2)
print(squares)
```

```
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
```

### 3.3. Simple Statistics

A few Python functions are specific to lists of numbers. For example, you can easily find the minimum, maximum, and sum of a list of numbers:

```python
numbers = list(range(1,11))
print(min(numbers))
print(max(numbers))
print(sum(numbers))
```

```
1
10
55
```

### 3.4 List Comprehensions

The approach described earlier for generating (and printing) the list squares consisted of using four or five lines of code. A **list comprehension** allows you to generate this same list (and print it) in just two line of code.

```python
squares = [value**2 for value in range(1,11)]
print(squares)
```

```
[1, 4, 9, 16, 25, 36, 49, 64, 81, 100]
```

💡NOTE: No colon is used at the end of the `for` statement.

## 4. SLICING

In this section, we will learn how to work with a part of items in a list, which Python calls a `slice`.

### 4.1. Slicing a List

To make a slice, you specify the index position of the first and last elements, Python stops one item before the second index you specify (this logic is similar to `range()` function that we covered earlier) In addition, slicing also support the step size. The general syntax for slicing can be written as: `list-name[start:stop:step-size]` Where the default value for `start` and `step-size` is `0` and `1`

Let suppose, we want to fetch the first two items from the list `cars`

```python
cars = ['honda','byd','tesla','toyota','toyota','bmw']
print(cars[0:2])
```

```
['honda', 'byd']
```

#### a. Omit the first index

If you omit the first index in a slice, Python automatically starts your slice from the beginning of the list, i.e. index position 0

```python
cars = ['honda','byd','tesla','toyota','toyota','bmw']
print(cars[:2])
```

```
['honda', 'byd']
```

#### b. Omit the last index

If you omit the last index in a slice, Python automatically starts your slice from the first index and goes till end of the list:

```python
cars = ['honda','byd','tesla','toyota','toyota','bmw']
print(cars[4:])
```

```
['toyota', 'bmw']
```

#### c. Omitting with negative index

Recall a negative index `-1` to fetch the last item of the list. We can use the same concept in slicing. Let suppose that we want the slice of last two cars, we will use `[-2:]` which tells Python to start from second last item in the list and ends at last item.

```python
cars = ['honda','byd','tesla','toyota','toyota','bmw']
print(cars[-2:])
```

```
['toyota', 'bmw']
```

#### d. Every other item in the list

Let suppose that we want to fetch every other in the list, starting from the first (index=0). In this case, we will provide the `step-size` of two:

```python
cars = ['honda','byd','tesla','toyota','toyota','bmw']
print(cars[::2])
```

```
['honda', 'tesla', 'toyota']
```

### 4.2. Looping Through a Slice

We can use the `for` loop with slicing in following way:

```python
cars = ['honda','byd','tesla','toyota','toyota','bmw']
print("My first two cars were:")
for car in cars[:2]:
    print(car.upper())

My first two cars were:	
HONDA
BYD
```

## 5. COPYING A LIST

Often, you would like to start with an existing list and make an entirely new list based on the first one.

### 5.1. Copying an entire list

To copy a list, you can make a slice that includes the entire original list by omitting the first index and the second index, i.e, `[:]` This will make a copy of the given list which works separately than the original list.

```python
cars = ['honda','byd','tesla','toyota','toyota','bmw']
total_cars = cars[:]
print(f"My cars are:\n{cars}")
print(f"After copying the list:\n{total_cars}") 
```

```
My cars are:
['honda', 'byd', 'tesla', 'toyota', 'toyota', 'bmw']
After copying the list:
['honda', 'byd', 'tesla', 'toyota', 'toyota', 'bmw']
```

### 5.2. Copying a slice of original list

Suppose we want to make a copy of a slice of original list

```python
cars = ['honda','byd','tesla','toyota','toyota','bmw']
tommy_cars = cars[:2]
print(f"My cars are:\n{cars}")
print(f"Tommy has following cars:\n{tommy_cars}")
```

```
My cars are:
['honda', 'byd', 'tesla', 'toyota', 'toyota', 'bmw']
Tommy has following cars:
['honda', 'byd']
```

### 5.3. What doesn’t work

You can’t copy entire or slice of a list using just the equal sign

```python
cars = ['honda','byd','tesla','toyota','toyota','bmw']
total_cars = cars
```

Doing this will assign a new variable `total_cars` to the original variable `cars` instead of making a copy of the list

## 6. TUPLES

Lists work well for storing sets of items that can change throughout the life of a program. However, sometimes you’ll want to create a list of items that cannot change — `tuples` allow you to do just that.

### 6.1 Defining a Tuple

> Python refers to values that cannot change as immutable, and an immutable list is called a tuple. A tuple looks just like a list except we *use parentheses `( )`instead of square brackets*. Once you define a tuple, you can access individual elements by using each item’s index, just as we did for a list.

```python
volume = (50,50,100)
for value in volume:
    print(value)
```

```
50
50
100
```

What will happen if we try to change the value of an item in the tuple? We will get the `TypeError`

```python
volume = (50,50,100)
volume[0] = 100   
```

```
TypeError: 'tuple' object does not support item assignment
```

### 6.2. Writing over a Tuple

Although you can’t modify a tuple, you can assign a *new value to a variable* that holds a tuple.

```python
volume = (50,50,100)
print("Original dimensions:")
for value in volume:
    print(value)

volume = (100,100,200)
print("\nModified dimensions:")
for value in volume:
    print(value)
```

```
Original dimensions:
50
50
100

Modified dimensions:
100
100
200
```
