Eloquent JavaScript 4.4 Deep Comparison - javascript

I'm trying to teach myself some JavaScript via Eloquent Javascript.
I'm on chapter 4 practice 4.4 Deep comparison.
I've written my own code, passed all the tests given, even some tests I've made myself, but when I checked the solution, it was something totally different than what I had.
Could anyone tell me whether my code produces the same result as the solution, or some ideas of how to figure out whether if my code is right even when mine is something totally different than what the solution is, not just for this practice problem but also in future problems?
I'm just afraid of having a wrong idea and not realizing since my code works.
The question is:
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.
To find out whether to compare two things by identity (use the === operator for that) or by looking at their properties, you can use the typeof operator. If it produces "object" for both values, you should do a deep comparison. But you have to take one silly exception into account: by a historical accident, typeof null also produces "object".
My code is:
function deepEqual(val1, val2) {
if(typeof val1 !== typeof val2) return false;
else if(typeof val1 !== "object") return val1 === val2;
for(var event in val1){
return deepEqual(val1[event], val2[event]);
}
}
and the solution is:
function deepEqual2(a, b) {
if (a === b) return true;
if (a == null || typeof a != "object" ||
b == null || typeof b != "object")
return false;
var propsInA = 0, propsInB = 0;
for (var prop in a)
propsInA += 1;
for (var prop in b) {
propsInB += 1;
if (!(prop in a) || !deepEqual(a[prop], b[prop]))
return false;
}
return propsInA == propsInB;
}
Thanks in advance!

There are a couple of problems with your code:
You only test the first property in an object, because you have a return in your for-in loop. So when comparing objects, all your code does is check that first property.
If you kept a flag and returned the result after the loop, it would still have the issue that it only checks the properties in a; what if b has a property a doesn't have?

Related

Detect if object is either an Array or typed array

I need to determine whether a given object is either an Array, or typed array such as Float32Array.
Currently I'm checking whether the .length property is defined, but this isn't always indicative of an array. Similar issues arise with existence checking of .forEach() or other methods.
Several instanceof checks would suffice, as done here - but I'm wondering if there is a simple built-in feature, e.g., a generic Array.isArray() function that does what I need.
function isArrayOrTypedArray(x) {
return Boolean(x && (typeof x === 'object') && (Array.isArray(x) || (ArrayBuffer.isView(x) && !(x instanceof DataView)));
}
Unfortunately, I don't believe there is.
You can do the instanceof checks you mentioned, or you could check the result of Object.prototype.toString.call(variable) to see if it's one of the predefind strings ("[object Array]", "[object Uint8Array]", etc.). (Edit: Ah, I see by following the link in your question that that's also demonstrated by that code.)
While I think, as T.J. Crowder already said, there's no built-in function, you should be able to combine Array.isArray and ArrayBuffer.isView to get the functionality you want:
function isArrayOrTypedArray(x) {
return Array.isArray(x) || (ArrayBuffer.isView(x) &&
Object.prototype.toString.call(x) !== "[object DataView]");
}
Array.isArray(x) returns true if x is an array. ArrayBuffer.isView(x)returns true if x is a typed array or DataView, so we just need to ignore the case where x is a DataView to get the function we want.
Demonstration:
function isArrayOrTypedArray(x) {
return Array.isArray(x) || (ArrayBuffer.isView(x) && Object.prototype.toString.call(x) !== "[object DataView]");
}
console.log(isArrayOrTypedArray()); // false
console.log(isArrayOrTypedArray({})); // false
console.log(isArrayOrTypedArray(null)); // false
console.log(isArrayOrTypedArray(undefined)); // false
console.log(isArrayOrTypedArray(new ArrayBuffer(10))); // false
console.log(isArrayOrTypedArray([])); // true
console.log(isArrayOrTypedArray([1,2,3,4,5])); // true
console.log(isArrayOrTypedArray(new Uint8Array())); // true
console.log(isArrayOrTypedArray(new Float32Array())); // true
console.log(isArrayOrTypedArray(new Int8Array(10).subarray(0, 3))); // true
var buffer = new ArrayBuffer(2);
var dv = new DataView(buffer);
console.log(isArrayOrTypedArray(dv)); // false
You could do something like this:
function isArray(array) {
if((array.length || array.length === 0) && (array.constructor !== String)) {
return true;
}
return false;
}
Note that a String also has a length property and we need to exclude that, hence the constructor check.
To determine if x is an ArrayBuffer,
You can take advantage of the fact that new DataView(x) throws "TypeError: First argument to DataView constructor must be an ArrayBuffer" if x isn't an ArrayBuffer.
In other words, simply do:
function isArrayBuffer(x) {
try {
new DataView(x);
return true;
}
catch (TypeError) {
return false;
}
}
And to test if a thing is a TypedArray,
I believe ArrayBuffer.isView does the job.
You can use obj.constructor.name as a way of getting the object's name rather than an instanceof matching ladder.
What all these arrays have in common is they have Array in their classname and a array.length property which is a number.
function isArray(x) {
if (typeof x.length === 'number'
&& x.constructor.name.includes('Array')) {
return true;
}
return false;
}
This works in later versions of Javascript/Node anyway.
You could use name.includes if your JS version supports it.
Array.constructor.name is Array for [] and others like Uint8Array for typed arrays.
In other versions of JavaScript you may need obj.prototype.constructor.name.

Javascript Deep Comparison

Questions about deep comparison of objects have been asked, and I have the solution. But there is a line in the solution that I don't completely understand.
This is the solution, it is a question in Ch 4 of Eloquent JS to compare objects. I get all of it except the line:
if (!(prop in a) || !deepEqual(a[prop], b[prop]))
It is found toward the bottom. Here is full function:
function deepEqual(a, b) {
if (a === b) return true;
if (a == null || typeof a != "object" ||
b == null || typeof b != "object")
return false;
var propsInA = 0, propsInB = 0;
for (var prop in a)
propsInA += 1;
for (var prop in b) {
propsInB += 1;
if (!(prop in a) || !deepEqual(a[prop], b[prop]))
return false;
}
return propsInA == propsInB;
}
Is if (!(prop in a) comparing the existence of a property in 'b' at that index, or is it comparing values?
Really the same q. about the second half, but I know it's a different answer: what type of comparison is !deepEqual(a[prop], b[prop]) making (e.g., true or false)? I understand the recursion, but as in my previous question, is this making an 'it exists' comparison, or a comparison of the information in the values?
Thank you in advance.
According to MDN:
The in operator returns true if the specified property is in the specified object.
Also:
The for...in statement iterates over the enumerable properties of an object, in arbitrary order. For each distinct property, statements can be executed.
So to answer your first question, prop in a is checking whether prop, a field from object b, exists in object a.
To answer your second question, deepEqual(a[prop], b[prop]) is checking whether the object a[prop] and b[prop] are equal including all its children and contents.
The in operator returns true if the object on the right of the expression contains a key with the value of the string on the left of the expression.
Eg: prop in a is true if a contains a key with the same name as the string value of prop. Eg:
var prop = "age";
var obj1 = {
name: "Dave",
age: 21,
record: { ... }
};
var obj2 = {
name: "John",
record: { ... }
};
console.log(prop in obj1); // true
console.log(prop in obj2); // false
If prop was set to "record" then deepEqual(a[prop], b[prop]) recursively compares the values in a.record and b.record.
It's checking for existence. It says: "For each property in B, examine A. If A doesn't have the property, or if the value of the property on A doesn't deeply equal the value of the property on B, return false".
An alternate implementation would avoid the existence check on that line, and instead use if (typeof A == 'undefined') at the top of the function to validate the input parameters at the beginning of each round of recursion...at a glance, I think that'd be equivalent, but less efficient. The current implementation avoids the invocation on the undefined property.

Why is JavaScript code executed while condition is false? (Eloquent JavaScript - Deep comparison)

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));
Code:
var deepEqual = function (x, y) {
if ((typeof x == "object" && x != null) && (typeof y == "object" && y != null)) {
if (Object.keys(x).length != Object.keys(y).length)
return false;
for (var prop in x) {
if (y.hasOwnProperty(prop)){
if (! deepEqual(x[prop], y[prop])) //should not enter!!
return false;
alert('test');
}else return false; // What does this section do?
}
return true;
}
else if (x !== y)
return false;
else
return true;
};
Originaly fulfilled by Paul Roub
Main question: I just added alert to the block of code after if (! deepEqual(x[prop], y[prop])) statement like kind of debugging, and now I have no idea why code inside is still executed while the statement itself is supposed to return true and ! turns it false..?
In addition: What is }else return false; for? (same statement) The function seems to work fine without this section..
You added the alert() in such a way that it only runs when the if test fails, because you didn't add { } to match the intention indicated by your indentation. If the if test succeeds, then the following return statement will exit the function and the alert() won't happen.
Any code that involves an if or else if block that always returns but is nevertheless followed by an else clause is a code smell. Either the else or the return is redundant; it's a matter of style which way you go.
That code is also flawed in that it tests the number of properties with Object.keys() — which implicitly only looks at "own" properties — but then uses a for ... in to loop through x without a .hasOwnProperty() check. (Whether inherited properties should affect the concept of "equality" is subjective, but it should probably be symmetric at least.)
Oh, and that first else return false clause after the check to see if y has one of the properties in x is just a quick exit. If y doesn't have a property name that's in x, then they can't be equal.
Finally note that any sort of "deep" equality tester that intends to be generic really needs to deal with object graph cycles and other even weirder things. (What of something in the object graph of x refers to part of y, and vice versa?) Deep object comparison is not a trivial thing.

Underscore.js _.isElement function

I have been looking underscore.js library functions and I noticed a function which returns whether the element is a DOM element or not. The function is below.
_.isElement = function(obj) {
return !!(obj && obj.nodeType == 1);
};
Can you please tell me why !! is being used instead of just returning (obj && obj.nodeType == 1). I am wondering whether !! adds any performance improvements. Any idea...
!! forces the result to be a boolean.
If you pass null, for example, then the && will return null. The !! converts that to false.
If obj is "truthy", you'll get the result of obj.nodeType == 1 which is a boolean.

How to test if two objects are the same with JavaScript?

I need a function:
function isSame(a, b){
}
In which, if a and b are the same, it returns true.
, I tried return a === b, but I found that [] === [] will return false.
Some results that I expect this function can gave:
isSame(3.14, 3.14); // true
isSame("hello", "hello"); // true
isSame([], []); // true
isSame([1, 2], [1, 2]); // true
isSame({ a : 1, b : 2}, {a : 1, b : 2}); //true
isSame([1, {a:1}], [1, {a:1}]); //true
You could embed Underscore.js and use _.isEqual(obj1, obj2).
The function works for arbitrary objects and uses whatever is the most efficient way to test the given objects for equality.
the best way to do that is to use a JSON serializer. serialize both to string and compare the string.
There are some examples, adapted from scheme, on Crockford's site. Specifically, check out:
function isEqual(s1, s2) {
return isAtom(s1) && isAtom(s2) ? isEqan(s1, s2) :
isAtom(s1) || isAtom(s2) ? false :
isEqlist(s1, s2);
}
It can all be found here:
http://javascript.crockford.com/little.js
Here is a working example:
http://jsfiddle.net/FhGpd/
Update:
Just wrote some test cases based on the OP. Turns out I needed to modify the sub1 function to check <= 0 not === 0 otherwise isEqual(3.14, 3.14) blew the stack. Also, isEqual does not work for object comparison, so you are on your own there. However, if you follow the examples on Crockford's site you will see how easy and fun it is to write recursive methods that could be used to check for object equality.
Here is something that can work:
function isSame(obj1, obj2, prefer){
// Optional parameter prefer allows to only check for object keys and not both keys and values of an object
var obj_prefer = prefer || "both";
function checkArray(arr1, arr2){
for(var i = 0, j = obj1.length; i<j; i++){
if(obj1[i] !== obj2[i]){return false;}
}
return true;
}
function checkValues(obj_1, obj_2){
for(var prop in obj_1){
if(typeof obj_1[prop] === "function"){ // converting functions to string so that they can be matched
obj_1[prop] = String(obj_1[prop]);
obj_2[prop] = String(obj_2[prop]);
}
if(obj_1[prop] !== obj_2[prop]){ return false;}
}
return true;
}
// The built in === will check everything except when typeof object is "object"
if ( typeof obj1 === "object"){
// typeof Array is object so this is an alternative
if((typeof obj1.push === "function") && (!obj1.hasOwnProperty('push'))){
return checkArray(obj1, obj2);
}
else{
if( obj_prefer !== "keys"){ // do not check for values if obj_prefer is "keys"
return checkValues(obj1, obj2);
}
var keys_1 = Object.keys(obj1);
var keys_2 = Object.keys(obj2);
if(!checkArray(keys_1, keys_2)){return false;}
return true;
}
}
// I thought undefined === undefined will give false but it isn't so you can remove it
if( typeof obj1 === "undefined" && typeof obj2 === "undefined" ){return true}
if(typeof obj1 === "function"){
return String(obj1) === String(obj2);
}
return obj1 === obj2;
}
console.log(isSame(2, 2)); //true
console.log(isSame([1], [1])); // true
Since it converts Functions into Strings to compare them, check out for spaces as that can break things:
var func1 = function(){},
func2 = function(){ }; // function with extra space
isSame(func1, func2); // false
You can check out http://jsfiddle.net/webholik/dwaLN/4/ to try it yourself.
If anyone reading this answer is using Angular.js, you can use angular.equals(obj1,obj2);
According to the docs:
Determines if two objects or two values are equivalent. Supports value
types, regular expressions, arrays and objects.
Two objects or values are considered equivalent if at least one of the
following is true:
Both objects or values pass === comparison.
Both objects or values are of the same type and all of their properties are equal by comparing them with angular.equals.
Both values are NaN. (In JavaScript, NaN == NaN => false. But we consider two NaN as equal).
Both values represent the same regular expression (In JavaScript, /abc/ == /abc/ => false. But we consider two regular expressions as
equal when their textual representation matches).
During a property comparison, properties of function type and properties with names that begin with $ are ignored.
Scope and DOMWindow objects are being compared only by identify (===).

Categories

Resources