Javascript full comparison function - javascript

I am developing a complete library (in Javascript) containing main structures and algorithms.
I need to design a compare function in order to compare every kind of data.
I’ll use this function as:
a = some data (string, number, object, array, ...)
b = some data (string, number, object, array, ...)
b.compare(a) :
0 if a is equal to b
1 if b i greater than
-1 if b is less than a
I designed my version and I got inspired by _.eq of underscore.js framework.
What do you think about it?
Is there any better way to implement it?
function compare (a, b, aStack, bStack) {
// with this I can youse compare as b.compare(a)
b = b || this.data;
// Unwrap any wrapped objects.
if (b instanceof ObjectNode) b = b.data;
if (a instanceof ObjectNode) a = a.data;
// Identical objects are equal. `0 === -0`, but they aren't identical.
// See the [Harmony `egal` proposal](http://wiki.ecmascript.org/doku.php?id=harmony:egal).
if (a === b) {
if(a !== 0) return 0;
else if(1 / a === 1 / b) return 0;
else return (1 / b > 1 / a)? 1 : -1;
}
// A strict comparison is necessary because `null == undefined`.
if (a == null || b == null || a == undefined || b == undefined) {
if(a === b) return 0;
/* Now I am defining:
(NaN > null)
null > undefined
null < everything else
undefined < everything */
if(a == undefined) return 1;
if(b == undefined) return -1;
if(a == null) return 1;
}
// Compare `[[Class]]` names.
var className = toString.call(a);
if (className !== toString.call(b)) {
// In this case I compare strings;
if(className < toString.call(b)) 1;
if(className > toString.call(b)) -1;
}
switch (className) {
// Strings, numbers, regular expressions, dates, and booleans are compared by value.
case '[object RegExp]':
// RegExps are coerced to strings for comparison (Note: '' + /a/i === '/a/i')
case '[object String]':
// Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is
// equivalent to `new String("5")`.
if('' + a === '' + b) return 0;
if('' + a < '' + b) return 1;
if('' + a > '' + b) return -1;
case '[object Number]':
// `NaN`s are equivalent, but non-reflexive.
// Object(NaN) is equivalent to NaN
// Nan is less than anyother but bigger than undefined and than null
if (+a !== +a) return (+b !== +b)? 0 : 1;
// An `egal` comparison is performed for other numeric values.
if (+a === 0) return (1 / +b === 1 / a)? 0 : ((1 / +b > 1 / a)? 1 : -1);
return (+a === +b)? 0 : (+b > +a)? 1 : -1;
case '[object Date]':
case '[object Boolean]':
// Coerce dates and booleans to numeric primitive values. Dates are compared by their
// millisecond representations. Note that invalid dates with millisecond representations
// of `NaN` are not equivalent.
return (+a === +b) ? 0 : (+b > +a)? 1 : -1;
}
var areArrays = className === '[object Array]';
if (!areArrays) {
if (typeof a != 'object') return 1;
if (typeof b != 'object') return -1;
// Objects with different constructors are not equivalent, but `Object`s or `Array`s
// from different frames are.
var aCtor = a.constructor, bCtor = b.constructor;
if (aCtor !== bCtor && !((typeof aCtor == 'function') && aCtor instanceof aCtor &&
(typeof bCtor == 'function') && bCtor instanceof bCtor)
&& ('constructor' in a && 'constructor' in b)) {
return b>a? 1 : -1;
}
}
// Assume equality for cyclic structures. The algorithm for detecting cyclic
// structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`.
// Initializing stack of traversed objects.
// It's done here since we only need them for objects and arrays comparison.
aStack = aStack || [];
bStack = bStack || [];
var length = aStack.length;
while (length--) {
// Linear search. Performance is inversely proportional to the number of
// unique nested structures.
if (aStack[length] === a) return bStack[length] === b;
}
// Add the first object to the stack of traversed objects.
aStack.push(a);
bStack.push(b);
// Recursively compare objects and arrays.
if (areArrays) {
// Compare array lengths to determine if a deep comparison is necessary.
length = a.length;
if (length !== b.length) return b.length>a.length? 1 : -1;
// Deep compare the contents, ignoring non-numeric properties.
var res;
while (length--) {
if ( (res = this.compare(a[length], b[length], aStack, bStack)) != 0) return res;
}
} else {
// Deep compare objects.
var keys = Object.keys(a), key;
length = keys.length;
// Ensure that both objects contain the same number of properties before comparing deep equality.
if (Object.keys(b).length !== length) return (Object.keys(b).length > length)? 1 : -1;
while (length--) {
// Deep compare each member
key = keys[length];
if (!(hasOwnProperty.call(b, key) && (res = this.compare(a[key], b[key], aStack, bStack)) === 0)) return res;
}
}
// Remove the first object from the stack of traversed objects.
aStack.pop();
bStack.pop();
return 0;
}

Related

How to sort a JS object list based on a property when the property is not consistent

I have list that contain various object. A few objects on this list have a date field (which basically is returned to me as a string from server, not a date object), while for others this field is null.
The requirement I have is to display objects without date at top, and those with date needs to be displayed after them sorted by date field.
Also for objects without date sorting needs to be done alphabetically.
Earlier I was using
$scope.lists.sort(function (a, b) {
return new Date(a.date.split("-")[2], a.date.split("-")[1], a.date.split("-")[0]) - new Date(b.date.split("-")[2], b.date.split("-")[1], b.date.split("-")[0]);
});
But now with null date fields this would not work. So unable to find anything, I wrote this logic:
{
var datelists=[];
var savelists =[];
$scope.lists.forEach(function (t) {
if (t.date !== null) {
datelists.push(t);
} else {
savelists.push(t);
}
});
datelists.sort(function (a, b) {
return new Date(a.date.split("-")[2], a.date.split("-")[1], a.date.split("-")[0]) - new Date(b.date.split("-")[2], b.date.split("-")[1], b.date.split("-")[0]);
});
savelists.sort(function (a, b) {
return a.name - b.name;
});
$scope.lists = [];
$scope.lists = savelists.concat(datelists);
}
I don't like this long method. I am sure there is an elegant way to do this.
I would like to know what other alternatives do I have?
To avoid splitting the arrays, sort the entire array on primary and secondary keys of date and text. Usingobj.date and obj.text as example property names holding date and sort text respectively:
function dateCompare( d, e)
{ // code to compare date strings
// return -1 for date d before e
// 0 for date d same as e
// +1 for date e before d
// algorithm depends on server date string format but could start
if( !d)
return e ? -1 : 0; // d is empty
if( !e)
return 1; // e is empty, d is not
// ... compare date strings
}
function textCompare(s, t)
{ // code to compare string values
// return -1 for s < t
// 0 for s == t
// +1 for t > s
// algorithms vary according to requirements.
}
function objCompare( a, b)
{ // return a non zero result of dateCompare, or the result of textCompare:
return dateCompare(a.date, b.date) || textCompare( a.text, b.text);
}
$scope.lists.sort( objCompare);
outlines how to go about it without getting into application specifics. Convert objCompare into an inline anonymous function with nested date and text comparison support functions (or inline the code) as required to match with existing programming style.
While sorting you must check if date is null, is undefined or is normal date (not tested)
.sort(function (a, b) {
// sort alphabetically
if (typeof a.date == 'undefined' && typeof b.date != 'undefined') {
return -1;
} else if (typeof a.date != 'undefined' && typeof b.date == 'undefined') {
return 1;
} else if (typeof a.date == 'undefined' && typeof b.date == 'undefined') {
return a.name.localeCompare(b.name);
// move null to top
} else if (a.date == null && b.date != null) {
return -1;
} else if (a.date != null && b.date == null) {
return 1;
} else if (a.date == null && b.date == null) {
return 0;
// both objects has date, sort by date.
} else {
var d1 = Date.parse(a.date);
var d2 = Date.parse(b.date);
return d1 - d2;
}
})
First, you can convert date to an ISO6801 date string.
If a date is supplied, or a falsy value, which is replaced by an empty string, you could use a String#localeCompare. This sorts empty strings to top.
If the date is the same or both have no date, then its sorted by name.
$scope.lists.sort(function (a, b) {
function date(s) {
return s.replace(/(\d{2})-(\d{2})-(\d{4})/g, '$3-$2-$1');
}
return (date(a.date) || '').localeCompare(date(b.date) || '') ||
a.name.localeCompare(b.name);
});

How can one compare string and numeric values (respecting negative values, with null always last)?

I'm trying to sort an array of values that can be a mixture of numeric or string values (e.g. [10,"20",null,"1","bar","-2",-3,null,5,"foo"]). How can I sort this array such that
null values are always placed last (regardless of sorting order, see jsFiddle)
negative numbers are sorted correctly (i.e. they are less than positive numbers and sort correctly amongst themselves)
? I made a jsFiddle with detailed numeric and string examples (using localeCompare and the numeric option), but will paste the numeric version of my sorting algorithm below as a starting point.
// Sorting order
var order = "asc"; // Try switching between "asc" and "dsc"
// Dummy arrays
var numericArr = [10,20,null,1,-2,-3,null,5];
// Sort arrays
$(".output1").append(numericArr.toString());
numericArr.sort(sortByDataNumeric);
$(".output2").append(numericArr.toString());
// Numeric sorting function
function sortByDataNumeric(a, b, _order) {
// Replace internal parameters if not used
if (_order == null) _order = order;
// If values are null, place them at the end
var dflt = (_order == "asc" ? Number.MAX_VALUE : -Number.MAX_VALUE);
// Numeric values
var aVal = (a == null ? dflt : a);
var bVal = (b == null ? dflt : b);
return _order == "asc" ? (aVal - bVal) : (bVal - aVal);
}
The problem with my string sorting algorithm (see jsFiddle) is that I can't find a way to always place null values last and negative values aren't correctly sorted within themselves (e.g. -3 should be less than -2)
Edit
To answer the comments, I expect [10,"20",null,"1","bar","-2",-3,null,5,"foo"] to sort to [-3,"-2","1",5,10,"20","bar","foo",null,null]
You should first check to see if either value is null and return the opposite value.
On a side note:
For your default _order value, you should check if the parameter is undefined instead of comparing its value to null. If you try to compare something that is undefined directly you will get a reference error:
(undefinedVar == null) // ReferenceError: undefinedVar is not defined
Instead, you should check if the variable is undefined:
(typeof undefinedVar == "undefined") // true
Also, it's probably a better idea to wrap your compare function in a closure instead of relying on a global order variable.
Sometime like:
[].sort(function(a, b){ return sort(a, b, order)})
This way you can sort at a per-instance level.
http://jsfiddle.net/gxFGN/10/
JavaScript
function sort(a, b, asc) {
var result;
/* Default ascending order */
if (typeof asc == "undefined") asc = true;
if (a === null) return 1;
if (b === null) return -1;
if (a === null && b === null) return 0;
result = a - b;
if (isNaN(result)) {
return (asc) ? a.toString().localeCompare(b) : b.toString().localeCompare(a);
}
else {
return (asc) ? result : -result;
}
}
function sortByDataString(a, b) {
if (a === null) {
return 1;
}
if (b === null) {
return -1;
}
if (isNumber(a) && isNumber(b)) {
if (parseInt(a,10) === parseInt(b,10)) {
return 0;
}
return parseInt(a,10) > parseInt(b,10) ? 1 : -1;
}
if (isNumber(a)) {
return -1;
}
if (isNumber(b)) {
return 1;
}
if (a === b) {
return 0;
}
return a > b ? 1 : -1;
}
fiddle here: http://jsfiddle.net/gxFGN/6/
I left out the order parameter, but you could always reverse the array at the end if needed.
Use this:
function typeOrder(x) {
if (x == null)
return 2;
if (isNaN(+x))
return 1;
return 0;
}
function sortNumber(a, b) {
a = parseInt(a, 10); b = parseInt(b, 10);
if (isNaN(a) || isNaN(b))
return 0;
return a - b;
}
function sortString(a, b) {
if (typeof a != "string" || typeof b != "string")
return 0;
return +(a > b) || -(b > a);
}
order = order == "dsc" ? -1 : 1;
numericArr.sort(function(a, b) {
return order * ( typeOrder(a)-typeOrder(b)
|| sortNumber(a, b)
|| sortString(a, b)
);
});
(updated fiddle)
I'm pretty sure that your problem is a red herring... the abstract function that you past into sort doesn't get a third parameter (in your case _order). So in your situation that's always going to be undefined.
Please reconsider your code with that in mind and see what you get.
The array you specify is entirely Numeric so your sort should work correctly, though as other commenters have suggested, if your array ever winds up with string values (i.e. "10", "-7" etc) you'll want to parseInt and test for isNaN before doing your comparison.

Best and/or shortest way to do strict (non-type converting) <, >, <=, >= comparison in Javascript

In Javascript, the == comparison has a strict (non-type converting) version: ===. Likewise, != has the strict form !==. These protect you from the following craziness:
var s1 = "1",
i1 = 1,
i2 = 2;
(s1 == i1) // true, type conversion
(s1 != i1) // false, type conversion
(s1 === i1) // false, no type conversion
(s1 !== i1) // true, no type conversion
However, the other comparison operators have no equivalent strict modes:
(s1 < i2) // true, type conversion
(s1 <= i2) // true, type conversion
([] < i2) // true, wait ... wat!?
The obvious solution seems pretty verbose:
((typeof s1 === typeof i2) && (s1 < i2)) // false
Is there a more idiomatic (or just less verbose) way to do this in Javascript?
Reference: MDN Comparison Operators
There are no built-in operators for what you want, but you can always create your own functions. For example, for <:
function lt(o1, o2) {
return ((typeof o1 === typeof o2) && (o1 < o2));
}
lt("10", 11); // false
Another option, if you're only dealing with strings and numbers, is extending String.prototype and Number.prototype:
function lt(o) {
return ((typeof this.valueOf() === typeof o) && (this < o));
}
String.prototype.lt = lt;
Number.prototype.lt = lt;
"10".lt(11); // false
(11).lt("12"); // false
How about creating a Object and using it
var strictComparison = {
"<" : function(a,b) { return ((typeof a === typeof b) && (a < b)) },
"<=" : function(a,b) { return ((typeof a === typeof b) && (a <= b)) },
">" : function(a,b) { return ((typeof a === typeof b) && (a > b)) },
">=" : function(a,b) { return ((typeof a === typeof b) && (a >= b)) }
};
console.log(strictComparison["<"](5,"6")) ;
console.log(strictComparison[">"](5,6)) ;

Testing for equality of regular expressions

I was surprised to see that
/a/ === /a/
evaluates to false in JavaScript. Reading through the specs:
Two regular expression literals in a program evaluate to regular
expression objects that never compare as === to each other even if the
two literals' contents are identical.
Since === cannot be used to test for equality, how can equality of regular expressions be tested in JavaScript?
Here's a case that even covers ordering of flags.
function regexEqual(x, y) {
return (x instanceof RegExp) && (y instanceof RegExp) &&
(x.source === y.source) && (x.global === y.global) &&
(x.ignoreCase === y.ignoreCase) && (x.multiline === y.multiline);
}
Tests:
regexEqual(/a/, /a/) // true
regexEqual(/a/gi, /a/ig) // also true.
regeXEqual(/a/, /b/) // false
Here's a function that fully tests all the relevant regex properties and makes sure it's the right type of object:
function regexSame(r1, r2) {
if (r1 instanceof RegExp && r2 instanceof RegExp) {
var props = ["global", "multiline", "ignoreCase", "source", "dotAll", "sticky", "unicode"];
for (var i = 0; i < props.length; i++) {
var prop = props[i];
if (r1[prop] !== r2[prop]) {
return false;
}
}
return true;
}
return false;
}
And, since flags sometimes get added to the regex object with new features (as has happened since this original answer in 2012 - though the above code has been updated as of 2019), here's a version that is a bit more future proof on future flags being added since it compares whatever flags are there rather than looking for a specific set of flags. It sorts the flags before comparing to allow for minor differences in how the regex was specified that wouldn't not actually change functionality.
function regexSame(r1, r2) {
return r1 instanceof RegExp &&
r2 instanceof RegExp &&
r1.source === r2.source &&
r1.flags.split("").sort().join("") === r2.flags.split("").sort().join("");
}
Compare them using toString(), and check their type too:
var a = /a/,
b = /a/;
a.toString() === b.toString() && typeof(a) === typeof(b) //true
var c = /a/,
d = /b/;
c.toString() === d.toString() && typeof(c) === typeof(d) //false
You can check the types with typeof, then toString() both regexes and compare those. It won't cover cases with equivalent flags, such as /a/gi and /a/ig, though.
function regexEquals(a, b)
{
if (typeof a !== 'object' || typeof b !== 'object') return false;
return a.toString() === b.toString();
}
Unfortunately there's no more-specific type from typeof, so if you really want to make sure they're regexes (or regex-like) you could do something along these lines:
RegExp.prototype.regexEquals = function (other)
{
return (typeof other.regexEquals === 'function')
&& (this.toString() === other.toString());
}
Then:
/a/.regexEquals(/a/); // true
/a/.regexEquals(/b/); // false
Answers above didn't consider case-sensitivity. So built upon jfriend00's answer, the function should be
function regexEqual(a, b) {
if (!(a instanceof RegExp) || !(b instanceof RegExp)) {
return false;
}
let sourceA = a.source;
let sourceB = b.source;
const flagsA = a.flags.split('').sort().join(',');
const flagsB = b.flags.split('').sort().join(',');
if (flagsA.includes('i') && flagsB.includes('i')) {
sourceA = sourceA.toLowerCase();
sourceB = sourceB.toLowerCase();
}
return sourceA === sourceB && flagsA === flagsB;
}

Find element in Javascript Array [duplicate]

This question already has answers here:
Closed 11 years ago.
Possible Duplicate:
Javascript - array.contains(obj)
What's wrong with this:
var zipCodes =(['90001','90002','90003']);
Test if the value exists in the array zipCodes
if('90001' in zipCodes) {
alert('True');
};
The in operator looks at property names, not values.
Because it's an Array, the property names will be the indices of the Array.
If you're only supporting a modern environment, you could use Array.prototype.indexOf().
if(zipCodes.indexOf('90001') > -1) {
If you need to support environments that don't have .indexOf(), you could implement the MDN fix.
if (!Array.prototype.indexOf) {
Array.prototype.indexOf = function (searchElement /*, fromIndex */ ) {
"use strict";
if (this === void 0 || this === null) throw new TypeError();
var t = Object(this);
var len = t.length >>> 0;
if (len === 0) return -1;
var n = 0;
if (arguments.length > 0) {
n = Number(arguments[1]);
if (n !== n) // shortcut for verifying if it's NaN
n = 0;
else if (n !== 0 && n !== (1 / 0) && n !== -(1 / 0)) n = (n > 0 || -1) * Math.floor(Math.abs(n));
}
if (n >= len) return -1;
var k = n >= 0 ? n : Math.max(len - Math.abs(n), 0);
for (; k < len; k++) {
if (k in t && t[k] === searchElement) return k;
}
return -1;
};
}
If you want to check if the array contains a given value, you can use the indexOf method to check for the position of an item. If the item is not found in the array, a -1 is returned:
var zipCodes =(['90001','90002','90003']);
zipCodes.indexOf('90001') // 0
zipCodes.indexOf('90002') // 1
zipCodes.indexOf('90003') // 2
zipCodes.indexOf('90004') // -1
if(zipCodes.indexOf('90001') != -1) {
alert('True');
};
See more at http://freewebdesigntutorials.com/javaScriptTutorials/jsStringObject/indexOfMethod.htm
Use an object instead. If this is all you're trying to do with the array, then an object is a much more efficient way to do a lookup list.
var zipCodes = {"90001": true, "90002": true, "90003": true};
if ('90001' in zipCodes) {
alert('True');
}
jsfiddle here to see it work: http://jsfiddle.net/jfriend00/ZNGTq/
You need something like this:
var zipCodes =(['90001','90002','90003']);
if (zipCodes.has('90001')) {
....
}
Array.prototype.has=function(v){
for (i=0;i<this.length;i++){
if (this[i]==v) return i;
}
return false;
}
See this for more info:
http://snook.ca/archives/javascript/testing_for_a_v
....
Because in checks for a property of an object. Check this out for converting "in object" to "in array": Testing for a Value in JavaScript Array

Categories

Resources