Функция format

Сегодня разговори пойдёт про функцию format. Это стандартный способ lisp для вывода данных в консоль. Наверное из-за того, что lisp изначально проектировался как язык обработки списков, функция format довольно мощная. Давайте начнём своё знакомство с ней.


Первый аргумент функции format это либо T, либо NIL, либо поток. Если значение аргумента будет T, то функция вернёт вывод в стандартный поток вывода. В случае NIL функция сформирует строку и вернёт её в качестве результата. Ну, а если поток, то очевидно вывод будет в поток.

Вторым аргументом этой функции является строка форматирования. Кстати, можно провести аналогию с printf из c.

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

Директивы форматирования

Все директивы начинаются со знака тильда ~ и кончаются символом директивы. Иногда между этими символами могут располагаться параметры, управляющие выводом. Кроме числовых параметров между тильдой и символом директивы могут быть символы решётки (#), двоеточия (:), собаки (@) и запятой (,).

~A — наиболее широкая директива, которая печатает S-выражение в удобном для восприятия формате. Например, сктроки печатаются без кавычек. Принимает числовой параметр, который определяет отступ в пробелах до значения (или если после символа @, то после)

(format nil "The value is: ~a" 10) ; "The value is: 10"
(format nil "The value is: ~a" "foo") ; "The value is: foo"
(format nil "The value is: ~a" (list 1 2 3)) ; "The value is: (1 2 3)"

~S — похоже на ~A, однако, пытается напечатать аргумент так, чтобы его потом можно было считать обратно.

Ещё две похожие директивы это ~%, ~&, обе они выполняют перевод на новую строку. Однако они отличаются тем, что ~% всегда создаёт новую строку, а ~& если она не находится в начале строки. Это удобно и позволяет не заботится о выводе дрегих функци, относительно своей. Т.е. мы можем просто ставить везде ~& и не думать о том, поставила ли перевод строки функция, которая была вызвана до нашей. Если поставила, то мы останемся на той же строке. Если нет, то сделаем перевод. Обе директивы принимают параметр обозначающий количество новых строк, которые нужно вставить.

Вывод знаков и чисел

Форматирование целых чисел

~C — самая простая директива для вывода символов. Наверное, это сокращение от character. С двоеточием эта директива может выводить и непечатные символы. А вместе с символом @ можно вывести символ как бы он писался в программе на lisp. А вместе эти два модификатора выведут информацию о том, как ввести этот символ с клавиатуры. Последняя возможность может быть и не реализована в вашей реализации lisp, сильно не надейтесь.

Директивы ~D, ~X, ~O, ~B и ~R используются для вывода целых чисел. Думаю понятно для какой системы счисления какая используется. Рассмотрим параметры для директивы ~d. Двоеточие выведет разделители разрядов, @ выведет знак. С помощью первого числового параметра можно задать длину строки вывода (количество символов), а вторым символ заполнения. Пример:

(format nil "~12d" 1000000) ; " 1000000"
(format nil "~12,'0d" 1000000) ; "000001000000"

Так же можно управлять и символом-разделителем разрядов.

(format nil "~:d" 100000000) ; "100,000,000"
(format nil "~,,'.,4:d" 100000000) ; "1.0000.0000"

 

Для директивы ~R система счисления передаётся первым параметром.

Форматирование чисел с плавающей точкой

~F, ~E, ~G, ~$ — директивы для вывода чисел с плавающей точкой. F используется для вывода в чнловеко-удобном виде, а директива E для вывода в компьютерной нотации. ~$ используется для вывода денежных переменных, она упрощает ~F. G используется для более общего вывода, объединяет возможности E и F. Символ @ выводит знак, числом можно указать количество цифр после запятой. Примеры

(format nil "~f" pi) ; "3.141592653589793d0"
(format nil "~,4f" pi) ; "3.1416"
(format nil "~e" pi) ; "3.141592653589793d+0"
(format nil "~,4e" pi) ; "3.1416d+0"

 

~R — эта директива уже была в целых числах, но это не единтсвенное её применение. Без параметра она выводит число словами на английском языке, с двоеточием делает из него числительное, а с @ выводит числов римским способом записи.

(format nil "~r" 1234) ; "one thousand two hundred thirty-four"
(format nil "~:r" 1234) ; "one thousand two hundred thirty-fourth"
(format nil "~@r" 1234) ; "MCCXXXIV"
(format nil "~:@r" 1234) ; "MCCXXXIIII"

Директива ~p просто добавляет s в конец слова если параметр не равен 1. С двоеточие эта директива печатает число словами. С символом @ вместо s будет добавляться ies.

(format nil "file~p" 1) ; "file"
(format nil "file~p" 10) ; "files"
(format nil "file~p" 0) ; "files"

(format nil "~r file~:p" 1) ; "one file"
(format nil "~r file~:p" 10) ; "ten files"
(format nil "~r file~:p" 0) ; "zero files"

(format nil "~r famil~:@p" 1) ; "one family"
(format nil "~r famil~:@p" 10) ; "ten families"
(format nil "~r famil~:@p" 0) ; "zero families

 

Директива ~( и парная ей ~) управляет регистром того, что внутри, на примерах станет всё ясно.

(format nil "~(~a~)" "tHe Quick BROWN foX") ; "the quick brown fox"
(format nil "~(~a~)" "tHe Quick BROWN foX") ; "the quick brown fox"
(format nil "~:(~a~)"p "tHe Quick BROWN foX") ; "The Quick Brown Fox"
(format nil "~:@(~a~)" "tHe Quick BROWN foX") ; "THE QUICK BROWN FOX"

 

Условное форматирование

Директива ~[ ~; ~] отвечает за этот вывод. Внутри квадратных скобок значения разделяются точкой запятой. В зависимости от условия будет выбрано одна из этих значений. Если значение больше количства элементов, то ничего не выведется. Для вывода значения по умолчанию нужно обозначить символ двоетосия у директивы ~:;.

(format nil "~[cero~;uno~;dos~]" 0) ; "cero"
(format nil "~[cero~;uno~;dos~]" 1) ; "uno"
(format nil "~[cero~;uno~;dos~]" 2) ; "dos"
(format nil "~[cero~;uno~;dos~]" 3) ; ""

(format nil "~[cero~;uno~;dos~:;mucho~]" 100) ; "mucho"

 

Ещё есть вот такая возможность выводить списки не полностью, но человеко-подобно. Т.е. как бы их вывел человек попроси мы его написать список:

(defparameter *list-etc*
"~#[NONE~;~a~;~a and ~a~:;~a, ~a~]~#[~; and ~a~:;, ~a, etc~].")

(format nil *list-etc*) ; "NONE."
(format nil *list-etc* 'a) ; "A."
(format nil *list-etc* 'a 'b) ; "A and B."
(format nil *list-etc* 'a 'b 'c) ; "A, B and C."
(format nil *list-etc* 'a 'b 'c 'd) ; "A, B, C, etc."
(format nil *list-etc* 'a 'b 'c 'd 'e) ; "A, B, C, etc."

 

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

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

(format t "~:[FAIL~;pass~]" test-result)

 

В последнем случае оба элемента могут быть пустыми, но директива всё равно должна содержать ~;.

Наконец, данная директива со знаком @, позволяет выводить значение внутри директивы, только если аргумет не nil.

(format nil "~@[x= ~a~]" 10) ; x= 10
(format nil "~@[x= ~a~]" nil) ; //nothing

 

Итеративное форматирование

Итеративная директива ~{ ~} управляет выводом списка. То есть каждый элемент списка выводится с помощью строки, которая между данными директивами. Пример для лучшего понимания:

(format nil "~{~a, ~}" (list 1 2 3)) ; "1, 2, 3, "

 

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

(format nil "~{~a~^, ~}" (list 1 2 3)) ; "1, 2, 3"

 

Модификатор @ для данной директивы позволяет обрабатывать оставшиеся аргументы как список. Специальный параметр # внутри этой директивы позволяет получить количество оставшихся элементов.

(format nil "~{~a~#[~;, and ~:;, ~]~}" (list 1 2 3)) ; "1, 2, and 3"

Решить это можно так:

(defparameter *english-list*
"~{~#[~;~a~;~a and ~a~:;~@{~a~#[~;, and ~:;, ~]~}~]~}")
(format nil *english-list* '()) ; ""
(format nil *english-list* '(1)) ; "1"
(format nil *english-list* '(1 2)) ; "1 and 2"
(format nil *english-list* '(1 2 3)) ; "1, 2, and 3"
(format nil *english-list* '(1 2 3 4)) ; "1, 2, 3, and 4"

 

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

Управление порядком аргументов

Директива ~* пропускает 1 параметр. С двоеточием эта директива отступает назад на 1 параметр. Это пригодится если хочется напечатать одно и то же число и текстом, и числом.
Объединяя две директивы

(format nil "I saw ~r el~:*~[ves~;f~:;ves~]." 0) ; "I saw zero elves."
(format nil "I saw ~r el~:*~[ves~;f~:;ves~]." 1) ; "I saw one elf."
(format nil "I saw ~r el~:*~[ves~;f~:;ves~]." 2) ; "I saw two elves."

 

Внутри директивы ~{ ~} директива ~* пропускает итерации. Вместе с символом @ директива ~* ссылается на аргумент относительно начала списка аргументов.
Это далеко не все директивы. К тому же пропущены другие способы вывода (табличный например). Быть может я рассмотрю это в следующих статьях.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *

Этот сайт использует Akismet для борьбы со спамом. Узнайте, как обрабатываются ваши данные комментариев.