Day 16 – Iterators and Generators
♾️ Day 16 – Iterators and Generators
1. Learning Objectives
By the end of Day 16, you will be able to:
- Understand the difference between iterable, iterator, and generator.
- Create generator functions using
yieldto lazily produce sequences. - Use generators to efficiently traverse large datasets (e.g., IFC models, point clouds).
- Build infinite sequences and pipeline data processing.
- Apply generators to AEC tasks: lazy reading of building elements, generating column grids, streaming coordinates.
2. Concept Explanation
2.1 Why Generators Matter in AEC
Large building models can have tens of thousands of elements – walls, beams, columns, rooms. Loading everything into memory at once is inefficient. Generators let you:
- Process one element at a time – memory efficient.
- Chain processing steps – filter, transform, analyse in a pipeline.
- Generate infinite sequences – e.g., parametric numbering.
- Lazy evaluation – compute values only when needed.
2.2 Iterables vs Iterators vs Generators
| Concept | Description | Example |
|---|---|---|
| Iterable | An object that can be looped over (__iter__) | list, tuple, str, range |
| Iterator | An object that produces values one at a time (__next__) | iter([1,2,3]) |
| Generator | A special iterator created by a function with yield | def gen(): yield 1 |
2.3 Generator Functions with yield
A generator function uses yield instead of return. It pauses execution, yields a value, and resumes when the next value is requested.
def floor_levels(start, count, step):
"""Generate floor elevations lazily."""
for i in range(count):
yield start + i * step
# Usage
for level in floor_levels(0.0, 5, 3.5):
print(f"Floor at {level:.1f} m")
Each call to next() resumes the function until the next yield.
2.4 Generator Expressions
Like list comprehensions but with parentheses – memory efficient.
# List comprehension – builds entire list in memory
squares_list = [x**2 for x in range(10_000)]
# Generator expression – yields values one at a time
squares_gen = (x**2 for x in range(10_000))
# Use in a loop
for sq in squares_gen:
if sq > 100:
break
print(sq)
2.5 Chaining Generators (Pipelines)
You can chain generators to build data processing pipelines:
def read_coordinates(file):
for line in open(file):
if line.strip():
x, y = map(float, line.split(","))
yield x, y
def filter_within_bbox(points, xmin, ymin, xmax, ymax):
for x, y in points:
if xmin <= x <= xmax and ymin <= y <= ymax:
yield x, y
def convert_to_mm(points):
for x, y in points:
yield x * 1000, y * 1000
# Pipeline: read → filter → convert
points = read_coordinates("columns.csv")
filtered = filter_within_bbox(points, 0, 0, 50, 50)
mm_points = convert_to_mm(filtered)
for x_mm, y_mm in mm_points:
print(f"({x_mm:.0f}, {y_mm:.0f})")
3. Code Examples
Example 1: Lazy column grid generator
def column_grid(x_count, y_count, x_spacing, y_spacing):
"""Generate (x, y) coordinates for a column grid, one at a time."""
for row in range(y_count):
for col in range(x_count):
yield (col * x_spacing, row * y_spacing)
# Use – no large list created
for i, (x, y) in enumerate(column_grid(4, 3, 6.0, 8.0)):
print(f"Column {i+1}: ({x:.1f}, {y:.1f})")
if i >= 5: # only need first 6
break
Example 2: Lazy reading of building elements (simulating IFC traversal)
def read_elements(csv_file):
"""Simulate lazy reading of building elements from a CSV."""
import csv
with open(csv_file, "r") as f:
reader = csv.DictReader(f)
for row in reader:
# Simulate processing delay
yield {
"id": row["ID"],
"type": row["Type"],
"level": int(row["Level"]),
"volume": float(row["Volume_m3"])
}
# Usage – process one element at a time
total_volume = 0.0
wall_count = 0
for elem in read_elements("building_elements.csv"):
if elem["type"] == "Wall":
total_volume += elem["volume"]
wall_count += 1
print(f"Processing wall {elem['id']}...")
print(f"\nTotal wall volume: {total_volume:.2f} m³ ({wall_count} walls)")
Example 3: Infinite generator for beam numbering
def beam_number_generator(prefix="B"):
"""Generate sequential beam marks: B1, B2, B3, ... infinitely."""
n = 1
while True:
yield f"{prefix}{n}"
n += 1
# Use with a finite loop
beams = beam_number_generator()
for i in range(5):
print(next(beams))
# Output: B1, B2, B3, B4, B5
Example 4: Pipeline for coordinate transformation
def read_coordinates(filename):
"""Read (x, y) pairs from a CSV file, one at a time."""
with open(filename, "r") as f:
for line in f:
if line.strip() and not line.startswith("x"):
parts = line.split(",")
yield float(parts[0]), float(parts[1])
def rotate_points(points, angle_deg):
"""Rotate points by given angle (degrees)."""
import math
rad = math.radians(angle_deg)
cos_a, sin_a = math.cos(rad), math.sin(rad)
for x, y in points:
yield (x * cos_a - y * sin_a, x * sin_a + y * cos_a)
def shift_points(points, dx, dy):
"""Shift points by (dx, dy)."""
for x, y in points:
yield (x + dx, y + dy)
# Build pipeline
points = read_coordinates("columns.csv")
rotated = rotate_points(points, 45.0)
shifted = shift_points(rotated, 10.0, 5.0)
for x, y in shifted:
print(f"Transformed: ({x:.2f}, {y:.2f})")
4. Hands‑on Exercises (3–5 Problems)
Problem 1 – Floor level generator
Write a generator floor_levels(base, height, num_floors) that yields the elevation of each floor. Test with base=0, height=3.5, num_floors=10.
Problem 2 – Filter long spans with a generator
Given a list spans = [6.0, 4.5, 7.2, 3.0, 8.1, 5.5], write a generator long_spans(spans, min_span=6.0) that yields only spans >= min_span. Use it in a loop.
Problem 3 – Fibonacci for structural load distribution (bonus concept)
Write a generator that yields Fibonacci numbers indefinitely. Use it to generate the first 15 Fibonacci numbers and print them.
Problem 4 – Generator expression for square metres
Given a list of room dimensions as tuples (length, width):rooms = [(8,5), (6,4), (10,6), (4,3)]
Use a generator expression to compute areas, then print only areas > 30 m².
Solutions (attempt first):
# P1
def floor_levels(base, height, num_floors):
for i in range(num_floors):
yield base + i * height
for level in floor_levels(0, 3.5, 10):
print(f"Level at {level:.1f} m")
# P2
def long_spans(spans, min_span=6.0):
for s in spans:
if s >= min_span:
yield s
spans = [6.0, 4.5, 7.2, 3.0, 8.1, 5.5]
for s in long_spans(spans):
print(f"Long span: {s} m")
# P3
def fibonacci():
a, b = 0, 1
while True:
yield a
a, b = b, a + b
fib = fibonacci()
for _ in range(15):
print(next(fib), end=" ")
print()
# P4
rooms = [(8,5), (6,4), (10,6), (4,3)]
areas = (l * w for l, w in rooms)
large_areas = (a for a in areas if a > 30)
for a in large_areas:
print(f"Area: {a} m²")
5. Applied Challenge Task
Task: Lazy IFC‑like Element Processor
You are given a CSV file building_elements.csv with columns:
ID,Type,Level,Length_m,Width_m,Height_m,Material
W1,Wall,0,6.0,0.2,3.0,Concrete
W2,Wall,0,4.5,0.2,3.0,Concrete
C1,Column,0,0.4,0.4,3.5,Steel
S1,Slab,0,12.0,8.0,0.2,Concrete
W3,Wall,1,8.0,0.2,2.8,Brick
W4,Wall,1,5.0,0.2,2.8,Brick
C2,Column,1,0.5,0.5,2.8,Steel
S2,Slab,1,12.0,8.0,0.2,Concrete
Write a script that:
Defines a generator
read_elements(filename)that yields each row as a dictionary lazily.Defines a generator
filter_by_type(elements, element_type)that yields only elements of a given type.Defines a generator
filter_by_level(elements, level)that yields only elements on a given level.Defines a generator
compute_volume(elements)that adds avolumekey (for walls: L×W×H; columns: L×W×H; slabs: L×W×H) and yields the enriched dictionary.Uses this pipeline to:
- Find total volume of all concrete elements on level 0.
- Find total volume of all walls on level 1.
- Count the number of steel columns overall.
Bonus:
Wrap the pipeline in a with statement (using context manager from Day 18 concept) or at minimum add proper error handling for missing files.
Why this matters:
Real IFC models can have hundreds of thousands of elements. Using generators, you can process them without loading everything into memory – a critical skill for BIM data analysis.
6. Brief Review Summary
- Generators (
yield) produce values lazily – memory efficient for large datasets. - Generator expressions
(x for x in iterable)are like lazy list comprehensions. - Chaining generators creates data processing pipelines.
- Infinite generators produce sequences on demand (e.g., beam numbering).
- Iterators and generators are fundamental for traversing large AEC models.
Key takeaway:
Generators allow you to process building models of any size without memory issues. Combined with pipeline chaining, they are a powerful tool for efficient AEC data processing.
7. Preview of Next Topic (Day 17)
Tomorrow we’ll cover Decorators.
You’ll learn:
- How to wrap functions to add behaviour (timing, logging, caching).
- Using
@decoratorsyntax for clean code. - Practical AEC examples: timing analysis runs, caching heavy structural calculations, validating inputs.
- Building a decorator that logs all function calls for audit trails.
Decorators are a key Python feature for writing clean, reusable cross‑cutting logic.

Comments
Post a Comment