Lesson 50Closures

Closures Practice

Module Complete!

Time to practice everything you've learned about closures. Complete these exercises to solidify your understanding!

Exercises Overview

1. Basic Closures

Create closure expressions

2. Trailing Closures

Use clean trailing syntax

3. Filter & Map

Chain transformations

4. Reduce

Combine values

5. Higher-Order

Create custom functions

6. Capturing

Value capture patterns

7. @escaping

Async closure handling

8. CompactMap

Handle optionals

9. Sorting

Custom sort logic

10. Lazy

Performance optimization

What You've Learned

  • Closure syntax and shorthand forms
  • Trailing closure syntax
  • Capturing values (by reference & value)
  • @escaping and @autoclosure
  • map, filter, reduce, compactMap
  • Best practices and memory management

Next Up: Structs!

In the next module, you'll learn about Structs - Swift's powerful value types for organizing data and behavior!

practice.swift
// CLOSURES PRACTICE - PUTTING IT ALL TOGETHER!

// ===== EXERCISE 1: Basic Closures =====
// Create a closure that takes two Ints and returns their average

let average: (Int, Int) -> Double = { a, b in
    Double(a + b) / 2.0
}
print(average(10, 20))  // 15.0

// ===== EXERCISE 2: Trailing Closures =====
// Use map with trailing closure to square each number

let numbers = [1, 2, 3, 4, 5]
let squared = numbers.map { $0 * $0 }
print(squared)  // [1, 4, 9, 16, 25]

// ===== EXERCISE 3: Filter & Map Chain =====
// Get names of adults (age >= 18) in uppercase

struct Person {
    let name: String
    let age: Int
}

let people = [
    Person(name: "Alice", age: 25),
    Person(name: "Bob", age: 15),
    Person(name: "Charlie", age: 30),
    Person(name: "Diana", age: 12)
]

let adultNames = people
    .filter { $0.age >= 18 }
    .map { $0.name.uppercased() }
print(adultNames)  // ["ALICE", "CHARLIE"]

// ===== EXERCISE 4: Reduce =====
// Calculate total price of in-stock items

struct Item {
    let name: String
    let price: Double
    let inStock: Bool
}

let inventory = [
    Item(name: "Laptop", price: 999.99, inStock: true),
    Item(name: "Mouse", price: 29.99, inStock: true),
    Item(name: "Keyboard", price: 79.99, inStock: false),
    Item(name: "Monitor", price: 299.99, inStock: true)
]

let inStockTotal = inventory
    .filter { $0.inStock }
    .reduce(0.0) { $0 + $1.price }
print("In-stock total: $\(inStockTotal)")  // $1329.97

// ===== EXERCISE 5: Custom Higher-Order Function =====
// Create a function that applies a transformation n times

func applyNTimes<T>(_ value: T, times: Int, transform: (T) -> T) -> T {
    var result = value
    for _ in 0..<times {
        result = transform(result)
    }
    return result
}

let doubled = applyNTimes(2, times: 4) { $0 * 2 }
print(doubled)  // 32 (2 -> 4 -> 8 -> 16 -> 32)

// ===== EXERCISE 6: Capturing Values =====
// Create a counter factory

func makeCounter(startAt: Int = 0, step: Int = 1) -> () -> Int {
    var count = startAt
    return {
        count += step
        return count
    }
}

let counter = makeCounter()
print(counter())  // 1
print(counter())  // 2
print(counter())  // 3

let tenCounter = makeCounter(startAt: 10, step: 5)
print(tenCounter())  // 15
print(tenCounter())  // 20

// ===== EXERCISE 7: Escaping Closure =====
// Simulate an async data loader

class DataLoader {
    var completionHandlers: [(String) -> Void] = []

    func loadData(completion: @escaping (String) -> Void) {
        completionHandlers.append(completion)
    }

    func finishLoading(with data: String) {
        for handler in completionHandlers {
            handler(data)
        }
        completionHandlers.removeAll()
    }
}

let loader = DataLoader()
loader.loadData { data in
    print("Received: \(data)")
}
loader.finishLoading(with: "Hello, World!")

// ===== EXERCISE 8: CompactMap =====
// Parse valid integers from mixed array

let mixedStrings = ["1", "two", "3", "four", "5", "6"]
let validNumbers = mixedStrings.compactMap { Int($0) }
print(validNumbers)  // [1, 3, 5, 6]

// ===== EXERCISE 9: Sorting with Closures =====
// Sort by multiple criteria

struct Student {
    let name: String
    let grade: Int
    let age: Int
}

let students = [
    Student(name: "Alice", grade: 90, age: 20),
    Student(name: "Bob", grade: 85, age: 22),
    Student(name: "Charlie", grade: 90, age: 19),
    Student(name: "Diana", grade: 85, age: 21)
]

// Sort by grade (descending), then by age (ascending)
let sorted = students.sorted {
    if $0.grade != $1.grade {
        return $0.grade > $1.grade
    }
    return $0.age < $1.age
}
sorted.forEach { print("\($0.name): \($0.grade), \($0.age)") }

// ===== EXERCISE 10: Lazy Evaluation =====
// Demonstrate lazy vs eager evaluation

let largeArray = Array(1...1000000)

// Eager - processes all elements
let eagerResult = largeArray.filter { $0 % 2 == 0 }.map { $0 * 2 }.prefix(5)

// Lazy - processes only what's needed
let lazyResult = largeArray.lazy.filter { $0 % 2 == 0 }.map { $0 * 2 }.prefix(5)
print(Array(lazyResult))  // [4, 8, 12, 16, 20]

print("\nšŸŽ‰ Congratulations! You've completed the Closures module!")

Challenge!

Create a function pipeline that takes an array of transformations [(Int) -> Int] and returns a closure that applies them all in sequence!