react-native-web-headroom
Version:
React Native Web Headroom
151 lines (122 loc) • 4.3 kB
JavaScript
const bitapScore = require('./bitap_score')
const matchedIndices = require('./bitap_matched_indices')
module.exports = (text, pattern, patternAlphabet, { location = 0, distance = 100, threshold = 0.6, findAllMatches = false, minMatchCharLength = 1 }) => {
const expectedLocation = location
// Set starting location at beginning text and initialize the alphabet.
const textLen = text.length
// Highest score beyond which we give up.
let currentThreshold = threshold
// Is there a nearby exact match? (speedup)
let bestLocation = text.indexOf(pattern, expectedLocation)
const patternLen = pattern.length
// a mask of the matches
const matchMask = []
for (let i = 0; i < textLen; i += 1) {
matchMask[i] = 0
}
if (bestLocation !== -1) {
let score = bitapScore(pattern, {
errors: 0,
currentLocation: bestLocation,
expectedLocation,
distance
})
currentThreshold = Math.min(score, currentThreshold)
// What about in the other direction? (speed up)
bestLocation = text.lastIndexOf(pattern, expectedLocation + patternLen)
if (bestLocation !== -1) {
let score = bitapScore(pattern, {
errors: 0,
currentLocation: bestLocation,
expectedLocation,
distance
})
currentThreshold = Math.min(score, currentThreshold)
}
}
// Reset the best location
bestLocation = -1
let lastBitArr = []
let finalScore = 1
let binMax = patternLen + textLen
const mask = 1 << (patternLen - 1)
for (let i = 0; i < patternLen; i += 1) {
// Scan for the best match; each iteration allows for one more error.
// Run a binary search to determine how far from the match location we can stray
// at this error level.
let binMin = 0
let binMid = binMax
while (binMin < binMid) {
const score = bitapScore(pattern, {
errors: i,
currentLocation: expectedLocation + binMid,
expectedLocation,
distance
})
if (score <= currentThreshold) {
binMin = binMid
} else {
binMax = binMid
}
binMid = Math.floor((binMax - binMin) / 2 + binMin)
}
// Use the result from this iteration as the maximum for the next.
binMax = binMid
let start = Math.max(1, expectedLocation - binMid + 1)
let finish = findAllMatches ? textLen : Math.min(expectedLocation + binMid, textLen) + patternLen
// Initialize the bit array
let bitArr = Array(finish + 2)
bitArr[finish + 1] = (1 << i) - 1
for (let j = finish; j >= start; j -= 1) {
let currentLocation = j - 1
let charMatch = patternAlphabet[text.charAt(currentLocation)]
if (charMatch) {
matchMask[currentLocation] = 1
}
// First pass: exact match
bitArr[j] = ((bitArr[j + 1] << 1) | 1) & charMatch
// Subsequent passes: fuzzy match
if (i !== 0) {
bitArr[j] |= (((lastBitArr[j + 1] | lastBitArr[j]) << 1) | 1) | lastBitArr[j + 1]
}
if (bitArr[j] & mask) {
finalScore = bitapScore(pattern, {
errors: i,
currentLocation,
expectedLocation,
distance
})
// This match will almost certainly be better than any existing match.
// But check anyway.
if (finalScore <= currentThreshold) {
// Indeed it is
currentThreshold = finalScore
bestLocation = currentLocation
// Already passed `loc`, downhill from here on in.
if (bestLocation <= expectedLocation) {
break
}
// When passing `bestLocation`, don't exceed our current distance from `expectedLocation`.
start = Math.max(1, 2 * expectedLocation - bestLocation)
}
}
}
// No hope for a (better) match at greater error levels.
const score = bitapScore(pattern, {
errors: i + 1,
currentLocation: expectedLocation,
expectedLocation,
distance
})
if (score > currentThreshold) {
break
}
lastBitArr = bitArr
}
// Count exact matches (those with a score of 0) to be "almost" exact
return {
isMatch: bestLocation >= 0,
score: finalScore === 0 ? 0.001 : finalScore,
matchedIndices: matchedIndices(matchMask, minMatchCharLength)
}
}