I shall make a JavaScript-function which does the same as PHP's array_diff: http://php.net/manual/de/function.array-diff.php
Because it is intended to use the function as a (internal) library-function I have tried to catch everything which could possibly go wrong concerning incorrect usage. Moreover I've tried to document the usage of the function well.
Can one expect that my algorithm works as expected?
How can I improve my documentation and value-checking further?
Should I use different error return-values to signal more precise what has gone wrong?
// ----- TESTING -------------------------------
var arr1 = [1, 3, 2, 1, 2, 7, 9, 2, 6, 5 ];
var arr2 = [1, 9, 6, 11, 0, 17, 4, 22];
var str1 = [1, 9, '6', 11, 0, 17, 4, 22];
var str2 = [1, 9, '6', 11, 'zero', 17, 4, 22];
var not = [1, 9, NaN, 11, 0, 17, 4, 22];
var flt = [1, 9, 2.68, 11, 0.32, 17, 4, 22];
var e1 = {};
var e2 = 31;
console.log(getArrayDiff(arr1, arr2));
// Array empty.
console.log(getArrayDiff([], arr2));
console.log(getArrayDiff(arr1, []));
console.log(getArrayDiff([], []));
// Wrong parameter
console.log(getArrayDiff(arr1, e2));
console.log(getArrayDiff(e1, arr2));
console.log(getArrayDiff(e1, e2));
console.log('String assigned: ', getArrayDiff(arr1, str1));
console.log('String assigned: ', getArrayDiff(str2, arr1));
console.log(getArrayDiff(not, arr1));
console.log('Float assigned: ', getArrayDiff(flt, arr1));
// ----- TESTING - END ----------------------
// The actual function
// ------------------------------------------
// Returns the numbers which are contained in
// ONE of two arrays.
// 1. Parameter: Array with ONLY integer values.
// 2. Parameter: Array with ONLY integer values.
// Return: Array with the difference set.
// ! Returns null in case of error.
function getArrayDiff(arr1, arr2) {
var ret = [];
var merged = [];
var current;
var i;
var sum;
var isArray = Array.isArray;
if (!isArray(arr1) || !isArray(arr2))
return null;
if (!arr1.length && arr2.length) {
return arr2;
} else if (arr1.length && !arr2.length) {
return arr1;
} else if (!arr1.length && !arr2.length) {
return [];
}
merged = arr1.concat(arr2);
merged.sort(function(a, b) {
return a - b;
});
for (i = 0; i < merged.length; i++) {
sum = 0;
if ( isNaN(merged[i]) ||
typeof merged[i] !== 'number' ||
merged[i] % 1 !== 0 )
return null;
if (merged[i] !== current || i === 0) {
current = merged[i];
} else {
continue;
}
if (arr1.indexOf(current) !== -1)
sum++;
if (arr2.indexOf(current) !== -1)
sum++;
if (sum === 1)
ret.push(current);
};
return ret
}
1 Answer 1
Feedback
- Use JSDoc comment style for the function, this style is widely used and most developers understand it.
- Add comments inside your function to describe what every variable is used for and what every section is doing.
- Do all the validations before starting to compute the result. So instead of checking if the elements are numbers while constructing the result; you should check them before.
- Whenever you find yourself doing something twice, extract it to a separate function.
- Throwing an error when it happens is more useful then returning
null
, it will give the user of your code a hint about what is going wrong and stops the execution of the code instead of usingnull
as an array or checking the result every time your function is called.
Refactored code
/**
* Checks if the given argument is an integer or not.
* @param {Mixed} n the argument to check
* @return {Boolean} TRUE if n is an integer; FALSE otherwise.
*/
function isInteger (n) {
return !isNaN(n) && typeof n === 'number' && n % 1 === 0;
}
/**
* Checks if the given argument is an array of integers.
* @param {Mixed} array the argument to check.
* @return {Boolean} TRUE if the given array is an array of integers
* (or an empty array); FALSE otherwise.
*/
function isArrayOfIntegers (array) {
return Array.isArray(array) && array.length == array.filter(isInteger).length;
}
/**
* Gets an object in which the keys are the items of the given array
* and the values are TRUE.
* @param {Array} array Array of simple elemets (numbers or strings)
* @return {Object}
*/
function toIndex (array) {
return array.reduce(function(index, value) {
index[value] = true;
return index;
}, {});
}
/**
* Gets the array of elemets which are present exclusively in one of the arrays.
* @param {Array} array1 First array of integers.
* @param {Array} array2 Second array of integers.
* @return {Array} Array of the integers which are present
* exclusively in one of the two arrays.
* @throws {Error} If one of the two arguments is not an array of integers.
*/
function getArrayDiff (array1, array2) {
// Validating the arrays
if (!isArrayOfIntegers(array1) || !isArrayOfIntegers(array2)) {
throw Error('One of the given parameters is not an array of integers');
}
// Making objects from the arrays. Objects in which the keys
// are the values of the arrays which will allow to search
// the elements in logarithmic time
var index1 = toIndex(array1);
var index2 = toIndex(array2);
// Computing the union of the arrays
var result = array1.concat(array2);
// Removing elements which are present in the two arrays
result = result.filter(function(item){
return index1[item] === undefined || index2[item] === undefined;
});
return result;
}
Hope this will help you and waiting for other members to give their suggestions and improve this code.
array_diff($a, $b)
in PHP will return the items of$a
which are not present in$b
. This is not what your function is actually doing. So are you sure you want to exact behaviour ofarray_diff
in PHP or you want to computeunion(a, b) - intersect(a, b)
which you call the difference ? \$\endgroup\$