Python: List

One of the fundamental data structures in Python is the list. A list is a mutable sequence of elements that can be of any type, and it allows you to store and manipulate data efficiently.

In this blog post, we will take a closer look at Python lists, their properties, and some common use cases.

Creating Lists

To create a list in Python, you can use square brackets and separate each element with a comma. For example, to create a list of integers, you can use the following code:

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

You can also create a list of strings:

names = ['Sachin', 'Virat', 'Rohit', 'Ramesh']

Lists can also contain elements of different types:

mixed = [1, 'Sachin', True, 3.14]

Accessing Elements

You can access elements in a list by their index, which starts at 0. For example, to access the first element of the list numbers, you can use the following code:

first_number = numbers[0]

You can also access elements from the end of the list by using negative indices. For example, to access the last element of the list numbers , you can use the following code:

last_number = numbers[-1]

Slicing Lists

You can also slice a list to create a new list that contains a subset of the elements. To slice a list, you can use the colon : operator. For example, to create a new list that contains the first three elements of the list numbers, you can use the following code:

first_three_numbers = numbers[0:3]

This will create a new list [1, 2, 3] . You can also omit the starting or ending index to slice from the beginning or to the end of the list, respectively. For example, to create a new list that contains the last three elements of the list numbers , you can use the following code:

last_three_numbers = numbers[-3:]

This will create a new list [3, 4, 5].

Modifying Lists

Lists are mutable, which means that you can modify them by adding, removing, or changing elements. To add an element to a list, you can use the append method. For example, to add the number 6 to the end of the list numbers, you can use the following code:

numbers.append(6)

To insert an element at a specific position in a list, you can use the insert method. For example, to insert the number 0 at the beginning of the list numbers , you can use the following code:

numbers.insert(0, 0)

To remove an element from a list, you can use the remove method. For example, to remove the number 3 from the list numbers, you can use the following code:

numbers.remove(3)

To remove the last element of a list, you can use the pop method. For example, to remove the number 6 from the end of the list numbers , you can use the following code:

numbers.pop()

List Methods

Here are some of the most commonly used methods:

len(): This method returns the number of elements in a list.

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

index(): This method returns the index of the first occurrence of a specified element in a list.

fruits = ['apple', 'banana', 'orange', 'banana']
print(fruits.index('banana')) # Output: 1

count(): This method returns the number of times a specified element appears in a list.

fruits = ['apple', 'banana', 'orange', 'banana']
print(fruits.count('banana')) # Output: 2

sort(): This method sorts the elements in a list in ascending order by default.

numbers = [4, 2, 5, 1, 3]
numbers.sort()
print(numbers) # Output: [1, 2, 3, 4, 5]

You can also specify the reverse argument to sort the list in descending order:

numbers = [4, 2, 5, 1, 3]
numbers.sort(reverse=True)
print(numbers) # Output: [5, 4, 3, 2, 1]

reverse(): This method reverses the order of the elements in a list.

numbers = [1, 2, 3, 4, 5]
numbers.reverse() # or numbers[::-1]
print(numbers) # Output: [5, 4, 3, 2, 1]

extend(): This method adds the elements of one list to another list.

fruits = ['apple', 'banana', 'orange']
more_fruits = ['grape', 'pineapple']
fruits.extend(more_fruits)
print(fruits) # Output: ['apple', 'banana', 'orange', 'grape', 'pineapple']

copy(): This method creates a shallow copy of a list.

numbers = [1, 2, 3, 4, 5]
numbers_copy = numbers.copy()
print(numbers_copy) # Output: [1, 2, 3, 4, 5]

It’s important to note that copy() creates a new list object, but the elements inside the list still refer to the same objects as the original list.

List Comprehension

List comprehension is a concise way of creating a new list in Python by iterating over an existing iterable object, such as a list, string, or tuple, and applying a condition to filter or transform its elements.

The basic syntax of a list comprehension is:

new_list = [expression for item in iterable if condition]

Here, expression is an operation performed on each item in the iterable that will be included in the new list. The if condition is optional and can be used to filter out elements that do not meet a certain condition.

For example, let’s say we have a list of numbers and we want to create a new list that contains only the even numbers:

numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
even_numbers = [num for num in numbers if num % 2 == 0]
print(even_numbers) # Output: [2, 4, 6, 8, 10]

In this example, we use a list comprehension to iterate over the numbers list and only include the elements that satisfy the condition num % 2 == 0, which checks if the number is even.

List comprehension can also be used to transform the elements of an iterable object. For example, let’s say we have a list of names and we want to create a new list that contains the lengths of each name:

names = ['Sachin', 'Virat', 'Rohit', 'Ramesh']
name_lengths = [len(name) for name in names]
print(name_lengths) # Output: [6, 5, 5, 6]

In this example, we use a list comprehension to iterate over the names list and apply the len() function to each name, resulting in a new list that contains the lengths of each name.

List comprehension can be a powerful tool for writing concise and efficient code, especially when dealing with large datasets. However, it’s important to use it judiciously and not sacrifice readability for brevity.

Time complexities

Here are some of the most commonly used methods and their time complexities:

Method Time Complexity
append() This method adds an element to the end of a list. The time complexity is O(1) on average, but it can be O(n) in the worst case if the list needs to be resized.
extend() This method adds the elements of one list to another list. The time complexity is O(k), where k is the length of the list being added.
insert() This method inserts an element at a specified index. The time complexity is O(n) in the worst case because all the elements after the index need to be shifted one position to the right.
remove() This method removes the first occurrence of a specified element from a list. The time complexity is O(n) in the worst case because all the elements after the removed element need to be shifted one position to the left.
pop() This method removes and returns the last element from a list. The time complexity is O(1).
index() This method returns the index of the first occurrence of a specified element in a list. The time complexity is O(n) in the worst case because all the elements need to be checked until the specified element is found.
sort() This method sorts the elements in a list. The time complexity is O(n log n) in the average case, but it can be O(n^2) in the worst case for certain input patterns.
reverse() This method reverses the order of the elements in a list. The time complexity is O(n).

Example

Implementation of Depth First Search (DFS) using List in Python.

Time complexity of the following implementation is O(V+E), where V is the number of vertices and E is the number of edges in the graph.

from typing import List, Dict


def dfs(graph: Dict[str, List[str]], start: str) -> None:
  """Performs depth-first search on a graph starting from a given node.

  This function does not return anything, but prints out the nodes visited during the search.

  Args:
    graph: A dictionary representing the graph, where each key is a node and its value is a list of its neighbors.
    start: The node to start the search from.
  """
  # create a visited set to keep track of visited nodes
  visited = set()
  # create a stack using a list
  stack = [start]
  # mark the start node as visited
  visited.add(start)

  while stack:
    # get the next node from the top of the stack
    node = stack.pop() # O(1) constant complexity in removing element from the last or most recent.
    print(node, end="\n")

    # iterate over the neighbors of the current node
    for neighbor in graph[node]:
      # if the neighbor has not been visited, add it to the stack and mark it as visited
      if neighbor not in visited:
        stack.append(neighbor) # O(1) constant complexity
        visited.add(neighbor)

if __name__ == "__main__":
  # example graph represented as an adjacency list
  graph = {'A': ['B', 'C'], 'B': ['D', 'E'], 'C': ['F'], 'D': [], 'E': ['F'], 'F': []}
  # run DFS starting from node 'A'
  dfs(graph, 'A')