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
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 { ($0, $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)")
With Swift 3, when you have an object that conforms to Sequence
protocol and you to want to link each element inside of it with its index, you can use enumerated()
method.
For example:
let array = [1, 18, 32, 7]
let enumerateSequence = array.enumerated() // type: EnumerateSequence<[Int]>
let newArray = Array(enumerateSequence)
print(newArray) // prints: [(0, 1), (1, 18), (2, 32), (3, 7)]
let reverseRandomAccessCollection = [1, 18, 32, 7].reversed()
let enumerateSequence = reverseRandomAccessCollection.enumerated() // type: EnumerateSequence<ReverseRandomAccessCollection<[Int]>>
let newArray = Array(enumerateSequence)
print(newArray) // prints: [(0, 7), (1, 32), (2, 18), (3, 1)]
let reverseCollection = "8763".characters.reversed()
let enumerateSequence = reverseCollection.enumerated() // type: EnumerateSequence<ReverseCollection<String.CharacterView>>
let newArray = enumerateSequence.map { ($0.0 + 1, String($0.1) + "A") }
print(newArray) // prints: [(1, "3A"), (2, "6A"), (3, "7A"), (4, "8A")]
Therefore, in the simplest case, you can implement a Luhn algorithm in a Playground like this:
let array = [8, 7, 6, 3]
let reversedArray = array.reversed()
let enumerateSequence = reversedArray.enumerated()
let luhnClosure = { (sum: Int, tuple: (index: Int, value: Int)) -> Int in
let indexIsOdd = tuple.index % 2 == 1
guard indexIsOdd else { return sum + tuple.value }
let newValue = tuple.value == 9 ? 9 : tuple.value * 2 % 9
return sum + newValue
}
let sum = enumerateSequence.reduce(0, luhnClosure)
let bool = sum % 10 == 0
print(bool) // prints: true
If you start from a String
, you can implement it like this:
let characterView = "8763".characters
let mappedArray = characterView.flatMap { Int(String($0)) }
let reversedArray = mappedArray.reversed()
let enumerateSequence = reversedArray.enumerated()
let luhnClosure = { (sum: Int, tuple: (index: Int, value: Int)) -> Int in
let indexIsOdd = tuple.index % 2 == 1
guard indexIsOdd else { return sum + tuple.value }
let newValue = tuple.value == 9 ? 9 : tuple.value * 2 % 9
return sum + newValue
}
let sum = enumerateSequence.reduce(0, luhnClosure)
let bool = sum % 10 == 0
print(bool) // prints: true
If you need to repeat those operations, you can refactor your code into an extension:
extension String {
func luhnCheck() -> Bool {
let characterView = self.characters
let mappedArray = characterView.flatMap { Int(String($0)) }
let reversedArray = mappedArray.reversed()
let enumerateSequence = reversedArray.enumerated()
let luhnClosure = { (sum: Int, tuple: (index: Int, value: Int)) -> Int in
let indexIsOdd = tuple.index % 2 == 1
guard indexIsOdd else { return sum + tuple.value }
let newValue = tuple.value == 9 ? 9 : tuple.value * 2 % 9
return sum + newValue
}
let sum = enumerateSequence.reduce(0, luhnClosure)
return sum % 10 == 0
}
}
let string = "8763"
let luhnBool = string.luhnCheck()
print(luhnBool) // prints: true
Or, in a much concise way:
extension String {
func luhnCheck() -> Bool {
let sum = characters
.flatMap { Int(String($0)) }
.reversed()
.enumerated()
.reduce(0) {
let indexIsOdd = $1.0 % 2 == 1
guard indexIsOdd else { return $0 + $1.1 }
return $0 + ($1.1 == 9 ? 9 : $1.1 * 2 % 9)
}
return sum % 10 == 0
}
}
let string = "8763"
let luhnBool = string.luhnCheck()
print(luhnBool) // prints: true
In addition to Nate Cook's example of map
, you can also apply this behavior to reduce
.
let numbers = [1,2,3,4,5]
let indexedNumbers = reduce(numbers, [:]) { (memo, enumerated) -> [Int: Int] in
return memo[enumerated.index] = enumerated.element
}
// [0: 1, 1: 2, 2: 3, 3: 4, 4: 5]
Note that the EnumerateSequence
passed into the closure as enumerated
cannot be decomposed in a nested fashion, thus the members of the tuple must be decomposed inside the closure (ie. enumerated.index
).
This is a working CollectionType extension for swift 2.1 using throws and rethrows:
extension CollectionType {
func map<T>(@noescape transform: (Self.Index, Self.Generator.Element) throws -> T) rethrows -> [T] {
return try zip((self.startIndex ..< self.endIndex), self).map(transform)
}
}
I know this is not what you were asking, but solves your issue. You can try this swift 2.0 Luhn method without extending anything:
func luhn(string: String) -> Bool {
var sum = 0
for (idx, value) in string.characters.reverse().map( { Int(String($0))! }).enumerate() {
sum += ((idx % 2 == 1) ? (value == 9 ? 9 : (value * 2) % 9) : value)
}
return sum > 0 ? sum % 10 == 0 : false
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With