Itertools#
Key Points#
itertools: A module providing functions for creating efficient iterators for various tasks.
count([start[, step]]): Creates an infinite iterator that starts at a specified value and increments by a specified step (defaults to 1).
cycle(p): Creates an infinite iterator that endlessly repeats the elements from an iterable.
permutations(iterable, r=None): Generates all possible arrangements (permutations) of elements from an iterable, with a specified length if provided. (Order does matter.)
combinations(iterable, r): Generates all possible combinations of elements from an iterable, with a specified length. (Order doesn’t matter unlike permutations)
accumulate(p[, func]): Applies a function (default is addition) cumulatively to elements in an iterable, yielding the sums at each step.
filterfalse(predicate, sequence): Creates an iterator that filters out elements from an iterable based on a predicate function (keeps elements that return False).
lru_cache(maxsize=128, typed=False): Creates a function decorator that caches the results of the function based on the arguments (Least Recently Used cache with size limit).
batched(p, n): Creates an iterator that yields fixed-size chunks of elements from an iterable.
Concept#
In computer programming, an iterable is essentially a container that holds elements and allows you to access them one by one in a specific order. Think of it like a box with items you can take out, one at a time.
Here’s a breakdown of what iterables are and how they work:
Usable with loops: The key feature of iterables is that you can use them in ‘for’ loops to process elements efficiently. The loop automatically iterates over the iterable, retrieving each element on each pass.
Examples: Common examples of iterables include lists, tuples, strings, sets, and dictionaries (in some languages).
Behind the scenes: Iterables typically implement a special method (often named
__iter__
) that creates an iterator object. This iterator keeps track of the current position within the iterable and provides access to elements one at a time.
Here’s an analogy: Imagine a bookshelf (iterable) containing books (elements). You can’t grab all the books at once, but you can go through them one by one (iteration) using a ladder (iterator) to reach each book on the shelf.
Iterables are fundamental for processing sequences of data in programming and form the backbone of many powerful techniques.
Itertools Library#
The itertools
module in Python is a toolbox specifically designed to work with iterables. It provides functions that create new iterators based on existing iterables. These new iterators can perform various manipulations on the original data in a memory-efficient way.
Here is a sample of how they work. This is the accumulate function. Note that it returns an iterator.
The itertools.accumulate()
function in Python is a powerful tool for performing cumulative operations on iterables. It creates an iterator that yields the sum (by default) or any other function applied cumulatively to the elements of the iterable.
Here’s a breakdown of how itertools.accumulate()
works:
Cumulative Operations: It applies a function to successive elements of the iterable, keeping track of a running total. This total is then yielded by the iterator at each step.
Default Behavior (Sum): If you don’t provide a custom function,
accumulate()
performs addition by default. This is useful for calculating running sums, like finding the total sales up to a certain point in a list.Custom Functions: You can provide a custom function to
accumulate()
to perform different cumulative operations. This function should take two arguments: the current accumulated value and the next element from the iterable. The function’s return value becomes the new accumulated value for the next iteration.
Here are some key points to remember about itertools.accumulate()
:
Returns an Iterator: It’s important to note that
accumulate()
returns an iterator, not a list. This means the elements are generated on-demand, saving memory compared to creating a new list to store all the results.Optional Initial Value (Python 3.8+): In Python versions 3.8 and later, you can specify an initial value using the
initial
argument. This value will be prepended to the beginning of the accumulated results.
Documentation#
https://docs.python.org/3.12/library/itertools.html#
Note there are more itertools listed in the official document
Code#
"""
Itertools
Programmer: James Goudy
Date: 07/19/2024
This script demonstrates various functionalities from the itertools library in Python.
"""
# Import libraries
import itertools
import operator
import functools
import sys
# Function to generate a count loop (runs indefinitely)
def iter_count():
"""
This function demonstrates the itertools.count() function
which generates an unending sequence of numbers.
Parameters:
None
Returns:
None (prints the sequence to the console)
"""
print("\nCount Example")
start = 0
stop = 500
step = 100
for i in itertools.count(start, step):
print(i)
# Break the loop when reaching the stop value
if i >= stop:
break
# Function to cycle through elements in a list repeatedly
def iter_cycle():
"""
This function demonstrates the itertools.cycle() function
which iterates through a list repeatedly.
Parameters:
None
Returns:
None (prints the cycled elements to the console)
"""
print("\nCycle Example")
cntr = 0
myList = [10, 20, 30, 40]
myList2 = ["AA", "BB", "CC", "DD"]
repeatCycle = 2
for i in itertools.cycle(myList):
print(i)
cntr += 1
# Break the cycle after a specified number of repetitions
if cntr >= (repeatCycle * len(myList)):
break
cntr = 0
print()
for i in itertools.cycle(myList2):
print(i)
cntr += 1
# Break the cycle after a specified number of repetitions
if cntr >= (repeatCycle * len(myList)):
break
# Function to generate permutations (ordered arrangements)
def iter_permutations():
"""
This function demonstrates the itertools.permutations() function
which generates all possible ordered arrangements (permutations) of elements.
Parameters:
None
Returns:
None (prints the permutations to the console)
"""
print("\nPermutation Example")
myList = [1, 2, 3]
for prm in itertools.permutations(myList):
print(prm)
# Function to generate combinations (unordered arrangements)
def iter_combinations():
"""
This function demonstrates the itertools.combinations() function
which generates all possible unordered arrangements (combinations) of elements.
Parameters:
None
Returns:
None (prints the combinations to the console)
"""
print("\nCombinations Example")
myList = [1, 2, 3, 4]
combLen = len(myList)
for cSize in range(1, combLen + 1):
print("\nCombination size = {}".format(cSize))
for prm in itertools.combinations(myList, cSize):
print(prm)
# Function to calculate accumulated sums
def iter_accumulate():
"""
This function demonstrates the itertools.accumulate() function
which returns a sequence of partial sums.
Parameters:
None
Returns:
None (prints the accumulated sums to the console)
"""
print("\nAccumulation Example")
myList = [2, 4, 5, 10]
print("Data is - {}".format(myList))
# Rolling total using accumulate
myAcc = itertools.accumulate(myList)
print(list(myAcc))
# Iterate through each item in the accumulated sequence
print("\nIterate through \neach item in Acc")
for x in itertools.accumulate(myList):
print("item is {}".format(x))
print("\nMultiple instead of add")
myAcc = itertools.accumulate(myList, operator.mul)
print(list(myAcc))
# --------------------------------------------------
# Filter out data based on a specific criteria
# Helper function for iter_filterfalse
def divByTwo(a):
"""
This function checks if a number is odd (not divisible by 2).
It's a helper function used by iter_filterfalse.
Parameters:
a (int): The number to check
Returns:
bool: True if the number is odd, False otherwise
"""
def iter_filterfalse():
"""
This function demonstrates the itertools.filterfalse() function
which filters out elements based on a condition.
Parameters:
None
Returns:
None (prints the filtered elements to the console)
"""
myList = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
# Filter out odd numbers (elements not divisible by 2) using divByTwo function
evenNums = itertools.filterfalse(divByTwo, myList)
print("Evens", list(evenNums))
# Filter out even numbers (elements divisible by 2) using lambda function
evenNums = itertools.filterfalse(lambda x: x % 2, myList)
print("Evens", list(evenNums))
# --------------------------------------------------
# ---------- leave recursion in cache --------------
global countFastSteps
global countSlowSteps
countFastSteps = 0
countSlowSteps = 0
@functools.lru_cache()
def fibfast(n):
"""
This function calculates the nth Fibonacci number using recursion
with memoization (lru_cache) for efficiency.
Parameters:
n (int): The index of the Fibonacci number to calculate
Returns:
int: The nth Fibonacci number
"""
global countFastSteps
countFastSteps = countFastSteps + 1
return fibfast(n-2) + fibfast(n-1) if n > 1 else 1
def fibslow(n):
"""
This function calculates the nth Fibonacci number using recursion
without memoization (slower approach).
Parameters:
n (int): The index of the Fibonacci number to calculate
Returns:
int: The nth Fibonacci number
"""
global countSlowSteps
countSlowSteps += 1
return fibslow(n-2) + fibslow(n-1) if n > 1 else 1
def lru_cache_example(i):
"""
This function demonstrates the performance improvement of memoization (lru_cache)
by comparing the number of function calls needed to calculate the nth Fibonacci number
with and without memoization.
Parameters:
i (int): The index of the Fibonacci number to calculate
Returns:
None (prints the number of function calls to the console)
"""
# Initialize variables globally to avoid errors
global countSlowSteps
global countFastSteps
countSlowSteps = 0
countFastSteps = 0
fibfast(i)
fibslow(i)
print(countFastSteps)
print('With lru_cache total function steps: ', countFastSteps)
print('Without lru_cache total function steps: ', countSlowSteps)
# --------------------------------------------------
def itr_batch():
"""
This function demonstrates the itertools.batched() function
which groups elements from an iterable into fixed-size chunks.
Parameters:
None
Returns:
None (prints the batched elements and manipulates them to the console)
"""
print("\nBatch Process")
# Create a list of numbers
my_list = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17]
# Use itertools.batched() to group elements in the list into chunks of size 4
for batch_item in itertools.batched(my_list, 4):
# Convert the batched item (generator) to a list and print it
print(list(batch_item)) # Print the batched elements
# Loop through each item within the current batch
for item in batch_item:
# Multiply the item by 10 and print it with a space
sys.stdout.write(str(item * 10) + " ")
# Add a newline and separator after processing each batch
print("\n---\n")
# Define a multi-line string containing a quote
quote = """
Never argue with stupid people,
they will drag you down to their level
and beat you with experience. - Mark Twain
"""
# Use itertools.batched() again to group characters in the quote into chunks of size 7
for chunk in itertools.batched(quote, 7):
# Print each chunk of the quote
print(chunk)
def main():
iter_count()
iter_cycle()
iter_permutations()
iter_combinations()
iter_accumulate()
iter_filterfalse()
lru_cache_example(25)
itr_batch()
print("\n\nbye\n")
main()
'''
Count Example
0
100
200
300
400
500
Cycle Example
10
20
30
40
10
20
30
40
AA
BB
CC
DD
AA
BB
CC
DD
Permutation Example
(1, 2, 3)
(1, 3, 2)
(2, 1, 3)
(2, 3, 1)
(3, 1, 2)
(3, 2, 1)
Combinations Example
Combination size = 1
(1,)
(2,)
(3,)
(4,)
Combination size = 2
(1, 2)
(1, 3)
(1, 4)
(2, 3)
(2, 4)
(3, 4)
Combination size = 3
(1, 2, 3)
(1, 2, 4)
(1, 3, 4)
(2, 3, 4)
Combination size = 4
(1, 2, 3, 4)
Accumulation Example
Data is - [2, 4, 5, 10]
[2, 6, 11, 21]
Iterate through
each item in Acc
item is 2
item is 6
item is 11
item is 21
Multiple instead of add
[2, 8, 40, 400]
Evens [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
Evens [2, 4, 6, 8, 10]
26
With lru_cache total function steps: 26
Without lru_cache total function steps: 242785
Batch Process
[1, 2, 3, 4]
10 20 30 40
---
[5, 6, 7, 8]
50 60 70 80
---
[9, 10, 11, 12]
90 100 110 120
---
[13, 14, 15, 16]
130 140 150 160
---
[17]
170
---
('\n', ' ', ' ', 'N', 'e', 'v', 'e')
('r', ' ', 'a', 'r', 'g', 'u', 'e')
(' ', 'w', 'i', 't', 'h', ' ', 's')
('t', 'u', 'p', 'i', 'd', ' ', 'p')
('e', 'o', 'p', 'l', 'e', ',', ' ')
('\n', ' ', ' ', 't', 'h', 'e', 'y')
(' ', 'w', 'i', 'l', 'l', ' ', 'd')
('r', 'a', 'g', ' ', 'y', 'o', 'u')
(' ', 'd', 'o', 'w', 'n', ' ', 't')
('o', ' ', 't', 'h', 'e', 'i', 'r')
(' ', 'l', 'e', 'v', 'e', 'l', ' ')
('\n', ' ', ' ', 'a', 'n', 'd', ' ')
('b', 'e', 'a', 't', ' ', 'y', 'o')
('u', ' ', 'w', 'i', 't', 'h', ' ')
('e', 'x', 'p', 'e', 'r', 'i', 'e')
('n', 'c', 'e', '.', ' ', '-', ' ')
('M', 'a', 'r', 'k', ' ', 'T', 'w')
('a', 'i', 'n', '\n', ' ', ' ')
bye
'''