Skip to content

Latest commit

 

History

History
1146 lines (784 loc) · 30.5 KB

06.Lists.md

File metadata and controls

1146 lines (784 loc) · 30.5 KB

Lesson 6: Data Structures p.1 (list)

"Lists are the backbone of data organization."

Content

  1. Mutable and Immutable data types
  2. Introduction to list
  3. Functions (len(), sum(), min(), max(), sorted())
  4. Methods of Lists
  5. General methods of Iterations
  6. Copying lists
  7. join() and split()
  8. Quiz
  9. Homework

1 Mutable and Immutable data types

Mutable data types - are those that can be changed after they are created.

This means you can change, add, or remove elements from these data types without creating a new object.

In programming, understanding the difference between mutable and immutable data types is extremely crucial because it affects how you work with data and how your program behaves.

This lesson we will be focusing on the list. But during next lessons we will get acquainted with tuple, set and dict data structures. So it will be the best to know which of them are mutable/immutable now.

1.1 Mutable

Data Structure Description Example Operations
List Ordered and changeable collection of items append(), remove(), pop()
Dictionary Unordered collection of key-value pairs update(), clear(), pop()
Set Unordered collection of unique items add(), discard(), pop()

1.2 Immutable

Immutable data types cannot be changed after their creation. Any "modification" creates a new object instead of changing the old one.

Data Type Description Example Operations
Tuple Ordered and unchangeable collection Accessing items, index(), count()
String Sequence of characters Accessing characters, concatenation creates new string
Integer Whole number without decimal Arithmetic operations create new integers
Float Number with decimal point Arithmetic operations create new floats
Boolean Represents True or False Used in logical operations

We have already seen the error trying to modify strings in the Lesson #3. Instead of this we created a new variable to store a new object.

Example

greeting = "Hello, world!"
print(f"Original string: {greeting}")

# Attempting to change a character
greeting[0] = 'J'       # This line will actually raise an error: TypeError

# Concatenation creates a new string and
greeting = 'J' + greeting[1:]
print(f"Modified string: {greeting}")

Output

Original string: Hello, world!
Traceback (most recent call last):
  File "<string>", line 5, in <module>
TypeError: 'str' object does not support item assignment

But how to verify if we created a new object? This can be done using id() function.

1.3 id()

The id() function in Python returns a unique identifier for an object, which corresponds to its location in memory. Actually, almost any operation that seems to modify the object actually creates a new object, if you work with immutable objest.

If the id() is different, it confirms that a new object was created. This can be helpful while debugging the project.

Example

# Immutable data type
greeting = "Hello, world!"
original_id = id(greeting)

greeting = 'J' + greeting[1:]
new_id = id(greeting)

# Comparing identifiers
print(f"Original ID: {original_id}")
print(f"Modified ID: {new_id}")

Output

Original ID: 140353776296400
Modified ID: 140353776295680

2. Introduction to list

In Python a list - is a mutable, ordered sequence of items.

It is one of the most versatile data types and can contain items of different data types, though typically, lists contain items of the same type.

2.1 Syntax

In order to create an empty list we can use the following syntax:

Example

list_1 = []
list_2 = list()

print(list_1, list_2)
print(type(list_1), type(list_2))

Output

[] []
<class 'list'> <class 'list'>

2.2 Data Type Conversion

Most iterable types in Python can be converted to a list using the list constructor.

Simply speaking, iterable types - objects that can give you elements one by one, which allows you to iterate through them using loops or advanced techniques such as iterators/generators.

Example

string_to_list = list('hello')
print(string_to_list)

Output

['h', 'e', 'l', 'l', 'o']

2.3 Indexing and slicing

Indexing and slicing work the same way for lists as they do with strings.

Indexing -> access individual items.

slicing -> access a range of items.

Example

my_list = [10, 20, 30, 40, 50]

# Indexing
print(my_list[1])  # 20

# Slicing
print(my_list[1:4])  # [20, 30, 40]

2.4 Concatenation

Lists can be concatenated using the + operator, which combines them into a new list.

Example

list_one = [1, 2, 3]
list_two = [4, 5, 6]
list_three = [7, 8, 9]

# Concat
new_list = list_one + list_two + list_three
print(new_list)  

Output

[1, 2, 3, 4, 5, 6, 7, 8, 9]

2.5 Multiplication

In Python, lists can be multiplied by an integer, which repeats the list's elements.

Example

# Example 1
numbers = [1, 2, 3]
new_numbers = numbers * 3 
print(new_numbers)

# Example 2 
names = ["Sasha", "Yehor"]
triple_names = names * 3
print(triple_names)

Output

[1, 2, 3, 1, 2, 3, 1, 2, 3]
['Sasha', 'Yehor', 'Sasha', 'Yehor', 'Sasha', 'Yehor']

In some cases this can be very useful and it's very cool that Python provides such functionality.

2.6 Operators += / *= and unpacking

The += and *= operators can modify lists in place. These operators perform the same operations as list + list and list * int. respectively.

Example

#  +=
nums = [1, 2, 3]
nums += [4, 5]  # Same as ``nums = nums + [4,5]``
print(nums)

# *= 
nums = [1, 2, 3]
nums *= 2  # Same as ``nums = nums * 2``
print(nums)

Output

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

2.7 Unpacking

Unpacking is the way of extracting values from iterable objects.

There are several types of unpacking presented in Python.

2.7.1 Basic Unpacking

The number of variables on the left side of the assignment operator should match the number of elements in the iterable.

Example

my_list = [1, 2, 3]
a, b, c = my_list  # Unpacking list into three variables
print(a)  # 1
print(b)  # 2
print(c)  # 3

2.7.2 Extended Unpacking

Using a star * expression, a.k.a (asterix), allows you to assign a portion of an iterable to a variable and the rest to another variable.

Output

my_list = [1, 2, 3, 4, 5]
first, *middle, last = my_list
print(first)   # 1
print(middle)  # [2, 3, 4]
print(last)    # 5

2.7.3 Swapping Variables

This lifehack can be used to swap the values of two variables efficiently without needing a temporary variable.

Example

x, y = 10, 20
x, y = y, x  # Swapping values
print(x)  # 20
print(y)  # 10

2.7.4 Unpacking with for

As it was mentioned before, list can consist of different types of elements and it being a list of lists. That's where nested loops come in handy. Alternatively, you can use that to upack variables with the same format.

Example

nested_lists = [[1, 'apple'], [2, 'banana'], [3, 'cherry']]
for number, fruit in nested_lists:
    print(f"Number: {number}, Fruit: {fruit}")

Output

Number: 1, Fruit: apple
Number: 2, Fruit: banana
Number: 3, Fruit: cherry

2.8 Operator in

The in operator in Python is used to check if a value exists within all iterable objects.

Example

my_list = [1, 2, 3, 4, 5, 'banana']
print(3 in my_list)  # True
print(6 in my_list)  # False
print("banana" in my_list) # True
print("a" in my_list[-1]) # True

3. Functions (len(), sum(), min(), max(), sorted())

Python provides several built-in functions that are readily available for common list operations. These functions can quickly perform actions on list items without the need for loops.

They work with all iterables beause they use polymorphism concept which we will explore in OOP (Object Oriented Programming).

3.1 len()

The len() function returns the number of items in a list (the length of the list).

Example

numbers = [1, 2, 3, 4, 5]
print(len(numbers)) 

matrix = [[1,2,3], [4,5,6]]     # we have 2 lists in the list, heh
print(len(matrix))

Output

5
2

3.2 sum()

The sum() function calculates the total sum of all numerical items in a list.

Example

numbers = [1, 2, 3, 4, 5]
print(sum(numbers))

# This won't work, as we need only ``numberical data types``
imposter_numbers = ["STRING???", 15]
print(sum(imposter_numbers))

Output

15
Traceback (most recent call last):
  File "<string>", line 5, in <module>
TypeError: unsupported operand type(s) for +: 'int' and 'str'

3.3 min()

The min() function returns the smallest item in a list.

Example

numbers = [5, 1, 8, 3, 2]
print(min(numbers))

# This won't work, as well
imposter_numbers = [[1, 2, 3], 15]
print(min(imposter_numbers))

Output

1
Traceback (most recent call last):
  File "<string>", line 2, in <module>
TypeError: '<' not supported between instances of 'list' and 'int'

3.4 max()

The max() function returns the biggest item in a list.

Example

numbers = [5, 1, 8, 3, 2]
print(max(numbers))  # Output: 8

# Nope, won't work, don't even try to do this!
imposter_numbers = ['s', 15]
print(min(imposter_numbers))

Output

8
Traceback (most recent call last):
  File "<string>", line 2, in <module>
TypeError: '<' not supported between instances of 'list' and 'int'

4. Methods of Lists

Suppose we have the following list:

x = [1, 2, 3]

4.1 Adding Elements

These methods let you add elements to the list, either at the end, a specific position, or extending the list by appending all elements from another iterable (str, list, tuple, dict, etc..) / numbers.

Method Description Example Output
append(x) Adds an item to the end of the list. x.append(4) [1, 2, 3, 4]
extend(iterable) Extends the list by appending elements from the iterable. x.extend([5, 6]) [1, 2, 3, 4, 5, 6]
insert(i, x) Inserts an item at a given position. x.insert(2, 'a') [1, 2, 'a', 3, 4]

4.2 Removing Elements

Method Description Example Output
remove(x) Removes the first item from the list whose value is x. x.remove('a') [1, 2, 3, 4]
pop([i]) Removes the item at the given position in the list, and returns it. x.pop(1) [1, 3, 4]
clear() Removes all items from the list. x.clear() []

4.3 Utility Methods

Method Description Example Output
index(x) Returns the index of the first item whose value is x. x.index(3) 2
count(x) Returns the number of times x appears in the list. x.count(1) 1
sort() Sorts the items of the list in place. x.sort() [1, 2, 3, 4]
reverse() Reverses the elements of the list in place. x.reverse() [4, 3, 2, 1]
copy() Returns a shallow copy of the list. new_x = x.copy() new_x is [1, 2, 3, 4]

Don't hesitate to use dir(), and help() functions or refer to the official Python documentation working with list.

5. General methods of Iterations

Iterating through lists is a fundamental aspect of working with data in Python, as it allows you to access and manipulate each item in a list.

As with strings, Python provides several ways to iterate over lists.

5.1 Using for in Loop

The for in loop is the most common way to iterate through each item in a list directly.

Example

fruits = ['apple', 'banana', 'cherry']
for fruit in fruits:
    print(fruit)

Output

apple
banana
cherry

Example

cities = ['London', 'Kyiv', 'Washington']
for city in cities:
    print(city)

Output

London
Kyiv
Washington

Don't forget to call variables which make sense for data scructures like in the examples above.

5.2 Using for index in range(len(list))

Sometimes, you need the index of the items while iterating through a list. In this case, you can use range() along with len() to iterate through the list indexes.

Example

fruits = ['apple', 'banana', 'cherry']
for index in range(len(fruits)):
    print(f"Index: {index}, Fruit: {fruits[index]}")

Output

Index: 0, Fruit: apple
Index: 1, Fruit: banana
Index: 2, Fruit: cherry

This method is useful when you need to modify the list items since you can use the index to set new values.

6. Copying lists

Ccpying lists can be tricky, especially for beginners, because of the way Python handles object references.

There are two primary ways how we can copy list objects:

6.1 Shallow Copying

Shallow copying creates a new list object, but it doesn't create new objects for the elements inside the list. It only copies references to those elements.

Be carefull using this copying.

Example

original_list = [1, 2, 3]
copied_list = original_list.copy()

print("Original List:", id(original_list))
print("Copied List:", id(copied_list))

In this case, original_list and copied_list have different ids, meaning they are different objects.

Output

Original List: 139827368587200
Copied List: 139827364976256

However, the elements inside them are still referencing the same objects.

Example

print(id(original_list[1]))
print(id(copied_list[1]))

Output

139827364976123
139827364976123

6.2 Assigning a reference

Assigning a reference to a list in Python is an operation that can lead to unintended consequences. It's often confused with creating a copy of the list, but in reality, it's quite different.

Example

What Happens When You Assign a Reference?

original_list = [1, 2, 3]
reference_list = original_list    # assign a list to one and another variable

# Output the ids
print("Original_list_id: ", id(original_list))
print("Reference_list_id: ", id(reference_list))

# Made a change to both ``original_list`` and ``reference_list`` 
original_list += [4]
reference_list += [5] 

# Output lists
print("Original List:", original_list)
print("Reference List:", reference_list)

Any changes made to original_list will also appear in reference_list, and vice versa. This is because they are two names for the same object.

If your intention was to work with two separate lists, this behavior could lead to bugs. Changes you thought were made to only one list can unexpectedly affect the other.

Output

Original_list_id:  140583473131392
Reference_list_id:  140583473131392

Original List: [1, 2, 3, 4, 5]
Reference List: [1, 2, 3, 4, 5]

6.3 Deep Copying Lists

Imagine, you have lists containing other mutable objects (e.g list of lists), it's crucial to understand the concept of deep copying, when working with more complex data structures.

A deep copy creates a new list with entirely new objects, even for nested mutable objects.

How it works?

  1. Creating a New List: A completely new list object is created.
  2. Copying Nested Objects: Even the nested objects within the list are copied and recreated in the new list.
  3. Independent Objects: Changes made to the deep-copied list (or its nested objects) do not affect the original list, and vice versa.

Example

"""Python provides a module named `copy`, which has a method `deepcopy()` for this purpose."""

import copy

# Original list with nested mutable objects
original_list = [[1, 2, 3], [4, 5, 6]]

# Creating a deep copy
deep_copied_list = copy.deepcopy(original_list)

# Modifying the deep copied list
deep_copied_list[0][1] = 'Changed'

print("Original List:", original_list)
print("Deep Copied List:", deep_copied_list)

Output

Original List: [[1, 2, 3], [4, 5, 6]]
Deep Copied List: [[1, 'Changed', 3], [4, 5, 6]]

Deep copying is more memory-intensive and can be slower for large lists. So use it wisely. For example, when your list contains nested mutable objects, and you need complete independence between the original and copied data.

7. List comprehantions

List comprehensions provide a more readable and concise way to create lists by transforming each element of an iterable.

They can be used in place of for loops for simplicity and efficiency, particularly when you're applying a single, straightforward transformation or condition to each element. Btw, they work faster than default loops.

7.1 Syntax

# Default option 
[expression for item in iterable]

# If clause
[expression for item in iterable if condition]

# If-else clause
[if expression else expression for item in itreable]

Basically it is the same as for loops and each list comprehension can be rewriten into the for loop.

Example

# List comprehension
squares = [i ** 2 for i in range(5)]

# For loop equivalant
squares = []
for i in range(5):
    squares.append(i ** 2)

Output

[0, 1, 4, 9, 16]

Example

# List comprehension
even_squares = [i ** 2 for i in range(10) if i % 2 == 0]


# For loop
even_squares = []
for i in range(10):
    if i % 2 == 0:
        even_squares.append(i ** 2)

Output

[0, 4, 16, 36, 64]

Example

# List comprehension
even_or_odd = ["even" if i % 2 == 0 else "odd" for i in range(5)]

# For loop
even_or_odd = []
for i in range(5):
    if i % 2 == 0:
        even_or_odd.append("even")
    else:
        even_or_odd.append("odd")

Output

["even", "odd", "even", "odd", "even"]

But there is some known rules between programers where we should or shouldn't use them.

In some cases for simple operation it is a great tool which working much faster in terms of efficiency, but in other cases it can lead to unnecessary complexion which will result in bad maintainability and readability of the code.

Please, be careful using list comprehesnsions.

7.2 For-Loop vs List Comprehension

Loops List Comprehension
Advantages - More flexible
- Easier to read with complex logic
- Allows complex operations
- More concise and readable for simple cases
- Can be written as a single line
- Often faster for creating a list
Disadvantages - Can be verbose for simple operations - Can be less readable with complex expressions
- Not suitable for multi-step operations
When to Use - When you have complex logic
- When the loop involves nested loops, conditions, or other complex operations
- When you're applying a single operation to all items
- When you need a quick and readable one-liner to create/update a list

Note: Always prioritize readability and maintainability of your code when choosing between a for-loop and a list comprehension.

8. join() and split()

In Python, join() and split() are two powerful methods that bridge the gap between strings and lists. They are widely used in data manipulation, especially in tasks involving text processing and data formatting.

8.1 join()

The join() method in Python is a string method that takes all items in an iterable and joins them into one string.

words = ['Python', 'is', 'extremly', 'powerful', 'and', 'complicated' 'language']
s = ' '.join(words)
print(s)

Output

Python is extremly powerful and complicated language
# I'm kidding Assembler is harder :)

A string can be specified as the separator, by default it is a whitespace as you have seen from the example above.

Example

separator = ', '
my_list = ['apple', 'banana', 'cherry']
joined_string = separator.join(my_list)
print(joined_string)

Output

apple, banana, cherry

8.2 split()

The split() method splits a string into a list where each word is a list item.

The splitting is done at the specified separator. If no separator is defined, whitespace is used by default.

Example

text = 'apple, banana, cherry'
split_list = text.split(', ')
print(split_list)

Output

['apple', 'banana', 'cherry']

Example

text = 'one two three four'
split_text = text.split(' ', 2) # You can the max number of splits as well.
print(split_list)

Output

['one', 'two', 'three four']

These methods are especially powerful when used together, allowing for back-and-forth transformations between strings and lists.

Sure, here's the corrected and reformatted list for your quiz:

9. Quiz

Question 1:

What will be the output of the following code?

my_list = [10, 20, 30, 40, 50]
print(my_list[-2])

Question 2:

What will be the output of the following code snippet?

my_list = [1, 2, 3, 4, 5]
my_list.append(6)
print(my_list)

Question 3:

Which of the following is a correct way to create a deep copy of a list?

original_list = [[1, 2], [3, 4]]

A)

new_list = original_list  

B)

new_list = original_list.copy()  

C)

new_list = list(original_list)  

D)

import copy
new_list = copy.deepcopy(original_list)

Question 4:

What does the extend() method do to a list?

A) Extends the list by adding a single element at the end.
B) Increases the size of the list without adding elements.
C) Adds multiple elements at the specified index of the list.
D) Appends all elements of an iterable to the end of the list.


Question 5:

What is the output of the following list comprehension?

numbers = [x for x in range(10) if x % 2 == 1]

A) [0, 2, 4, 6, 8]
B) [1, 3, 5, 7, 9]
C) [2, 4, 6, 8, 10]
D) [1, 2, 3, 4, 5]


Question 6:

What is the result of the following operation?

my_list = [1, 2, 3]
my_list.insert(1, 'a')
print(my_list)

A) [1, 'a', 2, 3]
B) ['a', 1, 2, 3]
C) [1, 2, 'a', 3]
D) An error occurs


Question 7:

What is the purpose of the reverse() method in a list?

A) To sort the list in ascending order.
B) To randomly shuffle the elements of the list.
C) To reverse the order of the elements in the list.
D) To remove the last element of the list.


Question 8:

Which of the following is a valid example of slicing a list?

A) my_list[1:]
B) my_list[:1]
C) my_list[1:3]
D) All of the above


Question 9:

Which of the following statements about list comprehensions is NOT true?

A) They are more memory-efficient than for-loops.
B) They can include conditional logic.
C) They can make code less readable if overused.
D) They cannot be used to create lists from other iterables.


Question 10:

How does pop() differ from remove() when used on a list?

A) pop() deletes an item by value, whereas remove() deletes by index.
B) pop() returns the removed item, whereas remove() does not.
C) remove() can delete multiple items, whereas pop() can only delete one.
D) There is no difference in their functionality.

10. Homework

Task 1: List Manipulator

Objective: Create a program that allows the user to manipulate a list in various ways. The user can choose to add, remove, or modify items in the list.

# Example of how the program should work

# Menu:
# 1. Add an item
# 2. Remove an item
# 3. Modify an item

# User chooses option 1:
Enter the item to add: Apple
Output: List: ['Apple']

# User chooses option 2:
Enter the item to remove: Apple
Output: List: []

# User chooses option 3:
Enter the item to modify: Apple
Enter the new item: Banana
Output: List: ['Banana']

Task 2: Slicer and Dicer

Objective: Develop a program that allows users to input a list and then perform various slicing operations based on the user input.

Input:
Enter a list of numbers (separated by space): 10 20 30 40 50
Enter start index for slicing: 1
Enter stop index for slicing: 4

Output:
The sliced list is: [20, 30, 40]

Task 3: Shopping List Organizer

Objective: Develop a program that helps users organize their shopping list by adding, viewing, removing and sort items alphabetically.

Additionaly, we would want to check if we add strings to the list. You can use isinstance() function documented here.

# Example of how the program should work

# Menu:
# 1. Add item
# 2. View list
# 3. Remove item
# 4. Exit

# User Interaction Example:
Choose an action (add/view/remove/exit): add
Enter an item to add: Milk
Item added. Your current list is: ['Milk']

Choose an action (add/view/remove/exit): add
Enter an item to add: Eggs
Item added. Your current list is: ['Eggs', 'Milk']

Choose an action (add/view/remove/exit): add
Enter an item to add: 25
Sorry, it is not a string

Choose an action (add/view/remove/exit): view
Shopping List: ['Eggs', 'Milk']

Choose an action (add/view/remove/exit): remove
Enter an item to remove: Milk
Item removed. Your current list is: ['Apples']

Choose an action (add/view/remove/exit): exit
Exiting the program.

# Don't forget to check if item exists in the list using operator ``in``

Task 4: Average of Evens

Objective: Generate a list of even elements using range from the input and display the average of them.

# Write a list comprehension here:

Task 5: List of numbers operations

Objective: Enhance the provided code to perform the following actions on a given list:

  • Display the length of the list.
  • Print the sum, max, min and the average.
  • Print the last element of the list.
  • Output the list in reverse order (remember to use slicing).
  • Print "YES" if the list contains the numbers 4 and 9, and "NO" otherwise.
  • Show the list with the first and last elements removed (create a new list object instead).
  • Print the third element from the end of the list.
  • If the second element is > 10, replace it with 10.
# Sample List
numbers = [3, 12, 5, 8, 4, 9, 15, 22]

# Your code here

Task 6: ASCII alphabet

Objective: Write a program which creates alphabet and stores it into the list.

# Output
['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z']

Task 7: Mini Database

Objective: Develop a program that stores detailed information about a person using nested lists.

Input:
Enter your name: Alice
Enter your age: 30
Enter your hobbies (separated by commas): reading, gardening, gaming
Enter your favorite foods (separated by commas): pizza, salad, ice cream

Output:
Personal Information: 
Name: Alice
Age: 30
Hobbies: ['reading', 'gardening', 'gaming']
Favorite Foods: ['pizza', 'salad', 'ice cream']

# Later on, the program can provide options to view, update and remove these details.

Task 8: Validating IP Addresses

Objective: Write a program that validates if the entered string is a correct IP address.

Note: An IP address is considered valid if it consists of four non-negative integers separated by dots, with each integer ranging from 0 to 254 inclusive.

## Example
Input: 192.168.1.1
Output: Correct

Input: 256.300.2.10
Output: Incorrect

Task 9: Email Extractor

Objective: Create a program to extract email addresses from a block of text. The program should identify strings that resemble email addresses and separate them using semicolons.

# Input:
Enter the text: Please contact us at [email protected] or [email protected] for assistance.

# Output:
Extracted Emails: [email protected]; [email protected]

Don't forget that while designing your program, remember to create an interactive and user-friendly interface!