it-roy-ru.com

Как работает подстрока 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>)

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

270
Suragch

enter image description here

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

var str = "Hello, playground"

Swift 4

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

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

660
Suragch

Я действительно разочарован в модели доступа 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
150
Code Different

Расширение 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] // "????"
52
Lou Zell

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

}
19
gebirgsbärbel

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

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

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

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

7
Rio Bautista

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

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)

Или вы можете увидеть этот Gist на GitHub

7
Nikesh Jha

Я новичок в Swift 3, но, глядя на синтаксис String (index) для аналогии, я думаю, что 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 (диапазон), мы можем получить подстроку, используя код ниже:

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
Nelson Mizutani

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

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

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

4
Tall Dane

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
iOS Calendar View OnMyProfile

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

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

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

1
Jeremy Andrews

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

Swift 4 Swift 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...]
0
t1ser

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
0
Peter Kreinz

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"
0
CAHbl463

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

Эта техника все еще использует 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.

0
Leslie Godwin