Learn how to iterate over two arrays like a PRO in Swift using zip

Overview

Having to iterate over two arrays sequentially is pretty common in programming. Although I have to say, it’s extremely annoying if you don’t know what you’re doing!

When iterating over two arrays sequentially you have to take into account which array is shorter (because we only traverse the length of the shorter array or else you will reach memory in the ether) and you have to deal with correctly accessing and grouping elements in their correct pairs. Not only that, but you have to use some nasty looking loop structure to take care of all of this followed by conditionals that make your code look 🤮 .

What if there was a better way?

Thankfully, there is. By the time you’re done reading this post, you’ll know how to iterate over two arrays at the same time without loops and see some practical example code to take your array testing from zero to hero! Let’s get started 👨‍🔬


What the Zip?

Instead of using loops and manually walking down sequences, we can use a built in Swift function called zip. Zip takes care of the dirty work for us by taking two sequences and returning a sequence of pairs (where the ith pair contains the ith elements) built from the two sequences. We can see this from the zip interface ⬇️

/// Creates a sequence of pairs built out of two underlying sequences.
///
/// In the `Zip2Sequence` instance returned by this function, the elements of
/// the *i*th pair are the *i*th elements of each underlying sequence. 
/// If the two sequences passed to `zip(_:_:)` are different lengths, the
/// resulting sequence is the same length as the shorter sequence. 
/// - Parameters:
///   - sequence1: The first sequence or collection to zip.
///   - sequence2: The second sequence or collection to zip.
/// - Returns: A sequence of tuple pairs, where the elements of each pair are
///   corresponding elements of `sequence1` and `sequence2`.
@inlinable public func zip<Sequence1, Sequence2>(_ sequence1: Sequence1, _ sequence2: Sequence2) -> Zip2Sequence<Sequence1, Sequence2> where Sequence1 : Sequence, Sequence2 : Sequence

In the awesome interface comments, we can see that zip takes care of the issues that we have when the sequences are two different lengths! Zip will automatically create a sequence that is the length of the shorter sequence! What a game-changer!

It’s also important to note that zip returns a Zip2Sequence. So if you’re expecting to store this as an array type we need to do a little bit of work. To fix this, all we need to do is place the returned sequence from zip in an Array init or we can use map as seen bellow ⬇️

var zippedArray = Array(zip(sequence1, sequence2)) 
zippedArr = zip(sequence1, sequence2).map { $0 }

Let’s see a real world example of using zip in code!


Zipping for the first time

Let’s look at a practical example of using zip in real life. Let’s say we’re writing a function that takes in two arrays (where the first array stores the monthly cost for the property i at index i and the second stores the monthly rent for the property i at index i) and is expected to return a boolean array where the value at index i is true if the property cashflows and false if it doesn’t. A property is considered cash flowing if the monthly rent exceeds the monthly expenses (rent > expenses).

This situation sounds like a perfect use for zip because we’re iterating over two sequences and comparing the i'th values from both sequences.

Let’s see how easy this is to do with zip ⬇️

func doesCashflow(expenses: [Double], rent: [Double]) -> [Bool] {
    zip(expenses, rent).map { $0 < $1 }
}

Here’s how I would do it without zip 🤮

func doesCashflow(expenses: [Double], rent: [Double]) -> [Bool] {
    return (0..<min(expenses.count, rent.count)).map { expenses[$0] < rent[$0] }
}

Notice that I have to add a subtle check to make sure that I don’t exceed the bounds of allocated memory by taking the min of the lengths of both inputted arrays. zip does this for us without any extra work. You also have to directly access the values in the array by index. I try not to do this as much as possible as it makes it easier to read for my teammates if they don’t have to “decode” how I’m getting a value.

Pretty neat, right? Let’s take a look at how I recently used zip to write beautiful unit tests for functions that returned arrays (yes I know testing🤮 but just like veggies it’s good for ya!)


Using zip for testing

Last week, I was given the task of testing a function that fires very important analytics for our product owners. The goal is to make sure that the analytics fired from the function are as expected and are fired in the correct order.

I first mocked the firing of our analytics by storing them in an array instead of sending them to our data lake. As a result, when the function call is over, the analytics would be sequentially ordered from their firing time in the array. Now, all I have to do is compare them to an expected analytics ordering and test to make sure that the analytics were fired in the right order. Sounds like a great time to use zip since we will be comparing equivalent indices in two different sequences! Let’s take a look at how I did it ⬇️

struct Analytic {
    var name: String
    var dateFired: Date
}
// checks to make sure analytic names are fired in the correct order
func assertCorrectAnalytics(for analytics: [Analytic], expected: [Analytic]) {
    XCTAssertEqual(analytics.count, expected.count)
    zip(analytics, expected).map { XCTAssertEqual($0.name, $1.name)}
}

Just like that, all my for loops were gone! How awesome!

Note that I also check to make sure the counts are the same length. This is because zip will only create a sequence of min length. As a result, if you didn’t check the lengths, you might have a sequence of length 3 and one of length 4 that have the same leading elements but wouldn’t fail the test because zip doesn’t include the 4th element!


Wrap up

Today we learned how to use an awesome built-in Swift function called zip. Zip allows us to iterate over two sequences sequentially without having to access the elements using index pointers. As a result, your code is much safer as out-of-bounds errors will not creep into your code!

I also showed you a real-life example of how I used zip to handle testing analytics at my job. So, I promise you, what you’re reading is important and will come in handy at least once in your swift career and you will thank me for writing about it. I really believe that zip is one of those under-appreciated functions that not enough people know, but now you aren’t one of those people! 🎉

Let me know in the comments ⬇️ if you knew about zip and how you used it or plan to use it in an upcoming project (or even interview)! Also make sure to share this post with your friends that might not know about this awesome function!

As always, if you liked today’s content, make sure you subscribe to the newsletter down below and if you want to support my coffee addiction, help me out by buying me a coffee! It keeps me going to create more AWESOME FREE CONTENT FOR YOU! As always, thanks for taking the time to unwrap some bytes with me. Cheers! 🍻

Processing…
Success! You're on the list.

Shoutout Tomas Sobek on Unsplash for the awesome photo!

Leave A Comment