Reputation: 711
While I occasionally dream in Perl regex, the format specification for Common Lisp (CLisp) still leaves me slightly bewildered. I'm shooting for the following result:
Given a list ("No Match" (-2378 11 4) (-2378 11 5))
I want:
| No Match| -2378 11 4| -2378 11 5|
out the other end. Here is what I get instead:
[685]> (fss sd)
("No Match" (-2378 11 4) (-2378 11 5))
[686]> (format t "|~{~9<~a~>~2*~}~:*~{~*~{|~6d~3d~3d~}~}|" (fss sd))
| No Match| -2378 11 4
*** - There are not enough arguments left for this format directive.
Current point in control string:
"|~{~9<~a~>~2*~}~:*~{~*~{|~6d~3d~3d~}~}|"
|
The following restarts are available:
ABORT :R1 Abort main loop
Break 1 [687]> :R1
[688]>
I'm delighted that I'm 2/3rds the way there, but the situation is driving me a little crazy. If I understand things correctly, the |~{~9<~a~>~2*~}
consumes the first element of the list, No Match
, and then skips the rest. The next portion, ~:*
resets the argument pointer back to the start of the list. Then the ~{~}
wrapper places me in the list. Next the ~*
skips the already handled portion of the list. The next pair of ~{~}
enters the first sub-list of the argument. The first sub-list is handled correctly. Then...ERROR. Clearly there is something amiss in my understanding of format, but I'm not clear on what that might be.
I've often felt that the rest of CL is pretty straight forward, but I really think we need a 'Format Cookbook' chapter in the CL Cookbook at the very least.
In sum, this aspirant needs help from a more knowledgeable follower of the way. HELP!
Upvotes: 3
Views: 731
Reputation: 85913
I constructed a solution incrementally. Since the format string has only one argument, I started by creating a format string that prints each element of the list:
CL-USER> (format t "~{|~A~}|"
'("No Match" (-2378 11 4) (-2378 11 5)))
|No Match|(-2378 11 4)|(-2378 11 5)|
Now, after the first element, we actually want to iterate over all the remaining arguments, which we can do with ~@{
. I've added square brackets around each element so we can see the bounds of the iteration.
CL-USER> (format t "~{|~A ~@{[~A]~}~}|"
'("No Match" (-2378 11 4) (-2378 11 5)))
|No Match [(-2378 11 4)][(-2378 11 5)]|
Now, each element within the list in the square bracket needs to be printed individually, since the field widths are not all the same. We can replace the initial ~A
with ~9<~A~>
now, too.
CL-USER> (format t "~{|~9<~A~>~@{~{|~6d~3d~3d~}~}~}|"
'("No Match" (-2378 11 4) (-2378 11 5)))
| No Match| -2378 11 4| -2378 11 5|
Now (and this is pointed out in another answer, too), the use of ~@
immediately followed by ~{
is a construct that can be replaced by ~:@{
, which shortens up the format string.
CL-USER> (format t "~{|~9<~A~>~:@{|~6d~3d~3d~}~}|"
'("No Match" (-2378 11 4) (-2378 11 5)))
| No Match| -2378 11 4| -2378 11 5|
Finally, the aesthetic directive, ~A
can used to specify the field width. ~mincolA
puts spaces on the right, but ~mincol@A
puts them on the left, so the use of ~<
is not necessary. ~9<~A~>
becomes ~9@A
:
CL-USER> (format t "~{|~9@A~:@{|~6d~3d~3d~}~}|"
'("No Match" (-2378 11 4) (-2378 11 5)))
| No Match| -2378 11 4| -2378 11 5|
This sort of incremental approach can be used very frequently in Lisp to solve part of a problem first, and then refine the solution incrementally. Unlike other languages that have a more expensive write-compile-run cycle, Lisp's quick REPL makes this kind of process very easy.
If you're going to do a lot of work with format
, it's worth skimming through section 22.3 Formatted Output in the HyperSpec. Most of the features you probably won't use for quite a while, but having skimmed through that section, they'll be in the back of your mind for when you need them. (You'll have to consult the manual then, but the all to often underestimated point is that you'll know that there's something in the manual, and where to look for it.)
Upvotes: 5
Reputation: 51531
With the third ~{
, you are entering element-wise printing of the first sublist. Each iteration consumes three elements of the sublist, so it is done after a single pass. Then, this loop is exited and the next higher loop goes to the next iteration. It skips another element of the outer list (the second sublist), but then there are no arguments left to use for entering the inner loop again.
Instead of skipping forward and backward, you could simply use both the :
and the @
modifier of ~{
to process the remaining lists:
"|~{~9<~a~>~:@{|~6d~3d~3d~}~}|"
Upvotes: 1