Day 11 – List Comprehensions, Lambda, map/filter


 

⚡ Day 11 – List Comprehensions, Lambda, map/filter

1. Learning Objectives

By the end of Day 11, you will be able to:

  • Write concise list comprehensions to create and filter lists in a single line.
  • Use dictionary comprehensions and set comprehensions for similar tasks.
  • Create anonymous functions with lambda.
  • Apply map() and filter() for functional‑style data processing.
  • Choose between comprehensions and map/filter for AEC tasks (e.g., filtering beams longer than 6 m, converting all lengths to mm, extracting unique material names).

2. Concept Explanation

2.1 List Comprehensions

A list comprehension provides a compact way to create a list by applying an expression to each item in an iterable, optionally with a filtering condition.

Syntax:

[expression for item in iterable if condition]

Traditional loop vs. comprehension:

# Traditional loop
squares = []
for x in range(10):
    squares.append(x**2)

# List comprehension (one line)
squares = [x**2 for x in range(10)]

With condition:

# Only even squares
even_squares = [x**2 for x in range(10) if x % 2 == 0]

Why comprehensions?

  • Readable – expresses intent clearly.
  • Faster – executes at C speed internally (important for large AEC datasets).
  • Pythonic – the preferred style in professional code.

2.2 Dictionary and Set Comprehensions

# Dictionary comprehension: {key_expression: value_expression for item in iterable}
beam_moments = {b["mark"]: b["load"] * b["span"]**2 / 8 for b in beams}

# Set comprehension: {expression for item in iterable}
unique_materials = {b["material"].lower() for b in beams if "material" in b}

2.3 Lambda Functions

A lambda is a small anonymous function defined in one line: lambda arguments: expression

# Regular function
def double(x):
    return x * 2

# Lambda equivalent
double = lambda x: x * 2

Use cases:

  • Short, throw‑away functions passed to map(), filter(), sorted(), etc.
  • Key functions for sorting: sorted(beams, key=lambda b: b["span"])

Limitations:

  • Can only contain a single expression (no statements, loops, or return).
  • Overuse reduces readability – if logic is complex, use a def.

2.4 map() and filter()

map(function, iterable) – applies a function to every item, returns an iterator.

filter(function, iterable) – keeps items where function returns True, returns an iterator.

# map: convert list of strings to floats
span_strings = ["6.0", "4.5", "7.2"]
spans = list(map(float, span_strings))   # [6.0, 4.5, 7.2]

# filter: keep spans > 6.0
long_spans = list(filter(lambda s: s > 6.0, spans))   # [7.2]

Comprehensions vs. map/filter – usually comprehensions are more readable and Pythonic. Use map/filter when you already have a named function (e.g., map(str.strip, lines)).


3. Code Examples

Example 1: Beam span filtering – comprehension vs. loop

all_spans = [6.0, 4.5, 7.2, 5.0, 8.1, 3.5]

# Traditional loop
long_spans_loop = []
for s in all_spans:
    if s > 6.0:
        long_spans_loop.append(s)

# List comprehension (preferred)
long_spans_comp = [s for s in all_spans if s > 6.0]

print(f"Spans > 6.0m: {long_spans_comp}")

Example 2: Applying unit conversion with map and lambda

# Convert a list of lengths from metres to millimetres
lengths_m = [3.5, 4.0, 6.0, 2.8]
lengths_mm = list(map(lambda x: x * 1000, lengths_m))
print(f"In mm: {lengths_mm}")   # [3500.0, 4000.0, 6000.0, 2800.0]

# Using comprehension (cleaner)
lengths_mm2 = [x * 1000 for x in lengths_m]

Example 3: Dictionary of beam moments with comprehension

beams = [
    {"mark": "B1", "span": 6.0, "load": 25},
    {"mark": "B2", "span": 4.5, "load": 18},
    {"mark": "B3", "span": 7.2, "load": 30},
]

# Dictionary comprehension: mark -> max moment (kNm)
moments = {b["mark"]: b["load"] * b["span"]**2 / 8 for b in beams}
print(moments)   # {'B1': 112.5, 'B2': 45.5625, 'B3': 194.4}

Example 4: Nested comprehension – column grid coordinates

# Generate all (x,y) coordinates for a 3x4 grid
x_spacing, y_spacing = 6.0, 8.0
columns = [(col * x_spacing, row * y_spacing) for row in range(3) for col in range(4)]
print(columns)
# [(0.0, 0.0), (6.0, 0.0), (12.0, 0.0), (18.0, 0.0),
#  (0.0, 8.0), (6.0, 8.0), (12.0, 8.0), (18.0, 8.0),
#  (0.0, 16.0), (6.0, 16.0), (12.0, 16.0), (18.0, 16.0)]

Example 5: filter() with lambda for material validation

materials = ["Concrete", "Steel", "Wood", "Aluminium", "Timber"]
valid_materials = {"concrete", "steel", "timber", "masonry"}

# Filter valid materials (case‑insensitive)
approved = list(filter(lambda m: m.lower() in valid_materials, materials))
print(approved)   # ['Concrete', 'Steel', 'Timber']

# Same with comprehension
approved2 = [m for m in materials if m.lower() in valid_materials]

4. Hands‑on Exercises (3–5 Problems)

Problem 1 – Filter short spans
Given spans = [6.0, 4.5, 7.2, 3.0, 5.5, 8.1], use a list comprehension to create a list of spans that are less than 5.0 m. Print the result.

Problem 2 – Convert weights
Given a list of beam weights in kg/m: weights_kgpm = [60, 80, 50, 100, 75]
Convert to kN/m (multiply by 0.00980665) using map() and lambda. Print the result.

Problem 3 – Material name cleaning
You have a list of raw material names:
raw_materials = [" Concrete ", "STEEL", " timber ", "Aluminium"]
Use a list comprehension to .strip().lower() each item. Print the cleaned list.

Problem 4 – Dictionary of volumes
Given a list of room dimensions as dictionaries:

rooms = [
    {"name": "Lobby", "length": 10.0, "width": 8.0, "height": 4.0},
    {"name": "Office A", "length": 6.0, "width": 5.0, "height": 3.0},
    {"name": "Meeting", "length": 8.0, "width": 6.0, "height": 3.5},
]

Use a dictionary comprehension to create a mapping room_name → volume. Print the result.

Problem 5 – Extract unique floor usages
Given the storeys list from Day 6:

storeys = [
    ("Level 1", 4.5, "Retail"),
    ("Level 2", 3.5, "Office"),
    ("Level 3", 3.5, "Office"),
    ("Level 4", 3.5, "Office"),
    ("Level 5", 3.0, "MEP"),
    ("Level 6", 3.0, "Roof Terrace")
]

Use a set comprehension to extract all unique usages. Print the set.

Solutions (attempt first):

# P1
spans = [6.0, 4.5, 7.2, 3.0, 5.5, 8.1]
short = [s for s in spans if s < 5.0]
print(short)   # [4.5, 3.0]

# P2
weights_kgpm = [60, 80, 50, 100, 75]
weights_kNm = list(map(lambda w: w * 0.00980665, weights_kgpm))
print(weights_kNm)   # [0.5884, 0.7845, 0.4903, 0.9807, 0.7355]

# P3
raw_materials = ["  Concrete  ", "STEEL", "  timber  ", "Aluminium"]
cleaned = [m.strip().lower() for m in raw_materials]
print(cleaned)   # ['concrete', 'steel', 'timber', 'aluminium']

# P4
rooms = [
    {"name": "Lobby", "length": 10.0, "width": 8.0, "height": 4.0},
    {"name": "Office A", "length": 6.0, "width": 5.0, "height": 3.0},
    {"name": "Meeting", "length": 8.0, "width": 6.0, "height": 3.5},
]
volumes = {r["name"]: r["length"] * r["width"] * r["height"] for r in rooms}
print(volumes)   # {'Lobby': 320.0, 'Office A': 90.0, 'Meeting': 168.0}

# P5
storeys = [
    ("Level 1", 4.5, "Retail"),
    ("Level 2", 3.5, "Office"),
    ("Level 3", 3.5, "Office"),
    ("Level 4", 3.5, "Office"),
    ("Level 5", 3.0, "MEP"),
    ("Level 6", 3.0, "Roof Terrace")
]
unique_usages = {usage for _, _, usage in storeys}
print(unique_usages)   # {'Retail', 'Office', 'MEP', 'Roof Terrace'}

5. Applied Challenge Task

Task: Structural Member Data Processor with Comprehensions

You are given a list of steel beam dictionaries:

beams = [
    {"mark": "B1", "span": 6.0, "load": 25, "fy": 250},
    {"mark": "B2", "span": 4.5, "load": 18, "fy": 250},
    {"mark": "B3", "span": 7.2, "load": 30, "fy": 355},
    {"mark": "B4", "span": 5.0, "load": 22, "fy": 250},
    {"mark": "B5", "span": 8.0, "load": 35, "fy": 355},
]

Write a script that uses comprehensions (list, dict, or set) and lambda/map/filter wherever possible to:

  1. Create a list of beam marks for beams with span > 6.0m.
  2. Create a dictionary mapping mark → max_moment (use M = load * span² / 8).
  3. Compute the required section modulus S_req = M * 1e6 / (0.6 * fy) (in mm³) for each beam using map().
  4. Filter out beams that would require S_req > 1_000_000 mm³ (too heavy). Print their marks.
  5. Find the total steel weight assuming each beam weighs 80 kg/m. Use map() and sum().
  6. Extract the set of unique yield strengths (fy) used.

Why this matters:
Comprehensions and functional tools let you process entire datasets (hundreds of members) in just a few lines – the hallmark of efficient AEC scripting.


6. Brief Review Summary

  • List comprehensions: [expr for item in iterable if condition] – concise, fast, Pythonic.
  • Dict comprehensions: {k: v for item in iterable} – build dictionaries in one line.
  • Set comprehensions: {expr for item in iterable} – unique items.
  • Lambda: single‑expression anonymous function: lambda args: expr.
  • map(func, iterable) – transform each element.
  • filter(func, iterable) – keep elements where function returns True.
  • Prefer comprehensions over map/filter for readability, unless a named function is already available.

Key takeaway:
These tools let you write expressive, high‑performance data‑processing code. For AEC, they are invaluable when analysing lists of beams, columns, rooms, or materials – turning multi‑line loops into single, clear expressions.


7. Preview of Next Topic (Day 12)

Tomorrow we’ll cover Modules, Packages, and Virtual Environments.
You’ll learn:

  • Organising your AEC functions into reusable modules (.py files).
  • Creating a package with __init__.py (e.g., aec_utils package).
  • Using import to bring in your own code and third‑party libraries.
  • Setting up virtual environments to manage dependencies for different projects.
  • Practical example: building a package for AEC unit conversions and section properties that you can reuse across projects.

Modular code is the foundation of professional software engineering in AEC.

Comments

EARN AT THE COMFORT OF YOUR HOME

Sponsored content

Popular posts from this blog

Day 5 – Functions, Scope, and Docstrings

Day 10 – Exception Handling and Debugging

Day 1 – Python Foundations for AEC Professionals