In clojure, how to write a function that applies several string replacements? - clojure

I would like to write a function replace-several that receives a string and a set of replacements and apply all the replacements (where the replacements see the result of the previous replacements).
I thought about the following interface:
(replace-several "abc" #"a" "c"
#"b" "l"
#"c" "j"); should return "jlj"
Two questions:
Is it the most idiomatic interface in clojure?
How to implement this function?
Remark: To make a single replacement, there is replace available in clojure.string.

Implementing #kotarak's advice using replace, reduce and partition:
(defn replace-several [content & replacements]
(let [replacement-list (partition 2 replacements)]
(reduce #(apply string/replace %1 %2) content replacement-list)))
; => (replace-several "abc" #"a" "c" #"b" "l" #"c" "j")
"jlj"

So you have replace, reduce and partition. From these building blocks you can build your replace-several.

Here is another shot but that has different output results, this one uses the regex engine features so it potentially may be faster, also the interface is different, since it maps keys to replacement strings. I provide this in case it may be useful to someone with a similar question.
(defn replace-map
"given an input string and a hash-map, returns a new string with all
keys in map found in input replaced with the value of the key"
[s m]
(clojure.string/replace s
(re-pattern (apply str (interpose "|" (map #(java.util.regex.Pattern/quote %) (keys m)))))
m))
So usage would be like so:
(replace-map "abc" {"a" "c" "b" "l" "c" "j"})
=> "clj"

I'm late to this party, but for what it's worth, I think the most idiomatic way to do this would be to use threading and multiple replacements:
(require '[clojure.string :refer [replace])
(-> "abc"
(replace #"a" "c")
(replace #"b" "l")
(replace #"c" "j"))
;=> "jlj"
The meaning of this is quite clear, though it is nice to avoid typing the "replace" multiple times.

(str/escape "abc" {\a "c" \b "l" \c "j"})
; => "clj"
Docs escape

You can use reduce with replace:
(defn replace-several
[str & replacements]
(reduce (fn [s [a b]]
(clojure.string/replace s a b))
str
(partition 2 replacements)))
(replace-several "abc"
#"a" "c"
#"b" "l"
#"c" "j")

Personally I wouldn't create a separate function for this, as it is just a composition of existing Clojure functions:
(reduce-kv clojure.string/replace "Hello" {#"[A-Z]" "J", "o" "y"}
;=> "Jelly"
Of course if you want varargs and an interface then:
Yes it seems reasonably idiomatic
I'd implement it thus:
(defn replace-many [string & {:as rplcmnts}]
(reduce-kv clojure.string/replace string rplcmnts))

first guess..
(defn replace-several [string & mappings]
(loop [grouped-mappings (partition 2 mappings) string string]
(if (empty? grouped-mappings)
string
(let [[re rep] (first grouped-mappings)]
(recur (rest grouped-mappings) (clojure.string/replace string re rep))))))
; => (replace-several "abc" #"a" "c" #"b" "l" #"c" "j")
"jlj"

Related

Split string in Clojure

I'm still new in Clojure; I'm trying to split the values parsed from CSV file but without using clojure.string/split lib or any other lib just clojure.core, please some help, thanks in advance.
you can accomplish the same result with re-seq and string/split
user> (clojure.string/split "a,b,c,d,e" #",")
["a" "b" "c" "d" "e"]
user> (re-seq #"[^,]+" "a,b,c,d,e")
("a" "b" "c" "d" "e")
Both of these are available with no dependencies so there is little reason not to use string/split in many cases.
parsing CSV is also a good choice if you are willing to add a dependency:
user> (require '[clojure.data.csv :as csv])
nil
user> (csv/read-csv "A,B,C\n1,2,3\n4,5,5")
(["A" "B" "C"] ["1" "2" "3"] ["4" "5" "5"])
If your hands were tied and couldn't use clojure.string or clojure.data.csv or re-seq or interop:
(defn comma-separate [s]
(->> s
(partition-by #{\,})
(take-nth 2)
(map #(apply str %))))
(comma-separate "foo,bar") ;; ("foo" "bar")
I agree that you should use clojure.string or clojure.data.csv

Clojure "first" returns (for me) unexpeted result?

(first ["a" "b" "c"])
->
"c"
where I would expect:
(first ["a" "b" "c"])
->
"a"
I think I must have misunderstood something here ,any help appreciated!
Best Regards.
(defn binnd-to-name [name-string to-bind]
(bind-to-name name-string to-bind))
(defmacro bind-to-name [name-string stuff-to-bind]
`(def ~(symbol name-string) ~stuff-to-bind))
(defn bind-services [list-of-services]
(if (empty? list-of-services)
nil
(do
(binnd-to-name (first (first list-of-services)) (last (first list-of-services)))
(bind-services (rest list-of-services)))))
(bind-services [["*my-service*" se.foo.bar.service.ExampleService]])
ExampleService is a Java class on the classpath, which I want to bind to the symbol my-service.
The idea is to loop through a list of name-value pairs and bind each name to the value.
It is not working as expected though.
So somehow in this code something evaluated into "def first last" apparently.
Problem is with your macros not expanding as you expect
(defmacro bind-to-name [name-string stuff-to-bind]
`(def ~(symbol name-string) ~stuff-to-bind))
(defmacro bind-services [services]
`(do
~#(for [s services]
`(bind-to-name ~(first s) ~(second s)))))
(bind-services [["*my-service*" se.foo.bar.service.ExampleService]])
If you try this approach your def symbol sequence will properly expand.
No way!
user=> (doc first)
-------------------------
clojure.core/first
([coll])
Returns the first item in the collection. Calls seq on its
argument. If coll is nil, returns nil.
user=> (first ["a" "b" "c"])
"a"

idiomatic clojure prefix replacement

I have a map representing information about a subversion commit.
Example contents:
(def commit
{:repository "/var/group1/project1"
:revision-number "1234"
:author "toolkit"
etc..}
I would like to change the repository based on a prefix match, so that:
/var/group1 maps to http://foo/group1
/var/group2 maps to http://bar/group2
I have created 2 functions like:
(defn replace-fn [prefix replacement]
(fn [str]
(if (.startsWith str prefix)
(.replaceFirst str prefix replacement)
str)))
(def replace-group1 (replace-fn "/var/group1" "http://foo/group1"))
(def replace-group2 (replace-fn "/var/group2" "http://bar/group2"))
And now I have to apply them:
(defn fix-repository [{:keys [repository] :as commit}]
(assoc commit :repository
(replace-group1
(replace-group2 repository))))
But this means I have to add an extra wrapper in my fix-repository for each new replacement.
I would like to simply:
Given a commit map
Extract the :repository value
Loop through a list of replacement prefixes
If any prefix matches, replace :repository value with the new string
Otherwise, leave the :repository value alone.
I can't seem to build the right loop, reduce, or other solution to this.
You can use function composition:
(def commit
{:repository "/var/group2/project1"
:revision-number "1234"
:author "toolkit"})
(defn replace-fn [prefix replacement]
(fn [str]
(if (.startsWith str prefix)
(.replaceFirst str prefix replacement)
str)))
(def replacements
(comp (replace-fn "/var/group1" "http://foo/group1")
(replace-fn "/var/group2" "http://foo/group2")))
(defn fix-repository [commit replacements]
(update-in commit [:repository] replacements))
(fix-repository commit replacements)
How about something like this?
(defn replace-any-prefix [replacements-list string]
(or (first
(filter identity
(map (fn [[p r]]
(when (.startsWith string p)
(.replaceFirst string p r)))
replacements-list)))
string)))
(update-in commit
[:repository]
(partial replace-any-prefix
[["/var/group1" "http://foo/group1"]
["/var/group2" "http:/foo/group2"]]))
Documentation for update-in: http://clojuredocs.org/clojure_core/clojure.core/update-in

Serialize an input-map into string

I am trying to write a generic serilization function in clojure. Something Like this
(def input-map {:Name "Ashwani" :Title "Dev"})
(defn serialize [input-map delimiter]
...rest of the code
)
Which when called
(serialize input-map ",") Produces
Ashwani,Dev
I have some thing as of now which needs specific keys of the map but does this
(defn serialize [input-map]
(map #(str (% :Name) "," (% :Title) "\n") input-map ) )
What I want to avoid is the hardcoding Name and title there. There must be some way to use reflection or something to accomplish this but unfortunately I dont know enough clojure to get this done.
(defn serialize [m sep] (apply str (concat (interpose sep (vals m)) ["\n"])))
Give this a shot:
(require 'clojure.string)
(defn serialize [m sep] (str (clojure.string/join sep (map (fn [[_ v]] v) m)) "\n"))
(def input-map {:Name "Ashwani" :Title "Dev"})
(serialize input-map ",")
yields
"Ashwani,Dev\n"
Not sure how idiomatic this is, but it should work for you.
Update: Julien's answer is way nicer than mine! vals ... how could I miss that :)
It is quit simple.
(str input-map)
"Normal" clojure types can be serialized using pr-str and re-instated using read-string. Unless you've got a reason to format your serialized data in the specific way you described, I'd suggest using pr-str instead if only because its output is more readable.

Clojure Parse String

I have the following string
layout: default
title: Envy Labs
What i am trying to do is create map from it
layout->default
title->"envy labs"
Is this possible to do using sequence functions or do i have to loop through each line?
Trying to get a regex to work with and failing using.
(apply hash-map (re-split #": " meta-info))
user> (let [x "layout: default\ntitle: Envy Labs"]
(reduce (fn [h [_ k v]] (assoc h k v))
{}
(re-seq #"([^:]+): (.+)(\n|$)" x)))
{"title" "Envy Labs", "layout" "default"}
The _ is a variable name used to indicate that you don't care about the value of the variable (in this case, the whole matched string).
I'd recommend using clojure-contrib/duck-streams/read-lines to process the lines then split the fields from there. I find this method is usually more robust to errors in the file.