是否有可能编写一个Swift函数,只替换扩展的字形集群的一部分,如?

Ben*_*ero 15 string replace unicode-string emoji swift

我想编写一个可以像这样使用的函数:

let ??? = "???".replacingFirstOccurrence(of: "", with: "")
Run Code Online (Sandbox Code Playgroud)

鉴于这个字符串和Swift的String库有多奇怪,这在Swift中是否可行?

Mar*_*n R 11

根据获得的见解为什么像????这样的表情符号字符在Swift字符串中处理得如此奇怪?,一种明智的方法可能是替换Unicode标量:

extension String {
    func replacingFirstOccurrence(of target: UnicodeScalar, with replacement: UnicodeScalar) -> String {

        let uc = self.unicodeScalars
        guard let idx = uc.index(of: target) else { return self }
        let prefix = uc[uc.startIndex..<idx]
        let suffix = uc[uc.index(after: idx) ..< uc.endIndex]
        return "\(prefix)\(replacement)\(suffix)"
    }
}
Run Code Online (Sandbox Code Playgroud)

例:

let family1 = "???"
print(family1.characters.map { Array(String($0).unicodeScalars) })
// [["\u{0001F469}", "\u{200D}"], ["\u{0001F469}", "\u{200D}"], ["\u{0001F467}", "\u{200D}"], ["\u{0001F466}"]]

let family2 = family1.replacingFirstOccurrence(of: "", with: "")
print(family2) // ???
print(family2.characters.map { Array(String($0).unicodeScalars) })
// [["\u{0001F469}", "\u{200D}"], ["\u{0001F469}", "\u{200D}"], ["\u{0001F466}", "\u{200D}"], ["\u{0001F466}"]]
Run Code Online (Sandbox Code Playgroud)

这是一个可能的版本,它定位并替换任意字符串的Unicode标量:

extension String {
    func replacingFirstOccurrence(of target: String, with replacement: String) -> String {
        let uc = self.unicodeScalars
        let tuc = target.unicodeScalars

        // Target empty or too long:
        if tuc.count == 0 || tuc.count > uc.count {
            return self
        }

        // Current search position:
        var pos = uc.startIndex
        // Last possible position of `tuc` within `uc`:
        let end = uc.index(uc.endIndex, offsetBy: tuc.count - 1)

        // Locate first Unicode scalar
        while let from = uc[pos..<end].index(of: tuc.first!) {
            // Compare all Unicode scalars:
            let to = uc.index(from, offsetBy: tuc.count)
            if !zip(uc[from..<to], tuc).contains(where: { $0 != $1 }) {
                let prefix = uc[uc.startIndex..<from]
                let suffix = uc[to ..< uc.endIndex]
                return "\(prefix)\(replacement)\(suffix)"
            }
            // Next search position:
            uc.formIndex(after: &pos)
        }

        // Target not found.
        return self
    }
}
Run Code Online (Sandbox Code Playgroud)

  • @OlegGordiichuk:看看OP上一个问题http://stackoverflow.com/questions/43618487/why-is-treated-so-strangely-in-swift-strings,这正是这个问题. (4认同)

xou*_*ini 7

使用该range(of:options:range:locale:)解决方案变得非常简洁:

extension String {
    func replaceFirstOccurrence(of searchString: String, with replacementString: String) -> String {
        guard let range = self.range(of: searchString, options: .literal) else { return self }
        return self.replacingCharacters(in: range, with: replacementString)
    }
}
Run Code Online (Sandbox Code Playgroud)

这首先找到searchString实例中的范围,如果找到范围,则替换为范围replacementString.否则,实例只返回自身.并且,由于该range(of:)方法在找到匹配后立即返回,因此保证返回的范围是第一次出现的.

"221".replaceFirstOccurrence(of: "2", with: "3")                // 321
"???".replaceFirstOccurrence(of: "\u{1f469}", with: "\u{1f468}") // ???
Run Code Online (Sandbox Code Playgroud)

*为了澄清,最后一个测试案例将女性 - 女性 - 女孩 - 男孩转变为男女女孩.

  • 使用`.literal`选项确实优雅且更容易. (2认同)
  • `.literal`被记录为"Exact character-by-character equivalence",但显然"character"并不意味着在这种情况下的Swift`Character`.我的猜测是它实际上意味着"精确的Unicode标量等价"或"精确的UTF-16等价"(因为`.literal`在`NSString.CompareOptions`中定义,而`NSString`基于`unichar`). (2认同)