NSArray от NSCharacterSet

В настоящее время я могу создать массив Alphabets, как показано ниже.

[[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];

Зная, что доступно

[NSCharacterSet uppercaseLetterCharacterSet]

Как сделать массив из него?

Ответ 1

Следующий код создает массив, содержащий все символы заданного набора символов. Он также работает для символов за пределами "базовой многоязычной плоскости" (символы > U + FFFF, например U + 10400 DESERET CAPITAL LETTER LONG 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];
            }
        }
    }
}

Для uppercaseLetterCharacterSet это дает массив из 1467 элементов. Но обратите внимание, что символы > U + FFFF сохраняются как суррогатная пара UTF-16 в NSString, поэтому, например, U + 10400 фактически хранится в NSString как 2 символа "\ uD801\uDC00".

Код Swift 2 можно найти в других ответах на этот вопрос. Вот версия Swift 3, написанная как метод расширения:

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
    }
}

Пример:

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

(Обратите внимание, что некоторые символы могут отсутствовать в шрифте, используемом для отобразите результат.)

Ответ 2

Поскольку символы имеют ограниченный, конечный (и не слишком широкий) диапазон, вы можете просто проверить, какие символы являются членами заданного набора символов (грубая сила):

// 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 sample code
for (unichar i = 0; i < UNICHAR_MAX; i++) {
    if (ptr[i >> 3] & (1u << (i & 7))) {
        [allCharsInSet addObject:[NSString stringWithCharacters:&i length:1]];
    }
}

Примечание. Из-за размера unichar и структуры дополнительных сегментов в bitmapRepresentation это решение работает только для символов <= 0xFFFF и не подходит для более высоких плоскостей.

Ответ 3

Вдохновленный ответом Satachito, вот эффективный способ сделать массив из CharacterSet с помощью 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
    }
}

Пример для прописных букв

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

Пример для прерывистых плоскостей

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

исполнениям

Очень хорошо: это решение, встроенное в выпуск с bitmapRepresentation, кажется в 3–10 раз быстрее, чем решение Martin R с contains или решение Оливера Аткинсона с longCharacterIsMember.

Ответ 4

Я создал версию алгоритма Martin R Swift (v2.1):

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)");
            }
        }
    }
}

Ответ 5

Это делается с помощью немного более быстрого для быстрого.

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)

Ответ 6

Только для A-Z латинского алфавита (ничего с греческими или диакритическими знаками или другими вещами, которые не были тем, о чем попросил парень):

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
    }
}

Ответ 7

Я нашел решение Martin R слишком медленным для своих целей, поэтому я решил его другим способом, используя свойство CharacterSet bitmapRepresentation.

Это значительно быстрее в соответствии с моими показателями:

var ranges = [CountableClosedRange<UInt32>]()
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

Это решение возвращает массив диапазонов codePoint, но вы можете легко адаптировать его для возврата отдельных символов или строк и т.д.

Ответ 8

Вы не должны; это не цель набора символов. A NSCharacterSet - возможно бесконечное множество символов, возможно, в еще не изобретенных кодовых точках. Все, что вы хотите знать, это "Является ли этот символ или набор символов в этом наборе?", И с этой целью это полезно.

Представьте этот код Swift:

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

Что аналогично этому Objective-C коду:

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

Легко сказать "перебрать все символы в asciiCharacterSet"; который будет просто перебирать все символы от U+0000 до U+007F. Но что это означает, чтобы перебрать все символы в nonAsciiCharacterSet? Вы начинаете с U+0080? Кто скажет, что в будущем не будет отрицательных кодовых страниц? Куда вы заканчиваете? Вы пропускаете непечатаемые символы? Как насчет расширенных кластеров графем? Так как это набор (где порядок не имеет значения), может ли ваш код обрабатывать неподходящие коды в этом цикле?

Это вопросы, на которые вы не хотите отвечать. Функционально nonAsciiCharacterSet является бесконечным, и все, что вы хотите использовать для него, - это указать, находится ли какой-либо данный символ вне набора символов ASCII.


Вопрос, который вы действительно должны задавать себе, - это: "Что я хочу выполнить с помощью этого массива заглавных букв?" Если (и, вероятно, только если), вам действительно нужно перебрать его в порядок, помещая те, о которых вы заботитесь, в Array или String (возможно, один из них читается из файла ресурсов), вероятно, лучший способ. Если вы хотите проверить, является ли символ частью набора прописных букв, тогда вам не нужен порядок или даже количество символов в наборе, и следует использовать CharacterSet.uppercaseLetters.contains(foo) (в Objective-C: [NSCharacterSet.uppercaseLetterCharacterSet contains: foo]).

Подумайте также о нелатинских персонажах. CharacterSet.uppercaseLetters охватывает Unicode Общие категории Lu и Lt, которые содержат A через Z, а также такие вещи, как Dž, 𝕹 и Խ. Вы не хотите об этом думать. Вы определенно не хотите выпускать обновление для своего приложения, когда Unicode Consortium добавляет новые символы в этот список. Если то, что вы хотите сделать, это решить, есть ли что-то в верхнем регистре, не беспокойтесь о жестком кодировании.

Ответ 9

Конечно, вы можете создавать наборы символов и алфавитов, используя CharacterSet, например:

var smallEmojiCharacterSet = CharacterSet(charactersIn:  Unicode.Scalar("😀")...Unicode.Scalar("😎"))

Проблема в том, что CharacterSet НЕ является Set (хотя и соответствует SetAlgebra), это скорее набор символов Юникода. Это вызывает проблему получения последовательности всех его символов, чтобы преобразовать ее в Array, Set или String. Я нашел решение, но есть лучшее решение. На самом деле, то, что вы хотите - это переходить от персонажа к персонажу, чтобы иметь диапазон "a"..."z". Это не сложно сделать на скалярном уровне. На уровне Character нужно учесть больше предостережений.

extension Unicode.Scalar: Strideable {
    public typealias Stride = Int

    public func distance(to other: Unicode.Scalar) -> Int {
        return Int(other.value) - Int(self.value)
    }

    public func advanced(by n: Int) -> Unicode.Scalar {
        return Unicode.Scalar(UInt32(Int(value) + n))!
    }
}


let alphabetScalarRange = (Unicode.Scalar("a")...Unicode.Scalar("z"))// ClosedRange<Unicode.Scalar>

let alphabetCharactersArr = Array(alphabetScalarRange.map(Character.init)) // Array of Characters from range
let alphabetStringsArr = Array(alphabetScalarRange.map(String.init)) // Array of Strings from range
let alphabetString = alphabetStringsArr.joined() // String (collection of characters) from range
// or simply
let uppercasedAlphabetString =  (("A" as Unicode.Scalar)..."Z").reduce("") { (r, us) -> String in
    r + String(us)
}

Если вы думаете, что расширение является излишним

let alphabetScalarValueRange = (Unicode.Scalar("a").value...Unicode.Scalar("z").value)
let alphabetStringsArr2 = Array(alphabetScalarValueRange.compactMap{ Unicode.Scalar($0)?.escaped(asASCII: false) })
let alphabetString2 = alphabetScalarValueRange.compactMap({ Unicode.Scalar($0)?.escaped(asASCII: false) }).joined(separator: ", ")

Но будьте осторожны: символы могут состоять из нескольких скаляров.