Search code examples
swiftstringsubstring

Index of a substring in a string with Swift


I'm used to do this in JavaScript:

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

Swift doesn't have this function, how to do something similar?


Solution

  • edit/update:

    Xcode 11.4 • Swift 5.2 or later

    import Foundation
    
    extension StringProtocol {
        func index<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? {
            range(of: string, options: options)?.lowerBound
        }
        func endIndex<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> Index? {
            range(of: string, options: options)?.upperBound
        }
        func indices<S: StringProtocol>(of string: S, options: String.CompareOptions = []) -> [Index] {
            ranges(of: string, options: options).map(\.lowerBound)
        }
        func ranges<S: StringProtocol>(of string: S, 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
        }
    }
    

    usage:

    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.indices(of: "play")    // [7, 19, 31]
    str.ranges(of: "play")     // [{lowerBound 7, upperBound 11}, {lowerBound 19, upperBound 23}, {lowerBound 31, upperBound 35}]
    

    case insensitive sample

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

    regular expression sample

    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"]