Clojure Goodness: Replacing Matching Values In String
We can search for a value in a string and replace it with another value using the clojure.string/replace
function. The first parameter is the original string value that we want to replace parts of. The second parameter can be a string value or regular expression. The last parameter is the replacement value that can be a string value or a function that returns a string value. The function itself gets either a string argument if the match has no nested groups (when match is a regular expression) or a vector with a complete match followed by the nested groups when the match has nested groups.
In the following example we several invocation of the clojure.string/replace
function with different arguments:
(ns mrhaki.string.replace
(:require [clojure.string :as str]
[clojure.test :refer [is]]))
;; Example string value to do replacements on.
(def s "Programming with Clojure is fun!")
;; Match argument can be a string value,
;; that gives same result as java.lang.String#replace method.
(is (= "Programming with Clojure is awesome!"
(str/replace s "fun" "awesome")
(.replace s "fun" "awesome")))
;; Match argument can also be regular expression pattern.
(is (= "Programming_with_Clojure_is_fun!"
(str/replace s #"\s+" "_")))
;; When the regular expression pattern has groups
;; we can refer to them using $ followed by matched
;; group number, eg. $1 for the first group.
(is (= "Execution 1 took 200ms"
(str/replace "run1=200ms" #"run(\d+)=(\d+ms)" "Execution $1 took $2")))
;; Replace argument can be a function.
;; Argument of the function is string of entire match
;; if there are no nested groups.
(is (= "[NOTE] [CAUTION]"
(str/replace "[note] [caution]" #"\[\w+\]" #(.toUpperCase %))))
;; Otherwise if there are nested groups a vector is
;; used as argument for the replacment function
;; where the first argument is the
;; entire match followed by the nested groups.
(is (= "ABC def"
(str/replace "abc DEF"
#"(\w+)(\s+)(\w+)"
#(str (.toUpperCase (% 1)) (% 2) (.toLowerCase (% 3))))))
;; By destructuring the vector argument
;; we can refer to the groups using a name.
(defn replacement
[[_ execution time]]
(let [seconds (/ (bigdec time) 1000)]
(str "Execution " execution " took " seconds " seconds")))
(is (= "Execution 1 took 0.2 seconds"
(str/replace "run1=200ms" #"run(\d+)=(\d+)ms" replacement)))
Written with Clojure 1.10.1.