improv-salet/filters.coffee
2017-11-10 16:11:35 +07:00

196 lines
5.4 KiB
CoffeeScript

###
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