Explain Codes LogoExplain Codes Logo

Finding the index of an item in a list

python
list-comprehension
performance
benchmarking
Alex KataevbyAlex Kataev·Oct 2, 2024
TLDR

The index() method provides a fast way to determine a list item's position. It returns the index of the first occurrence of the item:

fruits = ['apple', 'banana', 'cherry'] banana_position = fruits.index('banana') # Don't mix bananas and apples – 1

Behold, index() will make you face a ValueError if the item is lost in the list wilderness:

try: pos = fruits.index('grape') except ValueError: pos = None # 'grape' isn't a thing here

Implementing a complicated search, such as when you need to filter items based on a criterion? Use enumerate() in conjunction with a list comprehension:

pos = next((i for i, x in enumerate(fruits) if condition(x)), None)

Master the art of multiple hunting: Find all occurrences

index() might be the Usain Bolt of simple lookups, but it tires after seeing the first match it meets. Now, to spot all matches, a list comprehension with enumerate() is your new friend:

# Hunting for all cherries in the basket! indices = [i for i, x in enumerate(fruits) if x == 'cherry']

Scaling to work with huge lists? Blend itertools.count() and zip(). It will pair each item with a growing index, ensuring the underlying enumerate() doesn't exhaust itself scanning the entire list:

import itertools indexed_list = list(zip(itertools.count(), fruits)) # Cherry hunting season is open! indices = [i for i, x in indexed_list if x == 'cherry']

Taming the ValueError Beast: Safe coding practices

Roaming the list can be full of terrors, including a dreaded ValueError. Safeguard your search by checking if the item even booked a seat on the list. Use if item in list:

if 'grape' in fruits: pos = fruits.index('grape') # Found one! else: pos = None # No grapes in our garden

Zone Control: Range-limited searches

To confine your search to a specific range, use index() with start and end parameters. This is like telling the function, "Hey, it's between positions 2 and 5":

# Find a banana between positions 2 and 5 pos = fruits.index('banana', 2, 5) # Gotcha!

Walking the performance tightrope

Navigating a list has a time complexity of O(n): the operation's time requirement grows linearly with list length. When cracking a performance-intensive task, peek into modules like numpy to manage arrays with custom data types:

import numpy as np # Fast lookup using a numpy array fruits_np = np.array(['apple', 'banana', 'cherry']) indices_np = np.where(fruits_np == 'banana')

Riding the chronological waves: Sequential indexing

Your search might run in a series. Imagine a paparazzo chasing celebrities (your item) at a film festival (list). Although they might appear multiple times, you're only interested in their next appearance. To do it efficiently, increment the start point:

next_on_red_carpet = 0 # The 'banana' here is like an A-list celebrity while 'banana' in fruits[next_on_red_carpet:]: next_on_red_carpet = fruits.index('banana', next_on_red_carpet) + 1 print(f"Found 'banana' at: {next_on_red_carpet - 1}")

The legacy treasure: Python 2 compatibility

While our code can travel forward in time to Python 3, sometimes it needs to honour the elders. Substitute enumerate() with itertools.izip() for theoretically peak memory-efficiency:

import itertools indices = [i for i, x in itertools.izip(itertools.count(), fruits) if x == 'banana']

The balance of code: Readability vs. Efficiency

While readability is a prime concern, often, efficient code is favoured for meeting critical performance desires. However, make sure your code is like a well-written novel, understood by all who endeavour to read it.

Time the race: Benchmarking for efficiency

Test your custom indexing solutions against time with Python’s built-in timeit:

import timeit print(timeit.timeit('fruits.index("banana")', setup='fruits = list(range(1000))'))