Not a bad approach.
I can see one arguable bug, and have a few style notes on your code though. Lets start with the bug:
CL-USER> (defparameter a "test")
A
CL-USER> (pad-with-string 6 a :from-end t)
"tsetts"
CL-USER> a
"tset"
CL-USER>
Is that what you were expecting? It's happening because you call nreverse
on your input argument if from-end
is passed. When I've got a situation where I have to mutate parameters, I follow the Scheme convention and name the procedure with a trailing bang (so, like pad-with-string!
). Your comments state that you don't care about efficiency though, which leads me to believe you could just use the standard reverse
rather than nreverse
.
You're using apply
on circular list, but you're defining it locally and only calling it in one place. In this situation, I'd kill the &rest
and just call the function
...
(labels ((circular-list (elements)
(let ((cycle (copy-list elements)))
(nconc cycle cycle))))
(loop :repeat n
:for c
:in (circular-list (coerce (if from-end (nreverse str) str) 'list))
...
You've got the (if from-end (reverse a) a)
pattern in a couple of places. I'd pull that out into an additional local definition.
...
(maybe-reverse (thing)
(if from-end (reverse thing) thing)))
(loop :repeat n
:for c
:in (circular-list (coerce (maybe-reverse str) 'list))
:collect c
:into chars
:finally (return (coerce (maybe-reverse chars) 'string)))))
thanks to Rainer for pointing this out
The result of the collect
call is returned implicitly at the end of the loop
unless you give it an explicit name. Which means you can avoid finally (return ...)
by wrapping those transformations around the loop itself:
...
(coerce
(maybe-reverse
(loop :repeat n
:for c
:in (circular-list (coerce (maybe-reverse str) 'list))
:collect c))
'string)))
Not a hard and fast rule, but people typically use symbols rather than keywords for loop
words. So,
...
(coerce
(maybe-reverse
(loop repeat n
for c
in (circular-list (coerce (maybe-reverse str) 'list))
collect c))
'string)))
Again, there's not really a standard for this, but I tend to like putting conditions and their effects/modifiers on the same line.
...
(coerce
(maybe-reverse
(loop repeat n
for c in (circular-list (coerce (maybe-reverse str) 'list))
collect c))
'string)))
So,
(defun pad-with-string (n str &key (from-end nil))
(labels ((circular-list (elements)
(let ((cycle (copy-list elements)))
(nconc cycle cycle)))
(maybe-reverse (thing)
(if from-end (reverse thing) thing)))
(coerce (maybe-reverse
(loop repeat n
for c in (circular-list (coerce (maybe-reverse str) 'list))
collect c))
'string)))