Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve array patches #276

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
89 changes: 89 additions & 0 deletions commonjs/duplex.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,22 @@
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
Object.defineProperty(exports, "__esModule", { value: true });
/*!
* https://github.com/Starcounter-Jack/JSON-Patch
* (c) 2017 Joachim Wester
* MIT license
*/
var helpers_js_1 = require("./helpers.js");
var lodash_difference_js_1 = require("./lodash-difference.js");
var core_js_1 = require("./core.js");
var beforeDict = new WeakMap();
var Mirror = /** @class */ (function () {
Expand Down Expand Up @@ -113,6 +125,77 @@ function generate(observer, invertible) {
return temp;
}
exports.generate = generate;
/*
* 'compareArrays' doesnt do a good job with ordering,
* if bad ordering was detected, or bad job, return false, and revert
* to old code. its most likely that there isnt much added value anyway
*
*/
function testOrder(arr1, arr2, patches) {
// we dont want to mess up with arr1
// the patches are just remove / add so need to clone deep
var clonedArr1 = Array.from(arr1);
core_js_1.applyPatch(clonedArr1, patches);
if (clonedArr1.length !== arr2.length) {
return false;
}
for (var index = 0; index < arr2.length; index++) {
if (clonedArr1[index] !== arr2[index]) {
return false;
}
}
return true;
}
/*
* return array efficient array patches when possible.
* in frequenct cases of arrays additions or removals, where an element was removed, or added.
* and thats the only difference between the arrays, and all other elements are the exact same (===)
* then the code bellow can do a great job and having a very small number of patches.
* in some cases it will revert back to the old behaviour.
*
*/
function compareArrays(arr1, arr2, path, invertible) {
if (arr1.length === arr2.length) {
return [];
}
var diff = lodash_difference_js_1.default(arr1, arr2);
if (diff.length === arr1.length) {
// this means that the the arrays are completly different
// and there is no added value in this function - revert to old behaviour
return [];
}
var removePatches = [];
diff.forEach(function (value) {
var index = arr1.indexOf(value);
var op = 'remove';
removePatches.push({
op: op,
path: "/" + index
});
if (invertible) {
removePatches.push({
op: 'test',
path: "/" + index,
value: value
});
}
});
diff = lodash_difference_js_1.default(arr2, arr1);
var addPatches = diff.map(function (value) {
var index = arr2.indexOf(value);
var op = 'add';
return {
op: op,
value: value,
path: "/" + index
};
});
var finalPatches = removePatches.reverse().concat(addPatches);
if (testOrder(arr1, arr2, finalPatches)) {
return finalPatches.map(function (p) { return (__assign({}, p, { path: path + p.path })); });
}
return [];
}
// Dirty check if obj is different from mirror, generate patches and update mirror
function _generate(mirror, obj, patches, path, invertible) {
if (obj === mirror) {
Expand All @@ -125,6 +208,12 @@ function _generate(mirror, obj, patches, path, invertible) {
var oldKeys = helpers_js_1._objectKeys(mirror);
var changed = false;
var deleted = false;
if (Array.isArray(mirror) && Array.isArray(obj)) {
var newPatches = compareArrays(mirror, obj, path, invertible);
if (newPatches.length) {
return newPatches.forEach(function (p) { return patches.push(p); });
}
}
//if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)"
for (var t = oldKeys.length - 1; t >= 0; t--) {
var key = oldKeys[t];
Expand Down
61 changes: 61 additions & 0 deletions commonjs/lodash-difference.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
Object.defineProperty(exports, "__esModule", { value: true });
function baseIsNaN(value) {
return value !== value;
}
function strictIndexOf(array, value, fromIndex) {
var index = fromIndex - 1;
var length = array.length;
while (++index < length) {
if (array[index] === value) {
return index;
}
}
return -1;
}
function baseFindIndex(array, predicate, fromIndex, fromRight) {
var length = array.length;
var index = fromIndex + (fromRight ? 1 : -1);
while ((fromRight ? index-- : ++index < length)) {
if (predicate(array[index], index, array)) {
return index;
}
}
return -1;
}
function baseIndexOf(array, value, fromIndex) {
return value === value
? strictIndexOf(array, value, fromIndex)
: baseFindIndex(array, baseIsNaN, fromIndex, false);
}
function arrayIncludes(array, value) {
var length = array == null ? 0 : array.length;
return !!length && baseIndexOf(array, value, 0) > -1;
}
function lodashDifference(array, values) {
var includes = arrayIncludes;
var isCommon = true;
var result = [];
var valuesLength = values.length;
if (!array.length) {
return result;
}
outer: for (var _i = 0, array_1 = array; _i < array_1.length; _i++) {
var value = array_1[_i];
var computed = value;
value = (value !== 0) ? value : 0;
if (isCommon && computed === computed) {
var valuesIndex = valuesLength;
while (valuesIndex--) {
if (values[valuesIndex] === computed) {
continue outer;
}
}
result.push(value);
}
else if (!includes(values, computed)) {
result.push(value);
}
}
return result;
}
exports.default = lodashDifference;