Swift 5: строковый префикс с максимальной длиной UTF-8 - PullRequest
0 голосов
/ 10 марта 2020

У меня есть строка, которая может содержать произвольные символы Unicode, и я хочу получить префикс этой строки, длина которой в кодировке UTF-8 максимально приближена к 32 байтам , оставаясь действительным UTF-8 и не меняя значения символов (т.е. не обрезая расширенный кластер графем).

Рассмотрим этот пример ПРАВИЛЬНО :

let string = "\u{1F3F4}\u{E0067}\u{E0062}\u{E0073}\u{E0063}\u{E0074}\u{E007F}\u{1F1EA}\u{1F1FA}"
print(string)                    // ?????????
print(string.count)              // 2
print(string.utf8.count)         // 36

let prefix = string.utf8Prefix(32)  // <-- function I want to implement 
print(prefix)                    // ???????
print(prefix.count)              // 1
print(prefix.utf8.count)         // 28

print(string.hasPrefix(prefix))  // true

И этот пример реализации НЕПРАВИЛЬНО :

let string = "ar\u{1F3F4}\u{200D}\u{2620}\u{FE0F}\u{1F3F4}\u{200D}\u{2620}\u{FE0F}\u{1F3F4}\u{200D}\u{2620}\u{FE0F}"
print(string)                    // ar?‍☠️?‍☠️?‍☠️
print(string.count)              // 5
print(string.utf8.count)         // 41

let prefix = string.wrongUTF8Prefix(32)  // <-- wrong implementation 
print(prefix)                    // ar?‍☠️?‍☠️?
print(prefix.count)              // 5
print(prefix.utf8.count)         // 32

print(string.hasPrefix(prefix))  // false

Какой элегантный способ сделать это? (кроме проб и ошибок)

Ответы [ 2 ]

2 голосов
/ 11 марта 2020

Вы не пытались найти решение, и SO обычно не пишет для вас код. Итак, вместо этого вот несколько предложений алгоритмов для вас:

Какой элегантный способ сделать это? (кроме проб и ошибок)

По какому определению элегантный ? (как красота, это зависит от глаз смотрящего ...)

Простой?

Начните с String.makeIterator, напишите while l oop , добавьте Character s к своему префиксу, пока число байтов ≤ 32.

Это очень просто l oop, в худшем случае 32 итерации и 32 добавления.

«Умная» стратегия поиска?

Вы могли бы реализовать стратегию, основанную на средней длине байта каждого Character в String и использовании String.Prefix(Int).

Например, для вашего первого примера количество символов равно 2, а число байтов 36, дающее в среднем 18 байт / символ, 18 входит в 32 только один раз (мы не имеем дело с дробными символами или байтами!), Поэтому начните с Prefix(1), который имеет число байтов 28 и оставляет 1 символ и 8 байтов - так что остаток имеет среднюю длину байта 8, и вы ищете не более 4 дополнительных байтов, 8 переходит в 4 нулевых раза, и вы done.

В приведенном выше примере показан случай расширения (или нет) вашего предположения префикса. Если ваше предположение слишком длинное, вы можете просто запустить алгоритм с нуля, используя префиксный символ и количество байтов, а не исходную строку.

Если у вас возникли проблемы с реализацией вашего алгоритма, задайте новый вопрос, показывающий код, который вы ' Мы написали, опишите проблему, и кто-то, несомненно, поможет вам в следующем шаге.

HTH

1 голос
/ 11 марта 2020

Я обнаружил, что String и String.UTF8View имеют одни и те же индексы, поэтому мне удалось создать очень простое (и эффективное?) Решение, я думаю:

extension String {
    func utf8Prefix(_ maxLength: Int) -> Substring {
        if self.utf8.count <= maxLength {
            return Substring(self)
        }

        var index = self.utf8.index(self.startIndex, offsetBy: maxLength+1)
        self.formIndex(before: &index)
        return self.prefix(upTo: index)
    }
}

Объяснение ( при условии maxLength == 32 и startIndex == 0):

Первый случай (utf8.count <= maxLength) должен быть понятен, здесь не требуется никакой работы.
Во втором случае мы сначала получаем индекс utf8 33, который является либо

  • A: конечный индекс строки (если длина его ровно 33 байта),
  • B: индекс в начале символа (после 33 байт предыдущих символов)
  • C: индекс где-то посередине символа (после <33 байтов предыдущих символов) </li>

Так что если мы теперь переместим наш индекс обратно один символ (с formIndex(before:)) будет переходить к границе первого расширенного кластера графем до index, что в случае A и B является одним символом до и в C начале этого символа.
В любом случае, теперь будет гарантировано, что индекс utf8 будет максимум 32 и на расширенном графике граница кластера eme, поэтому prefix(upTo: index) создаст префикс длиной ≤32.


… но он не идеален.
Теоретически это также должно быть всегда оптимальное решение, то есть префикс count максимально приближен к maxLength, но иногда, когда строка заканчивается расширенным кластером графем, состоящим из более чем одного скалярного Unicode, formIndex(before: &index) возвращает на один символ больше, чем необходимо , поэтому префикс заканчивается короче. Я не совсем уверен, почему это так.

РЕДАКТИРОВАТЬ: Не так элегантно, но взамен полностью "правильное" решение было бы это (все еще только O (n)):

extension String {
    func utf8Prefix(_ maxLength: Int) -> Substring {
        if self.utf8.count <= maxLength {
            return Substring(self)
        }

        let endIndex = self.utf8.index(self.startIndex, offsetBy: maxLength)
        var index = self.startIndex
        while index <= endIndex {
            self.formIndex(after: &index)
        }
        self.formIndex(before: &index)
        return self.prefix(upTo: index)
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...