Lisp format a character a number of times
Asked Answered
E

4

14

I am looking for a way to output a character a number of times using format. Is this possible? Can someone fill in the _?_'s, so that the example works?

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

should return

=> "***"
Extemporaneous answered 19/11, 2013 at 13:34 Comment(7)
You probably want to tag your question common-lisp. The format works like that in Common Lisp, but dialects of Scheme can also implement similar functionality. format works differently in Emacs Lisp.Lorenzalorenzana
@wvxvw I always feel like format questions are a sort of perverse obfuscated code contest, or a special kind of code golf. In addition to the common-lisp tag, I wonder if there oughtn't be a stupid-format-tricks tag ,too? :)Somnambulate
Why so serious? delightful-format-tricksDuelist
@JoshuaTaylor Google says that there isn't yet a J interpreter in Lisp... might give you some ideas :)Lorenzalorenzana
@LarsBrinkhoff based on the term "stupid human tricks". I actually kind of enjoy these problems. They're good morning exercises. :)Somnambulate
note that the format does not output to a stream, but does create a string with the output.Parliament
@JoshuaTaylor: Likewise loop in Lisp and find in Unix/Linux. These are, in effect, embedded languages. Handy if you know them well, but quite unlike whatever languages they are embedded in.Deweese
D
12

It's nice to see so many solutions: ~A, ~<, and ~{ so far.

The ~@{ iteration construct provides a concise solution:

(format nil "~v@{~A~:*~}" 3 #\*)
Duelist answered 19/11, 2013 at 14:49 Comment(3)
Found the documentation for ~:*: lispworks.com/documentation/HyperSpec/Body/22_cga.htmTopical
it would also be possible to use ~C instead of ~A. Writing a character is a simpler operation than writing a Lisp object through the 'printer'.Parliament
Right, but you already suggested that in a separate answer, so I won't update mine.Duelist
L
5
(format nil "~a~:*~a~:*~a~:*" #\*)
"***"

Or elaborating a bit on Joshua Taylor's answer:

(format nil "~v{~a~:*~}" 3 '(#\*))

Would be one way of doing this. Don't be confused by the asterisks in the format directive, they are control characters, not characters being printed.


In terms of efficiency, however, this:

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

would be a preferred way to achieve the same effect.

Lorenzalorenzana answered 19/11, 2013 at 14:19 Comment(1)
I would also say "in terms of readability" this would be the preferred way (besides making a function for it ofcourse).Seamanlike
P
5

Like the answer of Lars, but we write the character wih ~C, instead of using the printer with ~A:

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

Writing a character with something like write-char is a simpler operation than printing a Lisp object. The printer has a lot of context to observe and has to find the right way to print the object. OTOH, something like WRITE-CHAR just writes a single character to a stream.

Parliament answered 30/12, 2017 at 12:26 Comment(0)
S
4

If you use the ~A directive, you can get this in exactly the form that you suggested, i.e.,

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

with three format arguments. However, if you use ~<, you can actually do this with just two format arguments. If you don't need this string inside of some other string that's already being generated by format, you could also just make the string using make-string.

Using Tilde A (~A)

You could print the character and specify a minimum width and the same character as the padding character. E.g., using ~v,,,vA and two arguments, you can ensure that some number of characters is printed, and what the padding character is.

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 #\* #\*))
"**********"

This uses the full form of ~A:

~mincol,colinc,minpad,padcharA is the full form of ~A, which allows control of the padding. The string is padded on the right (or on the left if the @ modifier is used) with at least minpad copies of padchar; padding characters are then inserted colinc characters at a time until the total width is at least mincol. The defaults are 0 for mincol and minpad, 1 for colinc, and the space character for padchar.

as well as v:

In place of a prefix parameter to a directive, V (or v) can be used. In this case, format takes an argument from args as a parameter to the directive. The argument should be an integer or character. If the arg used by a V parameter is nil, the effect is as if the parameter had been omitted. # can be used in place of a prefix parameter; it represents the number of args remaining to be processed. When used within a recursive format, in the context of ~? or ~{, the # prefix parameter represents the number of format arguments remaining within the recursive call.

Using Tilde Less Than (~<)

There's also a less commonly used format directive, tilde less than, that's used for justification. it takes a format string and makes s

~mincol,colinc,minpad,padchar<str~>

This justifies the text produced by processing str within a field at least mincol columns wide. str may be divided up into segments with ~;, in which case the spacing is evenly divided between the text segments.

We can (ab)use this by passing an empty format string and just specifying the width and the padding character:

CL-USER> (let ((n 3))
           (format nil "~v,,,v<~>"
                   n     ; width
                   #\*)) ; padding character
"***"

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

Just make a string

Of course, unless you need this special string inside of some other string that you're already formatting, you should do what wvxvw suggested, and just use make-string:

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

Other alternatives

format is very flexible, and as this and other answers are pointing out, there are lots of ways to do this. I've tried to stick to ones that should do this in one pass and not do explicit iterations, but this can be done with format iterations, too, as Lars Brinkhoff and wvxvw have pointed out.

Somnambulate answered 19/11, 2013 at 14:34 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.