Sponsor Link
環境&対象
- macOS Monterey 13 Beta3
- Xcode 14.0 Beta3
- iOS 16.0 beta
文字列処理の難しさ
Swift で 文字列を処理するコードを書いていると、String を使うことが多くなります。
String で文字列中の文字を指定しようとすると、その指定方法が Int 型でないことに気づきます。
String.Index 型という型です。
この String.Index 型を扱う プロパティ/メソッドをいくつかまとめることで、String.Index の理解を深めます。
NSString とその位置指定で使われる Int 型の組み合わせでは、0開始の1刻みだったので 直感的に理解でき、変数の値を見ることで正しく処理できているか確認することも容易でした。
最近の Swift では、String を使用することが推奨されていると思いますが、Apple のいくつかのフレームワークでは、文字列の位置情報に、Int を要求してくるものがあり、どうしても、混在しがちになります。
こうなると、アプリの中での主要な処理を String.Index を使ったものにするか Int を使ったものにするか考慮することが必要となります。
# 文字列を保持するのには、NSString ではなく String が採用されると思いますが、Range/NSRange については、使用するフレームワークも考慮する必要があるため、難しいところだと思います。
# 実際、TextKit2 では、当面 NSRange を使うようです。
ということで(?)、(String/NSString で使われる) Range/NSRange や String.Index/ Int について一度まとめておくことが、この記事のゴールです。
記事が長くなりすぎたので、Range/NSRange は別記事予定です。
文字列での位置計算
この記事では、以下の文字列位置の取得・計算をまとめています。
文字の位置情報
・文字列の開始位置
・文字列の終了位置
・文字列の特定文字の位置
・文字列の特定位置の次の文字位置
・文字列の特定位置の前の文字位置
・文字列の特定位置からオフセットした文字位置
・文字列の文字位置同士の距離
位置情報
文字列を処理する時には、位置情報を使います。
「文字列に含まれる 文字 X の位置」や「文字列の X 文字目に 文字を挿入する」等です。
NSString と String では、この位置情報の扱い方が、異なります。
位置情報の意味合い
最初に、文字列の位置情報の意味を再確認しておきます。
位置情報は、文字列中の位置を指し示す時に使用します。
String では、String.Index 型を使用します。
NSString では、Int 型を使用します。
指定した文字が文字列中のどこにあるかという意味で位置を取得したり、文字列中に挿入するときにこの 位置情報を使用します。
Int, String.Index の指すもの
String.Index や Int が具体的に何を指しているかについては、以下のように、文字列中の文字の “間” を指していると考えるのがわかりやすいと思います。
以下は、String.Index の指すもののイメージ図です。
# String.Index は、0から始まる数字では無いので、具体的な数字は書けません。
NSString の Int は、0 開始ですので、以下のような具体的な数値になりますが、指すものは、(String.Index と同じように) 文字そのものではなく、文字の 間 の位置です。
位置は、文字そのものではなく、文字前後のスペースを指しているので、位置だけを指定されても、該当する文字はありません。
実際に文字列中の文字を指定するためには、「位置とそこから何文字分 」というような情報が必要になります。
現実的には、指定位置直後の文字を 「指定位置の文字」と読み替えるケースが多い気がします。
ですので、NSString の 0 位置(から始まる1文字分)の文字は “0” となります。
ですが、位置自体は、文字の間を指しているということを覚えておかないと、文字列の中で移動しながら操作を行う時に、自分の位置を見失いやすくなります。
開始位置
文字列の開始位置を取得する方法です。
たいていの文字列操作は、開始位置・終了位置・特定文字の位置 のいずれかを開始点として処理されるケースが多いと思います。
String の開始位置
String では、String の startIndex というプロパティから取得することができます。
Apple のドキュメントは、こちら。
値を見たら なんとなくわかりそうな予感がしがちですが、見てもさっぱりわかりません。
let text: String = "012345"
print(text.startIndex)
// print-out
Index(_rawBits: 15)
NSString の開始位置
NSString では、Int を位置情報に使用しているため、開始位置は 0 です。
特に参照するためのプロパティ等が用意されているわけではありません。
終了位置
文字列の終了位置も取得することが必要になるケースが多いです。
文字列走査を行う時に文字列の終端に到達しているかを確認する時等に使われます。
String の終了位置
String では、開始位置と同様に 終了位置も、endIndex というプロパティから取得することができます。
Apple のドキュメントは、こちら。
NSString の終了位置
文字列の長さは変わるため、NSString でも終了位置を取得することが必要となります。
ただし、終了位置ということではなく、文字列長という意味で、length を使用して情報を取得します。
Apple のドキュメントは、こちら。
特定文字の位置
文字列中に特定の文字が含まれるか確認し、その位置情報を取得することもできます。
String での特定文字の位置
String で、特定文字の位置情報を取得するには、Collection でよく使われている firstIndex(of:) を使用します。
Apple のドキュメントは、こちら。
返されるデータの型は、String.Index? です。指定された文字列の開始位置を返してくれます。
見つからなければ、nil が返されます。
let text: String = "012345"
let index3 = text.firstIndex(of: "3") // 3 (の直前)を指す String.Index を取得
let indexNil = text.firstIndex(of: "7") // nil が返される
NSString での特定文字の位置
NSString での文字の位置取得は、位置情報ではなく、区間情報が返されます。
使用するメソッドは、range(of:) です。
Apple のドキュメントは、こちら。
let nsStr: NSString = "012345"
print(nsStr.range(of: "3"))
// print-out
{3,1} // location3 から1文字分という意味
次の文字位置
文字列を走査していくときに、(現在の文字の) 次の文字の位置情報を取得することがよくあります。
String での次の文字位置
String では、指定位置の次の位置情報を取得するためのメソッドが用意されています。
index(after:) というメソッドです。
Apple のドキュメントは、こちら。
なお、endIndex より前の String.Index しか渡せません。(例えば、endIndex を渡すと、fatalError になります。(Playground で確認))
let text: String = "012345"
let index3 = text.firstIndex(of: "3")!
let index4 = text.index(after: index3) // 3 の次の文字 4 (の直前)の位置を指す String.Index
let fatalIndex = text.index(after: text.endIndex) // -> FatalError
NSString での次の文字位置
NSString では、位置情報は、Int 型で 次の文字の位置を表すためには、+1 をします。
let nsStr: NSString = "012345"
let pos0 = 0 // 0 (の直前)を指す
let pos1 = pos0 + 1 // 1 (の直前)を指す
前の文字位置
次の文字と同様に、1文字前の文字位置を取得することもできます。
String での前の文字位置
String では、指定位置の前の位置情報を取得するためのメソッドが用意されています。
index(before:) というメソッドです。
Apple のドキュメントは、こちら。
こちらは、startIndex より後の String.Index を渡す必要があります。
let text: String = "012345"
let index3 = text.firstIndex(of: "3")!
let index2 = text.index(before: index3) // 2 (の直前)を指す String.Index
NSString での前の文字位置
NSString では、位置情報は、Int 型で 前の文字の位置を表すためには、-1 をします。
let nsStr: NSString = "012345"
let pos0 = 0 // 0 (の直前)を指す
let pos1 = pos0 + 1 // 1 (の直前)を指す
let anotherPos0 = pos1 - 1 // 1 の前の 0 を指す
特定位置からオフセットした文字位置
現在の位置から、1つ前/1つ後 ではなくいくつか移動したい時があります。そのようにオフセットを行なった位置を取得することもできます。
String でオフセットした文字位置
String では、指定位置からオフセットした位置情報を取得するためのメソッドが用意されています。
index(_:offsetBy:) というメソッドです。
Apple のドキュメントは、こちら。
引数のオフセット量に 符号付き Int を指定することができますので、前後いずれのオフセットも計算可能です。(負であれば、文字列中の前方向、正であれば、後方向です)
結果が文字列中におさまらないようなオフセット量を与えてはいけません。
let text: String = "012345"
let index3 = text.firstIndex(of: "3")!
let index5 = text.index(index3, offsetBy: 2) // 5を指す String.Index
NSString でオフセットした文字位置
次の文字位置は +1、前の文字位置は -1 でしたので、前後の移動は、+x, -x で計算することになります。
let nsStr: NSString = "012345"
let pos0 = 0
let pos4 = pos0 + 4 // 4 を指す
文字位置同士の距離
位置情報同士の距離を計算することもできます。
String.Index 同士の距離
String では、位置情報同士の距離を計算するために、位置情報算出の元になった String が必要になります。
distance(from:, to:) というメソッドです。
Apple のドキュメントは、こちら。
let text: String = "012345"
let index3 = text.firstIndex(of: "3")!
let index5 = text.index(index3, offsetBy: 2)
let dist35 = text.distance(from: index3, to: index5) // 3 と 5 の距離は、2
距離は、符号付きで返されますので、to に与えた String.Index が from よりも前にあれば 負の Int が返されます。index(_,offsetBy:) でのオフセットと同様の扱いです。
NSString の 位置情報の距離
NSString の位置情報は、もともと Int で表していますので、Int での差をとることで、距離がわかります。
まとめ
String と String.Index, NSString と Int を使った文字列中の位置指定をまとめました。
- String は、String.Index を使って位置指定
- NSString は、Int を使って位置指定
- String.Index は、常に String のメソッドを使って、操作しないといけない
- String.Index, Int いずれも 直接文字を指しているというよりは文字の直前のスペースを指している
説明は以上です。
不明な点やおかしな点ありましたら、こちらまで。
Swift学習におすすめの本
詳解Swift
Swift の学習には、詳解 Swift という書籍が、おすすめです。
著者は、Swift の初期から書籍を出していますし、Swift の前に主力言語だった Objective-C という言語についても同様の書籍を出しています。
最新版を購入するのがおすすめです。
現時点では、上記の Swift 5 に対応した第5版が最新版です。
Sponsor Link