web-dev-qa-db-fra.com

Pourquoi et quand utiliser paresseux avec Array dans Swift?

[1, 2, 3, -1, -2].filter({ $0 > 0 }).count // => 3

[1, 2, 3, -1, -2].lazy.filter({ $0 > 0 }).count // => 3

Quel est l'avantage d'ajouter lazy à la deuxième instruction. Selon ma compréhension, lorsque la variable lazy est utilisée, la mémoire est initialisée à cette variable au moment où elle est utilisée. Comment cela a-t-il un sens dans ce contexte?

enter image description here

Essayer de comprendre l'utilisation de LazySequence en détail. J'avais utilisé les fonctions map, reduce et filter sur les séquences, mais jamais sur la séquence lazy. Besoin de comprendre pourquoi utiliser cela?

25
Deep Arora

lazy change la façon dont le tableau est traité. Lorsque lazy n'est pas utilisé, filter traite l'ensemble du tableau et stocke les résultats dans un nouveau tableau. Lorsque lazy est utilisé, les valeurs de la séquence ou de la collection sont produites à la demande à partir des fonctions en aval. Les valeurs ne sont pas stockées dans un tableau; ils sont juste produits en cas de besoin.

Considérez cet exemple modifié dans lequel j'ai utilisé reduce au lieu de count afin que nous puissions imprimer ce qui se passe:

Ne pas utiliser lazy:

Dans ce cas, tous les éléments seront filtrés en premier avant que quoi que ce soit ne soit compté.

[1, 2, 3, -1, -2].filter({ print("filtered one"); return $0 > 0 })
    .reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
filtered one
filtered one
filtered one
filtered one
counted one
counted one
counted one

Utilisation de lazy:

Dans ce cas, reduce demande qu'un élément compte, et filter fonctionnera jusqu'à ce qu'il en trouve un, puis reduce en demandera un autre et filter fonctionnera jusqu'à ce qu'il en trouve un autre.

[1, 2, 3, -1, -2].lazy.filter({ print("filtered one"); return $0 > 0 })
    .reduce(0) { (total, elem) -> Int in print("counted one"); return total + 1 }
filtered one
counted one
filtered one
counted one
filtered one
counted one
filtered one
filtered one

Quand utiliser lazy:

option-cliquer sur lazy donne cette explication:

pop-up for lazy in Xcode

À partir de la discussion pour lazy:

Utilisez la propriété paresseuse lors du chaînage des opérations:

  1. pour empêcher les opérations intermédiaires d'allouer du stockage

    ou

  2. lorsque vous n'avez besoin que d'une partie de la collection finale pour éviter les calculs inutiles

    J'ajouterais un troisième:

  3. lorsque vous souhaitez que les processus en aval démarrent plus tôt et que vous n'ayez pas à attendre que les processus en amont effectuent tout leur travail en premier

Ainsi, par exemple, vous voudriez utiliser lazy avant filter si vous recherchiez le premier Int positif, car la recherche s'arrêterait dès que vous en auriez trouvé un et cela éviterait à filter d'avoir à filtrer tout le tableau et cela éviterait d'allouer de l'espace au tableau filtré.

Pour le 3ème point, imaginez que vous avez un programme qui affiche des nombres premiers dans la plage 1...10_000_000 en utilisant filter sur cette plage. Vous préférez afficher les nombres premiers tels que vous les avez trouvés plutôt que d'attendre de les calculer tous avant de montrer quoi que ce soit.

42
vacawama

Je n'avais jamais vu cela auparavant alors j'ai fait quelques recherches et je l'ai trouvé.

La syntaxe que vous publiez crée une collection paresseuse. Une collection paresseuse évite de créer toute une série de tableaux intermédiaires pour chaque étape de votre code. Ce n'est pas très pertinent lorsque vous n'avez qu'une instruction de filtrage, cela aurait beaucoup plus d'effet si vous faisiez quelque chose comme filter.map.map.filter.map, car sans la collection paresseuse, un nouveau tableau est créé à chaque étape.

Consultez cet article pour plus d'informations:

https://medium.com/developermind/lightning-read-1-lazy-collections-in-Swift-fa997564c1a

ÉDITER:

J'ai fait quelques analyses comparatives, et une série de fonctions d'ordre supérieur comme les cartes et les filtres est en fait un peu plus lente sur une collection paresseuse que sur une collection "régulière".

Il semble que les collections paresseuses vous offrent une plus petite empreinte mémoire au prix de performances légèrement plus lentes.

Édition n ° 2:

@discardableResult func timeTest() -> Double {
    let start = Date()
    let array = 1...1000000
    let random = array
        .map { (value) -> UInt32 in
            let random = arc4random_uniform(100)
            //print("Mapping", value, "to random val \(random)")
            return random
    }
    let result = random.lazy  //Remove the .lazy here to compare
        .filter {
            let result = $0 % 100 == 0
            //print("  Testing \($0) < 50", result)
            return result
        }
        .map { (val: UInt32) -> NSNumber in
            //print("    Mapping", val, "to NSNumber")
            return NSNumber(value: val)
        }
        .compactMap { (number) -> String? in
            //print("      Mapping", number, "to String")
            return formatter.string(from: number)
        }
        .sorted { (lhv, rhv) -> Bool in
            //print("        Sorting strings")
            return (lhv.compare(rhv, options: .numeric) == .orderedAscending)
    }

    let elapsed = Date().timeIntervalSince(start)

    print("Completed in", String(format: "%0.3f", elapsed), "seconds. count = \(result.count)")
    return elapsed
}

Dans le code ci-dessus, si vous modifiez la ligne

let result = random.lazy  //Remove the .lazy here to compare

à

let result = random  //Removes the .lazy here

Ensuite, il s'exécute plus rapidement. Avec paresseux, mon benchmark a pris environ 1,5 fois plus de temps avec la collection .lazy par rapport à un tableau droit.

5
Duncan C