Home > Software design >  Replacing characters in a string, with a set of characters inside of a map in Clojure
Replacing characters in a string, with a set of characters inside of a map in Clojure

Time:05-18

I have a function that takes in a string s and a map of characters charmap. If any characters in the string s are inside of charmap, replace the character with the value of the map.

Note, the keys in the map must be a string, rather than a char.

For example:

(replace-characters "Hi!" {"!" "Exclamation Mark"}) ;; => "HiExclamation Mark"

This is the code I am currently using:

(defn- replace-characters
  "Replaces any characters in a string that are mapped in the given charmap"
  [s charmap]
  (let [character-set (set s)
        characters (filter #(contains? charmap (str %)) character-set)]
    (string/replace s (re-pattern (string/join "|" characters)) charmap)))

However, I am getting a NullPointerException and I am seriously confused on why.

java.lang.NullPointerException: Cannot invoke "String.indexOf(int)" because "s" is null

I would like to solve this in pure Clojure preferably, not Java, etc.

Update: So the code above works in the repl. Which is nice. But for some reason the following is causing the error:

(-> s
     (string/replace #"[:/?#\[\]@!$&'()* ,;=]" "")
     (replace-characters charmap)) ;; Where charmap is a large map of key value characters.

CodePudding user response:

This is the expression that causes error:

(str/replace "Hi" #"" {"!" "Exclamation Mark"})

("!" was replaced with regex to "", character-set has value #{\H \i} and characters is (), so pattern created with re-pattern is #"".)

Empty regex matches every space between letters:

(str/replace "Hi" #"" " ")

=> " H i "

So, replace is looking for replacement in hash-map {"!" "Exclamation Mark"}, but doesn't find anything- there is no key "":

(str/replace "Hi" #"" {"!" "Exclamation Mark"})
=> error

(str/replace "Hi" #"" {"" " "})
=> " H i "

One possible solution can be to simplify definition of replace-characters (this solution will work only for non-empty charmap):

(defn replace-characters [s charmap]
  (str/replace s (re-pattern (str/join "|" (keys charmap)))
               charmap))

Tests:

(replace-characters "Hi!" {"!" "Exclamation Mark"})

=> "HiExclamation Mark"

(-> "Hi!"
    (str/replace #"[:/?#\[\]@!$&'()* ,;=]" "")
    (replace-characters {"!" "Exclamation Mark"}))

=> "Hi"

CodePudding user response:

i would just go with something as simple as this:

(apply str (replace {"!" "[exclamation mark]"
                         "?" "[question mark]"}
                       (map str "is it possible? sure!")))

;;=> "is it possible[question mark] sure[exclamation mark]"

or this way with transducers:

(apply str (eduction (map str) (replace {"!" "[exclamation mark]"
                                                "?" "[question mark]"})
                        "is it possible? sure!"))

or maybe like this:

(defn replace-characters [s rep]
  (apply str (map #(rep (str %) %) s)))

user> (replace-characters "is it possible? sure!" {"!" "[exclamation mark]"
                                                            "?" "[question mark]"})

;;=> "is it possible[question mark] sure[exclamation mark]"

CodePudding user response:

string/escape does exactly what you want:

(string/escape "Hi!" {\! "Exclamation Mark"})
;; => "HiExclamation Mark"

that function replaces characters using a map from character to replacement.

If you want to replace regexes or string you can use reduce-kv in combination with string/replace:

(def replacements
  (array-map
   "!" "Exclamation Mark"
   #"[:/?#\[\]@!$&'()* ,;=]" ""
   #_more_replacements))

(defn replace [s replacements]
  (reduce-kv string/replace s replacements))

So you loop (in order, when replacements is an array-map) over the key and values of the replacements and apply them on the string s using string/replace.

(replace "Hi!" replacements)
;; => "HiExclamation Mark"

if the order of the elements in the array-map is reversed the substitution of the ! with "" (since ! is in the regex) would succeed:

(replace "Hi!" (into (array-map) (reverse replacements)))
;; => "Hi"
  • Related