Skip to content

Instantly share code, notes, and snippets.

@cybersiddhu
Created September 2, 2024 21:46
Show Gist options
  • Save cybersiddhu/8ca8e7196c9149f1e17548d23fb32c5d to your computer and use it in GitHub Desktop.
Save cybersiddhu/8ca8e7196c9149f1e17548d23fb32c5d to your computer and use it in GitHub Desktop.
python list for freshman

Module 1: Introduction to Lists

Lesson 1: What are Lists?

  1. Definition of lists in Python:

    • A list is an ordered collection of items in Python.
    • Lists are mutable, meaning they can be changed after creation.
    • Lists can contain elements of different data types.
  2. How lists are used in programming:

    • To store multiple related items in a single variable.
    • To maintain sequences of data.
    • To easily iterate over a collection of items.
  3. Examples of lists in everyday life:

    • A shopping list
    • A playlist of songs
    • A list of tasks to complete

Let's look at an example that demonstrates these concepts:

Example 1:

# Creating lists
fruits = ["apple", "banana", "cherry"]
numbers = [1, 2, 3, 4, 5]
mixed_list = [1, "hello", 3.14, True]

print("List of fruits:", fruits)
print("List of numbers:", numbers)
print("Mixed list:", mixed_list)

Explanation:

  • We create three different lists: fruits, numbers, and mixed_list.
  • The fruits list contains strings (text) representing different fruits.
  • The numbers list contains integers (whole numbers).
  • The mixed_list demonstrates that a list can contain different data types: an integer, a string, a float (decimal number), and a boolean (True/False value).
  • When we print these lists, Python displays them enclosed in square brackets, with elements separated by commas.

Lesson 2: Creating and Accessing Lists

  1. Creating lists with square brackets:

    • Lists are created by enclosing elements in square brackets [].
    • Elements are separated by commas.
  2. Accessing list elements using indexing:

    • Each element in a list has a position, called an index.
    • Indexing starts at 0 for the first element.
    • Use square brackets with the index number to access elements.
  3. Negative indexing in lists:

    • Negative indices count from the end of the list.
    • The last element has an index of -1, the second-to-last -2, and so on.

Let's explore these concepts with an example:

Example 2:

# Accessing list elements
colors = ["red", "green", "blue", "yellow"]
print("The first color is:", colors[0])    # Output: red
print("The third color is:", colors[2])    # Output: blue
print("The last color is:", colors[-1])    # Output: yellow
print("The second-to-last color is:", colors[-2])   # Output: blue

# Let's break this down step-by-step:
print("\nStep-by-step explanation:")
print("1. Our list 'colors' contains:", colors)
print("2. Index 0 (first element):", colors[0])
print("3. Index 2 (third element):", colors[2])
print("4. Index -1 (last element):", colors[-1])
print("5. Index -2 (second-to-last element):", colors[-2])

Explanation:

  • We create a list called colors with four elements.
  • colors[0] accesses the first element ("red") because indexing starts at 0.
  • colors[2] accesses the third element ("blue").
  • colors[-1] accesses the last element ("yellow") using negative indexing.
  • colors[-2] accesses the second-to-last element ("blue").

This step-by-step breakdown helps visualize how indexing works in lists, both with positive and negative indices.

Module 2: List Operations

Lesson 1: Modifying Lists

  1. Changing list elements:

    • Lists are mutable, so we can change their elements after creation.
    • Use indexing to access and modify specific elements.
  2. Adding elements to a list:

    • append() method: Adds an element to the end of the list.
    • insert() method: Adds an element at a specific position in the list.
  3. Removing elements from a list:

    • remove() method: Removes the first occurrence of a specific value.
    • pop() method: Removes and returns an element at a specific index (or the last element if no index is specified).

Let's examine these operations in detail:

Example 3:

# Modifying lists
numbers = [1, 2, 3, 4, 5]
print("Original list:", numbers)

# Changing an element
numbers[2] = 10
print("After changing the third element:", numbers)

# Adding elements
numbers.append(6)
print("After appending 6:", numbers)

numbers.insert(1, 15)
print("After inserting 15 at index 1:", numbers)

# Removing elements
numbers.remove(10)
print("After removing 10:", numbers)

popped = numbers.pop()
print("Popped element:", popped)
print("List after popping:", numbers)

# Let's break this down step-by-step:
print("\nStep-by-step explanation:")
print("1. We start with the list:", [1, 2, 3, 4, 5])
print("2. We change the element at index 2 (third element) to 10")
print("3. We append 6 to the end of the list")
print("4. We insert 15 at index 1 (second position)")
print("5. We remove the first occurrence of 10")
print("6. We pop (remove and return) the last element")
print("7. Our final list is:", numbers)

Explanation:

  • We start with a list of numbers and modify it in various ways.
  • Changing an element: We use numbers[2] = 10 to change the third element to 10.
  • Adding elements:
    • append(6) adds 6 to the end of the list.
    • insert(1, 15) adds 15 at index 1 (second position).
  • Removing elements:
    • remove(10) removes the first occurrence of 10 from the list.
    • pop() removes and returns the last element of the list.

This example demonstrates how flexible and mutable lists are in Python.

Lesson 2: List Concatenation and Repetition

  1. Concatenating lists using the '+' operator:

    • The '+' operator combines two or more lists into a new list.
  2. Repeating lists using the '*' operator:

    • The '*' operator repeats a list a specified number of times.

Let's explore these operations:

Example 4:

# List concatenation and repetition
list1 = [1, 2, 3]
list2 = [4, 5, 6]

# Concatenation
combined = list1 + list2
print("Concatenated list:", combined)

# Repetition
repeated = list1 * 3
print("Repeated list:", repeated)

# Step-by-step explanation
print("\nStep-by-step explanation:")
print("1. We have two lists:")
print("   list1 =", list1)
print("   list2 =", list2)
print("2. Concatenation (list1 + list2) combines the lists:")
print("   Result:", combined)
print("3. Repetition (list1 * 3) repeats list1 three times:")
print("   Result:", repeated)

Explanation:

  • Concatenation: list1 + list2 creates a new list containing all elements from list1 followed by all elements from list2.
  • Repetition: list1 * 3 creates a new list that repeats the elements of list1 three times.
  • These operations do not modify the original lists; they create new lists.

Module 3: List Slicing and Methods

Lesson 1: List Slicing

  1. Basic slicing syntax: [start:end]

    • Creates a new list containing elements from index 'start' up to, but not including, index 'end'.
    • If 'start' is omitted, slicing begins from the start of the list.
    • If 'end' is omitted, slicing continues to the end of the list.
  2. Extended slicing syntax: [start:end:step]

    • 'step' specifies the increment between each item in the slice.
    • A negative step reverses the slicing direction.
  3. Omitting slice parameters

    • [:] creates a copy of the entire list.
    • [::] is the same as [:], creating a copy of the entire list.

Let's examine these concepts in detail:

Example 5:

# List slicing
numbers = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
print("Original list:", numbers)

# Basic slicing
print("Elements from index 2 to 5:", numbers[2:6])
print("Elements from start to index 3:", numbers[:4])
print("Elements from index 6 to end:", numbers[6:])

# Extended slicing
print("Every second element from index 1 to 7:", numbers[1:8:2])
print("List in reverse order:", numbers[::-1])

# Step-by-step explanation
print("\nStep-by-step explanation:")
print("1. Our original list:", numbers)
print("2. numbers[2:6] - Start at index 2, end before index 6:")
print("   Result:", numbers[2:6])
print("3. numbers[:4] - Start at beginning, end before index 4:")
print("   Result:", numbers[:4])
print("4. numbers[6:] - Start at index 6, go to the end:")
print("   Result:", numbers[6:])
print("5. numbers[1:8:2] - Start at index 1, end before 8, step by 2:")
print("   Result:", numbers[1:8:2])
print("6. numbers[::-1] - Reverse the entire list:")
print("   Result:", numbers[::-1])

Explanation:

  • numbers[2:6] returns a new list with elements from index 2 to 5 (6 is not included).
  • numbers[:4] returns elements from the start of the list up to, but not including, index 4.
  • numbers[6:] returns elements from index 6 to the end of the list.
  • numbers[1:8:2] returns every second element starting from index 1 up to index 7.
  • numbers[::-1] reverses the entire list by using a step of -1.

This detailed breakdown helps visualize how slicing works with different parameters and how it can be used to extract or manipulate portions of a list.

Lesson 2: Common List Methods

  1. len(): Finding the length of a list

    • Returns the number of elements in the list.
  2. count(): Counting occurrences of an element

    • Returns the number of times a specified element appears in the list.
  3. index(): Finding the index of an element

    • Returns the index of the first occurrence of a specified element.
  4. sort() and reverse(): Sorting and reversing lists

    • sort(): Arranges the list elements in ascending order.
    • reverse(): Reverses the order of elements in the list.

Let's explore these methods in detail:

Example 6:

# Common list methods
fruits = ["apple", "banana", "cherry", "date", "apple"]
print("Original fruit list:", fruits)

# Length of the list
print("Number of fruits:", len(fruits))

# Counting occurrences
print("Number of 'apple's:", fruits.count("apple"))

# Finding index
print("Index of 'cherry':", fruits.index("cherry"))

# Sorting and reversing
numbers = [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5]
print("\nOriginal number list:", numbers)

numbers.sort()
print("Sorted numbers:", numbers)

numbers.reverse()
print("Reversed numbers:", numbers)

# Step-by-step explanation
print("\nStep-by-step explanation:")
print("1. Our fruit list:", fruits)
print("2. len(fruits) counts the number of elements:")
print("   Result:", len(fruits))
print("3. fruits.count('apple') counts occurrences of 'apple':")
print("   Result:", fruits.count("apple"))
print("4. fruits.index('cherry') finds the first index of 'cherry':")
print("   Result:", fruits.index("cherry"))
print("5. For the number list:", [3, 1, 4, 1, 5, 9, 2, 6, 5, 3, 5])
print("6. numbers.sort() arranges elements in ascending order")
print("7. numbers.reverse() reverses the order of elements")

Explanation:

  • len(fruits) returns 5, as there are 5 elements in the list.
  • fruits.count("apple") returns 2, as "apple" appears twice in the list.
  • fruits.index("cherry") returns 2, as "cherry" is at index 2 (the third position).
  • numbers.sort() arranges the numbers in ascending order, modifying the original list.
  • numbers.reverse() reverses the order of elements in the list, again modifying the original list.

These methods are commonly used for getting information about lists and manipulating their contents. Understanding them is crucial for effective list management in Python.

Module 4: Advanced List Operations

Lesson 1: List Comprehensions

  1. Basic syntax of list comprehensions:

    • A concise way to create lists based on existing lists or other iterable objects.
    • Syntax: [expression for item in iterable]
  2. Conditional list comprehensions:

    • Include an if statement to filter elements.
    • Syntax: [expression for item in iterable if condition]
  3. Nested list comprehensions:

    • Create multi-dimensional lists or perform complex operations.
    • Involve multiple for loops within a single comprehension.

Let's examine these concepts in detail:

Example 7:

# List comprehensions

# Basic list comprehension
squares = [x**2 for x in range(10)]
print("Squares of numbers 0 to 9:", squares)

# Conditional list comprehension
even_squares = [x**2 for x in range(10) if x % 2 == 0]
print("Squares of even numbers 0 to 9:", even_squares)

# Nested list comprehension
matrix = [[i*j for j in range(3)] for i in range(3)]
print("3x3 multiplication table:")
for row in matrix:
    print(row)

# Step-by-step explanation
print("\nStep-by-step explanation:")
print("1. Basic list comprehension:")
print("   [x**2 for x in range(10)]")
print("   - This creates a list of squares for numbers 0 to 9")
print("   - It's equivalent to:")
print("     squares = []")
print("     for x in range(10):")
print("         squares.append(x**2)")

print("\n2. Conditional list comprehension:")
print("   [x**2 for x in range(10) if x % 2 == 0]")
print("   - This creates a list of squares for even numbers 0 to 9")
print("   - It's equivalent to:")
print("     even_squares = []")
print("     for x in range(10):")
print("         if x % 2 == 0:")
print("             even_squares.append(x**2)")

print("\n3. Nested list comprehension:")
print("   [[i*j for j in range(3)] for i in range(3)]")
print("   - This creates a 3x3 matrix of multiplication results")
print("   - It's equivalent to:")
print("     matrix = []")
print("     for i in range(3):")
print("         row = []")
print("         for j in range(3):")
print("             row.append(i*j)")
print("         matrix.append(row)")

Explanation:

  • The basic list comprehension [x**2 for x in range(10)] creates a list of squares for numbers 0 to 9.
  • The conditional list comprehension [x**2 for x in range(10) if x % 2 == 0] creates squares only for even numbers.
  • The nested list comprehension [[i*j for j in range(3)] for i in range(3)] creates a 3x3 multiplication table.

List comprehensions provide a concise and readable way to create lists based on various conditions and operations. They are often more efficient than equivalent for loops.

Lesson 2: Nested Lists

  1. Creating nested lists:

    • Lists can contain other lists as elements, creating a multi-dimensional structure.
  2. Accessing elements in nested lists:

    • Use multiple indices to access elements in nested lists.
  3. Modifying nested lists:

    • Elements in nested lists can be modified using indexing.

Let's explore these concepts with an example:

Example 8:

# Nested lists
matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
]

print("Original matrix:")
for row in matrix:
    print(row)

# Accessing elements
print("\nAccessing elements:")
print("Element at row 1, column 2:", matrix[1][2])  # Output: 6

# Iterating through nested list
print("\nIterating through the matrix:")
for row in matrix:
    for element in row:
        print(element, end=" ")
    print()

# Modifying an element
matrix[0][1] = 10
print("\nMatrix after modifying element at row 0, column 1:")
for row in matrix:
    print(row)

# Step-by-step explanation
print("\nStep-by-step explanation:")
print("1. We create a 3x3 matrix (a list of lists):")
print("   matrix = [")
print("       [1, 2, 3],")
print("       [4, 5, 6],")
print("       [7, 8, 9]")
print("   ]")
print("2. Accessing matrix[1][2] gives us the element in the second row, third column (6)")
print("3. We can iterate through the matrix using nested loops:")
print("   for row in matrix:")
print("       for element in row:")
print("           print(element, end=' ')")
print("4. To modify an element, we use indexing: matrix[0][1] = 10")
print("   This changes the element in the first row, second column to 10")

Explanation:

  • The matrix is a list containing three lists, representing a 3x3 grid.
  • We access individual elements using double indexing: matrix[row][column].
  • Nested loops allow us to iterate through all elements of the matrix.
  • We can modify elements by assigning new values to specific indices.

Nested lists are useful for representing multi-dimensional data structures like matrices, grids, or hierarchical data.

Lesson 3: List Copying

  1. Shallow copy vs. deep copy:

    • Shallow copy: Creates a new list but references the same nested objects.
    • Deep copy: Creates a new list and recursively copies all nested objects.
  2. Using the copy() method:

    • Creates a shallow copy of the list.
  3. Using the deepcopy() function:

    • Creates a deep copy of the list, including all nested objects.

Let's examine these concepts in detail:

Example 9:

import copy

# Original list
original = [1, [2, 3], 4]
print("Original list:", original)

# Shallow copy
shallow_copy = original.copy()
shallow_copy[1][0] = 5
print("\nAfter modifying shallow copy:")
print("Original:", original)
print("Shallow copy:", shallow_copy)

# Deep copy
deep_copy = copy.deepcopy(original)
deep_copy[1][1] = 6
print("\nAfter modifying deep copy:")
print("Original:", original)
print("Deep copy:", deep_copy)

# Step-by-step explanation
print("\nStep-by-step explanation:")
print("1. We start with the original list:", original)
print("2. We create a shallow copy using the copy() method:")
print("   shallow_copy = original.copy()")
print("3. When we modify the nested list in the shallow copy,")
print("   it affects the original because they share references")
print("4. We create a deep copy using copy.deepcopy():")
print("   deep_copy = copy.deepcopy(original)")
print("5. When we modify the nested list in the deep copy,")
print("   it doesn't affect the original because all nested objects are also copied")

Explanation:

  • Shallow copy (original.copy()):

    • Creates a new list object.
    • The elements of the new list are references to the same objects as in the original list.
    • Modifying a nested mutable object (like a list) in the shallow copy affects the original.
  • Deep copy (copy.deepcopy(original)):

    • Creates a new list object and recursively copies all nested objects.
    • Modifying the deep copy doesn't affect the original list or its nested objects.

Understanding the difference between shallow and deep copying is crucial when working with complex nested structures to avoid unintended side effects.

Lesson 4: List as Stack and Queue

  1. Using lists as stacks (LIFO - Last In, First Out):

    • append() to add an item to the top.
    • pop() to remove and return the top item.
  2. Using lists as queues (FIFO - First In, First Out):

    • While lists can be used as queues, it's not efficient for large lists.
    • append() to add an item to the end.
    • pop(0) to remove the first item (inefficient for large lists).
  3. Introduction to the deque class for efficient queue operations:

    • From the collections module, deque provides O(1) time complexity for append and pop operations at both ends.

Let's explore these concepts:

Example 10:

from collections import deque

# List as stack
print("Using list as a stack (LIFO):")
stack = []
stack.append(1)
stack.append(2)
stack.append(3)
print("Stack after pushing 1, 2, 3:", stack)
print("Popped item:", stack.pop())
print("Popped item:", stack.pop())
print("Final stack:", stack)

# List as queue (inefficient for large lists)
print("\nUsing list as a queue (FIFO):")
queue = [1, 2, 3]
print("Initial queue:", queue)
queue.append(4)
print("After enqueue 4:", queue)
print("Dequeue:", queue.pop(0))
print("Dequeue:", queue.pop(0))
print("Final queue:", queue)

# Using deque as an efficient queue
print("\nUsing deque as an efficient queue:")
efficient_queue = deque([1, 2, 3])
print("Initial deque:", efficient_queue)
efficient_queue.append(4)
print("After enqueue 4:", efficient_queue)
print("Dequeue:", efficient_queue.popleft())
print("Dequeue:", efficient_queue.popleft())
print("Final deque:", efficient_queue)

# Step-by-step explanation
print("\nStep-by-step explanation:")
print("1. Stack operations:")
print("   - append() adds items to the end (top of the stack)")
print("   - pop() removes and returns the last item (top of the stack)")
print("2. Queue operations with list (inefficient):")
print("   - append() adds items to the end")
print("   - pop(0) removes and returns the first item (inefficient for large lists)")
print("3. Queue operations with deque (efficient):")
print("   - append() adds items to the right end")
print("   - popleft() removes and returns the leftmost item efficiently")

Explanation:

  • Stack (LIFO):

    • append() adds items to the top of the stack.
    • pop() removes and returns the top item.
    • Ideal for scenarios where you need to process items in reverse order of arrival.
  • Queue with list (FIFO, but inefficient):

    • append() adds items to the end of the queue.
    • pop(0) removes the first item, but this operation is O(n) as it requires shifting all other elements.
  • Queue with deque (FIFO, efficient):

    • append() adds items to the right end.
    • popleft() removes and returns the leftmost item in O(1) time.
    • Efficient for both adding and removing elements from either end.

Using the appropriate data structure (stack or queue) depends on the specific requirements of your program. For queue operations, especially with large datasets, using deque is recommended for better performance.

Exercises:

  1. Create a list of your favorite movies. Add two more movies to the end of the list, and then remove the second movie in the list. Print the final list.

  2. Write a function that takes a list of numbers and returns a new list with only the even numbers.

  3. Create a nested list representing a 3x3 tic-tac-toe board. Fill it with 'X', 'O', and empty spaces ' '. Print the board in a user-friendly format.

  4. Write a program that takes a list of words and returns the longest word in the list. If there are multiple words with the same maximum length, return the first one that appears.

  5. Create a list of temperatures in Fahrenheit. Use a list comprehension to convert these temperatures to Celsius. (Formula: (F - 32) * 5/9)

  6. Write a function that takes two lists and returns a new list containing only the common elements (without duplicates).

  7. Create a list of dictionaries, where each dictionary represents a person with 'name' and 'age' keys. Sort the list based on the age of the people.

  8. Write a program that simulates a simple todo list. Allow the user to add tasks, mark tasks as complete (remove them), and view the current list of tasks.

  9. Create a function that takes a list of numbers and returns a new list where each element is the product of all other elements in the original list, except the element at that index.

  10. Write a program that generates a list of all prime numbers up to a given number n using the Sieve of Eratosthenes algorithm.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment