{"ScriptPreparationCode":"const alpha = \u0027abcdefghijklmnopqrstuvwxyz\u0027;\r\nconst keys = [];\r\n\r\nfor( let i = 0; i \u003C alpha.length; i\u002B\u002B ) {\r\n for( let j = 0; j \u003C alpha.length; j\u002B\u002B ) {\r\n for( let k = 0; k \u003C alpha.length; k\u002B\u002B ) {\r\n \t\tkeys.push( alpha[ i ] \u002B alpha[ j ] \u002B alpha[ k ] );\r\n }\r\n }\r\n}\r\n\r\nfunction shuffle(array) {\r\n let currentIndex = array.length, randomIndex;\r\n\r\n // While there remain elements to shuffle...\r\n while (currentIndex != 0) {\r\n\r\n // Pick a remaining element...\r\n randomIndex = Math.floor(Math.random() * currentIndex);\r\n currentIndex--;\r\n\r\n // And swap it with the current element.\r\n [array[currentIndex], array[randomIndex]] = [\r\n array[randomIndex], array[currentIndex]];\r\n }\r\n\r\n return array;\r\n}\r\n\r\nfunction fill( obj ) {\r\n shuffle( keys ).forEach( key =\u003E {\r\n obj[ key ] = key;\r\n } );\r\n return obj;\r\n}\r\n\r\nfunction areEquivalent1( value1, value2, stack = [] ) {\r\n // Numbers, strings, null, undefined, symbols, functions, booleans.\r\n // Also: objects (incl. arrays) that are actually the same instance\r\n if ( value1 === value2 ) return true;\r\n\r\n const type1 = typeof value1;\r\n\r\n // Ensure types match\r\n if ( type1 !== typeof value2 ) return false;\r\n\r\n // Special case for number: check for NaN on both sides\r\n // (only way they can still be equivalent but not equal)\r\n if ( type1 === \u0027number\u0027 ) {\r\n // Failed initial equals test, but could still both be NaN\r\n return ( isNaN( value1 ) \u0026\u0026 isNaN( value2 ) );\r\n }\r\n\r\n // Special case for function: check for toString() equivalence\r\n if ( type1 === \u0027function\u0027 ) {\r\n // Failed initial equals test, but could still have equivalent\r\n // implementations - note, will match on functions that have same name\r\n // and are native code: \u0060function abc() { [native code] }\u0060\r\n return value1.toString() === value2.toString();\r\n }\r\n\r\n // For these types, cannot still be equal at this point, so fast-fail\r\n if (\r\n type1 === \u0027bigint\u0027 ||\r\n type1 === \u0027boolean\u0027 ||\r\n type1 === \u0027function\u0027 ||\r\n type1 === \u0027string\u0027 ||\r\n type1 === \u0027symbol\u0027\r\n ) {\r\n return false;\r\n }\r\n\r\n // For dates, cast to number and ensure equal or both NaN (note, if same\r\n // exact instance then we\u0027re not here - that was checked above)\r\n if ( value1 instanceof Date ) {\r\n if ( !( value2 instanceof Date ) ) {\r\n return false;\r\n }\r\n // Convert to number to compare\r\n const asNum1 = \u002Bvalue1, asNum2 = \u002Bvalue2;\r\n // Check if both invalid (NaN) or are same value\r\n return asNum1 === asNum2 || ( isNaN( asNum1 ) \u0026\u0026 isNaN( asNum2 ) );\r\n }\r\n\r\n // At this point, it\u0027s a reference type and could be circular, so\r\n // make sure we haven\u0027t been here before... note we only need to track value1\r\n // since value1 being un-circular means value2 will either be equal (and not\r\n // circular too) or unequal whether circular or not.\r\n if ( stack.includes( value1 ) ) {\r\n throw new Error( \u0060areEquivalent value1 is circular\u0060 );\r\n }\r\n\r\n // breadcrumb\r\n stack.push( value1 );\r\n\r\n // Handle arrays\r\n if ( Array.isArray( value1 ) ) {\r\n if ( !Array.isArray( value2 ) ) {\r\n return false;\r\n }\r\n\r\n const length = value1.length;\r\n\r\n if ( length !== value2.length ) return false;\r\n\r\n for ( let i = 0; i \u003C length; i\u002B\u002B ) {\r\n if ( !areEquivalent1( value1[ i ], value2[ i ], stack ) ) {\r\n return false;\r\n }\r\n }\r\n\r\n // \r\n return true;\r\n }\r\n\r\n // Final case: object\r\n\r\n // get both key lists and check length\r\n const keys1 = Object.keys( value1 );\r\n const keys2 = Object.keys( value2 );\r\n const numKeys = keys1.length;\r\n \r\n if ( numKeys !== keys2.length ) {\r\n return false;\r\n }\r\n\r\n // Empty object on both sides?\r\n if ( numKeys === 0 ) {\r\n return true;\r\n }\r\n\r\n // sort is a native call so it\u0027s very fast - much faster than comparing the\r\n // values at each key if it can be avoided, so do the sort and then\r\n // ensure every key matches at every index\r\n // *** benchmark this vs for (var prop in obj)\r\n keys1.sort();\r\n keys2.sort();\r\n\r\n // Ensure perfect match across all keys\r\n for( let i = 0; i \u003C numKeys; i\u002B\u002B ) {\r\n if( keys1[ i ] !== keys2[ i ] ) {\r\n return false;\r\n }\r\n }\r\n\r\n // Ensure perfect match across all values\r\n for( let i = 0; i \u003C numKeys; i\u002B\u002B ) {\r\n if ( !areEquivalent1( value1[ keys1[ i ] ], value2[ keys1[ i ] ], stack ) ) {\r\n return false;\r\n }\r\n }\r\n\r\n // back up\r\n stack.pop();\r\n\r\n return true;\r\n}\r\n\r\nfunction areEquivalent2( value1, value2, stack = [] ) {\r\n // Numbers, strings, null, undefined, symbols, functions, booleans.\r\n // Also: objects (incl. arrays) that are actually the same instance\r\n if ( value1 === value2 ) return true;\r\n\r\n const type1 = typeof value1;\r\n\r\n // Ensure types match\r\n if ( type1 !== typeof value2 ) return false;\r\n\r\n // Special case for number: check for NaN on both sides\r\n // (only way they can still be equivalent but not equal)\r\n if ( type1 === \u0027number\u0027 ) {\r\n // Failed initial equals test, but could still both be NaN\r\n return ( isNaN( value1 ) \u0026\u0026 isNaN( value2 ) );\r\n }\r\n\r\n // Special case for function: check for toString() equivalence\r\n if ( type1 === \u0027function\u0027 ) {\r\n // Failed initial equals test, but could still have equivalent\r\n // implementations - note, will match on functions that have same name\r\n // and are native code: \u0060function abc() { [native code] }\u0060\r\n return value1.toString() === value2.toString();\r\n }\r\n\r\n // For these types, cannot still be equal at this point, so fast-fail\r\n if (\r\n type1 === \u0027bigint\u0027 ||\r\n type1 === \u0027boolean\u0027 ||\r\n type1 === \u0027function\u0027 ||\r\n type1 === \u0027string\u0027 ||\r\n type1 === \u0027symbol\u0027\r\n ) {\r\n return false;\r\n }\r\n\r\n // For dates, cast to number and ensure equal or both NaN (note, if same\r\n // exact instance then we\u0027re not here - that was checked above)\r\n if ( value1 instanceof Date ) {\r\n if ( !( value2 instanceof Date ) ) {\r\n return false;\r\n }\r\n // Convert to number to compare\r\n const asNum1 = \u002Bvalue1, asNum2 = \u002Bvalue2;\r\n // Check if both invalid (NaN) or are same value\r\n return asNum1 === asNum2 || ( isNaN( asNum1 ) \u0026\u0026 isNaN( asNum2 ) );\r\n }\r\n\r\n // At this point, it\u0027s a reference type and could be circular, so\r\n // make sure we haven\u0027t been here before... note we only need to track value1\r\n // since value1 being un-circular means value2 will either be equal (and not\r\n // circular too) or unequal whether circular or not.\r\n if ( stack.includes( value1 ) ) {\r\n throw new Error( \u0060areEquivalent value1 is circular\u0060 );\r\n }\r\n\r\n // breadcrumb\r\n stack.push( value1 );\r\n\r\n // Handle arrays\r\n if ( Array.isArray( value1 ) ) {\r\n if ( !Array.isArray( value2 ) ) {\r\n return false;\r\n }\r\n\r\n const length = value1.length;\r\n\r\n if ( length !== value2.length ) return false;\r\n\r\n for ( let i = 0; i \u003C length; i\u002B\u002B ) {\r\n if ( !areEquivalent2( value1[ i ], value2[ i ], stack ) ) {\r\n return false;\r\n }\r\n }\r\n\r\n // \r\n return true;\r\n }\r\n\r\n // Final case: object\r\n\r\n // get both key lists and check length\r\n const keys1 = Object.keys( value1 );\r\n const numKeys = keys1.length;\r\n \r\n if ( numKeys !== Object.keys( value2 ).length ) {\r\n return false;\r\n }\r\n\r\n // Empty object on both sides?\r\n if ( numKeys === 0 ) {\r\n return true;\r\n }\r\n\r\n let matches = 0;\r\n \r\n // Ensure perfect match across all keys\r\n for( let key in value1 ) {\r\n if( key in value2 ) matches\u002B\u002B;\r\n else return false;\r\n }\r\n \r\n if( matches \u003C numKeys ) return false;\r\n\r\n // Ensure perfect match across all values\r\n for( let key in value1 ) {\r\n if ( !areEquivalent2( value1[ key ], value2[ key ], stack ) ) {\r\n return false;\r\n }\r\n }\r\n\r\n // back up\r\n stack.pop();\r\n\r\n return true;\r\n}","TestCases":[{"Name":"sorted keys","Code":"const equivalent = areEquivalent1( fill( {} ), fill( {} ) );","IsDeferred":false},{"Name":"for in","Code":"const equivalent = areEquivalent2( fill( {} ), fill( {} ) );","IsDeferred":false}]}