The namespace clojure.set has useful functions when we work with sets. One of the functions is the index function. The index function can be used for a set with map elements to create a new map based on distinct values for one or more keys. The first argument of the function is the set we transform and the second argument is a vector of one or more keys we want to index on. The keys in the new map are maps themselves. The value for each key is a set of maps that have the given keyword/value combination. The new map can be easily queried with the get function to get the values for a key.
In the next example code we see how we can use the clojure.set/index function to first transform a set with map elements to the new map and how to work with the resulting map:
(ns mrhaki.set.index
(:require [clojure.test :refer [is]]
[clojure.set :refer [index]]))
(def languages #{{:platform :jvm :name "Clojure"}
{:platform :jvm :name "Groovy"}
{:platform :native :name "Ruby"}
{:platform :jvm :name "JRuby"}
{:platform :native :name "Rust"}})
;; index function returns a map with a key for
;; each unique key/value combination for the keys
;; passed as second argument.
;; The value of each key is a set of the
;; map that comply with the key/value combination.
(is (= {{:platform :jvm} #{{:platform :jvm :name "Clojure"}
{:platform :jvm :name "Groovy"}
{:platform :jvm :name "JRuby"}}
{:platform :native} #{{:platform :native :name "Ruby"}
{:platform :native :name "Rust"}}}
(index languages [:platform])))
;; We can use all collection functions on the map result
;; of the index function.
(is (= ["Clojure" "Groovy" "JRuby"]
(map :name (get (index languages [:platform]) {:platform :jvm}))))
;; Set with sample data describing a shape
;; at a x and y location.
(def data #{{:shape :rectangle :x 100 :y 100}
{:shape :circle :x 100 :y 100}
{:shape :circle :x 100 :y 0}
{:shape :circle :x 0 :y 100}})
;; We can use multiple keys as second argument of the
;; index function if we want to index on values of
;; more thane one key.
(is (= {{:x 0 :y 100} #{{:shape :circle :x 0 :y 100}}
{:x 100 :y 0} #{{:shape :circle :x 100 :y 0}}
{:x 100 :y 100} #{{:shape :circle :x 100 :y 100}
{:shape :rectangle :x 100 :y 100}}}
(index data [:x :y])))
(is (= #{{:shape :circle :x 100 :y 100}
{:shape :rectangle :x 100 :y 100}}
(get (index data [:x :y]) {:x 100 :y 100})))
Written with Clojure 1.10.1.