art-standard-lib
Version:
The Standard Library for JavaScript that aught to be.
252 lines (203 loc) • 7.93 kB
text/coffeescript
{remove} = require './ArrayExtensions'
{objectKeyCount} = require './ObjectExtensions'
{floatTrue0} = require './MathExtensions'
{isString, isNumber} = require './TypesExtended'
{min} = Math
# TODO: (BUG?) I think that just "returning true" if a or b is in testing will have some false positives.
# TODO: (PERF TEST) I'd like to do a dumb, unsafe recursive comparator that doesn't use testing.
# TODO: (PERF TEST) We may also want the dumb version not to do the undefined test if "hasOwnProperty" is slow.
# TODO: (PERF TEST) Object.keys is slower than iterating over the keys in objects < 100 properties. AND, Since we are iterating anyway...
# TODO: (PERF TEST) When testing for a.eq, is (typeof(a.eq) == "function") as-fast? If so, use that, else, stick with what we've got.
module.exports = class Eq
###
IN: see @compare
OUT:
true: if a and b are structurally equal
false: otherwise
###
@eq: (a, b, compareFunctionsAsStrings) => a == b || 0 == @compare a, b, true, compareFunctionsAsStrings
@neq: (a, b, compareFunctionsAsStrings) =>
if a == b
false
else
0 != @compare a, b, true, compareFunctionsAsStrings
@fastEq: (a, b) => a == b || 0 == @compare a, b, false
@fastNeq: (a, b) =>
if a == b
false
else
0 != @compare a, b, false
# recursively compares all elements with indexs < min a.length, b.length
# If all are equal, returns a.length - b.length
@_compareArray: (a, b, recursionBlockArray, compareFunctionsAsStrings) =>
aLength = a.length
bLength = b.length
for i in [0...Math.min aLength, bLength] by 1
av = a[i]
bv = b[i]
return val if 0 != val = @_compare av, bv, recursionBlockArray, compareFunctionsAsStrings
aLength - bLength
# recursively compares all properties in both a and b
# If all are equal, returns a.length - b.length
@_compareObject: (a, b, recursionBlockArray, compareFunctionsAsStrings) =>
aLength = 0
compared = 0
for k, av of a
aLength++
av = a[k]
bv = b[k]
if bv != undefined || b.hasOwnProperty k
compared++
return val if 0 != val = @_compare av, bv, recursionBlockArray, compareFunctionsAsStrings
if aLength == compared && compared == objectKeyCount b
0
else
NaN
###
compare is recursive. However, it only recurses for 'plain' objects and arrays.
If you want to compare custom objects deeply, you must add an .eq or .compare function to your custom objects.
signature: a.eq b, recursionBlockArray => truthy if a equals b
signature: a.compare b, recursionBlockArray => NaN / <0 / 0 / >0 for incomparable / a<b / a==b / a>b respectively
IN:
a: => this/@
b: compared with a
recursionBlockArray: an array of objects already on the stack being tested, pass this to
It is an array of every object recursively currently being tested - don't test an object in this array
recursionBlockArray can be altered, but should be returned in its original state. It may be null.
IN:
a and b: compare a and b
recursionBlockEnabled:
truthy: recursive structures will be handled correctly
falsey: (default) faster, but recursive structures result in infinite recursion
OUT:
NaN:
a and b are different types
a and b are otherwise not comparable
<0: a < b
0: a == b
>0: a > b
TODO:
recursionBlockArray could be reused.
Further, depth == 1 checks could be safely skipped to make
even slow-compare fast for simple objects. Only if we
have an object/array inside another object/array do we need
to start checking.
###
@compare: (a, b, recursionBlockEnabled, compareFunctionsAsStrings) =>
@_compare a, b, recursionBlockEnabled && [], compareFunctionsAsStrings
@_compare: (a, b, recursionBlockArray, compareFunctionsAsStrings) =>
return 0 if a == b
if a? && b? && a.constructor == _constructor = b.constructor
return a.localeCompare b if isString a
return floatTrue0 a - b if isNumber a
# recursion block
if recursionBlockArray
return 0 if a in recursionBlockArray || b in recursionBlockArray
recursionBlockArray.push a
recursionBlockArray.push b
# use a.compare if available
return a.compare b, recursionBlockArray if a.compare
# recurse on plain objects and arrays
return @_compareArray a, b, recursionBlockArray, compareFunctionsAsStrings if _constructor == Array
return @_compareObject a, b, recursionBlockArray, compareFunctionsAsStrings if _constructor == Object
return ("#{a}").localeCompare("#{b}") if compareFunctionsAsStrings && _constructor == Function
# fallback to .eq
return 0 if a.eq && a.eq b, recursionBlockArray
if recursionBlockArray
remove recursionBlockArray, recursionBlockArray.length - 2, 2
NaN
@plainObjectsDeepEqArray: (a, b) =>
return false if a.length != b.length
for av, i in a when !_plainObjectsDeepEq av, b[i]
return false
true
@plainObjectsDeepEqObject: (a, b) =>
aLength = 0
for k, av of a
aLength++
bv = b[k]
unless (bv != undefined || b.hasOwnProperty(k)) && _plainObjectsDeepEq av, bv
return false
aLength == objectKeyCount b
compareStack = []
@plainObjectsDeepEq: (a, b) ->
try
_plainObjectsDeepEq a, b
finally
compareStack = []
_plainObjectsDeepEq = (a, b) =>
if a == b
true
else if (a in compareStack) || (b in compareStack)
false
else if a && b && a.constructor == _constructor = b.constructor
if a.eq || (_isA = _constructor == Array) || (_isB = _constructor == Object)
try
compareStack.push a
compareStack.push b
switch
when _isA then @plainObjectsDeepEqArray a, b
when _isB then @plainObjectsDeepEqObject a, b
else a.eq b
finally
compareStack.pop()
compareStack.pop()
else false
else
false
# alias - ArtReact, ArtFlux and ArtEry were all making this local alias
# This standardizes the alias.
# 2016-09-27 SBd
@propsEq: @plainObjectsDeepEq
@plainObjectsDeepDiffArray: (before, after) =>
res = null
len = min before.length, after.length
for i in [0...len] when diff = plainObjectsDeepDiff before[i], after[i]
res ||= {}
res[i] = diff
if len < before.length
for i in [len...before.length]
res ||= {}
res[i] = removed: before[i]
if len < after.length
for i in [len...after.length]
res ||= {}
res[i] = added: after[i]
res
@plainObjectsDeepDiffObject: (before, after) =>
res = null
for k, beforeV of before
if after.hasOwnProperty k
if diff = plainObjectsDeepDiff beforeV, after[k]
res ||= {}
res[k] = diff
else
res ||= {}
res[k] = removed: beforeV
for k, afterV of after when !before.hasOwnProperty k
res ||= {}
res[k] = added: afterV
res
@plainObjectsDeepDiff: plainObjectsDeepDiff = (before, after) =>
if before == after
null
else if before && after && before.constructor == _constructor = after.constructor
if before.eq
if before.eq after
null
else
before: before
after: after
else if _constructor == Array then @plainObjectsDeepDiffArray before, after
else if _constructor == Object then @plainObjectsDeepDiffObject before, after
else
before: before
after: after
else
before: before
after: after
# alias - it's so often the right diff, I'm trying it out as just "diff"
# 2016-09-27 SBD
@diff: plainObjectsDeepDiff
# test with == and, failing that, use a's eq
@shallowEq: (a, b) => a == b || (a && b && a.eq && a.eq b)