Part 6 — A complete beginner's guide to Computer Programming with Clojure: Lists, Sets, Vectors, and Maps.

Image for post
Image for post
Photo by Paul Melki on Unsplash

In this lesson, we will demonstrate all the different ways you can store and retrieve stuff with Clojure.

Lists

A list is simply a collection of data items. There does not have to be any relation between the items and you can mix together items. For example, a list can contain numbers and strings.

Type all the following in a REPL and observe the results.

(println (list “Yoda” 401.0 true))

Here we have a list of three items, string, a floating-point number, and a boolean value: true. The list command in the inner brackets creates our list and then the outer brackets contain the println command to print the results to the terminal. Recall, as nothing is actually evaluated, you will see nil.

Image for post
Image for post
(println (first (list 20 30 40)))
Image for post
Image for post

Here the first item (20) is printed.

(println (rest (list 20 30 40)))
Image for post
Image for post

This prints all the rest i.e., everything but the first item.

(println (nth (list 20 30 40) 1))
Image for post
Image for post

This prints the number in position 1. 20 is located in the first position of 0 (zero). Therefore, position 1 holds 30.

(println (list* 9 8 [ 7 6 ]))
Image for post
Image for post

This command creates a new list from two separate items. The items in the square brackets are appended to the items after the star *.

(println (cons 2 (list 4 6)))
Image for post
Image for post

This is another way to create a new list. Here, cons simply places a new item to the front of an existing list to make a new list.

Sets

A set is different to a list, in that a set can only contain unique values. This can be a powerful tool in the Coder’s arsenal. For instance, I once needed to create code to identify a specific device present in more than one location. I treated each location as a set. For each location, all devices present displayed a unique identifier in the form of a hardware MAC address. Only one specific device (the one I was after) would be present in all locations. In short, the program looked for the one MAC address present in all sets.

Again, type all the following in a REPL and observe the results.

(println (set ‘(1 1 1 2 2 2 3 3 3 4 4 4)))
Image for post
Image for post

To prove the point, our set only contains unique values and is prefixed with a hash #.

(println (get (set ‘(3 7 5 30)) 30))
Image for post
Image for post

Let’s just get the number 30 from our set.

(println (conj (set ‘(3 7 5 30)) 50))
Image for post
Image for post

Conjoin (combine/join) two sets together to make one set containing all the values.

(println (contains? (set ‘(3 7 5 30)) 30))
Image for post
Image for post

Does the set contain the number 30? It does!

(println (disj (set ‘(3 7 5 30)) 30))
Image for post
Image for post

A new set is created after removing the number 30.

Vectors

In many ways, a Vector is similar to a List. Both a List and a Vector are heterogeneous i.e. you can mix together different elements like numbers and strings. Also, both are indexed so you can easily identify the nth item. The main difference is a Vector stores the elements contiguously in computer memory i.e. next to each other. This makes Vectors fast!

(println (get (vector 3 “Skywalker” 5 30) 1))
Image for post
Image for post

Get the element indexed in position 1.

(println (conj (vector 3 7 5 30) 50))
Image for post
Image for post

For this Vector, we will bring in the number 50.

(println (pop (vector 3 7 5 30)))
Image for post
Image for post

The pop command is applied directly to the computer memory and takes away the last element in memory.

(println (subvec (vector 3 7 5 30) 1 2 ))
Image for post
Image for post

The subvector command creates a new vector from the element stored in the first index (1) and just before but not including the next index point (2).

(println (subvec (vector 3 7 5 30) 1 4 ))
Image for post
Image for post

This is interesting as our subvector has captured all elements to the end. If you had used an index range of 1 5, you would have got an error indicating the result is out of range.

Maps

A Map is a powerful tool for storing and retrieving data. A Map is created when elements are mapped to a key, known as a key-value pair. In short, you can look up the key to find the associated value.

Image for post
Image for post
Photo by British Library on Unsplash

Test out the following code snippets in a REPL.

Try to figure out how key-value pairs can be applied to a programming problem.

(println (hash-map “Name” “Yoda” “Age” 900))
Image for post
Image for post

A simple hash-map created.

(println (sorted-map :color “green” :species “Minch” :first-name “Yoda” ))
Image for post
Image for post

A sorted-map orders the map by the index. In this case, it’s in alphabetic order: c, f, s.

(println (get (hash-map “name” “Yoda”) “name” ))
Image for post
Image for post

Get the value associated with the index of name.

(println (find (hash-map “name” “Yoda” “age” 900) “name”))
Image for post
Image for post

Similar to get but returns the key as well and delivers the result as a vector.

(println (contains? (hash-map “name” “Yoda” “age” “900”) “age”))
Image for post
Image for post

Check to see if the map contains the string “age”.

(println (keys (hash-map “name” “Yoda” “age” “900”)))
Image for post
Image for post

Pull out just the keys.

(println (vals (hash-map “name” “Yoda” “age” “900”)))
Image for post
Image for post

Pull out just the values.

Simple Database

Image for post
Image for post
Photo by Jan Antonin Kolar on Unsplash

Let’s create a simple database. Our database will take a book title and will collect together any associated data. For example, the book title will have an ISBN, Author, Publish year etc. Interestingly, books always have a title. However, they may have multiple authors or no author and referred to as anonymous. In addition, ISBN numbers change with publication dates, book versions, publishers, etc. Also, people often remember titles more often than they remember the author, least of all the ISBN number!

Our database will take a map of keys and values. Keys begin with a colon : and are paired with a value. In the example, the key pairs are nested under a primary value.

Type the following into a REPL

(def Library{“The Caves of Steel” { :ISBN “0–553–29340–0”                        :Title “The Caves of Steel”                        :Author “Isaac Asimov”                        :Year “1954”}“After Yesterdays Crash” { :ISBN “0–14–024085–3”                           :Title “After Yesterdays Crash”                           :Author “William Gibson”}})
Image for post
Image for post

Our database is called Library, hence def Library. The word def is a keyword that is used to save our data. In our example, def saves, or associates, the data to a symbol called Library.

To be a database, our program must possess CRUD functionality. CRUD stands for Create, Read, Update, and Delete.

READ

Let’s look for the author associated with the title ‘The Caves of Steel’.

(get-in Library [“The Caves of Steel” :Author] )

Here, we use the built-in function get-in to search for a Map vector with the string ‘The Caves of Steel’ and then any nested value attributed to the Key :Author. As expected, “Isaac Asimov” will be returned.

Image for post
Image for post

Should we look for something that is not there?

e.g. (get-in Library [“The Caves of Steel” :Publisher]), the program would only return nil. Recall, Clojure always has to return something even if it is nil.

We could combine commands to retrieve more data like so

(println “The author is “
(get-in Library [“The Caves of Steel” :Author] ) “published “
(get-in Library [“The Caves of Steel” :Year] ))

Unfortunately, our function has a lot of repetition e.g. ‘get-in’, ‘The Caves of Steel’, Library, etc.

let

The keyword let allows us to associate values and even other functions. For example, println is a function.

Look at the following

(let [p println] (p “Hello”))

Here, let assigns the function println to p. When p is called, it actually calls the function println.

IMPORTANT — def versus let

However, let only works inside the function where it is applied. If you call p outside of the function i.e. outside of the outermost brackets, it is plain and simple p. However, when we use a keyword like def, we can call it from outside its function.

Image for post
Image for post

Have a look at the function below

(let [x “The Caves of Steel” y get-in z Library] 
(println “The author is “(y z [x :Author] ) “published “
(y z [x :Year] )))

To save us typing, we have used let to assign x y z to “The Caves of Steel”, the function get-in, and our symbol Library. Note, x y z is only assigned these values inside the function. As Library is a symbol created with the keyword def, it can be used anywhere in the program.

To better explain def, recall when we used let inside a function to substitute p for println

(let [p println] (p “Hello”))

To do the same with def, we have to be more specific. Look at the following.

(def p (fn [x] (println x)))

Reading from left to right, we will use def to define a symbol called p. Symbol p will be a function fn that takes just one parameter which we will call x [x]. This single parameter x will have the function println applied to it.

As a result,

(p “Hello”)
Hello
nil
Image for post
Image for post

Now look at the following

(def G (fn [x y] (get-in x y)))

Reading from left to right, we will use def to define a symbol called G. Symbol G will be a function fn that takes two parameters which we will call x and y [x y]. The parameters x and y will have the function get-in applied to each of them respectively.

Here is the function applied, where the x parameter takes the symbol Library. Parameter y takes the vector [“The Caves of Steel” :Author].

(G Library [“The Caves of Steel” :Author])"Isaac Asimov"

Regarding this example, had we omitted the symbol Library and written

(G [“The Caves of Steel” :Author])

we would have received an error. Recall, the G function has two parameters.

Image for post
Image for post

The error message tells us that we only passed one argument (args) to a function with two parameters x & y. In other words, the function expected two arguments. An argument is anything that at passed to a parameter. So in our example, the function fn [x y] expected two arguments, Library and [“The Caves of Steel” :Author]).

To allow to use G without the symbol Library, we would have to re-write our function to take only one parameter. We would then need to include the symbol Library on our function

(def G (fn [x] (get-in Library x )))

Reading from left to right, we will use def to define a symbol called G. Symbol G will be a function fn that takes one parameter which we will call x [x ]. The parameters x will have the function get-in Library applied to it.

(G [“The Caves of Steel” :Author])"Isaac Asimov"

Going back to our let statement. we have the following

(let [x “The Caves of Steel”] (println “The author is” (G [x :Author]) “published” (G [x :Year])))"The author is Isaac Asimov published 1954"
nil

As you can see, this is very much abbreviated.

UPDATE

Returning to our database

(def Library{“The Caves of Steel” { :ISBN “0–553–29340–0”                        :Title “The Caves of Steel”                        :Author “Isaac Asimov”                        :Year “1954”}“After Yesterdays Crash” { :ISBN “0–14–024085–3”                           :Title “After Yesterdays Crash”                           :Author “William Gibson”}})

Let’s update ‘After Yesterdays Crash” by adding in the year 1970.

(assoc-in Library [“After Yesterdays Crash” :Year] “1970”)
Image for post
Image for post

Easy, now just fetch the data with

(get-in Library [“After Yesterdays Crash” :Year])
Image for post
Image for post

So. what just happened?

MUTABILITY — with an atom!

Clojure is a functional language and is designed to hold pieces of stored data in a static, unchangeable, form. In short, when you create anything with def it is created to be immutable. In other words, once it has been created, it cannot. be changed. Not much good for creating a database!

Nevertheless, you get around this by using the keyword atom like so

(def Library (atom{“The Caves of Steel” { :ISBN “0–553–29340–0”                        :Title “The Caves of Steel”                        :Author “Isaac Asimov”                        :Year “1954”}“After Yesterdays Crash” { :ISBN “0–14–024085–3”                           :Title “After Yesterdays Crash”                           :Author “William Gibson”}}))

To work with our database now, you have to make some slight changes to account for the fact that you are now working with the keyword atom.

For instance, when reading your database, apply @ to Library like so

(get-in @Library [“The Caves of Steel” :Author])
Image for post
Image for post

Now, let’s return to our original problem. Let’s update our database by adding the year 1970 to the book titled, “After Yesterdays Crash”. To do this we will use another necessary keyword, swap!

(swap! Library assoc-in [“After Yesterdays Crash” :Year] “1970”)

Let’s check it has updated

(get-in @Library [“After Yesterdays Crash” :Year])
Image for post
Image for post

The database can be listed in full by simply typing @Library

Image for post
Image for post

This how our database looks now

{“The Caves of Steel” { :ISBN “0–553–29340–0”,                        :Title “The Caves of Steel”,                        :Author “Isaac Asimov”,                        :Year “1954”},“After Yesterdays Crash” { :ISBN “0–14–024085–3”,                           :Title “After Yesterdays Crash”,                           :Author “William Gibson”,                           :Year “1970”}}

CREATE

At this point, we can read our database and we can update. Now, we will add a completely new book. The Book will be ‘The War of the Worlds’ by H G Wells.

Type the following. This will create a new entry, including the ISBN.

(swap! Library assoc-in [“The War of the Worlds” :ISBN] “978–1604502442”)

Let’s add the year

(swap! Library assoc-in [“The War of the Worlds” :Year] “1898”)

Now check it’s there

(contains? @Library “The War of the Worlds” )

Our Library database should like this

{“The Caves of Steel” { :ISBN “0–553–29340–0”,                        :Title “The Caves of Steel”,                        :Author “Isaac Asimov”,                        :Year “1954”},“After Yesterdays Crash” { :ISBN “0–14–024085–3”,                           :Title “After Yesterdays Crash”,`                          :Author “William Gibson”,                           :Year “1970”},`“The War of the Worlds” { :ISBN “978–1604502442”,`                          :Year “1898”}}

DELETE

Firstly, let’s check what we still have recorded against the book title, ‘The War of the Worlds’.

(get-in @Library [“The War of the Worlds”]){:ISBN "978-1605402442", :Year "1898"}
Image for post
Image for post

We will delete the Year

(swap! Library update-in [“The War of the Worlds” ] dissoc :Year)
Image for post
Image for post

A quick check will reveal it has gone

Image for post
Image for post

Our Library database should now look like this

{“The Caves of Steel” { :ISBN “0–553–29340–0”,                        :Title “The Caves of Steel”,                        :Author “Isaac Asimov”,                        :Year “1954”},“After Yesterdays Crash” { :ISBN “0–14–024085–3”,                           :Title “After Yesterdays Crash”,                           :Author “William Gibson”,                           :Year “1970”},“The War of the Worlds” { :ISBN “978–1604502442”, }}

Finally, let’s completely remove a complete book and its associated data from our Library database. We will completely remove ‘After Yesterdays Crash’.

(swap! Library dissoc “After Yesterdays Crash”)

After demonstrating all CRUD functionality, our database looks like this

{“The Caves of Steel” { :ISBN “0–553–29340–0”,                        :Title “The Caves of Steel”,                        :Author “Isaac Asimov”, :Year “1954”},“The War of the Worlds” { :ISBN “978–1604502442”}}

SUMMARY

In the first half of this Post, we introduced a number of ways to store and retrieve items with Clojure. It’s also worth noting the subtle representations given to stored items. For instance, a set is prefixed with # and is contained inside curly {} brackets. Whereas, a vector is represented with square [] brackets.

Further along, we looked at Maps and the principle of indexing via key-value pairs. Later, we extended our knowledge to look at a simple database. I also introduced some database-specific terminology, CRUD.

Another learning point concerned how def will create a static variable that can be called from outside a function. As opposed let, as let will only work inside the function it was created.

A further look at functions introduced the difference between a parameter and an argument. We also had our first look at an error message (arity) and explained it in terms of parameters and arguments.

Near the end, we briefly touched upon the concept of Mutability and Atoms. Part 7 will attempt to explain the subjects of Atoms and Mutability in greater detail.

To get the most out of this Post, practice all the examples but try and think of other applications where these techniques can be applied. For example, when would a list be preferable to a vector?

Previous

Part — 7 Atom vs Def

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store