The doc for map says:
user=> (doc map)
clojure.core/map
([f] [f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])
Returns a lazy sequence consisting of the result of applying f to
the set of first items of each coll, followed by applying f to the
set of second items in each coll, until any one of the colls is
exhausted. Any remaining items in other colls are ignored. Function
f should accept number-of-colls arguments. Returns a transducer when
no collection is provided.
nil
applying f to the set makes me unsure about whether f is applied to the sequence of n-th elements of every collection passed in to give the n-th element of the result sequence.
In other words:
Is there a guarantee that
(map str [ "1" "2" "3" ] [ "a" "b" "c" ] )
will always return
("1a" "2b" "3c")
and never
("a1" "b2" "3c")
?
Yes, (map str ["1" "2" "3"] ["a" "b" "c"]) will always return ("1a" "2b" "3c").
From the source code here you can see it calls the function on (first s1) (first s2) and then calls (map f (rest s1) (rest s2)), so it always processes them in order:
https://github.com/clojure/clojure/blob/master/src/clj/clojure/core.clj#L2756
Before it starts it calls seq on each of the collections. For an ordered collection like a vector, the elements in the resulting seq will always have the same order as the original collection. For an unordered set, you cannot expect them in a particular order, but that's not the case you're asking about.
Related
I want to write a function which simply updates a vector in a map with new value, but can take any number of args, but at least one.
Here is example:
(defn my-update [what item & items]
(update what :desired-key conj item items))
Unfortunately, this doesn't work. Despite that update do have a signature with multiple values (like [m k f x y]), all remaining arguments to my-update will be joined into one sequence, which will be passed to conj as one argument.
Instead, wrapping conj with apply in an anonymous function does work, but looks not so elegant:
(defn my-update [what item & items]
(update what :desired-key #(apply conj % item items))
What is the idiomatic way of writing such a function like my-update?
You can simply insert apply before update. That will call the function update with the arguments that follows except for the last argument which should be a sequence, whose elements become the remaining arguments in the call:
(defn my-update [what item & items]
(apply update what :desired-key conj item items))
(my-update {:desired-key [0]} 1 2 3 4)
;; => {:desired-key [0 1 2 3 4]}
(my-update {:desired-key [0]})
;; Exception: Wrong number of args (1) passed to: my-update
This way, you can keep the function argument list [what item & items] that makes it clear that at least one item needs to be provided.
In general, a call (apply f a b c ... [x y z ...]) will evaluate to the same as (f a b c ... x y z ...).
Your existing solution isn't so bad. One small improvement is to use the into function, which uses conj internally to join two sequences together:
(defn my-update [what & items]
(update what :a into items))
with result
(my-update {:a [1]} 2 3 4) => {:a [1 2 3 4]}
Another alternative is to extract out the anonymous function into a named function:
(defn append-to-seq
[seq item items]
(-> (vec seq) ; ensure it is a vector so conj adds to the end, not beginning
(conj item)
(into items)))
(defn my-update [what item & items]
(update what :a append-to-seq item items))
(defn foo [[a] b]
(println a b))
(foo "one" "two") ; => o two
(foo 1 2) ; => Execution error (UnsupportedOperationException) at user/foo (REPL:1).
; nth not supported on this type: Long
What are the second pair of brackets around a doing?
Or, a real-world example where I encountered it:
(parser/add-tag! :i18n
(fn [[k] context]
(->> k (keyword) (translate (or (:i18n/locale context) :en)))))
It is Clojure's syntax for destructuring.
(defn bar [[a b] c]
(println a b c))
would "pull out" the first and second items from the first argument passed to bar and immediately assign them to variables a and b respectively. It is functionally equivalent to:
(defn bar [vector c]
(let [a (nth vector 0) ; assign first item in first parameter to a
b (nth vector 1)] ; assign second item in first parameter to b
(println a b c))) ; c is passed & printed as-is
In your example:
(defn foo [[a] b]
(println a b))
Running (foo "one" "two") would mean "set a to first item in "one" and b to "two", then print a b.
In Clojure, strings are seq-able, so [[a]] in this specific case means "set a to the first character of the string "one", which is o.
(foo 1 2) fails because numbers (Longs) are not seq-able, so they cannot be destructured.
(fn [[k] context] … ) would mean "of the two arguments passed into this function, take the first item from the first argument and set it as k. Leave the second argument as-is."
I've been learning Clojure for a few weeks now. I know the basics of the data structures and some functions. (I'm reading the Clojure Programming book).
I'm stuck with the following. I'm writing a function which will lower case the keys of the supplied map.
(defn lower-case-map [m]
(def lm {})
(doseq [k (keys m)]
(assoc lm (str/lower-case k) (m k))))
This does what I want, but how do I return the map? Is the def correct?
I know this works
(defn lower-case-map [m]
(assoc {} :a 1))
But the doseq above seems to be creating a problem.
Within a function body you should define your local variables with let, yet this code looks alot like you try to bend it into an imperative mindset (def tempvar = new Map; foreach k,v in m do tempvar[k.toLower] = v; return tempvar). Also note, that the docs of doseq explicitly state, that it returns nil.
The functional approach would be a map or reduce over the input returning the result directly. E.g. a simple approach to map (iterating the sequence of elements, destructure the key/value tuple, emit a modified tuple, turn them back into a map):
user=> (into {} (map (fn [[k v]] [(.toLowerCase k) v]) {"A" 1 "B" 2}))
{"a" 1, "b" 2}
For your use-case (modify all keys in a map) is already a nice core function: reduce-kv:
user=> (doc reduce-kv)
-------------------------
clojure.core/reduce-kv
([f init coll])
Reduces an associative collection. f should be a function of 3
arguments. Returns the result of applying f to init, the first key
and the first value in coll, then applying f to that result and the
2nd key and value, etc. If coll contains no entries, returns init
and f is not called. Note that reduce-kv is supported on vectors,
where the keys will be the ordinals.
user=> (reduce-kv (fn [m k v] (assoc m (.toLowerCase k) v)) {} {"A" 1 "B" 2})
{"a" 1, "b" 2}
How to understand this simple clojure code?
I kind of understand what it is trying to do but can someone explain the syntax in great detail so I can confidently use it?
(map (fn [x] (.toUpperCase x)) (.split "Dasher Dancer Prancer" " "))
From Clojure REPL:
(doc map)
clojure.core/map
([f coll] [f c1 c2] [f c1 c2 c3] [f c1 c2 c3 & colls])
Returns a lazy sequence consisting of the result of applying f to the
set of first items of each coll, followed by applying f to the set
of second items in each coll, until any one of the colls is
exhausted. Any remaining items in other colls are ignored. Function
f should accept number-of-colls arguments.
(.split "Dasher Dancer Prancer" " ") is generating a sequence of strings and each tokenized string will be passed to (fn [x] (.toUpperCase x))
However, (fn [x] (.toUpperCase x)) is too much unnecessary typing. You can do:
(map #(.toUpperCase %) (.split "Dasher Dancer Prancer" " "))
or:
(map (memfn toUpperCase) (.split "Dasher Dancer Prancer" " "))
This is defining a lambda (an anonymous function, which calls toUpperCase on its single argument), and applying it (using map) to each result of String.split().
map takes a function and a sequence of things to apply that function to, and returns a sequence of results from applying the function to the input sequence.
The following breaks the operation down into smaller pieces:
(defn upper-case-fn [^String x]
"this makes the same function, but not anonymous, and provides a type hint
(making it more efficient by avoiding the need for reflection)."
(.toUpperCase x))
;; you could also write the above like so:
(def upper-case-fn (fn [x] (.toUpperCase x)))
(def input-seq
"this assigns your input seq to a var; in the real world, with dynamic data,
you wouldn't do this"
(.split "Dasher Dancer Prancer" " "))
(def output-seq
"this is precisely the same as your sample, except that it's using a named
function rather than an anonymous one, and assigns the output to a var"
(map upper-case-fn input-seq))
;; if you enter this at a repl, you're looking at the contents of this var
output-seq
I'm trying to build a set of functions to compare sentences to one another. So I wrote a function called split-to-sentences that takes an input like this:
"This is a sentence. And so is this. And this one too."
and returns:
["This is a sentence" "And so is this" "And this one too."]
What I am struggling with is how to iterate over this vector and get the items that aren't the current value. I tried nosing around with drop and remove but haven't quite figured it out.
I guess one thing I could do is use first and rest in the loop and conj the previous value to the output of rest.
(remove #{current-value} sentences-vector)
Just use filter:
(filter #(not= current-value %) sentences-vector)
I believe you may want something like this function:
(defn without-each [x]
(map (fn [i] (concat (subvec x 0 i) (subvec x (inc i))))
(range (count x))))
Use it like this:
>>> (def x ["foo" "bar" "baz"])
>>> (without-each x)
==> (("bar" "baz") ("foo" "baz") ("foo" "bar"))
The returned elements are lazily concatenated, which is why they are not vectors. This is desirable, since true vector concatenation (e.g. (into a b)) is O(n).
Because subvec uses sharing with the original sequence this should not use an excessive amount of memory.
The trick is to pass your sentences twice into the reduce function...
(def sentences ["abcd" "efg" "hijk" "lmnop" "qrs" "tuv" "wx" "y&z"])
(reduce
(fn [[prev [curr & foll]] _]
(let [aren't-current-value (concat prev foll)]
(println aren't-current-value) ;use it here
[(conj prev curr) foll]))
[[] sentences]
sentences)
...once to see the following ones, and once to iterate.
You might consider using subvec or pop because both operate very quickly on vectors.