About a sort function for javascript - javascript

Background:
As needed in some task, I need a simple sort function. For simplicity, I wrote another function to wrap the built-in sort function as:
function sortBy(obj, extra, func){
if(typeof func == 'function'){
f = func;
} else if(typeof extra != 'function'){
eval('function f(a, b, ai, bi, e){return ' + func + '}');
} else {
var f = extra;
extra = null;
}
var res = [];
for(var i in obj){
if(obj.hasOwnProperty(i)){
obj[i]._k_ = i;
res.push(obj[i]);
}
}
res.sort(function(a, b){
if(f(a, b, a._k_, b._k_, extra)){
return 1;
} else {
return -1;
}
})
return res;
}
My attempts are:
Make it possible to sort a object directly
Keep the original object as the hash table
Allow some simple syntax
For instance,
var data ={
12: {age:27, name:'pop', role: 'Programmer'},
32: {age:25, name:'james', role: 'Accontant'},
123:{age:19, name:'jerry', role:'Sales Representative'},
15:{age:22, name:'jerry', role:'Coder'},
17:{age:19, name:'jerry', role:'Tester'},
43:{age:14, name:'anna', role: 'Manager'},
55: {age:31, name:'luke', role:'Analyst'}
};
There are several usages:
var b = sortBy(data, '', 'a.age < b.age'); // a simple sort, order by age
var b = sortBy(data, 19, 'b.age == e'); // pick up all records of age 19, and put them in the beginning
var b = sortBy(data, function(a, b){return a.name > b.name}); // anonymous sort function is also allowed
QUESTION
Though it works as expected in our code, I would like to raise some question:
Is there any potiential problem about using eval to create sort function from string?
Is there any story about sort function returning -1(nagative), 0 and 1(positive)?
Can we change the code as "return if(f(a, b, a.k, b.k, extra)", instead of returning 1 or -1? We found it works in our firefox and chrome, but not sure whether it is safe to do so.

1. Is there any potiential problem about using eval to create sort function from string?
Not per se, but it does suffer all the same deficiencies as calling other eval-style functions with strings, e.g. setTimeout() or the Function() constructor. As long as you trust the source of the string there's no real problem. However, I would consider using the Function constructor instead:
f = new Function(a, b, ai, bi, e, 'return ' + func);
It's more manageable and it's definitely more appropriate than evaluating a function declaration.
2. Is there any story about sort function returning -1(nagative), 0 and 1(positive)?
Not really understanding this part of your question, but your function doesn't appear to tackle what to do if two items are the same from the comparison. You should be returning less than 0, 0 or more than 0 depending on the result. The best approach for this is to use String.prototype.localeCompare():
return String.prototype.localeCompare.call(a, b);

Try making it so you only do eval on a small part:
(fiddle: http://jsfiddle.net/maniator/SpJbN/)
function sortBy(obj, extra, func){
var f = null;
if(typeof func == 'function'){
f = func;
} else if(typeof extra != 'function'){
f = function(a, b, ai, bi, e){
return eval(func); // <-- smaller part
}
} else {
f = extra;
extra = null;
}
var res = [];
for(var i in obj){
if(obj.hasOwnProperty(i)){
obj[i]._k_ = i;
res.push(obj[i]);
}
}
res.sort(function(a, b){
if(f(a, b, a._k_, b._k_, extra)){
return 1;
} else {
return -1;
}
})
return res;
}

Thanks to Andy, we change the code to
var f = new Function('a, b, ai, bi, e', 'return ' + func);
Note that the arguments should be passed in as string, check out :
https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Function
About the second question, I think it is because we were trying to make the sort function more explicitly.
For instance,
sort([1, 2, 3, 5, 1], '', 'a < b') ==> [1, 1, 2, 3, 5]
Literal meaning of 'a < b' to us is "the latter item is greater than the former item", so the array is sorted to [1, 1, 2, 3, 5].
Another example, 'a.age < b.age' will return the records in the order that younger person comes before olders.
That's the reason I am asking can we use true or false instead of -1, 0, 1.
We keep doing some more small tests, and figure out something, would like to share with everyone.
For example:
var b = [
{age:27, name:'pop 2', role: 'Programmer'},
{age:19, name:'james', role: 'Accontant'},
{age:19, name:'jerry', role:'Sales Representative'},
{age:22, name:'jerry', role:'Coder'},
{age:19, name:'jerry', role:'Tester'},
{age:14, name:'anna', role: 'Manager'},
{age:19, name:'luke', role:'Analyst'},
{age:27, name:'pop', role: 'Programmer'},
{age:14, name:'anna 2', role: 'Manager'}
];
b.sort(function(a, b) {return a.age - b.age > 0? 1: -1}); // #1
b.sort(function(a, b) {return a.age > b.age}); // #2
b.sort(function(a, b) {return a.age - b.age}); // #3
Although the above sorts return the same result, try this one :
b.sort(function(a, b) {return a.age - b.age < 0? -1: 1}); // #4
In this statement, the records are still ordered by age, but the order is reversed within a same age group.
#1 and #2 is same as #3 by chance. If the browser use different algorithm to implement the sort function,
it is possible that #1 and #2 will behave like #4. If you are strict with the order of the result, you need
to explicitly return 0, when 'a' equals to item 'b'.
Besides, as Andy pointed out, in certain cases(e.g., #4), it is possible that unnecessary swaps are done if we don't return 0 explicitly, which could affect the performance.
The reason we didn't notice this before is because we didn't care the order within a group, providing the record
is sorted on certain property.

I think it would be appropriate to pass a function that does the comparing. so something like
sort(somedata, function(a, b) {
return a < b;
});

Related

includes function not working for plotly trace - js [duplicate]

A strict equality operator will tell you if two object types are equal. However, is there a way to tell if two objects are equal, much like the hash code value in Java?
Stack Overflow question Is there any kind of hashCode function in JavaScript? is similar to this question, but requires a more academic answer. The scenario above demonstrates why it would be necessary to have one, and I'm wondering if there is any equivalent solution.
Why reinvent the wheel? Give Lodash a try. It has a number of must-have functions such as isEqual().
_.isEqual(object, other);
It will brute force check each key value - just like the other examples on this page - using ECMAScript 5 and native optimizations if they're available in the browser.
Note: Previously this answer recommended Underscore.js, but lodash has done a better job of getting bugs fixed and addressing issues with consistency.
The short answer
The simple answer is: No, there is no generic means to determine that an object is equal to another in the sense you mean. The exception is when you are strictly thinking of an object being typeless.
The long answer
The concept is that of an Equals method that compares two different instances of an object to indicate whether they are equal at a value level. However, it is up to the specific type to define how an Equals method should be implemented. An iterative comparison of attributes that have primitive values may not be enough: an object may contain attributes which are not relevant to equality. For example,
function MyClass(a, b)
{
var c;
this.getCLazy = function() {
if (c === undefined) c = a * b // imagine * is really expensive
return c;
}
}
In this above case, c is not really important to determine whether any two instances of MyClass are equal, only a and b are important. In some cases c might vary between instances and yet not be significant during comparison.
Note this issue applies when members may themselves also be instances of a type and these each would all be required to have a means of determining equality.
Further complicating things is that in JavaScript the distinction between data and method is blurred.
An object may reference a method that is to be called as an event handler, and this would likely not be considered part of its 'value state'. Whereas another object may well be assigned a function that performs an important calculation and thereby makes this instance different from others simply because it references a different function.
What about an object that has one of its existing prototype methods overridden by another function? Could it still be considered equal to another instance that it otherwise identical? That question can only be answered in each specific case for each type.
As stated earlier, the exception would be a strictly typeless object. In which case the only sensible choice is an iterative and recursive comparison of each member. Even then one has to ask what is the 'value' of a function?
The default equality operator in JavaScript for Objects yields true when they refer to the same location in memory.
var x = {};
var y = {};
var z = x;
x === y; // => false
x === z; // => true
If you require a different equality operator you'll need to add an equals(other) method, or something like it to your classes and the specifics of your problem domain will determine what exactly that means.
Here's a playing card example:
function Card(rank, suit) {
this.rank = rank;
this.suit = suit;
this.equals = function(other) {
return other.rank == this.rank && other.suit == this.suit;
};
}
var queenOfClubs = new Card(12, "C");
var kingOfSpades = new Card(13, "S");
queenOfClubs.equals(kingOfSpades); // => false
kingOfSpades.equals(new Card(13, "S")); // => true
Short functional deepEqual implementation:
function deepEqual(x, y) {
return (x && y && typeof x === 'object' && typeof y === 'object') ?
(Object.keys(x).length === Object.keys(y).length) &&
Object.keys(x).reduce(function(isEqual, key) {
return isEqual && deepEqual(x[key], y[key]);
}, true) : (x === y);
}
Edit: version 2, using jib's suggestion and ES6 arrow functions:
function deepEqual(x, y) {
const ok = Object.keys, tx = typeof x, ty = typeof y;
return x && y && tx === 'object' && tx === ty ? (
ok(x).length === ok(y).length &&
ok(x).every(key => deepEqual(x[key], y[key]))
) : (x === y);
}
This is my version. It is using new Object.keys feature that is introduced in ES5 and ideas/tests from +, + and +:
function objectEquals(x, y) {
'use strict';
if (x === null || x === undefined || y === null || y === undefined) { return x === y; }
// after this just checking type of one would be enough
if (x.constructor !== y.constructor) { return false; }
// if they are functions, they should exactly refer to same one (because of closures)
if (x instanceof Function) { return x === y; }
// if they are regexps, they should exactly refer to same one (it is hard to better equality check on current ES)
if (x instanceof RegExp) { return x === y; }
if (x === y || x.valueOf() === y.valueOf()) { return true; }
if (Array.isArray(x) && x.length !== y.length) { return false; }
// if they are dates, they must had equal valueOf
if (x instanceof Date) { return false; }
// if they are strictly equal, they both need to be object at least
if (!(x instanceof Object)) { return false; }
if (!(y instanceof Object)) { return false; }
// recursive object equality check
var p = Object.keys(x);
return Object.keys(y).every(function (i) { return p.indexOf(i) !== -1; }) &&
p.every(function (i) { return objectEquals(x[i], y[i]); });
}
///////////////////////////////////////////////////////////////
/// The borrowed tests, run them by clicking "Run code snippet"
///////////////////////////////////////////////////////////////
var printResult = function (x) {
if (x) { document.write('<div style="color: green;">Passed</div>'); }
else { document.write('<div style="color: red;">Failed</div>'); }
};
var assert = { isTrue: function (x) { printResult(x); }, isFalse: function (x) { printResult(!x); } }
assert.isTrue(objectEquals(null,null));
assert.isFalse(objectEquals(null,undefined));
assert.isFalse(objectEquals(/abc/, /abc/));
assert.isFalse(objectEquals(/abc/, /123/));
var r = /abc/;
assert.isTrue(objectEquals(r, r));
assert.isTrue(objectEquals("hi","hi"));
assert.isTrue(objectEquals(5,5));
assert.isFalse(objectEquals(5,10));
assert.isTrue(objectEquals([],[]));
assert.isTrue(objectEquals([1,2],[1,2]));
assert.isFalse(objectEquals([1,2],[2,1]));
assert.isFalse(objectEquals([1,2],[1,2,3]));
assert.isTrue(objectEquals({},{}));
assert.isTrue(objectEquals({a:1,b:2},{a:1,b:2}));
assert.isTrue(objectEquals({a:1,b:2},{b:2,a:1}));
assert.isFalse(objectEquals({a:1,b:2},{a:1,b:3}));
assert.isTrue(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assert.isFalse(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));
Object.prototype.equals = function (obj) { return objectEquals(this, obj); };
var assertFalse = assert.isFalse,
assertTrue = assert.isTrue;
assertFalse({}.equals(null));
assertFalse({}.equals(undefined));
assertTrue("hi".equals("hi"));
assertTrue(new Number(5).equals(5));
assertFalse(new Number(5).equals(10));
assertFalse(new Number(1).equals("1"));
assertTrue([].equals([]));
assertTrue([1,2].equals([1,2]));
assertFalse([1,2].equals([2,1]));
assertFalse([1,2].equals([1,2,3]));
assertTrue(new Date("2011-03-31").equals(new Date("2011-03-31")));
assertFalse(new Date("2011-03-31").equals(new Date("1970-01-01")));
assertTrue({}.equals({}));
assertTrue({a:1,b:2}.equals({a:1,b:2}));
assertTrue({a:1,b:2}.equals({b:2,a:1}));
assertFalse({a:1,b:2}.equals({a:1,b:3}));
assertTrue({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assertFalse({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}.equals({1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));
var a = {a: 'text', b:[0,1]};
var b = {a: 'text', b:[0,1]};
var c = {a: 'text', b: 0};
var d = {a: 'text', b: false};
var e = {a: 'text', b:[1,0]};
var i = {
a: 'text',
c: {
b: [1, 0]
}
};
var j = {
a: 'text',
c: {
b: [1, 0]
}
};
var k = {a: 'text', b: null};
var l = {a: 'text', b: undefined};
assertTrue(a.equals(b));
assertFalse(a.equals(c));
assertFalse(c.equals(d));
assertFalse(a.equals(e));
assertTrue(i.equals(j));
assertFalse(d.equals(k));
assertFalse(k.equals(l));
// from comments on stackoverflow post
assert.isFalse(objectEquals([1, 2, undefined], [1, 2]));
assert.isFalse(objectEquals([1, 2, 3], { 0: 1, 1: 2, 2: 3 }));
assert.isFalse(objectEquals(new Date(1234), 1234));
// no two different function is equal really, they capture their context variables
// so even if they have same toString(), they won't have same functionality
var func = function (x) { return true; };
var func2 = function (x) { return true; };
assert.isTrue(objectEquals(func, func));
assert.isFalse(objectEquals(func, func2));
assert.isTrue(objectEquals({ a: { b: func } }, { a: { b: func } }));
assert.isFalse(objectEquals({ a: { b: func } }, { a: { b: func2 } }));
If you are working in AngularJS, the angular.equals function will determine if two objects are equal. In Ember.js use isEqual.
angular.equals - See the docs or source for more on this method. It does a deep compare on arrays too.
Ember.js isEqual - See the docs or source for more on this method. It does not do a deep compare on arrays.
var purple = [{"purple": "drank"}];
var drank = [{"purple": "drank"}];
if(angular.equals(purple, drank)) {
document.write('got dat');
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
If you are using a JSON library, you can encode each object as JSON, then compare the resulting strings for equality.
var obj1={test:"value"};
var obj2={test:"value2"};
alert(JSON.encode(obj1)===JSON.encode(obj2));
NOTE: While this answer will work in many cases, as several people have pointed out in the comments it's problematic for a variety of reasons. In pretty much all cases you'll want to find a more robust solution.
For those of you using Node, there is a convenient method called isDeepStrictEqual on the native util library that can achieve this.
const util = require('util');
const obj1 = {
foo: "bar",
baz: [1, 2]
};
const obj2 = {
foo: "bar",
baz: [1, 2]
};
obj1 == obj2 // false
util.isDeepStrictEqual(obj1, obj2) // true
https://nodejs.org/api/util.html#util_util_isdeepstrictequal_val1_val2
In Node.js, you can use its native require("assert").deepStrictEqual. More info:
http://nodejs.org/api/assert.html
For example:
var assert = require("assert");
assert.deepStrictEqual({a:1, b:2}, {a:1, b:3}); // will throw AssertionError
Another example that returns true / false instead of returning errors:
var assert = require("assert");
function deepEqual(a, b) {
try {
assert.deepEqual(a, b);
} catch (error) {
if (error.name === "AssertionError") {
return false;
}
throw error;
}
return true;
};
Are you trying to test if two objects are the equal? ie: their properties are equal?
If this is the case, you'll probably have noticed this situation:
var a = { foo : "bar" };
var b = { foo : "bar" };
alert (a == b ? "Equal" : "Not equal");
// "Not equal"
you might have to do something like this:
function objectEquals(obj1, obj2) {
for (var i in obj1) {
if (obj1.hasOwnProperty(i)) {
if (!obj2.hasOwnProperty(i)) return false;
if (obj1[i] != obj2[i]) return false;
}
}
for (var i in obj2) {
if (obj2.hasOwnProperty(i)) {
if (!obj1.hasOwnProperty(i)) return false;
if (obj1[i] != obj2[i]) return false;
}
}
return true;
}
Obviously that function could do with quite a bit of optimisation, and the ability to do deep checking (to handle nested objects: var a = { foo : { fu : "bar" } }) but you get the idea.
As FOR pointed out, you might have to adapt this for your own purposes, eg: different classes may have different definitions of "equal". If you're just working with plain objects, the above may suffice, otherwise a custom MyClass.equals() function may be the way to go.
Simplest and logical solutions for comparing everything Like Object, Array, String, Int...
JSON.stringify({a: val1}) === JSON.stringify({a: val2})
Note:
you need to replace val1and val2 with your Object
for the object, you have to sort(by key) recursively for both side objects
If you have a deep copy function handy, you can use the following trick to still use JSON.stringify while matching the order of properties:
function equals(obj1, obj2) {
function _equals(obj1, obj2) {
return JSON.stringify(obj1)
=== JSON.stringify($.extend(true, {}, obj1, obj2));
}
return _equals(obj1, obj2) && _equals(obj2, obj1);
}
Demo: http://jsfiddle.net/CU3vb/3/
Rationale:
Since the properties of obj1 are copied to the clone one by one, their order in the clone will be preserved. And when the properties of obj2 are copied to the clone, since properties already existing in obj1 will simply be overwritten, their orders in the clone will be preserved.
This question has more than 30 answers already. I am going to summarize and explain them (with a "my father" analogy) and add my suggested solution.
You have 4+1 classes of solutions:
1) Use a hacky incomplete quick one-liner
Good if you are in a rush and 99% correctness works.
Examples of this is, JSON.stringify() suggested by Pratik Bhalodiya, or JSON.encode by Joel Anair, or .toString(), or other methods that transform your objects into a String and then compare the two Strings using === character by character.
The drawback, however, is that there is no globally standard unique representation of an Object in String. e.g. { a: 5, b: 8} and {b: 8 and a: 5 } are equal.
Pros: Fast, quick.
Cons: Hopefully works! It will not work if the environment/browser/engine memorizes the ordering for objects (e.g. Chrome/V8) and the order of the keys are different (Thanks to Eksapsy.) So, not guaranteed at all. Performance wouldn't be great either in large objects.
My Father Analogy
When I am talking about my father, "my tall handsome father" and "my handsome tall father" are the same person! But the two strings are not the same.
Note that there is actually a correct (standard way) order of adjectives in English grammar, which says it should be a "handsome tall man," but you are risking your competency if you blindly assume Javascript engine of iOS 8 Safari is also abiding the same grammar, blindly! #WelcomeToJavascriptNonStandards
2) Write your own DIY recursive function
Good if you are learning.
Examples are atmin's solution.
The biggest disadvantage is you will definitely miss some edge cases. Have you considered a self-reference in object values? Have you considered NaN? Have you considered two objects that have the same ownProperties but different prototypical parents?
I would only encourage people to do this if they are practicing and the code is not going to go in production. That's the only case that reinventing the wheel has justifications.
Pros: Learning opportunity.
Cons: Not reliable. Takes time and concerns.
My Father Analogy
It's like assuming if my dad's name is "John Smith" and his birthday is "1/1/1970", then anyone whose name is "John Smith" and is born on "1/1/1970" is my father.
That's usually the case, but what if there are two "John Smith"s born on that day? If you think you will consider their height, then that's increasing the accuracy but still not a perfect comparison.
2.1 You limited scope DIY comparator
Rather than going on a wild chase of checking all the properties recursively, one might consider checking only "a limited" number of properties. For instance, if the objects are Users, you can compare their emailAddress field.
It's still not a perfect one, but the benefits over solution #2 are:
It's predictable, and it's less likely to crash.
You are driving the "definition" of equality, rather than relying on a wild form and shape of the Object and its prototype and nested properties.
3) Use a library version of equal function
Good if you need a production-level quality, and you cannot change the design of the system.
Examples are _.equal of lodash, already in coolaj86's answer or Angular's or Ember's as mentioned in Tony Harvey's answer or Node's by Rafael Xavier.
Pros: It's what everyone else does.
Cons: External dependency, which can cost you extra memory/CPU/Security concerns, even a little bit. Also, can still miss some edge cases (e.g. whether two objects having same ownProperties but different prototypical parents should be considered the same or not.) Finally, you might be unintentionally band-aiding an underlying design problem with this; just saying!
My Father Analogy
It's like paying an agency to find my biological father, based on his phone, name, address, etc.
It's gonna cost more, and it's probably more accurate than myself running the background check, but doesn't cover edge cases like when my father is immigrant/asylum and his birthday is unknown!
4) Use an IDentifier in the Object
Good if you [still] can change the design of the system (objects you are dealing with) and you want your code to last long.
It's not applicable in all cases, and might not be very performant. However, it's a very reliable solution, if you can make it.
The solution is, every object in the system will have a unique identifier along with all the other properties. The uniqueness of the identifier will be guaranteed at the time of generation. And you will use this ID (also known as UUID/GUID -- Globally/Universally Unique Identifier) when it comes to comparing two objects. i.e. They are equal if and only if these IDs are equal.
The IDs can be simple auto_incremental numbers, or a string generated via a library (advised) or a piece of code. All you need to do is make sure it's always unique, which in case of auto_incremental it can be built-in, or in case of UUID, can be checked will all existing values (e.g. MySQL's UNIQUE column attribute) or simply (if coming from a library) be relied upon giving the extremely low likelihood of a collision.
Note that you also need to store the ID with the object at all times (to guarantee its uniqueness), and computing it in real-time might not be the best approach.
Pros: Reliable, efficient, not dirty, modern.
Cons: Needs extra space. Might need a redesign of the system.
My Father Analogy
It's like known my father's Social Security Number is 911-345-9283, so anyone who has this SSN is my father, and anyone who claims to be my father must have this SSN.
Conclusion
I personally prefer solution #4 (ID) over them all for accuracy and reliability. If it's not possible I'd go with #2.1 for predictability, and then #3. If neither is possible, #2 and finally #1.
var object1 = {name: "humza" , gender : "male", age: 23}
var object2 = {name: "humza" , gender : "male", age: 23}
var result = Object.keys(object1).every((key) => object1[key] === object2[key])
Result will be true if object1 has same values on object2.
I use this comparable function to produce copies of my objects that are JSON comparable:
var comparable = o => (typeof o != 'object' || !o)? o :
Object.keys(o).sort().reduce((c, key) => (c[key] = comparable(o[key]), c), {});
// Demo:
var a = { a: 1, c: 4, b: [2, 3], d: { e: '5', f: null } };
var b = { b: [2, 3], c: 4, d: { f: null, e: '5' }, a: 1 };
console.log(JSON.stringify(comparable(a)));
console.log(JSON.stringify(comparable(b)));
console.log(JSON.stringify(comparable(a)) == JSON.stringify(comparable(b)));
<div id="div"></div>
Comes in handy in tests (most test frameworks have an is function). E.g.
is(JSON.stringify(comparable(x)), JSON.stringify(comparable(y)), 'x must match y');
If a difference is caught, strings get logged, making differences spottable:
x must match y
got {"a":1,"b":{"0":2,"1":3},"c":7,"d":{"e":"5","f":null}},
expected {"a":1,"b":{"0":2,"1":3},"c":4,"d":{"e":"5","f":null}}.
Heres's a solution in ES6/ES2015 using a functional-style approach:
const typeOf = x =>
({}).toString
.call(x)
.match(/\[object (\w+)\]/)[1]
function areSimilar(a, b) {
const everyKey = f => Object.keys(a).every(f)
switch(typeOf(a)) {
case 'Array':
return a.length === b.length &&
everyKey(k => areSimilar(a.sort()[k], b.sort()[k]));
case 'Object':
return Object.keys(a).length === Object.keys(b).length &&
everyKey(k => areSimilar(a[k], b[k]));
default:
return a === b;
}
}
demo available here
I don't know if anyone's posted anything similar to this, but here's a function I made to check for object equalities.
function objectsAreEqual(a, b) {
for (var prop in a) {
if (a.hasOwnProperty(prop)) {
if (b.hasOwnProperty(prop)) {
if (typeof a[prop] === 'object') {
if (!objectsAreEqual(a[prop], b[prop])) return false;
} else {
if (a[prop] !== b[prop]) return false;
}
} else {
return false;
}
}
}
return true;
}
Also, it's recursive, so it can also check for deep equality, if that's what you call it.
ES6: The minimum code I could get it done, is this. It do deep comparison recursively by stringifying all key value array sorted representing the object, the only limitation is no methods or symbols are compare.
const compareObjects = (a, b) => {
let s = (o) => Object.entries(o).sort().map(i => {
if(i[1] instanceof Object) i[1] = s(i[1]);
return i
})
return JSON.stringify(s(a)) === JSON.stringify(s(b))
}
console.log(compareObjects({b:4,a:{b:1}}, {a:{b:1},b:4}));
IMPORTANT: This function is doing a JSON.stringfy in an ARRAY with the keys sorted and NOT in the object it self:
["a", ["b", 1]]
["b", 4]
Below is a short implementation which uses JSON.stringify but sorts the keys as #Jor suggested here.
Some tests were taken from the answer of #EbrahimByagowi here.
Of course, by using JSON.stringify, the solution is limited to JSON-serializable types (a string, a number, a JSON object, an array, a boolean, null). Objects like Date, Function, etc. are not supported.
function objectEquals(obj1, obj2) {
const JSONstringifyOrder = obj => {
const keys = {};
JSON.stringify(obj, (key, value) => {
keys[key] = null;
return value;
});
return JSON.stringify(obj, Object.keys(keys).sort());
};
return JSONstringifyOrder(obj1) === JSONstringifyOrder(obj2);
}
///////////////////////////////////////////////////////////////
/// The borrowed tests, run them by clicking "Run code snippet"
///////////////////////////////////////////////////////////////
var printResult = function (x) {
if (x) { document.write('<div style="color: green;">Passed</div>'); }
else { document.write('<div style="color: red;">Failed</div>'); }
};
var assert = { isTrue: function (x) { printResult(x); }, isFalse: function (x) { printResult(!x); } }
assert.isTrue(objectEquals("hi","hi"));
assert.isTrue(objectEquals(5,5));
assert.isFalse(objectEquals(5,10));
assert.isTrue(objectEquals([],[]));
assert.isTrue(objectEquals([1,2],[1,2]));
assert.isFalse(objectEquals([1,2],[2,1]));
assert.isFalse(objectEquals([1,2],[1,2,3]));
assert.isTrue(objectEquals({},{}));
assert.isTrue(objectEquals({a:1,b:2},{a:1,b:2}));
assert.isTrue(objectEquals({a:1,b:2},{b:2,a:1}));
assert.isFalse(objectEquals({a:1,b:2},{a:1,b:3}));
assert.isTrue(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:26}}));
assert.isFalse(objectEquals({1:{name:"mhc",age:28}, 2:{name:"arb",age:26}},{1:{name:"mhc",age:28}, 2:{name:"arb",age:27}}));
Just wanted to contribute my version of objects comparison utilizing some es6 features. It doesn't take an order into account. After converting all if/else's to ternary I've came with following:
function areEqual(obj1, obj2) {
return Object.keys(obj1).every(key => {
return obj2.hasOwnProperty(key) ?
typeof obj1[key] === 'object' ?
areEqual(obj1[key], obj2[key]) :
obj1[key] === obj2[key] :
false;
}
)
}
you can use _.isEqual(obj1, obj2) from the underscore.js library.
Here is an example:
var stooge = {name: 'moe', luckyNumbers: [13, 27, 34]};
var clone = {name: 'moe', luckyNumbers: [13, 27, 34]};
stooge == clone;
=> false
_.isEqual(stooge, clone);
=> true
See the official documentation from here: http://underscorejs.org/#isEqual
Assuming that the order of the properties in the object is not changed.
JSON.stringify() works for deep and non-deep both types of objects, not very sure of performance aspects:
var object1 = {
key: "value"
};
var object2 = {
key: "value"
};
var object3 = {
key: "no value"
};
console.log('object1 and object2 are equal: ', JSON.stringify(object1) === JSON.stringify(object2));
console.log('object2 and object3 are equal: ', JSON.stringify(object2) === JSON.stringify(object3));
let std1 = {
name: "Abhijeet",
roll: 1
}
let std2 = {
name: "Siddharth",
roll: 2
}
console.log(JSON.stringify(std1) === JSON.stringify(std2))
One easy way I have found to compare the values of two javascript objects while ignoring property order is with the JSON stringify replacer function:
const compareReplacer = (key, value) => {
if(typeof value === 'object' && !(value instanceof Array))
return Object.entries(value).sort();
return value;
}
export const compareObjects = (a, b) => JSON.stringify(a, compareReplacer) === JSON.stringify(b, compareReplacer);
This will order the properties at every step of the way so that the string result will be invariant to property order. Some one has probably done this before but I just thought I would share it incase not :).
EDIT: This method is quite flawed, and is rife with its own issues. I don't recommend it, and would appreciate some down-votes! It is problematic because 1) Some things can not be compared (i.e. functions) because they can not be serialized, 2) It isn't a very fast method of comparing, 3) It has ordering issues, 4) It can have collision issues/false positives if not properly implemented, 5) It can't check for "exactness" (===), and instead is based of value equality, which is oftentimes not what is desired in a comparison method.
A simple solution to this issue that many people don't realize is to sort the JSON strings (per character). This is also usually faster than the other solutions mentioned here:
function areEqual(obj1, obj2) {
var a = JSON.stringify(obj1), b = JSON.stringify(obj2);
if (!a) a = '';
if (!b) b = '';
return (a.split('').sort().join('') == b.split('').sort().join(''));
}
Another useful thing about this method is you can filter comparisons by passing a "replacer" function to the JSON.stringify functions (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify#Example_of_using_replacer_parameter). The following will only compare all objects keys that are named "derp":
function areEqual(obj1, obj2, filter) {
var a = JSON.stringify(obj1, filter), b = JSON.stringify(obj2, filter);
if (!a) a = '';
if (!b) b = '';
return (a.split('').sort().join('') == b.split('').sort().join(''));
}
var equal = areEqual(obj1, obj2, function(key, value) {
return (key === 'derp') ? value : undefined;
});
Needing a more generic object comparison function than had been posted, I cooked up the following. Critique appreciated...
Object.prototype.equals = function(iObj) {
if (this.constructor !== iObj.constructor)
return false;
var aMemberCount = 0;
for (var a in this) {
if (!this.hasOwnProperty(a))
continue;
if (typeof this[a] === 'object' && typeof iObj[a] === 'object' ? !this[a].equals(iObj[a]) : this[a] !== iObj[a])
return false;
++aMemberCount;
}
for (var a in iObj)
if (iObj.hasOwnProperty(a))
--aMemberCount;
return aMemberCount ? false : true;
}
If you are comparing JSON objects you can use https://github.com/mirek/node-rus-diff
npm install rus-diff
Usage:
a = {foo:{bar:1}}
b = {foo:{bar:1}}
c = {foo:{bar:2}}
var rusDiff = require('rus-diff').rusDiff
console.log(rusDiff(a, b)) // -> false, meaning a and b are equal
console.log(rusDiff(a, c)) // -> { '$set': { 'foo.bar': 2 } }
If two objects are different, a MongoDB compatible {$rename:{...}, $unset:{...}, $set:{...}} like object is returned.
I faced the same problem and deccided to write my own solution. But because I want to also compare Arrays with Objects and vice-versa, I crafted a generic solution. I decided to add the functions to the prototype, but one can easily rewrite them to standalone functions. Here is the code:
Array.prototype.equals = Object.prototype.equals = function(b) {
var ar = JSON.parse(JSON.stringify(b));
var err = false;
for(var key in this) {
if(this.hasOwnProperty(key)) {
var found = ar.find(this[key]);
if(found > -1) {
if(Object.prototype.toString.call(ar) === "[object Object]") {
delete ar[Object.keys(ar)[found]];
}
else {
ar.splice(found, 1);
}
}
else {
err = true;
break;
}
}
};
if(Object.keys(ar).length > 0 || err) {
return false;
}
return true;
}
Array.prototype.find = Object.prototype.find = function(v) {
var f = -1;
for(var i in this) {
if(this.hasOwnProperty(i)) {
if(Object.prototype.toString.call(this[i]) === "[object Array]" || Object.prototype.toString.call(this[i]) === "[object Object]") {
if(this[i].equals(v)) {
f = (typeof(i) == "number") ? i : Object.keys(this).indexOf(i);
}
}
else if(this[i] === v) {
f = (typeof(i) == "number") ? i : Object.keys(this).indexOf(i);
}
}
}
return f;
}
This Algorithm is split into two parts; The equals function itself and a function to find the numeric index of a property in an array / object. The find function is only needed because indexof only finds numbers and strings and no objects .
One can call it like this:
({a: 1, b: "h"}).equals({a: 1, b: "h"});
The function either returns true or false, in this case true.
The algorithm als allows comparison between very complex objects:
({a: 1, b: "hello", c: ["w", "o", "r", "l", "d", {answer1: "should be", answer2: true}]}).equals({b: "hello", a: 1, c: ["w", "d", "o", "r", {answer1: "should be", answer2: true}, "l"]})
The upper example will return true, even tho the properties have a different ordering. One small detail to look out for: This code also checks for the same type of two variables, so "3" is not the same as 3.
stringify both objects and compare
return (JSON.stringify(obj1) === JSON.stringify(obj2))
This will return true or false
I'd advise against hashing or serialization (as the JSON solution suggest). If you need to test if two objects are equal, then you need to define what equals means. It could be that all data members in both objects match, or it could be that must the memory locations match (meaning both variables reference the same object in memory), or may be that only one data member in each object must match.
Recently I developed an object whose constructor creates a new id (starting from 1 and incrementing by 1) each time an instance is created. This object has an isEqual function that compares that id value with the id value of another object and returns true if they match.
In that case I defined "equal" as meaning the the id values match. Given that each instance has a unique id this could be used to enforce the idea that matching objects also occupy the same memory location. Although that is not necessary.

javascript push then sort array [duplicate]

I have an array of strings I need to sort in JavaScript, but in a case-insensitive way. How to perform this?
In (almost :) a one-liner
["Foo", "bar"].sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
Which results in
[ 'bar', 'Foo' ]
While
["Foo", "bar"].sort();
results in
[ 'Foo', 'bar' ]
It is time to revisit this old question.
You should not use solutions relying on toLowerCase. They are inefficient and simply don't work in some languages (Turkish for instance). Prefer this:
['Foo', 'bar'].sort((a, b) => a.localeCompare(b, undefined, {sensitivity: 'base'}))
Check the documentation for browser compatibility and all there is to know about the sensitivity option.
myArray.sort(
function(a, b) {
if (a.toLowerCase() < b.toLowerCase()) return -1;
if (a.toLowerCase() > b.toLowerCase()) return 1;
return 0;
}
);
EDIT:
Please note that I originally wrote this to illustrate the technique rather than having performance in mind. Please also refer to answer #Ivan Krechetov for a more compact solution.
ES6 version:
["Foo", "bar"].sort(Intl.Collator().compare)
Source: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Collator/compare
arr.sort(function(a,b) {
a = a.toLowerCase();
b = b.toLowerCase();
if (a == b) return 0;
if (a > b) return 1;
return -1;
});
You can also use the new Intl.Collator().compare, per MDN it's more efficient when sorting arrays. The downside is that it's not supported by older browsers. MDN states that it's not supported at all in Safari. Need to verify it, since it states that Intl.Collator is supported.
When comparing large numbers of strings, such as in sorting large arrays, it is better to create an Intl.Collator object and use the function provided by its compare property
["Foo", "bar"].sort(Intl.Collator().compare); //["bar", "Foo"]
If you want to guarantee the same order regardless of the order of elements in the input array, here is a stable sorting:
myArray.sort(function(a, b) {
/* Storing case insensitive comparison */
var comparison = a.toLowerCase().localeCompare(b.toLowerCase());
/* If strings are equal in case insensitive comparison */
if (comparison === 0) {
/* Return case sensitive comparison instead */
return a.localeCompare(b);
}
/* Otherwise return result */
return comparison;
});
Normalize the case in the .sort() with .toLowerCase().
You can also use the Elvis operator:
arr = ['Bob', 'charley', 'fudge', 'Fudge', 'biscuit'];
arr.sort(function(s1, s2){
var l=s1.toLowerCase(), m=s2.toLowerCase();
return l===m?0:l>m?1:-1;
});
console.log(arr);
Gives:
biscuit,Bob,charley,fudge,Fudge
The localeCompare method is probably fine though...
Note: The Elvis operator is a short form 'ternary operator' for if then else, usually with assignment.
If you look at the ?: sideways, it looks like Elvis...
i.e. instead of:
if (y) {
x = 1;
} else {
x = 2;
}
you can use:
x = y?1:2;
i.e. when y is true, then return 1 (for assignment to x), otherwise return 2 (for assignment to x).
The other answers assume that the array contains strings. My method is better, because it will work even if the array contains null, undefined, or other non-strings.
var notdefined;
var myarray = ['a', 'c', null, notdefined, 'nulk', 'BYE', 'nulm'];
myarray.sort(ignoreCase);
alert(JSON.stringify(myarray)); // show the result
function ignoreCase(a,b) {
return (''+a).toUpperCase() < (''+b).toUpperCase() ? -1 : 1;
}
The null will be sorted between 'nulk' and 'nulm'. But the undefined will be always sorted last.
arr.sort(function(a,b) {
a = a.toLowerCase();
b = b.toLowerCase();
if( a == b) return 0;
if( a > b) return 1;
return -1;
});
In above function, if we just compare when lower case two value a and b, we will not have the pretty result.
Example, if array is [A, a, B, b, c, C, D, d, e, E] and we use the above function, we have exactly that array. It's not changed anything.
To have the result is [A, a, B, b, C, c, D, d, E, e], we should compare again when two lower case value is equal:
function caseInsensitiveComparator(valueA, valueB) {
var valueALowerCase = valueA.toLowerCase();
var valueBLowerCase = valueB.toLowerCase();
if (valueALowerCase < valueBLowerCase) {
return -1;
} else if (valueALowerCase > valueBLowerCase) {
return 1;
} else { //valueALowerCase === valueBLowerCase
if (valueA < valueB) {
return -1;
} else if (valueA > valueB) {
return 1;
} else {
return 0;
}
}
}
In support of the accepted answer I would like to add that the function below seems to change the values in the original array to be sorted so that not only will it sort lower case but upper case values will also be changed to lower case. This is a problem for me because even though I wish to see Mary next to mary, I do not wish that the case of the first value Mary be changed to lower case.
myArray.sort(
function(a, b) {
if (a.toLowerCase() < b.toLowerCase()) return -1;
if (a.toLowerCase() > b.toLowerCase()) return 1;
return 0;
}
);
In my experiments, the following function from the accepted answer sorts correctly but does not change the values.
["Foo", "bar"].sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
This may help if you have struggled to understand:
var array = ["sort", "Me", "alphabetically", "But", "Ignore", "case"];
console.log('Unordered array ---', array, '------------');
array.sort(function(a,b) {
a = a.toLowerCase();
b = b.toLowerCase();
console.log("Compare '" + a + "' and '" + b + "'");
if( a == b) {
console.log('Comparison result, 0 --- leave as is ');
return 0;
}
if( a > b) {
console.log('Comparison result, 1 --- move '+b+' to before '+a+' ');
return 1;
}
console.log('Comparison result, -1 --- move '+a+' to before '+b+' ');
return -1;
});
console.log('Ordered array ---', array, '------------');
// return logic
/***
If compareFunction(a, b) is less than 0, sort a to a lower index than b, i.e. a comes first.
If compareFunction(a, b) returns 0, leave a and b unchanged with respect to each other, but sorted with respect to all different elements. Note: the ECMAscript standard does not guarantee this behaviour, and thus not all browsers (e.g. Mozilla versions dating back to at least 2003) respect this.
If compareFunction(a, b) is greater than 0, sort b to a lower index than a.
***/
http://jsfiddle.net/ianjamieson/wmxn2ram/1/
I wrapped the top answer in a polyfill so I can call .sortIgnoreCase() on string arrays
// Array.sortIgnoreCase() polyfill
if (!Array.prototype.sortIgnoreCase) {
Array.prototype.sortIgnoreCase = function () {
return this.sort(function (a, b) {
return a.toLowerCase().localeCompare(b.toLowerCase());
});
};
}
Wrap your strings in / /i. This is an easy way to use regex to ignore casing

Custom sorting of response in javascript

I have the following response I get from my server:
[
{"key":{"name":"1","kind":"a"},
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"5","kind":"ap"}
]
The responses come in a different order every time as the server does not guarantee order. Thus I need to sort the response like this after I receive it:
First sort so the smallest KIND is first so 'a' should come before 'b'. I then need to make it so name of the username is the first ordered within the 'a'.
var username = '5';
var response = [
{"key":{"name":"1","kind":"a"},
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"5","kind":"ap"}
];
response = entities.sort(function (a, b) {
if (a.key.kind != b.key.kind){ return a.key.kind < b.key.kind}
else if(a.key.name == username){ return a.key.name < b.key.name }
else return a.key.name > b.key.name;
});
This is the code I use to sort, but it does not work. It sorts the KIND correctly, but then when it needs to sort by NAME (username should come before other names) it does not work.
The actual result I get is equal to this:
[
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"5","kind":"ap"},
{"key":{"name":"1","kind":"a"}
]
But the result I want is this:
[
{"key":{"name":"5","kind":"ap"},
{"key":{"name":"1","kind":"ap"},
{"key":{"name":"1","kind":"a"}
]
As you can see my username is equal to 5, so {"key":{"name":"5","kind":"ap"} should come before {"key":{"name":"1","kind":"ap"} .
return a.key.name < b.key.name is not saying that a < b.
They actually will be compared.
Try to replace it with return -1; to say to comparator:
a is lesser than b in that case.
When you use .sort() methods, you have to pass a compare function which will say who should comes first between two elements.
When you are comparing numbers, you can simply do :
function compare(a, b) {
return a - b;
}
this will sort an array of numbers ascending.
However, when you are comparing string, you have to define some ordering criterion, in order to tell which element should be comes first.
If compare(a, b) is less than 0, sort a to a lower index than b, so a comes first.
If compare(a, b) is greater than 0, sort b to a lower index than a, so b comes first.
If compare(a, b) is equal to 0, there is no change
So will get something like :
function compare(a, b) {
if (a is lower than b by your criteria) {
return -1;
}
if (a is greater than b by your criteria) {
return 1;
}
return 0;
}
In your case, you can write function generator that takes the property of the object to sort, and a custom sort function. This is useful when the function needs to be applied in more than one situation.
const username = '5';
const response = [
{"key":{"name":"1","kind":"a"}},
{"key":{"name":"1","kind":"ap"}},
{"key":{"name":"5","kind":"ap"}}
];
//Retrieve the value at 'props' into 'obj'
function retrieve(props, obj) {
return props.reduce((result, current) => {
result = result[current];
return result;
}, obj);
}
//Custom sort function for Kind properties
function sortKind(a, b) {
return a < b ? -1 : (a > b) ? 1 : 0;
}
//Custom sort function for name properties
function sortName(a, b) {
return a === username ? -1 : 1;
}
//Generic sort function
function sortByProp(...props) {
const callback = props.pop();
return function(a, b) {
const v1 = retrieve(props, a);
const v2 = retrieve(props, b);
return callback(v1, v2);
}
}
//Then you just have to call your sort function, and you can chain them
const res = response
.sort(sortByProp('key', 'kind', sortKind))
.sort(sortByProp('key', 'name', sortName));
console.log(res);
You can see here a Working plunker
You missed "return" in this line
else if(a.key.name == username){ a.key.name < b.key.name }

Why is reduce working this way?

I wrote a function to fulfil the following criteria:
"Write a function that, given a string, produces a map of the indexes of all characters. For example,
indexes("Mississippi") should return a map associating 'M' with the set {0}, 'i' with the set {1, 4, 7, 10}, and
so on."
But why is the first value of i 1 in my implementation, thus chopping off the M?
var s = 'Mississippi';
function indexes(s) {
var acc = {};
return s.split('').reduce(function(p, c, i) {
if (!acc[c]) {
acc[c] = [i];
} else {
acc[c].push(i);
}
return acc
});
}
console.log(indexes(s)); // Object {i: Array[4], s: Array[4], p: Array[2]}
Because you haven't specified an initial value for the accumulator, and so the first call to your callback has the first two entries in your array (e.g., p will be "M" and c will be "i"), but you're not using p, so you end up missing the "M".
Not providing an initial value is useful when you're doing a straight sum (for instance), but for this sort of thing, I find the easiest way to use reduce to provide that initial value, like so:
var s = 'Mississippi';
function indexes(s) {
// Note: No `acc` here
return s.split('').reduce(function(p, c, i) {
// Note: Using `p` here
if (!p[c]) {
p[c] = [i];
} else {
p[c].push(i);
}
return p;
}, {});
// ^^-- Initializing the accumulator here
}
snippet.log(JSON.stringify(indexes(s))); // {"M":[0],"i":[1,4,7,10],"s":[2,3,5,6],"p":[8,9]}
<!-- Script provides the `snippet` object, see http://meta.stackexchange.com/a/242144/134069 -->
<script src="http://tjcrowder.github.io/simple-snippets-console/snippet.js"></script>
If you preferred to keep using the external acc instead, I'd probably use forEach rather than reduce.

Using extra callback parameters to Array.prototype.some

Do you have any real-world example of the use of the second and third parameters for the callback to Array.prototype.some or Array.prototype.any?
According to MDN:
callback is invoked with three arguments: the value of the element, the index of the
element, and the Array object being traversed.
I've personally never used them.
I have been working for some time on the Javascript functional programming library, Ramda, and early on we made the controversial decision not to use the index and array parameters for other similar functions that we created. There are good reasons for this, which I don't need to get into here, except to say that for some functions, such as map and filter, we find such extra parameters do have some occasional utility. So we offer a second function which supplies them to your callback. (For example, map.idx(yourFunc, list).)
But I've never even considered doing so for some or every. I never imagined a practical use of these. But there is now a suggestion that we include these functions in our list of index-supporting ones.
So my question again is whether you have ever found an actual, live, real-world callback function to some or every which actually needs these parameters? If so, could you describe it?
Answers of "No, I never do," would be helpful data too, thanks.
Quick search in our code:
function isAscending(array) {
return array.every(function (e, idx, arr) {
return (idx === 0) ? true : arr[idx-1] <= e;
});
}
I could imagine something like the following code to check whether an array is duplicate-free:
….every(function(v, i, arr) {
return arr.indexOf(v, i+1) == -1;
})
Where … is a complex expression so that you'd really have to use the arr parameter - which is no more an issue if you'd properly factor out the functionality in an own function that takes the array as an argument.
The second parameter can be useful sometimes, but I support your position that it is rather seldom used.
Yes, they are helpful
These extra parameters actually do come in handy, but not that often.
In the recent past, I had written a function to find all the permutations of a list of elements:
permute :: [a] -> [[a]]
For example permute [1,2,3] would be:
[ [1,2,3]
, [1,3,2]
, [2,1,3]
, [2,3,1]
, [3,1,2]
, [3,2,1]
]
The implementation of this function is quite simple:
If the input is [] then return [[]]. This is the edge case.
If the input is say [1,2,3]:
Add 1 to every permutation of [2,3].
Add 2 to every permutation of [1,3].
Add 3 to every permutation of [1,2].
Of course, the function is recursive. In JavaScript, I implemented it as follows:
var permute = (function () {
return permute;
function permute(list) {
if (list.length === 0) return [[]]; // edge case
else return list.reduce(permutate, []); // list of permutations
// is initially empty
}
function permutate(permutations, item, index, list) {
var before = list.slice(0, index); // all the items before "item"
var after = list.slice(index + 1); // all the items after "item"
var rest = before.concat(after); // all the items beside "item"
var perms = permute(rest); // permutations of rest
// add item to the beginning of each permutation
// the second argument of "map" is the "context"
// (i.e. the "this" parameter of the callback)
var newPerms = perms.map(concat, [item]);
return permutations.concat(newPerms); // update the list of permutations
}
function concat(list) {
return this.concat(list);
}
}());
As you can see, I have used both the index and the list parameters of the permutate function. So, yes there are cases where these extra parameters are indeed helpful.
However, they are also problematic
However these superfluous arguments can sometimes be problematic and difficult to debug. The most common example of this problematic behavior is when map and parseInt are used together: javascript - Array#map and parseInt
alert(["1","2","3"].map(parseInt));
As you can see it produces the unexpected output [1,NaN,NaN]. The reason this happens it because the map function calls parseInt with 3 arguments (item, index and array):
parseInt("1", 0, ["1","2","3"]) // 1
parseInt("2", 1, ["1","2","3"]) // NaN
parseInt("3", 2, ["1","2","3"]) // NaN
However, the parseInt function takes 2 arguments (string and radix):
First case, radix is 0 which is false. Hence default radix 10 is taken, resulting in 1.
Second case, radix is 1. There is no base 1 numeral system. Hence we get NaN.
Third case, radix is 2 which is valid. However there's no 3 in base 2. Hence we get NaN.
As you see, superfluous arguments can cause a lot of problems which are difficult to debug.
But, there is an alternative
So these extra arguments are helpful but they can cause a lot of problems. Fortunately, there is an easy solution to this problem.
In Haskell if you want to map over a list of values and the indices of each value then you use do it as follows:
map f (zip list [0..])
list :: [Foo]
[0..] :: [Int]
zip list [0..] :: [(Foo, Int)]
f :: (Foo, Int) -> Bar
map f (zip list [0..]) :: [Bar]
You could do the same thing in JavaScript as follows:
function Maybe() {}
var Nothing = new Maybe;
Just.prototype = new Maybe;
function Just(a) {
this.fromJust = a;
}
function iterator(f, xs) {
var index = 0, length = xs.length;
return function () {
if (index < length) {
var x = xs[index];
var a = f(x, index++, xs);
return new Just(a);
} else return Nothing;
};
}
We use a different map function:
function map(f, a) {
var b = [];
if (typeof a === "function") { // iterator
for (var x = a(); x !== Nothing; x = a()) {
var y = f(x.fromJust);
b.push(y);
}
} else { // array
for (var i = 0, l = a.length; i < l; i++) {
var y = f(a[i]);
b.push(y);
}
}
return x;
}
Finally:
function decorateIndices(array) {
return iterator(function (item, index, array) {
return [item, index];
}, array);
}
var xs = [1,2,3];
var ys = map(function (a) {
var item = a[0];
var index = a[1];
return item + index;
}, decorateIndices(xs));
alert(ys); // 1,3,5
Similarly you can create decorateArray and decorateIndicesArray functions:
function decorateArray(array) {
return iterator(function (item, index, array) {
return [item, array];
}, array);
}
function decorateIndicesArray(array) {
return iterator(function (item, index, array) {
return [item, index, array];
}, array);
}
Currently in Ramda you have two separate functions map and map.idx. The above solution allows you to replace map.idx with idx such that:
var idx = decorateIndices;
var arr = decorateArray;
var idxArr = decorateIndicesArray;
map.idx(f, list) === map(f, idx(list))
This will allow you to get rid of a whole bunch of .idx functions, and variants.
To curry or not to curry
There is still one small problem to solve. This looks ugly:
var ys = map(function (a) {
var item = a[0];
var index = a[1];
return item + index;
}, decorateIndices(xs));
It would be nicer to be able to write it like this instead:
var ys = map(function (item, index) {
return item + index;
}, decorateIndices(xs));
However we removed superfluous arguments because they caused problems. Why should we add them back in? Two reasons:
It looks cleaner.
Sometimes you have a function written by somebody else which expects these extra arguments.
In Haskell you can use the uncurry function to solve this problem:
map (uncurry f) (zip list [0..])
list :: [Foo]
[0..] :: [Int]
zip list [0..] :: [(Foo, Int)]
f :: Foo -> Int -> Bar
uncurry :: (a -> b -> c) -> (a, b) -> c
uncurry f :: (Foo, Int) -> Bar
map (uncurry f) (zip list [0..]) :: [Bar]
In JavaScript the uncurry function is simply apply. It is implemented as follows:
function uncurry(f, context) {
if (arguments.length < 2) context = null;
return function (args) {
return f.apply(context, args);
};
}
Using uncurry we can write the above example as:
var ys = map(uncurry(function (item, index) {
return item + index;
}), decorateIndices(xs));
This code is awesome because:
Each function does only one job. Functions can be combined to do more complex work.
Everything is explicit, which is a good thing according to the Zen of Python.
There's no redundancy. There's only one map function, etc.
So I really hope this answer helps.

Categories