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

277

Я обновлял часть своего старого кода и отвечал на Swift 3, но когда я добрался до Swift Strings и Indexing с подстроками, все стало запутанным.

В частности, я пытался сделать следующее:

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

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

Значение типа 'String' не имеет подстроки'WithRange '

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

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

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

Теги:
string
range
substring

13 ответов

669
Лучший ответ

Изображение 1681

Все приведенные ниже примеры

var str = "Hello, playground"

Swift 4

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

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

Изображение 1682

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

let myString = String(mySubstring)

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

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

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

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

  • 2
    Спасибо за описание. Хорошо заслуженные ставки. Apple это сильно усложнило. Подстрока должна быть такой же простой, как string.substring [от ... до].
  • 27
    Почти все в Swift слишком сложно. Безопасность типов и принудительная обработка ошибок приводят только к уродливому коду, усеянному пунктуацией, который в конечном итоге трудно читать и неэффективен для предотвращения неверного кода и связанных с ним исключений. Незначительные улучшения в скорости и эффективности не имеют значения для 99% приложений. Свифт - новый император, которого, кажется, мало кто замечает, на самом деле совершенно голый
Показать ещё 1 комментарий
151

Я действительно разочарован в модели доступа 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
  • 67
    Да, я понимаю, что символ (то есть расширенный кластер графем ) может занимать несколько байтов. Мое разочарование объясняется тем, что мы должны использовать подробный метод продвижения индекса для доступа к символам строки. Почему команда Swift не может просто добавить некоторые перегрузки в базовую библиотеку, чтобы абстрагировать ее. Если я наберу str[5] , я захочу получить доступ к символу с индексом 5, каким бы ни был этот символ или сколько байт ему потребуется. Разве Swift не все о производительности разработчика?
  • 0
    хорошая утилита. Я добавил это для NSRange. func substring(with r: NSRange) -> String { return substring(with: r.location..<(r.location + r.length)) }
Показать ещё 15 комментариев
52

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] // ""
  • 0
    Намного лучше, спасибо за публикацию этого расширения! Я думаю, что из Python, Swift гораздо сложнее, чем нужно, чтобы привыкнуть. Для людей, идущих в другом направлении от Цели С до Свифта, есть более позитивное подтверждение.
  • 0
    Какова цель счета, который возвращает счет?
Показать ещё 4 комментария
19

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

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

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

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

7

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

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

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

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

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

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

5

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

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

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-ю позицию.

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

4

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

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

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

  • 2
    Это некоторый код, но на самом деле он не объясняет, как работает индексация строк и подстрок в swift3.
1

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
1

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

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

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

  • 0
    Спасибо за Ваш ответ. Что-то отличается от строк в Swift 5? У меня еще не было времени поиграть с этим.
  • 0
    Они так говорят, но у меня не было возможности разобраться в этом.
0

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

Этот метод по-прежнему использует 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)
    }
}

Для подстроки из третьего символа:

let item = "Fred looks funny"
item.subString(item.index(at: 2)...) // "ed looks funny"

Я использовал верблюд subString чтобы указать, что он возвращает String а не Substring.

0

Swift 4

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

let greeting = "Hi there! It 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
    }
}

пример использования расширения 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"

Ещё вопросы

Сообщество Overcoder
Наверх
Меню