はじめに
ある文字列が空かどうか調べたい時、「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が小さい値をとることが期待される場合は、こちらの実装の方が有用でしょう。