有什么合理的方法可以访问 CharacterSet 的内容吗?

Is there any reasonable way to access the contents of a CharacterSet?

对于随机字符串生成器,我认为使用 CharacterSet 作为要使用的字母表的输入类型会很好,因为 CharacterSet.lowercaseLetters 等预定义集显然很有用(即使它们可能包含比您预期的更多样的字符集)。

但是,显然您只能查询字符集的成员资格,而不能枚举它们,更不用说索引它们了。我们得到的只是 _.bitmapRepresentation,一个 8kb 的数据块,每个 (?) 字符都有一个指示位。但是,即使您通过索引 i 剥离单个位(这不太好,通过面向字节的 Data),Character(UnicodeScalar(i)) 也不会给出正确的字母。这意味着格式有些模糊——当然,它是 not documented.

当然我们可以 iterate over all characters (per plane) 但从成本角度来看,这是一个坏主意:20 个字符的集合可能需要迭代数万个字符。用 CS 术语来说:位向量是稀疏集的(非常)糟糕的实现。为什么他们选择在这里进行这种权衡,我不知道。

我是不是遗漏了什么,或者 CharacterSet 只是 Foundation API 中的另一个死胡同?

根据您的定义,不,没有 "reasonable" 方式。这就是 NSCharacterSet 存储它的方式。它针对测试成员资格进行了优化,而不是枚举所有成员。

你的循环可以在代码点上增加一个计数器,或者它可以移动位(每个代码点一个),但无论哪种方式你都必须循环和测试。我的 Mac 上最高的 "Ll" 字符是 U+1D7CB (#120,779),所以如果你想在运行时计算这个字符列表,你的代码将必须至少循环那么多次。有关如何组织位向量的详细信息,请参阅 Objective-C version of the documentation

好消息是速度很快。在我 10 岁的 Mac 上使用未经优化的代码,找到所有 1,841 lowercaseLetters 只需不到 1/10 秒。如果这仍然不够快,可以通过在启动时在后台执行一次来轻松隐藏成本。

bitmapRepresentation 已记录。

https://developer.apple.com/documentation/foundation/nscharacterset/1417719-bitmaprepresentation

因此像下面这样迭代该数据:

var offset = 0
for ( var i, w ) in CharacterSet.whitespaces.bitmapRepresentation.enumerated() {
    if i % 8193 == 8192 {
        offset += 1
        continue
    }
    i -= offset
    if w != 0 {
        for j in 0 ..< 8 {
            if w & ( 1 << j ) != 0 {
                print( String( format:"%02X", i * 8 + j ) )
            }
        }
    }
}

结果:

09
20
A0
1680
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
200A
200B
202F
205F
3000

the documentation 之后,这里是对 Satachito 的改进,通过实际考虑平面索引来支持非连续平面的情况:

extension CharacterSet {
    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
    }

    func printHexValues() {
        codePoints().forEach { print(String(format:"%02X", [=10=])) }
    }
}

用法

print("whitespaces:")
CharacterSet.whitespaces.printHexValues()
print()
print("two characters from different planes:")
CharacterSet(charactersIn: "").printHexValues()

结果

whitespaces:
09
20
A0
1680
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
200A
200B
202F
205F
3000

two characters from different planes:
1D6A8
CC791

演出

这实际上比遍历所有字符快 3 到 10 倍:与之前在 NSArray from NSCharacterset 的答案进行比较。