You can use enumerate to convert a sequence (Array, String, etc.) to a sequence of tuples with an integer counter and and element paired together. That is:
let numbers = [7, 8, 9, 10]
let indexAndNum: [String] = numbers.enumerate().map { (index, element) in
return "\(index): \(element)"
}
print(indexAndNum)
// ["0: 7", "1: 8", "2: 9", "3: 10"]
Link to enumerate definition
Note that this isn't the same as getting the index of the collection—enumerate gives you back an integer counter. This is the same as the index for an array, but on a string or dictionary won't be very useful. To get the actual index along with each element, you can use zip:
let actualIndexAndNum: [String] = zip(numbers.indices, numbers).map { "\($0): \($1)" }
print(actualIndexAndNum)
// ["0: 7", "1: 8", "2: 9", "3: 10"]
When using an enumerated sequence with reduce, you won't be able to separate the index and element in a tuple, since you already have the accumulating/current tuple in the method signature. Instead, you'll need to use .0 and .1 on the second parameter to your reduce closure:
let summedProducts = numbers.enumerate().reduce(0) { (accumulate, current) in
return accumulate + current.0 * current.1
// ^ ^
// index element
}
print(summedProducts) // 56
Swift 3.0 and above
Since Swift 3.0 syntax is quite different.
Also, you can use short-syntax/inline to map array on dictionary:
let numbers = [7, 8, 9, 10]
let array: [(Int, Int)] = numbers.enumerated().map { (
1) }
// ^ ^
// index element
That produces:
[(0, 7), (1, 8), (2, 9), (3, 10)]
You can use enumerate to convert a sequence (Array, String, etc.) to a sequence of tuples with an integer counter and and element paired together. That is:
let numbers = [7, 8, 9, 10]
let indexAndNum: [String] = numbers.enumerate().map { (index, element) in
return "\(index): \(element)"
}
print(indexAndNum)
// ["0: 7", "1: 8", "2: 9", "3: 10"]
Link to enumerate definition
Note that this isn't the same as getting the index of the collection—enumerate gives you back an integer counter. This is the same as the index for an array, but on a string or dictionary won't be very useful. To get the actual index along with each element, you can use zip:
let actualIndexAndNum: [String] = zip(numbers.indices, numbers).map { "\($0): \($1)" }
print(actualIndexAndNum)
// ["0: 7", "1: 8", "2: 9", "3: 10"]
When using an enumerated sequence with reduce, you won't be able to separate the index and element in a tuple, since you already have the accumulating/current tuple in the method signature. Instead, you'll need to use .0 and .1 on the second parameter to your reduce closure:
let summedProducts = numbers.enumerate().reduce(0) { (accumulate, current) in
return accumulate + current.0 * current.1
// ^ ^
// index element
}
print(summedProducts) // 56
Swift 3.0 and above
Since Swift 3.0 syntax is quite different.
Also, you can use short-syntax/inline to map array on dictionary:
let numbers = [7, 8, 9, 10]
let array: [(Int, Int)] = numbers.enumerated().map { (
1) }
// ^ ^
// index element
That produces:
[(0, 7), (1, 8), (2, 9), (3, 10)]
For Swift 2.1 I wrote next function:
extension Array {
public func mapWithIndex<T> (f: (Int, Element) -> T) -> [T] {
return zip((self.startIndex ..< self.endIndex), self).map(f)
}
}
And then use it like this:
let numbers = [7, 8, 9, 10]
let numbersWithIndex: [String] = numbers.mapWithIndex { (index, number) -> String in
return "\(index): \(number)"
}
print("Numbers: \(numbersWithIndex)")
Videos
Let's start with a single array, like:
Copylet raceResult = ["one", "two", "four"]
If we want to combine each element with an offset counting from 0, we can use Array.enumerated(), along with map.
Copylet numberedRaceResult = raceResult
.enumerated()
.map { offset, element in "\(offset). \(element)" }
for numberedResult in numberedRaceResult {
print(numberedResult)
}
// Prints:
// 0. one
// 1. two
// 2. four
You can see that I didn't call print inside the closure passed to map. You can do this, but it kind of defeats the purpose of map (which is to create an equal-sized output array from the transformed elements of the input array), because the result would be unused. In that case, it makes more sense to just use a for loop or a call to forEach, like @Sh_Khan showed.
To handle a nested array, it's much the same. We can use the same logic as for one array, but apply it to each sub-array.
Copylet raceResults = [
["one", "two", "four"],
["two", "one", "five", "six"],
["two", "one", "four", "ten"],
["one", "two", "four"],
]
let numberedRaceResults = raceResults
.enumerated()
.flatMap { outterOffset, raceResult in
raceResult
.enumerated()
.map { innerOffset, element in "\(outterOffset).\(innerOffset). \(element)" }
}
for numberedResult in numberedRaceResults {
print(numberedResult)
}
// Prints:
// 0.0. one
// 0.1. two
// 0.2. four
// 1.0. two
// 1.1. one
// 1.2. five
// 1.3. six
// 2.0. two
// 2.1. one
// 2.2. four
// 2.3. ten
// 3.0. one
// 3.1. two
// 3.2. four
You'll notice that I used flatMap on the outter array, instead of a simple map. You can change it back and forth and compare the result. In short, flatMap gives you a single flat array of string results, rather than an array of sub-arrays of strings.
Map is used to convert one bunch of type T into things of some other type, X. Like map these Ints to String?s. You should not use map for side-effects, like printing the values, or updating a database etc. It should be a pure function that takes an input and returns an output. "Map these A's into B's". Pure meaning the value of the function only depends on the input, nothing else like the current state of the world, and doesn't change the world either (like printing to the console), so for example, map these int's by the function that adds 2 to them.
In your example:
Copyvar raceResults = [["one","two","four"],["two","one","five","six"],["two","one","four","ten"],["one","two","four"]]
You have an array of "arrays of strings".
You can map that to an array of so long as you have a function that takes "array of string" and turns that into "something else"
Here you map it with the Identity function, the function that just returns its input, which is going to take an array of strings as input and return the exact same array of strings as output:
Copy raceResults.map {
return $0 // Returns first array
}
This does nothing, and the result is the exact same thing as raceResults.
If you want to iterate over all these elements then the function flatMap is handy:
CopyraceResults.flatMap { $0 }.forEach { print($0) }
flatMap is flatten, then map. Flattening an array of arrays is to return an array with all the things 'flattened' one level, so [[1, 2, 3], [4, 5, 6]] -> [1, 2, 3, 4, 5, 6], but the definition of what to flatten means depends on the type of container, for example flatMap on Optional means something else to flatMap on Array.
You can filter the indices of the array directly, it avoids the extra mapping.
let items = ["A", "B", "A", "C", "A", "D"]
let filteredIndices = items.indices.filter {items[$0] == "A"}
or as Array extension:
extension Array where Element: Equatable {
func whatFunction(_ value : Element) -> [Int] {
return self.indices.filter {self[$0] == value}
}
}
items.whatFunction("A") // -> [0, 2, 4]
items.whatFunction("B") // -> [1]
or still more generic
extension Collection where Element: Equatable {
func whatFunction(_ value : Element) -> [Index] {
return self.indices.filter {self[$0] == value}
}
}
You can create your own extension for arrays.
extension Array where Element: Equatable {
func indexes(of element: Element) -> [Int] {
return self.enumerated().filter({ element == $0.element }).map({ $0.offset })
}
}
You can simply call it like this
items.indexes(of: "A") // [0, 2, 4]
items.indexes(of: "B") // [1]
Here's one way of doing this:
print(
productsList
.enumerated()
.filter { $0.offset % 2 == 1 }
.map { $0.element.name }
)
The enumerated method turns each product into a 2-tuple that contains both the product and its index in the array (offset). You then filter to leave only the products that have an odd (or even) index. After that, you need to map the 2-tuples to the name of the product.
You can map the odd indices to the corresponding product name:
let oddIndexedProducts = stride(from: 1, through: productsList.count, by: 2)
.map { productsList[$0].name }
print(oddIndexedProducts) // ["BBB", "DD", "EEE", "DSF", "GFDHT"]
Another way is to use compactMap on the enumerated() sequence:
let oddIndexedProducts = productsList.enumerated().compactMap {
$0.offset % 2 == 1 ? $0.element.name : nil
}
As a rule of thumb: “filter + map = compactMap” (“flatMap” in Swift 3).
Hi everyone,
I am currently going through the tutorial to code the Scrumdinger Application which is an apple developer tutorial. I am struggling to understand what this highlighted line of code means and the code seems quite abstract although I have come across some bits like this before, and used it but not really understood what it meant.
To paint you a picture of what's involved, the speakers array contains speakers which each have a Boolean property and also an id which is a UUID. I have also tried to work out what the 'firstIndex(where:..) bit of code is doing. Pulling up its overview gives me this -
I'm not really sure what the predicate is in this case and what throwing a bool means, or even equatable. If you guys could help me gain a better understanding, maybe by saying what this Scrumdinger code is doing and what this Summary means in a few sentences in English, I would be very grateful. Also if you happen to know any applications or tutorials in which this type of code is used very regularly, or even Youtube tutorials, or just a key overarching topic name this would come under, I would embrace practicing this a lot to gain a better understanding.
I want to be able to take an array of items where each item has a Bool property, and condense it into an array that only contains the items where the Bool property is set to true.
However, I also want to be able to indicate which index in the original array that I want to start at. So, it would start at the index that I give, and put any items after that index into the new array first, but then go back to the start of the original array, and include the items that came before the index that I gave as well.
Here is an example that I just made in a Playground to demonstrate...
import Foundation
struct Animal {
let name: String
let hasLegs: Bool
}
struct AnimalsWithLegs {
let animals: [Animal]
init(allAnimals: [Animal], startingAt index: Int = 0) {
//Need help here
animals = allAnimals.compactMap { animal in
if animal.hasLegs {
return animal
} else {
return nil
}
}
}
}
let animals = [
Animal(name: "Dog", hasLegs: true),
Animal(name: "Fish", hasLegs: false),
Animal(name: "Cat", hasLegs: true),
Animal(name: "Monkey", hasLegs: true),
Animal(name: "Whale", hasLegs: false),
Animal(name: "Turtle", hasLegs: true),
Animal(name: "Shark", hasLegs: false),
Animal(name: "Fox", hasLegs: true)
]
let animalsWithLegs = AnimalsWithLegs(allAnimals: animals, startingAt: 4)
for animal in animalsWithLegs.animals {
print(animal.name)
}Currently, this example will print "Dog, Cat, Monkey, Turtle, Fox"
Those are all of the animals with legs from the array, so that's great.
However, since I gave a startingAt index of 4 when I called the AnimalsWithLegs() initializer, I would actually like it to print "Turtle, Fox, Dog, Cat, Monkey" instead.
How can I modify the AnimalsWithLegs() initializer to get this result?
As swift is in some regards more functional than object-oriented (and Arrays are structs, not objects), use the function "find" to operate on the array, which returns an optional value, so be prepared to handle a nil value:
let arr:Array = ["a","b","c"]
find(arr, "c")! // 2
find(arr, "d") // nil
Use firstIndex and lastIndex - depending on whether you are looking for the first or last index of the item:
let arr = ["a","b","c","a"]
let indexOfA = arr.firstIndex(of: "a") // 0
let indexOfB = arr.lastIndex(of: "a") // 3
tl;dr:
For classes, you might be looking for:
let index = someArray.firstIndex{$0 === someObject}
Full answer:
I think it's worth mentioning that with reference types (class) you might want to perform an identity comparison, in which case you just need to use the === identity operator in the predicate closure:
Swift 5, Swift 4.2:
let person1 = Person(name: "John")
let person2 = Person(name: "Sue")
let person3 = Person(name: "Maria")
let person4 = Person(name: "Loner")
let people = [person1, person2, person3]
let indexOfPerson1 = people.firstIndex{$0 === person1} // 0
let indexOfPerson2 = people.firstIndex{$0 === person2} // 1
let indexOfPerson3 = people.firstIndex{$0 === person3} // 2
let indexOfPerson4 = people.firstIndex{$0 === person4} // nil
Note that the above syntax uses trailing closures syntax, and is equivalent to:
let indexOfPerson1 = people.firstIndex(where: {$0 === person1})
Swift 4 / Swift 3 - the function used to be called index
Swift 2 - the function used to be called indexOf
* Note the relevant and useful comment by paulbailey about class types that implement Equatable, where you need to consider whether you should be comparing using === (identity operator) or == (equality operator). If you decide to match using ==, then you can simply use the method suggested by others (people.firstIndex(of: person1)).