Swift5 で文字列検索

久しぶりに Swift で文字列検索処理を書こうとして、 index(of:) を使おうとしたら、思うような結果が得られず、ネットを検索しても新旧の情報が入り交じっていて、よく分からなかったので、まとめた。

配列の最初に一致したインデックス番号を取得

index(of:) → firstIndex(of:)
メソッド名が変更されているようだが、結果を利用することができなかった。
文字列から文字を検索して、単純にインデックス番号が取得したかったのだが、そういうものではないらしい。 😂
Index とか Range とかの意味を整理する必要があって、数分で内容を理解して使用することができなかった。

文字列から文字を検索できるけど…

let str = "abcabc"
let firstIndex = str.firstIndex(of: "c")
print(type(of: firstIndex), firstIndex ?? 0)

出力結果 :
Optional Index(_rawBits: 131329)

Index型は、文字列の範囲指定に使用するものらしい…

文字列から文字を検索して、出現するインデックスを取得

以下の方法であれば、インデックス番号が取得できる。

let str = "abcabc"
let indexes = str.enumerated().compactMap { $0.element == "c" ? $0.offset : nil }
print(type(of: indexes), indexes)
// compactMap は配列のデータから nil を取り除きたい場合に使用する関数

出力結果 :
Array [2, 5]

NSRange で文字列中で指定した文字列が最初に出現する範囲を検索して取得

NSRange で取得するのが便利そう。

var str = "abcdef"
let range = NSString(string: str).range(of: "cde")
print("location:", range.location, "length:", range.length)

出力結果 :
location: 2 length: 3

文字の出現回数を正規表現で取得

こちらも便利です。

let string = "abcdefgabcdefg"
let pattern = NSRegularExpression.escapedPattern(for: "a")
let regex = try! NSRegularExpression(pattern: pattern, options: .caseInsensitive)
print(regex.numberOfMatches(in: string, range: NSRange(0..<string.utf16.count)))

出力結果 :
2

Swiftの範囲についてもよく知らなかったので、まとめた。

これだけ知っていれば良いと思う。

Range0..<30 ~ 2
ClosedRange0…30 ~ 3
let range1 = 0..<3
print("type: \(type(of: range1))")
print("lower:\(range1.lowerBound) upper:\(range1.upperBound) count:\(range1.count)")
print("range1.contains(0) \(range1.contains(0))")
print("range1.contains(1) \(range1.contains(1))")
print("range1.contains(2) \(range1.contains(2))")
print("range1.contains(3) \(range1.contains(3))")
print("range1.contains(4) \(range1.contains(4))")

出力結果 :
type: Range<Int>
lower:0 upper:3 count:3
range1.contains(0) true
range1.contains(1) true
range1.contains(2) true
range1.contains(3) false
range1.contains(4) false
let range2 = 0...3
print("type: \(type(of: range2))")
print("lower:\(range2.lowerBound) upper:\(range2.upperBound) count:\(range2.count)")
print("range2.contains(0) \(range2.contains(0))")
print("range2.contains(1) \(range2.contains(1))")
print("range2.contains(2) \(range2.contains(2))")
print("range2.contains(3) \(range2.contains(3))")
print("range2.contains(4) \(range2.contains(4))")

出力結果 :
type: ClosedRange<Int>
lower:0 upper:3 count:4
range2.contains(0) true
range2.contains(1) true
range2.contains(2) true
range2.contains(3) true
range2.contains(4) false