Swift Collection Functions
Swift provides a rich set of higher-order functions that allow you to perform operations on collections in a concise and expressive way. These functions enable you to transform, filter, and process elements in collections without writing verbose loops. In this guide, we'll explore the most commonly used collection functions in Swift and how they can simplify your code.
Introduction to Collection Functions
Collection functions in Swift are higher-order functions that operate on collections (arrays, dictionaries, sets) and typically take closures as arguments. These functions make your code more:
- Readable: Express intent clearly
- Concise: Reduce boilerplate code
- Maintainable: Separate what you want to do from how it's done
- Safe: Minimize risks associated with manual indexing
Let's dive into the key collection functions that every Swift developer should know!
Common Collection Functions
map(_:)
The map(_:)
function transforms each element of a collection using a provided closure and returns a new collection with the transformed elements.
// Basic map example
let numbers = [1, 2, 3, 4, 5]
let doubled = numbers.map { $0 * 2 }
print(doubled) // Output: [2, 4, 6, 8, 10]
// Map with strings
let names = ["Alice", "Bob", "Charlie"]
let uppercased = names.map { $0.uppercased() }
print(uppercased) // Output: ["ALICE", "BOB", "CHARLIE"]
filter(_:)
The filter(_:)
function creates a new collection containing only the elements that satisfy a given condition.
// Basic filter example
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]
// Filter with strings
let names = ["Alice", "Bob", "Charlie", "Dave", "Eve"]
let shortNames = names.filter { $0.count <= 3 }
print(shortNames) // Output: ["Bob", "Eve"]
reduce(_:_:)
The reduce(_:_:)
function combines all elements of a collection into a single value using a closure.
// Basic reduce example - sum of numbers
let numbers = [1, 2, 3, 4, 5]
let sum = numbers.reduce(0) { $0 + $1 }
print(sum) // Output: 15
// Reduce with strings - concatenation
let words = ["Swift", "is", "awesome"]
let sentence = words.reduce("") { $0 + ($0.isEmpty ? "" : " ") + $1 }
print(sentence) // Output: "Swift is awesome"
forEach(_:)
The forEach(_:)
function executes a closure for each element in a collection, similar to a for-in loop but more concise.
// Basic forEach example
let numbers = [1, 2, 3, 4, 5]
numbers.forEach { number in
print("\(number) squared is \(number * number)")
}
// Output:
// 1 squared is 1
// 2 squared is 4
// 3 squared is 9
// 4 squared is 16
// 5 squared is 25
compactMap(_:)
The compactMap(_:)
function transforms each element using a closure and returns a new collection with non-nil results, automatically removing any nil values.
// compactMap with optional values
let possibleNumbers = ["1", "2", "three", "4", "five"]
let parsedNumbers = possibleNumbers.compactMap { Int($0) }
print(parsedNumbers) // Output: [1, 2, 4]
// compactMap with optional properties
struct User {
let id: Int
let email: String?
}
let users = [
User(id: 1, email: "[email protected]"),
User(id: 2, email: nil),
User(id: 3, email: "[email protected]")
]
let emails = users.compactMap { $0.email }
print(emails) // Output: ["[email protected]", "[email protected]"]
flatMap(_:)
The flatMap(_:)
function transforms each element into a sequence using a closure and then flattens the resulting sequences into a single collection.
// flatMap with nested arrays
let nestedNumbers = [[1, 2, 3], [4, 5], [6, 7, 8, 9]]
let allNumbers = nestedNumbers.flatMap { $0 }
print(allNumbers) // Output: [1, 2, 3, 4, 5, 6, 7, 8, 9]
// Note: For working with Optionals, use compactMap instead
Chaining Collection Functions
One of the most powerful aspects of collection functions is the ability to chain them together to create complex transformations with clean, readable code.
// Example of chained collection functions
let scores = [
"Alice": [75, 83, 90],
"Bob": [65, 78, 72],
"Charlie": [88, 92, 94]
]
// Get names of students with average score above 80
let highPerformers = scores
.map { (name, scores) in (name, scores.reduce(0, +) / scores.count) }
.filter { $0.1 > 80 }
.map { $0.0 }
print(highPerformers) // Output: ["Alice", "Charlie"]
Real-World Applications
Data Processing in Apps
// Processing data from an API
struct Product {
let id: Int
let name: String
let price: Double
let isAvailable: Bool
}
let products = [
Product(id: 1, name: "Laptop", price: 1299.99, isAvailable: true),
Product(id: 2, name: "Phone", price: 699.99, isAvailable: true),
Product(id: 3, name: "Headphones", price: 149.99, isAvailable: false),
Product(id: 4, name: "Tablet", price: 499.99, isAvailable: true)
]
// Get names of available products cheaper than $700
let affordableProducts = products
.filter { $0.isAvailable && $0.price < 700 }
.map { $0.name }
print(affordableProducts) // Output: ["Phone"]
// Calculate total inventory value
let totalValue = products
.filter { $0.isAvailable }
.reduce(0) { $0 + $1.price }
print("Total inventory value: $\(totalValue)") // Output: Total inventory value: $2499.97
Text Processing
// Count word frequencies in a text
let text = "Swift is a powerful and intuitive programming language for iOS, macOS, watchOS, and tvOS. Swift is friendly to new programmers."
// Convert to lowercase and split into words
let words = text.lowercased()
.components(separatedBy: CharacterSet.punctuationCharacters.union(.whitespaces))
.filter { !$0.isEmpty }
// Count word frequencies
let wordFrequency = words.reduce(into: [:]) { counts, word in
counts[word, default: 0] += 1
}
// Find the most frequent words (those occurring more than once)
let frequentWords = wordFrequency
.filter { $0.value > 1 }
.sorted { $0.value > $1.value }
.map { "\($0.key): \($0.value)" }
print(frequentWords) // Output: ["swift: 2", "is: 2", "and: 2", "for: 2", "programming: 2"]
Performance Considerations
While collection functions provide clean, expressive code, there are performance considerations to keep in mind:
-
Intermediate Collections: Each operation in a chain creates an intermediate collection, which can impact performance for very large datasets.
-
Lazy Collections: For large collections or when you don't need all results immediately, consider using
lazy
:
// Example of lazy collection
let numbers = Array(1...1000)
let result = numbers.lazy
.filter { $0 % 7 == 0 }
.map { $0 * 10 }
.prefix(5)
Array(result) // Only calculates what's needed: [70, 140, 210, 280, 350]
- Multiple Passes: Sometimes a single loop with multiple operations is more efficient than chaining several collection functions:
// Instead of:
let sum = numbers.filter { $0 % 2 == 0 }.map { $0 * 2 }.reduce(0, +)
// Consider:
var sum = 0
for number in numbers {
if number % 2 == 0 {
sum += number * 2
}
}
Summary
Swift's collection functions provide a powerful, expressive way to manipulate collections. By using map
, filter
, reduce
, forEach
, compactMap
, and flatMap
, you can write more concise and readable code that better expresses your intent.
Key takeaways:
map
transforms elementsfilter
selects elementsreduce
combines elementsforEach
executes an action on each elementcompactMap
transforms elements and removes nilsflatMap
flattens nested collections
These functions can be chained together to create complex transformations with minimal code, making your Swift code more elegant and maintainable.
Exercises
-
Create an array of integers and use collection functions to get the sum of squares of all odd numbers.
-
Given an array of strings, use collection functions to create a new array containing only the strings that start with a vowel, converted to uppercase.
-
Create a dictionary with names as keys and ages as values. Use collection functions to find the average age.
-
Given an array of optional strings, use the appropriate collection function to create an array containing only valid strings, with each string reversed.
Additional Resources
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)