Cartographier ou réduire avec index dans Swift

143

Existe-t-il un moyen d'obtenir l'index du tableau dans mapoureduce dans Swift? Je cherche quelque chose comme each_with_indexdans 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")

Je voudrais me débarrasser de la oddvariable ci-dessus .

Jonas Elfström
la source

Réponses:

314

Vous pouvez utiliser enumeratepour convertir une séquence ( Array, String, etc.) à une séquence de tuples d'un compteur de nombre entier et et l' élément jumelés. C'est:

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

Lien vers la enumeratedéfinition

Notez que ce n'est pas la même chose que d'obtenir l' index de la collection - enumeratevous renvoie un compteur entier. C'est le même que l'index pour un tableau, mais sur une chaîne ou un dictionnaire ne sera pas très utile. Pour obtenir l'index réel avec chaque élément, vous pouvez utiliser zip:

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

Lorsque vous utilisez une séquence énumérée avec reduce, vous ne pourrez pas séparer l'index et l'élément dans un tuple, car vous avez déjà le tuple cumulatif / actuel dans la signature de la méthode. Au lieu de cela, vous devrez utiliser .0et .1sur le deuxième paramètre de votre reducefermeture:

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

Swift 3.0 et supérieur

Depuis la syntaxe Swift 3.0 est assez différente.
En outre, vous pouvez utiliser la syntaxe courte / en ligne pour mapper le tableau sur le dictionnaire:

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

Cela produit:

[(0, 7), (1, 8), (2, 9), (3, 10)]
Nate Cook
la source
1
Je ne veux pas voler le plaisir de la finition, donc si vous en avez besoin, je mets le chèque Luhn modifié dans l'essentiel au lieu de dans la réponse: gist.github.com/natecook1000/1eb756d6b10297006137
Nate Cook
5
Dans Swift 2.0, vous devez faire:numbers.enumerate().map { (index, element) in ...
Robert
@CharlieMartin: Vous pouvez utiliser .reduceaprès enumerate()ou zip.
Nate Cook
Mais avec un index? J'obtiens une erreur selon laquelle la fonction ne nécessite que deux paramètres et la réduction prend l'objet initial (le résultat de la réduction) comme premier paramètre et la valeur actuelle étant itérée comme second. J'utilise actuellement juste un for..in à la place
Charlie Martin
In Swift 5 enumerateest maintenantenumerated
lustig
10

Car Swift 2.1j'ai écrit la fonction suivante:

extension Array {

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

Et puis utilisez-le comme ceci:

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

Avec Swift 3, lorsque vous avez un objet conforme au Sequenceprotocole et que vous souhaitez lier chaque élément à l'intérieur de celui-ci avec son index, vous pouvez utiliserenumerated() method.

Par exemple:

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

Par conséquent, dans le cas le plus simple, vous pouvez implémenter un algorithme de Luhn dans un Playground comme ceci:

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 vous partez d'un String , vous pouvez l'implémenter comme ceci:

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 vous devez répéter ces opérations, vous pouvez refactoriser votre code en une 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

Ou, d'une manière très concise:

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
la source
2

En plus de l'exemple de Nate Cook map, vous pouvez également appliquer ce comportement à 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]

Notez que le EnumerateSequencepassé dans la fermeture enumeratedne peut pas être décomposé de manière imbriquée, donc les membres du tuple doivent être décomposés à l'intérieur de la fermeture (c'est-à-dire enumerated.index).

Levi McCallum
la source
2

Il s'agit d'une extension CollectionType fonctionnelle pour swift 2.1 utilisant des lancers et des renvois:

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

}

Je sais que ce n'est pas ce que vous demandiez, mais cela résout votre problème. Vous pouvez essayer cette méthode Luhn 2.0 rapide sans rien étendre:

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
la source