noobtuts

Clojure Vector-2, Vector-3 & Vector-N

Foreword

In this tutorial we will implement the vector functions that are used in other tutorials on this website. To be more exact: we will implement what is commonly known as Vector2(x, y), Vector3(x, y, z) and Vector4(x, y, z, w) in other languages.

While we could implement Vector2, then Vector3 and then Vector4 functions we will simply make use of Clojure's functional nature and just go with Vector-N which will work with any dimension.

Motivation

If you never heard about a Vector, it is a data structure that represents (for example) a monster's position in a game world. It has several components like x(the horizontal position) and y(the vertical position). If the monster lives in a 3D world then it also has a z component (the height).

On Clojure's vector

As mentioned before, Clojure already has a vector data structure. We will make use of this data structure and simply create a few functions for things like vector multiplication, scaling, dot product, normalization and so on.

Clojure's vector looks like this:

; define a vector with x=0 and y=1:
(def u [0 1])

; define a vector with x=0 and y=1 and z=2:
(def v [0 1 2])

; access the components:
(nth u 0) ; x
(nth u 1) ; y
(nth u 2) ; z

The alternative would be a hash-map like this one:

(def u {:x 0 :y 1})

; access the components:
(:x u)
(:y u)
(:z u)

While this looks cool, it has the downside of not being dimension independent because we manually have to name the keys like :x, :y, :z, :w and so on. This would be a problem for something like Vector-100.

Helper Functions

Our vec-n functions won't require any external libraries. We need a few very simple helper functions though:

; calculates n²
; (sqr 2) => 4
(defn sqr [n]
  (* n n))

; (sqrt 9) => 3.0
; needed because (map Math/sqrt ...) doesn't work
(defn sqrt [n] (Math/sqrt n))

; (abs 3) => 3
; (abs -3) => 3
; needed because (map Math/abs ...) doesn't work
(defn abs [n] (Math/abs n))

Required knowledge

map

Before proceeding to the vector functions we will talk about the map function really quick. The common usage is like this:

(map inc [1 2 3]) ; => (2 3 4)

Where a function is mapped (in other words: applied) onto every element in the list.

Now map can also be used with multiple arguments, in which case it does what is known as map and zip in other languages:

; other languages:
(map (partial reduce +) (zip [1 2] [3 4]))
; => zip makes it ([1 3]
;                  [2 4])
; => + adds it to (4 6)

; in clojure:
(map + [1 2] [3 4]) ; => (4 6)

Or in other words: if we use map with multiple arguments then they are zipped together automatically. This is good because it keeps us from writing a lot of weird looking code.

Note: since we usually want vectors as results, we often use mapv.

comp

Another function that we will use several times is the comp function. It creates function compositions like this:

(def magic (comp sqr inc))
; (magic 1) => 4 because (sqr (inc 1)) => (sqr 2) => 4

As we will see soon, comp allows us to write some complicated things in a very elegant way.

Adding two Vectors

Alright, the first one will be easy. Given two vectors u and v, we will add together their components like this:
Vector Add

And here is how we do it in Clojure:

; here is the easily readable version:
; note: only works with 2 arguments
; (v+ [1 2 3] [1 1 1]) => [2 3 4]
(defn v+ [u v]
  (mapv + u v))

; here is the better version:
; note: works with more than two arguments
; (v+ [1 2 3] [1 1 1]) => [2 3 4]
; (v+ [1 2 3] [1 1 1] [2 2 2]) => [4 5 6]
(def v+ (partial mapv +))

What happens is that map first zips together the components so we have ([u.x v.x] [u.y v.y] [u.z v.z]), then it adds them together because we used map +.

The second version looks a bit weird, but it does exactly the same. The difference is that we created a function composition that works for more than two arguments.

Subtracting two Vectors

This one will be even more easy. Given two vectors u and v, we will subtract their components from another like this:
Vector Subtract

And here is how we do it in Clojure:

; again this is the easily readable version
; note: only works with 2 arguments
(defn v- [u v]
  (mapv - u v))

; and this is the better version:
; (v- [1 2 3] [1 1 1]) => [0 1 2]
; (v- [1 2 3] [1 1 1] [2 2 2]) => [-2 -1 0]
(def v- (partial mapv -))

The function works exactly like v+, just with a minus.

Vector Equality

It gets even easier now. Two vectors are considered equal if their corresponding components are equal, or in other words: if u.x==v.x and u.y==v.y and so on. We use == instead of = to properly compare floating point values with a certain precision tolerance.

Here is the code:

; the easier readable version:
(defn v= [u v]
  (every? true? (map == u v)))

; the version that works with >2 args
; (v= [1 2 3] [1 1 1]) => false
; (v= [1 2] [1 2] [1 2]) => true
(def v= (comp (partial every? true?) (partial map ==)))

Again the components are first zipped together by map, then they are compared with == so we get something like [true true false] if the x and y components were equal, but the z components were not. Afterwards the vectors are considered equal if the previous result contains only true values like [true true true].

Vector Length

Every vector has a certain length. By definition a vector like [3 4] is an arrow that points from the origin (which is [0 0]) to [3 4] like this:
Vector Length

Now the length of that arrow is exactly the length of the vector.

We calculate it like this:
Vector Length Calculation

For some computations we also want the squared length of that vector, so we will implement two length functions:

; (v-len-sqr [1 2 2]) => 9
(defn v-len-sqr [u]
  (reduce + (map sqr u)))

; (v-len [1 2 2]) => 3.0
(def v-len (comp sqrt v-len-sqr))

Again we used comp here to express 'use v-len-sqr and then use sqrt' in a very elegant way.

Scaling a Vector

There are two ways to scale a vector: either with another vector or with a so called scalar (which is a fancy word for a number):
Vector Scale

So we could either write two functions: v-scale-with-vector and v-scale-with-scalar or we could write one function that handles both cases:

; (v* [1 2 3] [2 2 2]) => [2 4 6]
; (v* [1 2 3] 2) => [2 4 6]
; (v* 2 [1 2 3]) => [2 4 6]
(defn v* [u v]
  (cond
    (and (vector? u) (vector? v)) ; both are vectors
      (mapv * u v)
    (vector? u) ; u is a vector, v is a scalar
      (mapv (partial * v) u)
    (vector? v) ; v is a vector, u is a scalar
      (mapv (partial * u) v)))

Note: we called the function v*, however there is no real vector multiplication in mathematics. There are things like dot-product, cross-product and scale. And because scale is used very commonly, we will call it v* here.

Vector Distances

If we have two vectors then we can also calculate the distance between those two vectors. In the following picture the distance between u and v is exactly the length of the gray arrow between them:
Vector Distance

Here is how we calculate it:
Vector Distance Calculation

There are cases where we want the squared distance for our calculations, hence why we have two functions in Clojure again:

; (v-dist [0 0] [1 1]) => 1.41
; (v-dist [1 1] [4 2]) => 3.16
(def v-dist (comp v-len v-))

; (v-dist-sqr [0 0] [1 1]) => 2
(def v-dist-sqr (comp v-len-sqr v-))

Like in the above picture, the function first subtracts the components from each other by using our previously created v- function, then does the square and square root thing. Since we already did that in our v-len-sqr and v-len functions, we just reused them here.

And thanks to comp, this looks really elegant.

Set the Length of a Vector

As mentioned before, by definition a vector is just an arrow that points from the origin (which is [0 0]) to some point. Sometimes we want to set the length of that arrow to something else, like shown in the following picture:
Vector SetLength

We can modify a vector's length by multiplying it with 'wished length / current length'. For example if we have a vector [0 1] and want to set its length to 3 we do this:
Vector SetLength Calculation

We already have a function that calculates a vector's length, so the implementation is really easy in Clojure. However we want to take care of an edge case where the length of the vector [0 0] should be set. This case would cause a NullDivision error, so let's take care of it manually:

; (v-setlen [0 1] 3) => [0.0 3.0]
; (v-setlen [0 0] 1) => [1 0]
; (v-setlen [0 0 0] 1) => [1 0 0]
(defn v-setlen [u t]
  (let [dim (count u)]
    (if (every? (partial == 0) u)       ; [0 0 ...]?
        (into [t] (repeat (dec dim) 0)) ; [t 0 ...]
        (v* u (/ t (v-len u))))))

This looks a bit weird, but it works. The if checks if u is a null vector like [0 0 0] (or [0 0] depending on the dimension), in which case it returns [t 0 0], otherwise it changes the length of u.

Normalize a Vector

Alright this one will be really easy again. Normalizing a vector means 'set its length to exactly 1'. We already have our v-setlen function, so let's just use it:

; (v-norm [2 2]) => [0.70 0.70]
; (v-norm [0 0]) => [1 0]
(defn v-norm [u]
  (v-setlen u 1))

Vector Dot Product

Almost done. The last thing we want to do is calculate the so called Dot Product like this:
Vector Dot Product

And in Clojure:

; (v-dot [1 2] [3 4]) => 11
; (v-dot [1 3 -5] [4 -2 -1]) => 3
; (1*4 + 3*-2 + -5*-1 = 3)
(defn v-dot [u v]
  (reduce +
    (map * u v)))

As usual our map function zips together u and v, then it multiplies them because we used *. And in the end we sum up the results by using reduce +.

Summary

Now we have a handful of vector functions that can be used with any dimension that we need. It will come in handy soon!