NSArray di NSCharacterSet

Attualmente sono in grado di creare una serie di Alfabeti come di seguito

[[NSArray alloc]initWithObjects:@"A",@"B",@"C",@"D",@"E",@"F",@"G",@"H",@"I",@"J",@"K",@"L",@"M",@"N",@"O",@"P",@"Q",@"R",@"S",@"T",@"U",@"V",@"W",@"X",@"Y",@"Z",nil]; 

Sapendo che è disponibile oltre

 [NSCharacterSet uppercaseLetterCharacterSet] 

Come fare una matrice fuori di esso?

Il codice seguente crea un array contenente tutti i caratteri di un determinato set di caratteri. Funziona anche per i caratteri al di fuori del “piano multilingue di base” (caratteri> U + FFFF, ad es. U + 10400 LETTERA MAIUSCOLO DESERTO LUNGO I).

 NSCharacterSet *charset = [NSCharacterSet uppercaseLetterCharacterSet]; NSMutableArray *array = [NSMutableArray array]; for (int plane = 0; plane <= 16; plane++) { if ([charset hasMemberInPlane:plane]) { UTF32Char c; for (c = plane << 16; c < (plane+1) << 16; c++) { if ([charset longCharacterIsMember:c]) { UTF32Char c1 = OSSwapHostToLittleInt32(c); // To make it byte-order safe NSString *s = [[NSString alloc] initWithBytes:&c1 length:4 encoding:NSUTF32LittleEndianStringEncoding]; [array addObject:s]; } } } } 

Per uppercaseLetterCharacterSet questo fornisce una matrice di 1467 elementi. Nota che i caratteri> U + FFFF sono memorizzati come coppia surrogata UTF-16 in NSString , quindi ad esempio U + 10400 è effettivamente memorizzato in NSString come 2 caratteri "\ uD801 \ uDC00".

Il codice Swift 2 può essere trovato in altre risposte a questa domanda. Ecco una versione di Swift 3 , scritta come metodo di estensione:

 extension CharacterSet { func allCharacters() -> [Character] { var result: [Character] = [] for plane: UInt8 in 0...16 where self.hasMember(inPlane: plane) { for unicode in UInt32(plane) << 16 ..< UInt32(plane + 1) << 16 { if let uniChar = UnicodeScalar(unicode), self.contains(uniChar) { result.append(Character(uniChar)) } } } return result } } 

Esempio:

 let charset = CharacterSet.uppercaseLetters let chars = charset.allCharacters() print(chars.count) // 1521 print(chars) // ["A", "B", "C", ... "] 

(Si noti che alcuni caratteri potrebbero non essere presenti nel carattere utilizzato per visualizzare il risultato.)

Dato che i personaggi hanno una gamma limitata, limitata (e non troppo ampia), puoi semplicemente testare quali personaggi sono membri di un determinato set di caratteri (forza bruta):

 // this doesn't seem to be available #define UNICHAR_MAX (1ull << (CHAR_BIT * sizeof(unichar))) NSData *data = [[NSCharacterSet uppercaseLetterCharacterSet] bitmapRepresentation]; uint8_t *ptr = [data bytes]; NSMutableArray *allCharsInSet = [NSMutableArray array]; // following from Apple's sample code for (unichar i = 0; i < UNICHAR_MAX; i++) { if (ptr[i >> 3] & (1u << (i & 7))) { [allCharsInSet addObject:[NSString stringWithCharacters:&i length:1]]; } } 

Nota: a causa delle dimensioni di un unichar e della struttura dei segmenti aggiuntivi in ​​bitmapRepresentation, questa soluzione funziona solo per i caratteri <= 0xFFFF e non è adatta per i piani più alti.

Ho creato una versione Swift (v2.1) dell’algoritmo di Martin R:

 let charset = NSCharacterSet.URLPathAllowedCharacterSet(); for var plane : UInt8 in 0...16 { if charset.hasMemberInPlane( plane ) { var c : UTF32Char; for var c : UInt32 = UInt32( plane ) << 16; c < (UInt32(plane)+1) << 16; c++ { if charset.longCharacterIsMember(c) { var c1 = c.littleEndian // To make it byte-order safe let s = NSString(bytes: &c1, length: 4, encoding: NSUTF32LittleEndianStringEncoding); NSLog("Char: \(s)"); } } } } 

Questo viene fatto usando un po ‘più di swift per swift.

 let characters = NSCharacterSet.uppercaseLetterCharacterSet() var array = [String]() for plane: UInt8 in 0...16 where characters.hasMemberInPlane(plane) { for character: UTF32Char in UInt32(plane) << 16..<(UInt32(plane) + 1) << 16 where characters.longCharacterIsMember(character) { var endian = character.littleEndian let string = NSString(bytes: &endian, length: 4, encoding: NSUTF32LittleEndianStringEncoding) as! String array.append(string) } } print(array) 

Solo per AZ dell’alfabeto latino (niente con il greco, o segni diacritici, o altre cose che non erano ciò che il ragazzo ha chiesto):

 for plane: UInt8 in 0...16 where characters.hasMemberInPlane(plane) { i = 0 for character: UTF32Char in UInt32(plane) << 16...(UInt32(plane) + 1) << 16 where characters.longCharacterIsMember(character) { var endian = character.littleEndian let string = NSString(bytes: &endian, length: 4, encoding: NSUTF32LittleEndianStringEncoding) as! String array.append(string) if(array.count == 26) { break } } if(array.count == 26) { break } } 

Non dovresti; questo non è lo scopo di un set di caratteri. Un NSCharacterSet è un insieme di caratteri possibilmente infinito, possibilmente in punti di codice non ancora inventati. Tutto quello che vuoi sapere è “È questo personaggio o collezione di personaggi in questo set?”, Ea tal fine è utile.

Immagina questo codice Swift:

 let asciiCodepoints = Unicode.Scalar(0x00)...Unicode.Scalar(0x7F) let asciiCharacterSet = CharacterSet(charactersIn: asciiCodepoints) let nonAsciiCharacterSet = asciiCharacterSet.inverted 

Che è analogo a questo codice Objective-C:

 NSRange asciiCodepoints = NSMakeRange(0x00, 0x7F); NSCharacterSet * asciiCharacterSet = [NSCharacterSet characterSetWithRange:asciiCodepoints]; NSCharacterSet * nonAsciiCharacterSet = asciiCharacterSet.invertedSet; 

È facile dire “loop su tutti i caratteri in asciiCharacterSet “; che passerebbe semplicemente su tutti i caratteri da U+0000 a U+007F . Ma cosa significa nonAsciiCharacterSet tutti i caratteri in nonAsciiCharacterSet ? Inizi a U+0080 ? Chi dirà che non ci saranno codepunti negativi in ​​futuro? Dove finisci? Salti caratteri non stampabili? Che dire dei grapphi grapheme estesi? Dal momento che è un set (dove l’ordine non ha importanza), il tuo codice può gestire i codepoint out-of-order in questo ciclo?

Queste sono domande a cui non vuoi rispondere qui; funzionalmente nonAsciiCharacterSet è infinito e tutto ciò che si vuole usare è per dire se un dato personaggio si trova fuori dal set di caratteri ASCII.


La domanda che dovresti davvero porsi è: “Cosa voglio realizzare con questa serie di lettere maiuscole?” Se (e probabilmente solo se) hai davvero bisogno di iterare su di esso in ordine, mettere quelli che ti interessano in una Array o String (forse uno letto da un file di risorse) è probabilmente il modo migliore. Se vuoi verificare se un personaggio fa parte del set di lettere maiuscole, allora non ti importa di un ordine o di quanti caratteri ci sono nel set , e dovresti usare CharacterSet.uppercaseLetters.contains(foo) (in Objective-C: [NSCharacterSet.uppercaseLetterCharacterSet contains: foo] ).

Pensa anche ai personaggi non latini. CharacterSet.uppercaseLetters include le categorie generali Unicode Lu e Lt , che contengono da A a Z e anche cose come Dž , 𝕹 e Խ . Non vuoi dover pensare a questo. Sicuramente non vuoi rilasciare un aggiornamento alla tua app quando il Consorzio Unicode aggiunge nuovi personaggi a questo elenco. Se quello che vuoi fare è decidere se qualcosa è maiuscolo, non preoccuparti di codificare a fondo nulla.

Ho trovato la soluzione di Martin R troppo lenta per i miei scopi, quindi l’ho risolta in un altro modo usando la proprietà bitmapRepresentation .

Questo è significativamente più veloce secondo i miei benchmark:

 var ranges = [CountableClosedRange]() let bitmap: Data = characterSet.bitmapRepresentation var first: UInt32?, last: UInt32? var plane = 0, nextPlane = 8192 for (j, byte) in bitmap.enumerated() where byte != 0 { if j == nextPlane { plane += 1 nextPlane += 8193 continue } for i in 0 ..< 8 where byte & 1 << i != 0 { let codePoint = UInt32(j - plane) * 8 + UInt32(i) if let _last = last, codePoint == _last + 1 { last = codePoint } else { if let first = first, let last = last { ranges.append(first ... last) } first = codePoint last = codePoint } } } if let first = first, let last = last { ranges.append(first ... last) } return ranges 

Questa soluzione restituisce un array di intervalli codePoint, ma è ansible adattarlo facilmente per restituire singoli caratteri o stringhe, ecc.

Ispirato dalla risposta Satachito , ecco un modo performante per creare una matrice da CharacterSet usando bitmapRepresentation :

 extension CharacterSet { func characters() -> [Character] { // A Unicode scalar is any Unicode code point in the range U+0000 to U+D7FF inclusive or U+E000 to U+10FFFF inclusive. return codePoints().compactMap { UnicodeScalar($0) }.map { Character($0) } } func codePoints() -> [Int] { var result: [Int] = [] var plane = 0 // following documentation at https://developer.apple.com/documentation/foundation/nscharacterset/1417719-bitmaprepresentation for (i, w) in bitmapRepresentation.enumerated() { let k = i % 8193 if k == 8192 { // plane index byte plane = Int(w) << 13 continue } let base = (plane + k) << 3 for j in 0 ..< 8 where w & 1 << j != 0 { result.append(base + j) } } return result } } 

Esempio per uppercaseLetters

 let charset = CharacterSet.uppercaseLetters let chars = charset.characters() print(chars.count) // 1733 print(chars) // ["A", "B", "C", ... "] 

Esempio per piani discontinui

 let charset = CharacterSet(charactersIn: "𝚨󌞑") let codePoints = charset.codePoints() print(codePoints) // [120488, 837521] 

Prestazioni

Molto buono: questa soluzione realizzata in versione con bitmapRepresentation sembra da 3 a 10 volte più veloce della soluzione di Martin R con contains o la soluzione di Oliver Atkinson con longCharacterIsMember .