3

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!

4
  • Can you tell us explicitly how you want the output to be formatted, not just what current format string does? Commented Jun 16, 2013 at 20:46
  • @JoshuaTaylor Notice where I say given a list...I want: | No Match| -2378 11 4| -2378 11 5| That is the formatted result I'm looking for... Commented Jun 16, 2013 at 20:49
  • What I meant was that the numbers you've got in your format string are describing some field widths. If the first element of the argument were "Match" instead of no match, you wouldn't want | Match|... (with one space between | and Match, but |    Match|... (with four spaces). At the moment, we have to decipher the format string to learn this. Commented Jun 16, 2013 at 20:59
  • @JoshuaTaylor Ah! You are correct, I should have explained what I wanted more carefully. The first is a string element, either "No Match" or "Match" which I want right justified in a 9 character space. The sub-lists consist of 3 numbers with the first needing 6 spaces and the next two needing 3. This allows a one space pad between each. Commented Jun 16, 2013 at 21:13

2 Answers 2

5

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.)

Sign up to request clarification or add additional context in comments.

9 Comments

Similar to what I did to get as far as I did. I missed the ~@ which I shall now read up on!
The expanded answer is also deserving of a check mark but I didn't see it in time. :( Kudos to you both. Regarding the hyperspec, since I own both editions I seldom use it. That said I do have a .pdf of 22.3.3 with a indexed coversheet for the operators. Quite handy. Your advice is excellent (my quibbles aside :) )
I make lot of use of the HyperSpec, because Googling for "format hyperspec" is quicker than finding it in a hardcopy or even in a PDF.
@hsmyers Regardless of which version is accepted, do be aware of that ~9<~A~> can be replaced by ~9@A. I added that in a later edit; it wasn't present in my first final answer (heh).
Correct again :) As a biblioholic, you would have to pry my copy of CLSE2 out of my cold hands though. It is marked with stickies for all items of interest including format. That said, the problem is a typical one: you have to know at least a part of the answer in order to know just where to look. I should have found both ~@ and ~9@A but my lack of experience forced a miss. I need to go back and re-read with the idea of "What can I use this for..." firmly in place.
|
1

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~}~}|"

3 Comments

That results in *** - -: ("No Match" (-2378 11 4) (-2378 11 5)) is not a number with the input of (format t "|~{~9<~a~>~:@{|~6d~3d~3d~}~}|" (fss sl))...
To quote Emily Latilla of long ago "Never Mind" your code works fine on |~{~9<~a~>~:@{|~6d~3d~3d~}~}|" sl) Note that (fss sl) != sl. Insert Homer-esq head slap here!
The aesthetic directive (~A) supports justification, so ~9<~a~> can be replaced by ~9@A.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.