Concatenate all fields within map - list

In clojure, I want to be able to concatenate all the fields(with a separator) in a map for each map in a list.
For the following result I want to be able to get:
(def h '({:key1 "one" :key2 "descone"} {:key1 "two" :key2 "destwo"}))
The result I want:
({one,descone} {two,destwo})
I did the following but I could not get the correct result
(def h '({:key1 "one" :key2"descone"} {:key1 "two" :key2"destwo"}))
(~#(apply interpose "," (map (juxt :key1 :key2) h))))
And I am getting the following instead:
one,desconetwo,destwo
Edit
The scenario is as follows: We use jdbc to make get all the records from postgres. The records are returned like this: ({:col1 "one" :col2 "descone"} {:col1 "two" :col2 "destwo"}). Now, we need to concatenate all the columns with a separator and have this as primary key and insert it back into a new table in postgres.

I'm assuming you want to return a string since you talk about a comma as a separator. I further assume that when you say "all the fields" you mean "all the values of each key-value pair", and that each value is a string. In that case, the following gives you what you wanted.
user> (str "(" (clojure.string/join " " (map #(str "{" (clojure.string/join "," (vals %)) "}") h)) ")")
"({one,descone} {two,destwo})"
user>
What the code is doing is first taking the values of each map and concatenating them with a comma separator and then enclosing each map with an open and close brace. Then it is taking each such string and concatenating them with a space character and then enclosing the whole result with an open and close paren.
EDIT: To match the edit to the question.
Since you want to generate a primary key value that consists of all the value of a database row, what I'm assuming you want to return is a sequence of strings. Where each string is a concatenation, with a separator, of all the values of each map in a specific order. Assuming that is correct, here is a modified answer.
user> (map #(str "{" (clojure.string/join "," ((juxt :key1 :key2) %)) "}") h)
("{one,descone}" "{two,destwo}")
user>

Related

Why doesn't this keyword function lookup work in a hashmap?

I guess I need some eyeballs on this to make some sense of this
(println record)
(println (keys record) " - " (class record) " : " (:TradeId record) (:Stock record))
(doall (map #(println "Key " % "Value " (% record)) (keys record)))
Output:
{:Stock ATT, :AccountId 1, :TradeId 37, :Qty 100, :Price 117, :Date 2011-02-24T18:30:00.000Z, :Notes SPLIT 1:10, :Type B, :Brokerage 81.12}
(:Stock :AccountId :TradeId :Qty :Price :Date :Notes :Type :Brokerage) - clojure.lang.PersistentHashMap : nil ATT
Key :Stock Value ATT
Key :AccountId Value 1
Key :TradeId Value 37
...
The issue is (:TradeId record) doesn't work even though it exists as a key. Iterating through all keys and values - Line 3 - yields the right value.
Tried renaming the column in the csv but no change in behavior. I see no difference from the other columns (which work) except that this is the first column in the csv.
The hashmap is created from code like this - reading records from a CSV. Standard code from the clojure.data.csv package.
(->> (csv/read-csv reader)
csv-data->maps
(map #(my-function some-args %))
doall))
(defn csv-data->maps
"Return the csv records as a vector of maps"
[csv-data]
(map zipmap
(->> (first csv-data) ;; First row is the header
(map keyword) ;; Drop if you want string keys instead
repeat)
(rest csv-data)))
The "first column" thing is definitely suspicous and points to some invisible characters such as a BOM quietly attaching itself to your first keyword.
To debug try printing out the hex of the names of the keywords. And/or maybe you'll see something if you do a hex dump, e.g., with head -n 2 file.csv | od -x, of the first few lines of the input file.
I would try two things. First, print the type of each key. They should all be clojure.lang.Keyword, if the creation code you included is accurate and my-function preserves their type; but if you created it in some other way and misremembered, you might discover that the key is a symbol, or a string or something like that. In general, don't use println on anything but strings, because it's pretty low-fidelity. prn is better at conveying an accurate picture of your data - it's not perfect, but at least you can tell a string from a keyword with it.
Second, look at the printed values more carefully, e.g. with od -t x1 - or you could do it in process with something like:
(let [k (key (first m)), s (name k)]
(clojure.string/join " "
(for [c s]
(format "%02x" (int c)))))
If the result isn't "53 74 6f 63 6b", then you have some weird characters in your file - maybe nonprinting characters, maybe something that looks like a capital S but isn't, whatever.
Once I reached the point of trying anything, I copied the keyword from the REPL and pasted it into VSCode and sure enough - there was this weird looking character :?Id within the keyword. Using the weird keyword, the lookup worked.
Workaround: Added a dummy column as the first column.
Then things started to click into place, I remembered reading something on BOM in the csv reader project docs. https://github.com/clojure/data.csv#byte-order-mark
Downloaded a hexdump file viewer which confirmed the problem bytes at the start of the file.
o;?Id,AccountId,...
Final solution: Before passing the reader to the data.csv read function, skip over the unwanted bytes.
(.skip reader 1)
The world makes sense again.

Using split on each element of a vector

Basically, I have used slurp to get the contents of a file that is supposed to be a database. I've split the data already once and have a vector that contains all the information correctly. Now I would like to split each element in the vector again. This would give me a vector of vectors. My problem is I can't seem to find the right way to iterate through the vector and make my changes. The changes either don't work or are not stored in the vector.
Using doseq:
(doseq [x tempVector]
(clojure.string/split x #"|")
)
If I add a print statement in the loop it prints everything spaced out with no changes.
What am I doing wrong?
The str/split function returns a new vector of strings, which you need to save. Right now it is being generated and then discarded. You need something like this:
(ns xyz
(:require
[clojure.string :as str]))
(def x "hello there to you")
(def y (str/split x #" ")) ; save result in `y`
(def z (str/split x #"e")) ; save result in `z`
y => ["hello" "there" "to" "you"]
z => ["h" "llo th" "r" " to you"]
You can read clojure basics online here: https://www.braveclojure.com .
I recommend buying the book as it has more stuff than the online version.
If you have several strings in a vector, you can use the map function to split each of them in turn:
(def my-strings
["hello is there anybody in there?"
"just nod if you can hear me"
"is there anyone at home?"])
(def my-strings-split
(mapv #(str/split % #" ") my-strings))
my-strings-split =>
[["hello" "is" "there" "anybody" "in" "there?"]
["just" "nod" "if" "you" "can" "hear" "me"]
["is" "there" "anyone" "at" "home?"]]
To restructure your slurped lines of text into a collection of vectors of words you could do something like:
(use '[clojure.string :as str :only [split]])
(defn file-as-words [filename re]
(let [lines (line-seq (clojure.java.io/reader filename))
line-words (vec (mapv #(str/split %1 re) lines))]
line-words))
Here we define a function which first uses line-seq to slurp the file in and break it into a collection of lines, then we map an anonymous function which invokes clojure.string/split on each line in the initial collection, breaking each line up into a collection of words delimited by the passed-in regular expression. The collection of vectors-of-words is returned.
For example, let's say we have a file named /usr/data/test.dat which contains
Alice,Eating,001
Kitty,Football,006
May,Football,004
If we invoke file-as-words by using
(file-as-words "/usr/data/test.dat" #",")
you get back
[["Alice" "Eating" "001"] ["Kitty" "Football" "006"] ["May" "Football" "004"]]

Pass abstract/null variables into functions Clojure

I have a function that i need to take in a word and im trying to make a variable x the sorted version of word variable. Im not sure how i go about doing this... i am trying to pass it in as a parameter for a function but not working.
How do i pass in a "word" and make a variable within the function equal to a sorted version of "word" so i have two copies, the original word and the x version of word. So i can then go on to pass into a map i need to create.
(for [wordset '("one" "two" "three" "FouR" "wot" "Rheet" "nope" "#")]
(transform2 wordset))
(defn transform2 [word x]
(let [x (sort-string (str/lower-case word))]
(prn x)))
(defn sort-string [s]
(apply str (sort s)))
This is the error im getting back
CompilerException java.lang.RuntimeException: Unable to resolve symbol: x in this context, compiling:(NO_SOURCE_PATH:108:1)
This expression is doing nothing for you:
(for [wordset '("one" "two" "three" "FouR" "wot" "Rheet" "nope" "#")]
(transform2 wordset))
You are not placing this anywhere where the return value will be used. A for is not like an imperative for loop in other languages. It just creates a sequence and returns it, it is not modifying anything.
Also you should use vectors and prefer keywords, in general:
[:one :two :three :whatever]
Though that would change the semantics of what you're doing, so maybe you have strings coming in from somewhere else and need to use them? Otherwise, don't use strings yourself as identifiers: that's what keywords are for.
As for your other questions, it's not quite clear what you mean. You say you'd like to make a variable, but it's not clear what you mean by this, as the code you have isn't doing anything along those lines.
Your transform2 accepts two arguments but you're passing only one. Try removing the x argument like below. Also, you may want to reverse the order you create the functions because they need to be defined before being used. (You may also use declare.)
(defn sort-string [s]
(apply str (sort s)))
(defn transform2 [word]
(let [x (sort-string (clojure.string/lower-case word))]
(prn x)))
(for [wordset '("one" "two" "three" "FouR" "wot" "Rheet" "nope" "#")]
(transform2 wordset))
Result will be:
"eno"
"otw"
"eehrt"
"foru"
"otw"
"eehrt"
"enop"
"#"

clojure: how to get values from lazy seq?

Iam new to clojure and need some help to get a value out of a lazy sequence.
You can have a look at my full data structure here: http://pastebin.com/ynLJaLaP
What I need is the content of the title:
{: _content AlbumTitel2}
I managed to get a list of all _content values:
(def albumtitle (map #(str (get % :title)) photosets))
(println albumtitle)
and the result is:
({:_content AlbumTitel2} {:_content test} {:_content AlbumTitel} {:_content album123} {:_content speciale} {:_content neues B5 Album} {:_content Album Nr 2})
But how can I get the value of every :_content?
Any help would be appreciated!
Thanks!
You could simply do this
(map (comp :_content :title) photosets)
Keywords work as functions, so the composition with comp will first retrieve the :title value of each photoset and then further retrieve the :_content value of that value.
Alternatively this could be written as
(map #(get-in % [:title :_content]) photosets)
A semi alternative solution is to do
(->> data
(map :title)
(map :_content))
This take advances of the fact that keywords are functions and the so called thread last macro. What it does is injecting the result of the first expression in as the last argument of the second etc..
The above code gets converted to
(map :_content (map :title data))
Clearly not as readable, and not easy to expand later either.
PS I asume something went wrong when the data was pasted to the web, because:
{: _content AlbumTitel2}
Is not Clojure syntax, this however is:
{:_content "AlbumTitel2"}
No the whitespace after :, and "" around text. Just in case you might want to paste some Clojure some other time.

Convert map keys and values to string array

How do I convert a clojure map into string, almost key value pair, as shown below:
Clojure data:
(def data { :starks "Winter is coming" :Lannisters "Hear me roar" })
I want to convert the above to
"starks" "winter is coming" "Lannisters" "hear me roar"
I don't want any identifiers / delimiters between but obviously "starks" should always be followed by "winter is coming"
I tried this:
(str (keys data) (vals data))
Which outputs this:
"(:starks :Lannisters)(\"Winter is coming\" \"Hear me roar\")"
Which is not what I want at all...
The map data keys and values are not always the same so it needs to be generic
there will always be just one level, as in, the value will not contain a nested map etc..
Edit
What I'm actually trying to do:
I am trying to index a few thousand Neo4j nodes with clojure. To help me with this task, I am using Neocons Clojure neo4j library.
According to the documentation, the add-to-index accepts properties and values like so:
(nn/add-to-index (:id node) (:name idx) "username" "joe")))
which is, in my above case, going to look like
(nn/add-to-index (:id node) (:name idx) "starks" "winter is coming" "Lannisters" "Hear me roar")))
now, I have my Node, I can access the node properties with (:data node) and that gives me a clojure map.
The property differs pretty much from node to node so I'm trying to figure out how to pass that map to the library in the way that it understands..
Marius Danila's answer got me almost there.
Doing
(map name (apply concat data))
still complains of the third parameter, as it has the braces around the result.
So, how can I achieve this?
Do I just have to write a whole lot of if-not blocks to construct the properties myself?
Thanks
This should do the trick:
(map name (apply concat data))
A map can be viewed as a sequence of key-value pairs, which in turn represented as arrays of 2 elements. We're concatenating the pairs and then we extract the name from the keywords and strings (for string this does nothing, for keywords it returns the bit without ':').
From the code you've posted, I'm guessing you would use this like so:
(apply nn/add-to-index (list* (:id node) (:name idx) (map name (apply concat data))))
The (nn/add-to-index ...) function simply accepts only four arguments. The node, index and one key/value pair. So you have too loop through your data like.
(doseq [[k v] data]
(nn/add-to-index (:id node) (:name idx) (name k) (clojure.string/lower-case v))))
Unlike the the str function in Clojure the add-to-index function is more limited and simply does not accept variable parameter lists.
You can use vector to have array like random access:
=> (def v (vec (map name (apply concat data))))
=> (v 0)
;"starks"
=> (v 1)
;"Winter is coming"
You could try the following:
=> (interleave (map name (keys data)) (vals data))
;; which returns ("starks" "Winter is coming" "Lannisters" "Hear me roar")