乍听之下,不无道理;仔细揣摩,胡说八道

0%

自己动手打印整数

在 Common Lisp 中,打印整数一般用函数format。例如,上面的代码会往标准输出中打印出233这个数字:

1
(format t "~D" 233)

除此之外,format还可以控制打印内容的宽度、填充字符、是否打印正负号等方面。例如,要控制打印的内容至少占据6列的话,可以用如下代码

1
(format t "~6D" 233)

如果不使用字符串形式的 DSL,而是以关键字参数的方式来实现一个能够达到同样效果的函数format-decimal,代码可能如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
(defun format-decimal (n
&key
mincol)
"打印整数 N 到标准输出。

MINCOL 如果不为 NIL,则表示所打印的内容至少要占据的列数。"
;; 通过取余的方式得到 N 的每一位并逐个入栈,之后出栈的顺序就是从左到右打印的顺序了。
(let ((digits '()))
(cond ((zerop n)
(push 0 digits))
(t
(do ((n n (truncate n 10)))
((zerop n))
(push (rem n 10) digits))))
;; 打印出填充用的空格。
(when (and (integerp mincol) (> mincol (length digits)))
(dotimes (i (- mincol (length digits)))
(declare (ignorable i))
(princ #\Space)))

(dolist (digit digits)
(princ (code-char (+ digit (char-code #\0)))))))

(format-decimal 233 :mincol 6)

如果要求用数字0而不是空格来填充左侧的列,用format的写法如下:

1
(format t "~6,'0D" 233)

format-decimal想要做到同样的事情,可以这么写:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
(defun format-decimal (n
&key
mincol
(padchar #\Space))
"打印整数 N 到标准输出。

MINCOL 如果不为 NIL,则表示所打印的内容至少要占据的列数。
PADCHAR 表达式为了填充多余的列时所用的字符。"
(check-type mincol (or integer null))
(check-type padchar character)
;; 通过取余的方式得到 N 的每一位并逐个入栈,之后出栈的顺序就是从左到右打印的顺序了。
(let ((digits '()))
(cond ((zerop n)
(push 0 digits))
(t
(do ((n n (truncate n 10)))
((zerop n))
(push (rem n 10) digits))))
;; 打印出填充用的空格。
(when (and (integerp mincol) (> mincol (length digits)))
(dotimes (i (- mincol (length digits)))
(declare (ignorable i))
(princ padchar)))

(dolist (digit digits)
(princ (code-char (+ digit (char-code #\0)))))))

(format-decimal 233 :mincol 6 :padchar #\0)

-D默认是不会打印非负整数的符号的,可以用修饰符@来修改这个行为。例如,(format t "~6,'0@D" 233)会打印出00+233。稍微修改一下就可以在format-decimal中实现同样的功能

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
(defun format-decimal (n
&key
mincol
(padchar #\Space)
signed)
"打印整数 N 到标准输出。

MINCOL 如果不为 NIL,则表示所打印的内容至少要占据的列数。
PADCHAR 表达式为了填充多余的列时所用的字符。"
(check-type mincol (or integer null))
(check-type padchar character)
(flet ((to-digits (n)
;; 通过取余的方式得到 N 的每一位并逐个入栈,之后出栈的顺序就是从左到右打印的顺序了。
(let ((digits '()))
(cond ((zerop n)
(push #\0 digits))
(t
(do ((n n (truncate n 10)))
((zerop n))
(push (code-char (+ (rem n 10) (char-code #\0))) digits))))
digits)))
;; 通过取余的方式得到 N 的每一位并逐个入栈,之后出栈的顺序就是从左到右打印的顺序了。
(let ((digits (to-digits (abs n))))
(when (or signed (< n 0))
(push (if (< n 0) #\- #\+) digits))
;; 打印出填充用的空格。
(when (and (integerp mincol) (> mincol (length digits)))
(dotimes (i (- mincol (length digits)))
(declare (ignorable i))
(princ padchar)))

(dolist (digit digits)
(princ digit)))))

(format-decimal 233 :mincol 6 :padchar #\0 :signed t)

除了@之外,:也是一个~D的修饰符,它可以让format每隔3个数字就打印出一个逗号,方便阅读比较长的数字。例如,下列代码会打印出00+23,333

1
(format t "~9,'0@:D" 23333)

为此,给format-decimal新增一个关键字参数comma-separated来控制这一行为。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
(defun format-decimal (n
&key
comma-separated
mincol
(padchar #\Space)
signed)
"打印整数 N 到标准输出。

COMMA-SEPARATED 如果为 T,则每打印3个字符就打印一个逗号。
MINCOL 如果不为 NIL,则表示所打印的内容至少要占据的列数。
PADCHAR 表示填充多余的列时所用的字符。
SIGNED 控制是否显示非负整数的加号。"
(check-type comma-separated boolean)
(check-type mincol (or integer null))
(check-type padchar character)
(check-type signed boolean)
(flet ((to-digits (n)
;; 通过取余的方式得到 N 的每一位并逐个入栈,之后出栈的顺序就是从左到右打印的顺序了。
(let ((digits '()))
(cond ((zerop n)
(push #\0 digits))
(t
(do ((count 0 (1+ count))
(n n (truncate n 10)))
((zerop n))
(when (and comma-separated (> count 0) (zerop (rem count 3)))
(push #\, digits))
(push (code-char (+ (rem n 10) (char-code #\0))) digits))))
digits)))
;; 通过取余的方式得到 N 的每一位并逐个入栈,之后出栈的顺序就是从左到右打印的顺序了。
(let ((digits (to-digits (abs n))))
(when (or signed (< n 0))
(push (if (< n 0) #\- #\+) digits))
;; 打印出填充用的空格。
(when (and (integerp mincol) (> mincol (length digits)))
(dotimes (i (- mincol (length digits)))
(declare (ignorable i))
(princ padchar)))

(dolist (digit digits)
(princ digit)))))

(format-decimal -23333 :comma-separated t :mincol 9 :padchar #\0 :signed t)

事实上,打印分隔符的步长,以及作为分隔符的逗号都是可以定制的。例如,可以改为每隔4个数字打印一个连字符

1
(format t "~9,'0,'-,4@:D" 23333)

对于format-decimal来说这个修改现在很简单了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
(defun format-decimal (n
&key
(commachar #\,)
(comma-interval 3)
comma-separated
mincol
(padchar #\Space)
signed)
"打印整数 N 到标准输出。

COMMACHAR 表示当需要打印分隔符时的分隔符。
COMMA-INTERVAL 表示当需要打印分隔符时需要间隔的步长。
COMMA-SEPARATED 如果为 T,则每打印3个字符就打印一个逗号。
MINCOL 如果不为 NIL,则表示所打印的内容至少要占据的列数。
PADCHAR 表示填充多余的列时所用的字符。
SIGNED 控制是否显示非负整数的加号。"
(check-type commachar character)
(check-type comma-interval integer)
(check-type comma-separated boolean)
(check-type mincol (or integer null))
(check-type padchar character)
(check-type signed boolean)
(flet ((to-digits (n)
;; 通过取余的方式得到 N 的每一位并逐个入栈,之后出栈的顺序就是从左到右打印的顺序了。
(let ((digits '()))
(cond ((zerop n)
(push #\0 digits))
(t
(do ((count 0 (1+ count))
(n n (truncate n 10)))
((zerop n))
(when (and comma-separated (> count 0) (zerop (rem count comma-interval)))
(push commachar digits))
(push (code-char (+ (rem n 10) (char-code #\0))) digits))))
digits)))
;; 通过取余的方式得到 N 的每一位并逐个入栈,之后出栈的顺序就是从左到右打印的顺序了。
(let ((digits (to-digits (abs n))))
(when (or signed (< n 0))
(push (if (< n 0) #\- #\+) digits))
;; 打印出填充用的空格。
(when (and (integerp mincol) (> mincol (length digits)))
(dotimes (i (- mincol (length digits)))
(declare (ignorable i))
(princ padchar)))

(dolist (digit digits)
(princ digit)))))


(format-decimal -23333 :commachar #\- :comma-interval 4 :comma-separated t :mincol 9 :padchar #\0 :signed t)

全文完。

Liutos wechat
欢迎您扫一扫上面的微信公众号,订阅我的博客!
你的一点心意,我的十分动力。