Racket: Строки и неизменяемость
- Изменяемость как свойство
- Преобразование строк в неизменяемые и обратно
- Предназначение неизменяемых строк
- Модификация изменяемой строки
В большинстве языков программирования высокого уровня строки неизменяемы. Это свойство уже практически стало стандартом. В Racket же строки по умолчанию изменяемые! Но неизменяемые строки также существуют. Разберёмся же, как с этой двойственностью работать.
Изменяемость как свойство
Изменяемые и неизменяемые строки не относятся к разным типам. Устойчивость к изменению — это свойство каждого конкретного строкового значения. Узнать, какая перед нами строка, можно с помощью предиката immutable?
. Посмотрим его в действии:
(immutable? "a") ; #t
(immutable? (string #\a)) ; #f
(immutable? (make-string 1 #\a)) ; #f
(string? "a") ;#t
(string? (string #\a)) ;#t
Видно, что строковые литералы неизменяемы, а строки, созданные с помощью функций, таковыми не являются, но при этом и те и другие являются строками!
Большинство операций над строками работают с любыми строками вне зависимости от свойства неизменяемости, однако возвращают почти все функции изменяемые строки. В большинстве случаев возвращается новая строка, так что беспокоиться о потенциальном изменении существующих строк практически не приходится. Кроме того, функции, изменяющие строку-аргумент, обычно имеют восклицательный знак (!
) в конце имени, что облегчает чтение кода с подобными побочными эффектами.
Преобразование строк в неизменяемые и обратно
Любую изменяемую строку можно сделать неизменяемой, создав её неизменяемую копию с помощью функции string->immutable-string
. Обратное преобразование не требует отдельной функции, вместо неё используется функция string-copy
, которая создаёт изменяемую копию любой строки.
Неизменяемые строки без потери неизменяемости можно только конкатенировать, используя отдельную функцию string-append-immutable
. Любые другие виды обработки потребуют работы с изменяемыми строками с преобразованием в неизменяемую строку результата.
Предназначение неизменяемых строк
Неизменяемые строки ценны… своей неизменяемостью: строковая константа, содержащая строковый литерал, всегда будет иметь одно и то же значение. Ключ хеш-таблицы тоже должен быть неизменяемым, чтобы хеш-функция вычисляла для него одно и то же значение при обращении к таблице по ключу. Даже в памяти неизменяемые строки хранятся более компактно!
Но есть у неизменяемости и обратная сторона: неизменяемые строки очень невыгодно обрабатывать: даже конкатенация потребует выделения памяти в количестве, равном суммарной длине всех объединяемых строк. И если в неизменяемой строке нужно поменять ровно один символ, то потребуется полная копия.
А вот изменяемые строки интерпретатор может использовать более эффективно. Особенно хорошо ему работать с изменяемой строкой фиксированной длины и заменять её части посимвольно или целиком, не меняя эту самую длину — в этом случае новая память выделяться не будет!
Модификация изменяемой строки
Рассмотрим две функции, которые модифицируют содержимое изменяемой строки "по месту", не создавая изменённой копии. Это будут функции string-set!
и string-copy!
. Как уже было отмечено выше, восклицательный знак в имени означает оказание эффекта на изменяемый аргумент.
string-set!
заменяет символ по указанному индексу на заданный:
(define s (make-copy "Cat")) ; изменяемая копия!
(string-set! s 0 #\B)
(string-set! s 1 #\o)
(displayln s) ; => Bot
string-copy!
копирует одну строку в середину другой, размещая копию в некоторой позиции. Опционально можно указать, какой участок копируемой строки будет вставлен в модифицируемую. Вот так функция применяется:
(define names "Bob,Tom")
(define s (make-string 10 #\.))
(displayln s) ; => ..........
(string-copy! s 3 " VS ")
(displayln s) ; => ... VS ...
(string-copy! s 0 names 0 3)
(displayln s) ; => Bob VS ...
(string-copy! s 7 names 4 7)
(displayln s) ; => Bob VS Tom
Строку можно копировать и саму в себя:
(define s (string-copy ".Tod"))
(string-copy! s 0 s 1 4)
s ; "Todd"
Задание
Реализуйте функцию scroll-left!
, которая должна "прокручивать" изменяемую строку-аргумент так, чтобы первый символ попадал в конец строки, а остальные символы, начиная со второго, сдвигались на одну позицию влево. Пустую строку модифицировать не нужно. Возвращать изменённую строку тоже не следует — функция должна только модифицировать свой аргумент!
Примеры:
(define s (string-copy "abc"))
(scroll-left! s)
s ; "bca"
(scroll-left! s)
s ; "cab"
Упражнение не проходит проверку — что делать? 😶
Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:
- Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет 🤨
Тесты устроены таким образом, что они проверяют решение разными способами и на разных данных. Часто решение работает с одними входными данными, но не работает с другими. Чтобы разобраться с этим моментом, изучите вкладку «Тесты» и внимательно посмотрите на вывод ошибок, в котором есть подсказки.
Мой код отличается от решения учителя 🤔
Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.
В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.
Прочитал урок — ничего не понятно 🙄
Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.
Кстати, вы тоже можете участвовать в улучшении курсов: внизу есть ссылка на исходный код уроков, который можно править прямо из браузера.