目录

Lisp - 快速指南

LISP - Overview

在FORTRAN开发后不久,John McCarthy于1958年发明了LISP。 它首先由Steve Russell在IBM 704计算机上实现。

它特别适用于人工智能程序,因为它有效地处理符号信息。

Common Lisp起源于20世纪80年代和90年代,旨在统一几个实现组的工作,这些组是Maclisp的继承者,如ZetaLisp和NIL(Lisp的新实现)等。

它是一种通用语言,可以轻松扩展以用于特定实现。

用Common LISP编写的程序不依赖于机器特定的特性,例如字长等。

共同LISP的特点

  • 它与机器无关

  • 它使用迭代设计方法,并且易于扩展。

  • 它允许动态更新程序。

  • 它提供高级调试。

  • 它提供了高级的面向对象编程。

  • 它提供方便的宏系统。

  • 它提供广泛的数据类型,如对象,结构,列表,向量,可调整数组,哈希表和符号。

  • 它是基于表达式的。

  • 它提供了面向对象的条件系统。

  • 它提供完整的I/O库。

  • 它提供了广泛的控制结构。

应用程序内置LISP

在Lisp中构建的大型成功应用程序。

  • Emacs
  • G2
  • AutoCad
  • Igor Engraver
  • Yahoo Store

LISP - Environment Setup

本地环境设置 (Local Environment Setup)

如果您仍然愿意为Lisp编程语言设置环境,则需要在计算机上使用以下两个软件:(a)文本编辑器和(b)Lisp Executer。

文本编辑器 (Text Editor)

这将用于键入您的程序。 少数编辑器的示例包括Windows Notepad,OS Edit命令,Brief,Epsilon,EMACS和vim或vi。

文本编辑器的名称和版本可能因不同的操作系统而异。 例如,Notepad将在Windows上使用,vim或vi可以在Windows以及Linux或UNIX上使用。

使用编辑器创建的文件称为源文件,包含程序源代码。 Lisp程序的源文件通常以扩展名“ .lisp ”命名。

在开始编程之前,请确保您有一个文本编辑器,并且您有足够的经验来编写计算机程序,将其保存在文件中,最后执行它。

Lisp Executer

源文件中编写的源代码是程序的可读源代码。 它需要“执行”,变成机器语言,以便您的CPU可以按照给出的指令实际执行程序。

这个Lisp编程语言将用于将源代码执行到最终的可执行程序中。 我假设您具有编程语言的基本知识。

CLISP是用于在Windows中设置LISP的GNU Common LISP多架构编译器。 Windows版本在Windows下使用MingW模拟unix环境。 安装程序会处理此问题并自动将clisp添加到Windows PATH变量中。

您可以从这里获取最新的Windows CLISP - https://sourceforge.net/projects/clisp/files/latest/download

列出环境设置

默认情况下,它为“逐行”解释器在“开始”菜单中创建快捷方式。

如何使用CLISP

在安装过程中,如果选择选项(推荐), clisp将自动添加到PATH变量中。这意味着您只需打开一个新的命令提示符窗口并键入“clisp”即可打开编译器。

要运行* .lisp或* .lsp文件,只需使用 -

clisp hello.lisp

LISP - Program Structure

LISP表达式称为符号表达式或s表达式。 s表达式由三个有效对象,原子,列表和字符串组成。

任何s表达式都是有效的程序。

LISP程序可以在interpreter上运行,也可以作为compiled code.

解释器在重复循环中检查源代码,这也称为读取 - 评估 - 打印循环(REPL)。 它读取程序代码,对其进行评估,并打印程序返回的值。

一个简单的程序

让我们编写一个s表达式来找到三个数字7,9和11的总和。为此,我们可以在解释器提示符下键入。

(+ 7 9 11)

LISP返回结果 -

27

如果要运行与编译代码相同的程序,则创建名为myprog.lisp的LISP源代码文件,并在其中键入以下代码。

(write (+ 7 9 11))

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它,返回的结果为 -

27

LISP使用前缀表示法

您可能已经注意到LISP使用prefix notation.

在上面的程序中,+符号作为数字求和过程的函数名称。

在前缀表示法中,运算符在其操作数之前写入。 例如,表达式,

a * ( b + c )/d

将写成 -

(/ (* a (+ b c) ) d)

让我们再举一个例子,让我们编写将60华氏度的华氏温度转换为摄氏度的代码 -

此转换的数学表达式将是 -

(60 * 9/5) + 32

创建名为main.lisp的源代码文件,并在其中键入以下代码。

(write(+ (* (/ 9 5) 60) 32))

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它,返回的结果为 -

140

评估LISP计划

LISP计划的评估有两个部分 -

  • 通过读者程序将程序文本翻译成Lisp对象

  • 评估者程序根据这些对象实现语言的语义

评估过程采取以下步骤 -

  • 读者将字符串转换为LISP对象或s-expressions.

  • 评估器定义了从s表达式构建的Lisp forms语法。 第二级评估定义了一种语法,用于确定哪些s-expressions是LISP表单。

  • 求值程序作为一个函数,将有效的LISP表单作为参数并返回一个值。 这就是我们将LISP表达式放在括号中的原因,因为我们将整个表达式/表单作为参数发送给赋值器。

'Hello World'计划

学习一门新的编程语言并没有真正起飞,直到你学会了用这种语言来问候整个世界,对吧!

因此,请创建名为main.lisp的新源代码文件,并在其中键入以下代码。

(write-line "Hello World")
(write-line "I am at 'IOWIKI'! Learning LISP")

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它,返回的结果为 -

Hello World
I am at 'IOWIKI'! Learning LISP

LISP - Basic Syntax

LISP中的基本构建块

LISP计划由三个基本构建模块组成 -

  • atom
  • list
  • string

atom是连续字符的数字或字符串。 它包括数字和特殊字符。

以下是一些有效原子的例子 -

hello-from-tutorials-point
name
123008907
*hello*
Block#221
abc123

list是括在括号中的原子序列和/或其他列表。

以下是一些有效列表的示例 -

( i am a list)
(a ( a b c) d e fgh)
(father tom ( susan bill joe))
(sun mon tue wed thur fri sat)
( )

string是用双引号括起来的一组字符。

以下是一些有效字符串的示例 -

" I am a string"
"a ba c d efg #$%^&!"
"Please enter the following details :"
"Hello from 'IOWIKI'! "

添加评论

分号符号(;)用于表示注释行。

例如,

(write-line "Hello World") ; greet the world
; tell them your whereabouts
(write-line "I am at 'IOWIKI'! Learning LISP")

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它,返回的结果为 -

Hello World
I am at 'IOWIKI'! Learning LISP

移动到下一个之前的一些值得注意的点

以下是一些需要注意的重点 -

  • LISP中的基本数字运算是+, - ,*和/

  • LISP表示函数调用f(x)为(fx),例如cos(45)写为cos 45

  • LISP表达式不区分大小写,cos 45或COS 45相同。

  • LISP尝试评估所有内容,包括函数的参数。 只有三种类型的元素是常量,并始终返回自己的值

    • Numbers

    • 字母t,代表逻辑真实。

    • nil,表示逻辑false,以及空列表。

关于LISP表格的更多信息

在前一章中,我们提到LISP代码的评估过程采取以下步骤。

  • 读者将字符串转换为LISP对象或s-expressions.

  • 评估器定义了从s表达式构建的Lisp forms语法。 第二级评估定义了一种语法,用于确定哪些s表达式是LISP表单。

现在,LISP形式可能是。

  • An Atom
  • An empty or non-list
  • 任何以符号作为第一个元素的列表

求值程序作为一个函数,将有效的LISP表单作为参数并返回一个值。 这就是我们将LISP expression in parenthesis,原因LISP expression in parenthesis,因为我们将整个表达式/表单作为参数发送给赋值器。

LISP中的命名约定

名称或符号可以包含除空格,开括号和右括号,双引号和单引号,反斜杠,逗号,冒号,分号和竖线之外的任意数量的字母数字字符。 要在名称中使用这些字符,您需要使用转义字符(\)。

名称可以有数字但不完全由数字组成,因为它会被读作数字。 类似地,名称可以具有句点,但不能完全由句点组成。

使用单引号

LISP评估所有内容,包括函数参数和列表成员。

有时,我们需要按字面意思获取原子或列表,并且不希望它们被评估或视为函数调用。

为此,我们需要在原子或列表前面加上一个引号。

以下示例演示了这一点。

创建一个名为main.lisp的文件,并在其中键入以下代码。

(write-line "single quote used, it inhibits evaluation")
(write '(* 2 3))
(write-line " ")
(write-line "single quote not used, so expression evaluated")
(write (* 2 3))

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它,返回的结果为 -

single quote used, it inhibits evaluation
(* 2 3) 
single quote not used, so expression evaluated
6

LISP - Data Types

在LISP中,变量不是类型,而是数据对象。

LISP数据类型可以归类为。

  • Scalar types - 例如,数字类型,字符,符号等。

  • Data structures - 例如,列表,向量,位向量和字符串。

任何变量都可以将任何LISP对象作为其值,除非您已明确声明它。

虽然,没有必要为LISP变量指定数据类型,但是,它有助于某些循环扩展,方法声明以及我们将在后面的章节中讨论的一些其他情况。

数据类型排列为层次结构。 数据类型是一组LISP对象,许多对象可能属于一个这样的集合。

typep谓词用于查找对象是否属于特定类型。

函数type-of返回给定对象的数据类型。

在LISP中键入说明符

类型说明符是数据类型的系统定义符号。

arrayfixnumpackagesimple-string
atomfloatpathnamesimple-vector
bignumfunctionrandom-statesingle-float
bithash-tableratiostandard-char
bit-vectorintegerrationalstream
characterkeywordreadtablestring
[common]listsequence[string-char]
compiled-functionlong-floatshort-floatsymbol
complexnillsigned-bytet
consnullsimple-arrayunsigned-byte
double-floatnumbersimple-bit-vectorvector

除了这些系统定义的类型,您还可以创建自己的数据类型。 使用defstruct函数定义结构类型时,结构类型的名称将成为有效的类型符号。

例子1 (Example 1)

创建名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq x 10)
(setq y 34.567)
(setq ch nil)
(setq n 123.78)
(setq bg 11.0e+4)
(setq r 124/2)
(print x)
(print y)
(print n)
(print ch)
(print bg)
(print r)

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它,返回的结果为 -

10 
34.567 
123.78 
NIL 
110000.0 
62

例子2 (Example 2)

接下来让我们检查上一个示例中使用的变量类型。 创建名为main的新源代码文件。 lisp并在其中键入以下代码。

(defvar x 10)
(defvar y 34.567)
(defvar ch nil)
(defvar n 123.78)
(defvar bg 11.0e+4)
(defvar r 124/2)
(print (type-of x))
(print (type-of y))
(print (type-of n))
(print (type-of ch))
(print (type-of bg))
(print (type-of r))

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它,返回的结果为 -

(INTEGER 0 281474976710655) 
SINGLE-FLOAT 
SINGLE-FLOAT 
NULL 
SINGLE-FLOAT 
(INTEGER 0 281474976710655)

LISP - Macros

宏允许您扩展标准LISP的语法。

从技术上讲,宏是一个函数,它将s表达式作为参数并返回一个LISP表单,然后对其进行求值。

定义宏

在LISP中,使用名为defmacro.另一个宏定义命名宏defmacro. 定义宏的语法是 -

(defmacro macro-name (parameter-list))
"Optional documentation string."
body-form

宏定义包括宏的名称,参数列表,可选的文档字符串以及定义宏要执行的作业的Lisp表达式主体。

例子 (Example)

让我们编写一个名为setTo10的简单宏,它将取一个数字并将其值设置为10。

创建名为main.lisp的新源代码文件,并在其中键入以下代码。

(defmacro setTo10(num)
(setq num 10)(print num))
(setq x 25)
(print x)
(setTo10 x)

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它,返回的结果为 -

25
10

LISP - Variables

在LISP中,每个变量由symbol 。 变量的名称是符号的名称,它存储在符号的存储单元格中。

全局变量 (Global Variables)

全局变量在整个LISP系统中具有永久值,并且在指定新值之前保持有效。

通常使用defvar构造声明全局变量。

例如 (For example)

(defvar x 234)
(write x)

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它并返回结果

234

由于LISP中没有变量的类型声明,因此您可以使用setq构造直接为符号指定值。

例如

->(setq x 10)

上面的表达式将值10赋给变量x。 您可以使用符号本身作为表达式来引用变量。

symbol-value功能允许您提取存储在符号存储位置的值。

例如

创建名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq x 10)
(setq y 20)
(format t "x = ~2d y = ~2d ~%" x y)
(setq x 100)
(setq y 200)
(format t "x = ~2d y = ~2d" x y)

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它并返回结果。

x = 10 y = 20 
x = 100 y = 200

局部变量 (Local Variables)

局部变量在给定的过程中定义。 在函数定义中命名为参数的参数也是局部变量。 局部变量只能在相应的功能中访问。

与全局变量一样,也可以使用setq构造创建局部变量。

还有另外两个构造 - letprog用于创建局部变量。

let结构具有以下语法。

(let ((var1  val1) (var2  val2).. (varn  valn))<s-expressions>)

其中var1,var2,.. varn是变量名,val1,val2,.. valn是分配给各个变量的初始值。

执行let ,为每个变量分配相应的值,最后计算s-expression 。 返回最后一个表达式的值。

如果未包含变量的初始值,则将其指定为nil.

例子 (Example)

创建名为main.lisp的新源代码文件,并在其中键入以下代码。

(let ((x 'a) (y 'b)(z 'c))
(format t "x = ~a y = ~a z = ~a" x y z))

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它并返回结果。

x = A y = B z = C

prog构造还将局部变量列表作为其第一个参数,后面是prog,的主体prog,以及任意数量的s表达式。

prog函数按顺序执行s表达式列表并返回nil,除非遇到名为return.的函数调用return. 然后评估并返回return函数的参数。

例子 (Example)

创建名为main.lisp的新源代码文件,并在其中键入以下代码。

(prog ((x '(a b c))(y '(1 2 3))(z '(p q 10)))
(format t "x = ~a y = ~a z = ~a" x y z))

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它并返回结果。

x = (A B C) y = (1 2 3) z = (P Q 10)

LISP - Constants

在LISP中,常量是在程序执行期间永远不会更改其值的变量。 使用defconstant构造声明常量。

例子 (Example)

以下示例显示声明全局常量PI,稍后在名为area-circle的函数内使用此值来计算area-circle的面积。

defun构造用于定义函数,我们将在Functions章节中查看它。

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defconstant PI 3.141592)
(defun area-circle(rad)
   (terpri)
   (format t "Radius: ~5f" rad)
   (format t "~%Area: ~10f" (* PI rad rad)))
(area-circle 10)

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它并返回结果。

Radius:  10.0
Area:   314.1592

LISP - Operators

运算符是一个符号,告诉编译器执行特定的数学或逻辑操作。 LISP允许对数据进行大量操作,由各种函数,宏和其他结构支持。

允许的数据操作可归类为 -

  • 算术运算
  • 比较操作
  • 逻辑运算
  • 按位操作

算术运算

下表显示了LISP支持的所有算术运算符。 假设变量A保持10,变量B保持20然后 -

Show Examples

操作者 描述
+ 添加两个操作数 (+ AB)将给出30
- 从第一个减去第二个操作数 ( - AB)将给-10
* 将两个操作数相乘 (* AB)将给200
/Divides numerator by de-numerator (/ BA)将给2
mod,rem 模数运算符和整数除法后的余数 (mod BA)将给出0
incf 增量运算符按指定的第二个参数增加整数值 (incf A 3)将给出13
decf Decrements运算符按指定的第二个参数减小整数值 (decf A 4)将给出9

比较操作

下表显示了LISP支持的所有关系运算符,用于比较数字。 然而,与其他语言中的关系运算符不同,LISP比较运算符可能需要两个以上的操作数,它们仅适用于数字。

假设变量A保持10,变量B保持20,则 -

Show Examples

操作者 描述
= 检查操作数的值是否全等,如果是,则条件变为真。 (= AB)不是真的。
/= 检查操作数的值是否全部不同,如果值不相等则条件变为真。 (/ = AB)是真的。
> 检查操作数的值是否单调递减。 (> AB)不是真的。
< 检查操作数的值是否单调递增。
>= 检查任何左操作数的值是否大于或等于下一个右操作数的值,如果是,则条件变为真。 (> = AB)不是真的。
<= 检查任何左操作数的值是否小于或等于其右操作数的值,如果是,则条件变为真。 (<= AB)是真的。
max 它比较两个或多个参数并返回最大值。 (最大AB)返回20
min 它比较两个或多个参数并返回最小值。 (最小AB)返回10

布尔值的逻辑运算

通用LISP提供三个逻辑运算符: and, or,not对布尔值进行操作。 假设A值为nil, B值为5,则 -

Show Examples

操作者 描述
and 它需要任意数量的参数。 参数从左到右进行评估。 如果所有参数都计算为非nil,则返回最后一个参数的值。 否则返回nil。 (和AB)将返回NIL。
or 它需要任意数量的参数。 从左到右计算参数,直到一个求值为非nil,在这种情况下返回参数值,否则返回nil (或AB)将返回5。
not 它接受一个参数,如果参数的计算结果为nil. ,则返回t nil. (不是A)将返回T.

数字的按位运算

按位运算符处理位并执行逐位运算。 按位和,或和xor运算的真值表如下 -

Show Examples

p q p和q p或q p xor q
00000
01011
11110
10011
Assume if A = 60; and B = 13; now in binary format they will be as follows:
A = 0011 1100
B = 0000 1101
-----------------
A and B = 0000 1100
A or B = 0011 1101
A xor B = 0011 0001
not A  = 1100 0011

LISP支持的Bitwise运算符如下表所示。 假设变量A保持60,变量B保持13,则 -

操作者 描述
logand 这将返回其参数的逐位逻辑AND。 如果没有给出参数,则结果为-1,这是此操作的标识。 (logand ab))将给出12
logior 这将返回其参数的逐位逻辑INCLUSIVE OR。 如果没有给出参数,则结果为零,这是此操作的标识。 (logior ab)将给出61
logxor 这将返回其参数的逐位逻辑EXCLUSIVE OR。 如果没有给出参数,则结果为零,这是此操作的标识。 (logxor ab)将给49
lognor 这将返回其参数的逐位NOT。 如果没有给出参数,则结果为-1,这是此操作的标识。 (lognor ab)将给出-62,
logeqv 这将返回其参数的逐位逻辑EQUIVALENCE(也称为异或)。 如果没有给出参数,则结果为-1,这是此操作的标识。 (logeqv ab)将给予-50

LISP - Decision Making

决策结构要求程序员指定一个或多个要由程序评估或测试的条件,以及在条件被确定为真时要执行的一个或多个语句,以及可选的,如果条件要执行的其他语句被认定是假的。

以下是大多数编程语言中常见决策结构的一般形式 -

做决定

LISP提供以下类型的决策构造。 单击以下链接以检查其详细信息。

Sr.No. 构造和描述
1 cond

此构造用于检查多个测试操作子句。 它可以与其他编程语言中的嵌套if语句进行比较。

2 if

if结构具有各种形式。 在最简单的形式中,它后面是测试子句,测试操作和一些其他后续操作。 如果test子句的计算结果为true,则执行测试操作,否则将评估consequent子句。

3 when

在最简单的形式中,它后跟一个测试子句和一个测试动作。 如果test子句的计算结果为true,则执行测试操作,否则将评估consequent子句。

4 case

此构造实现了多个测试操作子句,如cond构造。 但是,它会评估关键表单,并根据对该键表单的评估允许多个操作子句。

LISP - Loops

当您需要执行一段代码次数时,可能会出现这种情况。 循环语句允许我们多次执行语句或语句组,以下是大多数编程语言中循环语句的一般形式。

循环

LISP提供以下类型的构造来处理循环需求。 单击以下链接以检查其详细信息。

Sr.No. 构造和描述
1 loop

loop结构是LISP提供的最简单的迭代形式。 在最简单的形式中,它允许您重复执行一些语句,直到找到return语句。

2 循环 for

构造循环允许您实现像其他语言中最常见的for循环之类的迭代。

3 do

do构造还用于使用LISP执行迭代。 它提供了一种结构化的迭代形式。

4 dotimes

dotimes构造允许循环一些固定数量的迭代。

5 dolist

dolist构造允许迭代列表的每个元素。

优雅地从一个街区退出

blockreturn-from允许您在出现任何错误时从任何嵌套块中正常退出。

block函数允许您创建一个由零个或多个语句组成的主体的命名块。 语法是 -

(block block-name(
...
...
))

return-from函数采用块名称和可选(默认为nil)返回值。

以下示例演示了这一点 -

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码 -

(defun demo-function (flag)
   (print 'entering-outer-block)
   (block outer-block
      (print 'entering-inner-block)
      (print (block inner-block
         (if flag
            (return-from outer-block 3)
            (return-from inner-block 5)
         )
         (print 'This-wil--not-be-printed))
      )
      (print 'left-inner-block)
      (print 'leaving-outer-block)
   t)
)
(demo-function t)
(terpri)
(demo-function nil)

单击“执行”按钮或键入Ctrl + E时,LISP立即执行它,返回的结果为 -

ENTERING-OUTER-BLOCK 
ENTERING-INNER-BLOCK 
ENTERING-OUTER-BLOCK 
ENTERING-INNER-BLOCK 
5 
LEFT-INNER-BLOCK 
LEAVING-OUTER-BLOCK

LISP - 函数

函数是一组一起执行任务的语句。

您可以将代码划分为单独的函数。 你如何在不同的函数之间划分你的代码取决于你,但逻辑上这个划分通常是这样,每个函数执行一个特定的任务。

在LISP中定义函数

名为defun的宏用于定义函数。 defun宏需要三个参数 -

  • 功能名称
  • 功能参数
  • 身体的功能

defun的语法是 -

(defun name (parameter-list) "Optional documentation string." body)

让我们用简单的例子来说明这个概念。

例子1 (Example 1)

让我们编写一个名为averagenum的函数,它将打印四个数字的平均值。 我们将这些数字作为参数发送。

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defun averagenum (n1 n2 n3 n4)
   (/ ( + n1 n2 n3 n4) 4)
)
(write(averagenum 10 20 30 40))

执行代码时,它返回以下结果 -

25

例子2 (Example 2)

让我们定义并调用一个函数,当圆的半径作为参数给出时,该函数将计算圆的面积。

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defun area-circle(rad)
   "Calculates area of a circle with given radius"
   (terpri)
   (format t "Radius: ~5f" rad)
   (format t "~%Area: ~10f" (* 3.141592 rad rad))
)
(area-circle 10)

执行代码时,它返回以下结果 -

Radius:  10.0
Area:   314.1592

请注意 -

  • 您可以提供一个空列表作为参数,这意味着该函数不带参数,列表为空,写为()。

  • LISP还允许可选,多个和关键字参数。

  • 文档字符串描述了该函数的用途。 它与函数名称相关联,可以使用documentation功能获取。

  • 函数体可以包含任意数量的Lisp表达式。

  • 将返回正文中最后一个表达式的值作为函数的值。

  • 您还可以使用return-from特殊运算符从函数返回值。

让我们简单地讨论上述概念。 点击以下链接查找详细信息 -

LISP - Predicates

谓词是针对某些特定条件测试其参数的函数,如果条件为假则返回nil,或者某些非零值是条件为真。

下表显示了一些最常用的谓词 -

Sr.No. 谓词和描述
1

atom

它接受一个参数,如果参数是原子则返回t,否则返回nil。

2

equal

它需要两个参数,如果它们在结构上相等则返回t否则返回nil

3

eq

它需要两个参数,如果它们是相同的相同对象,则返回t ,共享相同的内存位置,否则返回nil

4

eql

它接受两个参数,如果参数是eq ,则返回t ,或者如果它们是具有相同值的相同类型的数字,或者它们是表示相同字符的字符对象,否则返回n。

5

evenp

它需要一个数字参数,如果参数是偶数,则返回t否则返回nil

6

oddp

它需要一个数字参数,如果参数是奇数,则返回t否则返回nil

7

zerop

它需要一个数字参数,如果参数为零则返回t否则返回nil

8

null

如果参数的计算结果为nil,则它接受一个参数并返回t ,否则返回nil

9

listp

它接受一个参数并返回t如果参数求值为列表,否则返回nil

10

greaterp

它需要一个或多个参数,如果有一个参数或者参数从左到右依次变大,则返回t否则返回nil

11

lessp

它需要一个或多个参数,如果有一个参数或者参数从左到右连续变小,则返回t否则返回nil

12

numberp

它接受一个参数,如果参数是数字则返回t否则返回nil

13

symbolp

它接受一个参数,如果参数是符号则返回t ,否则返回nil

14

integerp

它接受一个参数,如果参数是整数则返回t ,否则返回nil

15

rationalp

它接受一个参数,如果参数是有理数,则返回t ,比率或数字,否则返回nil

16

floatp

如果参数是浮点数,则它接受一个参数并返回t ,否则返回nil

17

realp

如果参数是实数,它需要一个参数并返回t ,否则返回nil

18

complexp

如果参数是复数,它需要一个参数并返回t ,否则返回nil.

19

characterp

如果参数是一个字符,它需要一个参数并返回t ,否则返回nil

20

stringp

如果参数是字符串对象,则它接受一个参数并返回t ,否则返回nil

21

arrayp

它接受一个参数,如果参数是数组对象则返回t ,否则返回nil

22

packagep

如果参数是一个包,它需要一个参数并返回t ,否则返回nil.

例子1 (Example 1)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (atom 'abcd))
(terpri)
(write (equal 'a 'b))
(terpri)
(write (evenp 10))
(terpri)
(write (evenp 7 ))
(terpri)
(write (oddp 7 ))
(terpri)
(write (zerop 0.0000000001))
(terpri)
(write (eq 3 3.0 ))
(terpri)
(write (equal 3 3.0 ))
(terpri)
(write (null nil ))

执行代码时,它返回以下结果 -

T
NIL
T
NIL
T
NIL
NIL
NIL
T

例子2 (Example 2)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defun factorial (num)
   (cond ((zerop num) 1)
      (t ( * num (factorial (- num 1))))
   )
)
(setq n 6)
(format t "~% Factorial ~d is: ~d" n (factorial n))

执行代码时,它返回以下结果 -

Factorial 6 is: 720

LISP - Numbers

Common Lisp定义了几种数字。 number数据类型包括LISP支持的各种数字。

LISP支持的数字类型是 -

  • Integers
  • Ratios
  • Floating-point numbers
  • Complex numbers

下图显示了LISP中可用的数字层次结构和各种数字数据类型 -

数字类型

LISP中的各种数字类型

下表描述了LISP中可用的各种数字类型数据 -

Sr.No. 数据类型和描述
1

fixnum

此数据类型表示不太大且大多数在-215到215-1范围内的整数(取决于机器)

2

bignum

这些是非常大的数字,其大小受限于为LISP分配的内存量,它们不是fixnum数字。

3

ratio

表示分子/分母形式中两个数字的比率。 当函数的参数是整数时,/ function总是以比率产生结果。

4

float

它表示非整数。 有四种浮点数据类型,精度越来越高。

5

complex

它表示复数,用#c表示。 实部和虚部可以是有理数或浮点数。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (/ 1 2))
(terpri)
(write ( + (/ 1 2) (/ 3 4)))
(terpri)
(write ( + #c( 1 2) #c( 3 -4)))

执行代码时,它返回以下结果 -

1/2
5/4
#C(4 -2)

Number 函数

下表描述了一些常用的数字函数 -

Sr.No. 功能说明
1

+, -, *, /

各算术运算

2

sin, cos, tan, acos, asin, atan

各自的三角函数。

3

sinh, cosh, tanh, acosh, asinh, atanh

各自的双曲函数。

4

exp

指数函数。 计算e x

5

expt

指数函数,取得基数和幂。

6

sqrt

它计算数字的平方根。

7

log

对数函数。 给出一个参数,然后计算其自然对数,否则第二个参数用作基数。

8

conjugate

它计算一个数的复共轭。 如果是实数,则返回数字本身。

9

abs

它返回数字的绝对值(或幅度)。

10

gcd

它计算给定数字的最大公约数。

11

lcm

它计算给定数字的最小公倍数。

12

isqrt

它给出的最大整数小于或等于给定自然数的精确平方根。

13

floor, ceiling, truncate, round

所有这些函数都将两个参数作为数字并返回商; floor返回不大于比率的最大整数, ceiling选择大于ratio的较小整数, truncate选择相同符号的整数作为比率小于绝对值的最大绝对值,并且round选择一个最接近比率的整数。

14

ffloor, fceiling, ftruncate, fround

与上面相同,但返回商作为浮点数。

15

mod, rem

返回除法运算中的余数。

16

float

将实数转换为浮点数。

17

rational, rationalize

将实数转换为有理数。

18

numerator, denominator

返回有理数的各个部分。

19

realpart, imagpart

返回复数的实部和虚部。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (/ 45 78))
(terpri)
(write (floor 45 78))
(terpri)
(write (/ 3456 75))
(terpri)
(write (floor 3456 75))
(terpri)
(write (ceiling 3456 75))
(terpri)
(write (truncate 3456 75))
(terpri)
(write (round 3456 75))
(terpri)
(write (ffloor 3456 75))
(terpri)
(write (fceiling 3456 75))
(terpri)
(write (ftruncate 3456 75))
(terpri)
(write (fround 3456 75))
(terpri)
(write (mod 3456 75))
(terpri)
(setq c (complex 6 7))
(write c)
(terpri)
(write (complex 5 -9))
(terpri)
(write (realpart c))
(terpri)
(write (imagpart c))

执行代码时,它返回以下结果 -

15/26
0
1152/25
46
47
46
46
46.0
47.0
46.0
46.0
6
#C(6 7)
#C(5 -9)
6
7

LISP - Characters

在LISP中,字符表示为character.类型的数据对象character.

您可以在字符本身之前表示#\前面的字符对象。 例如,#\ a表示字符a。

空格和其他特殊字符可以在字符名称前面的#\前面表示。 例如,#\ SPACE表示空格字符。

以下示例演示了这一点 -

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write 'a)
(terpri)
(write #\a)
(terpri)
(write-char #\a)
(terpri)
(write-char 'a)

执行代码时,它返回以下结果 -

A
#\a
a
*** - WRITE-CHAR: argument A is not a character

特殊字符

Common LISP允许在代码中使用以下特殊字符。 它们被称为半标准字符。

  • #\Backspace
  • #\Tab
  • #\Linefeed
  • #\Page
  • #\Return
  • #\Rubout

字符比较函数 (Character Comparison Functions)

数字比较函数和运算符(如)不适用于字符。 Common LISP提供了另外两组函数来比较代码中的字符。

一组区分大小写,另一组不区分大小写。

下表提供了功能 -

区分大小写的函数 不区分大小写的函数 描述
char=char-equal 检查操作数的值是否全等,如果是,则条件变为真。
char/=char-not-equal 检查操作数的值是否全部不同,如果值不相等则条件变为真。
char<char-lessp 检查操作数的值是否单调递减。
char>char-greaterp 检查操作数的值是否单调递增。
char<=char-not-greaterp 检查任何左操作数的值是否大于或等于下一个右操作数的值,如果是,则条件变为真。
char>=char-not-lessp 检查任何左操作数的值是否小于或等于其右操作数的值,如果是,则条件变为真。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

; case-sensitive comparison
(write (char= #\a #\b))
(terpri)
(write (char= #\a #\a))
(terpri)
(write (char= #\a #\A))
(terpri)
;case-insensitive comparision
(write (char-equal #\a #\A))
(terpri)
(write (char-equal #\a #\b))
(terpri)
(write (char-lessp #\a #\b #\c))
(terpri)
(write (char-greaterp #\a #\b #\c))

执行代码时,它返回以下结果 -

NIL
T
NIL
T
NIL
T
NIL

LISP - Arrays

LISP允许您使用make-array函数定义单维或多维数组。 数组可以存储任何LISP对象作为其元素。

所有阵列都包含连续的内存位置。 最低地址对应于第一个元素,最高地址对应于最后一个元素。

秩

数组的维数称为其排名。

在LISP中,数组元素由一系列非负整数索引指定。 序列的长度必须等于数组的等级。 索引从零开始。

例如,要创建一个名为my-array的10个单元格的数组,我们可以写 -

(setf my-array (make-array '(10)))

aref函数允许访问单元格的内容。 它需要两个参数,即数组的名称和索引值。

例如,要访问第十个单元格的内容,我们写 -

(aref my-array 9)

例子1 (Example 1)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (setf my-array (make-array '(10))))
(terpri)
(setf (aref my-array 0) 25)
(setf (aref my-array 1) 23)
(setf (aref my-array 2) 45)
(setf (aref my-array 3) 10)
(setf (aref my-array 4) 20)
(setf (aref my-array 5) 17)
(setf (aref my-array 6) 25)
(setf (aref my-array 7) 19)
(setf (aref my-array 8) 67)
(setf (aref my-array 9) 30)
(write my-array)

执行代码时,它返回以下结果 -

#(NIL NIL NIL NIL NIL NIL NIL NIL NIL NIL)
#(25 23 45 10 20 17 25 19 67 30)

例子2 (Example 2)

让我们创建一个3乘3的数组。

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setf x (make-array '(3 3) 
   :initial-contents '((0 1 2 ) (3 4 5) (6 7 8)))
)
(write x)

执行代码时,它返回以下结果 -

#2A((0 1 2) (3 4 5) (6 7 8))

例子3 (Example 3)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq a (make-array '(4 3)))
(dotimes (i 4)
   (dotimes (j 3)
      (setf (aref a i j) (list i 'x j '= (* i j)))
   )
)
(dotimes (i 4)
   (dotimes (j 3)
      (print (aref a i j))
   )
)

执行代码时,它返回以下结果 -

(0 X 0 = 0) 
(0 X 1 = 0) 
(0 X 2 = 0) 
(1 X 0 = 0) 
(1 X 1 = 1) 
(1 X 2 = 2) 
(2 X 0 = 0) 
(2 X 1 = 2) 
(2 X 2 = 4) 
(3 X 0 = 0) 
(3 X 1 = 3) 
(3 X 2 = 6)

make-array函数的完整语法

make-array函数需要许多其他参数。 让我们看看这个函数的完整语法 -

make-array dimensions :element-type :initial-element :initial-contents :adjustable :fill-pointer  :displaced-to :displaced-index-offset

dimensions参数外,所有其他参数都是关键字。 下表提供了参数的简要说明。

Sr.No. 论点和描述
1

dimensions

它给出了数组的尺寸。 它是一维数组的数字,以及多维数组的列表。

2

:element-type

它是类型说明符,默认值为T,即任何类型

3

:initial-element

初始元素值。 它将生成一个数组,其中所有元素都初始化为特定值。

4

:initial-content

初始内容为对象。

5

:adjustable

它有助于创建可调整大小(或可调整)的向量,其底层内存可以调整大小。 参数是一个布尔值,指示数组是否可调,默认值为NIL。

6

:fill-pointer

它跟踪可调整大小的向量中实际存储的元素数量。

7

:displaced-to

它有助于创建与指定数组共享其内容的替换数组或共享数组。 两个数组都应该具有相同的元素类型。 :displa-to选项不能与:initial-element或:initial-contents选项一起使用。 此参数默认为nil。

8

:displaced-index-offset

它给出了创建的共享数组的索引偏移量。

例子4 (Example 4)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq myarray (make-array '(3 2 3) 
   :initial-contents 
   '(((a b c) (1 2 3)) 
      ((d e f) (4 5 6)) 
      ((g h i) (7 8 9)) 
   ))
) 
(setq array2 (make-array 4 :displaced-to myarray :displaced-index-offset 2)) 
(write myarray)
(terpri)
(write array2)

执行代码时,它返回以下结果 -

#3A(((A B C) (1 2 3)) ((D E F) (4 5 6)) ((G H I) (7 8 9)))
#(C 1 2 3)

如果移位阵列是二维的 -

(setq myarray (make-array '(3 2 3) 
   :initial-contents 
   '(((a b c) (1 2 3)) 
      ((d e f) (4 5 6)) 
      ((g h i) (7 8 9)) 
   ))
) 
(setq array2 (make-array '(3 2) :displaced-to myarray :displaced-index-offset 2)) 
(write myarray)
(terpri)
(write array2)

执行代码时,它返回以下结果 -

#3A(((A B C) (1 2 3)) ((D E F) (4 5 6)) ((G H I) (7 8 9)))
#2A((C 1) (2 3) (D E))

让我们将流离失所的指数偏移更改为5 -

(setq myarray (make-array '(3 2 3) 
   :initial-contents 
   '(((a b c) (1 2 3)) 
      ((d e f) (4 5 6)) 
      ((g h i) (7 8 9)) 
   ))
) 
(setq array2 (make-array '(3 2) :displaced-to myarray :displaced-index-offset 5)) 
(write myarray)
(terpri)
(write array2)

执行代码时,它返回以下结果 -

#3A(((A B C) (1 2 3)) ((D E F) (4 5 6)) ((G H I) (7 8 9)))
#2A((3 D) (E F) (4 5))

例子5 (Example 5)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

;a one dimensional array with 5 elements, 
;initail value 5
(write (make-array 5 :initial-element 5))
(terpri)
;two dimensional array, with initial element a
(write (make-array '(2 3) :initial-element 'a))
(terpri)
;an array of capacity 14, but fill pointer 5, is 5
(write(length (make-array 14 :fill-pointer 5)))
(terpri)
;however its length is 14
(write (array-dimensions (make-array 14 :fill-pointer 5)))
(terpri)
; a bit array with all initial elements set to 1
(write(make-array 10 :element-type 'bit :initial-element 1))
(terpri)
; a character array with all initial elements set to a
; is a string actually
(write(make-array 10 :element-type 'character :initial-element #\a)) 
(terpri)
; a two dimensional array with initial values a
(setq myarray (make-array '(2 2) :initial-element 'a :adjustable t))
(write myarray)
(terpri)
;readjusting the array
(adjust-array myarray '(1 3) :initial-element 'b) 
(write myarray)

执行代码时,它返回以下结果 -

#(5 5 5 5 5)
#2A((A A A) (A A A))
5
(14)
#*1111111111
"aaaaaaaaaa"
#2A((A A) (A A))
#2A((A A B))

LISP - Strings

Common Lisp中的字符串是向量,即一维字符数组。

字符串文字用双引号括起来。 字符集支持的任何字符都可以用双引号括起来创建一个字符串,但双引号字符(“)和转义字符(\)除外。但是,您可以通过使用反斜杠(\)转义它们来包含它们。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write-line "Hello World")
(write-line "Welcome to IOWIKI")
;escaping the double quote character
(write-line "Welcome to \"IOWIKI\"")

执行代码时,它返回以下结果 -

Hello World
Welcome to IOWIKI
Welcome to "IOWIKI"

字符串比较函数 (String Comparison Functions)

数字比较函数和运算符,如不适用于字符串。 Common LISP提供了另外两组函数来比较代码中的字符串。 一组区分大小写,另一组不区分大小写。

下表提供了功能 -

区分大小写的函数 不区分大小写的函数 描述
string=string-equal 检查操作数的值是否全等,如果是,则条件变为真。
string/=string-not-equal 检查操作数的值是否全部不同,如果值不相等则条件变为真。
string<string-lessp 检查操作数的值是否单调递减。
string>string-greaterp 检查操作数的值是否单调递增。
string<=string-not-greaterp 检查任何左操作数的值是否大于或等于下一个右操作数的值,如果是,则条件变为真。
string>=string-not-lessp 检查任何左操作数的值是否小于或等于其右操作数的值,如果是,则条件变为真。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

; case-sensitive comparison
(write (string= "this is test" "This is test"))
(terpri)
(write (string> "this is test" "This is test"))
(terpri)
(write (string< "this is test" "This is test"))
(terpri)
;case-insensitive comparision
(write (string-equal "this is test" "This is test"))
(terpri)
(write (string-greaterp "this is test" "This is test"))
(terpri)
(write (string-lessp "this is test" "This is test"))
(terpri)
;checking non-equal
(write (string/= "this is test" "this is Test"))
(terpri)
(write (string-not-equal "this is test" "This is test"))
(terpri)
(write (string/= "lisp" "lisping"))
(terpri)
(write (string/= "decent" "decency"))

执行代码时,它返回以下结果 -

NIL
0
NIL
T
NIL
NIL
8
NIL
4
5

Case Controlling Functions

下表描述了案例控制功能 -

Sr.No. 功能说明
1

string-upcase

将字符串转换为大写

2

string-downcase

将字符串转换为小写

3

string-capitalize

大写字符串中的每个单词

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write-line (string-upcase "a big hello from tutorials point"))
(write-line (string-capitalize "a big hello from tutorials point"))

执行代码时,它返回以下结果 -

A BIG HELLO FROM TUTORIALS POINT
A Big Hello From IOWIKI

修剪字符串

下表描述了字符串修剪功能 -

Sr.No. 功能说明
1

string-trim

它将一串字符作为第一个参数,将字符串作为第二个参数,并返回一个子字符串,其中第一个参数中的所有字符都将从参数字符串中删除。

2

String-left-trim

它将一串字符作为第一个参数,将一个字符串作为第二个参数,并返回一个子字符串,其中第一个参数中的所有字符都从参数字符串的开头删除。

3

String-right-trim

它将字符串字符作为第一个参数,将字符串作为第二个参数,并返回一个子字符串,其中第一个参数中的所有字符都从参数字符串的末尾删除。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write-line (string-trim " " "   a big hello from tutorials point   "))
(write-line (string-left-trim " " "   a big hello from tutorials point   "))
(write-line (string-right-trim " " "   a big hello from tutorials point   "))
(write-line (string-trim " a" "   a big hello from tutorials point   "))

执行代码时,它返回以下结果 -

a big hello from tutorials point
a big hello from tutorials point   
   a big hello from tutorials point
big hello from tutorials point

其他字符串函数 (Other String Functions)

LISP中的字符串是数组,因此也是序列。 我们将在即将到来的教程中介绍这些数据类型。 适用于数组和序列的所有函数也适用于字符串。 但是,我们将使用各种示例演示一些常用函数。

计算长度

length函数计算字符串的长度。

Extracting Sub-string

subseq函数返回一个子字符串(作为字符串也是一个序列),从特定索引开始并继续到特定的结束索引或字符串的结尾。

访问字符串中的字符

char函数允许访问字符串的各个字符。

Example

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (length "Hello World"))
(terpri)
(write-line (subseq "Hello World" 6))
(write (char "Hello World" 6))

执行代码时,它返回以下结果 -

11
World
#\W

字符串的排序和合并

sort函数允许对字符串进行排序。 它接受一个序列(向量或字符串)和一个双参数谓词,并返回序列的排序版本。

merge函数接受两个序列和一个谓词,并根据谓词返回通过合并两个序列产生的序列。

Example

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

;sorting the strings
(write (sort (vector "Amal" "Akbar" "Anthony") #'string<))
(terpri)
;merging the strings
(write (merge 'vector (vector "Rishi" "Zara" "Priyanka") 
   (vector "Anju" "Anuj" "Avni") #'string<))

执行代码时,它返回以下结果 -

#("Akbar" "Amal" "Anthony")
#("Anju" "Anuj" "Avni" "Rishi" "Zara" "Priyanka")

反转字符串

reverse函数反转字符串。

例如,创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write-line (reverse "Are we not drawn onward, we few, drawn onward to new era"))

执行代码时,它返回以下结果 -

are wen ot drawno nward ,wef ew ,drawno nward ton ew erA

连接字符串

连接函数连接两个字符串。 这是通用序列函数,您必须提供结果类型作为第一个参数。

例如,创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write-line (concatenate 'string "Are we not drawn onward, " "we few, drawn onward to new era"))

执行代码时,它返回以下结果 -

Are we not drawn onward, we few, drawn onward to new era

LISP - Sequences

Sequence是LISP中的抽象数据类型。 向量和列表是此数据类型的两个具体子类型。 序列数据类型上定义的所有功能实际上应用于所有向量和列表类型。

在本节中,我们将讨论序列上最常用的函数。

在开始操作序列的各种方法(即向量和列表)之前,让我们看一下所有可用函数的列表。

创建序列

函数make-sequence允许您创建任何类型的序列。 这个函数的语法是 -

make-sequence sqtype sqsize &key :initial-element

它创建一个sqtype类型和sqsize.长度的sqsize.

您可以选择使用:initial-element参数指定一些值,然后将每个元素初始化为此值。

例如,创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (make-sequence '(vector float) 
   10 
   :initial-element 1.0))

执行代码时,它返回以下结果 -

#(1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0 1.0)

序列上的通用函数

Sr.No. 功能说明
1

elt

它允许通过整数索引访问单个元素。

2

length

它返回序列的长度。

3

subseq

它通过从特定索引开始提取子序列并继续到特定的结束索引或序列的结尾来返回子序列。

4

copy-seq

它返回一个包含与其参数相同的元素的序列。

5

fill

它用于将序列的多个元素设置为单个值。

6

replace

它需要两个序列,并且通过从第二个参数序列中将连续元素复制到其中来破坏性地修改第一个参数序列。

7

count

它接受一个项目和一个序列,并返回该项目在序列中出现的次数。

8

reverse

它返回一个序列,该序列包含与参数相同的元素,但顺序相反。

9

nreverse

它返回包含与序列相同的元素的相同序列,但顺序相反。

10

concatenate

它创建一个包含任意数量序列串联的新序列。

11

position

它接受一个项目和一个序列,并返回序列中项目的索引或nil。

12

find

它需要一个项目和一个序列。 它找到序列中的项并返回它,如果没有找到则返回nil。

13

sort

它接受一个序列和一个双参数谓词,并返回序列的排序版本。

14

merge

根据谓词,它需要两个序列和一个谓词,并返回通过合并两个序列产生的序列。

15

map

它采用n参数函数和n个序列,并返回一个新序列,其中包含将函数应用于序列的后续元素的结果。

16

some

它将谓词作为参数并迭代参数序列,并返回谓词返回的第一个非NIL值,如果谓词永远不满足则返回false。

17

every

它将谓词作为参数并迭代参数序列,一旦谓词失败,它就会终止,返回false。 如果始终满足谓词,则返回true。

18

notany

它将谓词作为参数并迭代参数序列,并在谓词满足后立即返回false,如果从不返回则返回true。

19

notevery

它将谓词作为参数并迭代参数序列,并在谓词失败时返回true,如果谓词始终满足则返回false。

20

reduce

它映射单个序列,首先将两个参数函数应用于序列的前两个元素,然后应用函数返回的值和序列的后续元素。

21

search

它搜索序列以定位满足某些测试的一个或多个元素。

22

remove

它接受一个项目和一个序列,并返回带有删除项目实例的序列。

23

delete

这也需要一个项目和一个序列,并返回一个与参数序列相同的序列,该序列具有除项目之外的相同元素。

24

substitute

它接受一个新项目,一个现有项目和一个序列,并返回一个序列,其中现有项目的实例被新项目替换。

25

nsubstitute

它采用新项目,现有项目和序列,并返回相同的序列,现有项目的实例替换为新项目。

26

mismatch

它需要两个序列并返回第一对不匹配元素的索引。

标准序列函数关键字参数

争论 含义 默认值
:test 它是一个双参数函数,用于将项目(或通过:key function提取的值)与元素进行比较。 EQL
:key 单参数函数从实际序列元素中提取键值。 NIL表示按原样使用元素。 NIL
:start 开始索引(包括)子序列。 0
:end 子序列的结束索引(不包括)。 NIL表示序列结束。 NIL
:from-end 如果为true,则序列将以相反的顺序遍历,从头到尾。 NIL
:count 数字表示要删除或替换的元素数或NIL表示全部(仅限REMOVE和SUBSTITUTE)。 NIL

我们刚刚讨论了在这些函数中用作序列的各种函数和关键字。 在下一节中,我们将看到如何使用示例来使用这些函数。

寻找长度和元素

length函数返回序列的长度, elt函数允许您使用整数索引访问各个元素。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq x (vector 'a 'b 'c 'd 'e))
(write (length x))
(terpri)
(write (elt x 3))

执行代码时,它返回以下结果 -

5
D

修改序列

一些序列函数允许迭代序列并执行一些操作,如搜索,删除,计数或过滤特定元素,而无需编写显式循环。

以下示例演示了这一点 -

例子1 (Example 1)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (count 7 '(1 5 6 7 8 9 2 7 3 4 5)))
(terpri)
(write (remove 5 '(1 5 6 7 8 9 2 7 3 4 5)))
(terpri)
(write (delete 5 '(1 5 6 7 8 9 2 7 3 4 5)))
(terpri)
(write (substitute 10 7 '(1 5 6 7 8 9 2 7 3 4 5)))
(terpri)
(write (find 7 '(1 5 6 7 8 9 2 7 3 4 5)))
(terpri)
(write (position 5 '(1 5 6 7 8 9 2 7 3 4 5)))

执行代码时,它返回以下结果 -

2
(1 6 7 8 9 2 7 3 4)
(1 6 7 8 9 2 7 3 4)
(1 5 6 10 8 9 2 10 3 4 5)
7
1

例子2 (Example 2)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (delete-if #'oddp '(1 5 6 7 8 9 2 7 3 4 5)))
(terpri)
(write (delete-if #'evenp '(1 5 6 7 8 9 2 7 3 4 5)))
(terpri)
(write (remove-if #'evenp '(1 5 6 7 8 9 2 7 3 4 5) :count 1 :from-end t))
(terpri)
(setq x (vector 'a 'b 'c 'd 'e 'f 'g))
(fill x 'p :start 1 :end 4)
(write x)

执行代码时,它返回以下结果 -

(6 8 2 4)
(1 5 7 9 7 3 5)
(1 5 6 7 8 9 2 7 3 5)
#(A P P P E F G)

排序和合并序列

排序函数采用序列和双参数谓词,并返回序列的排序版本。

例子1 (Example 1)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (sort '(2 4 7 3 9 1 5 4 6 3 8) #'<))
(terpri)
(write (sort '(2 4 7 3 9 1 5 4 6 3 8) #'>))
(terpri)

执行代码时,它返回以下结果 -

(1 2 3 3 4 4 5 6 7 8 9)
(9 8 7 6 5 4 4 3 3 2 1)

例子2 (Example 2)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (merge 'vector #(1 3 5) #(2 4 6) #'<))
(terpri)
(write (merge 'list #(1 3 5) #(2 4 6) #'<))
(terpri)

执行代码时,它返回以下结果 -

#(1 2 3 4 5 6)
(1 2 3 4 5 6)

序列谓词

函数every,some,notany和notevery称为序列谓词​​。

这些函数迭代序列并测试布尔谓词。

所有这些函数都将谓词作为第一个参数,其余参数是序列。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (every #'evenp #(2 4 6 8 10)))
(terpri)
(write (some #'evenp #(2 4 6 8 10 13 14)))
(terpri)
(write (every #'evenp #(2 4 6 8 10 13 14)))
(terpri)
(write (notany #'evenp #(2 4 6 8 10)))
(terpri)
(write (notevery #'evenp #(2 4 6 8 10 13 14)))
(terpri)

执行代码时,它返回以下结果 -

T
T
NIL
NIL
T

映射序列

我们已经讨论了映射函数。 类似地, map函数允许您将函数应用于一个或多个序列的后续元素。

map函数采用n参数函数和n个序列,并在将函数应用于序列的后续元素后返回新序列。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (map 'vector #'* #(2 3 4 5) #(3 5 4 8)))

执行代码时,它返回以下结果 -

#(6 15 16 40)

LISP - Lists

列表是传统LISP中最重要和最主要的复合数据结构。 当天的Common LISP提供其他数据结构,如矢量,哈希表,类或结构。

列表是单链表。 在LISP中,列表被构造为链接在一起的名为cons的简单记录结构的链。

记录结构

cons是包含两个组件的记录结构,称为carcdr.

缺点单元格或缺点是对象是使用函数cons.创建的值对cons.

cons函数接受两个参数并返回包含这两个值的新cons单元格。 这些值可以是对任何类型对象的引用。

如果第二个值不是nil或其他cons单元格,则将值打印为括号括起来的点对。

cons单元格中的两个值称为carcdr. car函数用于访问第一个值, cdr函数用于访问第二个值。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (cons 1 2))
(terpri)
(write (cons 'a 'b))
(terpri)
(write (cons 1 nil))
(terpri)
(write (cons 1 (cons 2 nil)))
(terpri)
(write (cons 1 (cons 2 (cons 3 nil))))
(terpri)
(write (cons 'a (cons 'b (cons 'c nil))))
(terpri)
(write ( car (cons 'a (cons 'b (cons 'c nil)))))
(terpri)
(write ( cdr (cons 'a (cons 'b (cons 'c nil)))))

执行代码时,它返回以下结果 -

(1 . 2)
(A . B)
(1)
(1 2)
(1 2 3)
(A B C)
A
(B C)

上面的示例显示了如何使用cons结构来创建单个链表,例如,列表(ABC)由三个由其cdrs链接在一起的cons单元cdrs

从图中可以看出,它可以表示为 -

LISP中的列表

虽然cons单元可用于创建列表,但是,使用嵌套cons函数调用构建列表不是最佳解决方案。 list函数更用于在LISP中创建列表。

list函数可以使用任意数量的参数,因为它是一个函数,它会计算其参数。

firstrest函数给出第一个元素和列表的其余部分。 以下示例演示了这些概念。

例子1 (Example 1)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (list 1 2))
(terpri)
(write (list 'a 'b))
(terpri)
(write (list 1 nil))
(terpri)
(write (list 1 2 3))
(terpri)
(write (list 'a 'b 'c))
(terpri)
(write (list 3 4 'a (car '(b . c)) (* 4 -2)))
(terpri)
(write (list (list 'a 'b) (list 'c 'd 'e)))

执行代码时,它返回以下结果 -

(1 2)
(A B)
(1 NIL)
(1 2 3)
(A B C)
(3 4 A B -8)
((A B) (C D E))

例子2 (Example 2)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defun my-library (title author rating availability)
   (list :title title :author author :rating rating :availabilty availability)
)
(write (getf (my-library "Hunger Game" "Collins" 9 t) :title))

执行代码时,它返回以下结果 -

"Hunger Game"

列表操作函数 (List Manipulating Functions)

下表提供了一些常用的列表操作函数。

Sr.No. 功能说明
1

car

它将列表作为参数,并返回其第一个元素。

2

cdr

它将列表作为参数,并返回没有第一个元素的列表

3

cons

它需要两个参数,一个元素和一个列表,并返回一个列表,其中元素插入到第一位。

4

list

它接受任意数量的参数并返回一个列表,其中参数作为列表的成员元素。

5

append

它将两个或多个列表合并为一个。

6

last

它需要一个列表并返回一个包含最后一个元素的列表。

7

member

它需要两个参数,其中第二个必须是一个列表,如果第一个参数是第二个参数的成员,然后它返回从第一个参数开始的列表的其余部分。

8

reverse

它需要一个列表并以相反的顺序返回包含顶部元素的列表。

请注意,所有序列功能都适用于列表。

例子3 (Example 3)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (car '(a b c d e f)))
(terpri)
(write (cdr '(a b c d e f)))
(terpri)
(write (cons 'a '(b c)))
(terpri)
(write (list 'a '(b c) '(e f)))
(terpri)
(write (append '(b c) '(e f) '(p q) '() '(g)))
(terpri)
(write (last '(a b c d (e f))))
(terpri)
(write (reverse '(a b c d (e f))))

执行代码时,它返回以下结果 -

A
(B C D E F)
(A B C)
(A (B C) (E F))
(B C E F P Q G)
((E F))
((E F) D C B A)

汽车和cdr功能的连接

carcdr函数及其组合允许提取列表中的任何特定元素/成员。

但是,汽车和cdr功能的顺序可以通过连接字母c和r中的字母a和汽车的字母a来缩短。

例如,我们可以编写cadadr来缩短函数调用的顺序 - car cdr car cdr。

因此,(cadadr'(a(cd)(efg)))将返回d

例子4 (Example 4)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (cadadr '(a (c d) (e f g))))
(terpri)
(write (caar (list (list 'a 'b) 'c)))   
(terpri)
(write (cadr (list (list 1 2) (list 3 4))))
(terpri)

执行代码时,它返回以下结果 -

D
A
(3 4)

LISP - Symbols

在LISP中,符号是表示数据对象的名称,有趣的是它也是数据对象。

使符号与众不同的是它们有一个称为property listplist.的组件plist.

物业清单

LISP允许您为符号指定属性。 例如,让我们有一个'人'对象。 我们希望这个“人”对象具有名称,性别,身高,体重,地址,职业等属性。属性就像属性名称。

属性列表实现为具有偶数(可能为零)元素的列表。 列表中的每对元素构成一个条目; 第一项是indicator,第二项是value.

创建符号时,其属性列表最初为空。 通过在setf形式中使用get来创建属性。

例如,以下语句允许我们将属性title,author和publisher以及相应的值分配给名为(symbol)'book'的对象。

例子1 (Example 1)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (setf (get 'books'title) '(Gone with the Wind)))
(terpri)
(write (setf (get 'books 'author) '(Margaret Michel)))
(terpri)
(write (setf (get 'books 'publisher) '(Warner Books)))

执行代码时,它返回以下结果 -

(GONE WITH THE WIND)
(MARGARET MICHEL)
(WARNER BOOKS)

各种属性列表函数允许您分配属性以及检索,替换或删除符号的属性。

get函数返回给定指标的符号属性列表。 它具有以下语法 -

get symbol indicator &optional default

get函数查找指定指标的给定符号的属性列表,如果找到则返回相应的值; 否则返回default(如果未指定默认值,则返回nil)。

例子2 (Example 2)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setf (get 'books 'title) '(Gone with the Wind))
(setf (get 'books 'author) '(Margaret Micheal))
(setf (get 'books 'publisher) '(Warner Books))
(write (get 'books 'title))
(terpri)
(write (get 'books 'author))
(terpri)
(write (get 'books 'publisher))

执行代码时,它返回以下结果 -

(GONE WITH THE WIND)
(MARGARET MICHEAL)
(WARNER BOOKS)

symbol-plist函数允许您查看符号的所有属性。

例子3 (Example 3)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setf (get 'annie 'age) 43)
(setf (get 'annie 'job) 'accountant)
(setf (get 'annie 'sex) 'female)
(setf (get 'annie 'children) 3)
(terpri)
(write (symbol-plist 'annie))

执行代码时,它返回以下结果 -

(CHILDREN 3 SEX FEMALE JOB ACCOUNTANT AGE 43)

remprop函数从符号中删除指定的属性。

例子4 (Example 4)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setf (get 'annie 'age) 43)
(setf (get 'annie 'job) 'accountant)
(setf (get 'annie 'sex) 'female)
(setf (get 'annie 'children) 3)
(terpri)
(write (symbol-plist 'annie))
(remprop 'annie 'age)
(terpri)
(write (symbol-plist 'annie))

执行代码时,它返回以下结果 -

(CHILDREN 3 SEX FEMALE JOB ACCOUNTANT AGE 43)
(CHILDREN 3 SEX FEMALE JOB ACCOUNTANT)

LISP - Vectors

向量是一维数组,因此是数组的子类型。 矢量和列表统称为序列。 因此,到目前为止我们讨论过的所有序列泛型函数和数组函数都适用于向量。

创建矢量

矢量函数允许您使用特定值制作固定大小的矢量。 它接受任意数量的参数并返回包含这些参数的向量。

例子1 (Example 1)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setf v1 (vector 1 2 3 4 5))
(setf v2 #(a b c d e))
(setf v3 (vector 'p 'q 'r 's 't))
(write v1)
(terpri)
(write v2)
(terpri)
(write v3)

执行代码时,它返回以下结果 -

#(1 2 3 4 5)
#(A B C D E)
#(P Q R S T)

请注意,LISP使用#(...)语法作为向量的文字表示法。 您可以使用此#(...)语法在代码中创建和包含文字向量。

但是,这些是文字向量,因此在LISP中未定义修改它们。 因此,对于编程,应始终使用vector函数或更通用的函数make-array来创建计划修改的向量。

make-array函数是创建矢量的更通用的方法。 您可以使用aref函数访问向量元素。

例子2 (Example 2)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq a (make-array 5 :initial-element 0))
(setq b (make-array 5 :initial-element 2))
(dotimes (i 5)
   (setf (aref a i) i))
(write a)
(terpri)
(write b)
(terpri)

执行代码时,它返回以下结果 -

#(0 1 2 3 4)
#(2 2 2 2 2)

填充指针

make-array函数允许您创建可调整大小的向量。

函数的fill-pointer参数跟踪实际存储在向量中的元素数。 它是向向量添加元素时要填充的下一个位置的索引。

vector-push功能允许您将元素添加到可调整大小的矢量的末尾。 它将填充指针增加1。

vector-pop函数返回最近推送的项目,并将填充指针递减1。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq a (make-array 5 :fill-pointer 0))
(write a)
(vector-push 'a a)
(vector-push 'b a)
(vector-push 'c a)
(terpri)
(write a)
(terpri)
(vector-push 'd a)
(vector-push 'e a)
;this will not be entered as the vector limit is 5
(vector-push 'f a)
(write a)
(terpri)
(vector-pop a)
(vector-pop a)
(vector-pop a)
(write a)

执行代码时,它返回以下结果 -

#()
#(A B C)
#(A B C D E)
#(A B)

作为序列的载体,所有序列功能都适用于载体。 有关矢量函数,请参阅序列章节。

LISP - Set

Common Lisp不提供set数据类型。 但是,它提供了许多允许在列表上执行集合操作的功能。

您可以根据各种条件添加,删除和搜索列表中的项目。 您还可以执行各种设置操作,例如:union,intersection和set difference。

在LISP中实现集合

像列表一样的集合通常根据cons单元格来实现。 但是,由于这个原因,集合获得的集合越大,效率越低。

adjoin函数允许您构建一个集合。 它接受一个项目和一个表示集合的列表,并返回一个列表,该列表表示包含该项目的集合以及原始集合中的所有项目。

adjoin函数首先查找给定列表中的项目,如果找到,则返回原始列表; 否则它会创建一个新的cons单元格,其car作为项目, cdr指向原始列表并返回此新列表。

adjoin函数还包括:key:test关键字参数。 这些参数用于检查项目是否存在于原始列表中。

由于adjoin函数不修改原始列表,要在列表本身中进行更改,您必须将pushnew返回的值分配给原始列表,或者,您可以使用宏pushnew将项添加到集合中。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

; creating myset as an empty list
(defparameter *myset* ())
(adjoin 1 *myset*)
(adjoin 2 *myset*)
; adjoin did not change the original set
;so it remains same
(write *myset*)
(terpri)
(setf *myset* (adjoin 1 *myset*))
(setf *myset* (adjoin 2 *myset*))
;now the original set is changed
(write *myset*)
(terpri)
;adding an existing value
(pushnew 2 *myset*)
;no duplicate allowed
(write *myset*)
(terpri)
;pushing a new value
(pushnew 3 *myset*)
(write *myset*)
(terpri)

执行代码时,它返回以下结果 -

NIL
(2 1)
(2 1)
(3 2 1)

检查会员资格

成员函数组允许您检查元素是否是集合的成员。

以下是这些函数的语法 -

member item list &key :test :test-not :key 
member-if predicate list &key :key 
member-if-not predicate list &key :key

这些函数在给定列表中搜索满足测试的给定项。 如果找不到这样的项,则函数返回nil. 否则,返回以元素作为第一个元素的列表尾部。

搜索仅在顶级进行。

这些函数可以用作谓词。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(write (member 'zara '(ayan abdul zara riyan nuha)))
(terpri)
(write (member-if #'evenp '(3 7 2 5/3 'a)))
(terpri)
(write (member-if-not #'numberp '(3 7 2 5/3 'a 'b 'c)))

执行代码时,它返回以下结果 -

(ZARA RIYAN NUHA)
(2 5/3 'A)
('A 'B 'C)

设置联盟

联合函数组允许您在两个列表上执行集合,这两个列表作为参数提供,作为测试的基础。

以下是这些函数的语法 -

union list1 list2 &key :test :test-not :key 
nunion list1 list2 &key :test :test-not :key

union函数接受两个列表并返回一个新列表,其中包含任一列表中的所有元素。 如果存在重复,则只有该成员的一个副本保留在返回的列表中。

nunion函数执行相同的操作,但可能会破坏参数列表。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq set1 (union '(a b c) '(c d e)))
(setq set2 (union '(#(a b) #(5 6 7) #(f h)) 
   '(#(5 6 7) #(a b) #(g h)) :test-not #'mismatch)
)
(setq set3 (union '(#(a b) #(5 6 7) #(f h)) 
   '(#(5 6 7) #(a b) #(g h)))
)
(write set1)
(terpri)
(write set2)
(terpri)
(write set3)

执行代码时,它返回以下结果 -

(A B C D E)
(#(F H) #(5 6 7) #(A B) #(G H))
(#(A B) #(5 6 7) #(F H) #(5 6 7) #(A B) #(G H))

请注意

如果没有:test-not #'mismatch union :test-not #'mismatch三个向量列表的参数,union函数将无法正常工作。 这是因为,列表由cons单元组成,虽然值看起来与我们看起来相同,但是单元格的cdr部分不匹配,因此它们与LISP解释器/编译器不完全相同。 这就是原因; 不建议使用列表来实现大集合。 它适用于小型套装。

设置交叉点

交集的函数组允许您在两个列表上执行交集,这些列表作为参数提供,作为测试的基础。

以下是这些函数的语法 -

intersection list1 list2 &key :test :test-not :key 
nintersection list1 list2 &key :test :test-not :key

这些函数采用两个列表并返回一个新列表,其中包含两个参数列表中的所有元素。 如果任一列表具有重复条目,则冗余条目可能会也可能不会出现在结果中。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq set1 (intersection '(a b c) '(c d e)))
(setq set2 (intersection '(#(a b) #(5 6 7) #(f h)) 
   '(#(5 6 7) #(a b) #(g h)) :test-not #'mismatch)
)
(setq set3 (intersection '(#(a b) #(5 6 7) #(f h)) 
   '(#(5 6 7) #(a b) #(g h)))
)
(write set1)
(terpri)
(write set2)
(terpri)
(write set3)

执行代码时,它返回以下结果 -

(C)
(#(A B) #(5 6 7))
NIL

交集函数是交集的破坏性版本,即它可能破坏原始列表。

设定差异

set-difference函数组允许您在基于测试的两个列表上执行设置差异,这两个列表作为这些函数的参数提供。

以下是这些函数的语法 -

set-difference list1 list2 &key :test :test-not :key 
nset-difference list1 list2 &key :test :test-not :key

set-difference函数返回第一个列表中未出现在第二个列表中的元素列表。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq set1 (set-difference '(a b c) '(c d e)))
(setq set2 (set-difference '(#(a b) #(5 6 7) #(f h)) 
   '(#(5 6 7) #(a b) #(g h)) :test-not #'mismatch)
)
(setq set3 (set-difference '(#(a b) #(5 6 7) #(f h)) 
   '(#(5 6 7) #(a b) #(g h)))
)
(write set1)
(terpri)
(write set2)
(terpri)
(write set3)

执行代码时,它返回以下结果 -

(A B)
(#(F H))
(#(A B) #(5 6 7) #(F H))

LISP - Tree

您可以从cons单元构建树数据结构,作为列表列表。

要实现树结构,您必须按特定顺序设计将遍历cons单元的功能,例如,二叉树的预订,有序和后订单。

树作为列表列表

让我们考虑由cons单元组成的树结构,它构成以下列表列表 -

((1 2)(3 4)(5 6))。

从图中可以看出,它可以表示为 -

树结构

LISP中的树函数

虽然大多数情况下您需要根据您的具体需要编写自己的树功能,但LISP提供了一些您可以使用的树功能。

除了所有列表函数之外,以下函数特别适用于树结构 -

Sr.No. 功能说明
1

copy-tree x和可选的vecp

它返回cons单元格x的副本。 它以递归方式复制汽车和cdr方向。 如果x不是cons单元格,则该函数只返回x不变。 如果可选的vecp参数为true,则此函数复制向量(递归)以及cons单元格。

2

tree-equal xy&key:test:test-not:key

它比较两个cons细胞树。 如果x和y都是cons单元格,则递归地比较它们的汽车和cdrs。 如果x和y都不是cons单元格,则通过eql或根据指定的测试对它们进行比较。 :key函数(如果已指定)将应用于两个树的元素。

3

subst new old tree&key:test:test-not:key

它用tree new项替换给定旧项的出现,这是一个缺点单元格的树。

4

nsubst new old tree&key:test:test-not:key

它与subst相同,但它会破坏原始树。

5

sublis alist tree&key:test:test-not:key

它像subst一样工作,除了它需要一个新旧对的关联列表。 树的每个元素(在应用:key函数之后,如果有的话)与alist的汽车进行比较; 如果匹配,则由相应的cdr替换。

6

nsublis alist tree&key:test:test-not:key

它与sublis相同,但是具有破坏性版本。

例子1 (Example 1)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq lst (list '(1 2) '(3 4) '(5 6)))
(setq mylst (copy-list lst))
(setq tr (copy-tree lst))
(write lst)
(terpri)
(write mylst)
(terpri)
(write tr)

执行代码时,它返回以下结果 -

((1 2) (3 4) (5 6))
((1 2) (3 4) (5 6))
((1 2) (3 4) (5 6))

例子2 (Example 2)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq tr '((1 2 (3 4 5) ((7 8) (7 8 9)))))
(write tr)
(setq trs (subst 7 1 tr))
(terpri)
(write trs)

执行代码时,它返回以下结果 -

((1 2 (3 4 5) ((7 8) (7 8 9))))
((7 2 (3 4 5) ((7 8) (7 8 9))))

建立自己的树

让我们尝试使用LISP中提供的列表函数构建我们自己的树。

首先让我们创建一个包含一些数据的新节点

(defun make-tree (item)
   "it creates a new node with item."
   (cons (cons item nil) nil)
)

接下来让我们将一个子节点添加到树中 - 它将需要两个树节点并将第二个树添加为第一个树的子节点。

(defun add-child (tree child)
   (setf (car tree) (append (car tree) child))
   tree)

此函数将返回给定树的第一个子节点 - 它将采用树节点并返回该节点的第一个子节点,如果此节点没有任何子节点,则返回nil。

(defun first-child (tree)
   (if (null tree)
      nil
      (cdr (car tree))
   )
)

此函数将返回给定节点的下一个兄弟节点 - 它将树节点作为参数,并返回对下一个兄弟节点的引用,如果节点没有,则返回nil。

(defun next-sibling (tree)
   (cdr tree)
)

最后,我们需要一个函数来返回节点中的信息 -

(defun data (tree)
   (car (car tree))
)

例子 (Example)

此示例使用上述功能 -

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defun make-tree (item)
   "it creates a new node with item."
   (cons (cons item nil) nil)
)
(defun first-child (tree)
   (if (null tree)
      nil
      (cdr (car tree))
   )
)
(defun next-sibling (tree)
   (cdr tree)
)
(defun data (tree)
   (car (car tree))
)
(defun add-child (tree child)
   (setf (car tree) (append (car tree) child))
   tree
)
(setq tr '((1 2 (3 4 5) ((7 8) (7 8 9)))))
(setq mytree (make-tree 10))
(write (data mytree))
(terpri)
(write (first-child tr))
(terpri)
(setq newtree (add-child tr mytree))
(terpri)
(write newtree)

执行代码时,它返回以下结果 -

10
(2 (3 4 5) ((7 8) (7 8 9)))
((1 2 (3 4 5) ((7 8) (7 8 9)) (10)))

LISP - Hash Table

散列表数据结构表示基于密钥的散列码组织的key-and-value对的集合。 它使用密钥来访问集合中的元素。

当您需要使用密钥访问元素时,将使用哈希表,并且可以标识有用的键值。 哈希表中的每个项都有一个键/值对。 该键用于访问集合中的项目。

在LISP中创建哈希表

在Common LISP中,哈希表是一个通用的集合。 您可以使用任意对象作为键或索引。

将值存储在哈希表中时,可以创建键值对,并将其存储在该键下。 稍后,您可以使用相同的密钥从哈希表中检索值。 尽管您可以在键中存储新值,但每个键都映射到单个值。

LISP中的哈希表可以根据密钥的比较方式分为三种类型 - eq,eql或者相等。 如果在LISP对象上散列哈希表,则将密钥与eq或eql进行比较。 如果哈希表在树结构上散列,那么它将使用相等进行比较。

make-hash-table函数用于创建哈希表。 该函数的语法是 -

make-hash-table &key :test :size :rehash-size :rehash-threshold

Where −

  • key参数提供了关键。

  • :test参数确定如何比较键 - 它应该具有三个值中的一个#'eq,#'eql或#'相等,或者三个符号之一eq,eql或者相等。 如果未指定,则假定为eql。

  • :size参数设置哈希表的初始大小。 这应该是大于零的整数。

  • :rehash-size参数指定在哈希表变满时增加哈希表的大小。 这可以是大于零的整数,即要添加的条目数,也可以是大于1的浮点数,即新大小与旧大小的比率。 此参数的默认值取决于实现。

  • :rehash-threshold参数指定哈希表在必须增长之前可以获得多长。 这可以是大于零且小于:rehash-size的整数(在这种情况下,它将在表生成时进行缩放),或者它可以是0到1之间的浮点数。此默认值参数依赖于实现。

您也可以在没有参数的情况下调用make-hash-table函数。

从哈希表中检索项目和将项目添加到哈希表中

gethash函数通过搜索其密钥从哈希表中检索项目。 如果找不到密钥,则返回nil。

它具有以下语法 -

gethash key hash-table &optional default

Where −

  • key:是关联的密钥

  • hash-table:是要搜索的哈希表

  • default:如果未找到条目,则返回值,如果未指定,则为nil。

gethash函数实际上返回两个值,第二个是谓词值,如果找到条目则为true,如果没有找到条目则为false。

要将项添加到哈希表,可以使用setf函数和gethash函数。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq empList (make-hash-table)) 
(setf (gethash '001 empList) '(Charlie Brown))
(setf (gethash '002 empList) '(Freddie Seal)) 
(write (gethash '001 empList)) 
(terpri)
(write (gethash '002 empList))  

执行代码时,它返回以下结果 -

(CHARLIE BROWN)
(FREDDIE SEAL)

删除条目

remhash函数删除散列表中特定键的任何条目。 这是一个谓词,如果有条目则为true,如果没有则为false。

这个函数的语法是 -

remhash key hash-table

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq empList (make-hash-table)) 
(setf (gethash '001 empList) '(Charlie Brown))
(setf (gethash '002 empList) '(Freddie Seal)) 
(setf (gethash '003 empList) '(Mark Mongoose)) 
(write (gethash '001 empList)) 
(terpri)
(write (gethash '002 empList)) 
(terpri)
(write (gethash '003 empList))  
(remhash '003 empList)
(terpri)
(write (gethash '003 empList))  

执行代码时,它返回以下结果 -

(CHARLIE BROWN)
(FREDDIE SEAL)
(MARK MONGOOSE)
NIL

maphash函数

maphash函数允许您在哈希表上的每个键值对上应用指定的函数。

它需要两个参数 - 函数和哈希表,并为哈希表中的每个键/值对调用一次函数。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(setq empList (make-hash-table)) 
(setf (gethash '001 empList) '(Charlie Brown))
(setf (gethash '002 empList) '(Freddie Seal)) 
(setf (gethash '003 empList) '(Mark Mongoose)) 
(maphash #'(lambda (k v) (format t "~a => ~a~%" k v)) empList)

执行代码时,它返回以下结果 -

3 => (MARK MONGOOSE)
2 => (FREDDIE SEAL)
1 => (CHARLIE BROWN)

LISP - Input & Output

通用LISP提供多种输入输出功能。 我们已经使用了格式化功能,并输出了打印功能。 在本节中,我们将研究LISP中提供的一些最常用的输入输出函数。

输入功能 (Input Functions)

下表提供了LISP最常用的输入功能 -

Sr.No. 功能说明
1

read和可选input-stream eof-error-p eof-value recursive-p

它从输入流中读取Lisp对象的打印表示,构建相应的Lisp对象,并返回该对象。

2

read-preserving-whitespace &optional in-stream eof-error-p eof-value recursive-p

它用于某些特殊情况,在这些情况下,需要准确确定哪个字符终止了扩展令牌。

3

read-line &optional input-stream eof-error-p eof-value recursive-p

它读入由换行符终止的一行文本。

4

read-char &optional input-stream eof-error-p eof-value recursive-p

它从输入流中获取一个字符并将其作为字符对象返回。

5

unread-char character & optional input-stream

它将最近从输入流中读取的字符放在输入流的前面。

6

peek-char &optional peek-type input-stream eof-error-p eof-value recursive-p

它返回从输入流中读取的下一个字符,而不实际从输入流中删除它。

7

listen和可选的input-stream

如果存在从输入流中立即可用的字符,则谓词listen为真,否则为假。

8

read-char-no-hang &optional input-stream eof-error-p eof-value recursive-p

它类似于read-char ,但如果它没有得到一个字符,它不会等待一个字符,但会立即返回nil。

9

clear-input和可选input-stream

它清除与input-stream.关联的任何缓冲输入input-stream.

10

read-from-string string &optional eof-error-p eof-value & key :start :end :preserve-whitespace

它连续获取字符串的字符并构建一个LISP对象并返回该对象。 它还返回字符串中未读取的第一个字符的索引,或字符串的长度(或长度+1),视情况而定。

11

parse-integer string & key :start :end :radix :junk-allowed

它检查由以下内容分隔的字符串的子字符串:start和:end(默认为字符串的开头和结尾)。 它会跳过空白字符,然后尝试解析整数。

12

read-byte binary-input-stream和可选的eof-error-p eof-value

它从二进制输入流中读取一个字节,并以整数形式返回。

从键盘读取输入

read功能用于从键盘输入。 它可能不会有任何争论。

例如,考虑代码段 -

(write ( + 15.0 (read)))

假设用户从STDIN输入输入10.2,它返回,

25.2

read函数从输入流中读取字符,并通过解析为Lisp对象的表示来解释它们。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码 -

; the function AreaOfCircle
; calculates area of a circle
; when the radius is input from keyboard
(defun AreaOfCircle()
(terpri)
(princ "Enter Radius: ")
(setq radius (read))
(setq area (* 3.1416 radius radius))
(princ "Area: ")
(write area))
(AreaOfCircle)

执行代码时,它返回以下结果 -

Enter Radius: 5 (STDIN Input)
Area: 78.53999

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(with-input-from-string (stream "Welcome to IOWIKI!")
   (print (read-char stream))
   (print (read-char stream))
   (print (read-char stream))
   (print (read-char stream))
   (print (read-char stream))
   (print (read-char stream))
   (print (read-char stream))
   (print (read-char stream))
   (print (read-char stream))
   (print (read-char stream))
   (print (peek-char nil stream nil 'the-end))
   (values)
)

执行代码时,它返回以下结果 -

#\W 
#\e 
#\l 
#\c 
#\o 
#\m 
#\e 
#\Space 
#\t 
#\o 
#\Space 

输出函数 (The Output Functions)

LISP中的所有输出函数都使用一个名为output-stream,的可选参数output-stream,其中输出被发送。 如果未提及或为nil,输出流默认为变量* standard-output *的值。

下表提供了LISP最常用的输出功能 -

Sr.No. 功能和描述
1

write object &key:stream:escape:radix:base:circle:pretty:level:length:case:gensym:array

write object &key:stream:escape:radix:base:circle:pretty:level:length:case:gensym:array:readably:right-margin:miser-width:lines:pprint-dispatch

两者都将对象写入由以下指定的输出流:stream,默认为* standard-output *的值。 其他值默认为为打印设置的相应全局变量。

2

prin1 object和可选的output-stream

print object和可选的output-stream

pprint object和可选的output-stream

princ object和可选的output-stream

所有这些函数都将对象的打印表示output-streamoutput-stream 。 但是,存在以下差异 -

  • prin1返回对象作为其值。

  • print使用前一个换行符打印对象,后跟一个空格。 它返回对象。

  • pprint就像print一样,只是省略了尾随空格。

  • princ就像prin1,但输出没有转义字符

3

write-to-string object & key :escape:radix:base:circle:pretty:level:length:case:gensym:array

write-to-string object &key:escape:radix:base:circle:pretty:level:length:case:gensym:array:readably:right-margin:miser-width:lines:pprint-dispatch

prin1-to-string object

princ-to-string object

有效地打印对象,并将输出字符转换为字符串,并返回该字符串。

4

write-char character和可选的output-stream

它将字符output-stream,output-stream,并返回字符。

5

write-string string &optional output-stream &key:start:end

它将string的指定子string的字符写入output-stream.

6

write-line string &optional output-stream &key:start:end

它的工作方式与write-string相同,但之后会输出换行符。

7

terpri和可选的output-stream

它为output-stream.输出换行符output-stream.

8

fresh-line和可选output-stream

仅当流不在行的开头时才输出换行符。

9

finish-output和可选output-stream

force-output和可选output-stream

clear-output和可选output-stream

  • 函数finish-output尝试确保发送到output-stream的所有输出都已到达其目标,然后才返回nil。

  • 函数force-output启动任何内部缓冲区的清空,但返回nil而不等待完成或确认。

  • 函数clear-output尝试中止正在进行的任何未完成的输出操作,以便允许尽可能少的输出继续到目标。

10

write-byte integer binary-output-stream

它写入一个字节,即integer.的值integer.

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

; this program inputs a numbers and doubles it
(defun DoubleNumber()
   (terpri)
   (princ "Enter Number : ")
   (setq n1 (read))
   (setq doubled (* 2.0 n1))
   (princ "The Number: ")
   (write n1)
   (terpri)
   (princ "The Number Doubled: ")
   (write doubled)
)
(DoubleNumber)

执行代码时,它返回以下结果 -

Enter Number : 3456.78 (STDIN Input)
The Number: 3456.78
The Number Doubled: 6913.56

格式化输出

函数format用于生成格式良好的文本。 它具有以下语法 -

format destination control-string &rest arguments

Where,

  • 目的地是标准输出
  • control-string保存要输出的字符和打印指令。

format directive由波浪号(〜),用逗号分隔的可选前缀参数,可选冒号(:)和at符号(@)修饰符以及指示这是什么类型的指令的单个字符组成。

前缀参数通常是整数,标记为可选的带符号十进制数。

下表提供了常用指令的简要说明 -

Sr.No. 指令和说明
1

~A

后跟ASCII参数。

2

~S

接下来是S表达式。

3

~D

对于十进制参数。

4

~B

对于二进制参数。

5

~O

对于八进制参数。

6

~X

对于十六进制参数。

7

~C

对于角色论点。

8

~F

对于固定格式的浮点参数。

9

~E

指数浮点参数。

10

~$

美元和浮点论证。

11

~%

打印一个新行。

12

~*

下一个参数被忽略。

13

~?

间接。 下一个参数必须是一个字符串,后面的一个是列表。

例子 (Example)

让我们重写计算圆圈区域的程序 -

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defun AreaOfCircle()
   (terpri)
   (princ "Enter Radius: ")
   (setq radius (read))
   (setq area (* 3.1416 radius radius))
   (format t "Radius: = ~F~% Area = ~F" radius area)
)
(AreaOfCircle)

执行代码时,它返回以下结果 -

Enter Radius: 10.234 (STDIN Input)
Radius: = 10.234
Area = 329.03473

LISP - File I/O

我们已经讨论了常见LISP如何处理标准输入和输出。 所有这些函数也可用于读取和写入文本和二进制文件。 唯一不同的是,在这种情况下,我们使用的流不是标准输入或输出,而是为写入或读取文件的特定目的而创建的流。

在本章中,我们将了解LISP如何为其数据存储创建,打开,关闭文本或二进制文件。

文件表示一个字节序列,如果它是文本文件或二进制文件则无关紧要。 本章将引导您完成文件管理的重要功能/宏。

打开文件

您可以使用open函数创建新文件或打开现有文件。 它是打开文件的最基本功能。 但是, with-open-file通常更方便,更常用,我们将在本节后面看到。

打开文件时,将构造流对象以在LISP环境中表示它。 流上的所有操作基本上等同于文件上的操作。

open函数的语法是 -

open filename &key :direction :element-type :if-exists :if-does-not-exist :external-format

Where,

  • filename参数是要打开或创建的文件的名称。

  • keyword参数指定流的类型和错误处理方式。

  • :direction关键字指定流是应该处理输入,输出还是两者,它采用以下值 -

    • :input - 用于输入流(默认值)

    • :output - 用于输出流

    • :io - 用于双向流

    • :probe - 用于检查文件是否存在; 流被打开然后关闭。

  • :element-type指定流的事务单元的类型。

  • :if-exists参数指定在:direction为:output或:io时要采取的操作,并且指定名称的文件已存在。 如果方向是:input或:probe,则忽略此参数。 它需要以下值 -

    • :错误 - 它发出错误信号。

    • :new-version - 它创建一个具有相同名称但版本号较大的新文件。

    • :rename - 重命名现有文件。

    • :rename-and-delete - 重命名现有文件,然后将其删除。

    • :append - 它附加到现有文件。

    • :supersede - 它取代现有文件。

    • nil - 它不会创建文件,甚至流也只返回nil表示失败。

  • :if-does-not-exist参数指定在指定名称的文件尚不存在时要采取的操作。 它需要以下值 -

    • :错误 - 它发出错误信号。

    • :create - 它创建一个具有指定名称的空文件,然后使用它。

    • nil - 它不会创建文件甚至是流,而只是返回nil来指示失败。

  • :external-format参数指定用于表示文件中字符的实现识别方案。

例如,您可以打开存储在/ tmp文件夹中的名为myfile.txt的文件 -

(open "/tmp/myfile.txt")

写入和读取文件

with-open-file允许使用与读/写事务关联的流变量读取或写入文件。 作业完成后,它会自动关闭文件。 使用起来非常方便。

它具有以下语法 -

with-open-file (stream filename {options}*)
   {declaration}* {form}*
  • filename是要打开的文件的名称; 它可以是字符串,路径名或流。

  • options与open函数的关键字参数相同。

例子1 (Example 1)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(with-open-file (stream "/tmp/myfile.txt" :direction :output)
   (format stream "Welcome to IOWIKI!")
   (terpri stream)
   (format stream "This is a tutorials database")
   (terpri stream)
   (format stream "Submit your Tutorials, White Papers and Articles into our Tutorials   Directory.")
)

请注意,前一章中讨论的所有输入输出函数(例如terpri和format)都适用于写入我们在此处创建的文件。

执行代码时,它不会返回任何内容; 但是,我们的数据被写入文件中。 :direction :output关键字允许我们这样做。

但是,我们可以使用read-line函数从这个文件中read-line

例子2 (Example 2)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(let ((in (open "/tmp/myfile.txt" :if-does-not-exist nil)))
   (when in
      (loop for line = (read-line in nil)
      while line do (format t "~a~%" line))
      (close in)
   )
)

执行代码时,它返回以下结果 -

Welcome to IOWIKI!
This is a tutorials database
Submit your Tutorials, White Papers and Articles into our Tutorials Directory.

关闭文件

close函数关闭一个流。

LISP - Structures

结构是用户定义的数据类型之一,它允许您组合不同类型的数据项。

结构用于表示记录。 假设您想要在图书馆中跟踪您的图书。 您可能希望跟踪每本书的以下属性 -

  • Title
  • Author
  • Subject
  • Book ID

定义一个结构 (Defining a Structure)

LISP中的defstruct宏允许您定义抽象记录结构。 defstruct语句定义了一个新的数据类型,为您的程序提供了多个成员。

为了讨论defstruct宏的格式,让我们编写Book结构的定义。 我们可以将书籍结构定义为 -

(defstruct book 
   title 
   author 
   subject 
   book-id 
)

请注意

  • 上述声明创建了一个包含四个named components.的书籍结构named components. 因此,每本创作的书都将成为这种结构的对象。

  • 它定义了四个函数,分别是book-title,book-author,book-subject和book-book-id,它们将采用一个参数,一个书籍结构,并将返回本书的字段标题,作者,主题和书籍ID。宾语。 这些功能称为access functions.

  • 符号簿成为数据类型,您可以使用typep谓词进行检查。

  • 还有一个名为book-p,的隐式函数book-p,它是一个谓词,如果它的参数是一本书则为true,否则为false。

  • 将创建另一个名为make-book隐式函数,它是一个constructor,在调用时,它将创建一个包含四个组件的数据结构,适用于访问函数。

  • #S syntax指的是一种结构,您可以使用它来读取或打印书籍的实例。

  • 还定义了一个名为copy-book的隐式函数,该函数包含一个参数。 它需要一个book对象并创建另一个book对象,它是第一个对象的副本。 此功能称为copier function.

  • 例如,您可以使用setf来更改书籍的组件

(setf (book-book-id book3) 100)

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defstruct book 
   title 
   author 
   subject 
   book-id 
)
( setq book1 (make-book :title "C Programming"
   :author "Nuha Ali" 
   :subject "C-Programming Tutorial"
   :book-id "478")
)
( setq book2 (make-book :title "Telecom Billing"
   :author "Zara Ali" 
   :subject "C-Programming Tutorial"
   :book-id "501")
) 
(write book1)
(terpri)
(write book2)
(setq book3( copy-book book1))
(setf (book-book-id book3) 100) 
(terpri)
(write book3)

执行代码时,它返回以下结果 -

#S(BOOK :TITLE "C Programming" :AUTHOR "Nuha Ali" :SUBJECT "C-Programming Tutorial" :BOOK-ID "478")
#S(BOOK :TITLE "Telecom Billing" :AUTHOR "Zara Ali" :SUBJECT "C-Programming Tutorial" :BOOK-ID "501")
#S(BOOK :TITLE "C Programming" :AUTHOR "Nuha Ali" :SUBJECT "C-Programming Tutorial" :BOOK-ID 100)

LISP - Packages

在编程语言的一般术语中,包被设计用于提供使一组名称与另一组名称分离的方式。 在一个包中声明的符号不会与在另一个包中声明的相同符号冲突。 这样,包减少了独立代码模块之间的命名冲突。

LISP阅读器维护一个包含所有符号的表格。 当它找到新的字符序列时,它会创建一个新符号并存储在符号表中。 该表称为包。

当前包由特殊变量* package *引用。

LISP中有两个预定义的包 -

  • common-lisp - 它包含所有定义的函数和变量的符号。

  • common-lisp-user - 它使用common-lisp包和所有其他包以及编辑和调试工具; 它简称为cl-user

LISP中的包功能

下表提供了用于创建,使用和操作包的最常用函数 -

Sr.No. 功能和描述
1

make-package package-name &key:nicknames:use

它创建并返回具有指定包名称的新包。

2

in-package package-name和密钥:昵称:使用

使包最新。

3

in-package name

此宏导致* package *设置为名为name的包,该包必须是符号或字符串。

4

find-package name

它搜索一个包。 返回包含该名称或昵称的包; 如果不存在这样的包,则find-package返回nil。

5

rename-package package new-name和可选的new-nicknames

它重命名一个包。

6

list-all-packages

此函数返回Lisp系统中当前存在的所有包的列表。

7

delete-package package

它删除了一个包。

创建LISP包

defpackage函数用于创建用户定义的包。 它具有以下语法 -

(defpackage :package-name
   (:use :common-lisp ...)
   (:export :symbol1 :symbol2 ...)
)

Where,

  • package-name是包的名称。

  • :use关键字指定此程序包所需的程序包,即定义此程序包中代码使用的函数的程序包。

  • :export关键字指定此包中的外部符号。

make-package函数也用于创建包。 这个函数的语法是 -

make-package package-name &key :nicknames :use

参数和关键字具有与以前相同的含义。

使用包

创建包后,可以使用此包中的代码,方法是将其作为当前包。 in-package宏使封装在环境中最新。

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(make-package :tom)
(make-package :dick)
(make-package :harry)
(in-package tom)
(defun hello () 
   (write-line "Hello! This is Tom's IOWIKI")
)
(hello)
(in-package dick)
(defun hello () 
   (write-line "Hello! This is Dick's IOWIKI")
)
(hello)
(in-package harry)
(defun hello () 
   (write-line "Hello! This is Harry's IOWIKI")
)
(hello)
(in-package tom)
(hello)
(in-package dick)
(hello)
(in-package harry)
(hello)

执行代码时,它返回以下结果 -

Hello! This is Tom's IOWIKI
Hello! This is Dick's IOWIKI
Hello! This is Harry's IOWIKI

删除包

delete-package宏允许您删除包。 以下示例演示了这一点 -

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(make-package :tom)
(make-package :dick)
(make-package :harry)
(in-package tom)
(defun hello () 
   (write-line "Hello! This is Tom's IOWIKI")
)
(in-package dick)
(defun hello () 
   (write-line "Hello! This is Dick's IOWIKI")
)
(in-package harry)
(defun hello () 
   (write-line "Hello! This is Harry's IOWIKI")
)
(in-package tom)
(hello)
(in-package dick)
(hello)
(in-package harry)
(hello)
(delete-package tom)
(in-package tom)
(hello)

执行代码时,它返回以下结果 -

Hello! This is Tom's IOWIKI
Hello! This is Dick's IOWIKI
Hello! This is Harry's IOWIKI
*** - EVAL: variable TOM has no value

LISP - Error Handling

在通用LISP术语中,例外称为条件。

事实上,条件比传统编程语言中的异常更通用,因为condition表示任何可能影响各种级别的函数调用堆栈的事件,错误或不存在。

LISP中的条件处理机制以这样的方式处理这种情况,即条件用于发出警告(例如通过打印警告),同时调用堆栈上的上层代码可以继续其工作。

LISP中的状态处理系统有三个部分 -

  • 发出信号
  • 处理条件
  • 重启过程

处理条件

让我们举一个处理由零除条件引起的条件的例子,来解释这里的概念。

您需要采取以下步骤来处理条件 -

  • Define the Condition - “条件是一个对象,其类指示条件的一般性质,其实例数据包含有关导致条件发出信号的特定情况的详细信息”。

    define-condition宏用于定义条件,该条件具有以下语法 -

    (define-condition condition-name (error)
       ((text :initarg :text :reader text))
    )
    

    使用MAKE-CONDITION宏创建新条件对象,该宏根据:initargs参数初始化新条件的槽。

    在我们的示例中,以下代码定义了条件 -

    (define-condition on-division-by-zero (error)
       ((message :initarg :message :reader message))
    )
    
  • Writing the Handlers - 条件处理程序是用于处理在其上发出信号的条件的代码。 它通常写在一个调用错误函数的高级函数中。 当发出条件信号时,信令机制根据条件的类搜索适当的处理程序。

    每个处理程序包括 -

    • 类型说明符,表示它可以处理的条件类型
    • 一个带有单个参数的函数,即条件

    当发出条件信号时,信令机制找到与条件类型兼容的最近建立的处理程序并调用其函数。

    handler-case建立一个条件处理程序。 处理程序案例的基本形式 -

    (handler-case expression error-clause*)
    

    其中,每个错误条款的形式 -

    condition-type ([var]) code)
    
  • Restarting Phase

    这是实际从错误中恢复程序的代码,然后条件处理程序可以通过调用适当的重新启动来处理条件。 重启代码通常放在中级或低级函数中,条件处理程序放在应用程序的上层。

    handler-bind宏允许您提供重启功能,并允许您继续执行较低级别的功能而无需展开函数调用堆栈。 换句话说,控制流程仍将处于较低级别的功能。

    handler-bind的基本形式如下 -

    (handler-bind (binding*) form*)
    

    每个绑定都是以下列表 -

    • 条件类型
    • 一个参数的处理函数

    invoke-restart宏以指定的名称作为参数查找并调用最近绑定的重启函数。

    您可以进行多次重启。

例子 (Example)

在这个例子中,我们通过编写一个名为division-function的函数来演示上述概念,如果divisor参数为零,它将创建一个错误条件。 我们有三个匿名函数提供了三种方法 - 通过返回值1,通过发送除数2并重新计算,或返回1。

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(define-condition on-division-by-zero (error)
   ((message :initarg :message :reader message))
)
(defun handle-infinity ()
   (restart-case
      (let ((result 0))
         (setf result (division-function 10 0))
         (format t "Value: ~a~%" result)
      )
      (just-continue () nil)
   )
)
(defun division-function (value1 value2)
   (restart-case
      (if (/= value2 0)
         (/ value1 value2)
         (error 'on-division-by-zero :message "denominator is zero")
      )
      (return-zero () 0)
      (return-value (r) r)
      (recalc-using (d) (division-function value1 d))
   )
)
(defun high-level-code ()
   (handler-bind
      (
         (on-division-by-zero
            #'(lambda (c)
               (format t "error signaled: ~a~%" (message c))
               (invoke-restart 'return-zero)
            )
         )
         (handle-infinity)
      )
   )
)
(handler-bind
   (
      (on-division-by-zero
         #'(lambda (c)
            (format t "error signaled: ~a~%" (message c))
            (invoke-restart 'return-value 1)
         )
      )
   )
   (handle-infinity)
)
(handler-bind
   (
      (on-division-by-zero
         #'(lambda (c)
            (format t "error signaled: ~a~%" (message c))
            (invoke-restart 'recalc-using 2)
         )
      )
   )
   (handle-infinity)
)
(handler-bind
   (
      (on-division-by-zero
         #'(lambda (c)
            (format t "error signaled: ~a~%" (message c))
            (invoke-restart 'just-continue)
         )
      )
   )
   (handle-infinity)
)
(format t "Done."))

执行代码时,它返回以下结果 -

error signaled: denominator is zero
Value: 1
error signaled: denominator is zero
Value: 5
error signaled: denominator is zero
Done.

除了“条件系统”之外,如上所述,Common LISP还提供可以用于发信号通知错误的各种功能。 然而,在发信号时处理错误是依赖于实现的。

LISP中的错误信令功能

下表提供了常用功能,用于发出警告,中断,非致命和致命错误。

用户程序指定错误消息(字符串)。 这些功能处理此消息,可能会/可能不会将其显示给用户。

应该通过应用format函数来构造错误消息,不应该在开头或结尾包含换行符,并且不需要指示错误,因为LISP系统将根据其首选样式处理这些错误消息。

Sr.No. 功能和描述
1

error format-string &rest args

它标志着一个致命的错误。 从这种错误中继续是不可能的; 因此错误永远不会返回其调用者。

2

cerror continue-format-string error-format-string &rest args

它发出错误信号并进入调试器。 但是,它允许在解决错误后从调试器继续执行程序。

3

warn format-string &rest args

它会输出错误信息,但通常不会进入调试器

4

break &optional format-string &rest args

它打印消息并直接进入调试器,不允许任何可能被编程的错误处理设施拦截

例子 (Example)

在这个例子中,阶乘函数计算一个数的阶乘; 但是,如果参数为负数,则会引发错误条件。

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defun factorial (x)
   (cond ((or (not (typep x 'integer)) (minusp x))
      (error "~S is a negative number." x))
      ((zerop x) 1)
      (t (* x (factorial (- x 1))))
   )
)
(write(factorial 5))
(terpri)
(write(factorial -1))

执行代码时,它返回以下结果 -

120
*** - -1 is a negative number.

LISP - CLOS

共同的LISP早于几十年前面向对象编程的发展。 然而,在后期将对象引入其中。

定义类

defclass宏允许创建用户定义的类。 它将类建立为数据类型。 它具有以下语法 -

(defclass class-name (superclass-name*)
   (slot-description*)
   class-option*))

插槽是存储数据或字段的变量。

插槽描述的格式为(slot-name slot-option *),其中每个选项都是一个关键字,后跟名称,表达式和其他选项。 最常用的插槽选项是 -

  • :accessor函数名称

  • :initform表达式

  • :initarg符号

例如,让我们定义一个Box类,它有三个插槽长度,宽度和高度。

(defclass Box () 
   (length 
   breadth 
   height)
)

为插槽提供访问和读/写控制

除非插槽具有可以访问,读取或写入的值,否则类非常无用。

您可以在定义类时为每个槽指定accessors 。 例如,参加我们的Box课程 -

(defclass Box ()
   ((length :accessor length)
      (breadth :accessor breadth)
      (height :accessor height)
   )
)

您还可以为读取和写入插槽指定单独的accessor名称。

(defclass Box ()
   ((length :reader get-length :writer set-length)
      (breadth :reader get-breadth :writer set-breadth)
      (height :reader get-height :writer set-height)
   )
)

创建类的实例

泛型函数make-instance创建并返回类的新实例。

它具有以下语法 -

(make-instance class {initarg value}*)

例子 (Example)

让我们创建一个Box类,它有三个槽,长度,宽度和高度。 我们将使用三个插槽访问器来设置这些字段中的值。

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defclass box ()
   ((length :accessor box-length)
      (breadth :accessor box-breadth)
      (height :accessor box-height)
   )
)
(setf item (make-instance 'box))
(setf (box-length item) 10)
(setf (box-breadth item) 10)
(setf (box-height item) 5)
(format t "Length of the Box is ~d~%" (box-length item))
(format t "Breadth of the Box is ~d~%" (box-breadth item))
(format t "Height of the Box is ~d~%" (box-height item))

执行代码时,它返回以下结果 -

Length of the Box is 10
Breadth of the Box is 10
Height of the Box is 5

定义类方法

defmethod宏允许您在类中定义方法。 以下示例扩展了我们的Box类,以包含一个名为volume的方法。

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defclass box ()
   ((length :accessor box-length)
      (breadth :accessor box-breadth)
      (height :accessor box-height)
      (volume :reader volume)
   )
)
; method calculating volume   
(defmethod volume ((object box))
   (* (box-length object) (box-breadth object)(box-height object))
)
 ;setting the values 
(setf item (make-instance 'box))
(setf (box-length item) 10)
(setf (box-breadth item) 10)
(setf (box-height item) 5)
; displaying values
(format t "Length of the Box is ~d~%" (box-length item))
(format t "Breadth of the Box is ~d~%" (box-breadth item))
(format t "Height of the Box is ~d~%" (box-height item))
(format t "Volume of the Box is ~d~%" (volume item))

执行代码时,它返回以下结果 -

Length of the Box is 10
Breadth of the Box is 10
Height of the Box is 5
Volume of the Box is 500

继承 (Inheritance)

LISP允许您根据另一个对象定义对象。 这称为inheritance. 您可以通过添加新的或不同的功能来创建派生类。 派生类继承父类的功能。

以下示例解释了这一点 -

例子 (Example)

创建一个名为main.lisp的新源代码文件,并在其中键入以下代码。

(defclass box ()
   ((length :accessor box-length)
      (breadth :accessor box-breadth)
      (height :accessor box-height)
      (volume :reader volume)
   )
)
; method calculating volume   
(defmethod volume ((object box))
   (* (box-length object) (box-breadth object)(box-height object))
)
;wooden-box class inherits the box class  
(defclass wooden-box (box)
((price :accessor box-price)))
;setting the values 
(setf item (make-instance 'wooden-box))
(setf (box-length item) 10)
(setf (box-breadth item) 10)
(setf (box-height item) 5)
(setf (box-price item) 1000)
; displaying values
(format t "Length of the Wooden Box is ~d~%" (box-length item))
(format t "Breadth of the Wooden Box is ~d~%" (box-breadth item))
(format t "Height of the Wooden Box is ~d~%" (box-height item))
(format t "Volume of the Wooden Box is ~d~%" (volume item))
(format t "Price of the Wooden Box is ~d~%" (box-price item))

执行代码时,它返回以下结果 -

Length of the Wooden Box is 10
Breadth of the Wooden Box is 10
Height of the Wooden Box is 5
Volume of the Wooden Box is 500
Price of the Wooden Box is 1000
<上一篇.LISP - CLOS
↑回到顶部↑
WIKI教程 @2018