How to convert string into an array in clojure - clojure

I want to convert the string into an array,
I have tried some steps but not getting the desired result.
(:require [clojure.string :as str])
(def stringval "fruit==Mango,fruit==Papaya;veggie==Onion,veggie==Potato")
(defn formatting [strFilters]
(let [filters (str/split strFilters #";")]
(for [filter filters]
(let [eachFilter (str/split filter #",")]
(for [each eachFilter]
(let [items (str/split each #"==")]
items
))))))
(formatting stringval)
I am getting below output
((["fruit" "Mango"] ["fruit" "Papaya"]) (["veggie" "Onion"] ["veggie" "Potato"]))
I want clojure function which returns the below array
Array
(
[fruit] => Array
(
[0] => Mango
[1] => Papaya
)
[veggie] => Array
(
[0] => Onion
[1] => Potato
)
)

You want a list of maps, so you have to turn your current intermediate
results into a map. You can do this with group-by and some some
post-processing, or you can use merge-with conj if you shape the
result from the innermost for in preparation for it. Also note, that
for can have :let in it.
(require '[clojure.string :as str])
(def s "fruit==Mango,fruit==Papaya;veggie==Onion,veggie==Potato")
(for [g (str/split s #";")]
(apply merge-with into
(for [kv (str/split g #",")
:let [[k v] (str/split kv #"==")]]
{k [v]})))
; → ({"fruit" ["Mango" "Papaya"]} {"veggie" ["Onion" "Potato"]})
And in case your target-output there is from PHP or some other language,
that got their basic data structures wrong, and you actually just want
a map with the keys to arrays of values, you just have to to shift the
merge-with into out and you can also split for ; and , one swoop.
(apply merge-with into
(for [kv (str/split s #"[;,]")
:let [[k v] (str/split kv #"==")]]
{k [v]}))
; → {"fruit" ["Mango" "Papaya"], "veggie" ["Onion" "Potato"]}

one more option is to get all the pairs with re-seq and reduce it with grouping:
(->> stringval
(re-seq #"([^,;].+?)==([^,;$]+)")
(reduce (fn [acc [_ k v]] (update acc k conj v)) {}))
;;=> {"fruit" ("Papaya" "Mango"), "veggie" ("Potato" "Onion")}

Related

How do I conditionally update keys and values in a Clojure map based on the key name?

I have a map and the keys are strings. If the key contains the word "kg" I want to multiply the value by 2.2 and then replace "kg" with "lb" in the key. I can't figure out how to iterate over the map in a way that I can conditionally update it.
Example map:
{"id" ("7215" "74777" "7219"),
"weight-kg" ("150" "220" "530"),
"time-seconds" ("1900" "2" "770")}
Desired output
{"id" ("7215" "74777" "7219"),
"weight-lb" ("330" "485" "1168"),
"time-seconds" ("1900" "2" "770")}
I've tried update, for map and reduce-kv. Project requirement is to not use the string library, which is why there is re-find. These are only attempts at changing the values. Since I can't change the values, I haven't attempted changing the keys.
(defn kg->lb [m k]
(if (re-find #"kg" k)
(map #(update m % * 2.2))))
(defn kg2->lb2 [m]
(reduce-kv #(if (re-find #"kg" %)
(update % * 2.2)) {} m)
(map #(if (re-find #"kg" %)
(update % * 2.2)) m)
(for [k (keys m)]
(if (re-find #"kg" k)
(update m k #(* % 2.2))))
Data:
(def data {"id" ["7215" "74777" "7219"],
"weight-kg" ["150" "220" "530"],
"time-seconds" ["1900" "2" "770"]})
Helper function to convert a string (kg amount) to string (lb amount):
(defn kg->lb [kg-string]
(-> kg-string
parse-long
(* 2.2)
int
str))
The most important function is reduce-kv.
If you find "kg" in key, you will replace that with "lb" and map helper function over all values.
If you don't find "kg" in key, you will just assoc that entry without change.
(reduce-kv (fn [m k v]
(if (re-find #"kg" k)
(assoc m (str/replace k #"kg" "lb")
(map kg->lb v))
(assoc m k v)))
{}
data)
I think I passed Project requirement is to not use the string library, except for (str/replace k #"kg" "lb"), which you can replace with String/replace interop: (.replace k "kg" "lb").
EDIT: Solution with map and into:
(defn update-entry [[k v]]
(if (re-find #"kg" k)
[(.replace k "kg" "lb") (map kg->lb v)]
[k v]))
(->> data
(map update-entry)
(into {}))
Transducer version:
(into {} (map update-entry) data)
I would do it a bit differently. First, I'd convert the strings to integers using the Java function Long/parseLong:
(ns tst.demo.core
(:use demo.core tupelo.core tupelo.test)
(:require
[tupelo.core :as t]
))
(defn parse-vec-longs
[v]
(mapv #(Long/parseLong %) v))
(verify
(is= (parse-vec-longs ["150" "220" "530"])
[150 220 530]))
Then I would write the code to convert the kg vals to lb vals. At the end, just use dissoc to get rid of the kg data, then assoc to add in the new lb data:
(defn convert-kg-lb
[data]
(let-spy-pretty [kg-vals (get data "weight-kg")
lb-vals (mapv #(Math/round (* 2.2 %)) kg-vals)
result (t/it-> data
(dissoc it "weight-kg")
(assoc it "weight-lb" lb-vals))]
result))
(verify
(let [data-str {"id" ["7215" "74777" "7219"]
"weight-kg" ["150" "220" "530"]
"time-seconds" ["1900" "2" "770"]}
data-parsed (t/map-vals data-str #(parse-vec-longs %))
expected-parsed {"id" [7215 74777 7219]
"weight-kg" [150 220 530]
"time-seconds" [1900 2 770]}
expected-out {"id" [7215 74777 7219]
"weight-lb" [330 484 1166]
"time-seconds" [1900 2 770]}
result (convert-kg-lb data-parsed)]
(is= data-parsed expected-parsed)
(is= (spyx-pretty result) (spyx-pretty expected-out))))
Normally, you'd also replace all the string keys with keywords as well, so "lb" => :lb.

How to split string in clojure on number and convert it to map

I have a string school_name_1_class_2_city_name_3 want to split it to {school_name: 1, class:2, city_name: 3} in clojure I tried this code which didn't work
(def s "key_name_1_key_name_2")
(->> s
(re-seq #"(\w+)_(\d+)_")
(map (fn [[_ k v]] [(keyword k) (Integer/parseInt v)]))
(into {}))
You are looking for the ungreedy version of regex.
Try using #"(\w+?)_(\d+)_?" instead.
user=> (->> s (re-seq #"(\w+?)_(\d+)_?"))
(["key_name_1_" "key_name" "1"] ["key_name_2" "key_name" "2"])
When faced with a problem, just break it down and solve one small step at a time. Using let-spy-pretty from the Tupelo library allows us to see each step of the transformation:
(ns tst.demo.core
(:use tupelo.core tupelo.test)
(:require [clojure.string :as str]))
(defn str->keymap
[s]
(let-spy-pretty
[str1 (re-seq #"([a-zA-Z_]+|[0-9]+)" s)
seq1 (mapv first str1)
seq2 (mapv #(str/replace % #"^_+" "") seq1)
seq3 (mapv #(str/replace % #"_+$" "") seq2)
map1 (apply hash-map seq3)
map2 (tupelo.core/map-keys map1 #(keyword %) )
map3 (tupelo.core/map-vals map2 #(Long/parseLong %) )]
map3))
(dotest
(is= (str->keymap "school_name_1_class_2_city_name_3")
{:city_name 3, :class 2, :school_name 1}))
with result
------------------------------------
Clojure 1.10.3 Java 11.0.11
------------------------------------
Testing tst.demo.core
str1 =>
(["school_name_" "school_name_"]
["1" "1"]
["_class_" "_class_"]
["2" "2"]
["_city_name_" "_city_name_"]
["3" "3"])
seq1 =>
["school_name_" "1" "_class_" "2" "_city_name_" "3"]
seq2 =>
["school_name_" "1" "class_" "2" "city_name_" "3"]
seq3 =>
["school_name" "1" "class" "2" "city_name" "3"]
map1 =>
{"city_name" "3", "class" "2", "school_name" "1"}
map2 =>
{:city_name "3", :class "2", :school_name "1"}
map3 =>
{:city_name 3, :class 2, :school_name 1}
Ran 2 tests containing 1 assertions.
0 failures, 0 errors.
Passed all tests
Once you understand the steps and everything is working, just replace let-spy-pretty with let and continue on!
This was build using my favorite template project.
Given
(require '[clojure.string :as str])
(def s "school_name_1_class_2_city_name_3")
Following the accepted answer:
(->> s (re-seq #"(.*?)_(\d+)_?")
(map rest) ;; take only the rest of each element
(map (fn [[k v]] [k (Integer. v)])) ;; transform second as integer
(into {})) ;; make a hash-map out of all this
Or:
(apply hash-map ;; the entire thing as a hash-map
(interleave (str/split s #"_(\d+)(_|$)") ;; capture the names
(map #(Integer. (second %)) ;; convert to int
(re-seq #"(?<=_)(\d+)(?=(_|$))" s)))) ;; capture the integers
or:
(zipmap
(str/split s #"_(\d+)(_|$)") ;; extract names
(->> (re-seq #"_(\d+)(_|$)" s) ;; extract values
(map second) ;; by taking only second matched groups
(map #(Integer. %)))) ;; and converting them to integers
str/split leaves out the matched parts
re-seq returns only the matched parts
(_|$) ensures that the number is followed by a _ or is at an end position
The least verbose (where (_|$) could be replaced by _?:
(->> (re-seq #"(.*?)_(\d+)(_|$)" s) ;; capture key vals
(map (fn [[_ k v]] [k (Integer. v)])) ;; reorder coercing values to int
(into {})) ;; to hash-map

Using clojure, Is there a better way to to remove a item from a sequence, which is the value in a map?

There is a map containing sequences. The sequences contain items.
I want to remove a given item from any sequence that contains it.
The solution I found does what it should, but I wonder if there is a better
or more elegant way to achieve the same.
my current solution:
(defn remove-item-from-map-value [my-map item]
(apply merge (for [[k v] my-map] {k (remove #(= item %) v)})))
The test describe the expected behaviour:
(require '[clojure.test :as t])
(def my-map {:keyOne ["itemOne"]
:keyTwo ["itemTwo" "itemThree"]
:keyThree ["itemFour" "itemFive" "itemSix"]})
(defn remove-item-from-map-value [my-map item]
(apply merge (for [[k v] my-map] {k (remove #(= item %) v)})))
(t/is (= (remove-item-from-map-value my-map "unknown-item") my-map))
(t/is (= (remove-item-from-map-value my-map "itemFive") {:keyOne ["itemOne"]
:keyTwo ["itemTwo" "itemThree"]
:keyThree ["itemFour" "itemSix"]}))
(t/is (= (remove-item-from-map-value my-map "itemThree") {:keyOne ["itemOne"]
:keyTwo ["itemTwo"]
:keyThree ["itemFour" "itemFive" "itemSix"]}))
(t/is (= (remove-item-from-map-value my-map "itemOne") {:keyOne []
:keyTwo ["itemTwo" "itemThree"]
:keyThree ["itemFour" "itemFive" "itemSix"]}))
I'm fairly new to clojure and am interested in different solutions.
So any input is welcome.
I throw in the specter
version for good measure. It keeps the vectors inside the map
and it's really compact.
(setval [MAP-VALS ALL #{"itemFive"}] NONE my-map)
Example
user=> (use 'com.rpl.specter)
nil
user=> (def my-map {:keyOne ["itemOne"]
#_=> :keyTwo ["itemTwo" "itemThree"]
#_=> :keyThree ["itemFour" "itemFive" "itemSix"]})
#_=>
#'user/my-map
user=> (setval [MAP-VALS ALL #{"itemFive"}] NONE my-map)
{:keyOne ["itemOne"],
:keyThree ["itemFour" "itemSix"],
:keyTwo ["itemTwo" "itemThree"]}
user=> (setval [MAP-VALS ALL #{"unknown"}] NONE my-map)
{:keyOne ["itemOne"],
:keyThree ["itemFour" "itemFive" "itemSix"],
:keyTwo ["itemTwo" "itemThree"]}
i would go with something like this:
user> (defn remove-item [my-map item]
(into {}
(map (fn [[k v]] [k (remove #{item} v)]))
my-map))
#'user/remove-item
user> (remove-item my-map "itemFour")
;;=> {:keyOne ("itemOne"),
;; :keyTwo ("itemTwo" "itemThree"),
;; :keyThree ("itemFive" "itemSix")}
you could also make up a handy function map-val performing mapping on map values:
(defn map-val [f data]
(reduce-kv
(fn [acc k v] (assoc acc k (f v)))
{} data))
or shortly like this:
(defn map-val [f data]
(reduce #(update % %2 f) data (keys data)))
user> (map-val inc {:a 1 :b 2})
;;=> {:a 2, :b 3}
(defn remove-item [my-map item]
(map-val (partial remove #{item}) my-map))
user> (remove-item my-map "itemFour")
;;=> {:keyOne ("itemOne"),
;; :keyTwo ("itemTwo" "itemThree"),
;; :keyThree ("itemFive" "itemSix")}
I think your solution is mostly okay, but I would try to avoid the apply merge part, as you can easily recreate a map from a sequence with into. Also, you could also use map instead of for which I think is a little bit more idiomatic in this case as you don't use any of the list comprehension features of for.
(defn remove-item-from-map-value [m item]
(->> m
(map (fn [[k vs]]
{k (remove #(= item %) vs)}))
(into {})))
Another solution much like #leetwinski:
(defn remove-item [m i]
(zipmap (keys m)
(map (fn [v] (remove #(= % i) v))
(vals m))))
Here's a one-liner which does this in an elegant way. The perfect function for me to use in this scenario is clojure.walk/prewalk. What this fn does is it traverse all of the sub-forms of the form that you pass to it and it transforms them with the provided fn:
(defn remove-item-from-map-value [data item]
(clojure.walk/prewalk #(if (map-entry? %) [(first %) (remove #{item} (second %))] %) data))
What the remove-item-from-map-value fn will do is it will check if current form is a map entry and if so, it will remove specified key from its value (second element of the map entry, which is a vector containing a key and a value, respectively).
The best this about this approach is that is is completely extendable: you could decide to do different things for different types of forms, you can also handle nested forms, etc.
It took me some time to master this fn but once I got it I found it extremely useful!

change all keywords in collection, removing dots from the namespace and replacing with a dash

How can I map a function over a vector of maps (which also contain vectors of maps) to remove all dots from keyword namespaces?
So, given:
[{:my.dotted/namespace "FOO"}
{:my.nested/vec [{:another.dotted/namespace "BAR"
:and.another/one "FIZ"}]}]
becomes:
[{:my-dotted/namespace "FOO"}
{:my-nested/vec [{:another-dotted/namespace "BAR"
:and-another/one "FIZ"}]}]
Sounds like a job for clojure.walk!
You can traverse the entire data structure and apply a transforming function (transform-map in my version) to all the sub-forms that switches a map's keys (here, with dotted->dashed) when it encounters one.
(require '[clojure
[walk :as walk]
[string :as str]])
(defn remove-dots-from-keys
[data]
(let [dotted->dashed #(-> % str (str/replace "." "-") (subs 1) keyword)
transform-map (fn [form]
(if (map? form)
(reduce-kv (fn [acc k v] (assoc acc (dotted->dashed k) v)) {} form)
form))]
(walk/postwalk transform-map data)))
I'm partial to clojure.walk for these sort of jobs. The basic idea is to create functions that perform the replacement you want if given a value that should be replaced, else returns the argument. Then you hand that function and a structure to postwalk (or prewalk) and it walks the data structure for you, replacing each value with the return value of the function on it.
(ns replace-keywords
(:require [clojure.walk :refer [postwalk]]
[clojure.string :refer [join]]))
(defn dash-keyword [k]
(when (keyword? k)
(->> k
str
(map (some-fn {\. \-} identity))
rest
join
keyword)))
(dash-keyword :foo.bar/baz)
;; => :foo-bar/baz
(defonce nested [ {:my-dotted/namespace "FOO"}
{:my-nested/vec [ {:another-dotted/namespace "BAR"
:and-another/one "FIZ"} ]}])
(postwalk (some-fn dash-keyword identity) nested)
;; =>[{:my-dotted/namespace "FOO"}
;; {:my-nested/vec [{:another-dotted/namespace "BAR",
;; :and-another/one "FIZ"}]}]
Twice here I use the combination of some-fn with a function that returns a replacement or nil, which can be a nice way to combine several "replacement rules" - if none of the earlier ones fire then identity will be the first to return a non-nil value and the argument won't be changed.
This problem can also be solved without clojure.walk:
(require '[clojure.string :as str])
(defn dot->dash [maps]
(mapv #(into {} (for [[k v] %]
[(keyword (str/replace (namespace k) "." "-") (name k))
(if (vector? v) (dot->dash v) v)]))
maps))
Example:
(dot->dash [{:my.dotted/namespace "FOO"}
{:my.nested/vec [{:another.dotted/namespace "BAR"
:and.another/one "FIZ"}]}])
;=> [{:my-dotted/namespace "FOO"}
; {:my-nested/vec [{:another-dotted/namespace "BAR"
; :and-another/one "FIZ"}]}]

changing nested map value without knowing keys

I need to change a value in a nested map where I don't know the values of keys in advance. I have come up with the following to do that.
;; input {String {String [String]}}
;; output {String {String String}}
(defn join-z
[x-to-y-to-z]
(zipmap (keys x-to-y-to-z)
(map (fn [y-to-z] (into {} (map (fn [[y z]] {y (clojure.string/join z)})
(seq y-to-z))))
(seq (vals x-to-y-to-z)))))
(def example
{"a" {"b" ["c" "d" "e"]}
"m" {"n" ["o" "p"]}})
;; (join-z example) => {"m" {"n" "op"}, "a" {"b" "cde"}}
This seems to be a hack. What is idiomatic clojure to do this? Or, is there something like Haskell's lens library to use?
UPDATE: based on user5187212 answer
(defn update-vals [f m0]
(reduce-kv (fn [m k v] (assoc m k (f v)))
{}
m0))
;; (update-vals clojure.string/join {"b" ["c" "d" "e"]}) => {"b" "cde"}
(defn join-z [x-to-y-to-z]
(update-vals (partial update-vals clojure.string/join) x-to-y-to-z))
;; (join-z example) => {"m" {"n" "op"}, "a" {"b" "cde"}}
This seems much more elegant. Thanks!
I would suggest reduce-kv.
For the last layer you can use something like:
(defn foo [x]
(reduce-kv
(fn [m k v]
(assoc m k (clojure.string/join v)))
{}
x))
then call it as many times as you need...
(reduce-kv
(fn [m k v]
(assoc m k (foo v)))
{}
example)
An other approach could be over all nested keys and then
(reduce
(fn [m ks]
(update-in m ks clojure.string/join))
example
all-nested-keys)
The short answer is yes, that is how you do it :)
I would go for something more like this:
(into {} (for [[k v] example]
[k (into {} (for [[k2 v2] v]
[k2 (string/join v2)]))]))
Which is pretty much the same thing.
There is a library called Specter
https://github.com/nathanmarz/specter
for queries and transformations:
(ns specter.core
(:require
[clojure.string :as string]
[com.rpl.specter :as s]))
(def example
{"a" {"b" ["c" "d" "e"]}
"m" {"n" ["o" "p"]}})
(s/transform
[s/ALL s/LAST s/ALL s/LAST]
string/join
example)
Which I think is a pretty neat way to express it.