Lisp格式化一个字符多次输出

14

我想通过格式化的方式输出一个字符多次。这可能吗?是否有人能够填写_?_,以便示例代码可以正常工作?

(let ((n 3))
  (format nil "_?_" _?_ #\* _?_ ))

应该返回

=> "***"

1
@wvxvw 我总觉得“格式化”问题就像是一种扭曲的混淆代码竞赛,或者是一种特殊的代码高尔夫。除了 [tag:common-lisp] 标签外,我想知道是否还应该有一个 [tag:stupid-format-tricks] 标签呢? :) - Joshua Taylor
为什么这么严肃?令人愉悦的格式技巧。 - Lars Brinkhoff
@JoshuaTaylor 谷歌说目前还没有Lisp语言的J解释器...这可能会给你一些启示 :) - user797257
请注意,format不会将输出发送到流中,但会创建一个包含输出的字符串。 - Rainer Joswig
@JoshuaTaylor:同样的,在Lisp中的loop和Unix/Linux中的find也是嵌入式语言。如果你熟练掌握它们,那么非常方便,但它们与嵌入它们的语言完全不同。 - Drew
显示剩余2条评论
4个回答

12

看到这么多解决方案真是太好了:~A, ~<, 和 ~{。

使用 ~@{ 迭代结构可以提供一个简洁的解决方案:

(format nil "~v@{~A~:*~}" 3 #\*)

找到了关于~:*:的文档:http://www.lispworks.com/documentation/HyperSpec/Body/22_cga.htm - Clayton Stanley
1
也可以使用C代替A。写入字符比通过“打印机”写入Lisp对象更简单。 - Rainer Joswig
没错,但你已经在另一个回答中提到了这一点,所以我不会更新我的回答。 - Lars Brinkhoff

5
(format nil "~a~:*~a~:*~a~:*" #\*)
"***"

或者对Joshua Taylor的回答进行一些详细说明:
(format nil "~v{~a~:*~}" 3 '(#\*))

这是一种实现方法。不要被format指令中的星号所困扰,它们是控制符号,而不是要打印的字符。


然而,从效率角度考虑,下面的方法更好:

(make-string 3 :initial-element #\*)

这里有一种更好的方法可以实现同样的效果。

1
我也会说,“就可读性而言”,这将是首选的方式(当然,除了为此编写一个函数)。 - aerique

5
如果您使用~A指令,您可以以您建议的形式准确地获得此内容,即,
(let ((n 3))
  (format nil "_?_" _?_ #\* _?_ ))

有三个格式参数。但是,如果使用~<,您实际上只需使用两个格式参数即可完成此操作。如果您不需要在format已经生成的某些其他字符串中使用此字符串,则还可以使用make-string创建字符串。

使用Tilde A(~A)

您可以打印字符并指定最小宽度和相同的字符作为填充字符。例如,使用~v,,,vA和两个参数,您可以确保打印一些字符,并指定填充字符是什么。

CL-USER> (let ((n 3))
           (format nil "~v,,,vA"
                   n     ; number of characters that must be printed
                   #\*   ; character to use as padding
                   #\*)) ; character to print with ~A
"***"

CL-USER> (let ((n 3))
           (format nil "~v,,,vA" n #\* #\*)) 
"***"

CL-USER> (let ((n 10))
           (format nil "~v,,,vA" n #\* #\*))
"**********"

这里使用了完整形式的~A:

~mincol,colinc,minpad,padcharA是~A的完整形式,它允许控制填充。字符串在右侧(如果使用@修饰符,则在左侧)至少用minpad个padchar进行填充;然后每次插入colinc个字符,直到总宽度至少为mincol。默认值为mincol和minpad为0,colinc为1,padchar为空格字符。

以及v:

在指令的前缀参数中,可以使用V(或v)代替。在这种情况下,格式会从args中选择一个参数作为指令的参数。该参数应该是整数或字符。如果由V参数使用的arg为空,那么效果就好像省略了该参数一样。#可以用来代替前缀参数;它代表要处理的args的剩余数量。在递归格式中,在~?或~{的上下文中使用#前缀参数表示递归调用中剩余的格式参数数目。

使用Tilde Less Than(~<)

还有一个不常用的格式指令,tilde less than,它用于对齐。它接受一个格式字符串并使其对齐。
~mincol,colinc,minpad,padchar<str~> 这将在至少mincol个列宽的字段内对处理str生成的文本进行对齐。str可以使用~;分成若干段,此时间距将均匀地分配在文本段之间。
我们可以通过传递一个空的格式字符串并仅指定宽度和填充字符来(滥用)利用它:
CL-USER> (let ((n 3))
           (format nil "~v,,,v<~>"
                   n     ; width
                   #\*)) ; padding character
"***"

CL-USER> (let ((n 5))
           (format nil "~v,,,v<~>" n #\*))
"*****"

制作字符串

当然,除非你需要在已经格式化的其他字符串内使用这个特殊字符串,否则应该按照wvxvw的建议,只需使用make-string

(make-string 3 :initial-element #\*)

其他替代方案

format非常灵活,正如本答案和其他答案所指出的那样,有许多方法可以实现这一点。我尝试坚持使用一遍完成而不进行显式迭代的方法,但是像Lars Brinkhoffwvxvw所指出的那样,也可以使用format迭代来完成。


5

像Lars的答案一样,但我们使用带有~C的字符来书写,而不是使用带有~A的打印机:

(format nil "~v@{~C~:*~}" 3 #\*)

使用类似于write-char的方法编写字符比打印Lisp对象简单。打印机需要观察许多上下文并找到正确的打印方式。而WRITE-CHAR等方法只需将单个字符写入流中。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接