web-dev-qa-db-ja.com

Swiftを含む文字列内の部分文字列のインデックス

私はJavaScriptでこれを行うのに慣れています:

var domains = "abcde".substring(0, "abcde".indexOf("cd")) // Returns "ab"

Swiftにはこの機能がありません、同様のことをする方法は?

52
Armand Grillet

編集/更新:

Xcode 10.2.x•Swift 5以降

extension StringProtocol { // for Swift 4.x syntax you will needed also to constrain the collection Index to String Index - `extension StringProtocol where Index == String.Index`
    func index(of string: Self, options: String.CompareOptions = []) -> Index? {
        return range(of: string, options: options)?.lowerBound
    }
    func endIndex(of string: Self, options: String.CompareOptions = []) -> Index? {
        return range(of: string, options: options)?.upperBound
    }
    func indexes(of string: Self, options: String.CompareOptions = []) -> [Index] {
        var result: [Index] = []
        var startIndex = self.startIndex
        while startIndex < endIndex,
            let range = self[startIndex...].range(of: string, options: options) {
                result.append(range.lowerBound)
                startIndex = range.lowerBound < range.upperBound ? range.upperBound :
                    index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
        }
        return result
    }
    func ranges(of string: Self, options: String.CompareOptions = []) -> [Range<Index>] {
        var result: [Range<Index>] = []
        var startIndex = self.startIndex
        while startIndex < endIndex,
            let range = self[startIndex...].range(of: string, options: options) {
                result.append(range)
                startIndex = range.lowerBound < range.upperBound ? range.upperBound :
                    index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
        }
        return result
    }
}

使用法:

let str = "abcde"
if let index = str.index(of: "cd") {
    let substring = str[..<index]   // ab
    let string = String(substring)
    print(string)  // "ab\n"
}

let str = "Hello, playground, playground, playground"
str.index(of: "play")      // 7
str.endIndex(of: "play")   // 11
str.indexes(of: "play")    // [7, 19, 31]
str.ranges(of: "play")     // [{lowerBound 7, upperBound 11}, {lowerBound 19, upperBound 23}, {lowerBound 31, upperBound 35}]

大文字と小文字を区別しないサンプル

let query = "Play"
let ranges = str.ranges(of: query, options: .caseInsensitive)
let matches = ranges.map { str[$0] }   //
print(matches)  // ["play", "play", "play"]

正規表現サンプル

let query = "play"
let escapedQuery = NSRegularExpression.escapedPattern(for: query)
let pattern = "\\b\(escapedQuery)\\w+"  // matches any Word that starts with "play" prefix

let ranges = str.ranges(of: pattern, options: .regularExpression)
let matches = ranges.map { str[$0] }

print(matches) //  ["playground", "playground", "playground"]
92
Leo Dabus

Swift 4.2/4.1/4.0/3.0でテスト済み

String[Range<String.Index>] subscriptを使用すると、サブ文字列を取得できます。範囲を作成するには開始インデックスと最後のインデックスが必要で、以下のようにできます

let str = "abcde"
if let range = str.range(of: "cd") {
  let substring = str[..<range.lowerBound] // or str[str.startIndex..<range.lowerBound]
  print(substring)  // Prints ab
}
else {
  print("String not present")
}

この演算子..<の開始インデックスを定義しない場合、開始インデックスが使用されます。 str[str.startIndex..<range.lowerBound]の代わりにstr[..<range.lowerBound]を使用することもできます

36

Swift 4で:

文字列内の文字のインデックスを取得する:

let str = "abcdefghabcd"
if let index = str.index(of: "b") {
   print(index) // Index(_compoundOffset: 4, _cache: Swift.String.Index._Cache.character(1))
}

Swift 4を使用して、StringからSubString(プレフィックスとサフィックス)を作成します。

let str : String = "ilike"
for i in 0...str.count {
    let index = str.index(str.startIndex, offsetBy: i) // String.Index
    let prefix = str[..<index] // String.SubSequence
    let suffix = str[index...] // String.SubSequence
    print("prefix \(prefix), suffix : \(suffix)")
}

出力

prefix , suffix : ilike
prefix i, suffix : like
prefix il, suffix : ike
prefix ili, suffix : ke
prefix ilik, suffix : e
prefix ilike, suffix : 

2つのインデックス間の部分文字列を生成する場合は、次を使用します。

let substring1 = string[startIndex...endIndex] // including endIndex
let subString2 = string[startIndex..<endIndex] // excluding endIndex
6
Ashis Laha

Swiftでこれを行うことは可能ですが、より多くの行を必要とします。ここでは、期待されることを実行する関数indexOf()があります。

func indexOf(source: String, substring: String) -> Int? {
    let maxIndex = source.characters.count - substring.characters.count
    for index in 0...maxIndex {
        let rangeSubstring = source.startIndex.advancedBy(index)..<source.startIndex.advancedBy(index + substring.characters.count)
        if source.substringWithRange(rangeSubstring) == substring {
            return index
        }
    }
    return nil
}

var str = "abcde"
if let indexOfCD = indexOf(str, substring: "cd") {
    let distance = str.startIndex.advancedBy(indexOfCD)
    print(str.substringToIndex(distance)) // Returns "ab"
}

この関数は最適化されていませんが、短い文字列を処理します。

4
Armand Grillet

Swiftバージョン3では、Stringには次のような関数はありません-

str.index(of: String)

部分文字列にインデックスが必要な場合、その方法の1つは範囲を取得することです。範囲を返す文字列には次の関数があります-

str.range(of: <String>)
str.rangeOfCharacter(from: <CharacterSet>)
str.range(of: <String>, options: <String.CompareOptions>, range: <Range<String.Index>?>, locale: <Locale?>)

たとえば、strで最初に再生されるインデックスを見つけるには

var str = "play play play"
var range = str.range(of: "play")
range?.lowerBound //Result : 0
range?.upperBound //Result : 4

注:範囲はオプションです。文字列が見つからない場合は、nilになります。例えば

var str = "play play play"
var range = str.range(of: "Zoo") //Result : nil
range?.lowerBound //Result : nil
range?.upperBound //Result : nil
2
Abhinav Arora

NSRangeの使用を検討しましたか?

if let range = mainString.range(of: mySubString) {
  //...
}
1
Kirill Kudaev

ここには、密接に関連する3つの問題があります。

  • Cocoa NSStringの世界では、すべての部分文字列検索メソッドが終了しています(Foundation)

  • Foundation NSRangeはSwift Rangeと一致しません。前者は開始と長さを使用し、後者はエンドポイントを使用します

  • 一般的に、Swift文字はIntではなくString.Indexを使用してインデックス付けされますが、Foundation文字areはIntを使用してインデックス付けされ、それらの間の単純な直接翻訳はありません(FoundationとSwiftは、キャラクターを構成するものについて異なる考えを持っています)

以上を踏まえて、書き方について考えてみましょう。

func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
    // ?
}

String Foundationメソッドを使用して、サブストリングs2sで検索する必要があります。結果の範囲は、NSRangeとして(これはFoundationメソッドですが)notとして返されますが、String.IndexのRangeとして(オプションでラップされた場合は、 tは部分文字列を見つけます)。ただし、他の番号fromはIntです。したがって、両方を含む範囲を形成することはできません。

しかし、そうする必要はありません! String.Indexを取るメソッドを使用して元の文字列のendを切り取り、元の文字列のstartを使用して切り取りますIntを取るメソッド。幸いなことに、そのような方法が存在します!このような:

func substring(of s: String, from:Int, toSubstring s2 : String) -> Substring? {
    guard let r = s.range(of:s2) else {return nil}
    var s = s.prefix(upTo:r.lowerBound)
    s = s.dropFirst(from)
    return s
}

または、次のように、このメソッドを文字列に直接適用できるようにする場合は...

let output = "abcde".substring(from:0, toSubstring:"cd")

...次に、Stringの拡張機能にします。

extension String {
    func substring(from:Int, toSubstring s2 : String) -> Substring? {
        guard let r = self.range(of:s2) else {return nil}
        var s = self.prefix(upTo:r.lowerBound)
        s = s.dropFirst(from)
        return s
    }
}
1
matt