2

I am new to clojure programming and need a little help. Say I have a vector of maps as shown.

(def testVMap 
  [ {:name "AAA" :rate 100 } 
    {:name "GEICO" :rate 120 }
    {:name "PROGRESSIVE" :rate 118} ] )

How do I do some operations like finding the rate of AAA or to see if I have the rates for, say ALLSTAR, if rates are not present I would want to able to add it, I know I can add that using into or conj. But my main concern is finding my map has a particluar name and if name is present I need to update the rate to a new value.

Mars
  • 8,689
  • 2
  • 42
  • 70
sunny
  • 69
  • 1
  • 11

3 Answers3

2

Here's one method:

(some #(if (= "GEICO" (:name %)) %) testVMap)

That says: Return the first thing in testVMap whose :name is equal to "GEICO". #(... % ...) defines a function, with % as its function parameter. This part (:name %) returns the value of the :name value for each map.

If there might be multiple "GEICO" maps, and you want them all, use filter instead of some.

Here's another version:

 (some #(and (= "GEICO" (:name %)) %) testVMap)

You may want to use update as well.

EDIT: e.g.:

(map #(if (= "GEICO" (:name %))
          (update % :rate inc)
          %)
testVMap)

Here inc is the function that adds 1 to a number. You should replace that with a function that performs the transformation that you want, such as #(+ 10 %), or (partial + 10), which is the same thing.

The function defined by #(...) checks whether the value of :name is "GEICO", and then if so, updates the rate with the function that you provide (inc in this case); otherwise, #(...) returns the map unchanged.

I'll leave it to you to look up the documentation of update, if it's not self-explanatory from the context.

You could also use mapv instead of map.

You'll no doubt want to build all of this into one or more functions. Remember that functions are things in Clojure--they're "data", first class objects, etc. So where I used inc, you can have a function parameter, and the function you define can accept another function as its argument, which the first function will then use in the update part of the code (instead of inc).

About adding a map if the one you want doesn't exist: If you're not dealing with a long sequence of maps, I'd consider doing this in a different step. See Andre's answer for names of functions that are useful for testing whether a collection contains an element.

However, note that testing whether a vector contains something that's not there requires Clojure to look through the entire vector. It might be better to use a Clojure set rather than a vector, or consider using an outer map with (for example) :aaa, :geico, etc. as keys. In that case, take a look at the function update-in.

Mars
  • 8,689
  • 2
  • 42
  • 70
  • This says nothing about how to actually *update* an item in the list. Just how to find an item. – Tim Pote Sep 08 '15 at 18:31
  • Yes. That was the basic question--how to find it. I added a tip about `update` now, but that's extra. – Mars Sep 08 '15 at 18:33
  • Thank you. But I also want to update, say if "GEICO" was found I want to increment rate by 10 – sunny Sep 08 '15 at 19:24
  • @Mars, `(some #(and (= "GEICO" (:name %))) testVMap)` will return true and not the element. The `and` will get evaluated as `(and true)` or `(and false)`. Your original `if` is correct. If anything, you could replace the `if` with a `when` to denote there is only one branch. An `if` raises the expectation of an `else`. – Amith George Sep 09 '15 at 00:21
  • @Mars, ah, you must have meant `#(and (= "GEICO" (:name %)) %)`. Yeah, that ought to work. – Amith George Sep 09 '15 at 00:30
  • @AmithGeorge you're right. Thanks. I've fixed it now with your correction. – Mars Sep 09 '15 at 02:37
1

Instead of telling you straight away how to do this, let me show you how you'd find this kind of function.

These two website structure the functions pretty well:

Now what you're dealing with is in general a collection and if that doesn't do what you want you can look for specific vector operations. But the first should be collection. You'll then see Content Tests section in both sites.

Now you have the following candidates:

distinct? empty? every? not-every? some not-any?

The right candidate here is some:

(some (comp (partial = "AAA") :name) testVMap)
;; => true
ClojureMostly
  • 4,652
  • 2
  • 22
  • 24
  • This says nothing about how to actually *update* an item in the list. Just how to find an item. Also, `some` here doesn't return the item. It returns true/false base on whether the item exists at all. – Tim Pote Sep 08 '15 at 18:31
  • Don't forget my favorite, the Clojure Cheatsheet: http://jafingerhut.github.io/cheatsheet/clojuredocs/cheatsheet-tiptip-cdocs-summary.html – Alan Thompson Sep 08 '15 at 19:43
1

Seeing if an item exists

(first
  (filter
    (comp #{"AAA"} :name)
    [{:name "AAA" :rate 100}
     {:name "GEICO" :rate 120}
     {:name "PROGRESSIVE" :rate 118}]))

Updating your structure

If order isn't important, you can reorganize the structure into a map of name->rate. For example:

{"AAA" 100 "GEICO" 120}

Then you can use update-in or assoc to update the rate.

If order is important, you can do something like this:

(mapv
  (fn [{:keys [name] :as v}]
    (if (= name "AAA")
      (assoc v :rate 500)
      v))
  [{:name "AAA" :rate 100}
   {:name "GEICO" :rate 120}
   {:name "PROGRESSIVE" :rate 118}])

Adding to your structure

You can add to it with a simple conj. So you do something like

(if (has-item? struct) ;; has-item would be defined using the first section of this answer
  (update-item struct ...) ;; update-item using the second section of this answer
  (conj struct {:name "AAA" :rate 100}))

There are other options of course, but these would be the first that I would go to.

Tim Pote
  • 27,191
  • 6
  • 63
  • 65
  • Thanks I can update the rate if AAA is found but how do I add a new map to the vector if AAA is not found? – sunny Sep 08 '15 at 19:47