Here’s the state machine implemented in Emacs Lisp. If there were a lot of states, the case-style implementation wouldn’t be very efficient but it’s fine for our three states.
1: (defun remove-repeated (ch-to-remove string)
2: "Remove any sequence of two or more occurrences of CH-TO-REMOVE
3: from STRING."
4: (let ((state :start))
5: (setq case-fold-search nil)
6: (dolist (current-ch (string-to-list string))
7: (case state
8: (:start (if (char-equal current-ch ch-to-remove)
9: (setq state :check-dupe)
10: (insert current-ch)))
11: (:check-dupe (if (char-equal current-ch ch-to-remove)
12: (setq state :remove-dupe)
13: (insert ch-to-remove)
14: (insert current-ch)
15: (setq state :start)))
16: (:remove-dupe (unless (char-equal current-ch ch-to-remove)
17: (insert current-ch)
18: (setq state :start)))))
19: (when (eql state :check-dupe)
20: (insert ch-to-remove))))
The when
in line 19 takes care of the case where there is a single target character at the end of the string.
When we run the code with
(with-temp-buffer
(remove-repeated ?X "XabXXcdXeXXXfX")
(buffer-string))
we get XabcdXefX
as expected. Notice how remove-repeated
doesn’t bother building a string. It just puts the required characters in the current buffer and returns. The caller coerces it into a string and the temporary buffer is killed when control flows out of the with-temp-buffer
form.
To illustrate the flexibility of this approach, suppose we want to return the length of the resulting string. We could, of course, just change the last line of the call to
but a more efficient solution is to simply replace the last line with
and not bother forming the actual string at all.