How can I improve this Clojure function? - clojure

I just wrote my first Clojure function based on my very limited knowledge of the language. I would love some feedback in regards to performance and use of types. For example, I'm not sure
if I should be using lists or vectors.
(defn actor-ids-for-subject-id [subject-id]
(sql/with-connection (System/getenv "DATABASE_URL")
(sql/with-query-results results
["SELECT actor_id FROM entries WHERE subject_id = ?" subject-id]
(let [res (into [] results)]
(map (fn [row] (get row :actor_id)) res)))))
It passes the following test (given proper seed data):
(deftest test-actor-ids-for-subject-id
(is (= ["123" "321"] (actor-ids-for-subject-id "123"))))
If it makes a difference (and I imagine it does) my usage characteristics of the returned data will almost exclusively involve generating the union and intersection of another set returned by the same function.

it's slightly more concise to use 'vec' instead of 'into' when the initial vector is empty. it may express the intent more clearly, though that's more a matter of preference.
(vec (map :actor_id results))

the results is a clojure.lang.Cons, is lazy sequence, return by clojure.java.jdbc/resultset-seq. each record is a map:
(defn actor-ids-for-subject-id [subject-id]
(sql/with-connection (System/getenv "DATABASE_URL")
(sql/with-query-results results
["SELECT actor_id FROM entries WHERE subject_id = ?" subject-id]
(into [] (map :actor_id results)))))

Related

Map over first element of list of vectors

How can I map a function over just the first elements of vectors in a list?
So I have
(["1" "sometexthere" ...]["2" "somemoretext" ...] ....)
I need to use read-string to convert the stringy numbers into ints (or longs).
If you want just the list of results, you can combine the function with first and map it, as #leetwinski recommended in the comments.
(map #(clojure.edn/read-string (first %)) items)
If you want to get back the structure you had, but with those particular elements mapped by the function, update and update-in are your friends:
(map #(update % 0 clojure.edn/read-string) items)
For more involved transformations you may also be interested in specter's transform.
You can use comp to compose functions:
(require '[clojure.edn :as edn])
(def items [["1" "sometexthere" ,,,] ["2" "somemoretext" ,,,] ,,,])
(map (comp edn/read-string first) items)
;=> (1 2 ,,,)
I like the comp solution by Elogent, however I think for readability I prefer the use of a threading macro:
(map #(-> % first clojure.edn/read-string) items)
To each his/her own, just my personal preference.

use 'for' inside 'let' return a list of hash-map

Sorry for the bad title 'cause I don't know how to describe in 10 words. Here's the detail:
I'd like to loop a file in format like:
a:1 b:2...
I want to loop each line, collect all 'k:v' into a hash-map.
{ a 1, b 2...}
I initialize a hash-map in a 'let' form, then loop all lines with 'for' inside let form.
In each loop step, I use 'assoc' to update the original hash-map.
(let [myhash {}]
(for [line #{"A:1 B:2" "C:3 D:4"}
:let [pairs (clojure.string/split line #"\s")]]
(for [[k v] (map #(clojure.string/split %1 #":") pairs)]
(assoc myhash k (Float. v)))))
But in the end I got a lazy-seq of hash-map, like this:
{ {a 1, b 2...} {x 98 y 99 z 100 ...} }
I know how to 'merge' the result now, but still don't understand why 'for' inside 'let' return
a list of result.
What I'm confused is: does the 'myhash' in the inner 'for' refers to the 'myhash' declared in the 'let' form every time? If I do want a list of hash-map like the output, is this the idiomatic way in Clojure ?
Clojure "for" is a list comprehension, so it creates list. It is NOT a for loop.
Also, you seem to be trying to modify the myhash, but Clojure's datastructures are immutable.
The way I would approach the problem is to try to create a list of pair like (["a" 1] ["b" 2] ..) and the use the (into {} the-list-of-pairs)
If the file format is really as simple as you're describing, then something much more simple should suffice:
(apply hash-map (re-seq #"\w+" (slurp "your-file.txt")))
I think it's more readable if you use the ->> threading macro:
(->> "your-file.txt" slurp (re-seq #"\w+") (apply hash-map))
The slurp function reads an entire file into a string. The re-seq function will just return a sequence of all the words in your file (basically the same as splitting on spaces and colons in this case). Now you have a sequence of alternating key-value pairs, which is exactly what hash-map expects...
I know this doesn't really answer your question, but you did ask about more idiomatic solutions.
I think #dAni is right, and you're confused about some fundamental concepts of Clojure (e.g. the immutable collections). I'd recommend working through some of the exercises on 4Clojure as a fun way to get more familiar with the language. Each time you solve a problem, you can compare your own solution to others' solutions and see other (possibly more idomatic) ways to solve the problem.
Sorry, I didn't read your code very thorougly last night when I was posting my answer. I just realized you actually convert the values to Floats. Here are a few options.
1) partition the sequence of inputs into key/val pairs so that you can map over it. Since you now how a sequence of pairs, you can use into to add them all to a map.
(->> "kvs.txt" slurp (re-seq #"\w") (partition 2)
(map (fn [[k v]] [k (Float. v)])) (into {}))
2) Declare an auxiliary map-values function for maps and use that on the result:
(defn map-values [m f]
(into {} (for [[k v] m] [k (f v)])))
(->> "your-file.txt" slurp (re-seq #"\w+")
(apply hash-map) (map-values #(Float. %)))
3) If you don't mind having symbol keys instead of strings, you can safely use the Clojure reader to convert all your keys and values.
(->> "your-file.txt" slurp (re-seq #"\w+")
(map read-string) (apply hash-map))
Note that this is a safe use of read-string because our call to re-seq would filter out any hazardous input. However, this will give you longs instead of floats since numbers like 1 are long integers in Clojure
Does the myhash in the inner for refer to the myhash declared in the let form every time?
Yes.
The let binds myhash to {}, and it is never rebound. myhash is always {}.
assoc returns a modified map, but does not alter myhash.
So the code can be reduced to
(for [line ["A:1 B:2" "C:3 D:4"]
:let [pairs (clojure.string/split line #"\s")]]
(for [[k v] (map #(clojure.string/split %1 #":") pairs)]
(assoc {} k (Float. v))))
... which produces the same result:
(({"A" 1.0} {"B" 2.0}) ({"C" 3.0} {"D" 4.0}))
If I do want a list of hash-map like the output, is this the idiomatic way in Clojure?
No.
See #DaoWen's answer.

Korma: or'ing dynamically generated where clauses

I have a set of dynamically generated parameters in a form of a map like
(def clauses {:apples 23 :plums 0 :bananas 7})
and I want to have it or'ed in a where statement, so it should become an equivalent of the Korma query:
(select fruit-shop
(where (or {:apples 23}
{:plums 0}
{:bananas 7})))
Generating a list of maps is quite easy:
(map #(apply array-map %)
(into [] clauses))
But one can't use (or statement applied to it, because it's handled at macro expansion time, before the clauses becomes bound to its value.
What statement should be used in such case?
After getting familiar with Korma source code, I have found korma.sql.fns/pred-or function that replaces or statements in where and having. So I wrote the following helper function that takes a map argument
(require '[korma.sql.fns :refer [pred-or]])
(defn or*
[m]
(apply pred-or
(map #(apply array-map %)
(into [] m))))
Given that, the intended query will look like this
(select fruit-shop
(where (or* clauses)))

Alternatives for converting list of nested maps to a map

I have the following working code to convert a list with nested maps (actually tweet data) to a map:
(defn filter
"This function returns a map with the user as key, #followers as value"
[raw-tweets]
(let [users (map :user raw-tweets)
names (map :name users)
followers (map :followers_count users)]
(zipmap names followers)))
Although this works as expected, I was wondering if there would be a more idiomatic way to do this in Clojure. Any alternatives?
What you have is fine, though you can build the map as you go by using reduce:
(defn user-followers [raw-tweets]
(reduce #(assoc %1 (:name %2) (:followers_count %2))
{} (map :user raw-tweets)))
I'm only starting to learn clojure but I think this way might be a bit more idiomatic. It's an alternative in any case.
(defn filter
"This function returns a map with the user as key, #followers as value"
[raw-tweets]
(into {} (map #(let [user (:user %)]
[(:name user) (:followers_count user)])
raw-tweets)))
It maps over the raw tweets with a function that retrieves the user for each tweet and returns a vector with the name and followers count for that user. The into function takes two sequences and conjoins every element of the second one onto the first, which will turn the list of vectors into a map before it's returned from the filter function.
I find #Daan's answer nice, but I'd add destructuring into the mix.
(defn filter-tweets
"This function returns a map with the user as key, #followers as value"
[raw-tweets]
(into {} (map (fn [{{name :name follower-count :followers_count} :user}]
[name follower-count])
raw-tweets)))
I don't like the (map (fn ...)) pattern - it's really just an ugly way to write a for comprehension. I'd write this as:
(into {}
(for [{:keys [user]} raw-tweets]
((juxt :name :followers_count) user)))
Or this, which feels a little less natural to me but avoids inventing names for values you're just going to use once anyway.
(into {} (map (comp (juxt :name :followers_count) :user)
raw-tweets))

Removing items from a map based on the contents of another map

Still working through Programming Collective Intelligence and using Clojure to write the code. I've got it working, but some parts are really ugly, so I thought I'd ask some of the experts around here to help clean it up.
Let's suppose I have a map that looks like this (bound to "recs"):
{"Superman Returns" 3.902419556891574, "Lady in the Water" 2.8325499182641614,
"Snakes on a Plane" 3.7059737842895792, "The Night Listener" 3.3477895267131017,
"You, Me and Dupree" 2.651006036204627, "Just My Luck" 2.5309807037655645}
and I want to remove those items with keys that are also in the map (bound to "mymovies"):
{"Snakes on a Plane" 4.5, "You, Me and Dupree" 1.0, "Superman Returns" 4.0}
so that I get the map:
{"Lady in the Water" 2.8325499182641614, "The Night Listener" 3.3477895267131017,
"Just My Luck" 2.5309807037655645}
the code that I managed to get to do this looks like:
(apply merge (map #(hash-map (first %) (second %))
(remove #(contains? mymovies (first %))
recs)))
That seems pretty ugly to me. It doesn't seem like it should be necessary to create a map from the value I get back from "remove". Is there a cleaner way to do this?
UPDATE: Joost's answer below sparked another idea. If I turn the keys of the two maps into sets I can use select-keys like this:
(select-keys recs (difference (set (keys recs))
(set (keys mymovies))))
Joost, thanks for turning me on to select-keys. I didn't know about that function before. Now to go rewrite several other sections with this new found knowledge!
(apply dissoc recs (keys mymovies))
The following first builds a seq of keys to keep, then extracts the "submap" for those keys from recs using select-keys. It also takes advantage of the fact that sets are predicates.
(select-keys recs (remove (apply hash-set (keys mymovies)) (keys recs)))
I think ponzao's answer is best for this case, but I wouldn't have thought to apply dissoc. Here are the two solutions I might have come up with: hopefully looking over them will help with similar future problems.
Note that the second solution will fail if your mymovies map contains nil or false values.
(into {}
(for [[k v] recs
:when (not (contains? mymovies k))]
[k v]))
(into {}
(remove (comp mymovies key) recs))