I would like to use Clojure hashmaps to define generalized vectors. In this picture, the hashmap {:x 3 :y 2 :z (- 3)} represents the symbolic expression 3x + 2y - 3z.
I would like a function gen+ which acts as an addition operation on hashmaps, and which satisfies the following constraints:
Applying gen+ to two hashmaps with overlapping keys returns a hashmap with those key values summed. e.g.,
(gen+ {:x 1} {:x 2})
;= {:x 3}
Applying gen+ to two hashmaps with distinct keys returns a hashmap with those key-values combined. e.g.,
(gen+ {:x 1} {:y 2})
;= {:x 1 :y 2}
The empty hashmap {} is an additive identity for the gen+ function. e.g.,
(gen+ {:x 1} {})
:= {:x 1}
By the above constraints, any hashmap with zero entries is also an additive identity. For example, {:z 0}. Owing to this redundancy, the gen+ function should always return hashmaps without any 0 values. e.g.,
(gen+ {:x 1 :y 0} {:z 0})
;= {:x 1}
Clojure interprets missing keys as having value nil, rather than 0, as with ({:x 3} :y) ;= nil. Consequently, gen+ should treat nil values the same way as 0. e.g.,
(gen+ {:x 1 :y 0} {:x nil :y nil})
{:x 1}
My Question: How can we write a function gen+ which satisfies the above constraints? Once this is in place, is it possible to overload the + operator with the gen+ function, enabling elementary addition with hashmaps?
It should be apparent why this formalism treats hashmaps as generalized vectors. Clojure interprets a vector like [x0 x1 x2] almost the same as the hashmap {:0 x0 :1 x1 :2 x2}, with a slight difference that I don't quite understand. Consequently, if we apply gen+ to two vectors, then it should be effectively the same as + applied to them. This empowers us to easily work with sparse vectors, as well as add vectors of different sizes. e.g.,
(gen+ [0 0 0 4] [0 0 0 0 0 0 0 0 0 9])
;= {:4 4 :9 9}
Here's what I don't understand about hashmaps and vectors. If I call a hashmap as a function, I need to apply a key argument like :2. On the other hand, if I call a vector as a function, I need to apply an index argument like 2. e.g.,
({:2 2} :2)
;= 2
([0 1 2] 2]
;= 2
Even if you can't help with the gen+ function, could you please explain why hashmaps and vectors behave differently when called as functions?
The answer to you first question is to use merge-with See http://clojuredocs.org/clojure_core/clojure.core/merge-with
From the docs:
Returns a map that consists of the rest of the maps conj-ed onto
the first. If a key occurs in more than one map, the mapping(s)
from the latter (left-to-right) will be combined with the mapping in
the result by calling (f val-in-result val-in-latter).
You can then write a function to combine the merged values (maybe +). Then toss the nil and 0 values.
Overloading + is not really a good idea in Clojure as it isn't really overloading but replacing.
The difference between a map and vector is a like an array while a map is more a dictionary of key value pairs. Both structures are also functions of them members. For a vector/array whose members are access by offset, it makes sense for it to accept an offset. For a map/dictionary whose members as accessed by key, it makes sense for it to accept a key.
This will first remove the nil and 0 values and then add the maps with merge-with, as suggested by M Smith:
(defn filter-vals
[pred m]
(into {} (filter (fn [[k v]] (pred v))
m)))
(defn gen+
[m1 m2]
(letfn [(keep-val? [val]
(and (not (nil? val))
(not (zero? val))))]
(merge-with +
(filter-vals keep-val? m1)
(filter-vals keep-val? m2))))
You need to filter out any nils before you start adding - otherwise you'll eventually end up trying to add nil to a number, which is an error. However, it's possible for the gen+ I outlined to return an entry with a 0 value (consider (gen+ {:x 1} {:x -1})).
If that's possible based on your inputs, and needs to be avoided, you need to add another filter after the merge:
(defn gen+
[m1 m2]
(letfn [(keep-val? [val]
(and (not (nil? val))
(not (zero? val))))]
(->> (merge-with +
(filter-vals keep-val? m1)
(filter-vals keep-val? m2))
(filter-vals keep-val?))))
And, finally, here's a version that can handle a variable number of input maps:
(defn gen+
[& maps]
(letfn [(keep-val? [val]
(and (not (nil? val))
(not (zero? val))))]
(->> (apply merge-with
+
(map #(filter-vals keep-val? %) maps))
(filter-vals keep-val?))))
So, for instance:
(gen+ {:x 1} {:x 3} {:x 4 :y 5}) ;=> {:x 8, :y 5}
Regarding the difference between maps and vectors, think of it this way: maps are functions from keys to values, whereas vectors are functions from indices to values. In both cases if you have a "key" (which for a vector is the index) you can use that to look up the associated value.
I agree with the answers and comments you've gotten so far about overloading clojure.core/+ (I don't recommend it); that said, here's one way to sort of do it, with plenty of caveats:
(ns overload.example
(:refer-clojure :exclude [+])
(:require [clojure.core :as clj]))
(in-ns 'overload.example)
(defn gen+ ...)
(defn + [& xs]
(if (every? number? xs)
(reduce clj/+ 0 xs)
(apply gen+ xs)))
Related
> (conj [0] 1 2 3)
[0 1 2 3]
> (conj {:a "ei"} {:b "bi"})
{:b "bi", :a "ei"}
>
See, when it acts on vector, it puts 1,2,3 at end of it.
Whereas it put :b "bi" in front of :a map k-v pair
Why is this?
thanks
As with many hashed maps implementations, Clojure's hashed maps do not sort their entries, not retain the order in which they were inserted. This allows for better performance.
Note that conj does not have general ordering semantics either (it has ordering semantics for some concrete types, such as vectors).
You don't have to go as far as maps to get inconsistent behaviour from conj:
(conj [1] 2) ; [1 2]
(conj (list 1) 2) ; (2 1)
Hash maps have no defined order. But, for any map,
the seq of entries will always be the same
the vals and keys will be in consistent order.
Thus, for map m,
(= (keys m) (map key m))
(= (vals m) (map val m))
(= m (zipmap (keys m) (vals m)))
Currently, this sequence seems to be independent of insertion order. I tested this on sets by randomly shuffling random integers.
My goal is to add up values in a collection of maps until a value is reached in one of the map values. I tried figuring it out using this example but it didn't touch on how I could only grab part of the list. Then return the collection without the values that went over. Something like this
(def foo '({:key 1, :value 2} {:key 1, :value 2} {:key 1, :value 2})
(defn addValuesUp [foo]
(take-while (< ((apply merge-with + foo) :value) 4) foo))
and have it return something like this
'({:key 1, :value 2} {:key 1, :value 2})
Instead I get an error Boolean cannot be cast to clojure.lang.IFn
The way I will solve the problem will be by adding to the maps a new key with the accumulation of all the previous values so you can do a simple take-while:
(defn sums-of-values [c]
(reductions + c))
(defn with-accum [c]
(map #(assoc %1 :accum %2)
c
(sums-of-values (map :value c))))
Now that the maps have a :accum slot, you can use take-while:
(take-while (comp (partial >= 4)
:accum)
(with-accum foo))
Have a look at the documentation for take-while:
clojure.core/take-while
([pred coll])
Returns a lazy sequence of successive items from coll while
(pred item) returns true. pred must be free of side-effects.
pred in this case is a function that returns a boolean. In your code, your first argument to take-while is not a function but an expression instead.
This is why you get the error ClassCastException java.lang.Boolean cannot be cast to clojure.lang.IFn. This error is telling you that where Clojure expects a function (IFn) but it found a boolean (the result of your expression).
Once you turn that into a function, you should make progress. Some more work might be required in the function implementation however.
In Clojure, the square brackets are a shorthand for defining vectors:
user=> (vector 'a 'b 'c)
[a b c]
user=> ['a 'b 'c]
[a b c]
The documentation page for vector speaks of the long way and the short way of defining vectors.
However, in defn and doseq there seems to be a difference.
user=> (doseq [x (range 1 4)] (printf "%d\n" x))
1
2
3
nil
user=> (doseq (vector 'x (range 1 4)) (printf "%d\n" x))
IllegalArgumentException doseq requires a vector for its binding in user:1 clojure.core/doseq (core.clj:2935)
What accounts for this difference? Are the square brackets given special status in the reader, or do they sugar some particular form?
vector is evaluated after macroexpansion, while [] is evaluated at read time, before macros are expanded. In your second case, the doseq macro does not see a vector, it sees a list starting with the symbol vector, since macros are expanded before regular functions are evaluated.
I would like to reduce the following seq:
({0 "Billie Verpooten"}
{1 "10:00"}
{2 "17:00"}
{11 "11:10"}
{12 "19:20"})
to
{:name "Billie Verpooten"
:work {:1 ["10:00" "17:00"]
:11 ["11:10" "19:20"]}}
but I have no idea to do this.
I was think about a recursive function that uses deconstruction.
There's a function for reducing a sequence to something in the standard library, and it's called reduce. Though in your specific case, it seems appropriate to remove the special case key 0 first and partition the rest into the pairs of entries that they're meant to be.
The following function gives the result described in your question:
(defn build-map [maps]
(let [entries (map first maps)
key-zero? (comp zero? key)]
{:name (val (first (filter key-zero? entries)))
:work (reduce (fn [acc [[k1 v1] [k2 v2]]]
(assoc acc (keyword (str k1)) [v1 v2]))
{}
(partition 2 (remove key-zero? entries)))}))
Just for variety here is a different way of expressing an answer by threading sequence manipulation functions:
user> (def data '({0 "Billie Verpooten"}
{1 "10:00"}
{2 "17:00"}
{11 "11:10"}
{12 "19:20"}))
user> {:name (-> data first first val)
:work (as-> data x
(rest x)
(into {} x)
(zipmap (map first (partition 1 2 (keys x)))
(partition 2 (vals x))))}
teh as-> threading macro is new to Clojure 1.5 and makes expressing this sort of function a bit more concise.
I am not to Clojure and attempting to figure out how to do this.
I want to create a new hash-map that for a subset of the keys in the hash-map applies a function to the elements. What is the best way to do this?
(let
[my-map {:hello "World" :try "This" :foo "bar"}]
(println (doToMap my-map [:hello :foo] (fn [k] (.toUpperCase k)))
This should then result a map with something like
{:hello "WORLD" :try "This" :foo "BAR"}
(defn do-to-map [amap keyseq f]
(reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq))
Breakdown:
It helps to look at it inside-out. In Clojure, hash-maps act like functions; if you call them like a function with a key as an argument, the value associated with that key is returned. So given a single key, the current value for that key can be obtained via:
(some-map some-key)
We want to take old values, and change them to new values by calling some function f on them. So given a single key, the new value will be:
(f (some-map some-key))
We want to associate this new value with this key in our hash-map, "replacing" the old value. This is what assoc does:
(assoc some-map some-key (f (some-map some-key)))
("Replace" is in scare-quotes because we're not mutating a single hash-map object; we're returning new, immutable, altered hash-map objects each time we call assoc. This is still fast and efficient in Clojure because hash-maps are persistent and share structure when you assoc them.)
We need to repeatedly assoc new values onto our map, one key at a time. So we need some kind of looping construct. What we want is to start with our original hash-map and a single key, and then "update" the value for that key. Then we take that new hash-map and the next key, and "update" the value for that next key. And we repeat this for every key, one at a time, and finally return the hash-map we've "accumulated". This is what reduce does.
The first argument to reduce is a function that takes two arguments: an "accumulator" value, which is the value we keep "updating" over and over; and a single argument used in one iteration to do some of the accumulating.
The second argument to reduce is the initial value passed as the first argument to this fn.
The third argument to reduce is a collection of arguments to be passed as the second argument to this fn, one at a time.
So:
(reduce fn-to-update-values-in-our-map
initial-value-of-our-map
collection-of-keys)
fn-to-update-values-in-our-map is just the assoc statement from above, wrapped in an anonymous function:
(fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key))))
So plugging it into reduce:
(reduce (fn [map-so-far some-key] (assoc map-so-far some-key (f (map-so-far some-key))))
amap
keyseq)
In Clojure, there's a shorthand for writing anonymous functions: #(...) is an anonymous fn consisting of a single form, in which %1 is bound to the first argument to the anonymous function, %2 to the second, etc. So our fn from above can be written equivalently as:
#(assoc %1 %2 (f (%1 %2)))
This gives us:
(reduce #(assoc %1 %2 (f (%1 %2))) amap keyseq)
(defn doto-map [m ks f & args]
(reduce #(apply update-in %1 [%2] f args) m ks))
Example call
user=> (doto-map {:a 1 :b 2 :c 3} [:a :c] + 2)
{:a 3, :b 2, :c 5}
Hopes this helps.
The following seems to work:
(defn doto-map [ks f amap]
(into amap
(map (fn [[k v]] [k (f v)])
(filter (fn [[k v]] (ks k)) amap))))
user=> (doto-map #{:hello :foo} (fn [k] (.toUpperCase k)) {:hello "World" :try "This" :foo "bar"})
{:hello "WORLD", :try "This", :foo "BAR"}
There might be a better way to do this. Perhaps someone can come up with a nice one-liner :)