Swift Collection Transformations
Collection transformations are powerful operations that allow you to process and modify the contents of collections in Swift. Rather than writing verbose loops to iterate through elements, transformations offer a concise, expressive way to manipulate data. In this lesson, we'll explore the most common collection transformation methods in Swift.
Introduction to Collection Transformations
Swift collections (Arrays, Sets, Dictionaries) include several built-in methods that follow functional programming principles. These methods allow you to:
- Transform each element into something new (
map
) - Filter elements based on conditions (
filter
) - Combine elements into a single result (
reduce
) - And more!
These operations make your code more readable, maintainable, and often more efficient. Let's dive into each one with examples.
The map
Method
The map
method transforms each element in a collection using a closure and returns a new array containing the transformed elements.
Basic Syntax
let transformedCollection = collection.map { element in
// Return transformed element
}
Example: Converting Numbers to Strings
let numbers = [1, 2, 3, 4, 5]
let stringNumbers = numbers.map { String($0) }
print(stringNumbers) // Output: ["1", "2", "3", "4", "5"]
Here, map
transforms each integer into its string representation.
Example: Calculating Prices with Tax
let prices = [10.0, 20.0, 30.0, 40.0]
let taxRate = 0.1
let pricesWithTax = prices.map { price in
return price * (1 + taxRate)
}
print(pricesWithTax) // Output: [11.0, 22.0, 33.0, 44.0]
The filter
Method
The filter
method creates a new collection containing only the elements that satisfy a provided condition.
Basic Syntax
let filteredCollection = collection.filter { element in
// Return true to include element, false to exclude
}
Example: Finding Even Numbers
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let evenNumbers = numbers.filter { $0 % 2 == 0 }
print(evenNumbers) // Output: [2, 4, 6, 8, 10]
Example: Filtering Products by Price
struct Product {
let name: String
let price: Double
}
let inventory = [
Product(name: "Apple", price: 0.99),
Product(name: "Banana", price: 0.59),
Product(name: "Mango", price: 2.99),
Product(name: "Pineapple", price: 3.99)
]
let affordableProducts = inventory.filter { $0.price < 1.00 }
// Output: Products with name "Apple" and "Banana"
The reduce
Method
The reduce
method combines all elements in a collection into a single value.
Basic Syntax
let result = collection.reduce(initialValue) { partialResult, element in
// Combine partialResult and element to form a new partialResult
}
Example: Summing Numbers
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { $0 + $1 }
// Alternatively: let sum = numbers.reduce(0, +)
print(sum) // Output: 15
Example: Creating a Sentence from Words
let words = ["Swift", "is", "a", "powerful", "language"]
let sentence = words.reduce("") { $0 + ($0.isEmpty ? "" : " ") + $1 }
print(sentence) // Output: "Swift is a powerful language"
Chaining Transformations
One of the most powerful aspects of collection transformations is that they can be chained together to perform complex operations succinctly.
Example: Processing a List of Numbers
let numbers = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
let sumOfSquaresOfEvenNumbers = numbers
.filter { $0 % 2 == 0 } // [2, 4, 6, 8, 10]
.map { $0 * $0 } // [4, 16, 36, 64, 100]
.reduce(0, +) // 220
print(sumOfSquaresOfEvenNumbers) // Output: 220
This chain:
- Filters to keep only even numbers
- Maps each number to its square
- Reduces the results to a sum
Additional Collection Transformations
The flatMap
Method
flatMap
flattens a collection of collections into a single collection and transforms elements.
let nestedArrays = [[1, 2], [3, 4], [5, 6]]
let flattened = nestedArrays.flatMap { $0 }
print(flattened) // Output: [1, 2, 3, 4, 5, 6]
The compactMap
Method
compactMap
transforms elements like map
, but also removes any nil
results.
let possibleNumbers = ["1", "2", "three", "4", "five"]
let validNumbers = possibleNumbers.compactMap { Int($0) }
print(validNumbers) // Output: [1, 2, 4]
The forEach
Method
forEach
performs an operation on each element but doesn't return anything.
let numbers = [1, 2, 3, 4, 5]
numbers.forEach { number in
print("Number: \(number)")
}
// Output:
// Number: 1
// Number: 2
// Number: 3
// Number: 4
// Number: 5
Real-World Applications
Example: Processing User Data
Consider an application that needs to process user data:
struct User {
let id: Int
let name: String
let age: Int
let isActive: Bool
}
let users = [
User(id: 1, name: "Alice", age: 28, isActive: true),
User(id: 2, name: "Bob", age: 19, isActive: false),
User(id: 3, name: "Charlie", age: 35, isActive: true),
User(id: 4, name: "Diana", age: 17, isActive: true),
User(id: 5, name: "Edward", age: 42, isActive: false)
]
// Get names of active adults (18+)
let activeAdultNames = users
.filter { $0.isActive && $0.age >= 18 }
.map { $0.name }
print(activeAdultNames) // Output: ["Alice", "Charlie"]
// Get average age of all users
let totalAge = users.reduce(0) { $0 + $1.age }
let averageAge = Double(totalAge) / Double(users.count)
print("Average age: \(averageAge)") // Output: Average age: 28.2
Example: Working with JSON Data
let jsonString = """
[
{"name": "Apple", "price": 0.99, "inStock": true},
{"name": "Orange", "price": 1.29, "inStock": true},
{"name": "Banana", "price": 0.59, "inStock": false},
{"name": "Mango", "price": 2.99, "inStock": true}
]
"""
// Parse JSON
if let data = jsonString.data(using: .utf8),
let jsonArray = try? JSONSerialization.jsonObject(with: data) as? [[String: Any]] {
// Get names of in-stock fruits under $1
let affordableFruits = jsonArray
.filter { ($0["inStock"] as? Bool) == true && ($0["price"] as? Double ?? 0) < 1.0 }
.compactMap { $0["name"] as? String }
print(affordableFruits) // Output: ["Apple"]
}
Performance Considerations
While collection transformations make code more readable, it's important to consider performance implications:
-
Multiple passes: Each transformation creates a new collection, which can be inefficient for large datasets.
-
Eager evaluation: Transformations are evaluated immediately. For large collections, consider using
lazy
for better performance:
let numbers = Array(1...1000000)
let result = numbers.lazy.filter { $0 % 2 == 0 }.map { $0 * 2 }.prefix(10)
print(Array(result)) // Only processes what's needed for the first 10 results
Summary
Swift's collection transformations provide a powerful, expressive way to manipulate data:
- map: Transform each element into something new
- filter: Keep only elements that meet a condition
- reduce: Combine all elements into a single result
- flatMap: Flatten nested collections while transforming
- compactMap: Transform and remove nil values
- forEach: Perform an operation on each element
These methods can be chained together to create concise, readable solutions for complex data processing tasks. By embracing these functional programming concepts, you can write more maintainable and expressive Swift code.
Additional Resources
Exercises
- Use
map
to convert an array of temperatures in Celsius to Fahrenheit. - Use
filter
to find all strings in an array that have more than 5 characters. - Use
reduce
to find the longest string in an array. - Chain transformations to find the sum of squares of all odd numbers in an array.
- Use
compactMap
to extract valid URLs from an array of strings.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)