Substitute character in a string

Nyquist does not appear to have a function for substituting characters in a string, so here’s a function to do just that.

;; substitute character in string

(defun subst-char-string (string old new)
   (setq newstring "") ; initialise the new string
   (dotimes (i (length string))
      (let ((ch (char string i))) ; ch is the next character in the string
         (if (char= ch old)(setq ch new))
         (setq newstring (strcat newstring (string ch))))) ; add character to string
   newstring)

(print (subst-char-string "Hello World" '#space '- ))

I’m quite sure you know, but the function above pollutes the global namespace by creating a global variable named “newstring”, it would be better to make “newstring” al local “let” variable. Here is what I usually write:

(defmacro string-append (variable &rest strings)
  `(setq ,variable (strcat ,variable ,@strings)))

(defun subst-char-string (string old-char new-char)
  (cond ((not (stringp string))
         (error "subst-char-string: not a string" string))
        ((not (characterp old-char))
         (error "subst-char-string: not a character" old-char))
        ((not (characterp new-char))
         (error "subst-char-string: not a character" new-char))
        (t
         (let ((end (length string))
               (result ""))
           (dotimes (index end)
             (let ((current-char (char string index)))
               (if (char= old-char current-char)
                   (string-append result (string new-char))
                   (string-append result (string current-char)))))
           result))))

(print (subst-char-string "Hello World" #space #-)) => "Hello-World"

The local “end” variable is not really necessary, because “dotimes” pre-evaluates its termination-test value and stores it in an internal local variable, but with more complicated “dotimes” loops the “end” variable makes the code more readable [at least for me].

BTW: The unicode research says: (char ) is safe with multi-byte unicode characters.

  • edgar

Good point re. ‘newstring’ being a global variable.

I’m a bit cautious about creating functions that depend on other functions/macros as it seems a bit too easy to forget about the dependency. For short functions I generally prefer to nest the dependant function to ensure that they stay together (unless of course the function/macro is also used elsewhere). Is there anything wrong with this approach?

(defun subst-char-string (string old-char new-char)
   ; nested macro
   (defmacro string-append (variable &rest strings)
     `(setq ,variable (strcat ,variable ,@strings)))
   ; back to main function code
   (let ((end (length string))
            (result ""))
      (dotimes (index end)
         (let ((current-char (char string index)))
            (if (char= old-char current-char)
               (string-append result (string new-char))
               (string-append result (string current-char)))))
      result))

(print (subst-char-string "Hello World" '#space '#-)) ; => "Hello-World"

or simply:

(defun subst-char-string (string old new)
   (let ((newstring "")) ; initialise the new string
      (dotimes (i (length string))
         (let ((ch (char string i))) ; ch is the next character in the string
            (if (char= ch old)(setq ch new))
            (setq newstring (strcat newstring (string ch))))) ; add character to string
      newstring))

(print (subst-char-string "Hello World" '#space '#- )) ; =>Hello-World

Of course there’s nothing wrong. The “string-append” is a simplified version of one of my standard text macros and I just simply was to lazy to fiddle it into the function’s code, that’s all [sorry if you had expexted more secret science behind that :wink: ] …

  • edgar

I’ve just been reading about functions in the xlisp manual - it looks like an another alternative would be to make ‘newstring’ a local variable by including it as &aux argument in the function, for example:

(defun subst-char-string (string old new &aux (newstring ""))
   (dotimes (i (length string))
      (let ((ch (char string i))) ; ch is the next character in the string
         (if (char= ch old)(setq ch new))
         (setq newstring (strcat newstring (string ch))))) ; add character to string
   newstring)

I don’t recall ever seeing this method used in any plug-ins, but it looks like a neat way to set local variables if there are only a few.

Hi Steve and Edgar. Using any of those code versions for the substitution, I’m getting an error. Any idea what I am doing wrong ?

Here is the following error in Debug window :

error: illegal character after # - 115
Function: #<Subr-(null): #13c0c840>
Arguments:
#<File-Stream: #4823f68>
##
Function: #<Subr-(null): #13c0c800>
Arguments:
#<File-Stream: #4823f68>
#(
Function: #<Subr-(null): #13c0c800>
Arguments:
#<File-Stream: #4823f68>
#(
1> error: unbound variable - PACE
if continued: try evaluating symbol again
Function: #<Subr-(null): #13c0c840>
Arguments:
#<File-Stream: #4823f68>
##
Function: #<Subr-(null): #13c0c800>
Arguments:
#<File-Stream: #4823f68>
#(
Function: #<Subr-(null): #13c0c800>
Arguments:
#<File-Stream: #4823f68>
#(
2> error: illegal character after # - 45
Function: #<Subr-(null): #13c0c840>
Arguments:
#<File-Stream: #4823ee8>
##
Function: #<Subr-(null): #13c0c840>
Arguments:
#<File-Stream: #4823f68>
##
Function: #<Subr-(null): #13c0c800>
Arguments:
#<File-Stream: #4823f68>
#(
Function: #<Subr-(null): #13c0c800>
Arguments:
#<File-Stream: #4823f68>
#(
3> error: misplaced right paren
Function: #<Subr-(null): #13c0c7e0>
Arguments:
#<File-Stream: #4823ee8>
#)
Function: #<Subr-(null): #13c0c840>
Arguments:
#<File-Stream: #4823ee8>
##
Function: #<Subr-(null): #13c0c840>
Arguments:
#<File-Stream: #4823f68>
##
Function: #<Subr-(null): #13c0c800>
Arguments:
#<File-Stream: #4823f68>
#(
Function: #<Subr-(null): #13c0c800>
Arguments:
#<File-Stream: #4823f68>
#(
4> error: misplaced right paren
Function: #<Subr-(null): #13c0c7e0>
Arguments:
#<File-Stream: #4823ee8>
#)
Function: #<Subr-(null): #13c0c7e0>
Arguments:
#<File-Stream: #4823ee8>
#)
Function: #<Subr-(null): #13c0c840>
Arguments:
#<File-Stream: #4823ee8>
##
Function: #<Subr-(null): #13c0c840>
Arguments:
#<File-Stream: #4823f68>
##
Function: #<Subr-(null): #13c0c800>
Arguments:
#<File-Stream: #4823f68>
#(
Function: #<Subr-(null): #13c0c800>
Arguments:
#<File-Stream: #4823f68>
#(
5> 4> 3> 2> 1>

OK, I think I found the trouble. Everything went fine when I change #space and #- by #\space and #-.

That’s correct.
This is a very old topic, and I guess that during one of the forum updates over the years, the backslashes got lost in the forum’s database. I don’t think these code samples would have ever worked without the backslashes.