Search code examples
stringloopsiterationlispcommon-lisp

Iterating over strings


I have two strings of the same length which differ in exactly one character and I want a string of all the characters which are pairwise equal. So basically something like this which evaluates to a string instead of a list of characters:

(loop for a across "abcd"
      for b across "abce"
      when (char= a b) collect a)

Although performance isn't an issue here, I found it cumbersome to have a (coerce ... 'string) around it.

So I came up with something like

(loop with result = ""
      for a across "abcd"
      for b across "abce"
      when (char= a b)
        do (setf result (concatenate 'string result (string a)))
      finally (return result))

which does the job but looks not very elegant to me.

(map 'string (lambda (a b) (when (char= a b) a)) "abcd" "abce")

looks nicer but is not working because NIL is not a character when aand bare not equal.

Is there a more elegant idiom to iterate over a string and get a string back?


Solution

  • (loop with result = ""
          for a across "abcd"
          for b across "abce"
          when (char= a b)
            do (setf result (concatenate 'string result (string a)))
          finally (return result))
    

    Repeated concatenate are not a that good idea for longer strings.

    Alternatives:

    Loop into a list and coercing to a string

    CL-USER 3 > (loop for a across "abcd"
                      and b across "abce"
                      when (char= a b) collect a into list
                      finally (return (coerce list 'string)))
    "abc"
    

    Using stream and converting it to a string

    CL-USER 4 > (with-output-to-string (*standard-output*)
                  (loop for a across "abcd"
                        and b across "abce"
                        when (char= a b) do (write-char a)))
    "abc"
    

    Using an adjustable string

    CL-USER 5 > (loop with string = (make-array 0
                                                :element-type 'character
                                                :adjustable t
                                                :fill-pointer 0)
                      for a across "abcd"
                      for b across "abce"
                      when (char= a b) do (vector-push-extend a string)
                      finally (return string))
    "abc"