UNPKG

needleman-js

Version:

Needleman-Wunsch algorithm in javascript

92 lines (80 loc) 2.65 kB
/** * Implementation of Needleman-Wunsch algorithm for searching words in a pool with or without errors. */ class NeedlemanSearch { /** * @param {object} config * @property {Number} config.indePenalty penalty for indentation unmatch * @property {Number} config.missPenalty penalty for matching miss * @property {Number} config.matchReward reward for value matching * @property {(String, String, Number, Number) => Number} config.scoringFn callback for calculating score * @property {(String) => Number} config.calcMinExpectedScore callback for calculating minimal score */ constructor ( { indePenalty, missPenalty, matchReward, scoringFn, calcMinExpectedScore } = { indePenalty: -1, missPenalty: -2, matchReward: 5, scoringFn: null, calcMinExpectedScore: null } ) { this.indePenalty = indePenalty || -1 this.missPenalty = missPenalty || -2 this.matchReward = matchReward || 5 this.calcMinExpectedScore = calcMinExpectedScore ? calcMinExpectedScore.bind(this) : this.defaultMinExpectedScore.bind(this) this.Score = scoringFn ? scoringFn.bind(this) : this.defaultScoring.bind(this) } defaultMinExpectedScore (needle) { return (needle.length * this.matchReward) / 2.0 } defaultScoring (needle, match, i, j) { return needle[i - 1] === match[j - 1] ? this.matchReward : this.missPenalty } search (needle, pool) { const minExpectedVal = this.calcMinExpectedScore(needle) const matches = pool.reduce((acc, str) => { const val = this.matchingScore(needle, str) if (val > minExpectedVal) { acc[val] = [...(acc[val] ? acc[val] : []), str] } return acc }, {}) return matches } matchingScore (needle, match) { const needleLower = needle.toLowerCase() const matchLower = match.toLowerCase() const M = Array.from( Array(needleLower.length + 1), () => new Array(matchLower.length + 1) ) for (let i = 0; i <= needleLower.length; i++) { M[i][0] = i * this.indePenalty } for (let i = 0; i <= matchLower.length; i++) { M[0][i] = i * this.indePenalty } for (let i = 1; i <= needleLower.length; i++) { for (let j = 1; j <= matchLower.length; j++) { M[i][j] = Math.max( M[i - 1][j - 1] + this.Score(needleLower, matchLower, i, j), // Match M[i][j - 1] + this.indePenalty, // Insert M[i - 1][j] + this.indePenalty // Delete ) } } return M[needleLower.length][matchLower.length] } } module.exports = NeedlemanSearch