Comparing similar objects of different instances in javascript - javascript

I had always wondered why this :
var a = [[0, 1]];
a[0] == [0, 1];
would return false. It seems like these 2 arrays, a[0] and [0, 1], despite not being the same instance of Array, ARE actually the same object, in so far as all their properties are the same. This is not the case in JS though and I don't know why.
Which test applied to these two arrays, and to much more complicated objects, would return true ? (answers from jQuery and D3.js are accepted, I don't plan on using any other)
Edit: wrapping the objects with JSON.stringify seems to work; are there any caveats I should be aware of though?

[Equal Operator] "If both operands are objects, then JavaScript compares internal references which are equal when operands refer to the same object in memory."
See: https://developer.mozilla.org/en/JavaScript/Reference/Operators/Comparison_Operators
So, even:
[0, 1] == [0, 1]
Will returns false, because they are different objects, even if with the same content.
If it's confuse you using the array literals, notice that the code above is exactly the same of the follow:
new Array(0, 1) == new Array(0, 1);

The two objects have the same value but are not the same object, for example if you push a new element to one the other will not get that new pushed element.
var a = [[0, 1]];
var b = [0, 1];
a[0].push(42);
alert(a[0].length); // now it's 3
alert(b.length); // still 2
Note also that the syntax [0, 1] is not representing an array object, but it's an array object "builder" that will produce a new fresh array each time it's evaluated:
function mkarr() {
return [0, 1];
}
var a = mkarr();
var b = mkarr();
a.push(42);
alert(a.length); // 3
alert(b.length); // still 2
alert(mkarr().length); // still 2 too
For numbers and strings instead equality matches because the referenced objects are immutable (i.e. you can make your variable to point to another number, but you cannot change the number itself).
Calling JSON.stringify on an object you get a string representation of the object, not the object... and the representations can indeed match equal for different objects (because they're just strings).
Note that the string representation doesn't really capture the object, and you can have substantially different objects with the same identical string representation... for example:
var a = [0, 1];
var b = [a, a];
var c = [[0, 1], [0, 1]];
alert(JSON.stringify(b) == JSON.stringify(c)); // true
b[0].push(42);
c[0].push(42);
alert(JSON.stringify(b)); // "[[0,1,42],[0,1,42]]"
alert(JSON.stringify(c)); // "[[0,1,42],[0,1]]"

Related

Sum of two multi dimensional array

I have a array in JavaScript like this.
var arr=
[
['A'],[1,2,3,4],
['A'],[4,3,2,1],
['B'],[10,12,3,1],
['B'],[1,2,3,4],
.
.
.
.
['AZ'],[1,2,3,4]
]
and I want the output to summarize the array like -
var output=
[
['A'],[5,5,5,5],
['B'],[11,14,6,5],
['AZ'],[1,2,3,4]
]
Thanks.
Script
You can use the following script to achieve what you want to do.
const arr = [
["A"],
[1, 2, 3, 4],
["A"],
[4, 3, 2, 1],
["B"],
[10, 12, 3, 1],
["B"],
[1, 2, 3, 4],
["AZ"],
[1, 2, 3, 4],
];
/**
* Generator to return key-value pairs with array[i] being the key and array[i+1] being the value
* #param {Array<any>} array
*/
function* keyValue(array) {
// make sure we can build pairs (other ways of handling this are also possible)
if (array.length % 2 !== 0)
throw new RangeError(
"Array length must be dividable by 2 without remainder!"
);
for (let i = 0; i < array.length; i += 2) {
yield [array[i], array[i + 1]];
}
}
// here the created key-value pairs
console.log("Key-value pairs created by keyValue() generator function:");
console.log([...keyValue(arr)]);
// loop over key value pairs and sum up all the individul arrays based on the letter assigned to them
const result = [...keyValue(arr)].reduce((all, [[key], array]) => {
// if we don't have values for this letter, assing copy of the array to that letter
if (!all[key]) all[key] = [...array];
// we have some values for that letter already, sum up each value
else all[key] = all[key].map((prev, idx) => prev + array[idx]);
return all;
}, {});
// this would be a "better" result to my mind as there is no point wrapping single string values in arrays
// When using objects the values can easily be accessed in O(1)
console.log(result);
// now transform JS object to array of arrays
console.log("Result:");
const transformed = Object.entries(result).flatMap(([key, value]) => [[key], value]);
console.log(transformed);
.as-console-wrapper { max-height: 100% !important; top: 0; }
Please note: This implementation assumes that the arrays for a given letter have the same length (as is the case in your example).
Explanation
First of all, I use a generator function keyValue() to always group two consecutive values in the array (a key and a value) together. One could also do this differently but once you understand how generators work that's an easy and elegant approach, I think. For this demo I just throw an error if the array is not dividable by 2 without remainder, but one could also handle this more gracefully.
Then, using reduce(), I iterate over the array created by using keyValue() and for each element in the array I check if I've encountered that value before. If I have not, I create a copy of the array (for immutablility) and assign it to the key i.e. a letter. If I have encountered a certain letter before I add up the values that I have previously saved assigned to that letter with the ones I am currently processing. After iteration all sums are calculated and I have a JavaScript object containing the results.
To my mind, this would be a good output because your output is a bit odd, since there is no point storing single letters in an array or even arrays of arrays. Using a JavaScript object is much more convenient and faster for lookups.
Nevertheless, you can easily deduct your result from the created object using flatMap().

Javascript some() returns false on zero

I need to check if numbers exist in my array. Using the some() function I find that zero comes back false. It's a problem because I am working with a ton of different numbers and zero being one of them.
var array = [0, 1, 3, 4, 5];
var test = function(element) {
return 0;
};
console.log(array.some(test));
// expected output: true on 0 <-- getting false
// expected output: true on 1
// expected output: false on 20
In short how can I get 0 to return true?
the test function always returns zero.
var test = function(element) {
return element == 0
};
This way the function should work properly.
The test function should return true/false.
In your case you always return 0 which is evaluated to boolean false.
For what you're trying to implement, it might make more sense to use the .includes function, which tests whether an array includes a value:
var array = [0, 1, 3, 4, 5];
console.log(array.includes(0));
console.log(array.includes(1));
console.log(array.includes(2));
Though, .includes (along with all array iteration methods) is an O(N) process - if you need to carry out a bunch of tests for the same array, you might convert the array to a Set first, so that you can then use Set.has (which is generally O(1)):
var array = [0, 1, 3, 4, 5];
const set = new Set(array);
console.log(set.has(0));
console.log(set.has(1));
console.log(set.has(2));

What is the difference between `var in array` and `array.indexOf(var)`?

I am trying to get my head around arrays in JS. My question is; are the following two tests equivalent?
var test = 2;
console.log(test in [1,2,3]);
console.log([1,2,3].indexOf(test) != -1);
They seem to be, but then answers like this and the book I am reading suggest that you can't do in on an array, only on an object. Looking for clarity. There must be a reason that people use .indexOf(x) (which I assume is linear time) and not in (which I assume is constant time).
No. They are completely different.
test in [1,2,3] checks if there is a property named 2 in the object. There is, it has the value 3.
[1,2,3].indexOf(test) gets the first property with the value 2 (which is in the property named 1)
suggest that you can't do in on an array, only on an object
Arrays are objects. (A subclass if we want to use classical OO terminally, which doesn't really fit for a prototypal language like JS, but it gets the point across).
The array [1, 2, 3] is like an object { "0": 1, "1": 2, "2": 3 } (but inherits a bunch of other properties from the Array constructor).
As per MDN,
The in operator returns true if the specified property is in the specified object.
in will check for keys. Its similar to Object.keys(array).indexOf(test)
var a1 = [1,2,3];
var a2 = ['a', 'b', 'c']
var test = 2;
console.log(test in a1)
console.log(test in a2)
// Ideal way
//ES5
console.log(a1.indexOf(test)>-1)
console.log(a2.indexOf(test)>-1)
//ES6
console.log(a1.includes(test))
console.log(a2.includes(test))
The first checks for an index, or if property of an object exists,
console.log(test in [1,2,3]);
and not for a value in the array, as the second is doing.
console.log([1,2,3].indexOf(test) != -1);
The in operator returns true if the specified property is in the
specified object.
Using in operator for an array checks of the indices and the length property - because those are the properties for an array:
console.log(Object.getOwnPropertyNames([1, 2, 3]));
console.log(Object.keys([1, 2, 3]));
console.log('length' in [1, 2, 3]);
Object.keys : return all enumerable properties
Object.getOwnPropertyNames : return all properties
Try this
var test = 2;
var arrval= [1, 5, 2, 4];
var a = arrval.indexOf(test);
if(a>-1) console.log("Having");
else console.log("Not Having");

How to tell if array contains another specific array, not just its elements?

I will go straight to code on this one because it is very specific. How can I test if arr contains an element which is itself a specific array. Why can I not use an array literal as an arg to indexOf? I'm trying to see if an array of coordinates contains a specific coordinate pair.
var arr = [[0,0], [1,1]];
arr[0]; // [0, 0]
arr.indexOf([0, 0]); // -1
var y = arr[0];
arr.indexOf(y); // 0
var x = [0, 0];
arr.indexOf(x); // -1
As others have pointed out, [0, 0] !== [0, 0]. Instead, use findIndex with a function which checks array equality:
var match = [0, 0];
array.findIndex(function(elt) { return arrayIsEqual(elt, match); })
Now you just have to write arrayIsEqual. See How to check if two arrays are equal with JavaScript? for some ideas. If your arrays always have two elements, then it could be just elt[0] === match[0] && elt[1] === match[1].
Obligatory disclaimer: findIndex is ES6. However, it's implemented almost everywhere you would care about, except apparently IE11. If need be, use a polyfill or write your own.
"Why can I not use an array literal as an arg to indexOf?"
I beleive the problem here is that an Array is an object. You cannot create a new object
var x = [0, 0];
and expect it to match arr[0]; because it is not the same object in memory.
To demonstrate I think this would work, but I havent tested it:
var arr = [[0,0], [1,1]];
var x = [0, 0];
arr[0] = x;
arr.indexOf(x); // 0
We can use instanceOf to check the type of variable or value
if(value instanceOf Array)
{
//value is of array type
}
and if you want to compare it with some specific array try below code in if statement
var is_same = (array1.length == array2.length) && array1.every(function(element, index) {
return element === array2[index];
});
if is_same is true then array is identical
Passing objects into the array#indexOf method might not give the results you expect
The indexOf() method returns the first index at which a given element can be found in the array, or -1 if it is not present.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf
Hopefully, someone else can confirm this.
Sounds like the perfect use for array.prototype.some
function hasCoordinates(array, coords) {
return array.some(function(element, coords) {
return Array.isArray(element) && String(array[element]) == String(coords);
});
}
hasCoordinates([1,3,[4,6]], [4,6])
=> should return true
We can use the isArray method to determine if an object is array.
It should be noted that the some function is only available in ie9 and up
To find the exact coordinates, you can not compare arrays for equality, as they are treated as different objects.
Ex.
[0,0] == [0,0]
=> false
We need to perform type conversion first
String[0,0] == String[0,0]
=> true
This is because, now the arrays are being evaluated as strings.
You must use a comparable type to use .indexOf(). When you use a comparison operator with objects (and Arrays are objects) then JS uses reference comparison (MDN Docs). It is probably not what you want to do but you can use a reference as I will show below:
var a = [0, 0];
var b = [1, 1];
var c = [1, 1];
var e = c; // e now has the same reference as c
console.log(b == c); // false - Reference comparison is used here
console.log(c == e); // true - References are the same
var d = [a, b, c];
console.log(d.indexOf(a)); // 0
console.log(d.indexOf(b)); // 1
console.log(d.indexOf(c)); // 2
console.log(d.indexOf(e)); // 2
If you create 2 objects with the same values inside they still do not have the same reference (like b and c in the above code). As mentioned by #torazaburo you could instead use the .findIndex() function.
You can do this something like below where you pass in your array to find and it returns a function which returns true when it matches each element.
var a = [0, 0],
b = [0, 0],
c = [0, 1];
var d = [a, b, c];
function equalArrays(a) {
return function(b) {
if (a.length != b.length)
return false;
for (var i = 0, len = a.length; i < len; i++) {
if (a[i] != b[i])
return false;
}
return true;
}
}
console.log(d.findIndex(equalArrays([0, 0]))); // 0 - index of FIRST array matching [0,0]
console.log(d.findIndex(equalArrays([0, 1]))); // 2 - index of an array matching [0,1]
console.log(d.findIndex(equalArrays([1, 1]))); // -1 - No match found
To answer the last question "Why can I not use an array literal as an arg to indexOf?" the answer is due to how indexOf is defined.
Refer to the ECMAScript 2015 Array.prototype.indexOf spec: http://www.ecma-international.org/ecma-262/6.0/#sec-array.prototype.indexof
indexOf compares searchElement to the elements of the array, in ascending order, using the Strict Equality Comparison algorithm (7.2.13), and if found at one or more indices, returns the smallest such index; otherwise, −1 is returned.
The important part is the algorithm used; Strict Equality Comparison. In other words, triple equals.
Consider this JS:
var a = [1, 2];
var b = a === [1, 2]; // true or false?
What is the value of b? It's false. If you want to learn more about why that is the case, I recommend reading https://stackoverflow.com/a/14853974/1022333.

Arrays created with new? [duplicate]

This question already has answers here:
Undefined values in Array(len) initializer
(5 answers)
Closed 7 years ago.
I am confused by the results of mapping over an array created with new:
function returnsFourteen() {
return 14;
}
var a = new Array(4);
> [undefined x 4] in Chrome, [, , , ,] in Firefox
a.map(returnsFourteen);
> [undefined x 4] in Chrome, [, , , ,] in Firefox
var b = [undefined, undefined, undefined, undefined];
> [undefined, undefined, undefined, undefined]
b.map(returnsFourteen);
> [14, 14, 14, 14]
I expected a.map(returnsFourteen) to return [14, 14, 14, 14] (the same as b.map(returnsFourteen), because according to the MDN page on arrays:
If the only argument passed to the Array constructor is an integer
between 0 and 2**32-1 (inclusive), a new JavaScript array is created
with that number of elements.
I interpret that to mean that a should have 4 elements.
What am I missing here?
When you create an array like so:
var arr1 = new Array( 4 );
you get an array that has a length of 4, but that has no elements. That's why map doesn't tranform the array - the array has no elements to be transformed.
On the other hand, if you do:
var arr2 = [ undefined, undefined, undefined, undefined ];
you get and array that also has a length of 4, but that does have 4 elements.
Notice the difference between having no elements, and having elements which values are undefined. Unfortunately, the property accessor expression will evaluate to the undefined value in both cases, so:
arr1[0] // undefined
arr2[0] // undefined
However, there is a way to differentiate these two arrays:
'0' in arr1 // false
'0' in arr2 // true
var a = new Array(4);
This defines a new array object with an explicit length of 4, but no elements.
var b = [undefined, undefined, undefined, undefined];
This defines a new array object with an implicit length of 4, with 4 elements, each with the value undefined.
From the docs:
callback is invoked only for indexes of the array which have assigned
values; it is not invoked for indexes which have been deleted or which
have never been assigned values.
For array a, there are no elements that have been assigned values, so it does nothing.
For array b, there are four elements that have been assigned values (yes, undefined is a value), so it maps all four elements to the number 14.
new Array(len) creates an empty array, and does something different than filling it with undefined values: It sets its length to len. So, it translates to this code:
var newArr = [];
newArr.length = len;
Let's have some fun with newArr (assuming that len = 4):
newArr.length; //4
newArr[1] === undefined; //true
newArr.hasOwnProperty(1); //false
This is because while the is 4 items long, it does not contain any of these 4 items. Imagine an empty bullet-clip: It has space for, say, 20 bullets, but it doesn't contain any of them. They weren't even set to the value undefined, they just are...undefined (which is a bit confusing.)
Now, Array.prototype.map happily walks along your first array, chirping and whistling, and every time it sees an array item, it calls a function on it. But, as it walks along the empty bullet-clip, it sees no bullets. Sure, there are room for bullets, but that doesn't make them exist. In here, there is no value, because the key which maps to that value does not exist.
For the second array, which is filled with undefined values, the value is undefined, and so is the key. There is something inside b[1] or b[3], but that something isn't defined; but Array.prototype.map doesn't care, it'll operate on any value, as long as it has a key.
For further inspection in the spec:
new Array(len) : http://es5.github.com/#x15.4.2.2
Array.prototype.map : http://es5.github.com/#x15.4.4.19 (pay close attention to step 8.b)
One additional answer on the behavior of console.log. Plain simple, the output is sugar and technically wrong.
Lets consider this example:
var foo = new Array(4),
bar = [undefined, undefined, undefined, undefined];
console.log( Object.getOwnPropertyNames(bar) );
console.log( Object.getOwnPropertyNames(foo) );
As we can see in the result, the .getOwnPropertyNames function returns
["length"]
for the foo Array/Object, but
["length", "0", "1", "2", "3"]
for the bar Array/Object.
So, the console.log just fools you on outputting Arrays which just have a defined .length but no real property assignments.

Categories