Common Lisp的艺术:从循环到格式化的美妙世界

🌿 引言

Roy Orbison的经典歌曲《Pretty Woman》唱道:“No one could look as good as you, mercy”。如果我们把这句话改编一下,大概也能形容Common Lisp的loop宏:“No one could look as complex as you, mercy”。确实,Common Lisp的loop宏既强大又复杂,初学者望而却步,老手也常常爱恨交加。但,正如那句老话所说:“熟能生巧”。今天,我们将一起走进这个充满魔力的世界,理解它的结构和美妙,并在最后聊聊format的神奇用法。


⚙️ Loop:循环的艺术

你可能听说过,loop宏是Common Lisp中最具争议的特性之一。有人称它为“代码炼金术”,因为它可以将复杂的迭代逻辑简化为简洁的表达式;也有人认为它丑陋,与Lisp的优雅相违背。不过,无论你站在哪一边,loop的确是一个值得掌握的工具。

让我们从loop的基本语法开始:

(loop 
    [ 设置循环 ]
    do 
    [ 每次循环要做的事情 ]
)

虽然这个结构看起来很简单,但别被它骗了,它能做的事情远不止这些。比如,loop可以像for循环一样使用:

(loop for N from 1 to 5 do (print N. )

👣 步进与集合遍历

loop中,你可以定义循环变量的步进方式,例如:

  • for N from 1 to 5 by 2:每次步进2
  • for N in '(1 2 3 4 5):遍历列表
  • for N across #(1 2 3 4 5):遍历向量

这些功能让loop成为处理不同集合类型时极为灵活的工具。


🔄 条件与重复

有时候,我们希望循环在特定条件下执行,这就是“条件循环”的用武之地:

  • while:只要条件为真,循环继续
  • until:直到条件为真,循环停止
(loop for y = 3 then (1- y) do (print y) until (= y 0))

在这个例子中,循环从y = 3开始,每次减1,直到y等于0时停止。


🎯 收集与计算

loop不仅能执行操作,还能收集数据或进行计算。你可以使用以下关键词来收集或计算循环的结果:

  • collect:将每次循环的值收集到一个列表中
  • sum:计算循环变量的总和
  • maximizeminimize:查找最大或最小值
  • count:计算循环次数

例如:

(loop for x from 1 to 5 sum x)

这个循环将返回1到5的总和。


🎨 格式化:数据的艺术展示

如果说loop是Common Lisp的“硬核”特性,那么format则是它的“柔情一面”。format函数不仅能将数据转换为字符串,还能生成各种格式的输出。其基本语法如下:

(format stream control-string data1 data2…)

其中,stream可以是t(表示标准输出),而control-string则是用于控制输出格式的指令。

当我们想输出简单的字符串时,可以这样做:

(format t "Hello, World!")

这与(princ "Hello, World!")的效果相同。但format的强大之处在于它可以处理复杂的格式化需求。例如,~A指令可以将任何Lisp对象转换为字符串:

(format t "Hello ~A, Lisp is cool!" "David")

输出结果为:

Hello David, Lisp is cool!

💡 列表与递归格式化

format的另一个强大功能是处理列表。通过使用~{}指令,我们可以对列表中的每个元素进行格式化:

(format t “~{Element of List: ~A~%~}” ‘(1 2 3 4))

输出结果为:

Element of List: 1
Element of List: 2
Element of List: 3
Element of List: 4

这非常适合在处理数据结构时生成漂亮的输出。让我们逐步分析这段Common Lisp代码:

背景介绍

format是Common Lisp中的一个强大的输出格式化函数,能够根据给定的格式字符串输出文本。它可以将数据以多种方式格式化,并且支持复杂的格式控制。

逐行解释

  1. (format t ...):
  • format函数的第一个参数t表示输出到标准输出(通常是控制台)。如果你想将输出保存到字符串中,可以使用nil或其他输出流。
  1. "~{Element of List: ~A~%~}":
  • 这是格式字符串,包含了格式控制符。
  • ~{ ... ~}: 这是一个循环格式控制符,表示对后面的列表进行迭代处理。
  • Element of List:: 在每次迭代中,这个字符串将与列表中的每个元素一起输出。
  • ~A: 这是一个格式控制符,表示以“人类可读”的格式输出其后面的参数(在这里是列表中的元素)。
  • ~%: 这个控制符表示输出一个换行符。每个元素之后都会换行。
  1. '(1 2 3 4):
  • 这是一个列表,包含数字1、2、3和4。单引号(')表示这是一个常量列表,而不是一个表达式。

整体功能

将这整段代码结合起来,它的作用是遍历列表'(1 2 3 4),对每个元素执行格式化输出,输出结果如下:

Element of List: 1
Element of List: 2
Element of List: 3
Element of List: 4

难点和要点讲解

  • 循环格式控制符~{ ... ~}:
  • 这个控制符允许我们对列表的每个元素执行相同的格式化操作,非常适合在输出时需要重复模式的场景。
  • 输出控制符的组合使用:
  • ~A~%的结合使用使得每个元素后都能换行输出,这是非常常见的输出格式需求。
  • 列表的处理:
  • Common Lisp对列表的处理非常灵活,使用单引号定义常量列表让我们能够方便地直接使用和输出。


📚 结论

通过这篇文章,我们浅尝了Common Lisp中两个最具代表性的特性:loopformat。它们一个是功能强大的循环控制工具,另一个则是灵活的格式化输出利器。虽然它们最初看起来可能让人望而生畏,但一旦掌握,它们将成为你编程工具箱中不可或缺的利器。

对于那些对loopformat感兴趣的读者,我强烈推荐进一步阅读相关文档和在线资源。正如Roy Orbison所唱的:“No one could look as good as you, mercy”,这些工具也是如此——一旦你理解了它们的美妙之处,你就会发现它们的无穷魅力。


📖 参考文献

  1. Common Lisp Loop Tutorial
  2. Common Lisp Format Reference
  3. The CLHS pages on Format
  4. Jean-Philippe Paradis, Hexstream

面向记忆的学习材料

帮助用户快速学习并记住Common Lisp中Loop和Format的基本用法和概念。

知识点: Loop的基本结构
题目: 以下哪个是Loop宏的基本结构?
选项:
A. (loop [设置循环] collect [每次执行的操作])
B. (loop [设置循环] do [每次执行的操作])
C. (loop [设置循环] repeat [每次执行的操作])
D. (loop [设置循环] for [每次执行的操作])

正确答案: B
解析: Loop宏的基本结构是(loop [设置循环] do [每次执行的操作])。其中,”do”关键字用于指定每次循环需要执行的操作。”do”也可以写成”doing”。
速记提示: 记住”do”是执行的关键,就像”做”这个动作。

知识点: Loop的For样式循环
题目: 以下哪个不是Loop中For样式循环的正确用法?
选项:
A. for N from 1 to 5
B. for N in ‘(1 2 3 4 5)
C. for N across #(1 2 3 4 5)
D. for N between 1 and 5

正确答案: D
解析: Loop中For样式循环的正确用法包括:from…to、in、across等。选项D中的”between…and”不是Loop中的标准语法。


速记提示: 记住常用的”from to”、”in”和”across”,排除不常见的表达。

知识点: Loop的While/Until样式循环
题目: 以下哪个是Loop中While样式循环的正确用法?
选项:
A. for N = X then Z while (condition) do (body)
B. for N = X then Z until (condition) do (body)
C. while N = X then Z do (body)
D. until N = X then Z do (body)

正确答案: A
解析: Loop中While样式循环的正确用法是:for N = X then Z while (condition) do (body)。这里,X是初始值,Z是每次循环的递增值,while后面跟随循环继续的条件。

1727243445242


速记提示: 记住”for…then…while”的结构,表示”从…然后…当…时”。

知识点: Loop的Repeat用法
题目: 如何使用Loop的repeat关键字重复执行5次操作?
选项:
A. (loop repeat 5 do (print “Hello”))
B. (loop 5 times do (print “Hello”))
C. (loop for i in 1 to 5 do (print “Hello”))
D. (loop while i < 5 do (print “Hello”))

正确答案: A
解析: 使用Loop的repeat关键字重复执行操作的正确方式是:(loop repeat 5 do (print “Hello”))。这将打印”Hello”5次。
速记提示: “repeat”直接跟数字,简单明了。

知识点: Loop的条件语句
题目: 以下哪个不是Loop中的条件语句?
选项:
A. if (condition) do (body) else do (body) end
B. when (condition) do (body) end
C. unless (condition) do (body) end
D. do (body) until (condition) end

正确答案: C
解析: Loop中的条件语句包括if、when和do…until。”unless”不是Loop中的标准条件语句。
速记提示: 记住常用的”if”、”when”和”until”,排除不常见的”unless”。

知识点: Loop的真/假条件
题目: 哪个Loop关键字用于检查循环中是否存在满足条件的情况?
选项:
A. always
B. never
C. thereis
D. sometimes

正确答案: C
解析: “thereis”关键字用于检查循环中是否存在满足条件的情况。如果存在,则返回true。
速记提示: “thereis”可以理解为”有没有”,询问是否存在。

知识点: Loop的Initially/Finally
题目: 如何在Loop循环开始前执行一些代码?
选项:
A. before (body)
B. start (body)
C. initially (body)
D. begin (body)

正确答案: C
解析: 使用”initially”关键字可以在Loop循环开始前执行一些代码。例如:(loop initially (print “start”) for y from 1 to 5 do (print y))
速记提示: “initially”意为”最初”,正好对应循环开始前。

知识点: Loop的解构
题目: 以下哪个是Loop中正确的解构绑定用法?
选项:
A. (loop for (a b) of ‘((1 2) (3 4) (5 6)) do (print (list a b)))
B. (loop for (a b) from ‘((1 2) (3 4) (5 6)) do (print (list a b)))
C. (loop for (a b) in ‘((1 2) (3 4) (5 6)) do (print (list a b)))
D. (loop for (a b) = ‘((1 2) (3 4) (5 6)) do (print (list a b)))

正确答案: C
解析: Loop中正确的解构绑定用法是使用”in”关键字,如:(loop for (a b) in ‘((1 2) (3 4) (5 6)) do (print (list a b)))。这样可以将每个子列表的元素分别绑定到a和b。
速记提示: 记住”in”是用于遍历列表的关键字。

知识点: Loop的收集关键字
题目: 以下哪个不是Loop中的数据收集关键字?
选项:
A. collect
B. append
C. nconc
D. gather

正确答案: D
解析: Loop中的数据收集关键字包括collect、append、nconc、count、sum、maximize和minimize。”gather”不是标准的Loop收集关键字。
速记提示: 记住常用的”collect”、”append”和”nconc”,排除不常见的”gather”。

知识点: Loop的变量声明
题目: 如何在Loop中声明一个新的循环变量?
选项:
A. declare new-loop-var
B. let new-loop-var
C. with new-loop-var
D. var new-loop-var

正确答案: C
解析: 在Loop中使用”with”关键字可以声明一个新的循环变量。例如:(loop with x = 0 for y from 1 to 5 do (setf x (+ x y)))
速记提示: “with”在英语中表示”带有”,这里表示循环带有一个新变量。

知识点: Format函数的基本语法
题目: Format函数的基本语法是什么?
选项:
A. (format stream control-string data1 data2…)
B. (format control-string stream data1 data2…)
C. (format data1 data2… control-string stream)
D. (format data1 data2… stream control-string)

正确答案: A
解析: Format函数的基本语法是(format stream control-string data1 data2…)。其中stream可以是t(标准输出)、nil(返回字符串)或一个流对象。
速记提示: 记住顺序:首先指定输出位置(stream),然后是控制字符串,最后是数据。

知识点: Format的流参数
题目: 在Format函数中,如果想要返回格式化后的字符串而不是直接输出,stream参数应该设置为什么?
选项:
A. t
B. nil
C. string
D. return

正确答案: B
解析: 在Format函数中,如果将stream参数设置为nil,函数将返回格式化后的字符串,而不是直接输出到标准输出流。
速记提示: nil表示”无”,这里表示不输出到任何流,而是返回字符串。

知识点: Format的基本指令
题目: 在Format函数中,哪个指令用于插入换行符?
选项:
A. ~N
B. ~L
C. ~%
D. ~R

正确答案: C
解析: 在Format函数中,~%指令用于插入换行符。例如:(format t “Hello~%World”) 将输出两行文本。
速记提示: %符号在很多编程语言中都用于表示特殊字符,这里用于换行。

知识点: Format的~A指令
题目: Format函数中的~A指令的作用是什么?
选项:
A. 将参数转换为ASCII码
B. 将参数转换为数组
C. 将任何Lisp类型转换为其打印表示
D. 将参数转换为地址

正确答案: C
解析: Format函数中的~A指令用于将任何Lisp类型转换为其打印表示。它是一个通用的参数转换器。
速记提示: A可以理解为”Any”,表示可以处理任何类型。

知识点: Format的列表处理
题目: 在Format函数中,如何处理列表中的每个元素?
选项:
A. 使用~L…~L指令
B. 使用~E…~E指令
C. 使用~{…~}指令
D. 使用~[…~]指令

正确答案: C
解析: 在Format函数中,使用~{…~}指令可以处理列表中的每个元素。例如:(format t “~{Element: ~A~%~}” ‘(1 2 3 4))
速记提示: 花括号{}在很多语言中用于表示代码块或集合,这里用于处理列表集合。

知识点: Format的数值格式化
题目: 在Format函数中,哪个指令用于以固定小数位数格式化浮点数?
选项:
A. ~D
B. ~F
C. ~E
D. ~G

正确答案: B
解析: 在Format函数中,~F指令用于以固定小数位数格式化浮点数。例如:(format nil “~,2F” 3.14159) 将输出”3.14″。
速记提示: F可以理解为”Fixed”,表示固定小数位数。

知识点: Format的条件指令
题目: Format函数中,哪个指令用于根据参数值选择不同的输出格式?
选项:
A. ~{…~}
B. ~[…~]
C. ~(…)
D. ~<…~>

正确答案: B
解析: 在Format函数中,~[…~]指令用于根据参数值选择不同的输出格式。例如:(format nil “~[零~;一~;二~:;很多~]” 2) 将输出”二”。
速记提示: 方括号[]常用于表示选择或索引,这里用于根据索引选择输出。

知识点: Format的递归处理
题目: 在Format函数中,如何递归处理嵌套的列表结构?
选项:
A. 使用~{~{…~}~}
B. 使用~[~[…~]~]
C. 使用~(~(…~)~)
D. 使用~<~<…~>~>

正确答案: A
解析: 在Format函数中,可以使用嵌套的~{…~}指令来递归处理嵌套的列表结构。例如:(format nil “~{(~{~A~^ ~})~^ ~}” ‘((1 2 3) (4 5 6)))
速记提示: 嵌套的花括号表示嵌套的列表处理。

知识点: Format的大小写转换
题目: 在Format函数中,哪个指令用于将输出转换为大写?
选项:
A. ~U
B. ~C
C. ~:@(
D. ~S
正确答题: C

解析: 在Format函数中,~:@(指令用于将输出转换为大写。例如:(format nil “~:@(hello, world~)”) 将输出”HELLO, WORLD”。
速记提示: @符号常用于表示特殊操作,这里用于大写转换。

总结

本学习材料涵盖了Common Lisp中Loop和Format的基本概念和用法。Loop宏是一个强大的迭代工具,提供了多种循环方式,包括For样式、While/Until样式、条件循环等。它还支持数据收集、变量声明和解构绑定等高级特性。Format函数是Lisp中通用的数据到字符串的转换工具,提供了丰富的指令来控制输出格式,包括基本的字符串插入、数值格式化、列表处理和条件输出等。掌握这些工具将大大提高你的Lisp编程效率和代码可读性。

参考文献

  1. Common Lisp – The Tutorial Part 8.pdf
  2. http://www.lispworks.com/documentation/lw50/CLHS/Body/22_ck.htm
  3. https://www.hexstreamsoft.com/articles/common-lisp-format-reference/clhs-summary/
0 0 投票数
Article Rating
订阅评论
提醒
0 评论
最多投票
最新 最旧
内联反馈
查看所有评论
0
希望看到您的想法,请您发表评论x