You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
195 lines
5.4 KiB
195 lines
5.4 KiB
### |
|
The MIT License (MIT) |
|
|
|
Copyright (c) 2013-2016, Reactive Sets |
|
|
|
equals( a, b ) |
|
|
|
Returns true if a and b are deeply equal, false otherwise. |
|
|
|
Parameters: |
|
- a (Any type): value to compare to b |
|
- b (Any type): value compared to a |
|
|
|
Implementation: |
|
'a' is considered equal to 'b' if all scalar values in a and b are strictly equal as |
|
compared with operator '==' except for these two special cases: |
|
- 0 == -0 but are not equal. |
|
- NaN is not == to itself but is equal. |
|
|
|
RegExp objects are considered equal if they have the same lastIndex, i.e. both regular |
|
expressions have matched the same number of times. |
|
|
|
Functions must be identical, so that they have the same closure context. |
|
"undefined" is a valid value, including in Objects |
|
### |
|
equals = ( a, b ) -> |
|
return a == b && # strict equality should be enough unless zero |
|
a != 0 || # because 0 == -0, requires test by _equals() |
|
_equals( a, b ) # handles not strictly equal or zero values |
|
|
|
_equals = ( a, b ) -> |
|
# a and b have already failed test for strict equality or are zero |
|
# They should have the same toString() signature |
|
if ( ( s = toString.call( a ) ) != toString.call( b ) ) |
|
return false |
|
|
|
switch s |
|
when '[object Number]' |
|
# Converts Number instances into primitive values |
|
# This is required also for NaN test bellow |
|
a = +a |
|
b = +b |
|
|
|
if a |
|
return a == b |
|
else |
|
if a == a # a is 0 or -O |
|
return 1/a == 1/b # 1/0 != 1/-0 because Infinity != -Infinity |
|
return b != b # NaN, the only Number not equal to itself! |
|
when '[object RegExp]' |
|
return a.source == b.source && |
|
a.global == b.global && |
|
a.ignoreCase == b.ignoreCase && |
|
a.multiline == b.multiline && |
|
a.lastIndex == b.lastIndex |
|
when '[object Function]' |
|
return false # functions should be strictly equal because of closure context |
|
when '[object Array]' |
|
if ( ( l = a.length ) != b.length ) |
|
return false |
|
# Both have as many elements |
|
|
|
while ( l-- ) |
|
if ( ( x = a[ l ] ) == ( y = b[ l ] ) && x != 0 || _equals( x, y ) ) |
|
continue |
|
return false |
|
return true |
|
when '[object Object]' |
|
l = 0 # counter of own properties |
|
|
|
for p in a |
|
if ( a.hasOwnProperty( p ) ) |
|
++l |
|
|
|
if ( ( x = a[ p ] ) == ( y = b[ p ] ) && x != 0 || _equals( x, y ) ) |
|
continue |
|
|
|
return false |
|
|
|
# Check if 'b' has as not more own properties than 'a' |
|
for p in b |
|
if ( b.hasOwnProperty( p ) && --l < 0 ) |
|
return false |
|
|
|
return true |
|
else # Boolean, Date, String |
|
return a.valueOf() == b.valueOf() |
|
|
|
partial = (func) -> |
|
boundArgs = arguments #slice.call(arguments, 1) |
|
bound = () -> |
|
position = 0 |
|
length = boundArgs.length |
|
args = Array(length) |
|
for i in [0..length] |
|
args[i] = boundArgs[i] |
|
while (position < arguments.length) |
|
args.push(arguments[position++]); |
|
return executeBound(func, bound, this, this, args) |
|
return bound |
|
|
|
TAG_COMPARISON = |
|
TOTAL: 1 |
|
PARTIAL: 0 |
|
MISMATCH: -1 |
|
|
|
compareTags = (a, b) -> |
|
# Returns a TAG_COMPARISON value |
|
if (equals(a, b)) |
|
return TAG_COMPARISON.TOTAL |
|
# If the tags are unequal but have the same length, it stands to reason |
|
# there is a mismatch. |
|
if (a.length == b.length) |
|
return TAG_COMPARISON.MISMATCH |
|
if a < b |
|
[shorter, longer] = [a, b] |
|
else |
|
[shorter, longer] = [b, a] |
|
for x, index in shorter |
|
if x != longer[index] |
|
return TAG_COMPARISON.MISMATCH |
|
return TAG_COMPARISON.PARTIAL |
|
|
|
# Ensures that the group and model don't have any mismatched tags |
|
mismatchFilterSub = (group, model) -> |
|
if not group.tags? |
|
return 0 |
|
result = group.tags.find((groupTag) -> |
|
# Look for a mismatch. |
|
matched = model.tags.find (modelTag) => |
|
modelTag[0] == groupTag[0] |
|
if not matched? |
|
return false |
|
if compareTags(groupTag, matched) == TAG_COMPARISON.MISMATCH |
|
return true; |
|
return false |
|
) |
|
if not result? |
|
return 0 |
|
return null |
|
|
|
bonusCompare = (mode, bonus = 1, cumulative = false) -> |
|
return (group, model) -> |
|
results = group.tags.filter((groupTag) -> |
|
matched = model.tags.find((modelTag) => |
|
modelTag[0] == groupTag[0] |
|
) |
|
if not matched? |
|
return false |
|
if compareTags(groupTag, matched) == mode |
|
return true |
|
return false |
|
) |
|
if (results.length) |
|
if cumulative |
|
return bonus * results.length |
|
else |
|
return bonus |
|
return 0 |
|
|
|
filters = {} |
|
|
|
filters.mismatchFilter = () -> |
|
return mismatchFilterSub |
|
|
|
filters.partialBonus = partial(bonusCompare, TAG_COMPARISON.PARTIAL) |
|
|
|
filters.fullBonus = partial(bonusCompare, TAG_COMPARISON.TOTAL) |
|
|
|
filters.dryness = () -> |
|
return (group) -> |
|
that = this |
|
newPhrases = [] |
|
for phrase in group.phrases |
|
if (that.history.indexOf(phrase) == -1 && phrase != null) |
|
newPhrases.push(phrase) |
|
newGroup = Object.create(group) |
|
newGroup.phrases = newPhrases |
|
return [0, newGroup] |
|
|
|
filters.unmentioned = (bonus = 1) -> |
|
return (group) -> |
|
if (!Array.isArray(group.tags)) |
|
return 0 |
|
if (group.tags.length == 0) |
|
return 0 |
|
that = this |
|
for t in group.tags |
|
# Return true if the tag is "novel". |
|
for u in that.tagHistory |
|
if u[0] == t[0] |
|
return bonus |
|
return 0 |
|
|
|
Improv.filters = filters
|
|
|