0%

74- Advanced Lists

Beyond basic list operations. Slicing, list comprehensions, deque, and advanced manipulation techniques. Unlock the full power of Python lists.

Lists are the workhorses of Python. You have used them for storing sequences, appending items, and basic iteration. But lists have many advanced features that can make your code more efficient and expressive.
Extended slicing with custom steps. List comprehensions with multiple conditions. The bisect module for maintaining sorted lists. array for memory-efficient numeric storage. deque for fast appends and pops from both ends.
This lesson explores these advanced list techniques. You will learn to slice lists with custom steps, use nested comprehensions, maintain sorted lists with binary search, and choose the right sequence type for your needs.

🕯️ Magic Note

The list type in Python is implemented as a dynamic array. Appending to the end is O(1) amortized, but inserting or deleting at the beginning is O(n). For frequent insertions at both ends, use collections.deque instead.

Extended Slicing with Step
Slice lists with start, stop, and step parameters.

Python

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

# Basic slicing [start:stop:step]

print(numbers[::2]) # [0, 2, 4, 6, 8] (every second)

print(numbers[1::2]) # [1, 3, 5, 7, 9] (every second starting at 1)

print(numbers[2:8:2]) # [2, 4, 6]

# Negative step (reverses order)

print(numbers[::-1]) # [9, 8, 7, 6, 5, 4, 3, 2, 1, 0] (reverse)

print(numbers[5:1:-1]) # [5, 4, 3, 2]

# Modifying slices with step (must match length)

numbers[::2] = [0, 0, 0, 0, 0]

print(numbers) # [0, 1, 0, 3, 0, 5, 0, 7, 0, 9]

# Deleting slices with step

del numbers[1::2]

print(numbers) # [0, 0, 0, 0, 0]

💡 Extended slicing with step is useful for extracting every nth element, reversing sequences, and performing strides on data arrays. It creates a new list (does not modify the original unless assigned).
Nested List Comprehensions
Create complex lists with nested loops in a single expression.

Python

# Flatten a matrix (nested for loops)

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

flat = [num for row in matrix for num in row]

print(flat) # [1, 2, 3, 4, 5, 6, 7, 8, 9]

# Equivalent to:

flat = []

for row in matrix:

for num in row:

flat.append(num)

# 2D list comprehension (create matrix)

matrix = [[i * j for j in range(1, 4)] for i in range(1, 4)]

print(matrix) # [[1, 2, 3], [2, 4, 6], [3, 6, 9]]

# Nested comprehensions with condition

pairs = [(x, y) for x in range(3) for y in range(3) if x != y]

print(pairs) # [(0, 1), (0, 2), (1, 0), (1, 2), (2, 0), (2, 1)]

# Cartesian product

colors = [“red”, “blue”]

sizes = [“S”, “M”, “L”]

products = [(c, s) for c in colors for s in sizes]

print(products) # [(‘red’, ‘S’), (‘red’, ‘M’), (‘red’, ‘L’), (‘blue’, ‘S’), (‘blue’, ‘M’), (‘blue’, ‘L’)]

🕯️ Magic Note

The order of for clauses in a nested comprehension follows the order of nesting. The outer loop comes first, then the inner loops. This matches the order you would write in a regular nested loop.

List Comprehensions with Conditionals
Filter elements while building lists.

Python

# Single condition (filter)

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

evens = [x for x in numbers if x % 2 == 0]

print(evens) # [2, 4, 6, 8, 10]

divisible = [x for x in range(1, 51) if x % 3 == 0 if x % 5 == 0]

print(divisible) # [15, 30, 45] (divisible by both 3 and 5)

# if-else in expression

labels = [“even” if x % 2 == 0 else “odd” for x in range(1, 6)]

print(labels) # [‘odd’, ‘even’, ‘odd’, ‘even’, ‘odd’]

# Using a function in comprehension

def is_prime(n):

if n < 2:

return False

for i in range(2, int(n ** 0.5) + 1):

if n % i == 0:

return False

return True

primes = [x for x in range(2, 50) if is_prime(x)]

print(primes) # [2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47]

Bisect: Maintaining Sorted Lists
The bisect module provides binary search and insertion for sorted lists.

Python

import bisect

sorted_list = [1, 3, 5, 7, 9]

# Find insertion point (index where to insert to keep order)

pos = bisect.bisect_left(sorted_list, 5)

print(pos) # 2 (index where 5 would go)

# bisect_right / bisect (insert after existing equal elements)

pos = bisect.bisect_right(sorted_list, 5)

print(pos) # 3 (for duplicates, inserts after)

# Insert while maintaining order (insort)

bisect.insort(sorted_list, 4)

print(sorted_list) # [1, 3, 4, 5, 7, 9]

# insort_left / insort_right for duplicate handling

bisect.insort_left(sorted_list, 5)

print(sorted_list) # [1, 3, 4, 5, 5, 7, 9]

# Using bisect for binary search

def binary_search(lst, target):

i = bisect.bisect_left(lst, target)

if i != len(lst) and lst[i] == target:

return i

return -1

print(binary_search(sorted_list, 5)) # 3

print(binary_search(sorted_list, 10)) # -1

🕯️ Magic Note

bisect uses binary search with O(log n) time complexity, much faster than linear search for large lists. Use it when you need to maintain a sorted list and perform frequent searches or insertions.

Deque: Fast Appends and Pops from Both Ends
The deque (double-ended queue) provides O(1) operations at both ends.

Python

from collections import deque

# Create deque

dq = deque([1, 2, 3])

print(dq) # deque([1, 2, 3])

# Append to right (same as list append)

dq.append(4) # deque([1, 2, 3, 4])

# Append to left (O(1) vs list insert(0) which is O(n))

dq.appendleft(0) # deque([0, 1, 2, 3, 4])

# Pop from right

last = dq.pop() # returns 4, deque([0, 1, 2, 3])

# Pop from left (O(1) vs list pop(0) which is O(n))

first = dq.popleft() # returns 0, deque([1, 2, 3])

# Extend from iterable

dq.extend([4, 5, 6]) # deque([1, 2, 3, 4, 5, 6])

dq.extendleft([-2, -1, 0]) # deque([0, -1, -2, 1, 2, 3, 4, 5, 6]) (note order!)

# Rotate deque

dq = deque([1, 2, 3, 4, 5])

dq.rotate(2) # deque([4, 5, 1, 2, 3]) (rotate right)

dq.rotate(-1) # deque([5, 1, 2, 3, 4]) (rotate left)

# Max length (fixed-size queue)

bounded = deque(maxlen=3)

for i in range(5):

bounded.append(i)

print(bounded) # oldest elements are dropped

💡 Use deque when you need fast appends and pops at both ends. For random access by index, list is better (deque indexing is O(n)). Use maxlen for fixed-size queues that automatically discard old items.
Array: Efficient Numeric Storage
The array module provides memory-efficient storage for numeric data.

Python

from array import array

import sys

# Type codes: ‘i’ (signed int), ‘f’ (float), ‘d’ (double), ‘b’ (signed char)

arr = array(“i”, [1, 2, 3, 4, 5]) # Array of signed integers

print(arr) # array(‘i’, [1, 2, 3, 4, 5])

# Array vs list memory usage

lst = list(range(1000000))

arr = array(“i”, range(1000000))

print(f”List memory: {sys.getsizeof(lst) / 1024 / 1024:.2f} MB”)

print(f”Array memory: {sys.getsizeof(arr) / 1024 / 1024:.2f} MB”)

# Array methods similar to lists

arr.append(6)

arr.extend([7, 8, 9])

arr.insert(0, 0)

print(arr[:5]) # array(‘i’, [0, 1, 2, 3, 4])

# Type codes

print(“Type codes: ‘b’ (signed char), ‘B’ (unsigned char), ‘i’ (signed int), ‘I’ (unsigned int)”)

print(” ‘l’ (signed long), ‘L’ (unsigned long), ‘q’ (long long), ‘Q’ (unsigned long long)”)

print(” ‘f’ (float), ‘d’ (double)”)

🕯️ Magic Note

The array module is more memory-efficient than lists for numeric data because it stores values in contiguous memory (like C arrays). Use it when working with large numeric datasets that fit in memory but where you want to save space.

itertools: Advanced Iteration Patterns
The itertools module provides powerful tools for working with sequences.

Python

import itertools

# chain: combine multiple iterables

result = list(itertools.chain([1, 2], [3, 4], [5, 6]))

print(result) # [1, 2, 3, 4, 5, 6]

# islice: slice iterator (lazy)

infinite = itertools.count()

first_ten = list(itertools.islice(infinite, 10))

print(first_ten) # [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]

# tee: create multiple independent iterators

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

iter1, iter2 = itertools.tee(original, 2)

print(list(iter1)) # [1, 2, 3, 4, 5]

print(list(iter2)) # [1, 2, 3, 4, 5]

# zip_longest: zip with fill value

result = list(itertools.zip_longest([1, 2], [3, 4, 5], fillvalue=0))

print(result) # [(1, 3), (2, 4), (0, 5)]

# permutations, combinations

items = [“A”, “B”, “C”]

perms = list(itertools.permutations(items, 2))

print(perms) # [(‘A’, ‘B’), (‘A’, ‘C’), (‘B’, ‘A’), (‘B’, ‘C’), (‘C’, ‘A’), (‘C’, ‘B’)]

combs = list(itertools.combinations(items, 2))

print(combs) # [(‘A’, ‘B’), (‘A’, ‘C’), (‘B’, ‘C’)]

Practical Example: Efficient Queue System
Use deque for managing a task queue with priority.

Python

from collections import deque

from bisect import insort

class TaskQueue:

def __init__(self):

self._high_priority = deque()

self._normal_priority = deque()

self._scheduled = [] # Sorted list for scheduled tasks

def add_high_priority(self, task):

self._high_priority.append(task)

def add_normal_priority(self, task):

self._normal_priority.append(task)

def add_scheduled(self, execute_time, task):

insort(self._scheduled, (execute_time, task))

def get_next(self, current_time=None):

if self._high_priority:

return self._high_priority.popleft()

if self._scheduled and current_time:

if self._scheduled[0][0] <= current_time:

return self._scheduled.pop(0)[1]

if self._normal_priority:

return self._normal_priority.popleft()

return None

queue = TaskQueue()

queue.add_high_priority(“Urgent task”)

queue.add_normal_priority(“Normal task 1”)

queue.add_normal_priority(“Normal task 2”)

print(queue.get_next()) # Urgent task

print(queue.get_next()) # Normal task 1

print(queue.get_next()) # Normal task 2

Common Mistakes with Advanced Lists
  • Modifying a list while iterating over it (use a copy or iterate backwards)
  • Using list for frequent insertions at the beginning (use deque)
  • Forgetting that extended slice assignment requires the right number of elements
  • Using nested comprehensions that are too complex (break them into steps)
  • Assuming list slicing creates a view (it creates a new list)
  • Using bisect on unsorted lists (results are unpredictable)
Check Your Understanding
  • Write a list comprehension that creates a list of squares for numbers 1 to 20 that are divisible by 3.
  • How do you reverse a list using slicing?
  • What is the difference between list.append() and deque.appendleft()?
  • Write a function that maintains a sorted list using bisect.insort.
  • Flatten a nested list [[1,2],[3,4],[5,6]] using a list comprehension.
  • When would you use array.array instead of a list?

⚡ Whisper

Lists are the containers you reach for first. But advanced lists are tools you reach for next. Slicing with step extracts every nth item. Nested comprehensions flatten matrices. bisect keeps your lists sorted without sorting. deque makes both ends fast. array saves memory for numbers.itertools chains, slices, and combines. Each tool solves a problem the basic list cannot. Choose the right one. Your code will be faster, clearer, and more efficient. The list is not just a list. It is a gateway to sequence mastery. Walk through it.

Related posts