Ответ 1
A Swift String
представляет собой набор из Character
s, a Character
представляет собой один расширенный кластер графем, который может быть одним или несколькими
Сканеры Unicode. Это делает некоторые операции индекса, такие как "пропустить первые N символов" медленными.
Но первое улучшение заключается в "коротком замыкании" isPalindrome()
функция. Вместо того, чтобы полностью построить обратную строку, сравните
последовательность символов с измененной последовательностью и остановка как только
как различие найдено:
func isPalindrome(_ s: String) -> Bool {
return !zip(s.characters, s.characters.reversed()).contains { $0 != $1 }
}
s.characters.reversed()
не создает новую коллекцию в обратном порядке
order, он просто перечисляет персонажей изнутри назад.
Однако с String(s.characters.reversed())
, как в вашем методе,
вы принудительно создаете новую коллекцию для инвертированной строки,
что замедляет работу.
Для строки с 110 символами
let string = String(repeating: "Hello world", count: 10)
это уменьшает время вычисления от 6 сек до 1,2 с в моем тесте.
Затем избегайте вычислений индекса, например
let endIndex = string.index(string.startIndex, offsetBy: length-1)
и вместо этого перебираем сам индекс символа:
func partition(_ s: String) -> [[String]] {
var result: [[String]] = []
func dfs(string: String, partiton: [String]) {
if string.isEmpty {
result.append(partiton)
return
}
var idx = string.startIndex
repeat {
string.characters.formIndex(after: &idx)
let part = string.substring(to: idx)
if isPalindrome(part) {
let leftPart = string.substring(from: idx)
dfs(string: leftPart, partiton: partiton + [part])
}
} while idx != string.endIndex
}
func isPalindrome(_ s: String) -> Bool {
return !zip(s.characters, s.characters.reversed()).contains { $0 != $1 }
}
dfs(string: s, partiton: [])
return result
}
Время вычисления составляет 0,7 с.
Следующий шаг - полностью исключить индексацию строк и работать с массив символов, потому что индексирование массива выполняется быстро. Даже лучше, использовать срезы массива, которые быстро создают и ссылаются на оригинал элементы массива:
func partition(_ s: String) -> [[String]] {
var result: [[String]] = []
func dfs(chars: ArraySlice<Character>, partiton: [String]) {
if chars.isEmpty {
result.append(partiton)
return
}
for length in 1...chars.count {
let part = chars.prefix(length)
if isPalindrome(part) {
let leftPart = chars.dropFirst(length)
dfs(chars: leftPart, partiton: partiton + [String(part)])
}
}
}
func isPalindrome(_ c: ArraySlice<Character>) -> Bool {
return !zip(c, c.reversed()).contains { $0 != $1 }
}
dfs(chars: ArraySlice(s.characters), partiton: [])
return result
}
Время вычисления составляет 0,08 с.
Если ваша строка содержит только символы в "базовой многоязычной плоскости" (т.е. <= U + FFFF), вы можете вместо этого работать с кодовыми точками UTF-16:
func partition(_ s: String) -> [[String]] {
var result: [[String]] = []
func dfs(chars: ArraySlice<UInt16>, partiton: [String]) {
if chars.isEmpty {
result.append(partiton)
return
}
for length in 1...chars.count {
let part = chars.prefix(length)
if isPalindrome(part) {
let leftPart = chars.dropFirst(length)
part.withUnsafeBufferPointer {
dfs(chars: leftPart, partiton: partiton + [String(utf16CodeUnits: $0.baseAddress!, count: length)])
}
}
}
}
func isPalindrome(_ c: ArraySlice<UInt16>) -> Bool {
return !zip(c, c.reversed()).contains { $0 != $1 }
}
dfs(chars: ArraySlice(s.utf16), partiton: [])
return result
}
Время вычисления составляет 0,04 с для тестовой строки 110 символов.
Итак, некоторые советы, которые потенциально могут улучшить производительность при работе со строками Swift, это
- Итерации по символам/индексам последовательно. Избегайте "прыгать" к n-й позиции.
- Если вам нужен "случайный" доступ ко всем символам, преобразуйте строку к массиву в первую очередь.
- Работа с представлением UTF-16 строки может быть более быстрой, чем работа с видом символов.
Конечно, это зависит от фактического использования. В этом приложении, мы смогли сократить время вычислений с 6 с до 0,04 с, что составляет 150.