Repository: pbiswas101/Mathball (js)

Issue:
Kaprekar Number
### Do the checklist before filing the issue:

- [ ] Is this something you can **debug and fix**?
- [x] Do you have Node.js and all the relevant dev-dependencies installed?
- [ ] Is this a **feature request** that you can add?

###### NOTE: Provide a clear and concise description of the feature that needs to be added! Or if its a bug, then provide the necessary steps to reproduce it.

- *Give your answer below:*

------------

[Function: **M.isKaprekar()**] needs to be added!
For more info:

https://www.geeksforgeeks.org/kaprekar-number/

*M.isKaprekar()* needs to accept a positive integer and all other arguments passed should throw a type error which needs to be handled gracefully! For error handling, you should refer to how other features like M.isArmstrong() is implemented.

Pull request:
Matrix Data Structure (#141)
*Added new Data Structure Matrix

*Added Matrix Data Structure in index.js

*Added unit tests in Matrix-spec

### Do the checklist before submitting the PR:

- [:heavy_check_mark:] Have you read the guidelines mentioned in **CONTRIBUTING.md**?
- [:heavy_check_mark:] Have you squashed your commits?

**Q**: What version of *Node.js* you've used in the project? (`e.g. Node v10.11.0`)
**A**:v11.10.1

**Q**: Mention the *Issue Number*! (`e.g. Fixed #8`)
**A**:#141

- *Give additional information regarding the PR below:*

------------


Patch:
diff --git a/src/Matrix/index.js b/src/Matrix/index.js
new file mode 100644
index 0000000..abe546f
--- /dev/null
+++ b/src/Matrix/index.js
@@ -0,0 +1,124 @@
+/*Matrix Functionalities
+Use: M.Matrix.functionality('string')
+*/
+
+const matrixValidate = require('../validation/integer-matrix');
+const stringValidate = require('../validation/string');
+const integerValidate = require('../validation/positive-integer');
+
+const matrixChain = require('../matrixChain').matrixChain;
+const matrixExpo = require('../matrixExponentiation').matrixExpo;
+
+const checkSquare = (matrix) => {
+	let len = matrix.length;
+	for(let i = 0; i < len; i++){
+		if(matrix[i].length !== len){
+			return 0;
+		}
+	}
+	return 1;
+};
+
+class Matrix{
+	constructor(args){
+		matrixValidate(args, 'Matrix');
+		this.args = args;
+	}
+	static chainOrder(arr){
+		return matrixChain(arr);
+	}
+
+	loc(row, col){
+		integerValidate(row, 'loc');
+		integerValidate(col, 'loc');
+		let arr = this.args;
+		return arr[row - 1][col - 1];
+	}
+
+	diag(type){
+
+		stringValidate(type, 'diag');
+		let arr = this.args, checkSq = checkSquare(arr);
+		if(checkSq !== 1){
+			throw new TypeError(`Invalid argument: '${arr}' received! 'diag()' only returns diagonals of square matrices!`);
+	}
+	else{
+		switch(type){
+
+		case 'primary':
+
+		let primary = [], k = 0;
+		for(let i = 0; i < arr.length; i++){
+				primary[k++] = arr[i][i];
+		}
+		return primary;
+
+		case 'secondary':
+
+		let secondary = [], p = 0;
+		for(let i = 0; i < arr.length; i++){
+			secondary[p++] = arr[i][arr.length - i - 1];
+		}
+		return secondary;
+
+		default:
+			throw new TypeError(`Invalid argument '${type}' received!`);
+			}
+		}
+	}
+
+	check(property){
+
+		stringValidate(property, 'check');
+		let arr = this.args;
+
+		switch(property){
+
+		case 'square':
+			return (checkSquare(arr) === 1)? true: false;
+
+		case 'identity':
+			if(checkSquare(arr)){
+			for(let i = 0, j = 0; i < arr.length && j < arr[0].length; i++, j++){
+					if(i === j && arr[i][j] !== 1 && arr[j][i] !== 0){
+							return false;
+						}
+					}
+					return true;
+				}
+				throw new TypeError(`Invalid argument received: '${arr}', the matrix has to be square to check '${property}' property!`);
+
+		case 'upper-triangular':
+			if(checkSquare(arr)){
+			for(let i = 1, j = 0; i < arr.length && j < i; i++){
+					if(arr[i][j] !== 0){
+						return false;
+						}
+					}
+				return true;
+			}
+			throw new TypeError(`Invalid argument received: '${arr}', the matrix has to be square to check '${property}' property!`);
+
+		case 'lower-triangular':
+		if(checkSquare(arr)){
+		for(let i = 0, j = i + 1; i < arr.length && j < arr[0].length; i++, j++){
+				if(arr[i][j] !== 0){
+					return false;
+					}
+				}
+				return true;
+			}
+		throw new TypeError(`Invalid argument received: '${arr}', the matrix has to be square to check '${property}' property!`);
+
+		default:
+			throw new TypeError(`Invalid argument '${property}' received!`);
+		}
+	}
+
+	pow(a){
+		integerValidate(a, 'pow');
+		return matrixExpo(this.args, a);
+	}
+}
+
+module.exports = Matrix;
diff --git a/src/index.js b/src/index.js
index f37ca59..90cd76f 100644
--- a/src/index.js
+++ b/src/index.js
@@ -37,8 +37,7 @@ const abs = require('./absolute'),
 	sum = require('./sum');
 
 /* Data Structures */
-const matrixChain = require('./matrixChain'), // module implementation under
-	matrixExpo = require('./matrixExponentiation'), // one data type, 'Matrix'
+const Matrix = require('./Matrix'),
 	/*=====*/
 	Graph = require('./graph'),
 	PriorityQueue = require('./priority-queue'),
@@ -58,8 +57,7 @@ module.exports = {
 	length,
 	/* ===== */
 	/* Replace these two with 'Matrix' */
-	matrixChain: matrixChain.matrixChain,
-	matrixExpo: matrixExpo.matrixExpo,
+	Matrix,
 	/* ===== */
 	max,
 	median,
diff --git a/src/validation/integer-matrix.js b/src/validation/integer-matrix.js
new file mode 100644
index 0000000..9cdf8c9
--- /dev/null
+++ b/src/validation/integer-matrix.js
@@ -0,0 +1,16 @@
+/*
+Validation: Integer Matrix
+*/
+
+module.exports = (args, func) => {
+	if(args.constructor !== Array || args[0].constructor !== Array){
+		throw new TypeError(`Invalid argument received: '${args}' , '${func}' only accepts a N-d array of numbers!`);
+	}
+	for(let i = 0; i < args.length; i++){
+		args[i].forEach((e) => {
+			if(typeof e !== 'number'){
+				throw new TypeError(`Invalid argument received: '${args}' , '${func}' only accepts a N-d array of positive integers!`);
+			}
+		});
+	}
+};


Test patch:
diff --git a/test/Matrix-spec.js b/test/Matrix-spec.js
new file mode 100644
index 0000000..7bcc965
--- /dev/null
+++ b/test/Matrix-spec.js
@@ -0,0 +1,126 @@
+const assert = require('assert'),
+	Matrix = require('../src/Matrix'),
+	x = new Matrix([[1,2],[3,4]]),
+	f = new Matrix([[1,0,0],[0,0,1]]),
+	upper = new Matrix([[1,2,3,4],[0,1,2,3],[0,0,1,2],[0,0,0,1]]),
+	lower = new Matrix([[1,0,0,0],[1,2,0,0],[1,2,3,0],[1,2,3,4]]),
+	identity = new Matrix([[1,0,0],[0,1,0],[0,0,1]]);
+
+describe('[Function: Matrix]', () => {
+	it('should return a function', () => {
+		assert.strictEqual(typeof Matrix, 'function');
+	});
+
+	it('should be an object', () => {
+		assert.strictEqual(typeof x, 'object');
+	});
+
+	it('should return 26000 when [40,20,30,10,30] is passed to chainOrder', () => {
+		assert.strictEqual(Matrix.chainOrder([40,20,30,10,30]), 26000);
+	});
+
+	it('should throw a TypeError when 30,40,50 is passed to chainOrder', () => {
+		assert.throws(() => Matrix.chainOrder(30,40,50), TypeError);
+	});
+
+	it('should return 3 when loc is called with 2,1', () => {
+		assert.strictEqual(x.loc(2,1), 3);
+	});
+
+	it('should throw an error when loc is called with "2", "1"', () => {
+		assert.throws(() => x.loc('2','1'), TypeError);
+	});
+
+	it('should return primary elements of the diagonal', () => {
+		assert.deepStrictEqual(x.diag('primary'), [1,4]);
+	});
+
+	it('should return secondary elements of diagonal', () => {
+		assert.deepStrictEqual(x.diag('secondary'), [2,3]);
+	});
+
+	it('should throw an error when diag is called with a non-sqaure matrix', () => {
+		assert.throws(() => f.diag('primary'), TypeError);
+	});
+
+	it('should return true if the matrix is square matrix', () => {
+		assert.strictEqual(x.check('square'), true);
+	});
+
+	it('should return false if the matrix is not square matrix', () => {
+		assert.strictEqual(f.check('square'), false);
+	});
+
+	it('should throws an error if the matrix is not square and checks identity', () => {
+		assert.throws(() => f.check('identity'), TypeError);
+	});
+
+	it('should return false if the matrix is square and not identity', () => {
+		assert.strictEqual(x.check('identity'), false);
+	});
+
+	it('should return true if the matrix is identity', () => {
+		assert.strictEqual(identity.check('identity'), true);
+	});
+
+	it('should return false if the matrix is not upper-triangular', () => {
+		assert.strictEqual(x.check('upper-triangular'), false);
+	});
+
+	it('should throw an error if the matrix is not a square and checks uppper-triangular', () => {
+		assert.throws(() => f.check('upper-triangular'), TypeError);
+	});
+
+	it('should return true if the matrix is upper-triangular', () => {
+		assert.strictEqual(upper.check('upper-triangular'), true);
+	});
+
+	it('should return false if the matrix is not lower-triangular', () => {
+		assert.strictEqual(x.check('lower-triangular'), false);
+	});
+
+	it('should throw an error if the matrix is not a square and checks lower-triangular', () => {
+		assert.throws(() => f.check('lower-triangular'), TypeError);
+	});
+
+	it('should return true if the matrix is lower-triangular', () => {
+		assert.strictEqual(lower.check('lower-triangular'), true);
+	});
+
+	it('should return [[7,10],[15,22]] when pow is called with 2', () => {
+		assert.deepStrictEqual(x.pow(2), [[7,10],[15,22]]);
+	});
+
+	it('should throw an error if a string or floating point is passed to pow', () => {
+		assert.throws(() => x.pow('2'), TypeError);
+	});
+
+	it('should throw an error if a number is passed to diag()', () => {
+		assert.throws(() => x.diag(4), TypeError);
+	});
+
+	it('should throw a TypeError when type passed to diag() is not correct', () => {
+		assert.throws(() => x.diag('tertiary'), TypeError);
+	});
+
+	it('should throw a TypeError when a number is passed to check', () => {
+		assert.throws(() => x.check(3), TypeError);
+	});
+
+	it('should throw a TypeError when property passed to check() is not correct', () => {
+		assert.throws(() => x.check('circle'), TypeError);
+	});
+
+	it('should throw a TypeError when a 1D array is passed', () => {
+		assert.throws(() => {
+			const z = new Matrix([3,4,5,6,7,8]);
+			z.loc(2,1);
+		}, TypeError);
+	});
+
+	it('should throw a TypeError when matrix does not contain positive integers', () => {
+		assert.throws(() => {
+			const z = new Matrix([[3,4], ['4','5']]);
+		}, TypeError);
+	});
+});