Основы Swift / 6.7. Строки и символы 4 - Изменение и доступ к строкам


Видео


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

Каждое значение типа String имеет ассоциированный тип индекса - String.Index, который связан с позицией каждого Character в строке.

Как сказано выше, разные символы могут требовать разное количество памяти для хранения, так что для определения, какой символ находится на конкретной позиции, Вы должны проитерироваться через каждый Юникод-скаляр от начала или конца этой String. По этой причине Swift строки не могут быть проиндексированы целыми числами.

Используйте свойство startIndex для доступа к позиции первого Character в String. Свойство endIndex - это позиция после последнего Character в String. В результате свойство endIndex не является корректным аргументом для сабскрипта/ строки. Если String пуста, то startIndex и endIndex равны.

Вы можете получить доступ к индексам перед и после заданного индекса, используя методы index(before:) и index(after:). Для доступа к индексу на расстоянии от заданного индекса Вы можете использовать метод index(_:offsetBy:) вместо вызова одного из ранее описанных методов несколько раз.

Вы можете использовать синтаксис сабскриптов для доступа к символу на конкретной позиции в String.
@{6.7\1}
let greeting = "Guten Tag!"
greeting[greeting.startIndex]
// G
greeting[greeting.index(before: greeting.endIndex)]
// !
greeting[greeting.index(after: greeting.startIndex)]
// u
let index = greeting.index(greeting.startIndex, offsetBy: 7)
greeting[index]
// a
Выход за границы строки
Попытка доступа к индексу за границами интервала строки или Character на позиции за границами интервала строки вызовет ошибку времени выполнения.
@{6.7\2}
let greeting = "Guten Tag!"
greeting[greeting.endIndex] // Ошибка
greeting.index(after: greeting.endIndex) // Ошибка
Свойство indices
Используете свойство indices для доступа ко всем индексам отдельных символов в строке:
Свойства и методы для коллекций
Вы можете использовать свойства startIndex и endIndex, ровно как и методы index(before:), index(after:) и index(_:offsetBy:) на любом типе, который удовлетворяет протоколу Collection. Это подразумевает под собой String, а также типы коллекций как Array, Dictionary и Set.
@{6.7\3}
let greeting = "Guten Tag!"
for index in greeting.characters.indices {
    print("\(greeting[index]) ", terminator: " ")
}

// Напечатает "G u t e n T a g !"
Вставка
Для вставки одного символа в строку на конкретной позиции используйте метод insert(_:at:) и для вставки контента другой строки на специальную позицию используйте метод insert(contentsOf:at:).
@{6.7\4}
var welcome = "hello"
welcome.insert("!", at: welcome.endIndex)
// welcome теперь равен "hello!"

welcome.insert(contentsOf:" there".characters, at: welcome.index(before: welcome.endIndex))
// welcome теперь равен "hello there!”
Удаление
Чтобы убрать отдельный символ из строки на конкретной позиции используйте метод remove(at:) и для удаления подстроки в конкретном диапазоне Вы можете использовать метод removeSubrange(_:):
@{6.7\5}
var welcome = "hello there!"
welcome.remove(at: welcome.index(before: welcome.endIndex))
// welcome теперь равно "hello there"

let range = welcome.index(welcome.endIndex, offsetBy: -6)..<
welcome.endIndex
welcome.removeSubrange(range)
// welcome теперь равно "hello”
И ещё раз о коллекциях
Вы можете использовать методы insert(_:at:), insert(contentsOf:at:), remove(at:) и removeSubrange(_:) на любом типе, который удовлетворяет протоколу Collection. Это подразумевает под собой String, а также типы коллекций как Array, Dictionary и Set.
Подстроки
Когда Вы получаете подстроку из строки - например, используя сабскрипт или метод наподобие prefix(_:), - результат будет экземпляром Substring, а не другой строкой. Подстроки в Swift в основном имеют те же методы, что и строки: Вы можете использовать подстроки как строки. В отличие от строк Вы можете использовать подстроки только на короткое время в процессе выполнения действий над строками. Когда Вы будете готовы сохранить результат на большее время, Вы должны конвертировать подстроки в экземпляр типа String. Например:
@{6.7\6}
let greeting = "Hello, world!"
let index = greeting.index(of: ",") ?? greeting.endIndex
let beginning = greeting[..< index]
// beginning равен "Hello"

//Преобразуйте результат в String на долгосрочное хранение.
let newString = String(beginning)
Подстроки и строки
Как и строка, каждая подстрока имеет область памяти, где хранятся символы, образующие подстроку. Разница между строками и подстроками состоит в том, что ради оптимизации производительности подстрока может переиспользовать часть памяти, что использует оригинальная строка, или часть памяти, которая используется другой подстрокой. (Строки имеют схожую оптимизацию, но, если две строки делят память, то они идентичны.) Эта оптимизация производительности означает, что Вам не нужно платить производительностью за копирование памяти, пока Вы не модифицируете либо строку, либо подстроку. Как указано выше, подстроки не предназначены для долгосрочного хранения: так как они переиспользуют хранилище оригинальной строки, то вся оригинальная строка должна храниться в памяти так долго, как подстроки находятся в использовании.

И String, и Substring удовлетворяют протоколу StringProtocol. Если Вы пишите код, который манипулирует строковыми данными, применение значения типа StringProtocol позволит Вам передавать строковые данные в качестве String или Substring.

В примере greeting - это строка, что значит, что она имеет участок памяти, где хранятся символы, образующие строки. Так как begining - это подстрока greeting, то она реиспользует память greeting. Напротив, newString - это строка: когда она создана из подстроки, она будет иметь своё собственное хранилище. Рисунок ниже демонстрирует эти отношения.
Рисунок 6.7.1
Сравнение строк
Swift предоставляет три способа сравнивать текстовые значения: строковое и символьное совпадение, префиксная или суффиксная эквивалентность.

Сравнение строк и символов в Swift не чувствительно к региональным настройкам языка.

Строковая и символьная эквивалентность проверяется с помощью оператора равенства (==) или оператора неравенства (!=).
@{6.7\7}
let quotation = "Мы ведь так похожи, Ты и я."
let sameQuotation = "Мы ведь так похожи, Ты и я."
if quotation == sameQuotation {
    print("Эти две строки считаются эквивалентными.")
}
// Выведет "Эти две строки считаются эквивалентными."
Каноническая эквивалентность
Два значения типа String (или Character) считаются равными, если их расширенные графем-кластеры канонично эквивалентны. Расширенные графем-кластеры канонично эквиваленты, если они имеют одинаковые лингвистические значение и вид, даже если они составлены из разных Юникод-скаляров за кулисами.
@{6.7\8}
// "Voulez-vous un café?" использует LATIN SMALL LETTER E WITH ACUTE
let eAcuteQuestion = "Voulez-vous un caf\u{E9}?"
// "Voulez-vous un café?" использует LATIN SMALL LETTER E и COMBINING ACUTE ACCENT
let combinedEAcuteQuestion = "Voulez-vous un caf\u{65}\u{301}?"

if eAcuteQuestion == combinedEAcuteQuestion {
    print("Эти две строки считаются эквивалентными.")
}
// Выведет "Эти две строки считаются эквивалентными."
Например, LATIN SMALL LETTER E WITH ACUTE (U+00E9) канонично эквивалентен LATIN SMALL LETTER E (U+0065), дополненному COMBINING ACUTE ACCENT (U+0301). Оба этих расширенных графем-кластера - корректные способы для представления символа é, так что они канонично эквивалентны.
@{6.7\9}
let latinCapitalLetterA: Character = "\u{41}"
let cyrillicCapitalLetterA: Character = "\u{0410}"

if latinCapitalLetterA != cyrillicCapitalLetterA {
    print("Эти строки считаются не равными")
}
// Выведет "Эти строки считаются не равными"
Обратно, LATIN CAPITAL LETTER A (U+0041 или "A"), используемая в Английском, не эквивалентна CYRILLIC CAPITAL LETTER A (U+0410 или "А"), используемой в Русском. Эти символы визуально одинаковы, но не имеют одинакового лингвистического значения.
Префиксная и суффикса эквивалентность
Чтобы проверить, имеет ли строка определённый строковый префикс или суффикс, вызовите методы строк hasPrefix(_:) или hasSuffix(_:), каждый из которых принимает один аргумент типа String и возвращает Булево значение.

Методы hasPrefix(_:) и hasSuffix(_:) осуществляют посимвольное канонично эквивалентное сравнение между расширенными графем-кластерами.

Примеры далее демонстрируют массив строк, отображающий название сцен из двух первых актов "Ромео и Джульетты" Шекспира:
@{6.7\10\1}
let romeoAndJuliet = [
    "Act 1 Scene 1: Verona, A public place",
    "Act 1 Scene 2: Capulet's mansion",
    "Act 1 Scene 3: A room in Capulet's mansion",
    "Act 1 Scene 4: A street outside Capulet's mansion",
    "Act 1 Scene 5: The Great Hall in Capulet's mansion",
    "Act 2 Scene 1: Outside Capulet's mansion",
    "Act 2 Scene 2: Capulet's orchard",
    "Act 2 Scene 3: Outside Friar Lawrence's cell",
    "Act 2 Scene 4: A street in Verona",
    "Act 2 Scene 5: Capulet's mansion",
    "Act 2 Scene 6: Friar Lawrence's cell"
]
@{6.7\10\2}
var act1SceneCount = 0
for scene in romeoAndJuliet {
    if scene.hasPrefix("Act 1 ") {
        act1SceneCount += 1
    }
}
print("Есть \(act1SceneCount) сцен в акте 1")
// Выведет "Есть 5 сцен в акте 1"
Вы можете использовать метод hasPrefix(_:) с массивом romeoAndJuliet для подсчёта количества сцен в первом акте пьесы.
@{6.7\10\3}
var mansionCount = 0
var cellCount = 0
for scene in romeoAndJuliet {
    if scene.hasSuffix("Capulet's mansion") {
        mansionCount += 1
    } else if scene.hasSuffix("Friar Lawrence's cell") {
        cellCount += 1
    }
}
print("\(mansionCount) сцен в особняке; \(cellCount) сцены в камере")
// Выводит "6 сцен в особняке; 2 сцены в камере"
Аналогично Вы можете использовать метод hasSuffix(_:) для подсчёта количества сцен, которые происходят внутри или вокруг особняка Капулетти и клетки монаха Лоренцо.