はじめに
ある文字列が空かどうか調べたい時、「string.count == 0
ではなくstring.isEmpty
を使いましょう」ということは聞いたことがあるかと思います。
理由としては、String
におけるvar count: Int
は $O(n)$ だけどvar isEmpty: Bool
は $O(1)$ だからということになります1。
では、ある文字列のcount
が任意の数と等しいかどうかを調べるにはどうしたらいいでしょうか。
extension String {
func countIsEqual(to expectedCount: Int) -> Bool {
// ここを考える
}
}
実装例
単純、では△
いちばん簡単なのは次のようなものでしょう。
extension String {
func countIsEqual(to expectedCount: Int) -> Bool {
return self.count == expectedCount
}
}
let string = "Qiita"
print(string.countIsEqual(to: 1)) // -> false
print(string.countIsEqual(to: 5)) // -> true
print(string.countIsEqual(to: 10)) // -> false
でも、ちょっと待ってください。文字列が空かどうか調べるときにisEmpty
を使うのはcount
が$O(n)$だからでした。このcountIsEqual
は内部でcount
を呼んでいます。self
が高々数文字なら許容範囲でしょうが、self
が1000万文字とかになり得るのであれば無視できません。
count
を呼ばない方法を考えてみましょう。
前から数えていく
count
を使わないとなると、地道に自分で数えるしかありません。でも自分で数えていけばexpectedCount
を超えた時に数えることを打ち切ることができます。
というわけで、こうなりました:
extension String {
func countIsEqual(to expectedCount: Int) -> Bool {
guard expectedCount >= 0 else { return false }
var countNow = 0
for _ in self {
countNow += 1
if countNow > expectedCount {
return false
}
}
return countNow == expectedCount
}
}
let string = "Qiita"
print(string.countIsEqual(to: 1)) // -> false
print(string.countIsEqual(to: 5)) // -> true
print(string.countIsEqual(to: 10)) // -> false
ね、簡単でしょ?
ちなみに、これも$O(n)$ではあるのですが、この場合の $n$ は「count
とexpectedCount
の小さい方」ということになります。基本的にcount
とexpectedCount
がそんなに変わらないのであれば最初の単純な実装のほうでいいかもしれません。しかし、たとえば、外部からの入力などで(意図的か否かは別として)大きな文字列が入ってくる可能性があり、且つexpectedCount
が小さい値をとることが期待される場合は、こちらの実装の方が有用でしょう。