Advanced Array Operations
Advanced Array Functions
So far we have been doing all of our work on arrays based on elements and their location in an array. But, what if we don't know where an element is?
First
There will be times when you want to get an element from an array, but you don't where it is. Instead, you may know what particular field of that element is, or a property of that element. In this case, we create an anonymous function that will return true if the given element satisfies some criteria and false otherwise. This function will be applied to every element in the list, in order, until an element meets the requirement. Execution then stops and the element is returned. If no element in the list fulfills the criteria, the result will be nil
. For this, we use first(where predicate: (Element) throws -> bool) rethrows -> Element?
. This is one scary function declaration–let's pick it apart. first
takes one argument of type (Element) throws -> bool
. This argument takes in our element and has the option to throw an error, and otherwise, must return a boolean. We can ignore the error because this is a pretty niche case. Rethrows just means, that the function takes in a function that can throw an error, and if that is the case, it may just throw the error itself, so again, we can ignore this. first(where: (Element) -> bool) -> element
this is a lot easier to tackle. Okay so let's put it to use.
When you use this, of course, you would want to just use a property instead of the entire instance because then what's the point of getting it out the list if you already have it. So maybe we have something more like:
There are other cases where it may actually be more helpful to get the location in the list instead of the actual element. This is the exact same process, except instead of using first(_:)
we use firstIndex(_:)
.
If a list stores an equatable type–meaning that it conforms to Equatable
we can simplify this slightly–but just slightly. Instead of use firstIndex(where:)
, we can use firstIndex(of: Element)
. As the function header suggests, instead of creating a closure to determine criteria, we create an item and use equality for criteria. You can think of this as:
You'll notice that weird where
–this is a slight exposure of Array's implementation. Arrays conform to a protocol with an associated type called Element. This is an advanced technique that you do not need to understand–although if you're interested see Protocols with Associated Types. To understand this block of code, think of Array, again, as a generic with the parametric type named Element. When this type conforms to equatable (when we have access to the == function), we will provide another function that takes in an element and applies it, with ==, to first(where:)
. Hopefully, the code is a little easier to understand than my explanation.
Last
This is exactly the same as First
, but it returns the last element that fulfills the critera–so execution never stops short of the length of the list (i.e. it is always exactly O(n)
). For last, just change the function name from first to last. So, firstIndex
becomes lastIndex
and etc. I'm not going to re-explain everything here, so if you're curious about a function look at the previous section.
RemoveAll
Well there is removeAll()
, which quite literally removes everything. But more helpfully, there is removeAll(where shouldBeRemoved: (Element) throws -> Bool) rethrows
. Hmmm... This looks awfully familiar. Referring back to our decomposition of first
, we can simplify this function by throw
ing out the error handling: removeAll(where: (Element) -> Bool)
. So this function takes in another function that takes in an argument and returns a bool. Aka we are taking in a function that will either determine if that element should be removed or not and just applies that to every element in a list.
Going back to our Person
data structure:
Contains
If we want to see if an array contains an element that fulfills some critera, we use the contains
function. This is exactly like first, last, everything we've talked about so far. Here's the header: contains(where predicate: (Element) throws -> Bool) rethrows
. Additionally, if Element
conforms to Equatable
, we can use contains(_: Element) -> Bool
. SeefirstIndex(of: Element)
for an explanation.
Map
Map is a common function that maps from one list to another. The idea is that you apply a function that transforms an element into an element of another kind. This function is then applied to everything in a list, so the entire list is transformed. Maps type is map(_ transform: (Element) throws -> T) rethrows [T]
. We can re-imagine map
as:
Or a more simple version:
Filter
Filter does pretty much exactly what you expect it's going to do. It goes through the array, checks criteria, and keeps elements that pass the critera.
Reduce
Reduce will iterate through the list and will return some form of accumulation. OCaml programmers think fold_left
and fold_right
. Reduce takes in an initial state and a function. Its accumulator starts at the initial state. Then it iterates through the list, applies the function to the accumulator and the next element in the list, and stores the result in the accumulator. It continues until the list is done. Think of reduce as:
Mixing map, filter, and reduce can feel so cool–while potentially inefficient. Let's try to do it. Say I have a list of words and integers in string form. I want to see the sum of even numbers in the list. Here's one way to do it:
A few notes:
Int(_: String)
is an optional initializer.I can forceunwrap here because I already checked that I could transform the elements into an integer
And here it is as a one-liner
That's quite a long one-liner because of the optional... Let's fix that!
Compact Map
compactMap
is exactly like map, except if result is nil
, it will automatically be filtered!
With this new tool, let's try that one-linear again!
Last updated