VPython MapReduceFilter

From Physics Book
Revision as of 19:13, 26 April 2026 by Sabrinayang (talk | contribs)
Jump to navigation Jump to search

Sabrina Yang - Spring 2026

Introduction

In Python, map(), filter(), and reduce() are known as higher-order functions. Higher-order functions are functions that take other functions as arguments. These functions are especially valuable. A typical simulation can include hundreds of objects and variables that each operate under the same physics laws. Instead of writing for loops to iterate through each object, map(), filter(), and reduce() handles the iteration automatically, which makes the code shorter and more efficient.

The three functions each serve a its own role:

  • map(): transforms every element in a list by applying a function to it
  • filter(): selects a subset of elements that satisfy a condition
  • reduce(): aggregates all elements into a single accumulated value

Important note for Python 3: map() and filter() return lazy iterator objects, in other words objects that don't do any work until you ask it for the results, rather than lists. This means they do not compute anything until you actually need the values. Wrap them in list() to force evaluation and get a plain list back. reduce() was removed from Python 3's built-ins and must be explicitly imported:

from functools import reduce

Important note for GlowScript VPython: GlowScript runs a restricted subset of Python and does not support lambda expressions or the functools module. Use named def functions instead of lambdas, and implement reduce() manually as shown in the interactive simulation section below.


Background: Functional Programming

To fully understand map(), filter(), and reduce(), you need to understand first-class functions. In Python, all functions are first class objects, meaning they can be:

  • Assigned to variables
  • Passed as arguments to other functions
  • Returned as values from other functions

This is what makes higher-order functions possible. When you write map(calc_weight, masses), you are passing the function calc_weight itself as an argument to map(). map() then calls calc_weight for each element.


Lambda Expressions

A lambda expression is a function you write in one line without giving it a name. The syntax is:

lambda <parameters>: <expression>

The function exists only at the point where it is used. These two definitions produce the same results:

# Traditional way
def square(x):
    return x**2

# Using lambda
square = lambda x: x**2

Lambdas save you from having to define a whole separate function when you only need it once:

# Without lambda: requires a function defined somewhere else
result = list(map(square, [1, 2, 3, 4]))

# With lambda: no defined function needed
result = list(map(lambda x: x**2, [1, 2, 3, 4]))
# Result: [1, 4, 9, 16]

Lambda expressions can also take multiple parameters, which is useful for reduce():

from functools import reduce
total = reduce(lambda acc, x: acc + x, [1, 2, 3, 4], 0)
# Result: 10

When to use lambdas vs def:

  • Use a lambda when the function is short, used only once, and

passed directly as an argument

  • Use def when the function is longer than one line or reused constantly

GlowScript limitation: Lambda expressions are not supported in GlowScript VPython. Always use named def functions when writing code for Trinket or GlowScript.


Inputs and Type Matching

All three functions share the same basic calling convention:

higher_order_function(function, iterable)

The function argument describes the operation to perform. The iterable argument is the sequence of data to operate on, which can be a list, tuple, range, or any other iterable Python object.

One important constraint is type matching: the function must be able to accept the types contained in the iterable. For example, if your list contains floating-point numbers, your function must expect floats. Mismatches will cause a TypeError at runtime.

Example using a named function:

def cubed(x):
    return x**3

items = [1, 2, 3, 4]
result = list(map(cubed, items))
# Result: [1, 8, 27, 64]

The same using a lambda:

items = [1, 2, 3, 4]
result = list(map(lambda x: x**3, items))
# Result: [1, 8, 27, 64]

You can also pass Python's built-in functions directly:

words = ['hello', 'world', 'vpython']
lengths = list(map(len, words))
# Result: [5, 5, 7]

Map()

How it works

map(function, iterable) applies a function to every element of an iterable and returns an iterator of the results. The original list is never modified. Instead, a new sequence of transformed values is produced. The output always has the same number of elements as the input.

map(function, iterable)

For a list [a, b, c, d] and a function f:

map(f, [a, b, c, d])  →  [f(a), f(b), f(c), f(d)]

Basic example

numlist = [1, 2, 3, 4, 5]
result = list(map(lambda x: x * 2, numlist))
# Result: [2, 4, 6, 8, 10]

Examples using Physics

Computing gravitational weight (F = mg) for a set of masses:

g = 9.8  # m/s^2
masses = [0.5, 1.0, 2.5, 5.0, 10.0]  # kg

weights = list(map(lambda m: m * g, masses))
# Result: [4.9, 9.8, 24.5, 49.0, 98.0]  Newtons

Computing kinetic energy (KE = ½mv²) for a set of velocities:

mass = 2.0   # kg
velocities = [3.0, 5.5, 2.1, 8.0]  # m/s

ke_list = list(map(lambda v: 0.5 * mass * v**2, velocities))
# Result: [9.0, 30.25, 4.41, 64.0]  Joules

Converting a list of temperatures from Celsius to Kelvin:

temps_C = [0, 20, 37, 100, -273.15]
temps_K = list(map(lambda T: T + 273.15, temps_C))
# Result: [273.15, 293.15, 310.15, 373.15, 0.0]  Kelvin

Why use map() instead of a for-loop?

Both of the following produce the same result, but map() is shorter, and expresses the point more directly:

# For-loop approach
weights = []
for m in masses:
    weights.append(m * 9.8)

# map() approach — same result, fewer lines
weights = list(map(lambda m: m * 9.8, masses))

Filter()

How it works

filter(function, iterable) keeps only the elements for which the function returns True. The function must return a boolean value (or a value Python treats as truthy or falsy). The output list may be shorter than the input list.

filter(function, iterable)
filter(lambda x: condition, iterable)

Conceptually, for a list [a, b, c, d] and a predicate function p:

filter(p, [a, b, c, d])  →  [x for x in [a,b,c,d] if p(x) is True]

Basic example

numbers = [3, 7, 5, 2, 1, 6]
result = list(filter(lambda x: x > 3, numbers))
# Result: [7, 5, 6]

Physics examples

Isolating particles moving above a speed threshold:

speeds = [120, 340, 95, 500, 210, 80]  # m/s

fast_particles = list(filter(lambda v: v > 200, speeds))
# Result: [340, 500, 210]

Keeping only positive charges from a mixed list:

charges = [-1.6e-19, 1.6e-19, -3.2e-19, 3.2e-19, 0, 1.6e-19]  # Coulombs

positive = list(filter(lambda q: q > 0, charges))
# Result: [1.6e-19, 3.2e-19, 1.6e-19]

Filtering out particles that have left the simulation boundary:

# Each particle p has a .pos.x attribute representing its x position
boundary = 10.0  # meters

inside = list(filter(lambda p: abs(p.pos.x) < boundary, particles))

Passing None as the function

A special case: passing None instead of a function removes all false values from the list (zeros, empty strings, None, False):

messy = [1, 0, 3, None, 5, 0, 7]
clean = list(filter(None, messy))
# Result: [1, 3, 5, 7]

Reduce()

How it works

reduce(function, iterable, initializer) cumulatively applies a two-argument function to the elements of a list, reducing the entire sequence down to a single scalar value.

The function passed to reduce() must accept exactly two arguments:

  • The accumulator — the running result
  • The current element — the next item from the list

After each call, the return value becomes the new accumulator for the next call.

from functools import reduce
reduce(function, iterable, initializer)

Step-by-step walkthrough

from functools import reduce

numbers = [1, 2, 3, 4]
result = reduce(lambda x, y: x * y, numbers)
# Step 1: x=1, y=2  →  1 * 2 = 2
# Step 2: x=2, y=3  →  2 * 3 = 6
# Step 3: x=6, y=4  →  6 * 4 = 24
# Final result: 24

Physics examples

Adding all masses in a system:

from functools import reduce

masses = [1.0, 2.0, 3.0, 4.0]  # kg
total_mass = reduce(lambda acc, m: acc + m, masses, 0.0)
# Result: 10.0 kg

Finding the maximum speed in a list of particles:

from functools import reduce

speeds = [3.2, 7.8, 1.1, 9.4, 5.5]  # m/s
max_speed = reduce(lambda a, b: a if a > b else b, speeds)
# Result: 9.4 m/s

Computing total work done by forces over different displacements (W = F·d):

from functools import reduce

forces = [10.0, 25.0, 5.0, 40.0]       # Newtons
displacements = [2.0, 1.5, 3.0, 0.5]   # meters

work_list = list(map(lambda fd: fd[0] * fd[1], zip(forces, displacements)))
total_work = reduce(lambda acc, w: acc + w, work_list, 0.0)
# Result: 20.0 + 37.5 + 15.0 + 20.0 = 92.5 Joules

Important warning

Always provide an initializer (third argument) when using reduce(). Calling it on an empty list without an initializer raises a TypeError. With an initializer, an empty list returns the initializer value:

reduce(lambda acc, x: acc + x, [], 0.0)
# Returns 0.0 safely instead of raising an error

Combining map(), filter(), and reduce()

The power of these functions come from chaining them together into a data pipeline. Each function's output becomes the next function's input, creating a clean sequence of transformation → selection → aggregation.

Example — total kinetic energy of only the fast-moving particles in a system:

from functools import reduce

# Step 1: filter() — only keep particles with a speed greater than 3 m/s
moving = list(filter(lambda p: p.speed > 3.0, particles))

# Step 2: map() — calculate the kinetic energy for each particle
ke_list = list(map(lambda p: 0.5 * p.mass * p.speed**2, moving))

# Step 3: reduce() — add all the kinetic energies
total_ke = reduce(lambda acc, ke: acc + ke, ke_list, 0.0)
print("Total KE of fast particles:", round(total_ke, 2), "J")

This three-step pattern (filter, map, reduce) is one of the most widely used patterns in programming.



Interactive Simulation

The following GlowScript simulation demonstrates all three functions working together in a physics context: map(), filter(), and reduce() in VPython Physics — Trinket

The simulation creates five spheres arranged in a horizontal row, each assigned a different mass (ranging from 1 to 8 kg) and a different speed (ranging from 1.5 to 6 m/s). The radius of each sphere is proportional to its mass.

The program then demonstrates each function in sequence:

  • map() iterates over the list of masses and computes the gravitational weight

of each sphere using Newton's second law (F = mg, with g = 9.8 m/s²). The results are printed to the console, showing the weight in Newtons for each sphere.

  • filter() checks every sphere's speed and keeps only those moving faster than

3.0 m/s. Those spheres are turned red in the 3D scene, providing a visual indicator of which objects meet the threshold condition. The speeds of the selected spheres are also printed.

  • reduce() — implemented manually as a custom function since GlowScript does

not support the functools module. It adds the kinetic energy of every sphere into a single total. The individual kinetic energies and the system total are printed to the console.

Together, the simulation illustrates the filter → map → reduce pipeline in a physical setting, showing how the three functions complement each other.


References

1. Python map, filter, reduce — bogotobogo.com

2. Map, Filter — Python Tips

3. VPython Documentation — vpython.org

4. Python 3 Built-in Functions (map, filter) — Python Software Foundation

5. functools module (reduce) — Python Software Foundation

6. Functional Programming HOWTO — Python Software Foundation

7. Python's map() — Real Python

8. Python's filter() — Real Python

9. Python's reduce() — Real Python

10. Lambda Expressions in Python — Real Python

11. Functional Programming in Python — GeeksforGeeks

12. Higher-Order Functions in Python — GeeksforGeeks

13. GlowScript VPython — Official Site

14. GlowScript on Trinket

15. VPython Official Documentation