Как работает строковая подстрока в Swift

я обновлял некоторые из моих старых кодов и ответов с помощью Swift 3, но когда я добрался до строк Swift и индексирования с подстроками, все запуталось.

в частности, я пробовал следующее:

let str = "Hello, playground"
let prefixRange = str.startIndex..<str.startIndex.advancedBy(5)
let prefix = str.substringWithRange(prefixRange)

где вторая строка дает мне следующую ошибку

значение типа 'String' не имеет члена 'substringWithRange'

Я вижу String имеет следующие методы теперь:

str.substring(to: String.Index)
str.substring(from: String.Index)
str.substring(with: Range<String.Index>)

Они действительно сбивали меня с толку сначала, поэтому я начал играть вокруг и серии. Это следующий вопрос и ответ для подстроки. Я добавляю ответ ниже, чтобы показать, как они используются.

11 ответов


enter image description here

все следующие примеры использования

var str = "Hello, playground"

Swift 4

Strings получил довольно большой ремонт в Swift 4. Когда вы получаете некоторую подстроку из строки сейчас, вы получаете Substring тип, а не String. Почему так? Строки-это типы значений в Swift. Это означает, что если вы используете одну строку для создания новой, ее нужно скопировать. Это хорошо для стабильности (никто другой не собирается менять его без ваши знания), но плохо для эффективности.

подстрока, с другой стороны, является ссылкой на исходную строку, из которой она пришла. Вот изображение из документация свидетельствует о том, что.

копирование не требуется, поэтому его гораздо эффективнее использовать. Однако представьте, что вы получили подстроку из десяти символов из строки с миллионом символов. Поскольку подстрока ссылается на строку, системе придется удерживайте всю строку до тех пор, пока подстрока находится вокруг. Таким образом, когда вы закончите манипулировать своей подстрокой, преобразуйте ее в строку.

let myString = String(mySubstring)

это скопирует только подстроку, и старая строка может быть собрана мусор. Подстроки (как тип) предназначены для короткой жизни.

еще одним большим улучшением в Swift 4 является то, что строки являются коллекциями (снова). Это означает, что все, что вы можете сделать с коллекцией, вы можете сделать со строкой (используйте подстрочные, перебирать символы, фильтра и т. д.).

в следующих примерах показано, как получить подстроку в Swift.

получение подстроки

вы можете получить подстроку из строки, используя индексы или ряд других методов (например,prefix, suffix, split). Вам все равно нужно использовать String.Index, а не Int индекс для диапазона, хотя. (См.мой другой ответ если вам нужна помощь с что.)

начало строки

вы можете использовать индекс (обратите внимание на односторонний диапазон Swift 4):

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str[..<index] // Hello

или prefix:

let index = str.index(str.startIndex, offsetBy: 5)
let mySubstring = str.prefix(upTo: index) // Hello

или еще проще:

let mySubstring = str.prefix(5) // Hello

конец строки

С помощью индексов:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str[index...] // playground

или suffix:

let index = str.index(str.endIndex, offsetBy: -10)
let mySubstring = str.suffix(from: index) // playground

или еще проще:

let mySubstring = str.suffix(10) // playground

обратите внимание, что при использовании suffix(from: index) мне пришлось отсчитывать от конца, используя -10. Это не обязательно при использовании suffix(x), который как раз принимает последнее x символы строки.

продление?

боюсь использовать Int расширение индекса на основе после прочтения статьи строки в Swift 3 по скорости полета и Ole Begemann. Хотя в Swift 4 строки являются коллекциями, команда Swift намеренно не использовала Int индексы. Это все еще String.Index. Это связано с тем, что Swift-символы состоят из различного количества кодовых точек Unicode. Фактический индекс должен быть рассчитан однозначно для каждой строки.

я должен сказать, я надеюсь, что команда Swift найдет способ абстрагироваться String.Index в будущем. Но до тех пор я предпочитаю использовать их API. Это помогает мне помнить, что струнные манипуляции не просто Int поиска индекс.


Я действительно расстроен моделью доступа к строкам Swift: все должно быть Index. Все, что я хочу, это получить доступ к i-му символу строки с помощью Int, а не неуклюжий индекс и продвижение (который меняется с каждым основным выпуском). Поэтому я сделал расширение до String:

extension String {
    func index(from: Int) -> Index {
        return self.index(startIndex, offsetBy: from)
    }

    func substring(from: Int) -> String {
        let fromIndex = index(from: from)
        return substring(from: fromIndex)
    }

    func substring(to: Int) -> String {
        let toIndex = index(from: to)
        return substring(to: toIndex)
    }

    func substring(with r: Range<Int>) -> String {
        let startIndex = index(from: r.lowerBound)
        let endIndex = index(from: r.upperBound)
        return substring(with: startIndex..<endIndex)
    }
}

let str = "Hello, playground"
print(str.substring(from: 7))         // playground
print(str.substring(to: 5))           // Hello
print(str.substring(with: 7..<11))    // play

Расширение Swift 4:

extension String { 
    subscript(_ range: CountableRange<Int>) -> String { 
        let idx1 = index(startIndex, offsetBy: max(0, range.lowerBound))
        let idx2 = index(startIndex, offsetBy: min(self.count, range.upperBound))
        return String(self[idx1..<idx2])
    }    
}       

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

let s = "hello"
s[0..<3] // "hel"
s[3..<s.count] // "lo"

или unicode:

let s = ""
s[0..<1] // ""

Swift 4

в swift 4 String соответствует Collection. Вместо substring, теперь мы должны использовать subscript. поэтому, если вы хотите вырезать только слово "play" С "Hello, playground", вы могли бы сделать это вот так:

var str = "Hello, playground"
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let result = str[start..<end] // The result is of type Substring

интересно знать, что это даст вам Substring вместо String. Это быстро и эффективно, как Substring делится своим хранилищем с исходной строкой. Однако совместное использование памяти также может легко привести к памяти подтеки.

вот почему вы должны скопировать результат в новую строку, когда вы хотите очистить исходную строку. Вы можете сделать это с помощью обычного конструктора:

let newString = String(result)

вы можете найти более подробную информацию о новом Substring класс в [документации Apple].1

Итак, если вы, например, получаете Range в результате NSRegularExpression, вы можете использовать следующие расширения:

extension String {

    subscript(_ range: NSRange) -> String {
        let start = self.index(self.startIndex, offsetBy: range.lowerBound)
        let end = self.index(self.startIndex, offsetBy: range.upperBound)
        let subString = self[start..<end]
        return String(subString)
    }

}

у меня была та же реакция. Я тоже был разочарован тем, как синтаксис и объекты меняются так резко в каждом крупном выпуске.

тем не менее, я понял из опыта, как я всегда в конечном итоге страдаю от последствий попытки бороться с "изменениями", как иметь дело с многобайтовыми символами, что неизбежно, если вы смотрите на глобальную аудиторию.

поэтому я решил признать и уважать усилия, прилагаемые инженерами Apple, и внести свой вклад, понимая их мышление, когда они придумали этот "ужасный" подход.

вместо того, чтобы создавать расширения, которые просто обходной путь, чтобы сделать вашу жизнь проще (я не говорю, что они неправильные или дорогие), почему бы не выяснить, как строки теперь предназначены для работы.

например, у меня был этот код, который работал на Swift 2.2:

let rString = cString.substringToIndex(2)
let gString = (cString.substringFromIndex(2) as NSString).substringToIndex(2)
let bString = (cString.substringFromIndex(4) as NSString).substringToIndex(2)

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

let rString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let gString = String(cString.characters.prefix(2))
cString = String(cString.characters.dropFirst(2))
let bString = String(cString.characters.prefix(2))

Я надеюсь, что это помогает...


Я новичок в Swift 3, но String (index) синтаксис для аналогии я думаю, что индекс похож на" указатель", ограниченный строкой, и Int может помочь как независимый объект. Используя синтаксис Base + offset, мы можем получить i-й символ из строки с кодом ниже:

let s = "abcdefghi"
let i = 2
print (s[s.index(s.startIndex, offsetBy:i)])
// print c

для диапазона символов (индексов) из строки с использованием синтаксиса String (range) мы можем получить от i-го до f-го символов с кодом ниже:

let f = 6
print (s[s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 )])
//print cdefg

для a подстрока (диапазон) из строки с помощью String.подстрока (диапазон), мы можем получить подстроку, используя код ниже:

print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) )
//print cdefg

Примечания:

  1. i-й и f-й начинаются с 0.

  2. для f-го я использую offsetBY: f + 1, потому что диапазон использования подписки ..

  3. конечно, должны включать ошибки проверки, такие как недопустимый индекс.


вот функция, которая возвращает подстроку данной подстроки, когда предоставляются начальный и конечный индексы. Для полной справки вы можете посетить ссылки, приведенные ниже.

func substring(string: String, fromIndex: Int, toIndex: Int) -> String? {
    if fromIndex < toIndex && toIndex < string.count /*use string.characters.count for swift3*/{
        let startIndex = string.index(string.startIndex, offsetBy: fromIndex)
        let endIndex = string.index(string.startIndex, offsetBy: toIndex)
        return String(string[startIndex..<endIndex])
    }else{
        return nil
    }
}

вот ссылка на сообщение в блоге, которое я создал для работы со строковыми манипуляциями в swift. строка манипуляции в swift (охватывает swift 4, а также)

или вы можете увидеть эту суть на github


такое же разочарование, это не должно быть так сложно...

я скомпилировал этот пример получения позиций для подстроки(ов) из большего текста:

//
// Play with finding substrings returning an array of the non-unique words and positions in text
//
//

import UIKit

let Bigstring = "Why is it so hard to find substrings in Swift3"
let searchStrs : Array<String>? = ["Why", "substrings", "Swift3"]

FindSubString(inputStr: Bigstring, subStrings: searchStrs)


func FindSubString(inputStr : String, subStrings: Array<String>?) ->    Array<(String, Int, Int)> {
    var resultArray : Array<(String, Int, Int)> = []
    for i: Int in 0...(subStrings?.count)!-1 {
        if inputStr.contains((subStrings?[i])!) {
            let range: Range<String.Index> = inputStr.range(of: subStrings![i])!
            let lPos = inputStr.distance(from: inputStr.startIndex, to: range.lowerBound)
            let uPos = inputStr.distance(from: inputStr.startIndex, to: range.upperBound)
            let element = ((subStrings?[i])! as String, lPos, uPos)
            resultArray.append(element)
        }
    }
    for words in resultArray {
        print(words)
    }
    return resultArray
}

возвращает ("Почему", 0, 3) ("подстрок", 26, 36) ("Swift3", 40, 46)


Я создал простое расширение для этого (Swift 3)

extension String {
    func substring(location: Int, length: Int) -> String? {
        guard characters.count >= location + length else { return nil }
        let start = index(startIndex, offsetBy: location)
        let end = index(startIndex, offsetBy: location + length)
        return substring(with: start..<end)
    }
}

Swift 4

extension String {
    subscript(_ i: Int) -> String {
        let idx1 = index(startIndex, offsetBy: i)
        let idx2 = index(idx1, offsetBy: 1)
        return String(self[idx1..<idx2])
    }
}

let s = "hello"

s[0]    // h
s[1]    // e
s[2]    // l
s[3]    // l
s[4]    // o

Swift 4

"подстрока" (https://developer.apple.com/documentation/swift/substring):

let greeting = "Hi there! It's nice to meet you! "
let endOfSentence = greeting.index(of: "!")!
let firstSentence = greeting[...endOfSentence]
// firstSentence == "Hi there!"

пример строки расширения:

private typealias HowDoYouLikeThatElonMusk = String
private extension HowDoYouLikeThatElonMusk {

    subscript(_ from: Character?, _ to: Character?, _ include: Bool) -> String? {
        if let _from: Character = from, let _to: Character = to {
            let dynamicSourceForEnd: String = (_from == _to ? String(self.reversed()) : self)
            guard let startOfSentence: String.Index = self.index(of: _from),
                let endOfSentence: String.Index = dynamicSourceForEnd.index(of: _to) else {
                return nil
            }

            let result: String = String(self[startOfSentence...endOfSentence])
            if include == false {
                guard result.count > 2 else {
                        return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        } else if let _from: Character = from {
            guard let startOfSentence: String.Index = self.index(of: _from) else {
                return nil
            }
            let result: String = String(self[startOfSentence...])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[result.index(result.startIndex, offsetBy: 1)...])
            }
            return result
        } else if let _to: Character = to {
            guard let endOfSentence: String.Index = self.index(of: _to) else {
                    return nil
            }
            let result: String = String(self[...endOfSentence])
            if include == false {
                guard result.count > 1 else {
                    return nil
                }
                return String(result[..<result.index(result.endIndex, offsetBy: -1)])
            }
            return result
        }
        return nil
    }
}

пример использования строки расширения:

let source =                                   ">>>01234..56789<<<"
// include = true
var from =          source["3", nil, true]  //       "34..56789<<<"
var to =            source[nil, "6", true]  // ">>>01234..56"
var fromTo =        source["3", "6", true]  //       "34..56"
let notFound =      source["a", nil, true]  // nil
// include = false
from =              source["3", nil, false] //        "4..56789<<<"
to =                source[nil, "6", false] // ">>>01234..5"
fromTo =            source["3", "6", false] //        "4..5"
let outOfBounds =   source[".", ".", false] // nil

let str = "Hello, playground"
let hello = str[nil, ",", false] // "Hello"