Checking if a Set contains a list in Javascript - javascript

I have a set that I've added a list to.
var a = new Set();
a.add([1]);
Now I check if [1] exists in a:
a.has([1]);
> false
Based on the spec, this might be happening because if type is same for two objects being compared:
Return true if x and y refer to the same object. Otherwise, return
false.
And this also happens:
[1] == [1];
> false
So it might be that the Set .has() is using == for comparing equality (not sure though). Is there a .contains() method for Set() in JS like Java has?

You can't compare references like arrays and objects for equality (you can compare values, though).
The way you're doing it, you're checking against a different reference, even though the values appear to be the same.
Do something like this:
var a = new Set();
var b = [1];
a.add(b);
a.has(b); // => true
Take a look at MDN's Equality comparisons and sameness.
So it might be that the Set .has() is using == for comparing equality (not sure though)
Not necessarily. [1] === [1] (using the strict equality operator) also returns false.
Is there a .contains() method for Set() in JS like Java has?
Not in the way you're imagining it. You can do some sort of deep comparison, as mentioned in the first link in this answer.

although both [1]'s have the same value, they are two different arrays, pointing to two different locations in memory, hence they are not equal.
you can use a basic loop to check
for (let item of mySet.keys()) {
item.toString() == [1].toString();
//true on match.
}

If you compare two arrays with the "==" operator they will never be equals. They are different instance.
If you want using an array object :
Define how two arrays are compared.
Make a subclass of Set and override the "has" method.
.
function eqArray (a1, a2) {
if (!(a1 instanceof Array && a2 instanceof Array))
return false;
if ( a1.length != a2.length)
return false;
for (var i = 0, n=a1.length; i < n; i++) {
if (a1[i] instanceof Array && a2[i] instanceof Array) {
if (!eqArray(a1[i], a2[i]))
return false;
}
else if (a1[i] != a2[i])
return false;
}
return true;
}
function MySet(elems) {
var set = new Set (elems);
set.__proto__ = MySet.prototype;
return set;
}
MySet.prototype = new Set;
MySet.prototype.has = function (elem) {
if (elem instanceof Array){
for (var v of this) {
if (v instanceof Array && eqArray(elem,v))
return true;
}
return false;
}else {
return Set.prototype.has.call (this, elem);
}
}
var a = new MySet();
a.add([1]);
a.has([1]); //true
eqArray([1],[1]); //true

Ready to use it with any object you need.
You just need to define how to compare them on a function
class SetDeep extends Set {
constructor (elems){
super(elems)
}
has(elem, compare) {
if (compare instanceof Function) {
return Array.from(this).some( setElem => compare(elem, setElem));
} else {
return super.has(elem);
}
}
}
Using fbohorquez eqArray:
function eqArray (a1, a2) {
if (!(a1 instanceof Array && a2 instanceof Array))
return false;
if ( a1.length != a2.length)
return false;
for (var i = 0, n=a1.length; i < n; i++) {
if (a1[i] instanceof Array && a2[i] instanceof Array) {
if (!eqArray(a1[i], a2[i]))
return false;
}
else if (a1[i] != a2[i])
return false;
}
return true;
}
var a = new SetDeep();
var b = [1];
a.add(b);
a.has(b); // true
var c = new SetDeep();
c.add(b);
c.has([1]); // false
c.has([1], eqA) // true
With an object:
function equalId (a, b) {
return a.id === b.id;
}
var objSet = new SetDeep([ { id: 'foo'}, {id: 'bar'} ]);
var foo = {id: 'foo' };
objSet.has(foo); // false;
objSet.has(foo, equalId); // true

Related

How would you create Set.prototype.add() from scratch?

On ECMAScript, it explains how you would create the add prototype.
The following steps are taken:
Let S be the this value.
If Type(S) is not Object, throw a TypeError exception.
If S does not have a [[SetData]] internal slot, throw a TypeError exception.
Let entries be the List that is the value of S’s [[SetData]] internal slot.
Repeat for each e that is an element of entries,
If e is not empty and SameValueZero(e, value) is true, then
Return S.
If value is −0, let value be +0.
Append value as the last element of entries.
Return S."
I am fairly new to JS and have no idea what this means. If I were to create a class such that
class mySet {
constructor(){
this.set = {}
}
//adds a value to set
add(){
}
How would I proceed?
Here is a sample implementation. It's basically just following the steps outlined in the specs. I'm however not entirely sure what is empty means so (i'm treating it as undefined.)
To check for a negative zero you need to cheat and test for -Infinity or sth alike.
function isNegativeZero (x) {
return 1 / x === -Infinity
}
function SameValueZero (a, b) {
if (typeof a !== typeof b) return false
if (typeof a === 'number') {
if (Number.isNaN(a) && Number.isNaN(b)) return true;
if (isNegativeZero(a) && b === 0) return true;
if (isNegativeZero(b) && a === 0) return true;
if (+a === +b) return true;
return false
}
return a === b;
}
class MySet {
constructor(){
this[MySet.Symbols.SetData] = [];
}
//adds a value to set
add(value){
const S = this;
if (typeof S !== 'object') throw new TypeError(`Type mismatch. Expected ${this} to be of type object`);
const entries = this[MySet.Symbols.SetData];
for (const e of entries) {
if (typeof e !== 'undefined' && SameValueZero(e, value)) return S;
}
if (isNegativeZero (value)) value = 0;
entries.push(value);
return S;
}
toString() {
return `MySet(${this[MySet.Symbols.SetData].length}) {${this[MySet.Symbols.SetData]}}`
}
}
MySet.Symbols = {
SetData: Symbol('SetData')
}
const mySet = new MySet();
mySet.add(1);
console.log(mySet + '');
mySet.add(1);
console.log(mySet + '');
mySet.add(NaN);
console.log(mySet + '');
mySet.add(NaN);
console.log(mySet + '');
Here's an idea. It doesn't work with undefined, because that value is used to indicate an empty space. It could work with undefined if a holey array was used instead, but that would have performance costs.
In this example the value is not appended as the last element of the array, instead it is appended in the place of the first empty space, to prevent needlessly expanding the array.
Of course, JavaScript code can't access internal slots, so a property is used here.
class MySet {
setData = [];
add(value) {
if (!("setData" in this))
throw new TypeError("add method called on incompatible Object");
let firstEmptyIndex = -1;
for (let i = 0; i < this.setData.length; i++) {
const item = this.setData[i];
if (firstEmptyIndex < 0 && item === undefined) {
firstEmptyIndex = i;
continue;
}
if ((typeof item === "number" && Number.isNaN(item) && typeof value === "number" && Number.isNaN(value)) || item === value) // Item already in the set
return this;
}
if (firstEmptyIndex < 0)
this.setData.push(value);
else
this.setData[firstEmptyIndex] = value;
return this;
}
}

Comparing equality of elements in two arrays

I have an assignment where I am supposed to check two arrays (unsorted) with integers, to see if
They have the same length
The first element contains integers and the second has the same values squared, in any order
For example:
test([5,4,1], [1,16,25]) // would return true ..
What I've done so far is first sort the two input arrays, and then compare the length. Once we confirm the length is the same we iterate through each value to make sure they're equal. Keep in mind I haven't gotten to comparing the values to their squared counterpart yet, because my loop is not giving me expected results. Here is the code:
function test(arr1, arr2){
// sort arrays
const arr1Sort = arr1.sort(),
arr2Sort = arr2.sort();
// compare length and then compare values
if(arr1Sort.length === arr2Sort.length) {
for(let i = 0; i < arr1Sort.length; i++) {
if(arr1Sort[i] === arr2Sort[i]) {
return true;
} else {
return false;
}
}
}
}
console.log(test([1,2,3], [1,5,4])); returns true but the array values are different?!
Inside the for, no matter whether the if or else is fulfilled, the function will immediately return true or false on the first iteration - it'll never get past index 0. To start with, return true only after the loop has concluded, and return false if arr1Sort[i] ** 2 !== arr2Sort[i] (to check if the first squared equals the second).
Also, when sorting, make sure to use a callback function to compare each item's difference, because otherwise, .sort will sort lexiographically (eg, [1, 11, 2]):
function comp(arr1, arr2){
// sort arrays
const sortCb = (a, b) => a - b;
const arr1Sort = arr1.sort(sortCb),
arr2Sort = arr2.sort(sortCb);
// compare length and then compare values
if(arr1Sort.length !== arr2Sort.length) {
return false;
}
for(let i = 0; i < arr1Sort.length; i++) {
if(arr1Sort[i] ** 2 !== arr2Sort[i]) {
return false;
}
}
return true;
}
console.log(comp([1,2,3], [1,5,4]));
console.log(comp([5,4,1], [1,16,25]));
You can decrease the computational complexity to O(N) instead of O(N log N) by turning arr2 into an object indexed by the squared number beforehand:
function comp(arr1, arr2){
if (arr1.length !== arr2.length) {
return false;
}
const arr2Obj = arr2.reduce((a, num) => {
a[num] = (a[num] || 0) + 1;
return a;
}, {});
for (let i = 0; i < arr1.length; i++) {
const sq = arr1[i] ** 2;
if (!arr2Obj[sq]) {
return false;
}
arr2Obj[sq]--;
}
return true;
}
console.log(comp([1,2,3], [1,5,4]));
console.log(comp([5,4,1], [1,16,25]));
(if duplicates weren't permitted, this would be a lot easier with a Set instead, but they are, unfortunately)
This should work, no mater the data to compare:
function similar(needle, haystack, exact){
if(needle === haystack){
return true;
}
if(needle instanceof Date && haystack instanceof Date){
return needle.getTime() === haystack.getTime();
}
if(!needle || !haystack || (typeof needle !== 'object' && typeof haystack !== 'object')){
return needle === haystack;
}
if(needle === null || needle === undefined || haystack === null || haystack === undefined || needle.prototype !== haystack.prototype){
return false;
}
var keys = Object.keys(needle);
if(exact && keys.length !== Object.keys(haystack).length){
return false;
}
return keys.every(function(k){
return similar(needle[k], haystack[k]);
});
}
console.log(similar(['a', {cool:'stuff', yes:1}, 7], ['a', {cool:'stuff', yes:1}, 7], true));
// not exact
console.log(similar(['a', {cool:'stuff', yes:1}, 7], ['a', {cool:'stuff', stuff:'more', yes:1}, 7, 'more stuff only at the end for numeric array']));

JavaScript: Deep comparison recursively: Objects and properties

Today I finished reading Ch. 4 in Eloquent JS and I'm struggling to understand how to perform a deep comparison between objects and their properties, particularly through the use of a recursive call. I know my solution below is quite naive and a bit bulky, but I'm trying to wrap my head around all these new things I'm still learning! Just less than a month in on programming :) I would appreciate any tips and help you might have in improving the code and also if you could help me better understand the recursion that needs to occur. Thank you in advance!
Question (Eloquent JS 2nd Ed, Chapter 4, Exercise 4):
Write a function, deepEqual, that takes two values and returns true
only if they are the same value or are objects with the same
properties whose values are also equal when compared with a recursive
call to deepEqual.
Test Cases:
var obj = {here: {is: "an"}, object: 2};
var obj1 = {here: {is: "an"}, object: 2};
console.log(deepEqual(obj,obj1));
My Code:
function objectTester(x) {
if (typeof x === 'object' && x !== null)
return true;
}
function deepEqual(valOne, valTwo) {
if (valOne === valTwo) return true;
var comp1 = objectTester(valOne);
var comp2 = objectTester(valTwo);
if (comp1 === comp2) {
var count1;
var count2;
for (var prop in valOne) {
count1++
return count1;
}
for (var prop in valTwo) {
count2++
return count2;
}
if (count1 === count2) {
// This is where I'm getting stuck, not sure how I can recurisvely compare
// two arguments that are to be compared.
}
}
Probably easiest to just post a function that does the job with plenty of comments. The recursive part is near the bottom, in the function passed to every:
// Helper to return a value's internal object [[Class]]
// That this returns [object Type] even for primitives
function getClass(obj) {
return Object.prototype.toString.call(obj);
}
/*
** #param a, b - values (Object, RegExp, Date, etc.)
** #returns {boolean} - true if a and b are the object or same primitive value or
** have the same properties with the same values
*/
function objectTester(a, b) {
// If a and b reference the same value, return true
if (a === b) return true;
// If a and b aren't the same type, return false
if (typeof a != typeof b) return false;
// Already know types are the same, so if type is number
// and both NaN, return true
if (typeof a == 'number' && isNaN(a) && isNaN(b)) return true;
// Get internal [[Class]]
var aClass = getClass(a);
var bClass = getClass(b)
// Return false if not same class
if (aClass != bClass) return false;
// If they're Boolean, String or Number objects, check values
if (aClass == '[object Boolean]' || aClass == '[object String]' || aClass == '[object Number]') {
return a.valueOf() == b.valueOf();
}
// If they're RegExps, Dates or Error objects, check stringified values
if (aClass == '[object RegExp]' || aClass == '[object Date]' || aClass == '[object Error]') {
return a.toString() == b.toString();
}
// Otherwise they're Objects, Functions or Arrays or some kind of host object
if (typeof a == 'object' || typeof a == 'function') {
// For functions, check stringigied values are the same
// Almost certainly false if a and b aren't trivial
// and are different functions
if (aClass == '[object Function]' && a.toString() != b.toString()) return false;
var aKeys = Object.keys(a);
var bKeys = Object.keys(b);
// If they don't have the same number of keys, return false
if (aKeys.length != bKeys.length) return false;
// Check they have the same keys
if (!aKeys.every(function(key){return b.hasOwnProperty(key)})) return false;
// Check key values - uses ES5 Object.keys
return aKeys.every(function(key){
return objectTester(a[key], b[key])
});
}
return false;
}
The tests on Date, RegExp, Error, etc. should probably return false if the values/strings aren't the same then fall through to the property checks, but do that only if you think someone might attach properties to Number objects (it's extremely rare to use Number objects, much less add properties to them, but I guess it might happen).
Here it is:
/*
** #param a, b - values (Object, RegExp, Date, etc.)
** #returns {boolean} - true if a and b are the object or same primitive value or
** have the same properties with the same values
*/
function objectTester(a, b) {
// If a and b reference the same value, return true
if (a === b) return true;
// If a and b aren't the same type, return false
if (typeof a != typeof b) return false;
// Already know types are the same, so if type is number
// and both NaN, return true
if (typeof a == 'number' && isNaN(a) && isNaN(b)) return true;
// Get internal [[Class]]
var aClass = getClass(a);
var bClass = getClass(b)
// Return false if not same class
if (aClass != bClass) return false;
// If they're Boolean, String or Number objects, check values
if (aClass == '[object Boolean]' || aClass == '[object String]' || aClass == '[object Number]') {
if (a.valueOf() != b.valueOf()) return false;
}
// If they're RegExps, Dates or Error objects, check stringified values
if (aClass == '[object RegExp]' || aClass == '[object Date]' || aClass == '[object Error]') {
if (a.toString() != b.toString()) return false;
}
// For functions, check stringigied values are the same
// Almost impossible to be equal if a and b aren't trivial
// and are different functions
if (aClass == '[object Function]' && a.toString() != b.toString()) return false;
// For all objects, (including Objects, Functions, Arrays and host objects),
// check the properties
var aKeys = Object.keys(a);
var bKeys = Object.keys(b);
// If they don't have the same number of keys, return false
if (aKeys.length != bKeys.length) return false;
// Check they have the same keys
if (!aKeys.every(function(key){return b.hasOwnProperty(key)})) return false;
// Check key values - uses ES5 Object.keys
return aKeys.every(function(key){
return objectTester(a[key], b[key])
});
return false;
}
You can use below code for deep comparison -
const isEqual = (a, b) => {
if (a === b) return true;
if (a instanceof Date && b instanceof Date) return a.getTime() === b.getTime();
if (!a || !b || (typeof a !== 'object' && typeof b !== 'object')) return a === b;
if (a === null || a === undefined || b === null || b === undefined) return false;
if (a.prototype !== b.prototype) return false;
let keys = Object.keys(a);
if (keys.length !== Object.keys(b).length) return false;
return keys.every(k => isEqual(a[k], b[k]));
};
example -
isEqual({ prop1: [2, { e: 3 }], prop2: [4], prop3: 'foo' }, { prop1: [2, { e: 3 }], prop2: [4], prop3: 'foo' }); // true
Check the code below. That should work for you.
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]); });
}
I think all given answers are amazing.
But for not so experts eyes I will like to make reading easier clarifying how the solutions that use Array.prototype.every works for nested arrays.
It turns out that the keys method from the global Object has a not so commonly used capability where receive an array as parameter and
returns an array whose elements are strings corresponding to the
enumerable properties found directly upon object.
console.log(typeof [] === 'object') // true
Object.keys([true, { a:'a', b: 'b' }, 2, null]) // ['0', '1', '2', '3']
So this is how Object.keys().every() works fine for function arguments, indexing {}s and []s, either with property keys or array indexes.
Hope help someone.

Sort an array of objects in Javascript by key value

First time poster, long time reader. I’m having a problem sorting an array of objects, this is homework so I’m not asking for someone to write the code for me just point me in the right direction or show me my over sight. The object is to write a function to sort an array of objects when passing in an array and a key ie:
([{a:2},{b:2},{a:1},{a:3},{b:3},{b:1}], “a”)
Should return
[{a:1},{a:2},{a:3},{b:1},{b:2},{b:3}];
I can’t use anything like underscore.js or node.js
//example array to use
var testarr = [{a:2},{b:2},{a:1},{a:3},{b:3},{b:1}];
console.log("array sort test: should look like [{a:1},{a:2},{a:3},{b:1},{b:2},{b:3}]");
//first attempt
var sortArrayByKey = function (arr1, key) {
(arr1.sort(function(a,b){
return a[key] - b[key];}));
return arr1;
};
//still only returns testarr
console.log(sortArrayByKey(testarr, "a") );
//second attempt
var sortArrayByKey1 = function (array, key) {
var compareByKey = function (a, b) {
var x = a[key]; var y = b[key];
return x - y;
}
array.sort(compareByKey);
return array;
};
//still only returns testarr
console.log(sortArrayByKey1(testarr, “a”));
![pic of requirements in-case i'm describing it wrong photo
Here's my solution. I made it so you can also add more keys and sort them too.
Fiddle - http://jsfiddle.net/Q7Q9C/3/
function specialSort(arrayToSort, keyOrder) {
arrayToSort = arrayToSort.sort(function (a, b) {
for (var key in keyOrder) {
if (!keyOrder.hasOwnProperty(key)) {
continue;
}
var aKey = keyOrder[key];
if (typeof a[aKey] === "undefined" && typeof b[aKey] === "undefined") {
continue;
}
if (typeof a[aKey] !== "undefined" && typeof b[aKey] === "undefined") {
return -1;
}
if (typeof a[aKey] === "undefined" && typeof b[aKey] !== "undefined") {
return 1;
}
if (a[aKey] > b[aKey]) {
return 1;
}
else if (a[aKey] < b[aKey]) {
return -1;
}
}
return 0;
});
return arrayToSort;
}
var arrayToSort = [
{a:2},
{b:2},
{a:1},
{a:3},
{b:3},
{c:3},
{c:2},
{b:1}
];
var keyOrder = ["a", "b", "c"];
var sortedArray = specialSort(arrayToSort, keyOrder);
console.log(JSON.stringify(sortedArray));
Hmm.. this is a weird one. First you need to check if one of the keys is the priority key and sort based on that. Then if both keys are equal sort by the values. The problem is that there is no straightforward way to get the key but you can use the for .. in loop.
I'm going to assume that each object contains only one property otherwise the code will not make sense since property is unordered in objects:
function sortPreferredKey(arr,key) {
arr.sort(function(a,b){
// get the keys of each object
for (var a_key in a) {break}
for (var b_key in b) {break}
if (a_key != b_key) {
if (a_key == key) return 1;
else if (b_key == key) return -1;
else return 0;
}
return a[a_key] - b[b_key];
});
}
I may have gotten the order of sort wrong but you get the idea. It's really weird that you'd even need to do something like this.
This is the best I can come up with. It will sort all the elements with the given key to the front; the elements without the key will be in the back, but they'll be in an unpredictable order.
function sortArrayByKey(arr, key) {
function compareKey(a, b) {
if (a.hasOwnProperty(key)) {
if (b.hasOwnProperty(key)) {
return a[key] - b[key];
} else {
return -1;
}
} else if (b.hasOwnProperty(key)) {
return 1;
} else {
return 0;
}
}
arr.sort(compareKey);
return arr;
}
The documentation for the sort method is here. The compare function:
should be a function that accepts two arguments x and y and returns a
negative value if x < y, zero if x = y, or a positive value if x > y.
The function is passed the values in the array, so it's like calling the function with:
compareFunction({a:2},{b:2});
What you seem to want to do is sort on the property name first, then on the value. The problem with that is that you can't guarantee what order the property names are returned in. In this case, if you have exactly one own property for each object, you can do:
// Return first own property returned by in
// ORDER IS NOT GUARANTEED
function getPropName(o) {
for (var p in o) {
if (o.hasOwnProperty(p)) {
return p;
}
}
}
function specialSort(array, key) {
array.sort(function (a, b) {
var aProp = getPropName(a);
var bProp = getPropName(b);
// If properties are the same, compare value
if (aProp == bProp) {
return a[aProp] - b[bProp];
}
// Otherwise, compare keys
return aProp == key? -1 : bProp == key? 1 : aProp.charCodeAt(0) - bProp.charCodeAt(0);
});
return array;
}
The above will also sort any other keys (c, d, e, etc.) after the preferred key so:
var a = [{c:3},{a:2},{b:2},{c:2},{a:1},{a:3},{b:3},{b:1},{c:1}]
specialSort(a, 'b'); // [{b:1}, {b:2}, {b:3}, {a:1}, {a:2}, {a:3}, {c:1}, {c:2}, {c:3}]
Here's a solution that makes a guess what to do if neither object being compared in the array has the passed in comparison key:
var data = [{a:2},{b:2},{a:1},{a:3},{b:3},{b:1}];
function sortByProperty(array, propName) {
function findFirstProperty(obj) {
for (x in obj) {
if (obj.hasOwnProperty(x)) {
return x;
}
}
}
return array.sort(function(first, second) {
var firstHasProp = propName in first;
var secondHasProp = propName in second;
if (firstHasProp) {
if (secondHasProp) {
// both have the property
return first[propName] - second[propName];
} else {
// only first has the property
return -1;
}
} else if (secondHasProp){
// only second has the property
return 1;
} else {
// Neither sort candidate has the passed in property name
// It is not clear what you want to do here as no other property
// name has been specified
return first[findFirstProperty(first)] - second[findFirstProperty(second)]
}
});
}
Working demo: http://jsfiddle.net/jfriend00/PFurT/
Logically, here's what it does:
If both comparison candidates have the desired property, then simply sort by the value of that property.
If only one comparison candidate has the desired property, then make the one with the desired property be first in the sort order
If neither comparison candidate has the desired property, find the first other property on the object and sort by that. This is a guess because you don't really explain what you want to happen in this case, but it works for the data example you provided.
Here's a version that works like the above one, but is has been extended to sort properties that are not the passed in property in alpha order and to deal with empty objects (with no properties) so they go at the end of the sort:
var data = [{c:4},{a:2},{b:2},{a:1},{a:3},{b:3},{b:1},{},{c:3}];
function sortByProperty(array, propName) {
function findFirstProperty(obj) {
for (x in obj) {
if (obj.hasOwnProperty(x)) {
return x;
}
}
}
return array.sort(function(first, second) {
var firstHasProp = propName in first;
var secondHasProp = propName in second;
if (firstHasProp) {
if (secondHasProp) {
// both have the property
return first[propName] - second[propName];
} else {
// only first has the property
return -1;
}
} else if (secondHasProp){
// only second has the property
return 1;
} else {
// Neither sort candidate has the passed in property name
// It is not clear what you want to do here as no other property
// name has been specified
var firstProp = findFirstProperty(first);
var secondProp = findFirstProperty(second);
if (firstProp === undefined && secondProp === undefined) {
return 0;
} else if (firstProp === undefined) {
return 1;
} else if (secondProp === undefined) {
return -1;
}
else if (firstProp === secondProp) {
return first[firstProp] - second[secondProp];
} else {
return firstProp.localeCompare(secondProp);
}
}
});
}
Working demo: http://jsfiddle.net/jfriend00/6QsVv/

Why Array.indexOf doesn't find identical looking objects

I have array with objects.
Something Like this:
var arr = new Array(
{x:1, y:2},
{x:3, y:4}
);
When I try:
arr.indexOf({x:1, y:2});
It returns -1.
If I have strings or numbers or other type of elements but object, then indexOf() works fine.
Does anyone know why and what should I do to search object elements in array?
Of course, I mean the ways except making string hash keys for objects and give it to array...
indexOf compares searchElement to elements of the Array using strict equality (the same method used by the ===, or triple-equals, operator).
You cannot use === to check the equability of an object.
As #RobG pointed out
Note that by definition, two objects are never equal, even if they have exactly the same property names and values. objectA === objectB if and only if objectA and objectB reference the same object.
You can simply write a custom indexOf function to check the object.
function myIndexOf(o) {
for (var i = 0; i < arr.length; i++) {
if (arr[i].x == o.x && arr[i].y == o.y) {
return i;
}
}
return -1;
}
DEMO: http://jsfiddle.net/zQtML/
As nobody has mentioned built-in function Array.prototype.findIndex(), I'd like to mention that it does exactly what author needs.
The findIndex() method returns the index of the first element in the
array that satisfies the provided testing function. Otherwise -1 is
returned.
var array1 = [5, 12, 8, 130, 44];
function findFirstLargeNumber(element) {
return element > 13;
}
console.log(array1.findIndex(findFirstLargeNumber));
// expected output: 3
In your case it would be:
arr.findIndex(function(element) {
return element.x == 1 && element.y == 2;
});
Or using ES6
arr.findIndex( element => element.x == 1 && element.y == 2 );
More information with the example above: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/findIndex
As noted, two objects are never equal, but references can be equal if they are to the same object, so to make the code do what you want:
var a = {x:1, y:2};
var b = {x:3, y:4};
var arr = [a, b];
alert(arr.indexOf(a)); // 0
Edit
Here's a more general specialIndexOf function. Note that it expects the values of the objects to be primitives, otherwise it needs to be more rigorous.
function specialIndexOf(arr, value) {
var a;
for (var i=0, iLen=arr.length; i<iLen; i++) {
a = arr[i];
if (a === value) return i;
if (typeof a == 'object') {
if (compareObj(arr[i], value)) {
return i;
}
} else {
// deal with other types
}
}
return -1;
// Extremely simple function, expects the values of all
// enumerable properties of both objects to be primitives.
function compareObj(o1, o2, cease) {
var p;
if (typeof o1 == 'object' && typeof o2 == 'object') {
for (p in o1) {
if (o1[p] != o2[p]) return false;
}
if (cease !== true) {
compareObj(o2, o1, true);
}
return true;
}
}
}
var a = new String('fred');
var b = new String('fred');
var arr = [0,1,a];
alert(specialIndexOf(arr, b)); // 2
This works without custom code
var arr, a, found;
arr = [{x: 1, y: 2}];
a = {x: 1, y: 2};
found = JSON.stringify(arr).indexOf(JSON.stringify(a)) > - 1;
// found === true
Note: this does not give the actual index, it only tells if your object exists in the current data structure
Those objects aren't equal.
You must implement your own function.
You may do that for example :
var index = -1;
arr.forEach(function(v, i) {
if (this.x==v.x && this.y==v.y) index=i;
}, searched);
where searched is one of your object (or not).
(I would implement it with a simple loop but it's prettier with foreach)
Because two separate objects are not === to each other, and indexOf uses ===. (They're also not == to each other.)
Example:
var a = {x:1, y:2};
var b = {x:1, y:2};
console.log(a === b);
=== and == test for whether their operands refer to the same object, not if they refer to equivalent objects (objects with the same prototype and properties).
Here's another solution, where you pass a compare function as a parameter :
function indexOf(array, val, from, compare) {
if (!compare) {
if (from instanceof Function) {
compare = from;
from = 0;
}
else return array.__origIndexOf(val, from);
}
if (!from) from = 0;
for (var i=from ; i < array.length ; i++) {
if (compare(array[i], val))
return i;
}
return -1;
}
// Save original indexOf to keep the original behaviour
Array.prototype.__origIndexOf = Array.prototype.indexOf;
// Redefine the Array.indexOf to support a compare function.
Array.prototype.indexOf = function(val, from, compare) {
return indexOf(this, val, from, compare);
}
You can then use it these way:
indexOf(arr, {x:1, y:2}, function (a,b) {
return a.x == b.x && a.y == b.y;
});
arr.indexOf({x:1, y:2}, function (a,b) {
return a.x == b.x && a.y == b.y;
});
arr.indexOf({x:1, y:2}, 1, function (a,b) {
return a.x == b.x && a.y == b.y;
});
The good thing is this still calls the original indexOf if no compare function is passed.
[1,2,3,4].indexOf(3);
Looks like you weren't interested in this type of answer, but it is the simplest to make for others who are interested:
var arr = new Array(
{x:1, y:2},
{x:3, y:4}
);
arr.map(function(obj) {
return objStr(obj);
}).indexOf(objStr({x:1, y:2}));
function objStr(obj) {
return "(" + obj.x + ", " + obj.y + ")"
}

Categories

Resources