Try this first

Two apples, described by weight and diameter: [180, 7.2] and [205, 7.6].

Write the difference between them. Not a sentence about it. A value your program could hold in a variable.

A vector is an array you agree to treat as one thing

The difference is [25, 0.4]. Twenty five grams heavier, four tenths of a centimeter wider. You subtracted position by position and kept the answer in the same order.

That is all a vector is. An ordered list of numbers, plus the agreement that you operate on it position by position and never shuffle it. JavaScript has no vector type and does not need one. A plain array works, as long as every function you write respects the order.

Four functions

function add(a, b) {
  return a.map((v, i) => v + b[i]);
}

function sub(a, b) {
  return a.map((v, i) => v - b[i]);
}

function scale(a, k) {
  return a.map(v => v * k);
}

function norm(a) {
  return Math.sqrt(a.reduce((s, v) => s + v * v, 0));
}

add and sub walk both arrays together and combine slot 0 with slot 0, slot 1 with slot 1. The index i from map is what reaches into b at the matching position.

scale multiplies every slot by the same number. Doubling a vector doubles each of its parts, which points it the same way but makes it twice as long.

norm is the length of the vector. Square each number, add them up, take the square root. In two dimensions that is Pythagoras. The formula does not change for three numbers or for seven hundred, which is the reason this notation is worth adopting.

Try them.

const a = [180, 7.2];
const b = [205, 7.6];

log("difference", sub(b, a));
log("sum       ", add(a, b));
log("half of b ", scale(b, 0.5));
log("length of the difference", norm(sub(b, a)));
0 2 4 6 0 2 4 slot 0 slot 1 a = [5, 4] b = [2, 3] a − b = [3, 1] scale(a, 0.5) the four functions add(a, b) tip to tail sub(a, b) the arrow from b to a scale(a, k) same direction, k times as long norm(a) the length of the arrow all of them work slot by slot, and never shuffle the order
A vector is an ordered list of numbers, and it is also an arrow. Subtracting means "what would I add to b to reach a", which is exactly what the dashed arrow shows.

Averaging a group

One more function, because we will want the center of a group of points several times later.

function mean(vectors) {
  let m = vectors[0].map(() => 0);
  for (const v of vectors) m = add(m, v);
  return scale(m, 1 / vectors.length);
}

Start at a vector of zeros the same length as the data. Add every vector into it. Divide by how many there were. The result is the average item, which may not be any real item in the set.

What to watch

These functions fail quietly. Pass arrays of different lengths to add and b[i] is undefined for the extra slots, so you get NaN in your results and no error anywhere. Every later bug of the form “the numbers went to NaN and I do not know when” starts here.

There is a deeper problem in norm. It squared 25 grams and 0.4 centimeters and added them together, which is arithmetic on two different units. The answer, 25.003, is essentially the weight difference with a rounding error attached. Hold that thought. Lesson 5 in this section is about fixing it.

Exercises

  1. Add a length check to add and sub that throws if the two arrays differ. Then pass mismatched arrays and confirm you get a message instead of NaN.
  2. Compute the mean of the three apples and the mean of the three oranges from the last lesson. Plot all six points and both means, using a larger radius for the means.
  3. Show that add(a, scale(sub(b, a), 0.5)) lands exactly halfway between a and b. Then try 0.1 instead of 0.5. Taking a small step from where you are, in the direction of somewhere better, is the shape of every training loop in section 4.