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

354

Я обновлял свой старый код и ответы с помощью 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>)

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

Suragch
источник
2
Для тех, кто хочет получить подстроку из строки stackoverflow.com/q/32305891/468724
Inder Kumar Rathore
или подстрочная строка или подстрока stackoverflow.com/questions/24092884/…
Лео Дабус

Ответы:

832

введите описание изображения здесь

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

var str = "Hello, playground"

Swift 4

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

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

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

let myString = String(mySubstring)

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

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

В следующих примерах показано, как получить подстроку в 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 строки являются коллекциями, команда Swift специально не использовала Intиндексы. Это все ещеString.Index . Это связано с тем, что символы Swift состоят из различного числа кодовых точек Unicode. Фактический индекс должен быть уникально рассчитан для каждой строки.

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

Suragch
источник
9
Спасибо за описание. Хорошо заслуженные ставки. Apple это сильно усложнило. Подстрока должна быть такой же простой, как string.substring [от ... до].
Тедди
Действительно хорошее объяснение. кроме одной мелочи garbage collected;-) Я надеюсь, что люди здесь знают, что в Swift нет сборки мусора.
Кристиан Якорь Дампф
@ChristianAnchorDampf, Спасибо, что нашли время для комментариев. Я вывозил мусор. Как новая формулировка?
Сурагч
Какой удивительный ответ, сэр!
Давидев
194

Я действительно разочарован в модели доступа 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 String(self[fromIndex...])
    }

    func substring(to: Int) -> String {
        let toIndex = index(from: to)
        return String(self[..<toIndex])
    }

    func substring(with r: Range<Int>) -> String {
        let startIndex = index(from: r.lowerBound)
        let endIndex = index(from: r.upperBound)
        return String(self[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
Код Разный
источник
5
Индексы очень полезны, потому что символ может быть больше чем один байт. Попробуйтеlet str = "🇨🇭🇩🇪🇺🇸Hello" print(str.substring(to: 2))
vadian
112
Да, я понимаю, что символ (то есть расширенный кластер графем ) может занимать несколько байтов. Мое разочарование объясняется тем, что мы должны использовать подробный метод продвижения индекса для доступа к символам строки. Почему команда Swift не может просто добавить некоторые перегрузки в базовую библиотеку, чтобы абстрагировать ее? Если я печатаю str[5], я хочу получить доступ к символу с индексом 5, каким бы ни был этот символ или сколько байт для него требуется. Разве Swift не все о производительности разработчика?
Код Различный
6
@RenniePet Я верю, что Apple осознает проблему и грядут перемены. Согласно странице Swift Evolution на GitHub: «Swift 4 стремится сделать строки более мощными и простыми в использовании, сохраняя при этом правильность Unicode по умолчанию». Это расплывчато, но давайте продолжим наши надежды
Code Different
3
@CodeDifferent, почему Apple не добавила доступ к символам нижнего индекса? Чтобы люди понимали, что это плохо. В основном, если бы вы делали для i в 0..string.count, используя индексы, которые были бы двойными циклами, причина под индексом капота должна проходить через каждый байт строки, чтобы узнать, какой следующий символ. Если вы выполняете цикл с использованием индекса, вы перебираете строку только один раз. Кстати, ненавижу это сам, но это причина того, что подстрочный текст не доступен для строки в swift.
Раймундас Сакалаускас
4
@RaimundasSakalauskas этот аргумент не проходит мимо меня. В C # есть и правильность Юникода, и целочисленная подписка, что действительно удобно. В Swift 1 Apple хотела, чтобы разработчики использовали ее countElement(str)для определения длины. В Swift 3 Apple сделала строку, не соответствующую, Sequenceи заставила всех использовать str.charactersвместо нее. Эти ребята не боятся вносить изменения. Их упрямство при целочисленной подписке действительно трудно понять
Code Different
103

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

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

    subscript(_ range: CountablePartialRangeFrom<Int>) -> String {
        let start = index(startIndex, offsetBy: max(0, range.lowerBound))
         return String(self[start...])
    }
}

Применение:

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

Или Юникод:

let s = "😎🤣😋"
s[0..<1] // "😎"
Лу Зелл
источник
2
Намного лучше, спасибо за размещение этого расширения! Я думаю, что из Python, Swift гораздо сложнее, чем нужно, чтобы привыкнуть. Для людей, идущих в другом направлении от Цели С до Свифта, есть более позитивное подтверждение.
user3064009
1
@ Леон, я только что удалил это. До 4.1 countбыл доступен толькоself.characters
Лу Зелл
1
Есть ли какие-нибудь ошибки, которые нужно соблюдать с этим конкретным расширением? Почему Apple не сделала что-то подобное?
Andz
1
@ Andz это очень неэффективно. Он начинается с начала строки - дважды - и должен анализировать каждый символ оттуда до «диапазона» - дважды.
кареман
3
Вам также нужно добавить расширение, которое принимает,CountableClosedRange<Int> если вы хотите написать, например s[0...2].
Крис Фредерик
24

Свифт 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] -> "a"

"abcde" [0 ... 2] -> "abc"

"abcde" [2 .. <4] -> "cd"

Суф Рочди
источник
20

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

Итак, если вы, например, получите a 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)
    }

}
gebirgsbärbel
источник
Ваш код потерпит крах, если range.upperBound> длина строки. Кроме того, пример использования также был бы полезен, так как я не был знаком с подписчиками в Swift. Вы можете включить что-то вроде datePartOnly = "2018-01-04-08: 00" [NSMakeRange (0, 10)]. Кроме этого, очень хороший ответ, +1 :).
17
В наши дни это странная вещь: text[Range( nsRange , in: text)!]
Толстяк
10

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

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

Никеш Джа
источник
9

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

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

Поэтому я решил признать и уважать усилия, прилагаемые инженерами 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))

Я надеюсь, что это способствует ...

Рио Баутиста
источник
1
Ну, решение сложной проблемы не означает, что решение может быть элегантным. Опять же, я тоже понимаю проблему, но весь класс String и работа с ним просто ужасны.
excitus
5

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

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

//
// 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)

Высокий датчанин
источник
3
Это некоторый код, но на самом деле он не объясняет, как работает индексация строк и подстрок в swift3.
Роберт
5

Я новичок в 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

Ноты:

  1. I и F начинаются с 0.

  2. Для f-го я использую offsetBY: f + 1, поскольку диапазон подписки используют .. <(полуоткрытый оператор), не включая f-ю позицию.

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

Нельсон Мизутани
источник
5

Свифт 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
Питер Крейнз
источник
4

Я довольно механическое мышление. Вот основы ...

Свифт 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...]
t1ser
источник
3

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
FlowUI. SimpleUITesting.com
источник
2

Я создал простое расширение для этого (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)
    }
}
Лукас Алгарра
источник
2

Вот более общая реализация:

Эта техника все еще используется 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чтобы указать, что он возвращает a, Stringа не a Substring.

Лесли Годвин
источник
2

Основываясь на вышеизложенном, мне нужно было разбить строку на непечатаемый символ, удалив непечатный символ. Я разработал два метода:

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 5? У меня еще не было времени поиграть с этим.
Сурагч,
Они так говорят, но у меня не было возможности разобраться в этом.
Джереми Эндрюс
1

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"
CAHbl463
источник
-1

Swift 5
let desiredIndex: Int = 7 let substring = str[String.Index(encodedOffset: desiredIndex)...]
Эта переменная подстроки даст вам результат.
Просто здесь Int конвертируется в индекс, а затем вы можете разделить строки. Если вы не получите ошибки.

Вимукти Раджапакша
источник
2
Это не верно. Символ может состоять из одного или нескольких байтов. Работает только с текстом ascii.
Лев Дабус