Бесплатный курс по Racket. Зарегистрируйтесь для отслеживания прогресса →

Racket: Форматирование строк

В программировании часто возникает задача собрать строку из значений, которые не являются строками сами по себе. Для решения подобных задач применяют форматирование (formatting) строк (оно же — "строковая интерполяция"). В Racket средств для форматирования присутствует довольно много, но мы рассмотрим самые популярные.

Функции format и printf

Функции format и printf ожидают в качестве первого аргумента строку-шаблон и несколько значений, которые будут в этот шаблон подставлены. Разница же между функциями в том, что вызов format вычисляется в строку, а printf сразу выводит значение на экран (результатом же вычисления будет #<void>).

Шаблон может содержать произвольный текст, в нужные места которого вставлены следующие последовательности:

  • ~a, означающая "подставить в читаемом виде";
  • ~v, означающая "вывести как в REPL";
  • ~n, означающая "вставить перевод строки";
  • ~~, означающая "просто вывести ~".

Есть и другие последовательности, о которых можно почитать здесь, но перечисленные выше используются чаще всего.

~a используется всегда, когда нужно подставить число, строку или строковый символ. При этом в текст не попадут кавычки, обрамляющие строковый литерал, и символы будут подставлены в печатаемом виде, а не в виде кода (a вместо #\a).

~v пригодится тогда, когда нужно вывести некое комплексное значение в таком виде, в каком его отображает интерпретатор в интерактивном режиме. Этот вариант вывода очень полезен при отладке: выведенные значения часто можно скопировать в REPL или в редактор и повторно вычислить.

Назначение ~n и ~~ довольно очевидно, согласитесь?

Вот несколько примеров применения printf:

(printf "This is a list: ~v~n" '(1 2 3))
; => This is a list: '(1 2 3)
(printf "This is a string: ~v~n" "hello")
; => This is a string: "hello"
(printf "~a + ~a = ~a~n" 40 2 (+ 40 2))
; => 40 + 2 = 42
(printf "~v is \"~a\"~n" #\! #\!)
; => #\! is "!"
(printf "~v prints as \"~a\"~n" "abc" "abc")
; => "abc" prints as "abc"
(printf "~v prints as \"~a\"~n" '(42) '(42))
; => '(42) prints as "(42)"

Функции ~a и ~v

Для большинства последовательностей, работающих в шаблонах, есть аналоги в виде одноимённых функций, таких как ~a и ~v. Рассмотрим подробнее ~a, остальные же вы сможете изучить сами.

~a принимает одно или несколько значений, которые будет преобразованы в строки и объединены в результирующую строку. Кроме перечня значений функция принимает десяток опциональных именованных аргументов, таких как #:separator, #:min-width и #:align (полный список, как обычно, имеется в документации).

Вот как работают само преобразование аргументов и подстановка разделителя:

(~a 1)                      ; "1"
(~a 1 2 3)                  ; "123"
(~a 1 "+" 3)                ; "1+3"
(~a 1 2 3 #:separator ", ") ; "1, 2, 3"

#:min-width и #:align удобны для вывода чисел в столбик:

(~a 42 #:min-width 6)                 ; "42    "
(~a 42 #:min-width 6 #:align 'center) ; "  42  "
(~a 42 #:min-width 6 #:align 'right)  ; "    42"

Слова с одинарной кавычкой в начале — это те самые символы, которые по-английски называются "symbols". Symbols будут рассмотрены позднее, пока можете воспринимать их как константы, означающие сами себя — более компактная и быстрая альтернатива строковым константам.

Среди аргументов ~a есть и такие, которые ограничивают максимальную длину результирующей строки, задающие то, чем заполняются отступы слева и справа. Словом, возможности широки!

Задание

Реализуйте функцию add, которая должна для двух чисел вычислить сумму в виде "сложения в столбик" (в виде строки). Ширина текста должна подстраиваться под количество разрядов в сумме. Сами аргументы считайте всегда целыми и неотрицательными.

Примеры:

(displayln (add 1 2))
; => +1
; =>  2
; =>  -
; =>  3
(displayln (add 1 9))
; => + 1
; =>   9
; =>  --
; =>  10
(displayln (add 1223234 56))
; => +1223234
; =>       56
; =>  -------
; =>  1223290
Упражнение не проходит проверку — что делать? 😶

Если вы зашли в тупик, то самое время задать вопрос в «Обсуждениях». Как правильно задать вопрос:

  • Обязательно приложите вывод тестов, без него практически невозможно понять что не так, даже если вы покажете свой код. Программисты плохо исполняют код в голове, но по полученной ошибке почти всегда понятно, куда смотреть.
В моей среде код работает, а здесь нет 🤨

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

Мой код отличается от решения учителя 🤔

Это нормально 🙆, в программировании одну задачу можно выполнить множеством способов. Если ваш код прошел проверку, то он соответствует условиям задачи.

В редких случаях бывает, что решение подогнано под тесты, но это видно сразу.

Прочитал урок — ничего не понятно 🙄

Создавать обучающие материалы, понятные для всех без исключения, довольно сложно. Мы очень стараемся, но всегда есть что улучшать. Если вы встретили материал, который вам непонятен, опишите проблему в «Обсуждениях». Идеально, если вы сформулируете непонятные моменты в виде вопросов. Обычно нам нужно несколько дней для внесения правок.

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


Нашли ошибку? Есть что добавить? Пулреквесты приветствуются https://github.com/hexlet-basics
Если вы столкнулись с трудностями и не знаете, что делать, задайте вопрос в нашем большом и дружном сообществе