Well, lets try to solve some simpler problems first and work our way up to the string replace problem. First, how do we determine two strings are equal? Fairly straightforward, right? If the strings have the same length, and the same characters in the same order, then they are equal:
(defn str-eq? [s1 s2] (and (= (count s1) (count s2)) (every? identity (map = s1 s2))))
Okay, lets try checking if a string has some other string as a prefix. This turns out to have an almost identical solution, we just ease the requirement that both strings be the same length:
(defn str-prefix? [s prefix] (and (>= (count s) (count prefix)) (every? identity (map = s prefix))))
Now, lets start thinking about how we might find all occurrences of some string s1 in some string s. Well, if we take all of the suffixes of s, ordered from longest to shortest, we can determine where s1 occurs in s by checking which suffixes s1 is a prefix of:
(defn str-suffixes [s] (->> s (iterate next) (take (inc (count s))) ;; make sure we include the empty string (map #(apply clojure.core/str %)))) (defn str-find [s s1] (->> (str-suffixes s) (map (fn [index suffix] (if (str-prefix? suffix s1) index nil)) (iterate inc 0)) (filter identity)))
Finally, it's time for the string replace. We'll start with the simple case where a string s1 appears in a string s at most once at index. Just concatenate everything before the index with the replacement string, s2, and everything after the occurrence of s1 in s:
(defn str-replace [s s1 s2] (reduce (fn [s index] (apply clojure.core/str (concat (take index s) s2 (drop (+ index (count s1)) s)))) s (str-find s s1)))
If s1 appears in s multiple times, you'll find that this doesn't work. This is because str-find gives its results in ascending order. With every string replacement we do, we invalidate indices later in the string. To fix this, we just process each index from greatest to least:
(defn str-replace [s s1 s2] (reduce (fn [s index] (apply clojure.core/str (concat (take index s) s2 (drop (+ index (count s1)) s)))) s (sort > (str-find s s1))))
And here it is in action:
(str-replace "foobar" "foo" "baz") => "bazbar" (str-replace "foobar" "bar" "baz") => "foobaz" (str-replace "foobarbarfoo" "bar" "bazz") => "foobazzbazzfoo" (str-replace "foobar" "z" "baz") => "foobar"
When solving problems like this, it helps to think of what other similar operations there are, and how they might be related to the one you're trying to implement. Let me know if you have any questions.