I am currenly using a function
(def mymap {})
(defn function1 [var1 var2 var3 var4 var5]
;calls another functions with all variables.
(function2 var1 var2 var3 var4 var5)
)
But as this is having more parameters I would like to convert this to a map before calling functions2.
(function2((assoc mymap (keyword var1) var1
(keyword var2) var2
(keyword var3) var3
(keyword var4) var4
(keyword var5) var5 ))
)
Is this the correct way? Do we have better way to do this(In java we direclty use some objects in this scenario)
For general function args, I always go in order from the biggest to the smallest,
in either size or "importance" (somewhat subjective).
However, if you have more than 3 args, I prefer to pass a map containing the args and appropriate keyword names.
The Tupelo Clojure library has some tools to make this easy. The macro vals->map takes multiple variable names and constructs a map from the (keywordized) variable name to its value, like:
(let [ctx (let [a 1
b 2
c 3
d 4
e 5]
(t/vals->map a b c d e))]
(is= ctx {:a 1 :b 2 :c 3 :d 4 :e 5})
The macro with-map-vals does the opposite, deconstructing map values into local variables named for their keys. It is similar to the Clojure :keys destructuring, but in (IMHO) a more natural form:
(let [{:keys [a b c d e]} ctx] ; Clojure destructing syntax
(is= [a b c d e] [1 2 3 4 5]))
(t/with-map-vals ctx [a b c d e]
(is= [a b c d e] [1 2 3 4 5])
(is= 15 (+ a b c d e)))
(t/with-map-vals ctx [b a d c e] ; order doesn't matter
(is= [a b c d e] [1 2 3 4 5])
(is= 15 (+ a b c d e)))
(t/with-map-vals ctx [b a d] ; can ignore stuff you don't care about
(is= [d a b] [4 1 2]))
(throws?
(t/with-map-vals ctx [a b z] ; throws if key doesn't exist
(println "won't ever get here")))))
If you have nested data in maps and/or vectors, you can use the more powerful destruct and restruct tools. Here is a brief example (from the unit tests):
(let [info {:a 1
:b {:c 3
:d 4}}
mania {:x 6
:y {:w 333
:z 666}}]
(t/it-> (t/destruct [info {:a ?
:b {:c ?
:d ?}}
mania {:y {:z ?}}] ; can ignore unwanted keys like :x
(let [a (+ 100 a)
c (+ 100 c)
d z
z 777]
(restruct-all)))
(t/with-map-vals it [info mania]
(is= info {:a 101, :b {:c 103, :d 666}})
(is= mania {:x 6, :y {:w 333, :z 777}})))
As you can see, a question mark ? will cause the corresponding value to be destructed into a variable named for the corresponding keyword. It is also possible to create explicit variable names like so:
(dotest
(let [info {:a 777
:b [2 3 4]}
mania [{:a 11} {:b 22} {:c [7 8 9]}]]
(let [z ::dummy]
(t/it-> (t/destruct [info {:a z
:b [d e f]}
mania [{:a ?} BBB {:c clutter}]]
(let [clutter (mapv inc clutter)
BBB {:BBB 33}
z 77
d (+ 7 d)]
(restruct-all)))
(t/with-map-vals it [info mania]
(is= info {:a 77, :b [9 3 4]})
(is= mania [{:a 11} {:BBB 33} {:c [8 9 10]}]))))))
It also works for mixed maps & vectors:
(let [data {:a [{:b 2}
{:c 3}
[7 8 9]]} ]
(t/destruct [data {:a [{:b p}
{:c q}
[r s t]]} ]
(is= [2 3 7 8 9] [p q r s t])))
Related
So in ES6, I can do something like this:
let x=1 y=1 z=1
let o = {x, y,z}
console.log(o.x) //prints 1
I don't see any built in way to do something like this in clojure. Can one do any better than this to return a map from a function that takes a number of arguments?
(defn foo
[x y z]
{:x x :y y :z z})
(pprint (get ( foo 1 2 3) :x)) ;prints 1
There's a dozen reasons why doing this isn't a good idea, but the cool thing about a lisp is that you can do what you want, and if the language doesn't do what you want, you can extend it.
Here's a simple macro
(defmacro infer-map [& args]
{:pre [(every? symbol? args)]}
`(hash-map
~#(interleave
(map (comp keyword name) args)
args)))
And the usage:
(let [x 1 y 2 z 3]
(infer-map x y z))
=> {:y 2, :z 3, :x 1}
You can do this using vals->context and with-context in the Tupelo Clojure library. The unit tests show this feature in action:
(dotest
(let [ctx (let [a 1
b 2
c 3
d 4
e 5]
(vals->context a b c d e)) ]
(is= ctx {:a 1 :b 2 :c 3 :d 4 :e 5})
(let [{:keys [a b c d e]} ctx]
(is= [a b c d e] [1 2 3 4 5]))
(with-context ctx [a b c d e]
(is= [a b c d e] [1 2 3 4 5])
(is= 15 (+ a b c d e)))
(with-context ctx [b a d c e] ; order doesn't matter
(is= [a b c d e] [1 2 3 4 5])
(is= 15 (+ a b c d e)))
(throws?
(with-context ctx [x y z]
(println "shouldn't ever get here")))))
In clojure, I am trying to accomplish the following logic:
Input:
{:a [11 22 33] :b [10 20 30]}, 2
Output:
{:a [11] :b [10 20 30 22 33]}
i.e. Move the last 2 elements from :a to :b
Is there a clojurish way for this operation?
Since you're effectively modifying both mappings in the map, it's probably easiest to explicitly deconstruct the map and just return the new map via a literal, using subvec and into for the vector manipulation:
(defn move [m n]
(let [{:keys [a b]} m
i (- (count a) n)
left (subvec a 0 i)
right (subvec a i)]
{:a left :b (into b right)}))
(move {:a [11 22 33] :b [10 20 30]} 2)
;;=> {:a [11], :b [10 20 30 22 33]}
As a bonus, this particular implementation is both very idiomatic and very fast.
Alternatively, using the split-at' function from here, you could write it like this:
(defn split-at' [n v]
[(subvec v 0 n) (subvec v n)])
(defn move [m n]
(let [{:keys [a b]} m
[left right] (split-at' (- (count a) n) a)]
{:a left :b (into b right)}))
First, using the sub-vec in the other answers will throw an IndexOutOfBoundsException when the number of elements to be moved is greater than the size of the collection.
Secondly, the destructuring, the way most have done here, couples the function to one specific data structure. This being, a map with keys :a and :b and values for these keys that are vectors. Now if you change one of the keys in the input, then you need to also change it in move function.
My solution follows:
(defn move [colla collb n]
(let [newb (into (into [] collb) (take-last n colla))
newa (into [] (drop-last n colla))]
[newa newb]))
This should work for any collection and will return vector of 2 vectors. My solution is far more reusable. Try:
(move (range 100000) (range 200000) 10000)
Edit:
Now you can use first and second to access the vector you need in the return.
I would do it just a little differently than Josh:
(defn tx-vals [ {:keys [a b]} num-to-move ]
{:a (drop-last num-to-move a)
:b (concat b (take-last num-to-move a)) } )
(tx-vals {:a [11 22 33], :b [10 20 30]} 2)
=> {:a (11), :b (10 20 30 22 33)}
Update
Sometimes it may be more convenient to use the clojure.core/split-at function as follows:
(defn tx-vals-2 [ {:keys [a b]} num-to-move ]
(let [ num-to-keep (- (count a) num-to-move)
[a-head, a-tail] (split-at num-to-keep a) ]
{ :a a-head
:b (concat b a-tail) } ))
If vectors are preferred on output (my favorite!), just do:
(defn tx-vals-3 [ {:keys [a b]} num-to-move ]
(let [ num-to-keep (- (count a) num-to-move)
[a-head, a-tail] (split-at num-to-keep a) ]
{:a (vec a-head)
:b (vec (concat b a-tail))} ))
to get the results:
(tx-vals-2 data 2) => {:a (11), :b (10 20 30 22 33)}
(tx-vals-3 data 2) => {:a [11], :b [10 20 30 22 33]}
(defn f [{:keys [a b]} n]
(let [last-n (take-last n a)]
{:a (into [] (take (- (count a) n) a))
:b (into b last-n)}))
(f {:a [11 22 33] :b [10 20 30]} 2)
=> {:a [11], :b [10 20 30 22 33]}
In case if the order of those items does not matter, here is my attempt:
(def m {:a [11 22 33] :b [10 20 30]})
(defn so-42476918 [{:keys [a b]} n]
(zipmap [:a :b] (map vec (split-at (- (count a) n) (concat a b)))))
(so-42476918 m 2)
gives:
{:a [11], :b [22 33 10 20 30]}
i would go with an approach, which differs a bit from the previous answers (well, technically it is the same, but it differs on the application-scale level).
First of all, transferring data between two collections is quite a frequent task, so it at least deserves some special utility function for that in your library:
(defn transfer [from to n & {:keys [get-from put-to]
:or {:get-from :start :put-to :end}}]
(let [f (if (= get-from :end)
(partial split-at (- (count from) n))
(comp reverse (partial split-at n)))
[from swap] (f from)]
[from (if (= put-to :start)
(concat swap to)
(concat to swap))]))
ok, it looks verbose, but it lets you transfer data from start/end of one collection to start/end of the other:
user> (transfer [1 2 3] [4 5 6] 2)
[(3) (4 5 6 1 2)]
user> (transfer [1 2 3] [4 5 6] 2 :get-from :end)
[(1) (4 5 6 2 3)]
user> (transfer [1 2 3] [4 5 6] 2 :put-to :start)
[(3) (1 2 4 5 6)]
user> (transfer [1 2 3] [4 5 6] 2 :get-from :end :put-to :start)
[(1) (2 3 4 5 6)]
So what's left, is to make your domain specific function on top of it:
(defn move [data n]
(let [[from to] (transfer (:a data) (:b data) n
:get-from :end
:put-to :end)]
(assoc data
:a (vec from)
:b (vec to))))
user> (move {:a [1 2 3 4 5] :b [10 20 30 40] :c [:x :y]} 3)
{:a [1 2], :b [10 20 30 40 3 4 5], :c [:x :y]}
I would like to have a search and replace on the values only inside data structures:
(def str [1 2 3
{:a 1
:b 2
1 3}])
and
(subst str 1 2)
to return
[2 2 3 {:a 2, :b 2, 1 3}]
Another example:
(def str2 {[1 2 3] x, {a 1 b 2} y} )
and
(subst str2 1 2)
to return
{[1 2 3] x, {a 1 b 2} y}
Since the 1's are keys in a map they are not replaced
One option is using of postwalk-replace:
user> (def foo [1 2 3
{:a 1
:b 2
1 3}])
;; => #'user/foo
user> (postwalk-replace {1 2} foo)
;; => [2 2 3 {2 3, :b 2, :a 2}]
Although, this method has a downside: it replaces all elements in a structure, not only values. This may be not what you want.
Maybe this will do the trick...
(defn my-replace [smap s]
(letfn [(trns [s]
(map (fn [x]
(if (coll? x)
(my-replace smap x)
(or (smap x) x)))
s))]
(if (map? s)
(zipmap (keys s) (trns (vals s)))
(trns s))))
Works with lists, vectors and maps:
user> (my-replace {1 2} foo)
;; => (2 2 3 {:a 2, :b 2, 1 3})
...Seems to work on arbitrary nested structures too:
user> (my-replace {1 2} [1 2 3 {:a [1 1 1] :b [3 2 1] 1 1}])
;; => (2 2 3 {:a (2 2 2), :b (3 2 2) 1 2})
I want to do this
(let [[a b c] '(1 2 3)]
{:a a :b b :c c}) ;; gives {:a 1, :b 2, :c 3}
But with [a b c] saved in a vector like this
(def vect '[a b c])
(let [vect '(1 2 3)]
{:a a :b b :c c}) ;; complains that a is unresolved
Is it possible to somehow use a var to define how to destructure?
The error occurs, because in this snippet:
(let [vect '(1 2 3)]
{:a a :b b :c c})
You're binding vect to '(1 2 3). The vect earlier defined as '[a b c] earlier will be shadowed by the local let binding. a,b and c will be left unbound.
The only way I think you can do what you ask is by using (abusing?) eval/macros, and building up the exact form that you need.
(eval (list 'let [vect ''(1 2 3)] '{:a a :b b :c c}))
;; => {:a 1 :b 2 :c 3}
However, I really urge you to put in some hammock time here and think about why you need to destructure using a var, and possible alternative designs. The solution above is already pretty hacky and using it could get very ugly...
I would agree with Daniel to possibly rethink the reason why you need to do it, e.g. what exactly the problem is you are after.
But if you insist :), pretending that we literally work with "a b c.."s, you can do this:
user=> (def xs '[a b c])
#'user/xs
user=> (into {} (for [v xs] [(keyword v) v]))
{:a a, :b b, :c c}
Let's say I have
(defn test [ & {:keys [a b c]}]
(println a)
(println b)
(println c))
What I want is to call test with a map {:a 1 :b 2 :c 3}.
This works:
(apply test [:a 1 :b 2 :c 3])
These do not:
(apply test {:a 1 :b 2 :c 3})
(apply test (seq {:a 1 :b 2 :c 3}))
EDIT
So you can of course define the function like this also:
(defn test [{:keys [a b c]}] ; No &
(println a)
(println b)
(println c))
And then you can pass a map to it:
(test {:a 1 :b 2 :c 3})
1
2
3
When learning clojure I had missed this was possible. Nevertheless if you ever come across a function defined by me or somebody like me then knowing how to pass a map to it could still be useful ;)
user> (apply list (mapcat seq {:a 1 :b [2 3 4]}))
(:a 1 :b [2 3 4])
Any good reason not to define it like this in the first place?
(defn my-test [{:keys [a b c]}] ;; so without the &
(println a)
(println b)
(println c))
and then call it like this?
(my-test {:a 10 :b 20 :c 30})
which outputs:
10
20
30
nil
This works, but is inelegant:
(apply test (flatten (seq {:a 1 :b 2 :c 3})))
The reason (apply test (seq {:a 1 :b 2 :c 3})) doesn't work is that (seq {:a 1 :b 2 :c 3}) returns [[:a 1] [:b 2] [:c 3]], flatten takes care of this.
Better solutions?