How do you find an item in an array in Swift? Let’s find out! In this tutorial you’ll learn how to use the various generic functions to find matching items in an array.
We’ll get into:
first(where:)
Ready? Let’s go.
Before we dive into Swift’s ways you can find items in an array, let’s find out how such an algorithm actually works.
This will definitely help your understanding of algorithms and iOS development in general. When you’re learning iOS development, it’s a great idea to set aside some time to learn how commonly used algorithms work.
The challenge is simple: given an array of an arbitrary number of items, find the index of a given value. So, if we have an array of names, your job is to find the index for a particular name. Where’s the name in the array?
Here’s our approach at a glance:
Here’s what that looks like in Swift code:
[sandbox]
let names = [“Ford”, “Arthur”, “Trillian”, “Zaphod”, “Marvin”, “Deep Thought”, “Eddie”, “Slartibartfast”, “Humma Kuvula”]
let searchValue = “Zaphod”
var currentIndex = 0
for name in names
{
if name == searchValue {
print(“Found \(name) for index \(currentIndex)”)
break
}
currentIndex += 1
}
[/sandbox]
This is what happens at the top of the above code:
names
of type [String]
, which contains a few namessearchValue
, this is the name we’re looking forcurrentIndex
, which keeps track of the current index inside the loopThen, we’re using a for loop to loop over every name in the names
array, with for name in names { ···
. Inside the loop, this happens:
name
is equal to searchValue
. It compares the string values, and evaluates to true
if they are the same.break
. If we don’t do that, the loop will continue, even though we’ve already found what we’re looking for.currentIndex
by one. This way we’re keeping track of the current array index, which goes from 0
to the end of the array.Quite simple, right? The code just loops over every item in the array until it finds what we’re looking for.
The time complexity of the above algorithm is O(n), or linear time, because in the worst case scenario we’re looping over every n items once to find what we’re looking for. The time needed to search the array increases linearly with the number of items in the array. This is about as good as it’s going to get for a collection that doesn’t use a hashmap. If you need a test membership in O(1), check out Set.
We can actually save ourselves some trouble by slightly changing the for loop. And lets turn the code into a Swift function, too.
func find(value searchValue: String, in array: [String]) -> Int?
{
for (index, value) in array.enumerated()
{
if value == searchValue {
return index
}
}
return nil
}
In the above function we’re using the enumerated()
function to get a sequence of index-value pairs. This way we can directly get the index
of a particular value
, without needing to keep track of the current index ourselves.
And the function directly returns an index when it has found a value, which will stop the loop. When no value could be found, the function returns nil
. As such, the return type of the function is Int?
. (And note that we’re using function argument labels, too.)
You’d use the function like this:
let index = find(value: "Eddie", in: names)
print(index)
// Output: Optional(6)
Awesome! Now that we know how the algorithm works, let’s see what Swift functions we can use to find an item in an array.
The easiest approach to find an item in an array is with the firstIndex(of:)
function. Here’s how:
if let index = names.firstIndex(of: "Marvin") {
print(index) // Output: 4
}
You call the firstIndex(of:)
function on the array you want to search. This function is a generic, so it can be used regardless of the array’s type.
Let’s say we’re tired of Zaphod, and we want to replace him with someone else. Here’s how:
if let index = names.firstIndex(of: "Zaphod") {
names[index] = "Prostetnic Vogon Jeltz"
}
In the above code, the index of the value "Zaphod"
is first searched for in the names
array. When found, that index is used to replace the value with another string. (Although I have no idea why you’d want a Vogon on your team, unless you like bureaucracy, highways and/or poetry.)
Because the firstIndex(of:)
function searches an array from front to back, it’ll always return the index of the first match it finds. If you want to get the last match in an array, you can use the lastIndex(of:)
function.
What if you don’t know exactly what you’re looking for? That’s where the firstIndex(where:)
function comes in. This function takes a closure, a so-called predicate, as a parameter to search the array for matches.
Here’s an example:
[sandbox]
let grades = [8, 9, 10, 1, 2, 5, 3, 4, 8, 8]
if let index = grades.firstIndex(where: { $0 < 7 }) {
print(“The first index < 7 = \(index)”)
}
[/sandbox]
In the above code we’re working with a grades
array of integers. We want to know the index of the first array item that’s below 7. We don’t know the exact number we’re looking for, only that it needs to be less than 7.
That’s what we use firstIndex(where: { $0 < 7 })
for. The code between the squiggly brackets is a closure. This closure is executed for every item of grades
, and we’ve found a successful match when it returns true
.
Let’s break that down:
$0 < 7
is a boolean expression that returns true
or false
. The $0
is shorthand for the first parameter of the closure, i.e. the value in the array that we’re inspecting.8 < 7
. This is false
, so the function moves on to the next item.1 < 7
, which is true
. Because that’s a match, the firstIndex(where:)
function returns the index of this item, which is 3
.And we’re using optional binding to get the non-optional value from firstIndex(where:)
if it doesn’t return nil
.
The function firstIndex(where:)
belongs to a group of functions that can select elements from an array that match a given predicate, including:
firstIndex(where:)
returns the index of the first item matching a predicatelastIndex(where:)
returns the index of the last item matching a predicatecontains(where:)
returns a boolean indicating if the array contains an element matching a predicateallSatisfy(_:)
returns a boolean indicating if all of the items in the array match a predicatefirst(where:)
returns the first item (not an index) of the array that matches a predicatelast(where:)
returns the last item (not an index) of the array that matches a predicateKeep in mind that if you want to use the above functions, the items in your array must conform to the Equatable
protocol. Types that conform to this protocol can be compared with the ==
operator, which is used to check if two items are equal.
And your code gets pretty cool, pretty quickly from there. You can use any kind of comparison in the where
closure. Things like:
$0.contains("Q")
$0.name == "Dave"
300...599 ~= $0
Let’s move on!
The syntax for functions like firstIndex(where:)
is similar to Swift’s higher-order functions map(_:)
, reduce(_:_:)
and filter(_:)
.
What if you want to find all items in an array that match a given predicate? The Swift Standard Library does not yet have a simple function for that, but we can of course make our own!
Let’s have a look. Here’s the all(where:)
function:
extension Array where Element: Equatable {
func all(where predicate: (Element) -> Bool) -> [Element] {
return self.compactMap { predicate($0) ? $0 : nil }
}
}
The all(where:)
function is an extension for the Array type, with a generic type constraint that says that this extension only applies for array elements that conform to the Equatable
protocol. In other words, you can only use all(where:)
for array items that can be compared with the ==
operator – just like the other firstIndex(where:)
functions.
The all(where:)
function declaration defines a predicate
parameter of closure type (Element) -> Bool
, and it returns an array of type [Element]
. This is exactly the same as the other functions.
Inside the function, we’re using a function called compactMap(_:). This function essentially calls a closure on every item in an array (i.e., “map”) and also removes a value when it is nil
(i.e., “filter”).
The expression predicate($0) ? $0 : nil
will return the value $0
when the predicate closure returns true
, and nil
when it returns false
. Differently said, the closure will only let values pass for which predicate($0)
is true
, i.e. only array items that match the given condition are returned. And that’s exactly what we want!
Here’s how you use it:
let grades = [8, 9, 10, 1, 2, 5, 3, 4, 8, 8]
let goodGrades = grades.all(where: { $0 > 7 })
print(goodGrades) // Output: [8, 9, 10, 8, 8]
Awesome!
The Swift programming language lets us do programmatic somersaults, cartwheels and saltos – with just a few lines of code. In this tutorial, we’ve begun to understand how these algorithms work, and then applied them to find different bits of data in arrays. Great work!
Want to learn more? Check out these resources: