Как работает подстрока String в Swift
Я обновлял свой старый код и ответы с помощью Swift 3, но когда я перешел к Swift Strings и Indexing с подстроками, все стало не так.
В частности, я пытался следующее:
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>)
Сначала это действительно смущало меня, поэтому я начал играть с индексом и диапазоном. Это дополнительный вопрос и ответ для подстроки. Я добавляю ответ ниже, чтобы показать, как они используются.
25 ответов
Все следующие примеры использования
var str = "Hello, playground"
Swift 4
Строки получили довольно большой пересмотр в 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
символы строки.
Диапазон в строке
Опять же, мы просто используем подписки здесь.
let start = str.index(str.startIndex, offsetBy: 7)
let end = str.index(str.endIndex, offsetBy: -6)
let range = start..<end
let mySubstring = str[range] // play
преобразование Substring
в String
Не забывайте, что когда вы готовы сохранить свою подстроку, вы должны преобразовать ее в String
так что память старой строки может быть очищена.
let myString = String(mySubstring)
Используя Int
расширение индекса?
Я не решаюсь использовать Int
на основе индекса расширения после прочтения статьи Строки в Swift 3 от Airspeed Velocity и Оле Бегеманна. Хотя в Swift 4 Strings являются коллекциями, команда Swift специально не использовала Int
индексов. Это все еще String.Index
, Это имеет отношение к символам Swift, состоящим из различного числа кодовых точек Unicode. Фактический индекс должен быть уникально рассчитан для каждой строки.
Надо сказать, я надеюсь, что команда Swift найдет способ абстрагироваться String.Index
в будущем. Но до них я выбираю использовать их API. Это помогает мне помнить, что манипуляции со строками не просто Int
поиск по индексу
Я действительно разочарован в модели доступа Swift String: все должно быть 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"
Или Юникод:
let s = ""
s[0..<1] // ""
Swift 4 и 5:
extension String {
subscript(_ i: Int) -> String {
let idx1 = index(startIndex, offsetBy: i)
let idx2 = index(idx1, offsetBy: 1)
return String(self[idx1..<idx2])
}
subscript (r: Range<Int>) -> String {
let start = index(startIndex, offsetBy: r.lowerBound)
let end = index(startIndex, offsetBy: r.upperBound)
return String(self[start ..< end])
}
subscript (r: CountableClosedRange<Int>) -> String {
let startIndex = self.index(self.startIndex, offsetBy: r.lowerBound)
let endIndex = self.index(startIndex, offsetBy: r.upperBound - r.lowerBound)
return String(self[startIndex...endIndex])
}
}
Как это использовать:
"abcde" [0] -> "а"
"abcde" [0...2] -> "abc"
"abcde" [2..<4] -> "cd"
Swift 4
Свифт 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)
}
}
Наткнулся на этот довольно короткий и простой способ добиться этого.
var str = "Hello, World"
let arrStr = Array(str)
print(arrStr[0..<5]) //["H", "e", "l", "l", "o"]
print(arrStr[7..<12]) //["W", "o", "r", "l", "d"]
print(String(arrStr[0..<5])) //Hello
print(String(arrStr[7..<12])) //World
Вот функция, которая возвращает подстроку данной подстроки, когда предоставляются начальный и конечный индексы. Для полной справки вы можете посетить ссылки, приведенные ниже.
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)
У меня была такая же начальная реакция. Я также был разочарован тем, как синтаксис и объекты так сильно меняются в каждом основном выпуске.
Однако из опыта я понял, что я всегда в конечном итоге страдаю от последствий борьбы с "переменами", например, от работы с многобайтовыми символами, что неизбежно, если вы смотрите на глобальную аудиторию.
Поэтому я решил признать и уважать усилия, прилагаемые инженерами Apple, и внести свой вклад, поняв их мышление, когда они придумали этот "ужасающий" подход.
Вместо создания расширений, которые являются просто обходным путем, чтобы сделать вашу жизнь проще (я не говорю, что они неправильные или дорогие), почему бы не выяснить, как Strings теперь разработаны для работы.
Например, у меня был этот код, который работал на 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)
и после отказа от попыток заставить работать тот же подход, например, с использованием Substrings, я, наконец, понял концепцию обработки Strings как двунаправленной коллекции, для которой я получил эту версию того же кода:
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))
Я надеюсь, что это способствует...
Я довольно механическое мышление. Вот основы...
Свифт 4Свифт 5
let t = "abracadabra"
let start1 = t.index(t.startIndex, offsetBy:0)
let end1 = t.index(t.endIndex, offsetBy:-5)
let start2 = t.index(t.endIndex, offsetBy:-5)
let end2 = t.index(t.endIndex, offsetBy:0)
let t2 = t[start1 ..< end1]
let t3 = t[start2 ..< end2]
//or a shorter form
let t4 = t[..<end1]
let t5 = t[start2...]
print("\(t2) \(t3) \(t)")
print("\(t4) \(t5) \(t)")
// result:
// abraca dabra abracadabra
Результатом является подстрока, означающая, что она является частью исходной строки. Чтобы получить полноценную отдельную строку, просто используйте, например,
String(t3)
String(t4)
Это то, что я использую:
let mid = t.index(t.endIndex, offsetBy:-5)
let firstHalf = t[..<mid]
let secondHalf = t[mid...]
Свифт 5
// представьте, нужно сделать подстроку из 2, длина 3
let s = "abcdef"
let subs = s.suffix(s.count-2).prefix(3)
// теперь саб = "cde"
Я создал такую простую функцию:
func sliceString(str: String, start: Int, end: Int) -> String {
let data = Array(str)
return String(data[start..<end])
}
вы можете использовать его следующим образом
print(sliceString(str: "0123456789", start: 0, end: 3)) // -> prints 012
Я новичок в Swift 3, но ищу String
Синтаксис (индекс) для аналогии. Я думаю, что индекс похож на "указатель", ограниченный строкой, а 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
Для подстроки (диапазона) из строки, используя String.substring (диапазон), мы можем получить подстроку, используя код ниже:
print (s.substring (with:s.index(s.startIndex, offsetBy:i )..<s.index(s.startIndex, offsetBy:f+1 ) ) )
//print cdefg
Заметки:
I-й и F-й начинаются с 0.
Для f-го я использую offsetBY: f + 1, поскольку диапазон подписки используют..<(полуоткрытый оператор), не включая f-ю позицию.
Конечно, должны включать в себя проверки ошибок, таких как неверный индекс.
Swift 4+
extension String {
func take(_ n: Int) -> String {
guard n >= 0 else {
fatalError("n should never negative")
}
let index = self.index(self.startIndex, offsetBy: min(n, self.count))
return String(self[..<index])
}
}
Возвращает подпоследовательность из первых n символов или всю строку, если строка короче. (на основе: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.text/take.html)
Пример:
let text = "Hello, World!"
let substring = text.take(5) //Hello
То же разочарование, это не должно быть так сложно...
Я скомпилировал этот пример получения позиций для подстроки из текста большего размера:
//
// 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
}
return ("почему", 0, 3) ("подстроки", 26, 36) ("Swift3", 40, 46)
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 5, которое работает какsubstring
на большинстве других языков.length
является необязательным, индексы ограничены, а недопустимые выборки приводят к пустой строке (не к ошибке или nil):
extension String {
func substring(_ location: Int, _ length: Int? = nil) -> String {
let start = min(max(0, location), self.count)
let limitedLength = min(self.count - start, length ?? Int.max)
let from = index(startIndex, offsetBy: start)
let to = index(startIndex, offsetBy: start + limitedLength)
return String(self[from..<to])
}
}
Основываясь на вышеизложенном, мне нужно было разбить строку на непечатаемый символ, удалив непечатный символ. Я разработал два метода:
var str = "abc\u{1A}12345sdf"
let range1: Range<String.Index> = str.range(of: "\u{1A}")!
let index1: Int = str.distance(from: str.startIndex, to: range1.lowerBound)
let start = str.index(str.startIndex, offsetBy: index1)
let end = str.index(str.endIndex, offsetBy: -0)
let result = str[start..<end] // The result is of type Substring
let firstStr = str[str.startIndex..<range1.lowerBound]
который я собрал, используя некоторые ответы выше.
Поскольку String - это коллекция, я сделал следующее:
var fString = String()
for (n,c) in str.enumerated(){
*if c == "\u{1A}" {
print(fString);
let lString = str.dropFirst(n + 1)
print(lString)
break
}
fString += String(c)
}*
Который для меня был более интуитивным. Какой из них лучше? Я не могу сказать, что они оба работают со Swift 5
Я создал простое расширение для этого (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)
}
}
Вот более общая реализация:
Эта техника все еще использует index
чтобы соответствовать стандартам Swift, и подразумевать полный характер.
extension String
{
func subString <R> (_ range: R) -> String? where R : RangeExpression, String.Index == R.Bound
{
return String(self[range])
}
func index(at: Int) -> Index
{
return self.index(self.startIndex, offsetBy: at)
}
}
Подстрока из 3-го символа:
let item = "Fred looks funny"
item.subString(item.index(at: 2)...) // "ed looks funny"
Я использовал верблюда subString
чтобы указать, что это возвращает String
и не Substring
,
var str = "VEGANISM"
print (str[str.index(str.startIndex, offsetBy:2)..<str.index(str.endIndex, offsetBy: -1)] )
//Output-> GANIS
Здесь,
str.startIndex
а также
str.endIndex
- это начальный и конечный индексы вашей строки.
Здесь как offsetBy в startIndex = 2 ->
str.index(str.startIndex, offsetBy:2)
поэтому обрезанная строка будет начинаться с индекса 2 (т. е. со второго символа) и offsetBy в endIndex = -1->
str.index(str.endIndex, offsetBy: -1)
т.е. 1 символ обрезается с конца.
var str = "VEGANISM"
print (str[str.index(str.startIndex, offsetBy:0)..<str.index(str.endIndex, offsetBy: 0)] )
//Output-> VEGANISM
Как
offsetBy value = 0
с обеих сторон, т.е.
str.index(str.startIndex, offsetBy:0)
а также
str.index(str.endIndex, offsetBy: 0)
поэтому печатается вся строка
Решение Swift 5 Высокая производительность
let fromIndex = s.index(s.startIndex, offsetBy: fromIndex)
let toIndex = s.index(s.startIndex, offsetBy: toIndex)
Я использовал этот подход, чтобы получить подстроку изfromIndex
кtoIndex
для проблемы с Leetcode, и время ожидания истекло, кажется, что это довольно неэффективно и медленно и вызывало тайм-аут.
Более быстрый чистый способ Swift сделать это:
let fromIndex = String.Index(utf16Offset:fromIndex, in: s)
let toIndex = String.Index(utf16Offset: toIndex, in: s)
Специфика в основном была рассмотрена в других ответах. Перефразируя: имеет конкретный
Index
который не относится к типу
Int
потому что строковые элементы в общем случае не имеют одинакового размера. Следовательно,
String
не соответствует
RandomAccessCollection
а доступ к определенному индексу подразумевает обход коллекции, что не является операцией O(1).
Во многих ответах предлагались обходные пути для использования диапазонов, но они могут привести к неэффективному коду, поскольку они используют методы String (
index(from:)
,
index(:offsetBy:)
, ...), которые не являются O(1).
Для доступа к строковым элементам, как в массиве, вы должны использовать
Array
:
let array = Array("Hello, world!")
let letter = array[5]
Это компромисс, создание массива - это операция O(n), но доступ к массиву тогда выполняется за O(1). Вы можете преобразовать обратно в строку, когда хотите, с
String(array)
.
Тот, кто когда-либо отвечал за строки в Swift, все испортил, и это определенно одна из худших особенностей языка.
Простым обходным решением является реализация такой функции (или сделать ее функцией расширения):
func substring(str: String, start: Int, end : Int) -> String
{
let startIndex = str.index(str.startIndex, offsetBy: start)
let endIndex = str.index(str.startIndex, offsetBy: end)
return String(str[startIndex..<endIndex])
}
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!"
Пример расширения String:
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
}
}
Пример использования расширения String:
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"
Swift 5 let desiredIndex: Int = 7
let substring = str[String.Index(encodedOffset: desiredIndex)...]
Эта переменная подстроки даст вам результат.
Просто здесь Int преобразуется в индекс, а затем вы можете разделить строки. Если только вы не получите ошибок.