Lists & Vectors

Finding Elements

Clojure has convenience functions such as first and second to retrieve items from a collection:

(first [1 2 3])

More generally however, specific items can be retrieved using the nth function.

If you have a series of numbers 0, 1, 2, 3, ... n, where n represents any specific number, then 0th, 1st, 2nd, 3rd, ... nth is how you refer to that element.

nth has the form:

(nth collection index)

Remember that indexes start at 0, not 1, so if we want to grab the first element, we call nth using index 0. Below is (almost) equivalent to calling first:

(nth [1 2 3] 0)

Almost equivalent, because nth will throw an error if you try to retrieve an index that doesn't exist:

(nth [1 2 3] 10)

There's also the get function, which in this instance functions similarly to nth, but does not throw an error on a non-existing index, instead returning nil. first will also return nil for an empty sequence.

(get [1 2 3] 10)

Now is a good time to introduce the concept of null values, called nils in Clojure. As opposed to the number 0 representing zero of something, a null is the complete absence of a value.

Adding Elements

To add an element to a list or vector, we conjoin the element to the collection with the conj function. An interesting note: when we add an element to a vector, it's added to the end. When we add an element to a list, it's added to the beginning. Why that is will be explained later.

(conj [1 2 3] 4)
(conj '(1 2 3) 4)

To combine two lists or vectors into one, we concatenate them with the concat function.

(concat [1 2] [3 4])

Generating

Let's create some data of our own, shall we? Suppose we want to create a list of numbers from 1 to 10. Clojure has a very useful function called range, which has the form:

(range start end)

Let's see what we get...

(range 1 11)

Why did I use 11 as the end? One gotcha here is that the end of the range is exclusive, that is, it excludes the number itself. This means that the end of our range actually has to be one higher than the end number we want.

What if we want a list of 10 fives? Clojure has the repeat function for that:

(repeat count value)
(repeat 10 5)

Infinite Lists

Through Clojure, we can harness the power of infinity!... in a way. Clojure has many list functions which generate infinite sequences that you can take however many items you want from. The take function has the form:

(take count collection)

These infinite sequences don't actually generate their values all at once. Doing so would cause a computer to run out of memory. These sequences are lazy - they only generate values when those values are requested.

repeat actually has a different form without a parameter to specify a count of items to return:

(repeat value)

In this instance, it will return an infinite sequences of value. If you were to evaluate this by itself in your REPL, it would print a huge number of values and cause issues with your editor.

To get a list of 10 fives, we could have also written:

(take 10 (repeat 5))

Important note: here you can see that we can pass the result of evaluating a function as the argument of another function.

Manipulation & Functions as Arguments

Functions in Clojure are first-class, meaning they can be treated just like pieces of data. This includes being passed as arguments to other functions!

Many core functions in Clojure take other functions as their arguments. Often, these argument functions are used by the calling function to somehow manipulate a collection of data.

Before we discuss one such function, I'd like to get a potential source of confusion out of the way. Clojure has data structures called (hash) maps, and it also has a function called map, which are different things.

The map function has the form:

(map function collection)

It takes a function as its first input argument, which will be applied to each element of its second input argument, a collection of data. You can think of this as mapping over the data with some operation.

Let's take for an example the helper function inc. Its name is short for increment, as in, adding 1 to a value. When use map to apply inc to each value in a collection, we get...

(map inc [1 2 3 4])

We showed in chapter 1.4 how to define our own functions, both named and anonymous.

Let's pass in a custom function into map:

(defn times-ten
  [x]
  (* 10 x))

(map times-ten [1 2 3 4])

But remember, a function doesn't need to have a name. Instead of pre-defining a named function, we can also give map an anonymous function in-place using fn:

(map (fn [x] (* 10 x))
     [1 2 3 4])

Generation Follow-up

Earlier, to generate a list of 1 through 10, we could have also generated an infinite list with the function iterate:

(iterate function value)

Given a starting value x and a function f, it will return an infinite sequence of x, (f x), (f (f x)), and so on.

If we pick a starting value of 1, and use the funtion inc to increment each item in the sequence, we should get 1, 2, 3, and so on. Then we just need to take 10 items from the sequence.

(take 10 (iterate inc 1))

Printing Each Item

What if you want to do something to each item in a collection, but you don't care what the result is? Say for instance that you just want to display each value.

First you'll need to know the function println, which takes one or more pieces of data, and prints a line in a console containing the human-readable version of that data.

(println "hello!")

We can combine that with the function doseq, which does something to each item in a sequence:

(doseq [item [1 2 3 4]]
  (println item))

A lot going on there in just two lines! Something brand new is the first argument, [item [1 2 3 4]]. doseq uses this form to bind each value in [1 2 3 4] to the variable item. Then for each of these items, it evaluates (println item).

You'll also notice that the editor printed nil at the end. In Clojure, every evaluation has to return something, and if it doesn't return something, it will default to returning nil.

A Challenge

If you're ready, the following is a challenge that combines everything we've learned so far, and introduces several more things.

Let's say we want display the following:

*
**
***
****
*****

What may seem simple is actually a bit tricky. I'm going to demonstrate one solution, and ask you to complete another.

First Solution, with lazy sequences

What is it that we need to do here?

Here's what we'll do:

  • write a function that adds a '*' character to a string, in order to "grow" a string of stars
  • generate a sequence of "growing" star strings
  • take elements from this sequence
  • print each element

Are you ready? Let's begin.

First, we need a function to "grow" our star strings. We can use the function str, short for string, which takes any number of arguments and combines them together into a single string.

(str "a" 1 "b" 2)
(defn add-star
  [s]
  (str s "*"))

(add-star "*")

Hey, two stars!

Now we need to generate a growing sequence. We can use iterate, passing in our add-star function, with "*" as the starting value. Let's save this sequence to a variable called stars.

Remember, this sequence in infinite, so we need to take a number of items in order for this to display without issue in our editor.

(defn add-star
  [s]
  (str s "*"))

(def stars
  (iterate add-star "*"))

(def five-stars
  (take 5 stars))

five-stars

We're almost there! We just need to print each item as a line with doseq and println.

(defn add-star
  [s]
  (str s "*"))

(def star-sequence
  (iterate add-star "*"))

(def five-stars
  (take 5 star-sequence))

(doseq [star-line five-stars]
  (println star-line))

Second Solution, with map + range

This solution uses slightly a different process than the first:

  • write a function that generates a string containing a number of stars
  • map over a range of numbers, calling the above function on each number
  • print each element

One thing to note before we begin. In the previous exercise, I introduce str to create a single string out of a numbers of arguments. However, this only works on the list of arguments, not an argument which happens to be a list. Meaning, this does not do what we might expect:

(str ["abc" "def"])

Allow me to introduce apply, a function which takes an input function and a collection, and treats that collection like individual arguments to the input function:

(apply function collection)
(apply str ["abc" "def"])

We can now move on to writing a function which, for a given number x, returns a string containing x number of stars.

To do this, we can use the repeat function to get a list of x number of stars as individual strings, then apply str to make them into one string.

(println "Individual stars: " (repeat 5 "*"))

(apply str (repeat 5 "*"))

We then want to generate a list of numbers with range, and use map to pass each of those numbers to the function we define. Then, we print each element of the result with doseq and println.

(defn make-star-string
  [length]
  (apply str (repeat length "*")))

(def five-stars
  (map make-star-string (range 1 6)))

(doseq [star-line five-stars]
  (println star-line))