Подтвердить что ты не робот

Можно ли написать функцию Swift, которая заменяет только часть расширенного кластера графем, например ?????

Я хочу написать функцию, которая может быть использована следующим образом:

let 👩‍👩‍👧‍👦 = "👩‍👩‍👧‍👧".replacingFirstOccurrence(of: "👧", with: "👦")

Учитывая как странно и эта строка, и Swift String библиотека, возможно ли это в Swift?

4b9b3361

Ответ 1

Используя 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)
    }
}

Это работает, сначала обнаруживая диапазон searchString внутри экземпляра, и если найден диапазон, диапазон заменяется на replacementString. В противном случае экземпляр просто возвращает себя. И поскольку метод range(of:) возвращает, как только он находит совпадение, возвращаемый диапазон гарантированно будет первым вхождением.

"221".replaceFirstOccurrence(of: "2", with: "3")                // 321
"👩‍👩‍👧‍👦".replaceFirstOccurrence(of: "\u{1f469}", with: "\u{1f468}") // 👨‍👩‍👧‍👦

* Чтобы уточнить, последний тестовый случай превращает женщину-женщину-девочку в мужчину-женщину-девочку.

Ответ 2

Основываясь на знаниях, полученных в Почему символы emoji, такие как 👩👩👧👦 так странно относятся к строкам 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)"
    }
}

Пример:

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

И вот возможная версия, которая находит и заменяет скаляры 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
    }
}