Skip to content

Commit

Permalink
feat: Faster char() for ranges < 256
Browse files Browse the repository at this point in the history
For ranges < 256, we precompute the array of characters to allow for O(1) lookup instead of O(log n)
  • Loading branch information
justinvdm committed May 17, 2020
1 parent ca90403 commit ea745ea
Show file tree
Hide file tree
Showing 3 changed files with 68 additions and 22 deletions.
4 changes: 3 additions & 1 deletion char.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ var hash = require('./hash')
var fitRanges = require('./utils/fitRanges')
var fromCodePoint = require('./utils/fromCodePoint')

var VALUES_THRESHOLD = 256

var char = charInRanges([
// ascii
[0x61, 0x7a],
Expand Down Expand Up @@ -40,7 +42,7 @@ char.letter = charInRanges([char.asciiLetter, char.latin1Letter])

function charInRanges(ranges) {
ranges = normalizeRanges(ranges)
var fitFn = fitRanges(ranges)
var fitFn = fitRanges(ranges, VALUES_THRESHOLD)

charFn.__fictional_char = {
ranges: ranges
Expand Down
7 changes: 7 additions & 0 deletions utils/expandRange.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
module.exports = function (a, b) {
var results = []
var i = a - 1
var n = b + 1
while (++i < n) results.push(i)
return results
}
79 changes: 58 additions & 21 deletions utils/fitRanges.js
Original file line number Diff line number Diff line change
@@ -1,32 +1,43 @@
var hash = require('../hash')
var expandRange = require('./expandRange')

module.exports = function fitRanges(ranges) {
module.exports = function fitRanges(ranges, valuesThreshold) {
if (!ranges.length) {
throw new Error('Empty range set given to fitRanges()')
}

var meta = computeRangeMetadata(ranges.slice().sort(compareRanges))
var offsets = meta.offsets
var meta = computeRangeMetadata(
ranges.slice().sort(compareRanges),
valuesThreshold
)
var values = meta.values
var segments = meta.segments
var size = meta.size

fitRangesFn.meta = meta
return fitRangesFn
var fn = values
? fitRangesByValues(values)
: fitRangesByOffsets(segments, size)

function fitRangesFn(input) {
fn.meta = meta
return fn
}

function fitRangesByOffsets(segments, size) {
return function fitRangesByOffsetsFn(input) {
var key = hash([input, 'fitRanges']) % size

var l = 0
var r = offsets.length - 1
var r = segments.length - 1
var m
var d
var min
var max

while (l <= r) {
m = Math.floor((l + r) / 2)
d = offsets[m]
min = d.min
max = d.max
d = segments[m]
min = d.modMin
max = d.modMax

if (key > max) l = m + 1
else if (key < min) r = m - 1
Expand All @@ -37,6 +48,14 @@ module.exports = function fitRanges(ranges) {
}
}

function fitRangesByValues(values) {
var n = values.length

return function fitRangesByValuesFn(input) {
return values[hash([input, 'fitRanges']) % n]
}
}

function compareRanges(a, b) {
var aMin = a[0]
var bMin = b[0]
Expand All @@ -49,8 +68,8 @@ function compareRanges(a, b) {
return diff
}

function computeRangeMetadata(ranges) {
var offsets = []
function computeRangeMetadata(ranges, valuesThreshold) {
var segments = []
var n = ranges.length
var i = 0

Expand All @@ -71,9 +90,11 @@ function computeRangeMetadata(ranges) {
if (nextMin > currentMax + 1) {
modMax = modMin + (currentMax - currentMin)

offsets.push({
min: modMin,
max: modMax,
segments.push({
modMin: modMin,
modMax: modMax,
min: currentMin,
max: currentMax,
offset: currentMin - modMin
})

Expand All @@ -85,18 +106,34 @@ function computeRangeMetadata(ranges) {
currentMax = Math.max(currentMax, nextMax)
}

offsets.push({
ranges: ranges,
min: modMin,
max: modMin + (currentMax - currentMin),
segments.push({
modMin: modMin,
modMax: modMin + (currentMax - currentMin),
min: currentMin,
max: currentMax,
offset: currentMin - modMin
})

size += currentMax + 1 - currentMin

return {
ranges: ranges,
offsets: offsets,
size: size
segments: segments,
size: size,
values: size <= valuesThreshold ? computeRangeValues(segments) : null
}
}

function computeRangeValues(segments) {
var i = -1
var n = segments.length
var d
var results = []

while (++i < n) {
d = segments[i]
results.push.apply(results, expandRange(d.min, d.max))
}

return results
}

0 comments on commit ea745ea

Please sign in to comment.