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:
Add count
### Do the checklist before submitting the PR:

- [x] Have you read the guidelines mentioned in **CONTRIBUTING.md**?
- [ ] 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.6.0

**Q**: Mention the *Issue Number*! (`e.g. Fixed #8`)
**A**:#107

- *Give additional information regarding the PR below:*

------------


Patch:
diff --git a/README.md b/README.md
index af3b654..9f11392 100644
--- a/README.md
+++ b/README.md
@@ -30,7 +30,7 @@ $ npm install mathball --save
 
 - **Range of Numbers** - [rangePrime][7] - [rangeFibonacci][8] - [rangePalindrome][6]
 
-- **Mathematical Utilities** - [Complex][49] - [frequency][40] - [gcd][10] - [getAllPerm][55] - [isEven][11] - [isOdd][12] - [lcm][13] - [length][14] - [max][15] - [min][16] - [modInv][39] - [nCr][48] - [pow][46] - [sort][17] - [sum][18] - [toDegrees][50] - [toRadians][51] - [matrixMultiply][58] - [matrixExpo][59] - [matrixChain][60]
+- **Mathematical Utilities** - [Complex][49] - [frequency][40] - [gcd][10] - [getAllPerm][55] - [isEven][11] - [isOdd][12] - [lcm][13] - [length][14] - [max][15] - [min][16] - [modInv][39] - [nCr][48] - [pow][46] - [sort][17] - [sum][18] - [toDegrees][50] - [toRadians][51] - [matrixMultiply][58] - [matrixExpo][59] - [matrixChain][60] - [count][61]
 
 - **Data Structures** - [priorityQueue][44] - [stack][53] - [graph][57]
 
@@ -128,4 +128,5 @@ The MIT License 2019 - [Priyabrata Biswas][20].
 [57]: https://github.com/pbiswas101/Mathball/blob/master/src/graph/index.js
 [58]: https://github.com/pbiswas101/Mathball/blob/master/src/matrixMultiply/index.js
 [59]: https://github.com/pbiswas101/Mathball/blob/master/src/matrixExponentiation/index.js
-[60]: https://github.com/pbiswas101/Mathball/blob/master/src/matrixChain/index.js
\ No newline at end of file
+[60]: https://github.com/pbiswas101/Mathball/blob/master/src/matrixChain/index.js
+[61]: https://github.com/pbiswas101/Mathball/blob/master/src/count/index.js
\ No newline at end of file
diff --git a/src/count/index.js b/src/count/index.js
new file mode 100644
index 0000000..a11d8a8
--- /dev/null
+++ b/src/count/index.js
@@ -0,0 +1,80 @@
+const validate = require('../validation/count');
+
+const deepCompare = (var1, var2) => {
+  if((typeof var1 === "string" || typeof var1 === "number")&&(typeof var2 === "string" || typeof var2 === "number")){
+    return var1 === var2;
+  }
+  else if(Array.isArray(var1) && Array.isArray(var2)){
+    return (var1.filter(val => var2.includes(val)).length === var1.length && var1.length === var2.length);
+  }
+  else{
+    for(var ele in var1){
+      if(var1.hasOwnProperty(ele) !== var2.hasOwnProperty(ele)){
+        return false;
+      }
+      else{
+            if (!deepCompare(var1[ele], var2[ele])){
+              return false;
+            }                
+        }
+      }
+    }
+  return true;
+};
+
+/**
+* Find the occurance count of a phrase
+* @param {string} str
+* @param {string} occurance
+*/
+const findStroccurance = (str, occurance) => {
+  let count = 0;
+  let flag = -1;
+
+  do {
+    flag = str.indexOf(occurance, flag + 1);
+    if (flag !== -1) {
+      count++;
+    }
+  } while (flag !== -1);
+
+  return count;
+};
+
+/**
+* Find the occurance count of a phrase
+* @param {object} obj
+* @param {string|number|object} occurance
+*/
+const findObjOccurance = (obj, occurance) => {
+    let count = 0;
+    let keyVals = Object.keys(obj);
+
+    for(let i = 0; i < keyVals.length; i++){
+        if(deepCompare(occurance, obj[keyVals[i]])){              //obj[keyVals[i]] === occurance
+            count++;
+        }
+    }
+    return count;
+};
+
+/**
+ * Frequency
+ *
+ * `count()` accepts two arguments and return the occurance of second argument in first
+ * @param {string|number|string[]|number[]|object} arg - Accepts either a String or an Array
+ * @param {string|number|object} occurance - The phrase or array item you want to find
+ */
+module.exports = (arg, occurance) => {
+  validate(arg, 'count');
+  
+  if (arg && arg.constructor === Array) {
+    return (arg = arg.filter(val => val === occurance).length);
+  }
+  else if(arg.constructor === Object){
+      return findObjOccurance(arg, occurance);
+  }
+  else {
+    return findStroccurance(arg, occurance);
+  }
+};
\ No newline at end of file
diff --git a/src/index.js b/src/index.js
index db4b063..66c908c 100644
--- a/src/index.js
+++ b/src/index.js
@@ -29,6 +29,7 @@ const bell = require('./bell'),
 
 /* Mathematical Utilities */
 const frequency = require('./frequency'),
+	count = require('./count'),
   gcd = require('./gcd'),
 	isEven = require('./isEven'),
 	isOdd = require('./isOdd'),
@@ -101,6 +102,7 @@ module.exports = {
 	toRadians: toRadians.toRadians,
 	modInv: modInv.modInv,
 	frequency,
+	count,
 	isDeficient,
 	PriorityQueue,
 	Stack,
diff --git a/src/validation/count.js b/src/validation/count.js
new file mode 100644
index 0000000..01f7e7b
--- /dev/null
+++ b/src/validation/count.js
@@ -0,0 +1,28 @@
+/*
+ * Array and object validation 
+ */
+
+module.exports = (arg, func) => {
+	let isValid = false;
+
+	if (typeof arg === 'string') {
+		isValid = true;
+	}
+	else if (arg.constructor === Array) {
+		isValid = true;
+	}
+	else if(arg.constructor === Object){
+		isValid = true;
+	}
+	else {
+		isValid = false;
+	}
+
+	if (!isValid) {
+		throw new TypeError(
+			`Invalid argument received -  ${JSON.stringify(
+				arg
+			)}\n'${func}()' only accept an Array or a String!\n`
+		);
+	}
+};


Test patch:
diff --git a/test/count-spec.js b/test/count-spec.js
new file mode 100644
index 0000000..e4296cc
--- /dev/null
+++ b/test/count-spec.js
@@ -0,0 +1,81 @@
+const assert = require('assert'),
+	count = require('../src/count/index');
+
+describe('[Function: count]', () => {
+	it('should be a function', () => {
+		assert.strictEqual(typeof count, 'function');
+	});
+
+	it('should return 3 when the following array and occurance is passed', () => {
+		assert.strictEqual(count([1, 2, 3, 2, 1, 2], 2), 3);
+	});
+
+	it('should return 1 when the following string and occurance is passed', () => {
+		assert.strictEqual(count('testing count', 'es'), 1);
+	});
+
+	it('should return 2 when the following string and occurance is passed', () => {
+		assert.strictEqual(count(['testing', 'count', 'testing'], 'testing'),2);
+	});
+
+	it('should return 3 when the following array and occurance is passed', () => {
+		assert.strictEqual(count([1, 2, 3, 1, 2, 4, 5, 1], 1), 3);
+	});
+
+	it('should return 2 when the following array and occurance is passed', () => {
+		assert.strictEqual(count(['a', 'y', 'a', 'b', 'c', 'z', 'i'], 'a'), 2);
+	});
+
+	it('should return 1 when the following array and occurance is passed', () => {
+		assert.strictEqual(count(['hello', 'world', 'lorem', 'ipsum', 'hello'], 'world'),1);
+	});
+
+	it('should return 0 when the following array and occurance is passed', () => {
+		assert.strictEqual(count(['hello', 5, 'lorem', 7, 'ipsum', 'hello'], 'world'),0);
+	});
+
+	it('should return 2 when the following string and occurance is passed', () => {
+		assert.strictEqual(count('banana', 'ana'), 2);
+	});
+
+	//Object type testing part
+	it('should return 1 when the following object and occurance is passed', () => {
+		assert.strictEqual(count({'ipsum': [true, 1]}, [true, 1]), 1);
+	});
+
+	it('should return 0 when the following object and occurance is passed', () => {
+		assert.strictEqual(count({'ipsum': [true, 1]}, [false, 1]), 0);
+	});
+
+	it('should return 1 when nested object and occurance is passed', () => {
+		assert.strictEqual(count({'ipsum': {true: 1}}, {true: 1}), 1);
+	});
+
+	it('should return 0 when nested object and occurance is passed', () => {
+		assert.strictEqual(count({'ipsum': {true: 1}}, {true: 2}), 0);
+	});
+
+	it('should return 0 when nested object and occurance is passed', () => {
+		assert.strictEqual(count({'ipsum': {true: 1}}, {false: 1}), 0);
+	});
+
+	it('should return 0 when the following object and occurance is passed', () => {
+		assert.strictEqual(count({'ipsum': [true, 1]}, [true]), 0);
+	});
+
+	it('should return 0 when the following object and occurance is passed flag', () => {
+		assert.strictEqual(count({'ipsum': [true, 1]}, [true, 2]), 0);
+	});
+
+	it('should return 2 when the following object and occurance is passed', () => {
+		assert.strictEqual(count({'foo': 'bar', 'spam': 'egg', 'lorem': 'bar'}, 'bar'), 2);
+	});
+
+	it('should throw TypeError when a number is passed as arguments', () => {
+		assert.throws(() => count(87), TypeError);
+	});
+
+	it('should throw TypeError when nothing is passed as arguments', () => {
+		assert.throws(() => count(), TypeError);
+	});
+});
\ No newline at end of file