Click on the three arrows for better reading experience.
JavaScript is NOT the language that comes to mind when you look for fast vector operations. But sometimes it is handy to use Vector like syntax and operations instead of map() or similar standard programming techniques you use for arrays.
I just happened to need Vectors and found this very nice module : https://github.com/RodionChachura/linear-algebra
The drawback was I needed slightly different API, more compact code and I also found I could abstract things differently. It was also a good exercise. So I used the module as a template to make my own. You can see the result below.
First we add several basic Functions that we would need to the Math class. They could be standalone Functions if you will, but Math seems like a better host for them.
Second thing is that the vector class extends directly the Array class, so you can reuse all Array methods if you need.
The third noticeable thing to mention is that the core functionality is implemented as the class method called .vmap(), short from vector-map.
There is also an object method called .vmap(), but that is just a wrapper that internally simply calls the class method.
The vmap() accepts the following arguments :
- op : a two argument function that is applied to every element of the vectors.
- vector1 : first argument.
- vvf : aka vector-or-value-or-function
- fun1 : pre-processing function for vector1
- fun2 : pre-processing function for vector2
In effect the idea is that you can apply a function to the vectors. As a bonus you have the option to pre-process the any of them or both before applying this operator-function.
Use the Math.id() for the first vector if you have to pre-process only the second vector.
//you create vector by calling the constructor
vec = new vector(5,32,26,48,13)
//or random vector
vec = vector.rand(min=0,max=10,count=5)
and now the source code :
const EPSILON = 0.00000001 const areEqual = (one, other, epsilon = EPSILON) => Math.abs(one - other) < epsilon const to_degrees = radians => (radians * 180) / Math.PI Math.add = (a,b) => a + b Math.sub = (a,b) => a - b Math.mul = (a,b) => a * b Math.div = (a,b) => a / b Math.id = val => val class vector extends Array { //apply op on a vector|fun(vec) OR vector and num|vec static vmap(op,vec1,vvf,fun1,fun2){ //preprocess the vectors if (typeof fun1 == 'function') vec1 = vector.vmap(fun1,vec1) if (typeof fun2 == 'function') vvf = vvf instanceof vector ? vector.vmap(fun2,vvf) : fun2(vvf) switch (typeof vvf) { //function ON vector case 'undefined': return new vector(...vec1.map(a => op(a)) ) //function ON function ON vector case 'function' : return new vector(...vec1.map(a => op(vvf(a))) ) //number ON vector case 'number' : return new vector(...vec1.map(a => op(a,vvf)) ) //vector ON vector default : return new vector(...vec1.map((a,ix) => op(a,vvf[ix])) ) } } //object method vmap(op,vvf,fun1,fun2) { return vector.vmap(op,this,vvf,fun1,fun2) } static rand(min,max,len){ let vec = new vector() for (let i=0; i < len; i++) { vec.push(Math.floor((Math.random() * max) + min)) } return vec } add(vec) { return vector.vmap(Math.add,this,vec) } sub(vec) { return vector.vmap(Math.sub,this,vec) } mul(vec) { return vector.vmap(Math.mul,this,vec) } div(vec) { return vector.vmap(Math.div,this,vec) } count(val=0){ return this.reduce( (acc,ix) => this[ix] == val ? acc : acc+1, 0) } non_count(val=0){ return this.reduce( (acc,ix) => this[ix] == val ? acc+1 : acc, 0) } len() { return Math.hypot(...this) } // scale_by(number) { return new vector(...this.map(val => val * number) ) } dot(vec) { return vec.reduce((acc, val, index) => acc + val * this[index], 0) } norm() {return this.mul(1 / this.len()) } angle(other) { return to_degrees(Math.acos(this.dot(other) / (this.len() * other.len()) ) ) } negate() { return this.mul(-1) } }
Beside vmap-based methods I included some basic Vector operations. You can see examples of how to use the module below …..
say = console.log //generate random vectors v1 = vector.rand(0,100,5) v2 = vector.rand(0,100,5) say("vector 1 : ", v1) say("vector 2 : ", v2) say("v1 + 10 : ", v1.add(10)) say("v1 * 2 : ", v1.mul(2)) say("v1 + v2 : ", v1.add(v2)) ----- vector 1 : vector [ 38, 48, 31, 20, 11 ] vector 2 : vector [ 77, 49, 73, 14, 40 ] v1 + 10 : vector [ 48, 58, 41, 30, 21 ] v1 * 2 : vector [ 76, 96, 62, 40, 22 ] v1 + v2 : vector [ 115, 97, 104, 34, 51 ] ===== say("\n-----: general methods") say("dot product : v1 . v2 : ", v1.dot(v2)) say("normalized v1 : ", v1.norm()) say("vector length v1 : ", v1.len()) say("angle v1,v2 : ", v1.angle(v2)) ----- -----: general methods dot product : v1 . v2 : 8261 normalized v1 : vector [ 0.5254516403878032, 0.6637283878582778, 0.42865791715847107, 0.27655349494094905, 0.152104422217522 ] vector length v1 : 72.31873892705818 angle v1,v2 : 23.240650242950494 ===== say("\n-----: using .vmap()") say("v1 + 10 : ", v1.vmap(Math.add,10)) say("sqrt(v1) + 10 : ", v1.vmap(Math.add,10,Math.sqrt)) say("v1 + v2 : ", v1.vmap(Math.add,v2)) say("v1/2 + v2/2 : ", v1.vmap(Math.add,v2, (a)=> a/2, (b)=>b/2)) -----: using .vmap() v1 + 10 : vector [ 48, 58, 41, 30, 21 ] sqrt(v1) + 10 : vector [ 16.164414002968975, 16.928203230275507, 15.567764362830022, 14.47213595499958, 13.3166247903554 ] v1 + v2 : vector [ 115, 97, 104, 34, 51 ] v1/2 + v2/2 : vector [ 57.5, 48.5, 52, 17, 25.5 ] ===== //creating a vector via coonstructor bin = new vector(1,0,0,0,1,1,0,0) say("binary vector : ", bin) say("count 1s : ", bin.count(1)) say("count 0s : ", bin.count(0)) say("count 1s : ", bin.non_count(0)) ----------- binary vector : vector [ 1, 0, 0, 0, 1, 1, 0, 0 ] count 1s : 3 count 0s : 5 count 1s : 3