2017-01-26 3 views
4

J'ai environ 2000 éléments dans ma matrice, et quand il est filtré, je voudrais mettre fin au filtrage dès que j'ai 5 éléments dans mon tableau filtré.Limiter les résultats d'un filtre tableau Swift à X pour les performances

Actuellement, il est:

providerArray.filter({($0.lowercased().range(of:((row.value as? String)?.lowercased())!) != nil)}) 

qui peut revenir jusqu'à 2000 des résultats qui est une perte de traitement, et le temps.

Pour être plus clair, je besoin d'une solution qui est équivalente à limiter les résultats du filtre comme je peux avec CoreData va chercher [request setFetchLimit:5];

Répondre

6

La solution la plus rapide en termes de temps d'exécution semble être une boucle explicite qui ajoute des éléments correspondant jusqu'à ce que la limite est atteinte:

extension Sequence { 
    public func filter(where isIncluded: (Iterator.Element) -> Bool, limit: Int) -> [Iterator.Element] { 
     var result : [Iterator.Element] = [] 
     result.reserveCapacity(limit) 
     var count = 0 
     var it = makeIterator() 

     // While limit not reached and there are more elements ... 
     while count < limit, let element = it.next() { 
      if isIncluded(element) { 
       result.append(element) 
       count += 1 
      } 
     } 
     return result 
    } 
} 

Exemple d'utilisation:

let numbers = Array(0 ..< 2000) 
let result = numbers.filter(where: { $0 % 3 == 0 }, limit: 5) 
print(result) // [0, 3, 6, 9, 12] 
+0

Encore un hommage aux boucles explicites, soignées! :) Un cas de coin, mais vous pourriez envisager de réserver une capacité qui est le minimum de 'limite' et le' underestimatedCount' de la séquence (par exemple 'result.reserveCapacity (Swift.min (limit, underestimatedCount))') dans cas, un utilisateur (abusivement) appelle cela avec une énorme limite sur une séquence beaucoup plus petite. – dfri

+0

@dfri: Je ne sais pas si c'est mieux. 'underestimatedCount' est nul pour toutes les séquences dont la longueur n'est pas connue a priori, donc cela ne fonctionnerait que pour les tableaux ou autres' RandomAccessCollection's. –

+0

Ah oui, bien sûr. Alors peut-être seulement dans le cas où l'extension devrait être faite à 'Collection' plutôt qu'à' Sequence' :) Y a-t-il une raison particulière d'utiliser l'extension plus générale de 'Sequence' ici plutôt que' Collection'? (Puisque nous retournons un type qui est plus spécifique comme 'Collection' et pas seulement' Sequence'). Ninja-edit: mais encore une fois le [std lib 'filter' de' Sequence'] (https://github.com/apple/swift/blob/master/stdlib/public/core/Sequence.swift#L846) ... – dfri

5

Vous pouvez utiliser .lazy stimuler aussi la performance un peu:

let numbers: [Int] = Array(0 ..< 2000) 

let result: AnySequence = numbers 
    .lazy 
    .filter { 
     print("Calling filter for: \($0)") 
     return ($0 % 3) == 0 
    } 
    .prefix(5) 

print(Array(result)) 

Cela appellera la fonction filter seulement pour les 15 premières valeurs (jusqu'à ce qu'il trouve 5 qui passent le filtre).

Maintenant vous pouvez vous concentrer sur l'amélioration des performances du filter lui-même. Par exemple. en mettant en cache des valeurs. Vous n'avez pas à le faire, mais si certaines valeurs ne cessent de se répéter, cela peut améliorer considérablement les performances.

let numbers: [Int] = Array(0 ..< 2000) 
var filterCache: [Int: Bool] = [:] 

let result: AnySequence = numbers 
    .lazy 
    .filter { 
     if let cachedResult = filterCache[$0] { 
      return cachedResult 
     } 

     print("Calling filter for: \($0)") 
     let result = (($0 % 3) == 0) 

     filterCache[$0] = result 

     return result 
    } 
    .prefix(5) 

print(Array(result)) 

Vous pouvez appliquer cette méthode directement à votre fonction.

Notez également que pour améliorer les performances, vous devez soit:

  • sauver ((row.value as? String)?.lowercased())! dans une variable locale, car il est exécuté plusieurs fois

  • simplifier l'expression à l'aide des options:

let result: AnySequence = providerArray 
    .lazy 
    .filter { 
     $0.range(of: row.value as! String, options: [.caseInsensitive]) != nil 
    } 
    .prefix(5) 
+0

whould qui serait un filtre valide? Je veux dire qu'il appliquera le filtre uniquement sur les 15 premières valeurs. –

+3

Si vous annotez 'result' comme une' AnySequence', le prédicat 'filter' ne sera appelé qu'une fois par élément (vous pouvez alors supprimer la mise en cache). Ceci est dû au fait que 'prefix (_ :)' génère par défaut une tranche, qui nécessite d'abord l'indexation de la collection paresseuse, qui doit passer par le filtre, puis de nouveau pour l'initialiseur 'Array (_ :)'. Bien que ce soit pour une troisième fois, je ne suis pas trop sûr. – Hamish

+0

@Hamish Je pense que la conversion en 'Array' est actuellement faite. Mais tu as raison. La déclaration 'AnySequence' a réduit les 3 appels à 1. – Sulthan