bag24
bag24

Reputation: 1

How to populate a hashmap through iteration or recursion in clojure?

I am new to clojure and can't really wrap my head around adding to a hashmap without using a typical for loop like other languages would. For example, if I have the following code segment:

(def empty-hashmap {})
(def big-hashmap (assoc empty-hashmap 1 2)) 

how would I iterate through and add 300 separate elements to the big hashmap? In this case I want my code to look something like

(def empty-hashmap {})
(def big-hashmap (assoc empty-hashmap n (abundance n)))

where n is the numbers 1 to 300 and it populates 300 elements into the big hashmap.

Upvotes: 0

Views: 145

Answers (3)

Matias Bjarland
Matias Bjarland

Reputation: 4482

Just wanted to add a reduce code example to @amalloy's answer:

  (let [keys [:a :b :c :d :e :f :g]
        vals [1  2  3  4  5  6  7]]
      (map vector keys vals))

  => ([:a 1] [:b 2] [:c 3] [:d 4] [:e 5] [:f 6] [:g 7])

  
  (let [keys [:a :b :c :d :e :f :g]
        vals [1  2  3  4  5  6  7]]
    (reduce
      (fn [a [k v]] (assoc a k v))
      {}
      (map vector keys vals)))
  => {:a 1, :b 2, :c 3, :d 4, :e 5, :f 6, :g 7}

This uses reduce with an "accumulator function". For each iteration, the function is called with the old value of the map and a new key-value pair. It assoc's in the new value and returns a new map with an extra key-value pair. The initial value is provided as the second argument to reduce (the {} empty map).

Upvotes: 0

amalloy
amalloy

Reputation: 91917

As Alan Thompson says, reduce is the general purpose tool for iterating over a sequence and accumulating a result. But if you need to make many "independent" changes, as here you associate keys in a map with values that don't depend on anything but the key, there are better tools. map is the general purpose tool for producing a new sequence based on an old one, and into is for turning sequences into maps. So, you can write

(into {}
      (map (fn [n] [n (abundance n)])
           (range 1 301)))

Note that (fn [n] [n (abundance n)]) could also be written (juxt identity abundance), though it's up to you which you find clearer.

Personally I don't like writing (map (fn [n] ...)) - usually if you need a (one-argument) lambda, for is a better tool than map. The into/for pairing is very common for tasks like this:

(into {}
      (for [n (range 1 301)]
        [n (abundance n)]))

I would not at all recommend using an atom just for a "more imperative feel". There are good times to use an atom, but beginners don't run into them super quickly, and this isn't one of them.

Upvotes: 3

Alan Thompson
Alan Thompson

Reputation: 29958

Reduce is a good option here, starting from my favorite template project:

(ns tst.demo.core
  (:use demo.core tupelo.core tupelo.test))

(verify
  (let [result (reduce  (fn [accum item]
                          (assoc accum item (str "item " item)))
                 {} ; initial empty map
                 (range 5)) ; list of items to add
                 ]
    (is= result
      {0 "item 0"
       1 "item 1"
       2 "item 2"
       3 "item 3"
       4 "item 4"})))

If you want a more imperative-style solution, you can always accumulate the result into an atom:

(verify
  (let [accum (atom {})]
    (doseq [idx (range 5)]
      (swap! accum
        #(assoc % idx (str "item " idx))))
    (is= @accum
      {0 "item 0"
       1 "item 1"
       2 "item 2"
       3 "item 3"
       4 "item 4"})))

Upvotes: -2

Related Questions