I've got a map from keywords to compass direction strings:
(def dirnames {:n "North", :s "South", :e "East", :w "West"})
I can look up names using the map as a function:
(dirnames :n)
;# = "North"
It seems to me that
(map dirnames [:n :s])
ought to return the vector
["North" "South"]
but it returns
[:n :s]
instead. I've tried this half a dozen ways, supplying different functions in place of "dirnames" in the (map) call, and I always get the vector of keywords back.
Clearly I'm missing something basic. What is it?
Works for me, am i misinterpreting the question:
user> (def dirnames {:n "North", :s "South", :e "East", :w "West"})\
#'user/dirnames
user> (map dirnames [:n :s])
("North" "South")
also:
user> (map #(dirnames %) [:n :s])
("North" "South")
user> (mapv #(dirnames %) [:n :s])
["North" "South"]
I bet you forgot some parens. Consider this function definition:
(defn foo [dirnames]
map dirnames [:n :s])
It looks almost right, but it evaluates map for side effects, then dirnames for side effects (both of those do nothing), and then finally returns [:n :s]. That's the only reasonable explanation I can think of for behavior like what you're describing.
Related
I've started to get some functional programming some weeks ago and I'm trying to perform a mapping from a list of maps to a list considering a specific key in clojure.
My list of maps looks like: '({:a "a1" :b "b1" :c "c1"} {:a "a2" :b "b2" :c "c2"} {:a "a3" :b "b3" :c "c3"})
And the output I'm trying to get is: '("b1" "b2" "b3").
I've tried the following:
(doseq [m maps]
(println (list (get m :b))))
And my output is a list of lists (what is expected as I'm creating a list for each iteration). So my question is, how can I reduce this to a single list?
Update
Just tried the following:
(let [x '()]
(doseq [m map]
(conj x (get m :b))))
However, it is still not working. I`m not getting the point as I was expecting to be appending the elements into a empty list
This is a very common pattern in production Clojure code so it's a good place to learn. In general check out the docs on sequences at https://clojure.org/reference/sequences and when faced with similar task, look to see which pattern best fits and explore functions in that group. In this case it's "Process each item of a seq to create a new seq" and the first item listed is map
your example might look like
(map :b my-data)
You have the right idea, but are using the wrong function. doseq is intended only for side effects and always returns nil. The function you are looking for is for, which takes a sequence as input and returns another sequence as output. I generally prefer for over the similar map as for allows you to name the loop variable:
(def data-list
[{:a "a1" :b "b1" :c "c1"}
{:a "a2" :b "b2" :c "c2"}
{:a "a3" :b "b3" :c "c3"}])
(let [result (vec (for [item data-list]
(:b item)))]
(println result) ; print result
result) ; return result from `let` expression
result => ["b1" "b2" "b3"]
If instead you do this:
(println
(doseq [item data-list]
(println (:b item))))
you can see the difference with doseq vs for:
b1 ; loop item #1
b2 ; loop item #2
b3 ; loop item #3
nil ; return value of doseq
Please see https://www.braveclojure.com/ for online details, and buy a good book (or 5) like Getting Clojure, etc.
(doseq [m maps]
(println (list (get m :b))))
In two short lines, you break several general rules of functional programming:
Pass data into a function as arguments, not as references to global
variables.
Don't print the results of computation. Return them as the value of
the function.
Avoid mechanisms such as doseq that work by side-effects.
Despite this, you were not too far from a solution. doseq is essentially a version of for that throws away its result. If we replace doseq with for, and get rid of the println and the list, we get
=> (for [m maps] (get m :b))
("b1" "b2" "b3")
But Arthur Ulfeldt's simple use of map is better.
What is the idiomatic way of returning the next item in collection, given a member in a collection?
For example, given (def coll [:a :b :c :d :e :f]), what should the f be to make (f coll :d) return :e?
Typically this is just not a thing one does very much in Clojure. The only possible implementation requires a linear scan of the input collection, which means that you are using the wrong data structure for this task.
Instead, we usually try to structure our data so that it is convenient for the tasks we need to perform on it. How best to do this will depend on why you want to look up "the element after foo". For example, if you are going the input one item at a time and want to know the next item as well as the current item, you could write (partition 2 1 input) to get a sequence of pairs of adjacent values.
That is, you ask for an idiomatic implementation, but there is none: the idiom is to solve the problem differently. Of course it is straightforward to write the loop yourself, if you believe you are in an exceptional case where you are using the right data structure and just need to do this weird thing once or twice.
As #amalloy said in his answer, this isn't something for which you would want to use the original data structure, because it would require a linear lookup every time. In other words, your (f coll :d) pattern wouldn't be a particularly useful thing due to its performance.
However, what you could do is define a function that, given a collection, builds a data structure that makes this sort of lookup efficient, and use that as your function. It might look something like this:
(defn after [xs]
(into {} (map vec (partition 2 1 xs))))
Examples:
(-> [:a :b :c :d :e :f] after :d)
;;=> :e
(let [xs [:a :b :c :d :e :f]
f (after xs)]
(map f xs))
;;=> (:b :c :d :e :f nil)
If we generalise the problem to finding the thing following the first thing to pass a test, we get something like
(defn following [pred coll]
(->> coll
(drop-while (complement pred))
(second)))
For example,
(following #{6} (range))
=> 7
Or, your example,
(following #{:d} coll)
=> :e
This is no more or less idiomatic than take-while or drop-while.
I have a question regarding two functions, one taking a complete map and the other specific keywords like so:
(def mapVal
{:test "n"
:anotherKey "n"})
(defn functionTest
[& {:keys [test anotherKey] :or {:test "d" :anotherKey "d"}}]
(println :test :anotherKey))
(defn mapFunc
[map]
(functionTest (get-in map [:test :anotherKey])))
The goal would be that all the keys in the parameter map will be passed correctly to the functionTest. Is there anyway this could work? I tried a few things but i just cant get all the keywords and values passed to the functionTest. What i dont want is just the values of the map, it should be passed to the other function with keyword and value.
You're pretty close. A few things should clear it up.
First, when you declare parameters with [& varname] that means varname will be a list containing all the extra parameters. So you don't need to use that '&' here to destructure the input. Instead, you just name which keys you want to have become variables.
Try this:
(defn functionTest
[{:keys [test anotherKey]}]
(println test anotherKey))
And the other problem is using get-in. With get-in you're defining a "path" through nested data structures with that vector. For example, given:
{:first {:a 1 :b 2} :second {:c 3 :d 4}}
You could use get-in to get the value at :second :c with this:
(get-in {:first {:a 1 :b 2} :second {:c 3 :d 4}} [:second :c])
In your case, you don't need to use get-in at all. You just need to pass the entire map. The destructuring you defined in functionTest will handle the rest. Here is what I did that worked:
(defn mapFunc
[map]
(functionTest map))
I would also suggest you not name the that variable 'map' since it conflicts with the map function.
get-in is for accessing nested associative data structures.
(def m {:a {:x 1}})
(get-in m [:a :x]) ;;=> 1
After you destructure a map those values are in scope and are accessed via symbols. Your example should look like this:
(def mapVal
{:test "n"
:anotherKey "n"})
(defn functionTest
[& {:keys [test anotherKey]
:or {:test "d" :anotherKey "d"}}]
(println test anotherKey))
(defn mapFunc
[m]
(apply functionTest (apply concat (select-keys m [:test :anotherKey]))))
(mapFunc mapVal) ;;=> prints: n n
You have to go through this because functionTest is accepting bare key value pairs as optional parameters (the ones to the right of the &), as in:
(functionTest :test "n"
:anotherKey "n" )
;;=> Also prints: n n
select-keys returns a map with only the specified keys:
(select-keys mapVal [:test])
;; => {:test "n"}
applying concat to a map returns a flat seq of keys and values:
(apply concat (select-keys mapVal [:test :anotherKey]))
;; => (:test "n" :anotherKey "n")
apply applies a function to a seq, as though the seq were its argument list:
(+ [1 2 3]) ;;=> Error.
(apply + [1 2 3]) ;; => 6
As a side note, conventionally in Clojure code, snake-case is preferred to camelCase for most names.
I'm trying to figure out an idiomatic, performant, and/or highly functional way to do the following:
I have a sequence of maps that looks like this:
({:_id "abc" :related ({:id "123"} {:id "234"})}
{:_id "bcd" :related ({:id "345"} {:id "456"})}
{:_id "cde" :related ({:id "234"} {:id "345"})})
The :id fields can be assumed to be unique within any one :_id.
In addition, I have two sets:
ids like ("234" "345") and
substitutes like ({:id "111"} {:id "222"})
Note that the fact that substitutes only has :id in this example doesn't mean it can be reduced to a collection of ids. This is a simplified version of a problem and the real data has other key/value pairs in the map that have to come along.
I need to return a new sequence that is the same as the original but with the values from substitutes replacing the first occurrence of the matching id from ids in the :related collections of all of the items. So what the final collection should look like is:
({:_id "abc" :related ({:id "123"} {:id "111"})}
{:_id "bcd" :related ({:id "222"} {:id "456"})}
{:_id "cde" :related ({:id "234"} {:id "345"})})
I'm sure I could eventually code up something that involves nesting maps and conditionals (thinking in iterative terms about loops of loops) but that feels to me like I'm not thinking functionally or cleverly enough given the tools I might have available, either in clojure.core or extensions like match or walk (if those are even the right libraries to be looking at).
Also, it feels like it would be much easier without the requirement to limit it to a particular strategy (namely, subbing on the first match only, ignoring others), but that's a requirement. And ideally, a solution would be adaptable to a different strategy down the line (e.g. a single, but randomly positioned match). The one invariant to strategy is that each id/sub pair should used only once. So:
Replace one, and one only, occurrence of a :related value whose :id matches a value from ids with the corresponding value from substitutes, where the one occurrence is the first (or nth or rand-nth...) occurrence.
(def id-mapping (zipmap ids
(map :id substitutes)))
;; id-mapping -> {"345" "222", "234" "111"}
(clojure.walk/prewalk-replace id-mapping original)
Assuming that the collection is called results:
(require '[clojure.zip :as z])
(defn modify-related
[results id sub]
(loop [loc (z/down (z/seq-zip results))
done? false]
(if (= done? true)
(z/root loc)
(let [change? (->> loc z/node :_id (= id))]
(recur (z/next (cond change?
(z/edit loc (fn [_] identity sub))
:else loc))
change?)))))
(defn modify-results
[results id sub]
(loop [loc (z/down (z/seq-zip results))
done? false]
(if (= done? true)
(z/root loc)
(let [related (->> loc z/node :related)
change? (->> related (map :_id) set (#(contains? % id)))]
(recur (z/next (cond change?
(z/edit loc #(assoc % :related (modify-related related id sub)))
:else loc))
change?)))))
(defn sub-for-first
[results ids substitutes]
(let [subs (zipmap ids substitutes)]
(reduce-kv modify-results results subs)))
I have the following line in my code:
(spit path (prn-str job-data))
It does it's work well execpt for one thing, every item in the list are put between double-quotes...
( ":a" ":b" ":a" )
the expected result that I'd like to have
( :a :b :a )
How to get the expected result?
Thanks in advance!
What's happening
The issue isn't that the items are being put in double quotes per se but that they're strings (as opposed to the keywords you're expecting).
prn-str, which is ultimately based on pr, prints objects "in a way that objects can be read by the reader". This means strings are printed in double-quotes - otherwise the reader wouldn't be able to tell strings from symbols, or read strings with whitespace in them. See here for more information on Clojure's reader.
println and print, on the other hand, are intended to "produce output for human consumption" and do not put strings in double-quotes. This is why you're seeing the difference in output between prn-str and println.
You can verify this with class. If you try (-> job-data first class) the answer will be either java.lang.String or clojure.lang.Keyword.
Here are some examples demonstrating the different behaviors of the printing functions when used with keywords and strings:
(def str-job-data '(":a" ":b" ":c"))
(def key-job-data '(:a :b :c))
;; `println` prints both keywords and strings without quotes
(with-out-str (println str-job-data)) ;=> "(:a :b :c)\n"
(with-out-str (println key-job-data)) ;=> "(:a :b :c)\n"
;; `prn-str` prints the strings in quotes but the keywords without quotes
(prn-str str-job-data) ;=> "(\":a\" \":b\" \":c\")\n"
(prn-str key-job-data) ;=> "(:a :b :c)\n"
How to change it
Now for possible solutions. If you were expecting job-data to contain keywords then the right fix is most likely to modify job-data. However, I can't offer much guidance here without knowing more about how job-data is produced.
If for some reason you can't modify job-data (for instance, if it's produced by code you don't control) and you want to write keywords wherever it contains keyword-like strings then something like #maxthoursie's suggestion is probably your best bet. (You could hypothetically just switch to print or println but that could have undesirable effects on how other objects are printed).
(defn keyword-string->keyword [s]
(keyword (subs s 1)))
(spit path (prn-str (map keyword-string->keyword job-data)))
If job-data might contain objects other than keyword-like strings you could apply the function only when appropriate.
(defn convert-job-data [obj]
(if (and (string? obj)
(= (.charAt obj 0) \:))
(keyword-string->keyword obj)
obj))
(spit path (prn-str (map convert-job-data job-data)))
Of course, if the file you're writing is for human consumption anyway and all this business about the reader is irrelevant you could trivially make your own println-str:
(defn println-str [& more]
(with-out-str (apply println more)))
(spit path (println-str job-data))
I'm guessing job-data is not what you expect it to be.
user=> (prn-str '(:a :b :c))
"(:a :b :c)\n"
If you do have a list with strings that looks like keywords, and you would like to convert it to keywords, you could use something like
(map (comp keyword #(subs % 1)) '(":a" ":b" ":c"))
Which skips the : of each element, and then converts it to a keyword.
user=> (prn-str (map (comp keyword #(subs % 1)) '(":a" ":b" ":c")))
"(:a :b :c)\n"