26

目前我可以制作如下的字母数组

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

如何用它制作一个数组?

4

9 回答 9

53

以下代码创建一个包含给定字符集的所有字符的数组。它也适用于“基本多语言平面”之外的字符(字符 > 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", ... "]

(请注意,用于显示结果的字体中可能不存在某些字符。)

于 2013-04-01T11:32:51.153 回答
17

Satachito answer的启发,这是一种从 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 % 0x2001
            if k == 0x2000 {
                // 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]

表演

非常好,具体取决于数据/使用情况:这个内置版本的解决方案似乎比 Martin R 的解决方案或 Oliver Atkinson 的解决bitmapRepresentation方案快 2 到 10 倍。containslongCharacterIsMember

一定要根据自己的需要进行比较:性能最好在非调试版本中进行比较;所以避免在操场上比较表演。

于 2018-09-02T03:03:16.637 回答
10

由于字符有一个有限的、有限的(而不是太宽的)范围,您可以测试哪些字符是给定字符集的成员(蛮力):

// 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]];
    }
}

备注:由于 unichar 的大小和 bitmapRepresentation 中附加段的结构,此解决方案仅适用于字符 <= 0xFFFF,不适用于更高的平面。

于 2013-04-01T10:29:00.847 回答
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)");
            }
        }
    }
}
于 2015-11-25T13:24:30.177 回答
2

这是使用更多的 swift for 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)
于 2016-01-18T15:05:48.710 回答
1

你不应该; 这不是字符集的目的。ANSCharacterSet是一组可能是无限的字符,可能在尚未发明的代码点中。你想知道的只是“这个角色或角色集合在这个集合中吗?”,为此它很有用。

想象一下这段 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 字符集之外。


你真正应该问自己的问题是:“我想用这组大写字母来完成什么?” 如果(并且可能仅当)您确实需要按顺序对其进行迭代,则将您关心的那些放入Arrayor String(也许从资源文件中读取)可能是最好的方法。如果你想检查一个字符是否是大写字母集合的一部分,那么你不关心顺序,甚至不关心集合中有多少个字符,并且应该使用CharacterSet.uppercaseLetters.contains(foo)(在 Objective-C 中:)[NSCharacterSet.uppercaseLetterCharacterSet contains: foo]

也想想非拉丁字符。CharacterSet.uppercaseLetters涵盖 Unicode 通用类别LuLt,其中包含AthroughZ以及Dž,Խ. 你不想考虑这个。当 Unicode Consortium 将新字符添加到此列表中时,您绝对不想发布对您的应用程序的更新。如果您要做的是确定某些内容是否为大写,请不要费心对任何内容进行硬编码。

于 2017-11-02T15:45:25.987 回答
1

我发现 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 范围数组,但您可以轻松调整它以返回单个字符或字符串等。

于 2018-03-09T13:33:13.403 回答
0

仅针对拉丁字母的 AZ(没有带有希腊语、变音符号或其他不是该人要求的东西):

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
    }
}
于 2016-08-12T18:20:18.413 回答
0

您当然可以使用以下方法创建字符集和字母集CharacterSet

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

问题是它CharacterSet不是一个Set(尽管它符合SetAlgebra),而是一个 unicode 字符集。这会导致获取所有字符的序列,将其转换为Array.SetString. 我找到了解决方案,但存在更好的解决方案。实际上,你想要的是从一个字符到另一个字符,有一个范围“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: ", ")

但要小心:字符可以由多个标量组成。

于 2019-09-20T09:47:14.093 回答