从一开始Swift字符串就很棘手,因为它们能够正确地使用UTF,并且有一个来自Apple的标准示例:
let cafe1 = "Cafe\u{301}"
let cafe2 = "Café"
print(cafe1 == cafe2)
// Prints "true"
这意味着比较具有一些隐含的逻辑,并不仅仅是简单地比较两个内存区域是否相同。我曾经见过一些建议将字符串拆分成[Character],因为这样做会使所有与Unicode相关的转换只发生一次,然后所有操作都更快。此外,字符串不一定使用连续的内存区域,这使得它们相比字符数组更昂贵。
简而言之,我在leetcode上解决了这个问题:https://leetcode.com/problems/implement-strstr/,并尝试了不同的方法:KMP、字符数组和字符串。令我惊讶的是,字符串是最快的。
原因是什么?KMP有一些预处理工作,总体效率较低,但为什么字符串比[Character]更快?这是某个Swift版本的新功能还是我在概念上遗漏了什么?
下面是参考用的代码:
- [Character],8毫秒,15MB内存
func strStr(_ haystack: String, _ needle: String) -> Int {
guard !needle.isEmpty else { return 0 }
guard haystack.count >= needle.count else { return -1 }
var result: Int = -1
let str = Array(haystack)
let pattern = Array(needle)
for i in 0...(str.count - pattern.count) {
if str[i] == pattern[0] && Array(str[i...(i + pattern.count - 1)]) == pattern {
result = i
break
}
}
return result
}
- 字符串,4毫秒(!!!),14.5mb内存
func strStr(_ haystack: String, _ needle: String) -> Int {
guard !needle.isEmpty else { return 0 }
guard haystack.count >= needle.count else { return -1 }
var result: Int = -1
for i in 0...(haystack.count - needle.count) {
var hIdx = haystack.index(haystack.startIndex, offsetBy: i)
if haystack[hIdx] == needle[needle.startIndex] {
var hEndIdx = haystack.index(hIdx, offsetBy: needle.count - 1)
if haystack[hIdx...hEndIdx] == needle {
result = i
break
}
}
}
return result
}
let cafe1 = Array("Cafe\u{301}"); let cafe2 = Array("Café"); print("\(cafe1[3]) \(cafe2[3])")
- Igor Kashin.unicodeScalars.count
。其中一个有两个元素,另一个只有一个元素。然而它们对于==
返回true,就像它们的字符串对应物一样。实际上,Character只是String的包装器,因此当你使用[Character]
时,你要为String付出所有的代价,再加上Array。https://github.com/apple/swift/blob/5a874822018d2150bd9ac1b79ef3397e74b250d4/stdlib/public/core/Character.swift#L65-L74但即使他们改变了实现方式,规范化成本也必须存在。 - Rob Napier