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
?
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.
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
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