web-dev-qa-db-fra.com

Quelle est la meilleure façon de tester si un CharacterSet contient un caractère dans Swift 4?

Je cherche un moyen, dans Swift 4, pour tester si un personnage est membre d'un CharacterSet arbitraire. J'ai cette classe Scanner qui sera utilisée pour certains analyse légère. L'une des fonctions de la classe consiste à ignorer tous les caractères, à la position actuelle, qui appartiennent à un certain ensemble de caractères possibles.

class MyScanner {
  let str: String
  var idx: String.Index
  init(_ string: String) {
    str = string
    idx = str.startIndex
  }
  var remains: String { return String(str[idx..<str.endIndex])}

  func skip(charactersIn characters: CharacterSet) {
    while idx < str.endIndex && characters.contains(str[idx])) {
      idx = source.index(idx, offsetBy: 1)
    }
  }
}

let scanner = MyScanner("fizz   buzz fizz")
scanner.skip(charactersIn: CharacterSet.alphanumerics)
scanner.skip(charactersIn: CharacterSet.whitespaces)
print("what remains: \"\(scanner.remains)\"")

Je voudrais implémenter la fonction skip(charactersIn:) pour que le code ci-dessus imprime buzz fizz.

La partie délicate est characters.contains(str[idx])) dans le while - .contains() nécessite un Unicode.Scalar, Et je suis en train d'essayer de comprendre l'étape suivante .

Je sais que je pourrais passer un String à la fonction skip, mais j'aimerais trouver un moyen de le faire fonctionner avec un CharacterSet, à cause de toutes les commodités membres statiques (alphanumerics, whitespaces, etc.).

Comment tester un CharacterSet s'il contient un Character?

13
G-Mark

Je sais que vous vouliez utiliser CharacterSet plutôt que String, mais CharacterSet ne prend pas (encore, au moins) en charge les caractères composés de plusieurs Unicode.Scalar. Voir le caractère "famille" (???? ‍ ???? ‍ ???? ‍ ????) ou les caractères du drapeau international (par exemple "????????" ou "???? ???? ") que Apple démontré dans la discussion sur les chaînes dans la vidéo WWDC 2017 Quoi de neuf dans Swift . Les emoji à tons de peau multiples manifestent également ce comportement (par exemple? ??????? contre ????????).

Par conséquent, je me méfierais de l'utilisation de CharacterSet (qui est un "ensemble de valeurs de caractères Unicode à utiliser dans les opérations de recherche"). Ou, si vous souhaitez fournir cette méthode pour des raisons de commodité, sachez qu'elle ne fonctionnera pas correctement avec les caractères représentés par plusieurs scalaires Unicode.

Ainsi, vous pouvez proposer un scanner qui fournit les rendus CharacterSet et String de la méthode skip:

class MyScanner {
    let string: String
    var index: String.Index

    init(_ string: String) {
        self.string = string
        index = string.startIndex
    }

    var remains: String { return String(string[index...]) }

    /// Skip characters in a string
    ///
    /// This rendition is safe to use with strings that have characters
    /// represented by more than one unicode scalar.
    ///
    /// - Parameter skipString: A string with all of the characters to skip.

    func skip(charactersIn skipString: String) {
        while index < string.endIndex, skipString.contains(string[index]) {
            index = string.index(index, offsetBy: 1)
        }
    }

    /// Skip characters in character set
    ///
    /// Note, character sets cannot (yet) include characters that are represented by
    /// more than one unicode scalar (e.g. ????‍????‍????‍???? or ???????? or ????????). If you want to test
    /// for these multi-unicode characters, you have to use the `String` rendition of
    /// this method.
    ///
    /// This will simply stop scanning if it encounters a multi-unicode character in
    /// the string being scanned (because it knows the `CharacterSet` can only represent
    /// single-unicode characters) and you want to avoid false positives (e.g., mistaking
    /// the Jamaican flag, ????????, for the Japanese flag, ????????).
    ///
    /// - Parameter characterSet: The character set to check for membership.

    func skip(charactersIn characterSet: CharacterSet) {
        while index < string.endIndex,
            string[index].unicodeScalars.count == 1,
            let character = string[index].unicodeScalars.first,
            characterSet.contains(character) {
                index = string.index(index, offsetBy: 1)
        }
    }

}

Ainsi, votre exemple simple fonctionnera toujours:

let scanner = MyScanner("fizz   buzz fizz")
scanner.skip(charactersIn: CharacterSet.alphanumerics)
scanner.skip(charactersIn: CharacterSet.whitespaces)
print(scanner.remains)  // "buzz fizz"

Mais utilisez le rendu String si les caractères que vous souhaitez ignorer peuvent inclure plusieurs scalaires unicode:

let family = "????\u{200D}????\u{200D}????\u{200D}????"  // ????‍????‍????‍????
let boy = "????"

let charactersToSkip = family + boy

let string = boy + family + "foobar"  // ????????‍????‍????‍????foobar

let scanner = MyScanner(string)
scanner.skip(charactersIn: charactersToSkip)
print(scanner.remains)                // foobar

Comme Michael Waterfall l'a noté dans les commentaires ci-dessous, CharacterSet a un bogue et ne gère même pas le 32 bits Unicode.Scalar valeurs correctement, ce qui signifie qu'il ne gère même pas correctement les caractères scalaires simples si la valeur dépasse 0xffff (y compris les emoji, entre autres). Cependant, le rendu String ci-dessus les gère correctement.

6
Rob

Je ne sais pas si c'est le moyen le plus efficace mais vous pouvez créer un nouveau CharSet et vérifier s'il s'agit de sous/super-ensembles (la comparaison des ensembles est plutôt rapide)

let newSet = CharacterSet(charactersIn: "a")
// let newSet = CharacterSet(charactersIn: "\(character)")
print(newSet.isSubset(of: CharacterSet.decimalDigits)) // false
print(newSet.isSubset(of: CharacterSet.alphanumerics)) // true
13
nathan

Swift 4.2CharacterSet fonction d'extension pour vérifier si elle contient Character:

extension CharacterSet {
    func containsUnicodeScalars(of character: Character) -> Bool {
        return character.unicodeScalars.allSatisfy(contains(_:))
    }
}

Exemple d'utilisation:

CharacterSet.decimalDigits.containsUnicodeScalars(of: "3") // true
CharacterSet.decimalDigits.containsUnicodeScalars(of: "a") // false
4
Vadim Akhmerov