return editPath(costMatrix(source, target, ins, del, sub), target);
}
- function patch(diff, source) {
- /** Apply a list of edits to source */
- var edit;
- var i;
+ function patchArray(diff, source) {
+ for (var i = 0; i < diff.length; ++i) {
+ var edit = diff[i];
+ source.splice(edit[1], edit[0], edit[2]);
+ }
+ return source;
+ }
- if (Array.isArray(source)) {
- for (i = 0; i < diff.length; ++i) {
- edit = diff[i];
- source.splice(edit[1], edit[0], edit[2]);
- }
- } else {
- for (i = 0; i < diff.length; ++i) {
- edit = diff[i];
- var head = source.slice(0, edit[1]);
- var tail = source.slice(edit[1] + edit[0]);
- source = head + edit[2] + tail;
- }
+ function patchString(diff, source) {
+ for (var i = 0; i < diff.length; ++i) {
+ var edit = diff[i];
+ var head = source.slice(0, edit[1]);
+ var tail = source.slice(edit[1] + edit[0]);
+ source = head + edit[2] + tail;
}
return source;
}
+ function patch(diff, source) {
+ /** Apply a list of edits to source */
+ var patcher = Array.isArray(source) ? patchArray : patchString;
+ return patcher(diff, source);
+ }
+
var MULTI = /[\uD800-\uDBFF][\uDC00-\uDFFF]|[\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]/;
var GLYPH = /([\0-\u02FF\u0370-\u1DBF\u1E00-\u20CF\u2100-\uD7FF\uDC00-\uFE1F\uFE30-\uFFFF]|[\uD800-\uDBFF][\uDC00-\uDFFF]|[\uD800-\uDBFF])([\u0300-\u036F\u1DC0-\u1DFF\u20D0-\u20FF\uFE20-\uFE2F]*)/g;
// This split is not perfect for all languages, but at least
// it won't create invalid surrogate pairs or orphaned
// combining characters.
+ //
+ // TODO: The way this is called is a hack.
if (source.match && (source.match(MULTI) || target.match(MULTI))) {
var sourceGlyphs = source.match(GLYPH) || [];
var targetGlyphs = target.match(GLYPH) || [];
return targetParts.join("");
}
- function fastLerp(a, b, p) {
+ function fastLerpAstral(source, target, amount) {
+ var sourceGlyphs = source.match(GLYPH) || [];
+ var targetGlyphs = target.match(GLYPH) || [];
+ var sourceLength = Math.round(sourceGlyphs.length * amount);
+ var targetLength = Math.round(targetGlyphs.length * amount);
+ var head = targetGlyphs.slice(0, targetLength);
+ var tail = sourceGlyphs.slice(sourceLength, sourceGlyphs.length);
+ head.push.apply(head, tail);
+ return head.join("");
+ }
+
+ function fastLerpBasic(source, target, amount) {
+ var sourceLength = Math.round(source.length * amount);
+ var targetLength = Math.round(target.length * amount);
+ var head = target.substring(0, targetLength);
+ var tail = source.substring(sourceLength, source.length);
+ return head + tail;
+ }
+
+ function fastLerp(source, target, amount) {
/** Interpolate between two strings based on length
This interpolation algorithm progressively replaces the
// strings, e.g. in the megabyte range. These are large enough
// that it should be fine to just pick a codepoint and search
// for the nearest glyph start.
- if (a.match(MULTI) || b.match(MULTI)) {
- var ca = a.match(GLYPH) || [];
- var cb = b.match(GLYPH) || [];
- var calen = Math.round(ca.length * p);
- var cblen = Math.round(cb.length * p);
- var r = cb.slice(0, cblen);
- r.push.apply(r, ca.slice(calen, ca.length));
- return r.join("");
- } else {
- var alen = Math.round(a.length * p);
- var blen = Math.round(b.length * p);
- return b.substring(0, blen) + a.substring(alen, a.length);
- }
+ if (source.match(MULTI) || target.match(MULTI))
+ return fastLerpAstral(source, target, amount);
+ else
+ return fastLerpBasic(source, target, amount);
}
- function lerp(a, b, p) {
+ function lerp(source, target, amount) {
/** Interpolate between two strings as best as possible
If the strings are identical aside from numbers in them,
Otherwise, they are passed through fastLerp.
*/
- a = a.toString();
- b = b.toString();
+ source = source.toString();
+ target = target.toString();
// Fast path for boundary cases.
- if (p === 0) return a;
- if (p === 1) return b;
+ if (amount === 0) return source;
+ if (amount === 1) return target;
- if (areNumericTwins(a, b))
- return numericLerp(a, b, p);
+ if (areNumericTwins(source, target))
+ return numericLerp(source, target, amount);
// Numeric lerps should over- and under-shoot when fed numbers
// outside 0 to 1, but other types cannot.
- if (p < 0) return a;
- if (p > 1) return b;
+ if (amount < 0) return source;
+ if (amount > 1) return target;
- var n = a.length * b.length;
- return ((n && n < MAX_MATRIX_SIZE) ? diffLerp : fastLerp)(a, b, p);
+ var n = source.length * target.length;
+ var appropriate = (n && n < MAX_MATRIX_SIZE) ? diffLerp : fastLerp;
+ return appropriate(source, target, amount);
}
exports.costMatrix = costMatrix;
assertEqual("Chapter 5. The sky was rgb(0, 0, 128).", lerp(A, B, 0.5));
}});
}});
+
+JS.Test.describe('fast lerp', function () { with (this) {
+ var lerp = m.lerp;
+ var A = "Do you like green eggs and ham?";
+ var B = "I do not like them, Sam-I-am.";
+
+ it("handles empty strings", function () { with (this) {
+ assertEqual("", lerp("", "", -1));
+ assertEqual("", lerp("", "", 0));
+ assertEqual("", lerp("", "", 0.5));
+ assertEqual("", lerp("", "", 1));
+ assertEqual("", lerp("", "", 2));
+ }});
+
+ it("maintains identity", function () { with (this) {
+ for (var i = -1; i < 2; i += 1/1024) {
+ assertEqual(A, lerp(A, A, i));
+ assertEqual(B, lerp(B, B, i));
+ }
+ }});
+
+ it("handles lows", function () { with (this) {
+ assertEqual(A, lerp(A, B, -Infinity));
+ assertEqual(A, lerp(A, B, -1));
+ assertEqual(A, lerp(A, B, 0));
+ }});
+
+ it("handles highs", function () { with (this) {
+ assertEqual(B, lerp(A, B, 1));
+ assertEqual(B, lerp(A, B, 2));
+ assertEqual(B, lerp(A, B, Infinity));
+ }});
+}});