zengod
zengod

Reputation: 1174

How to create this hashmap?

Basically what I'm trying to do is print a hashmap that contains keys that are characters in a string, and these keys have value 1. For example, string "aabbce" should give a dictionary {:a 1 :b 1 :c 1}. The following is my attempt but it just prints the empty hashmap

(defn isValid [s]
    (def dict {})

    (map (fn [x] ((assoc dict :x 1))) (seq s))

    (println dict)

)

Upvotes: 0

Views: 127

Answers (4)

Thumbnail
Thumbnail

Reputation: 13473

You've broken several guidelines of Clojure programming:

  • Don't print a result - return it.
  • Don't use def for locals. Use let.
  • Don't force the use of keywords. Map keys don't need to be keywords.
  • Don't try to assign to names. Use the amended result of a function.

To do the last thing here, use reduce instead of map. And the seq is redundant: functions such as map and reduce will treat a string as a sequence of characters. So ...

(defn isValid [s]
  (reduce (fn [dict x] (assoc dict x 1)) {} s))

For example ...

=> (isValid "aabbce" )
{\a 1, \b 1, \c 1, \e 1}

The local dict and the initial value {} have been captured by the reduce.


Why map to 1? Is this just a set in disguise? If so, ...

(defn isValid [s]
  (set s))

Or just ...

(def isValid set)

For example,

=> (isValid "aabbce" )
#{\a \b \c \e}

You'll find this with functional programming. Boilerplate code melts away like snow in a Chinook wind.


A final trivial gripe. isValid is camel case. The Clojure conventions are

  • kebab case and
  • trailing ? for predicates.

So valid? instead of isValid.

Upvotes: 2

Denis Fuenzalida
Denis Fuenzalida

Reputation: 3346

There are lots of ways to implement this leveraging on the large number of functions in the Clojure core library. This is one of the common issues that happens to every new dev coming to Clojure: You think you need to write a function yourself, but actually something already exists, it's just that you don't know it's name yet, so the Clojure cheat-sheet might come handy.

Let's start with the string aabbce. You want to remove the duplicates, so (set "aabbce") would read the string as a collection of chars and create a set out of them. You can use the map function to take each character and turn it into a keyword. The problem is that the keyword function takes a string, not a char, so we need to use str on each char first.

Once we have a sequence of keywords, one simple way of constructing the map is using the frequencies function, it will create a hash-map of each element in the collection of keywords with the keyword as the key, and the number of times it appears in the collection as the value, and since we already removed the duplicates, we're guaranteed that each value will be just 1.

Putting everything together:

(defn is-valid? [s]
  (frequencies
   (map (comp keyword str)  ;; Turns \a into :a for every letter in the set
        (set s))))          ;; remove duplicates, yields: (\a, \b, \c, \e)

;; or, using the ->> macro:

(defn is-valid? [s]
  (->> (set s)                   ;; turn string into set of chars
       (map (comp keyword str))  ;; turn chars into keywords
       frequencies))             ;; compute occurrences hash-map

;; (println (is-valid? "aabbce"))
;; => {:a 1, :b 1, :c 1, :e 1}

Now, the name isValid suggests that you want to use this function as a predicate (eg. return true or false depending on the input). Maybe want to build a function that checks that a string is composed of certain letters?

Upvotes: 0

leetwinski
leetwinski

Reputation: 17859

another way is:

(zipmap (map (comp keyword str) "abc") (repeat 1))

;;=> { :a 1 :b 1 :c 1 }

Upvotes: 5

jas
jas

Reputation: 10865

> (into {} (for [c "aabbce"] [(keyword (str c)) 1]))
{:a 1, :b 1, :c 1, :e 1}

into {} ... sequence of pairs ... is often a convenient way to create hashmaps. For example

> (into {} [[:x 1] [:y "foo"]])
{:x 1, :y "foo"}

and for [item collection] [(key-from item) (value-from item)] can be a nice way to iterate over a collection to create that list of key-value pairs.

> (for [color ["red" "blue" "green"]] [(clojure.string/upper-case color) (count color)])
(["RED" 3] ["BLUE" 4] ["GREEN" 5])

I find that putting those together is often the trick for when I want to create a hashmap:

> (into {} (for [color ["red" "blue" "green"]] [(clojure.string/upper-case color) (count color)]))
{"RED" 3, "BLUE" 4, "GREEN" 5}

Upvotes: 1

Related Questions