【问题标题】:If a sequence of code points forms a Unicode character, does every non-empty prefix of that sequence also form a valid character?如果一个代码点序列构成一个 Unicode 字符,那么该序列的每个非空前缀是否也构成一个有效字符?
【发布时间】:2020-04-19 22:07:25
【问题描述】:

我遇到的问题是,给定一个字节序列,我想确定它的最长前缀,假设 UTF8 编码,它形成一个有效的 Unicode 字符(扩展字形簇)。

我正在使用 Swift,所以我想使用 Swift 的内置函数来执行此操作。但这些函数只解码完整的字节序列。所以我想通过 Swift 转换字节序列的前缀,并采用最后一个没有失败且仅包含 1 个字符的前缀。显然,这可能会导致尝试整个字节序列,这是我想避免的。一个解决方案是在连续 4 个前缀失败后停止尝试前缀。如果我的问题中提出的属性成立,那么这将保证所有较长的前缀也必须失败。

我发现Unicode文本分割标准不可读,否则我会尝试直接实现扩展字素簇的边界检测...

【问题讨论】:

  • “实现扩展字素簇的边界检测。”哈哈你不想想那样做。那就是疯狂。老实说,使用init?(validatingUTF8:) 来检查逐渐变长的前缀并不是一个坏主意。虽然扩展的字形簇可能变得异常大(尝试将 this answer 复制到 unicode inspector 哈哈),但其中绝大多数都很小。
  • 我绝对不想这样做 :-) 但这就是我问这个问题的原因!我希望我的代码即使在错误情况下也具有可预测的性能。
  • 看起来完美的解决方案应该是像findLongestValidCharacter(in input: [UnicodeScalar]) -> Character 这样的函数。即使存在这样的功能,病态长字符仍然是一个问题。考虑到函数可能是O(input.count) 而不是蛮力方法的O(input.count^2),“坏”的情况会不会那么糟糕,但这仍然会变得很糟糕。
  • 您可以尝试深入研究标准库实现,看看它是如何破坏集群的:github.com/apple/swift/blob/…
  • 我不太担心具有很长代码点序列的实际有效字符。如果发生这种情况,哦,好吧。但在我的场景中,字节序列很可能根本不是 UTF-8 字符串。如果那个字节序列是 80GB 长,我不想全部检查来发现它是无效的。

标签: swift unicode


【解决方案1】:

在仔细研究了https://www.unicode.org/reports/tr29/#Grapheme_Cluster_Boundary_Rules 上用于计算扩展字素簇 (EGC) 边界的规范之后, 很明显,EGC 的规则都具有描述何时允许将代码点附加到现有 EGC 以形成更长的 EGC 的形式。仅从这一事实来看,我的两个问题如下:1)是的,形成 EGC 的代码点的每个非空前缀也是 EGC。 2) 不,通过将代码点添加到有效的 Unicode 字符串,您不会减少其包含的 EGC 数量的长度。

因此,鉴于此,以下 Swift 代码将从字节序列的开头提取最长的 Unicode 字符(如果那里没有有效的 Unicode 字符,则返回 nil):

    func lex<S : Sequence>(_ input : S) -> (length : Int, out: Character)? where S.Element == UInt8 {
        // This code works under three assumptions, all of which are true:
        // 1) If a sequence of codepoints does not form a valid character, then appending codepoints to it does not yield a valid character
        // 2) Appending codepoints to a sequence of codepoints does not decrease its length in terms of extended grapheme clusters
        // 3) a codepoint takes up at most 4 bytes in an UTF8 encoding
        var chars : [UInt8] = []
        var result : String = ""
        var resultLength = 0
        func value() -> (length : Int, out : Character)? {
            guard let character = result.first else { return nil }
            return (length: resultLength, out: character)
        }
        var length = 0
        var iterator = input.makeIterator()
        while length - resultLength <= 4 {
            guard let char = iterator.next() else { return value() }
            chars.append(char)
            length += 1
            guard let s = String(bytes: chars, encoding: .utf8) else { continue }
            guard s.count == 1 else { return value() }
            result = s
            resultLength = length
        }
        return value()
    }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-03-18
    • 2023-04-06
    • 2013-09-06
    • 2011-12-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-07-01
    相关资源
    最近更新 更多