Индекс подстроки в строке с Swift

Я использую это в JavaScript:

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

У Swift нет этой функции, как сделать что-то подобное?

Ответ 1

изменить/обновить:

Xcode 11 • Swift 5.1 или более поздняя версия

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] {
        var indices: [Index] = []
        var startIndex = self.startIndex
        while startIndex < endIndex,
            let range = self[startIndex...]
                .range(of: string, options: options) {
                indices.append(range.lowerBound)
                startIndex = range.lowerBound < range.upperBound ? range.upperBound :
                    index(range.lowerBound, offsetBy: 1, limitedBy: endIndex) ?? endIndex
        }
        return indices
    }
    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
    }
}

Использование:

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

образец без учета регистра

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

Ответ 2

Используя индекс String[Range<String.Index>], вы можете получить подстроку. Вам нужен начальный индекс и последний индекс, чтобы создать диапазон, и вы можете сделать это, как показано ниже

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]

Ответ 3

В Swift 4:

Получение указателя символа в строке:

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

Создание SubString (префикс и суффикс) из String с помощью Swift 4:

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 : 

Если вы хотите сгенерировать подстроку между двумя индексами, используйте:

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

Ответ 4

Выполнение этого в 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"
}

Эта функция не оптимизирована, но выполняет задание для коротких строк.

Ответ 5

В версии Swift версии 3 у String нет таких функций, как -

str.index(of: String)

Если для подстроки требуется индекс, один из способов - получить диапазон. У нас есть следующие функции в строке, которая возвращает диапазон -

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

Примечание: диапазон является необязательным. Если он не сможет найти String, он сделает его нулевым. Например

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

Ответ 6

Здесь есть три тесно связанных вопроса:

  • Все методы поиска подстроки закончились в мире Cocoa NSString (Фонд)

  • Фонд NSRange имеет несоответствие с Swift Range; первый использует начало и длину, последний использует конечные точки

  • В общем, символы Swift индексируются с использованием String.Index, а не Int, но символы Foundation индексируются с использованием Int, и между ними нет простого прямого перевода (поскольку Foundation и Swift имеют разные представления о том, что составляет символ)

Учитывая все это, давайте подумаем, как написать:

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

Подстрока s2 должна быть найдена в s использованием метода String Foundation. Полученный диапазон возвращается к нам не как NSRange (даже если это метод Foundation), а как Range из String.Index (заключенный в необязательный String.Index, если мы вообще не нашли подстроку). Тем не менее, другой номер, from, является Int. Таким образом, мы не можем сформировать какой-либо диапазон, включающий их обоих.

Но мы не должны! Все, что нам нужно сделать, это отрезать конец нашей исходной строки, используя метод, который принимает String.Index, и отрезать начало нашей исходной строки, используя метод, который принимает 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
    }
}

Ответ 7

Рассматривали ли вы использовать NSRange?

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

Ответ 8

Лео Дабус ответ велик. Вот мой ответ, основанный на его ответе с использованием compactMap, чтобы избежать ошибки Index out of range.

Swift 5.1

extension StringProtocol {
    func ranges(of targetString: Self, options: String.CompareOptions = [], locale: Locale? = nil) -> [Range<String.Index>] {

        let result: [Range<String.Index>] = self.indices.compactMap { startIndex in
            let targetStringEndIndex = index(startIndex, offsetBy: targetString.count, limitedBy: endIndex) ?? endIndex
            return range(of: targetString, options: options, range: startIndex..<targetStringEndIndex, locale: locale)
        }
        return result
    }
}

// Usage
let str = "Hello, playground, playground, playground"
let ranges = str.ranges(of: "play")
ranges.forEach {
    print("[\($0.lowerBound.utf16Offset(in: str)), \($0.upperBound.utf16Offset(in: str))]")
}

// result - [7, 11], [19, 23], [31, 35]

Ответ 9

Swift 5

Найти индекс подстроки

let str = "abcdecd"
if let range: Range<String.Index> = str.range(of: "cd") {
    let index: Int = str.distance(from: str.startIndex, to: range.lowerBound)
    print("index: ", index) //index: 2
}
else {
    print("substring not found")
}

Найти индекс персонажа

let str = "abcdecd"
if let firstIndex = str.firstIndex(of: "c") {
    let index = str.distance(from: str.startIndex, to: firstIndex)
    print("index: ", index)   //index: 2
}
else {
    print("symbol not found")
}