2 votes

Compter le nombre de lignes dans une chaîne Swift

Après avoir lu un fichier de taille moyenne (environ 500kByte) à partir d'un service web, j'ai une chaîne régulière Swift ( lines ) codé à l'origine dans .isolatin1 . Avant de le diviser effectivement, je voudrais compter le nombre de lignes (rapidement) afin de pouvoir initialiser une barre de progression.

Quel est le meilleur idiome Swift pour y parvenir ?

Je suis arrivé à la conclusion suivante :

let linesCount = lines.reduce(into: 0) { (count, letter) in
   if letter == "\r\n" {
      count += 1
   }
}

Cela n'a pas l'air trop mal mais je me demande s'il n'y a pas un moyen plus court/rapide de le faire. Le site characters permet d'accéder à une séquence de graphèmes Unicode qui traitent la propriété \r\n comme une seule entité. Vérifier cela avec tous les CharacterSet.newlines ne fonctionne pas, puisque CharacterSet n'est pas un ensemble de Character mais un ensemble de Unicode.Scalar (de manière un peu contre-intuitive dans mon livre) qui est un ensemble de points de code (où \r\n compte pour deux points de code), pas graphèmes . Essayer

var lines = "Hello, playground\r\nhere too\r\nGalahad\r\n"
lines.unicodeScalars.reduce(into: 0) { (cnt, letter) in
if CharacterSet.newlines.contains(letter) {
    cnt += 1
}

}

comptera jusqu'à 6 au lieu de 3. Cette méthode est donc plus générale que la précédente, mais elle ne fonctionnera pas correctement pour les fins de ligne CRLF.

Existe-t-il un moyen de permettre des conventions de fin de ligne plus nombreuses (comme dans CharacterSet.newlines ) qui donne toujours le bon résultat pour CRLF ? Le nombre de lignes peut-il être calculé avec moins de code (tout en restant lisible) ?

7voto

Eric D. Points 16409

Si vous pouvez utiliser une méthode Foundation sur une NSString, je vous suggère d'utiliser la méthode suivante

enumerateLines(_ block: @escaping (String, UnsafeMutablePointer<ObjCBool>) -> Void)

Voici un exemple :

import Foundation

let base = "Hello, playground\r\nhere too\r\nGalahad\r\n"
let ns = base as NSString

ns.enumerateLines { (str, _) in
    print(str)
}

Il sépare les lignes correctement, en tenant compte de tous les types de saut de ligne, tels que " \r\n ", " \n ", etc :

Bonjour, terrain de jeu
ici aussi
Galahad

Dans mon exemple, j'imprime les lignes mais il est trivial de les compter à la place, comme vous en avez besoin - ma version est juste pour la démonstration.

5voto

Patru Points 1151

Comme je n'ai pas trouvé de méthode générique pour compter les nouvelles lignes, j'ai fini par résoudre mon problème en itérant à travers tous les caractères en utilisant

let linesCount = text.reduce(into: 0) { (count, letter) in
     if letter == "\r\n" {      // This treats CRLF as one "letter", contrary to UnicodeScalars
        count += 1
     }
}

J'étais sûr que ce serait beaucoup plus rapide que d'énumérer les lignes pour simplement compter, mais j'ai résolu de faire éventuellement la mesure. Aujourd'hui, je m'y suis finalement attelé et j'ai découvert... que je n'aurais pas pu me tromper davantage.

Une chaîne de 10000 lignes a compté les lignes comme ci-dessus en environ 1,0 seconde, mais le comptage par énumération en utilisant la fonction

var enumCount = 0
text.enumerateLines { (str, _) in
    enumCount += 1
}

n'a pris qu'environ 0,8 seconde et a été constamment plus rapide d'un peu plus de 20%. Je ne sais pas quelles astuces les ingénieurs de Swift cachent dans leurs manches, mais ils ont réussi à enumerateLines très rapidement. Pour mémoire, ceci.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X