Alternating upcase/downcase for a string in Common Lisp

Viewed 154

I want to write a function that will return a string formatted with alternative upcase/downcase in Common Lisp. For example, entering "stackoverflow" should return the string "StAcKoVeRfLoW". Here's my attempt, but it just returns a list of cons pairs. Am I on the right track?

(defun mockify (chars)
  (let ((lst (coerce chars 'list)))
    (if (equal lst nil) nil
        (coerce (cons
                 (cons (char-upcase (car lst)) (char-downcase (cadr lst)))
                      (mockify (cddr lst)))
                'string))))

CL-USER> (mockify "meow")
((#\M . #\e) (#\O . #\w))
4 Answers

Using MAP: we are creating a new string, moving over the original string and upcase/downcase based on an alternating boolean variable.

CL-USER 353 > (let ((string "stackoverflow")
                    (upcase t))
                (map (type-of string)
                     (lambda (element)
                       (prog1 (if upcase
                                  (char-upcase element)
                                (char-downcase element))
                         (setf upcase (not upcase))))
                     string))
"StAcKoVeRfLoW"
(defun mockify (chars)
  (let ((lst (coerce chars 'list)))
    (if (equal lst nil)
        ;; return nil
        nil
        ;; return a string (coerce)
        (coerce 
          ;; a list whose elements are cons-cells, but ...
          (cons (cons (char-upcase (car lst))
                      (char-downcase (cadr lst)))
                ;; ... the rest is computed by calling mockify,
                ;; which returns either an empty list or a string
                (mockify (cddr lst)))
          'string))))

The types of your expressions are confusing, and in fact your example leads to an error when using SBCL:

> (mockify "meow")
The value
  (#\O . #\w)
is not of type
  CHARACTER
when setting an element of (ARRAY CHARACTER)
   [Condition of type TYPE-ERROR]

Also, you are going to have to handle corner cases in your code, because as is, it is possible that (cadr list), i.e. (second list), is called on a list that has only one element. Then, the result would be NIL and char-downcase would fail with an error.

Using only strings

I'd suggest writing a version of the function that does not use intermediate lists:

  1. let R be the string-downcase of the whole string
  2. then modify every other character of R by upcasing it

So for example, one way to do it (among others) would be:

(defun mockify (chars)
  (let ((chars (string-downcase chars)))
    (prog1 chars
      (upcasify chars 0))))

(defun upcasify (string index)
  (when (< index (length string))
    (setf (char string index) (char-upcase (char string index)))
    (upcasify string (+ index 2))))

Using only lists

If you prefer having a recursive function that processes lists, I'd rather define it in layers:

  1. coerce string to list
  2. process the list recursively
  3. eventually, coerce the resulting list back to a string

This will avoid doing conversions from strings to lists at every step, and make the code simpler at each level.

(defun mockify (chars)
  (coerce (mockify-list (coerce chars 'list)) 'string))

(defun mockify-list (chars)
  ...)

The list version is recursive and look like what you tried to do, but take care of corner cases.

There is more than one way to do it. Here is a loop based solution:

(let ((string "StackOverflow"))
  (with-output-to-string (s)
    (loop :for c :across string
          :for up := t :then (not up)
          :do (princ (if up
                         (char-upcase c)
                         (char-downcase c))
                     s))))
Related