map into a hashmap of input to output? Does this function exist? - clojure

I have a higher-order map-like function that returns a hashmap representing the application (input to output) of the function.
(defn map-application [f coll] (into {} (map #(vector % (f %)) coll)))
To be used thus:
(map-application str [1 2 3 4 5])
{1 "1", 2 "2", 3 "3", 4 "4", 5 "5"}
(map-application (partial * 10) [1 2 3 4 5])
{1 10, 2 20, 3 30, 4 40, 5 50}
Does this function already exist, or does this pattern have a recognised name?
I know it's only a one-liner, but looking at the constellation of related functions in clojure.core, this looks like the kind of thing that already exists.

I guess the term you are looking for is transducer.
https://clojure.org/reference/transducers
in fact the transducing variant would look almost like yours (the key difference is that coll argument is passed to into function not map), but it does it's job without any intermediate collections:
user> (defn into-map [f coll]
(into {} (map (juxt identity f)) coll))
#'user/into-map
user> (into-map inc [1 2 3])
;;=> {1 2, 2 3, 3 4}
this can also be done with the simple reduction, though it requires a bit more manual work:
user> (defn map-into-2 [f coll]
(reduce #(assoc %1 %2 (f %2)) {} coll))
#'user/map-into-2
user> (map-into-2 inc [1 2 3])
;;=> {1 2, 2 3, 3 4}

What you're describing is easily handled by the built-in zipmap function:
(defn map-application
[f coll]
(zipmap coll (map f coll)))
(map-application (partial * 10) [1 2 3 4 5])
=> {1 10, 2 20, 3 30, 4 40, 5 50}

Related

Juxtaposed transducers

Let's imagine we want to compute two different functions on some given input. How can we do that with transducers?
For example, let's say we have these two transducers:
(def xf-dupl (map #(* 2 %)))
(def xf-inc (map inc))
Now, I would like some function f that takes a collection of transducers and returns a new transducer that combines them, as follows:
(into [] (f [xf-dupl xf-inc]) (range 5))
; => [[0 2 4 6 8] [1 2 3 4 5]]
There should probably be a very simple solution to this, but I cannot find it.
Note: I have tried with cgrand/xforms library's transjuxt, but there I get the following
(into [] (x/transjuxt {:a xf-dupl :b xf-inc}) (range 5))
; => [{:a 0 :b 1}]
Thanks for your help!
Using cgrand/xforms you can define f as
(defn f
[xfs]
(comp
(x/multiplex (zipmap (range) xfs))
(x/by-key (x/into []))
(map second)))
Calling f as you outlined in your question yields
user> (into [] (f [xf-dupl xf-inc]) (range 5))
[[0 2 4 6 8] [1 2 3 4 5]]

Clojure: how to move vector elements in a map elegantly

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]}

Remove n instances of matched elements from collection

What is the best way to remove n instances of matched elements of collection-2 from collection-1?
(let [coll-1 [8 2]
coll-2 [8 8 8 2]
Here's what I first came up with to solve original problem:
...
;; (remove (set coll-1) coll-2))
;; --> ()
But realised I must achieve:
...
;; (some-magic coll-1 coll-2))
;; --> (8 8)
Clarification:
(some-magic {8 2} [8 8 8 2]) ;;Removes 1x8 and 1x2 from vector.
(some-magic {8 8 2} [8 8 8 2]) ;;Removes 2x8 and 1x2 from vector.
Edit:
Preserving the order is desired.
Here is a lazy solution, written in the style of distinct:
(defn some-magic [count-map coll]
(let [step (fn step [xs count-map]
(lazy-seq
((fn [[f :as xs] count-map]
(when-let [s (seq xs)]
(if (pos? (get count-map f 0))
(recur (rest s) (update-in count-map [f] dec))
(cons f (step (rest s) count-map)))))
xs count-map)))]
(step coll count-map)))
The first argument needs to be a map indicating how many of each value to remove:
(some-magic {8 1, 2 1} [8 8 8 2]) ;; Removes 1x8 and 1x2
;=> (8 8)
(some-magic {8 2, 2 1} [8 8 8 2]) ;; Removes 2x8 and 1x2
;=> (8)
Here is an example dealing with falsey values and infinite input:
(take 10 (some-magic {3 4, 2 2, nil 1} (concat [3 nil 3 false nil 3 2] (range))))
;=> (false nil 0 1 4 5 6 7 8 9)
I don't see any of the built in sequence manipulation functions quite solving this, though a straitforward loop can build the result nicely:
user> (loop [coll-1 (set coll-1) coll-2 coll-2 result []]
(if-let [[f & r] coll-2]
(if (coll-1 f)
(recur (disj coll-1 f) r result)
(recur coll-1 r (conj result f)))
result))
[8 8]

Clojure equality of collections with sequences

I noticed that Clojure (1.4) seems to be happy to consider vectors equal to the seq of the same vector, but that the same does not apply for maps:
(= [1 2] (seq [1 2]))
=> true
(= {1 2} (seq {1 2}))
=> false
Why should the behaviour of = be different in this way?
Clojure's = can be thought of as performing its comparisons in two steps:
Check if the types of the things being compared belong to the same "equality partition", that is a class of types whose members might potentially be equal (depending on things like the exact members of a given data structure, but not the particular type in the partition);
If so, check if the things being compared actually are equal.
One such equality partition is that of "sequential" things. Vectors are considered sequential:
(instance? clojure.lang.Sequential [])
;= true
As are seqs of various types:
(instance? clojure.lang.Sequential (seq {1 2}))
;= true
Therefore a vector is considered equal to a seq if (and only if) their corresponding elements are equal.
(Note that (seq {}) produces nil, which is not sequential and compares "not equal" to (), [] etc.)
On the other hand, maps constitute an equality partition of their own, so while a hash map might be considered equal to a sorted map, it will never be considered equal to a seq. In particular, it is not equal to the seq of its entries, which is what (seq some-map) produces.
I guess this is because in sequences order as well as value at particular position matters where as in map the order of key/value doesn't matter and this difference between semantics causes this to work as shown by your sample code.
For more details have a look at mapEquals in file https://github.com/clojure/clojure/blob/master/src/jvm/clojure/lang/APersistentMap.java
It checks if the other object is not map then return false.
user=> (seq {1 2})
([1 2])
user=> (type {1 2})
clojure.lang.PersistentArrayMap
It seems to me that this example points out a slight inconsistency in the notion of equality of values in clojure for this case where they are different types derived from the same type (by the seq function). It could well be argued that this is not inconsistent because it is comparing a derived type to the type it is derived from and I can understand that if the same logic was applied to this same example using vectors (note at the bottom)
the contents are the same type:
user> (type (first (seq {1 2})))
clojure.lang.MapEntry
user> (type (first {1 2}))
clojure.lang.MapEntry
user> (= (type (first {1 2})) (type (first (seq {1 2}))))
true
user> (= (first {1 2}) (first (seq {1 2})))
true
the sequences have the same values
user> (map = (seq {1 2}) {1 2})
(true)
but they are not considered equal
user> (= {1 2} (seq {1 2}))
false
this is true for longer maps as well:
user> (map = (seq {1 2 3 4}) {1 2 3 4})
(true true)
user> (map = (seq {1 2 3 4 5 6}) {1 2 3 4 5 6})
(true true true)
user> (map = (seq {9 10 1 2 3 4 5 6}) {9 10 1 2 3 4 5 6})
(true true true true)
even if they are not in the same order:
user> (map = (seq {9 10 1 2 3 4 5 6}) {1 2 3 4 5 6 9 10})
(true true true true)
but again not if the containing types differ :-(
user> (= {1 2 3 4} (seq {1 2 3 4}))
false
EDIT: this is not always true see below:
to work around this you can convert everything to a seq before comparison, which is (I presume) safe because the seq function always iterates the whole data structure the same way and the structures are immutable values and a seq of a seq is a seq
user> (= (seq {9 10 1 2 3 4 5 6}) {1 2 3 4 5 6 9 10})
false
user> (= (seq {9 10 1 2 3 4 5 6}) (seq {1 2 3 4 5 6 9 10}))
true
vectors are treated differently:
user> (= [1 2 3 4] (seq [1 2 3 4]))
true
Perhaps understanding the minor inconsistencies is part of learning a language or someday this could be changed (though I would not hold my breath)
EDIT:
I found two maps that produce different sequences for the same value so just calling seq on the maps will not give you proper map equality:
user> (seq (zipmap [3 1 5 9][4 2 6 10]))
([9 10] [5 6] [1 2] [3 4])
user> (seq {9 10 5 6 1 2 3 4})
([1 2] [3 4] [5 6] [9 10])
user>
here is an example of what I'm calling proper map equality:
user> (def a (zipmap [3 1 5 9][4 2 6 10]))
#'user/a
user> (def b {9 10 5 6 1 2 3 4})
#'user/b
user> (every? true? (map #(= (a %) (b %)) (keys a)))
true
(seq some-hash-map) gives you a sequence of entries (key/value pairs).
For example:
foo.core=> (seq {:a 1 :b 2 :c 3})
([:a 1] [:c 3] [:b 2])
which is not the same as [:a 1 :b 2 :c 3].

From a set to a sorted set

I'm hitting some kind of mental roadblock related to destructuring...
(sorted-set 4 2 5)
gives:
#{2 4 5}
But How I can get that same sorted set from:
((fn [???] (sorted-set ???)) [4 2 5])
or from a set (unsorted) passed as an argument:
((fn [???] (sorted-set ???)) #{4 2 5})
I tried several destructuring, I was thinking that:
((fn [#{elems}] (sorted-set elems)) #{4 2 5})
would work but this is not the case.
I'd like to know how to do that and it would be even better if you could explain why my way of reasoning is bogus...
the sorted-set function param is a var-arg: [& keys], which means that if you have a collection and you want to call that function you need to use the apply function like:
user=> (def v [4 8 2])
#'user/v
user=> (apply sorted-set v)
#{2 4 8}
The signature of a function that accepts a collection and returns a sorted-set would be like:
user=> (defn to-ss [c] (apply sorted-set c))
#'user/to-ss
user=> (to-ss v)
#{2 4 8}
You could also create an empty sorted-set and then add all the elements of the collection to it:
(defn to-ss [c] (into (sorted-set) c))
Note that if you were to define your function with a var-arg parameter, you will need to use apply to both call the function and create the sorted-set:
user=> (defn to-ss [& items] (apply sorted-set items))
#'user/to-ss
user=> (apply to-ss v)
#{2 4 8} <-- expected value
user=> (to-ss v)
#{[4 8 2]} <-- sorted-set with just one value