Проверка на вхождения атома в список — КиберПедия 

История развития пистолетов-пулеметов: Предпосылкой для возникновения пистолетов-пулеметов послужила давняя тенденция тяготения винтовок...

Своеобразие русской архитектуры: Основной материал – дерево – быстрота постройки, но недолговечность и необходимость деления...

Проверка на вхождения атома в список

2018-01-03 434
Проверка на вхождения атома в список 0.00 из 5.00 0 оценок
Заказать работу

>(defun checkA (at lst) (cond ((null lst) nil) ((eql at (car lst)) T) (T (checkA at (cdr lst))) )) CHECKA >(checkA 'A '(b c)) NIL >(checkA 'A '(a b c)) T

Удаление из списка N-ного элемента

>(defun delAt (pos lst) (cond ((null lst) nil) ((eql pos 0) (cdr lst)) (T (cons (car lst) (delat (- pos 1) (cdr lst)))) ) ) DELAT >(delAt 2 '(a b c d)) (A B D) >(delAt 5 '(a b c d)) (A B C D)

Добавление в список элемента в позицию N

>(defun insAt (pos lst at) (cond ((null lst) (cons at nil)) ((eql pos 0) (cons at lst)) (T (cons (car lst) (insAt (- pos 1) (cdr lst) at))) ) ) INSAT >(insat 3 '(a b c d e) 'f) (A B C F D E) >(insat 30 '(a b c d e) 'f) (A B C D E F)

В приведённых примерах рассматривалась рекурсия как таковая, однако в Lisp различают несколько вариантов рекурсии. Рассмотрим их подробнее.

 

· Параллельная рекурсия. Такая рекурсия возникает, когда в теле некоторой функции f1 содержится вызов функции f2, в качестве аргументов которой используется вызов функции f1.

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

· Вложенная рекурсия - возникает, когда функция в своём теле вызывает саму себя, используя себя же в качестве параметра этого вызова.

 

Очевидно, что функции INSAT, DELAT и CHECKA, рассмотренные выше, имеют параллельную рекурсивную схему. В качестве ещё одного примера параллельной рекурсии приведём следующий код, осуществляющий инверсию списка любой глубины вложенности:

>(defun ReversePlus (List) (cond ((atom List) List) ((null (cdr List)) (cons (ReversePlus (car List)) nil)) (T (append (ReversePlus (cdr List)) (ReversePlus (cons (car List) nil)))))) REVERSEPLUS >(ReversePlus '(1 (2 3) (4 (5 6)) 7)) (7 ((6 5) 4) (3 2) 1)

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

>(defun ReversePlus (List) (cond ((atom List) List) (T (Change List nil)) ) ) REVERSEPLUS >(defun Change (List Result) (cond ((null List) Result) (T (change (cdr List) (cons (ReversePlus (car List)) Result))) ) ) CHANGE >(ReversePlus '(1 (2 3) (4 (5 6)) 7)) (7 ((6 5) 4) (3 2) 1)

Вложенная рекурсия является достаточно редкой. Обычно этот вид рекурсии иллюстрируется с помощью специализированных математических функций, таких как функции Аккермана (Вирт, 1986). Такая функция определяется следующим образом:

 

A(0,n)=n+1;

A(m,0)=A(m-1,1); (m>0)

A(m,n)=A(m-1,A(m,n-1)); (m,n>0)

Очевидно, что функция Lisp, вычисляющая функцию Аккермана, будет построена в соответствии с определением:

> (defun akk (m n) (cond ((eql m 0) (+ 1 n)) ((eql n 0) (akk (- m 1) 1)) (T (akk (- m 1) (akk m (- n 1)))))) AKK >(akk 2 2)

Автор рекомендует выполнить вызов (akk 2 2) после вызова (trace akk) и рассмотреть дерево функций, имеющее достаточно любопытный вид.

Функционалы

 

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

Первым делом параметр-функцию надо передать, подавив её вычисление. Для этого используется функция FUNCTION

 

function (function функция аргументы) Функция позволяет подавить вычисление своего параметра. Действует аналогично QUOTE, но для аргументов функционального типа. В большинстве случаев функциональный параметр используется вместе с QUOTE, однако FUNCTION может быть использована для указания, что в качестве параметра следует ждать именно функцию.

 

Другая задача - вычислить переданный таким образом аргумент.

 

funcall (funcall функция) Обратная FUNCTION функция, позволяющая отменить подавление вычисления.

 

>(FUNCALL (FUNCTION (LAMBDA (X Y) (* X Y)) ) 2 4)

В приведённом примере с помощью LAMBDA задаётся функция, принимающая два аргумента и возвращающая их произведение. Вычисление этой функции подавляется с помощью FUNCTION (иначе мы получили бы ошибку о недопустимом числе аргументов). Затем над результатом, а именно функциональным аргументом, выполняется FUNCALL, с указанием двух аргументов - 2 и 4. Как видно, результатом было искомое произведение.

Приведём пример использования функционала. Например, мы хотим вычислить экстремум в некоторой последовательности:

>(defun extremum (lst fn el) (cond ((eq (cdr lst) nil) (car lst)) ((funcall fn (car lst) (extremum (cdr lst) fn el)) (car lst)) (T (extremum (cdr lst) fn el)))) EXTREMUM

Теперь вычислим минимум и максимум в последовательностях:

>(extremum '(6 -3 1 2 3) (function <) 1000) -3 >(extremum '(6 -3 1 2 3) (function >) -1000)

Таким образом, в зависимости от переданного в функцию функционального аргумента (< или >) мы вычисляем минимум или максимум. Третьим параметром функции является условно наибольшее и наименьшее число. В принципе, можно таковым считать первый символ последовательности (пример апеллирует к определённой ранее функции EXTREMUM).

>(defun extremum2 (lst arg) (extremum (cdr lst) arg (car lst))) EXTREMUM2 >(extremum2 '(1 2 3) (function <)) >(extremum2 '(1 2 3) (function >))

В Lisp присутствует также некоторое количество стандартных функций с функциональными аргументами, которые в основном также как и в нашем примере реализуют работу с последовательностями. Различают два основных класса функционалов - отображающие и применяющие. Применяющими функционалами являются уже известный нам FUNCALL, а также APPLY.

 

apply (apply функция аргумент) Функционал применяет некоторую функцию к аргументам, которые заданы списком в качестве второго параметра APPLY

 

>(apply 'car '((a b))) A >(funcall 'car '((a b))) (A B)

Отметим, что не все функции могут использоваться в качестве функциональных аргументов, например, использование OR и AND даёт ошибку (интерпретатор аргументирует это тем, что OR, AND и некоторые другие функции имеют некую специальную реализацию).

Общая идея, объединяющая применяющие функционалы, заключается в применении функций к спискам аргументов. Отображающие функционалы предназначены для повторения вычисления функции на элементах списка.

 

mapcar (mapcar функция список) Функция, позволяющая выполнить некоторое вычисление последовательно надо всеми элементами списка. Результатом является список, построенный из результатов применения функционального аргумента к элементам списка.

 

>(mapcar 'null '(() (a b) (nil c d))) (T NIL NIL) >(mapcar '(lambda (x) (cons x nil)) '(a b c)) ((A) (B) (C))

 

maplist (maplist функция список) Функция, позволяющая выполнить некоторое вычисление последовательно над хвостовыми частями списка. Сперва вычисляется функция от списка, затем (CDR список), затем (CDDR список), (СDDDR список) и так далее. Результатом является список, построенный из результатов применения функционального аргумента к хвостовым элементам списка.

 

>(maplist '(lambda (x) x) '(a b c)) ((A B C) (B C) (C))

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

>(defun max2 (lst) (progn (setq x (car lst)) (mapcar '(lambda (y) (if (< x y) (setq x y))) lst) x)) MAX2 >(max2 '(1 2 3))

 

mapcan mapcon (mapcan функция список) (mapcon функция список) Аналоги MAPCAR и MAPLIST, однако при своей работе строят список, используя функцию NCONC, т.е. потенциально структура исходных данных может разрушиться. Эти функционалы называются объединяющими, и одно из основных проявлений их особенностей заключается в уничтожении всех вхождений NIL в результирующий список.

 

>(setq x '(A B C)) (A B C) >(mapcar 'list x) ((A) (B) (C)) >(mapcan 'list x) (A B C) >(maplist 'list x) (((A B C)) ((B C)) ((C))) >(mapcon 'list x) ((A B C) (B C) (C))

Используем способность объединяющих функционалов уничтожать NIL для написания функции, проверяющей вхождение элемента в список:

 

>(defun isinlist (lst el) (car (mapcan (function (lambda (y) (if (eql y el) '(t) nil))) lst))) ISINLIST >(isinlist '(1 2 3) 2) T >(isinlist '(1 2 3) 4) NIL
mapc mapl (mapc функция список) (mapl функция список) Аналоги MAPCAR и MAPLIST, однако эти функции не строят никакого нового результата, а просто возвращают исходный список. Таким образом, для них может быть важен только некоторых побочный результат, в том случае, если использованная в качестве параметра функция содержит в той или иной форме присвоение, вывод на экран и т.п.

В качестве примера такого побочного действия рассмотрим вывод на экран.

>(mapc 'print '(A B C)) A B C (A B C)

Таким образом, все элементы списка были по очереди выведены, а в качестве результата вернулся исходный список. Очевидно, этот же эффект мог быть достигнут и MAPCAR, и MAPCAN, однако при этом не тратились ресурсы на конструирование новых структур в памяти.

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

>(mapcar '* '(1 2 3) '(4 5 6)) (4 10 18)

Или, например, попарное объединение элементов двух списков:

>(mapcar 'list '(1 2 3) '(4 5 6)) ((1 4) (2 5) (3 6))

Расширением этого примера может быть вычисление скалярного произведения двух векторов (в примере [1 2 3] и [4 5 6]):

>(apply '+ (mapcar '* '(1 2 3) '(4 5 6)))

В случае, если у списков параметров не совпадают размеры, лишние элементы списков будут просто проигнорированы:

>(mapcar 'list '(1 2 3) '(4 5)) ((1 4) (2 5))

Для MAPC и MAPL возвращаемым значением всегда будет первых аргумент-список.

Макросы

Макросом называется специальная функция, которая вычисляется не сразу а в два этапа: сперва вычисляется её тело, и только потом тело применяется к аргументам. Это бывает полезно, например, если мы хотим сформировать последовательность вычислений программно, а затем эту последовательность выполнить.

 

defmacro (defmacro имя лямбда-список тело) Определяет макрос. Синтаксис определения макроса в целом сходен с синтаксисом определения функции, однако при вызове макроса не происходит предварительное вычисление его аргументов.

 

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

В качестве примера макроса определим функцию SETQQ, которая была бы аналогична SETQ, но не требовала явного подавления вычисления второго аргумента.

>(defmacro setqq (x y) (list 'setq x (list 'quote y))) SETQQ >setqq x (a b c)) (A B C) >x (A B C)

 

macroexpand (macroexpand макровызов) Функция возвращает результат расширения макроса. Имеет важное значение для тестирования.

 

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

>(macroexpand '(setqq x (a b c))) (SETQ X '(A B C)) T

В том, что, несмотря на выведенное на печать значение T, функция возвращает именно результат раскрытия макроса, можно убедиться на следующем примере:

>(setq x (macroexpand '(setqq x (a b c)))) (SETQ X '(A B C)) >x (SETQ X '(A B C))

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

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

Макросы могут быть рекурсивными. Это означает, что расширение макроса даёт нам новый макрос. Рекурсивные макросы сродни рекурсивным функциям, за исключением того, что вычисление нового макровызова также производится в два этапа. В качестве примера рекурсивного макроса приведём следующий макрос, считающий сумму чисел в списках специального вида:

>(DEFMACRO UNWRAP (X Y) (cond ((atom Y) (list '+ X Y)) (T (list '+ X (list 'UNWRAP (car y) (cadr y)))))) UNWRAP >(unwrap 1 2) >(unwrap 1 (2 3)) >(unwrap 1 (2 (3 4))) >(macroexpand '(unwrap 1 (2 (3 4)))) (+ 1 (UNWRAP 2 (3 4)))

Естественно, отладка подобных макросов является достаточно трудоёмкой задачей.

Также как и для функций, для параметров макросов можно использовать зарезервированные слова &OPTIONAL, &KEY, &REST и &AUX. Дополнительно можно использовать свойство параметра &WHOLE, которое свяжет параметр со всей формой макровызова. Особенно полезно это в макросах, способных динамически менять свой код с помощью псевдофункций, таких как RPLACA, RPLACD, SETF м другие. В качестве примера приведём макрос, который сам себя превращает в функцию CAR:

>(defmacro frst (arg &whole call) >(rplaca call 'car)) FRST >(frst '(1 2))

С макросами тесно связан механизм обратной блокировки, позволяющий упростить их запись. Обратная блокировка изображается символом ` (апостроф, наклонённый вправо) и отличается от обычной тем, что внутри такого выражения можно управлять вычислением или невычислением секций. Например, запятая указывает, что следующее выражение должно быть вычислено. Символ @ позволяет включить идущее после него выражение в результирующий список.

>`(a,(+ 1 2) 3) (A 3 3) >(setq x '(+ 1 2)) (+ 1 2) >`(a,@x 3) (A + 1 2 3)

 

Ввод и вывод

 

Одной из основных задач для каждого языка программирования является организация взаимодействия с пользователем. В Lisp для этой цели предусмотрены функции ввода-вывода для стандартного устройства, а также функции работы с файлами.

 

read (read [поток]) Функция читает выражение из консоли и возвращает его в качестве результата. Функция никак не показывает пользователю, что ожидается ввод. В этой функции и далее: если поток не указан, чтение (или для других функций запись) производится со стандартного устройства ввода - консоли.

 

>(read) >>a+b A+B

Здесь при вызове функции READ пользователь ввёл выражение A+B (символ >> символизирует приглашение к вводу и не должен переноситься в текст при попытке выполнить пример). Результатом вычисления явилось собственно введённое выражение.

В качестве примера рассмотрим вычисление суммы от двух введённых чисел:

>(* (read) (read)) >>3 >>8 >(* (read) (read)) >>3 8

Иными словами, Lisp, как и большинство языков высокого уровня, выполняет синтаксический разбор ввода с консоли и выделяет параметры, идущие через пробел. При этом READ вводит именно выражения, т.е. если бы в примере мы ввели, например, (+ 5 7) (- 6 4) то этот ввод также был бы распознан как два параметра, другое дело что подобное выражение недопустимо в рамках примера. Модифицируем пример следующим образом:

>(* (eval (read)) (eval (read))) >>(+ 5 7) (- 6 4)

Некоторые символы в Lisp при вводе указывают интерпретатору на выполнение определённых действий (например, скобки, пробел, точка). Lisp хранит информацию о таких символах в специальной таблице, называемой таблице чтения. Эти символы называются макрознаками, и их набор может быть изменён (вследствие чего, естественно, может измениться и синтаксис языка Lisp).

 

set-macro-character (set-macro-character символ функция) Функция сопоставляет символу некоторую функцию, обрабатывающую этот символ при синтаксическом разборе выражений. Функция разбора должна иметь два аргумента - поток чтения и собственно символ, который мы обрабатываем.

 

Следующий пример заставляет Lisp интерпретировать символ % так же, как апостроф (т.е. как функцию QUOTE)

>(SET-MACRO-CHARACTER #\% (lambda (stream char) (list 'quote (read)))) T > %(A) (A)

 

print prin1 princ (print выражение [поток]) (prin1 выражение [поток]) (princ выражение [поток]) Функция выводит данные на консоль. PRINT выводит данные, печатает пробел и переводит строку. В различных диалектах указанные действия производятся в различной последовательности. Кроме того, иногда функция не печатает пробел. PRIN1 работает точно также, но не переводит строку. PRINC работает также, как и PRIN1, но преобразует при выводе некоторые типы данных (например, строковые) к более приятному для восприятия виду.

 

>(progn (setq data "a b c d") (print data) (prin1 data) (princ data)) "a b c d" "a b c d"a b c d "a b c d"

Как видно из примера, в Allegro Common Lisp функция PRINT сперва перевела строку, а затем вывела данные и пробел. Затем функция PRIN1 вывела те же самые данные, но строку не перевела и пробел не напечатала. Наконец, PRINC вывела всё ту же самую строку, но при выводе исключила кавычки. Однако, результатом этой функции, как мы видим, является всё та же строка.

 

terpri (terpri [поток]) Функция переводит строку в стандартном устройстве вывода.

 

format (format поток образец [аргументы]) Функция выводит строку в соответствии с образцом. Если вы качестве потока задаётся Т, то вывод производится на экран, в противном случае необходимо указать ассоциированный с файлом и открытый поток вывода. В качестве образца в функцию подаётся строка, определяющая формат вывода (в чём-то сходная со строкой форматирования функции printf языка С). Значения управляющих символов в строке даны в таблице 1. Текст в образце, не являющийся управляющими символами, также печатается. Функция возвращает в качестве своего значения nil.

 

Таблица 1

Символы управления выводом функции format

Символ Смысл
~% Перевод строки
~S Вывод очередного аргумента функцией PRIN1
~A Вывод очередного аргумента функцией PRINС
~nT Начинает вывод с колонки n. Если она уже была достигнута, выводится пробел.
~~ (Два знака тильды подряд) Вывод самого знака тильды

 

>(format t "~~Первый параметр ~A и второй параметр ~S" 5 7) ~ 5 7 NIL

Данная функция весьма удобна при выводе таблиц:

>(format t "~10T~%~A~10T~A~%~A~10T~A~%~A~10T~A~%" 1 2 3 4 5 6) 1 2 3 4 5 6

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

Для работы с файлами в Lisp предусмотрены следующие средства. Во-первых, определены специальные символы, указывающие на стандартное устройство ввода и стандартное устройство вывода. Эти символы, например, могут применяться в функциях PRIN1, PRINC и READ, а также FORMAT.

 

*STANDARD-INPUT* *STANDARD-OUTPUT* Символы указывают на стандартные устройства ввода и вывода соответственно (т.е. клавиатуру и экран).

 

Можно также связать какой-либо поток с файлом, используя специальные функции.

 

open (open файл направление_обмена) Функция открывает файл для чтения или записи. Если файл необходимо открыть для чтения, в качестве направления обмена указывается:INPUT, если для записи -:OUTPUT. Для одновременного чтения и записи указывают направление обмена:IO.В качестве возвращаемого значения передаётся открытый поток, который может быть использован в других функциях.

 

close (close поток) Функция закрывает поток, открытый ранее с помощью OPEN

 

with-open-file (with-open-file (имя_потока имя_файла направление_обмена) формы) Функция открывает поток, выполняет все формы и закрывает поток, не требуя от пользователя никаких дополнительных действий.

 

Отдельно следует отметить функцию, существенно облегчающую написание Lisp-программ и позволяющую создавать их во внешних редакторах и дробить на части:

 

load (load имя_файла) Функция загружает и исполняет код на языке Lisp, хранящийся в указанном файле.

 

Типы данных

Иерархия типов

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

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

В виду отсутствия жёсткой типизации важное значение имеет преобразование типов и проверка на принадлежность какому-либо типу.

 

typep (typep объект тип) Функция возвращает Т, если объект имеет указанный тип, и NIL в противном случае.

 

type-of (typep объект) Функция возвращает имя типа, который имеет объект.

 

coerce (coerce объект тип) Функция преобразует тип объекта в указанный параметром функции COERCE.

 

В качестве примера приведём следующий код:

>(type-of 'ATOM) SYMBOL >(type-of 1) FIXNUM >(type-of '(A B C)) CONS >(typep 'A 'atom) T >(typep 'A 'symbol) T >(typep 'A 'fixnum) NIL

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

В таблице 2 приводится полный список всех простых типов языка Lisp, а в таблице 3 - составных (в соответствии со спецификацией ANSI Lisp):

 

 

Таблица 2

Полный перечень простых типов языка Lisp.

arithmetic-error division-by-zero function real simple-condition
array double-float generic-function restart simple-error
atom echo-stream hash-table sequence simple-string
base-char end-of-file integer serious-condition simple-type-error
base-string error keyword short-float simple-vector
bignum extended-char list signed-byte simple-warning
bit file-error logical-pathname simple-array single-float
bit-vector file-stream long-float simple-base-string standard-char
broadcast-stream fixnum method simple-bit-vector standard-class
built-in-class float method-combination style-warning standard-generic-function
cell-error floating-point-inexact nil symbol standard-method
character floating-point-invalid-operation null synonym-stream standard-object
class floating-point-overflow number t storage-condition
compiled-function floating-point-underflow package two-way-stream stream
complex random-state package-error type-error stream-error
concatenated-stream ratio parse-error unbound-slot string
Condition rational pathname unbound-variable string-stream
Cons reader-error print-not-readable undefined-function structure-class
control-error readtable program-error unsigned-byte structure-object
      vector warning

 

Таблица 3

Полный перечень составных типов языка Lisp.

and double-float member satisfies simple-string
array eql mod short-float simple-vector
base-string float not signed-byte Single-float
bit-vector function or simple-array String
complex integer rational simple-base-string unsigned-byte
cons long-float real simple-bit-vector values
        vector

 

В отличие от большинства языков типы языка Lisp в своей иерархии не образуют выраженного дерева, хотя обычно и говорят об иерархии типов. Во-первых, имеются два специальных типа - T и NIL, первый из которых является наиболее общим типом для всех остальных, а второй является потомком любого из определённых типов. Иными словами, каждый символ имеет тип Т, а NIL является значением, принадлежащим всем типам. Кроме того, некоторые типы имеют несколько предков. Например, тип NULL является одновременно потомком SYMBOL и LIST. Кроме того, взглянув на таблицы 2 и 3, становится понятно, что некоторые типы одновременно являются и простыми, и составными. Таким образом, иерархия достаточно запутана и в дальнейшем мы будем рассматривать типы данных отдельными фрагментами общей иерархии.

 

 

Числа

 

Реализация численной арифметики первоначально не была одной из основных задач при разработке языка Lisp, однако, в виду необходимости конкурировать с другими языками в области научных вычислений, Lisp был существенно расширен именно в части работы с числами. Наиболее общим типом является тип NUMBER. Этот тип обозначает любое число, будь то целое, с плавающей запятой или комплексное. Также имеются следующие типы: RATIONAL (рациональные), FLOAT (с плавающей запятой) COMPLEX (комплексные) INTEGER (целые - подмножество RATIONAL) и RATION (натуральные дроби - подмножество RATIONAL). Также рассматривается работа с "целыми числами обыкновенного диапазона" - FIXNUM, которые наиболее эффективно поддерживаются языком.

Важным моментом является задание числового значения соответствующего типа. Целые числа в языке Lisp задаются в следующей форме:

 

[знак]модуль[точка]

 

Например: 5, +5, -5 и 5. являются целыми числами. Для определения дробного числа используется черта, например, 1/5, -5/7 и т.п. Сложнее дело обстоит с плавающей запятой. Обычная в таких случаях буква E может быть заменена буквой, указывающие точность числа (зависит от реализации). Допустимы следующие модификаторы:

Таблица 4

Модификаторы типа FLOAT

Буква Тип
S SHORT-FLOAT (укороченное число)
F SINGLE-FLOAT (одинарная точность)
E SINGLE-FLOAT (одинарная точность)
D DOUBLE-FLOAT (двойная точность)
L LONG-FLOAT (удлинённое число)

 

Например, 5F3 является числом 5000, взятым с обычной точностью. Наконец, комплексные числа могут быть заданы с помощью модификатора #C(действительная часть, мнимая часть). Эти числа существуют не во всех модификациях Lisp.

>#C(5 5) #c(5 5)

Для типа NUMBER определены следующие операции:

 

plusp minusp zerop (plusp число) (minusp число) (zerop число) Функции возвращают Т если число является, соответственно, положительным, отрицательным и нулём.

 

abs (abs число) Функция возвращает модуль числа.

 

Также определена операция унарного минуса, изменяющая знак:

>(setq X 5) >(plusp x) T >(minusp X) NIL >(minusp (- X)) T >(minusp (abs (- x))) NIL

Отдельно следует отметить функции сравнения (табл. 5). В Lisp они могут иметь произвольное количество аргументов:

Таблица 5

Основные арифметико-логические языка:Lisp

Мнемоника Смысл
= равенство
/= неравенство
> больше
< меньше
>= больше или равно
<= меньше или равно
min минимум из аргументов
max максимум из аргументов
+ Сложение
- Вычитание
* Умножение
/ Деление

 

>(< 1 2 3) T >(< 2 1 3) NIL >(< 1 3 2) NIL >(= 1 1 1 2) NIL >(= 1 1 1 1) T >(/= 1 1 1 2) NIL >(/= 1 2 3 4) T

Как видно, оператор < проверяет, упорядочен ли список по возрастанию, оператор = сравнивает все свои аргументы и т.п. Аналогичной особенностью обладают арифметические функции. Сложение складывает все аргументы, вычитание вычитает из первого все остальные и так далее.

 

Несколько отдельно стоят функции инкремента. Также как и SETF они могут изменять сам символ, а не только формировать возвращаемое значение:

 

incf decf (incf переменная приращение) (decf переменная приращение) Функции соответственно увеличивают и уменьшают значение ячейки памяти на величину приращения.

 

>(setq A 5) >(incf A) >A >(decf A) >A

Очевидно, что конструкция (DECF A) эквивалентна (SETF A(- A 1)).

Рассмотрим ещё несколько математических функций:

 

exp expt log sqrt (exp x) (expt x y) (log x [y]) (sqrt x) Функции работы со степенями и логарифмами: EXP вычисляет экспоненту в степени X, EXPT вычисляет X степени Y, LOG вычисляет логарифм X, если второй параметр указан, то по основанию Y, иначе по основанию E. SQRT вычисляет квадратный корень из X.

 

sin cos tan asin acos atan sinh cosh tanh asinh acosh atanh (sin x) (cos x) (tan x) (asin x) (acos x) (atan x) (sinh x) (cosh x) (tanh x) (asinh x) (acosh x) (atanh x) Тригонометрические функции: синус, косинус, тангенс, арксинус, арккосинус, арктангенс, гиперболические синус, косинус, тангенс, арксинус, арккосинус, арктангенс.

 

В качестве примера проверим правильность известной тригонометрической формулы :

>(setq x 12) >(+ (EXPT (SIN X) 2) (EXPT (COS X) 2)) 1.0

 

random (random x) Генерирует случайные целые числа, неотрицательные и меньшие X.

 

>(random 10) >(random 10)

Для отдельных подтипов типа NUMBER предусмотрены специальные операции. Отметим следующие операции, определённые только для целых чисел:

 

evenp oddp gcd lcm (evenp x) (oddp x) (gcd x [x1 x2 x3…]) (lcm x [x1 x2 x3…]) EVENP возвращает T, если число чётно, ODDP - если нечётно. GCD вычисляет наибольший общий делитель, а LCM - наименьшее общее кратное.

 

>(gcd 8 14) >(lcm 8 14)

Символы

Символ является достаточно необычной структурой данных. Во-первых, с символом может быть связано некоторое значение, с помощью использования функций SET, SETQ и SETF. Кроме того, символ может указывать на лямбда-выражение. И, наконец, символ может иметь свойства. Таким образом, символ является структурированным объектом, состоящим из нескольких элементов - имени, значения, лямбда-выражения и списка свойств, а также данные о пространстве имён, которому принадлежит этот символ.

Некоторые функции, такие как BOUNDP, FBOUNDP и SYMBOL-FUNCTION мы уже рассматривали, когда говорили о переменных и функциях. Здесь мы рассмотрим ещё несколько функций, связанных с получением значений символов.

 

symbol-name (symbol-name симвыол) Возвращает имя символа в виде строки.
>(setq x 5) >(symbol-name 'x) "X"

Любопытной особенностью символов является возможность их связывания со специальными ячейками памяти, называемыми свойствами. Свойство символа характеризуется его именем и значением. У подобного механизма есть нечто общее с понятиями структур и записей таких языков, как С и Pascal, однако символ может иметь динамический набор свойств. Кроме того, свойства символа не связаны с его значением, независимо от изменения значения символа его свойства не меняются.

 

get (get символ свойство) Возвращает ссылку на свойство символа, если такого свойства не существует, оно не создаётся, возвращается NIL. По умолчанию ссылка может использоваться для чтения его значения, однако в случае применения функции SETF становится возможно создание свойства и запись в него нового значения.

В следующем примере мы создаём у символа y свойство property, И записываем в него значение 5. Отметим, что способ, которым это было выполнено, специфичен для Common Lisp, в других диалектах для этого используется специальная функция PUTPROP:

>(get 'y 'property) NIL >(setf (get 'y 'property) 5) >(get 'y 'property)

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

 

symbol-plist (symbol-plist символ) Возвращает список всех свойств и их значений для некоторого символа.

 

remprop (remprop символ имя_свойства) Удаляет присвоенное ранее символу свойство. Функция возвращает Т, если такое свойство есть, и NIL, если такого свойства нет.

 

>(setf (get 'sym 'size) 'big) BIG >(setf (get 'sym 'color) 'red) RED >(symbol-plist 'sym) (SIZE BIG COLOR RED) >(remprop 'sym 'color) T >(symbol-plist 'sym) (SIZE BIG)

Списки

Наряду с атомом, список является основной структурой данных Lisp. Как уже упоминалось, любой список состоит из ячеек, включающих в себя голову и хвост. Абстрактной списковой ячейкой является точечная пара, у которой хвост может представлять собой не только список, но и атом. Важно понимать, что точечная пара является обобщением всех списковых ячеек, и её голова и хвост сами могут быть точечными парами.

 

Список может быть определён рекуррентно следующим образом:

1. NIL или () является списком.

2. Структура, состоящая из точечных пар, в каждой из которых хвост является списком, сама является списком.

Таким образом, результат объединения (CONS A (B)) является списком, а (CONS A B) нет, так как B это атом, а не список. Тем не менее, обе функции в результате дадут нам точечную пару.

Списки в Lisp имеют базовый тип LIST. Два основных его подтипа это пустой список NIL и непустой список CONS. Элементом списка может быть объект произвольного типа.

Многие операции со списками были уже рассмотрены ранее, поэтому здесь мы остановимся только на некоторых, ранее не упоминавшихся:

 

rplaca rplacd (rplaca список значение) (rplacd список значение) Функции изменяет значение соответственно головы и хвоста списка, указанного первым аргументом, на значение второго аргумента.

 

>(setq x '(a b (c))) (A B (C)) >(rplaca x '(m n)) ((M N) B (C)) >x ((M N) B (C)) >(rplacd x 'nil) ((M n))

 

append (append список1 список2) Функция объединяет свои аргументы-списки в один список. В отличие от LIST, аргументы APPEND считаются списками элементов, а не элементами списка.
>(APPEND '(a (b) c) '(d (e) f)) (A (B) C D (E) F) >(LIST '(a (b) c) '(d (e) f)) ((A (B) C) (D (E) F))

 

nconc (nconc список1 список2) Функция объединяет свои аргументы-списки в один список. В отличие от APPEND, функция разрушает структуру исходных данных - список, заданный первым аргументом, принимает значение результата NCONC.

 

>(APPEND '(a (b) c) '(d (e) f)) (A (B) C D (E) F) >(nconc '(a (b) c) '(d (e) f)) (A (B) C D (E) F) >(setq arg1 '(a (b) c)) (A (B) C) >(setq arg2 '(d (e) f)) (D (E) F) >(nconc arg1 arg2) (A (B) C D (E) F) >arg1 (A (B) C D (E) F)

Структуроразрушающие функции надо использовать с большой осторожностью, так как их вызов может неожиданным образом влиять на другие функции. Например, следующий вызов приводит к результатам, на первый взгляд достаточно странным:

>(setq x '(a)) (A) >(nconc x x) (A A A A A A A A A A...)

 

Таким образом, объединяя, казалось бы, два элементарных списка, мы получили список бесконечной длины[5].

 

remove (remove образец список) Функция удаляе<

Поделиться с друзьями:

Архитектура электронного правительства: Единая архитектура – это методологический подход при создании системы управления государства, который строится...

Кормораздатчик мобильный электрифицированный: схема и процесс работы устройства...

Адаптации растений и животных к жизни в горах: Большое значение для жизни организмов в горах имеют степень расчленения, крутизна и экспозиционные различия склонов...

Индивидуальные и групповые автопоилки: для животных. Схемы и конструкции...



© cyberpedia.su 2017-2024 - Не является автором материалов. Исключительное право сохранено за автором текста.
Если вы не хотите, чтобы данный материал был у нас на сайте, перейдите по ссылке: Нарушение авторских прав. Мы поможем в написании вашей работы!

0.209 с.