interface ContentDifferentialComparisonResult { matching : Array; unmatched : Array; } class ContentDifferential { public nouns : Array = []; public score : number = 0; public constructor (...nouns : Array) { this.addNoun(...nouns); } public addNoun (...nouns : Array) { nouns.forEach(noun => { this.nouns.push(noun); }); this.score = this.getScore(); return this; } public getNouns () { return this.nouns; } public replaceNouns (...nouns : Array) { this.nouns = nouns; return this; } public isMatch (cd : ContentDifferential, allowPartial = false) { let check = this.getUnmatched(cd); if ((allowPartial || check.unmatched.length == 0) && check.matching.length == 0) { return true; } else if (check.matching.length == 0) { for (let i = 0; i < check.unmatched.length; i++) { if (!(check.unmatched[i] instanceof ContentMarker)) { return false; } else { if (( check.unmatched[i]).isImportant()) { return false; } } } return true; } return false; } public getUnmatched (cd : ContentDifferential) : ContentDifferentialComparisonResult { let unmatched = cd.nouns.slice(); let matching = this.nouns.slice(); for (let i = matching.length - 1; i >= 0; i--) { for (let k = unmatched.length - 1; k >= 0; k--) { if (ContentDifferential.compareNouns(matching[i], unmatched[k])) { unmatched.splice(k, 1); matching.splice(i, 1); break; } } } return { matching : matching, unmatched : unmatched }; } public getScore () { let highest = 0; let count = this.nouns.length; this.nouns.forEach((noun) => { let level = ContentDifferential.getNounLevel(noun); if (highest < level) { highest = level; } }); return highest + (count / 100); } public static getNounLevel (noun : Thing | typeof Thing | ContentDifferential | ContentMarker) { if (noun == undefined || noun == null) { return 0; } else if (typeof noun == "function") { if (noun.prototype instanceof Thing) { let specifity = 2; // Vague Thing let parentClass = Object.getPrototypeOf(noun); while (parentClass != Thing) { specifity += 0.1; parentClass = Object.getPrototypeOf(parentClass); } return specifity; } else { return 2.5; // It's not a "Thing", so it's probably a weird class, which is kind of specific } } else if (noun instanceof Thing) { return 4; // Specific thing } else if (noun instanceof ContentDifferential) { return 1; // Minor thing } else { return 0.5; } } public static compareNouns (a : Thing | typeof Thing | ContentDifferential | ContentMarker, b : Thing | typeof Thing | ContentDifferential | ContentMarker) { if (a == undefined || a == null) { return true; } if (typeof a == "function") { // b must inherit a or be a return b == a || b instanceof a || (typeof b == "function" && (b).prototype instanceof a) } else if (a instanceof Thing) { // b must be a return b == a; } return a === b; } public static isMatch (matchFrom : Array, matchAgainst : Array) { let unmatched = matchAgainst.slice(); let matching = matchFrom.slice(); for (let i = matching.length - 1; i >= 0; i--) { for (let k = unmatched.length - 1; k >= 0; k--) { if (matching[i].isMatch(unmatched[k])) { unmatched.splice(k, 1); matching.splice(i, 1); break; } } } if (unmatched.length == 0 && matching.length == 0) { return true; } else if (unmatched.length == 0) { // Check if the only thing missing are unimportant ContentMarkers for (let i = 0; i < matching.length; i++) { let nouns = matching[i].getNouns(); for (let k = 0; k < nouns.length; k++) { if (!(nouns[k] instanceof ContentMarker)) { return false; } else { if (( nouns[k]).isImportant()) { return false; } } } } // Nothing important found return true; } return false; } }