1. ホーム
  2. string

[解決済み】Swift Stringで文字のインデックスを検索する

2022-04-08 16:04:18

質問

そろそろ負けを認めてもいいのでは...。

Objective-Cでは、こんなのが使えました。

NSString* str = @"abcdefghi";
[str rangeOfString:@"c"].location; // 2

Swiftでは、似たようなものがありますね。

var str = "abcdefghi"
str.rangeOfString("c").startIndex

...しかし、これでは String.Index これは、元の文字列に添字で戻すには使えますが、位置を取り出すには使えません。

ちなみに、その String.Index というプライベートなivarを持っています。 _position には正しい値が入っています。ただ、それがどのように公開されているのかがわからないのです。

自分で簡単にStringに追加できるのは分かっています。それよりも、この新しいAPIで何が欠けているのかが気になります。

どのように解決するのですか?

解決策が見つからないのは、あなただけではありません。

String は実装されていません。 RandomAccessIndexType . おそらく、異なるバイト長の文字を有効にするためでしょう。そのため、私たちは string.characters.count ( count または countElements Swift 1.xでは) を使って文字数を取得します。それはポジションにも当てはまります。そのため _position は、おそらくバイトの生の配列へのインデックスであり、それを公開したくないのでしょう。そのため String.Index は、文字の途中のバイトにアクセスするのを防ぐためのものです。

つまり、取得するインデックスは必ず String.startIndex または String.endIndex ( String.Index 実装 BidirectionalIndexType ). その他のインデックスを作成するには successor または predecessor メソッドを使用します。

さて、インデックスを助けるために、メソッド(Swift 1.xでは関数)の集合があります。

スイフト4.x

let text = "abc"
let index2 = text.index(text.startIndex, offsetBy: 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let characterIndex2 = text.index(text.startIndex, offsetBy: 2)
let lastChar2 = text[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

Swift 3.0

let text = "abc"
let index2 = text.index(text.startIndex, offsetBy: 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let characterIndex2 = text.characters.index(text.characters.startIndex, offsetBy: 2)
let lastChar2 = text.characters[characterIndex2] //will do the same as above

let range: Range<String.Index> = text.range(of: "b")!
let index: Int = text.distance(from: text.startIndex, to: range.lowerBound)

Swift 2.x

let text = "abc"
let index2 = text.startIndex.advancedBy(2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!
let lastChar2 = text.characters[index2] //will do the same as above

let range: Range<String.Index> = text.rangeOfString("b")!
let index: Int = text.startIndex.distanceTo(range.startIndex) //will call successor/predecessor several times until the indices match

Swift 1.x

let text = "abc"
let index2 = advance(text.startIndex, 2) //will call succ 2 times
let lastChar: Character = text[index2] //now we can index!

let range = text.rangeOfString("b")
let index: Int = distance(text.startIndex, range.startIndex) //will call succ/pred several times

との連携 String.Index は面倒ですが、ラッパーを使用して整数でインデックスを作成します ( https://stackoverflow.com/a/25152652/669586 ) は、本当のインデックスの非効率性を隠してしまうので、危険です。

なお、Swiftのインデックス実装には ある文字列のために作成されたインデックス/レンジは、別の文字列に確実に使用することはできません。 といった具合です。

Swift 2.x

let text: String = "abc"
let text2: String = "????????????"

let range = text.rangeOfString("b")!

//can randomly return a bad substring or throw an exception
let substring: String = text2[range]

//the correct solution
let intIndex: Int = text.startIndex.distanceTo(range.startIndex)
let startIndex2 = text2.startIndex.advancedBy(intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]

Swift 1.x

let text: String = "abc"
let text2: String = "????????????"

let range = text.rangeOfString("b")

//can randomly return nil or a bad substring 
let substring: String = text2[range] 

//the correct solution
let intIndex: Int = distance(text.startIndex, range.startIndex)    
let startIndex2 = advance(text2.startIndex, intIndex)
let range2 = startIndex2...startIndex2

let substring: String = text2[range2]