List Comprehension
Next Topic(s):
Created:
22nd of September 2024
06:34:20 AM
Modified:
10th of September 2025
09:32:31 AM
Understanding List Comprehension in Python
List comprehension is a powerful feature in Python that allows you to create and manipulate lists in a concise and readable way. For civil engineers, list comprehension can significantly simplify tasks involving data analysis, such as manipulating force tables, rainfall data, survey results, and even GIS and GPS datasets. List comprehension enables the transformation and filtering of large data sets with minimal code.
Features of List Comprehension
- Concise Syntax: Create new lists in one line, instead of multiple loop lines.
- Readable: Clear intent for simple transforms and filters.
- Efficient: Often faster than manual loops, useful for larger civil datasets.
- Flexible: Handles filtering, aggregation, and transformation succinctly.
Trivia: List comprehension is inspired by set-builder notation in mathematics, letting you describe results, not steps.
Flowchart: Using List Comprehension in Python
flowchart TD A(["Start"]) --> B["Define Source Data"] B --> C["Apply Condition or Transformation"] C --> D["Store in List"] D --> E(["End"])
Sequence Diagram: Working with List Comprehension
sequenceDiagram participant User participant Data as Source Data User->>Data: Define source data (list, table, etc.) User->>List: Apply list comprehension List-->>User: Create new list with transformations User->>List: Print or store the final list List-->>User: Display the updated list
Working with List Comprehension in Python
- Basic Syntax:
[expression for item in iterable if condition]
- Components:
- Expression: The transform or value to output.
- Item: The current element being processed.
- Iterable: The sequence to iterate (list, range, etc.).
- Condition (optional): Filter to include only some items.
Example and loop equivalents:
# Basic list comprehension: squares 1..5
squares = [x**2 for x in range(1, 6)]
print(squares) # [1, 4, 9, 16, 25]
# For-loop equivalent
squares = []
for x in range(1, 6):
squares.append(x**2)
print(squares)
# While-loop equivalent
squares = []
x = 1
while x <= 5:
squares.append(x**2)
x += 1
print(squares)
Adding a Condition
# Only even squares (1..10)
even_squares = [x**2 for x in range(1, 11) if x % 2 == 0]
print(even_squares) # [4, 16, 36, 64, 100]
# For-loop equivalent
even_squares = []
for x in range(1, 11):
if x % 2 == 0:
even_squares.append(x**2)
print(even_squares)
# While-loop equivalent
even_squares = []
x = 1
while x <= 10:
if x % 2 == 0:
even_squares.append(x**2)
x += 1
print(even_squares)
Tip: Use the if
clause to eliminate unwanted elements at source.
Nested List Comprehension (Flatten a Matrix)
# Flatten a 3x3 matrix
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = [num for row in matrix for num in row]
print(flattened) # [1, 2, 3, 4, 5, 6, 7, 8, 9]
# Nested for-loops equivalent
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = []
for row in matrix:
for num in row:
flattened.append(num)
print(flattened)
# While-loops equivalent
matrix = [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
flattened = []
i = 0
while i < len(matrix):
j = 0
while j < len(matrix[i]):
flattened.append(matrix[i][j])
j += 1
i += 1
print(flattened)
Examples (Civil Engineering Context)
Example 1: Factor of Safety from a Force Table
# Forces (kN) and safety factors
forces = [50, 100, 150, 200, 250]
factors_of_safety = [1.5, 1.6, 1.7, 1.8, 2.0]
safe_loads = [force / factor for force, factor in zip(forces, factors_of_safety)]
print("Safe loads (kN):", safe_loads)
# For-loop equivalent
forces = [50, 100, 150, 200, 250]
factors_of_safety = [1.5, 1.6, 1.7, 1.8, 2.0]
safe_loads = []
for force, factor in zip(forces, factors_of_safety):
safe_loads.append(force / factor)
print("Safe loads (kN):", safe_loads)
Example 2: Filter Rainfall > 30 mm
rainfall_data = [10, 50, 3, 20, 0, 75, 100, 15, 5, 80, 25, 35, 0, 45]
heavy_rainfall_days = [r for r in rainfall_data if r > 30]
print("Days with heavy rainfall (> 30 mm):", heavy_rainfall_days)
# For-loop equivalent
rainfall_data = [10, 50, 3, 20, 0, 75, 100, 15, 5, 80, 25, 35, 0, 45]
heavy_rainfall_days = []
for r in rainfall_data:
if r > 30:
heavy_rainfall_days.append(r)
print("Days with heavy rainfall (> 30 mm):", heavy_rainfall_days)
# While-loop equivalent
rainfall_data = [10, 50, 3, 20, 0, 75, 100, 15, 5, 80, 25, 35, 0, 45]
heavy_rainfall_days = []
i = 0
while i < len(rainfall_data):
if rainfall_data[i] > 30:
heavy_rainfall_days.append(rainfall_data[i])
i += 1
print("Days with heavy rainfall (> 30 mm):", heavy_rainfall_days)
Example 3: Analysing Survey Data (Building Heights)
# Survey heights (m)
building_heights = [15, 25, 30, 12, 18, 20, 25, 50, 45]
# Taller than 20 m
tall_buildings = [h for h in building_heights if h > 20]
print("Buildings taller than 20 m:", tall_buildings)
# For-loop equivalent
building_heights = [15, 25, 30, 12, 18, 20, 25, 50, 45]
tall_buildings = []
for h in building_heights:
if h > 20:
tall_buildings.append(h)
print("Buildings taller than 20 m:", tall_buildings)
Example 4: Format GIS Coordinates
# GPS coordinates (lat, lon)
coordinates = [(12.9716, 77.5946), (28.7041, 77.1025), (19.0760, 72.8777)]
formatted = [f"Lat: {lat:.2f}, Long: {lon:.2f}" for lat, lon in coordinates]
print("Formatted GIS Coordinates:", formatted)
# For-loop equivalent
coordinates = [(12.9716, 77.5946), (28.7041, 77.1025), (19.0760, 72.8777)]
formatted = []
for lat, lon in coordinates:
formatted.append(f"Lat: {lat:.2f}, Long: {lon:.2f}")
print("Formatted GIS Coordinates:", formatted)
Example 5: Pollution Category
pollution_levels = [30, 80, 50, 120, 90, 70, 110]
pollution_category = ["High" if x > 50 else "Low" for x in pollution_levels]
print("Pollution Categories:", pollution_category)
# For-loop equivalent
pollution_levels = [30, 80, 50, 120, 90, 70, 110]
pollution_category = []
for x in pollution_levels:
pollution_category.append("High" if x > 50 else "Low")
print("Pollution Categories:", pollution_category)
Iterators That Cut Computation (Short-Circuiting)
# Generator with next(): stops on first match
rainfall_data = [10, 22, 0, 81, 12, 95, 18]
first_heavy = next((r for r in rainfall_data if r > 80), None)
print(first_heavy) # 81
# For-loop with early break
rainfall_data = [10, 22, 0, 81, 12, 95, 18]
first_heavy = None
for r in rainfall_data:
if r > 80:
first_heavy = r
break
print(first_heavy)
# While-loop with early break
rainfall_data = [10, 22, 0, 81, 12, 95, 18]
first_heavy = None
i = 0
while i < len(rainfall_data):
if rainfall_data[i] > 80:
first_heavy = rainfall_data[i]
break
i += 1
print(first_heavy)
Why generators help: Values are produced lazily; next()
, any()
, all()
can stop scanning early, saving time and memory.
Lists as Matrices (quick primer)
# 3x3 stiffness-like matrix
K = [
[12, -6, 0],
[-6, 8, -2],
[ 0, -2, 5],
]
# Access, then row-wise sum via comprehension
print(K[1][2]) # -2
row_sums = [sum(row) for row in K]
print(row_sums) # [6, 0, 3]
# Construct common matrices
n = 3
Z = [[0 for _ in range(n)] for _ in range(n)] # zeros
I = [[1 if i == j else 0 for j in range(n)] for i in range(n)] # identity
print(Z)
print(I)
Colour Data Primer for Remote Sensing & JPEG Handling
Colour spaces used in satellite/remote-sensing workflows:
- RGB / RGBA: Red-Green-Blue (8-bit/channel typical); A = alpha (opacity).
- HEX: Web hex form of RGB, e.g.,
#228B22
(ForestGreen). - HSL / HSV: Hue-Saturation-Lightness/Value; convenient for hue-based masking (e.g., green cover).
- YCbCr: Luma + chroma (internal to JPEG); libraries convert automatically.
- CMYK: Print-oriented; less common for analysis.
- CIELAB (L*a*b*): Perceptual; good for colour clustering and distance.
- Greyscale: Single-channel luminance; useful for masks/thresholds.
Rule of thumb: Store/visualise in RGB, threshold in HSV/HSL, cluster in LAB. JPEGs are lossy and typically 8-bit/channel.
Representing & Converting Colours
# Simple colour representations
rgb = (34, 139, 34) # ForestGreen as RGB
hex_code = "#228B22" # HEX
# HEX <-> RGB helpers
def hex_to_rgb(h):
h = h.lstrip("#")
return tuple(int(h[i:i+2], 16) for i in (0, 2, 4))
def rgb_to_hex(r, g, b):
return "#{:02X}{:02X}{:02X}".format(r, g, b)
print(hex_to_rgb(hex_code)) # (34, 139, 34)
print(rgb_to_hex(*rgb)) # #228B22
# RGB <-> HSL / HSV using coloursys (values in 0..1)
import colorsys
r, g, b = (c / 255 for c in (34, 139, 34)) # normalise
h, l, s = colorsys.rgb_to_hls(r, g, b) # HSL
h2, s2, v2 = colorsys.rgb_to_hsv(r, g, b) # HSV
print("HSL:", (h, l, s))
print("HSV:", (h2, s2, v2))
JPEG: Read & Make a Green-Cover Mask (HSV)
# Requires Pillow: pip install pillow
from PIL import Image
import colorsys
# 1) Open a JPEG and ensure RGB
img = Image.open("satellite.jpg").convert("RGB")
pixels = list(img.getdata()) # [(R,G,B), ...]
# 2) Mask green areas by HSV hue band (approx. 80°..160°)
def is_green(rgb):
r, g, b = (c / 255 for c in rgb)
h, s, v = colorsys.rgb_to_hsv(r, g, b)
return (80/360) <= h <= (160/360) and s > 0.2 and v > 0.2
green_mask = [1 if is_green(p) else 0 for p in pixels] # comprehension
green_pct = 100 * sum(green_mask) / len(green_mask)
print(f"Estimated green cover: {green_pct:.2f}%")
# For-loop equivalent (same logic)
from PIL import Image
import colorsys
img = Image.open("satellite.jpg").convert("RGB")
pixels = list(img.getdata())
def is_green(rgb):
r, g, b = (c / 255 for c in rgb)
h, s, v = colorsys.rgb_to_hsv(r, g, b)
return (80/360) <= h <= (160/360) and s > 0.2 and v > 0.2
green_mask = []
for p in pixels:
green_mask.append(1 if is_green(p) else 0)
green_pct = 100 * sum(green_mask) / len(green_mask)
print(f"Estimated green cover: {green_pct:.2f}%")
Quick Greyscale & Average Colour
from PIL import Image
img = Image.open("satellite.jpg").convert("RGB")
pixels = list(img.getdata())
# Greyscale by luminosity (manual, 8-bit)
grey = [int(0.299*r + 0.587*g + 0.114*b) for (r, g, b) in pixels]
# Average RGB colour of the image
avg = tuple(sum(ch)//len(pixels) for ch in zip(*pixels)) # (R, G, B)
print("Average RGB:", avg)
Tip: For rigorous pipelines (classification/segmentation), consider NumPy + scikit-image or GeoTIFFs for multi-band data. To start of with understanding image handling and JPEG-only demos, Pillow + list comprehensions are perfect.
Solutions to the Exercises
Solution 1: Average Rainfall (exclude zero days)
rainfall_data = [0, 15, 0, 25, 50, 0, 20]
valid = [r for r in rainfall_data if r > 0]
avg = sum(valid) / len(valid)
print("Average Rainfall (excluding no-rain days):", avg)
Console Output:
Average Rainfall (excluding no-rain days): 27.5
Solution 2: Safe Forces
forces = [500, 600, 750, 800, 900]
factors = [2.0, 1.8, 1.6, 1.5, 1.4]
safe_forces = [F / k for F, k in zip(forces, factors)]
print("Safe Forces:", safe_forces)
Console Output:
Safe Forces: [250.0, 333.3333333333333, 468.75, 533.3333333333334, 642.8571428571429]
Solution 3: Safe Building Heights (< 40 m)
heights = [25, 30, 45, 10, 35, 50, 40]
safe = [h for h in heights if h < 40]
print("Safe Building Heights:", safe)
Console Output:
Safe Building Heights: [25, 30, 10, 35]
Solution 4: Groundwater Parameters to PPM
concentration_mg_per_l = [0.5, 1.0, 1.5, 2.0, 0.8]
concentration_ppm = [c * 1000 for c in concentration_mg_per_l]
print("Concentration in PPM:", concentration_ppm)
Console Output:
Concentration in PPM: [500.0, 1000.0, 1500.0, 2000.0, 800.0]
Solution 5: Filter GPS Coordinates by Longitude 70..80
coordinates = [(12.9716, 77.5946), (28.7041, 77.1025), (19.0760, 72.8777)]
filtered = [c for c in coordinates if 70 <= c[1] <= 80]
print("Filtered Coordinates:", filtered)
Console Output:
Filtered Coordinates: [(12.9716, 77.5946), (28.7041, 77.1025)]
Key Takeaway
List comprehensions make transformations concise; loop equivalents clarify execution; generators help stop early. For colour-based studies (green cover, built-up vs. open land), convert RGB to HSV/HSL for intuitive thresholds, or use LAB for perceptual distance.