Mapa o reducir con índice en Swift

143

¿Hay alguna manera de obtener el índice de la matriz en mapo reduceen Swift? Estoy buscando algo como each_with_indexen Ruby.

func lunhCheck(number : String) -> Bool
{
    var odd = true;
    return reverse(number).map { String($0).toInt()! }.reduce(0) {
        odd = !odd
        return $0 + (odd ? ($1 == 9 ? 9 : ($1 * 2) % 9) : $1)
    }  % 10 == 0
}

lunhCheck("49927398716")
lunhCheck("49927398717")

Me gustaría deshacerme de la oddvariable anterior .

Jonas Elfström
fuente

Respuestas:

313

Se puede utilizar enumeratepara convertir una secuencia ( Array, String, etc.) a una secuencia de tuplas con un contador de números enteros y y el elemento emparejado juntos. Es decir:

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"]

Enlace a la enumeratedefinición

Tenga en cuenta que esto no es lo mismo que obtener el índice de la colección: enumeratele devuelve un contador entero. Esto es lo mismo que el índice para una matriz, pero en una cadena o diccionario no será muy útil. Para obtener el índice real junto con cada elemento, puede usar zip:

let actualIndexAndNum: [String] = zip(numbers.indices, numbers).map { "\($0): \($1)" }
print(actualIndexAndNum)
// ["0: 7", "1: 8", "2: 9", "3: 10"]

Al usar una secuencia enumerada con reduce, no podrá separar el índice y el elemento en una tupla, ya que ya tiene la tupla acumulada / actual en la firma del método. En su lugar, deberá usar .0y .1en el segundo parámetro para su reducecierre:

let summedProducts = numbers.enumerate().reduce(0) { (accumulate, current) in
    return accumulate + current.0 * current.1
    //                          ^           ^
    //                        index      element
}
print(summedProducts)   // 56

Swift 3.0 y superior

Desde la sintaxis de Swift 3.0 es bastante diferente.
Además, puede usar sintaxis corta / en línea para mapear la matriz en el diccionario:

let numbers = [7, 8, 9, 10]
let array: [(Int, Int)] = numbers.enumerated().map { ($0, $1) }
//                                                     ^   ^
//                                                   index element

Eso produce:

[(0, 7), (1, 8), (2, 9), (3, 10)]
Nate Cook
fuente
1
No quiero robar la diversión de terminar, así que si lo necesitas pongo el cheque Luhn modificado en un resumen en lugar de en la respuesta: gist.github.com/natecook1000/1eb756d6b10297006137
Nate Cook
55
En swift 2.0 debes hacer:numbers.enumerate().map { (index, element) in ...
Robert
@CharlieMartin: Puedes usar .reducedespués enumerate()o zip.
Nate Cook
Pero con un índice? Me da un error de que la función requiere solo dos parámetros y reduce toma el objeto inicial (el resultado de la reducción) como el primer parámetro y el valor actual se itera como el segundo. Actualmente solo estoy usando un for..in en su lugar
Charlie Martin
En Swift 5 enumeratees ahoraenumerated
lustig
10

Porque Swift 2.1escribí la siguiente función:

extension Array {

 public func mapWithIndex<T> (f: (Int, Element) -> T) -> [T] {     
     return zip((self.startIndex ..< self.endIndex), self).map(f)
   }
 }

Y luego úsalo así:

    let numbers = [7, 8, 9, 10]
    let numbersWithIndex: [String] = numbers.mapWithIndex { (index, number) -> String in
        return "\(index): \(number)" 
    }
    print("Numbers: \(numbersWithIndex)")
Oleksandr Karaberov
fuente
8

Con Swift 3, cuando tiene un objeto que se ajusta al Sequenceprotocolo y desea vincular cada elemento dentro de él con su índice, puede usar el enumerated()método.

Por ejemplo:

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")]

Por lo tanto, en el caso más simple, puede implementar un algoritmo Luhn en un área de juegos como este:

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

Si comienza desde a String, puede implementarlo así:

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

Si necesita repetir esas operaciones, puede refactorizar su código en una extensión:

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

O, de una manera muy concisa:

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
Imanou Petit
fuente
2

Además del ejemplo de Nate Cook map, también puede aplicar este comportamiento 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]

Tenga en cuenta que el EnumerateSequencepaso al cierre enumeratedno puede descomponerse de forma anidada, por lo que los miembros de la tupla deben descomponerse dentro del cierre (es decir enumerated.index).

Levi McCallum
fuente
2

Esta es una extensión de CollectionType que funciona para Swift 2.1 usando lanzamientos y repeticiones:

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)
    }

}

Sé que esto no es lo que estabas preguntando, pero resuelve tu problema. Puedes probar este método Swift 2.0 Luhn sin extender nada:

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
}
Peter Stajger
fuente