Python Indexing and Slicing
· Seokhyeon Byun
Note: This post is based on my old programming study notes when I taught myself.
Sequence Operations
len()
: Returns the number of items in a sequence. When the length of a sequence is $n$, the index set is 0~$n$-1
Indexing
# Indexing Example
a = "Life is too short"
a[0] = L, a[5] = i # 0th item of string a
a[-1] = t, a[-2] = r # reverse indexing
- The first index is 0
- When processing indexes in reverse from the end, you can specify indexes like -1, -2, -3, etc.
Slicing
- Function that cuts from start to just before end based on index
A[x:y:z]
- x or more, less than y, z intervalB[n:]
- from n index to the endC[:m]
- from beginning to (m-1)th index valueD[:]
- from beginning to end
Examples
# Example 1)
b = "20010331Rainy" # string including numbers and character
b[:8] = 20010331 # Returns a value starting from 0th to before 8th index
# Warning: it looks like integer but type of returned value of b[:8] is still string!!!
b[8:] = Rainy # from 8th index to the end
# Example 2)
c = "12345678"
c[::1] = 12345678 # from beginning to end with 1 space interval
c[::2] = 1357 # every 2nd character
c[::-1] = 87654321 # from beginning to end in reverse order
c[::-2] = 7531 # every 2nd character in reverse
Key Points
- Index Range: For a sequence of length n, valid indexes are 0 to n-1
- Negative Indexing: -1 refers to last element, -2 to second-to-last, etc.
- Slicing Syntax:
[start:stop:step]
start
: inclusive starting positionstop
: exclusive ending positionstep
: interval between elements
- Default Values: Omitted values use defaults (0 for start, length for stop, 1 for step)
- Reverse Slicing: Use negative step values to reverse direction
Advanced Slicing Techniques
Multi-dimensional Slicing
# Working with lists of lists
matrix = [
[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12]
]
# Access specific row
first_row = matrix[0] # [1, 2, 3, 4]
last_row = matrix[-1] # [9, 10, 11, 12]
# Access specific element
element = matrix[1][2] # 7 (row 1, column 2)
# Access column (requires list comprehension)
first_column = [row[0] for row in matrix] # [1, 5, 9]
# Slice rows
first_two_rows = matrix[:2] # [[1, 2, 3, 4], [5, 6, 7, 8]]
# Slice within rows
partial_rows = [row[1:3] for row in matrix] # [[2, 3], [6, 7], [10, 11]]
String Slicing Patterns
text = "Programming Python"
# Extract words
words = text.split() # ['Programming', 'Python']
first_word = text.split()[0] # 'Programming'
# Extract substring between positions
middle = text[5:10] # 'ammin'
# Extract every nth character
every_second = text[::2] # 'Pormig yhn'
every_third = text[::3] # 'Prgmn to'
# Extract first/last n characters
first_5 = text[:5] # 'Progr'
last_5 = text[-5:] # 'ython'
# Remove first/last n characters
without_first_last = text[1:-1] # 'rogramming Pytho'
Technical Interview Essentials
Time Complexity of Indexing & Slicing
Time Complexities (Critical for Interviews):
# List operations
my_list = [1, 2, 3, 4, 5]
# Indexing: O(1) - direct memory access
item = my_list[2] # O(1)
item = my_list[-1] # O(1)
# Slicing: O(k) where k = number of elements returned
sublist = my_list[1:4] # O(3) = O(k)
reversed_list = my_list[::-1] # O(n)
# String operations
my_string = "hello world"
# String indexing: O(1)
char = my_string[0] # O(1)
# String slicing: O(k) - creates new string
substring = my_string[1:5] # O(4) = O(k)
reversed_str = my_string[::-1] # O(n)
Common Interview Patterns
1. Two Pointers Pattern
def reverse_string(s):
"""
Reverse string in-place using two pointers.
Time: O(n), Space: O(1)
"""
s = list(s) # Convert to list since strings are immutable
left, right = 0, len(s) - 1
while left < right:
s[left], s[right] = s[right], s[left]
left += 1
right -= 1
return ''.join(s)
def is_palindrome(s):
"""
Check if string is palindrome using two pointers.
Time: O(n), Space: O(1)
"""
left, right = 0, len(s) - 1
while left < right:
if s[left] != s[right]:
return False
left += 1
right -= 1
return True
# Test
print(reverse_string("hello")) # "olleh"
print(is_palindrome("racecar")) # True
print(is_palindrome("hello")) # False
2. Sliding Window Pattern
def max_sum_subarray(arr, k):
"""
Find maximum sum of subarray of size k.
Time: O(n), Space: O(1)
"""
if len(arr) < k:
return None
# Calculate sum of first window
window_sum = sum(arr[:k])
max_sum = window_sum
# Slide window and update sum
for i in range(k, len(arr)):
window_sum = window_sum - arr[i-k] + arr[i]
max_sum = max(max_sum, window_sum)
return max_sum
def longest_substring_without_repeating(s):
"""
Find length of longest substring without repeating characters.
Time: O(n), Space: O(min(m,n)) where m is character set size
"""
char_set = set()
left = 0
max_length = 0
for right in range(len(s)):
# Shrink window until no duplicates
while s[right] in char_set:
char_set.remove(s[left])
left += 1
char_set.add(s[right])
max_length = max(max_length, right - left + 1)
return max_length
# Test
print(max_sum_subarray([1, 2, 3, 4, 5], 3)) # 12 (3+4+5)
print(longest_substring_without_repeating("abcabcbb")) # 3 ("abc")
3. Array Rotation Using Slicing
def rotate_array_right(arr, k):
"""
Rotate array to right by k positions using slicing.
Time: O(n), Space: O(n)
"""
if not arr or k == 0:
return arr
n = len(arr)
k = k % n # Handle k > n
return arr[-k:] + arr[:-k]
def rotate_array_left(arr, k):
"""
Rotate array to left by k positions using slicing.
Time: O(n), Space: O(n)
"""
if not arr or k == 0:
return arr
n = len(arr)
k = k % n # Handle k > n
return arr[k:] + arr[:k]
# In-place rotation without extra space
def rotate_array_inplace(arr, k):
"""
Rotate array in-place using reversal method.
Time: O(n), Space: O(1)
"""
def reverse(start, end):
while start < end:
arr[start], arr[end] = arr[end], arr[start]
start += 1
end -= 1
n = len(arr)
k = k % n
# Reverse entire array
reverse(0, n - 1)
# Reverse first k elements
reverse(0, k - 1)
# Reverse remaining elements
reverse(k, n - 1)
return arr
# Test
print(rotate_array_right([1, 2, 3, 4, 5], 2)) # [4, 5, 1, 2, 3]
print(rotate_array_left([1, 2, 3, 4, 5], 2)) # [3, 4, 5, 1, 2]
Common Interview Gotchas
1. Index Out of Bounds
# Safe indexing techniques
def safe_get(lst, index, default=None):
"""Safely get element at index, return default if out of bounds."""
try:
return lst[index]
except IndexError:
return default
# Alternative using length check
def safe_get_alt(lst, index, default=None):
"""Alternative safe get using bounds checking."""
if 0 <= index < len(lst):
return lst[index]
elif -len(lst) <= index < 0: # Handle negative indices
return lst[index]
else:
return default
# Test edge cases
test_list = [1, 2, 3]
print(safe_get(test_list, 10)) # None
print(safe_get(test_list, -10)) # None
print(safe_get(test_list, 1)) # 2
2. Slicing Edge Cases
# Slicing never raises IndexError - important for interviews!
test_str = "hello"
print(test_str[10:20]) # "" - empty string, no error!
print(test_str[-10:3]) # "hel" - handles out of bounds gracefully
print(test_str[3:100]) # "lo" - stops at end
# Step must not be zero
try:
result = test_str[::0] # ValueError!
except ValueError as e:
print(f"Error: {e}")
# Empty sequences
empty_list = []
print(empty_list[:5]) # [] - still empty
print(empty_list[-5:]) # [] - still empty
3. Immutable vs Mutable Slicing
# String slicing creates new objects (immutable)
original_str = "hello"
sliced_str = original_str[1:4] # Creates new string "ell"
modified_str = original_str[:2] + "y" # Creates new string "hey"
# List slicing creates new objects but elements may be shared
original_list = [[1, 2], [3, 4], [5, 6]]
sliced_list = original_list[1:] # New list: [[3, 4], [5, 6]]
# But inner lists are shared!
sliced_list[0].append(7)
print(original_list) # [[1, 2], [3, 4, 7], [5, 6]] - original affected!
# Deep copy for complete independence
import copy
deep_sliced = copy.deepcopy(original_list[1:])
Performance Tips for Interviews
1. Efficient String Operations
# BAD - O(n²) due to string immutability
def build_string_slow(words):
result = ""
for word in words:
result += word + " "
return result.strip()
# GOOD - O(n) using join
def build_string_fast(words):
return " ".join(words)
# For character-level operations, use list
def process_chars_fast(s):
chars = list(s) # O(n) conversion
# ... modify chars in-place ...
return ''.join(chars) # O(n) conversion back
2. Memory-Efficient Slicing
# For large sequences, consider iterators
def process_large_slice(large_list, start, end):
# Instead of creating a slice: large_list[start:end]
# Use iterator to save memory
for i in range(start, min(end, len(large_list))):
yield large_list[i]
# Example usage
large_data = list(range(1000000))
for item in process_large_slice(large_data, 1000, 2000):
# Process without creating intermediate slice
pass