

Swift2で文字列検索で何番目に指定の文字列があるか調べる方法はありますか。

(例)  
"あいうえおかきくけこさしすせそ"  
上記のようなStringがあり、この中から「"かきく"」が何番目にあるか調べる

このようなことは可能でしょうか。方法を知りたいです。

↓

Le Pere d'OO 様のご回答を参考に以下のように実装しました。  
文字列（中身はHTMLのソース）の中からimgタグのsrcの中身を取得するというものです。

[code]

    let imageHTML: String = "<hoge>hugahuga</hoge><img src="payapaya.jpg"><p>hunyahunya</p>.........."
    guard let range = imageHTML.rangeOfString("src=") else {return}
    let start: Int = imageHTML.startIndex.distanceTo(range.startIndex)
    let imageHTML2 = (imageHTML as NSString).substringFromIndex(start+5)
    guard let range2 = imageHTML2.rangeOfString("\"") else {return}
    let start2: Int = imageHTML2.startIndex.distanceTo(range2.startIndex)
    let imageURL = (imageHTML2 as NSString).substringToIndex(start2)
    print(imageURL) // payapaya.jpg
    
[/code]

汚いソースですがこれで一応取得することができました。  
いい書き方があれば教えていただけるとうれしいです。


CC BY-SA 3.0
2016-03-05T17:01:10.373
22838
2017-12-08T12:58:02.580
2016-03-06T06:25:26.427
12389
12389
question
swift swift2
文字列中に指定の文字列が何番目にあるか調べる方法

[code]

    let str = "あいうえおかきくけこさしすせそ"
    let range = str.rangeOfString("かきく")
    print(range) // 5..<8
    
[/code]

StringクラスのrangeOfStringメソッドで可能です。


CC BY-SA 3.0
2016-03-05T20:32:28.913
22840
2016-03-05T20:32:28.913
14518
22838
answer


補足的事項なのでコメント欄に書こうと思ったのですが、コメントにするには長すぎるようなのでこちらに。

Stringクラスの(Foundation拡張にある)`rangeOfString(_:)`メソッドを使う場合の注意点ですが、結果は`Range<Int>`型ではなく`Optional<Range<String.CharacterView.Index>>`型なんてものになります。Optionalを適切にはがすのはSwiftプログラマーなら常識として割愛するにしても、Rangeの要素型である`String.CharacterView.Index`型は取り扱い要注意なので少し書き添えておきます。

[code]

    let str: String = "あいうえおかきくけこさしすせそ"
    if let range = str.rangeOfString("かきく") {
        let start: Int = range.startIndex //->error: cannot convert value of type 'Index' (aka 'String.CharacterView.Index') to specified type 'Int'
    }
    
[/code]

やってみればすぐにわかるのですが、`String.CharacterView.Index`型の値である`range.startIndex`はInt型の変数に代入することはできません。

他にもInt型との加減算もできませんし、printで表示される整数値はあくまでも内部表現として使っている値がそのまま表示されているだけ(この辺りは困った仕様です)なので、それが本当に「文字数」を表すものと考えてはいけません。

[code]

    let str2 = "あいうえお\u{1f48c}かきくけこ"
    if let range = str2.rangeOfString("かきく") {
        let start = range.startIndex
        print(start) //->7 (先頭を0とするなら「文字数」で数えると6のはず)
    }
    
[/code]

質問者さんの記述だけでは「何番目にあるか調べ」た後にその値をどう使うのか記載されていませんが、もし整数値として取り出したいのなら、こんな面倒くさい書き方をする必要があります。

[code]

    if let range = str.rangeOfString("かきく") {
        let nth: Int = str.startIndex.distanceTo(range.startIndex)
        print(nth) //->5
    }
    if let range = str2.rangeOfString("かきく") {
        let nth: Int = str2.startIndex.distanceTo(range.startIndex)
        print(nth) //->6
    }
    
[/code]

さて、以上ではInt型として「文字数」では何番目にあたるかを得る方法を書いたわけですが、逆にInt型にしてしまうと、今度はStringのsubscript(`[]`でのアクセス)等には使えなくなってしまいます。

と言うわけで、Swiftで文字列中の「位置」や「文字数」を考える場合、どの単位(Character, unicodeScalar, utf16,
utf8)で数えるのかと、どんなデータ型が欲しいのか(後でどんな用途に使うのか)を意識しておかないといけません。ご注意ください。

(追記部分について)

  * NSStringの`substring...`系メソッドに渡す位置や長さはutf16単位で数えたものでないといけません。私が示したコード例は`Swift.Character`単位でカウントするので、そのままだと現れる文字によっては誤動作します。
  * StringクラスのFoundation拡張には`String.CharacterView.Index`型(とか、そのRange型)を受け取るメソッドがあるので、そちらを使うならInt型に変換する必要も無くなりますし、`Swift.Character`単位かutf16単位かで誤動作を招く心配も無くなります。

と言うわけでその点を踏まえてあなたのコードを書き直すとこんな感じになります。

[code]

    let imageHTML: String = "<hoge>hugahuga</hoge><img src=\"payapaya.jpg\"><p>hunyahunya</p>.........."
    guard let range = imageHTML.rangeOfString("src=") else {return}
    let start = range.startIndex.advancedBy(5)
    guard let range2 = imageHTML.rangeOfString("\"", range: start..<imageHTML.endIndex) else {return}
    let start2 = range2.startIndex
    let imageURL = imageHTML.substringWithRange(start..<start2)
    print(imageURL) // payapaya.jpg
    
[/code]

ただ、掲載された例のように特定の文字列の中からパターンにマッチする一部分を抜き取るのでしたら、正規表現を採用した方がいいかもしれません。

[code]

    let imageHTML: String = "<hoge>hugahuga</hoge><img src=\"payapaya.jpg\"><p>hunyahunya</p>.........."
    let regex = try! NSRegularExpression(pattern: "src=\"([^\"]*)\"", options: [])
    if let match = regex.firstMatchInString(imageHTML, options: [], range: NSRange(0..<imageHTML.utf16.count)) {
        let imageURL = (imageHTML as NSString).substringWithRange(match.rangeAtIndex(1))
        print(imageURL)
    }
    
[/code]

正規表現に関しては、元のご質問と大きくずれてきますので、お試しの上でわからない点があれば、あらためて質問スレを立てられた方がいいでしょう。

* * *

せっかく Yuki Inoue さんからコメントをいただいたので、上記2つのコードのSwift4版を載せておきます。Swift 3でも、Swift
4でも文字列の取り扱いにはあれこれと修正が加えられました。

`range(of:)`メソッド(以前の`rangeOfString(_:)`)を使う方

[code]

    let imageHTML: String = "<hoge>hugahuga</hoge><img src=\"payapaya.jpg\"><p>hunyahunya</p>.........."
    guard let range = imageHTML.range(of: "src=") else {return}
    //Swift3以降で文字列のIndexと整数値の「加減算」には、元の文字列のメソッドを使用
    let start = imageHTML.index(range.lowerBound, offsetBy: 5)
    guard let range2 = imageHTML.range(of: "\"", range: start..<imageHTML.endIndex) else {return}
    let start2 = range2.lowerBound
    //Swift4で部分文字列を得るには添え字構文を使う。これだけだと`imageURLSubstr`は`String`型ではなく、`Substring`型になる
    let imageURLSubstr = imageHTML[start..<start2]
    print(imageURLSubstr, type(of: imageURLSubstr)) //-> payapaya.jpg Substring
    //`Substring`を`String`に変換したいのであれば、イニシャライザ構文を使う
    let imageURLStr = String(imageURLSubstr)
    print(imageURLStr, type(of: imageURLStr)) //-> payapaya.jpg String
    
[/code]

正規表現を使う方

[code]

    let imageHTML: String = "<hoge>hugahuga</hoge><img src=\"payapaya.jpg\"><p>hunyahunya</p>.........."
    let regex = try! NSRegularExpression(pattern: "src=\"([^\"]*)\"")
    if let match = regex.firstMatch(in: imageHTML, options: [], range: NSRange(0..<imageHTML.utf16.count)) {
        //Swift4 でUTF-16ベースのNSRangeをSwiftのRange<String.Index>に変換する方法
        let range = Range(match.range(at: 1), in: imageHTML)!
        let imageURLSubstr = imageHTML[range]
        print(imageURLSubstr, type(of: imageURLSubstr)) //-> payapaya.jpg Substring
        let imageURLStr = String(imageURLSubstr)
        print(imageURLStr, type(of: imageURLStr)) //-> payapaya.jpg String
    }
    
[/code]


CC BY-SA 3.0
2016-03-05T23:21:21.083
22841
2017-12-08T12:58:02.580
2017-12-08T12:58:02.580
13972
13972
22838
answer