Skip to content

Commit 195cba6

Browse files
committed
better support for array differences
1 parent 2e0063f commit 195cba6

File tree

2 files changed

+154
-0
lines changed

2 files changed

+154
-0
lines changed

src/duplex.ts

Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* MIT license
55
*/
66
import { _deepClone, _objectKeys, escapePathComponent, hasOwnProperty } from './helpers.js';
7+
import lodashDifference from './lodash-difference.js'
78
import { applyPatch, Operation } from './core.js';
89

910
export interface Observer<T> {
@@ -139,6 +140,81 @@ export function generate<T>(observer: Observer<Object>, invertible = false): Ope
139140
return temp;
140141
}
141142

143+
/*
144+
* 'compareArrays' doesnt do a good job with ordering,
145+
* if bad ordering was detected, or bad job, return false, and revert
146+
* to old code. its most likely that there isnt much added value anyway
147+
*
148+
*/
149+
150+
function testOrder(arr1, arr2, patches) {
151+
// we dont want to mess up with arr1
152+
// the patches are just remove / add so need to clone deep
153+
let clonedArr1 = arr1.map(e => e);
154+
155+
applyPatch(clonedArr1, patches);
156+
if (clonedArr1.length !== arr2.length) {
157+
return false;
158+
}
159+
for (let index = 0; index < arr2.length; index++) {
160+
if (clonedArr1[index] !== arr2[index]) {
161+
return false;
162+
}
163+
}
164+
return true;
165+
166+
}
167+
168+
/*
169+
* return array efficient array patches when possible.
170+
* in frequenct cases of arrays additions or removals, where an element was removed, or added.
171+
* and thats the only difference between the arrays, and all other elements are the exact same (===)
172+
* then the code bellow can do a great job and having a very small number of patches.
173+
* in some cases it will revert back to the old behaviour.
174+
*
175+
*/
176+
177+
function compareArrays(arr1, arr2, path, invertible) {
178+
let diff = lodashDifference(arr1, arr2);
179+
if (diff.length === arr1.length) {
180+
// this means that the the arrays are completly different
181+
// and there is no added value in this function - revert to old behaviour
182+
return [];
183+
}
184+
let removePatches = [];
185+
diff.forEach(value => {
186+
const index = arr1.indexOf(value);
187+
188+
const op = 'remove';
189+
removePatches.push({
190+
op,
191+
path: "/" + index
192+
})
193+
if (invertible) {
194+
removePatches.push({
195+
op: 'test',
196+
path: "/" + index,
197+
value
198+
});
199+
}
200+
});
201+
diff = lodashDifference(arr2, arr1);
202+
const addPatches = diff.map(value => {
203+
const index = arr2.indexOf(value);
204+
const op = 'add';
205+
return {
206+
op,
207+
value,
208+
path: "/" + index
209+
}
210+
});
211+
const finalPatches = removePatches.reverse().concat(addPatches);
212+
if (testOrder(arr1, arr2, finalPatches)) {
213+
return finalPatches.map(p => ({ ...p, path: path + p.path }));
214+
}
215+
return []
216+
}
217+
142218
// Dirty check if obj is different from mirror, generate patches and update mirror
143219
function _generate(mirror, obj, patches, path, invertible) {
144220
if (obj === mirror) {
@@ -154,6 +230,13 @@ function _generate(mirror, obj, patches, path, invertible) {
154230
var changed = false;
155231
var deleted = false;
156232

233+
if(Array.isArray(mirror) && Array.isArray(obj)) {
234+
const newPatches = compareArrays(mirror, obj, path, invertible)
235+
if (newPatches.length) {
236+
return newPatches.forEach(p => patches.push(p));
237+
}
238+
}
239+
157240
//if ever "move" operation is implemented here, make sure this test runs OK: "should not generate the same patch twice (move)"
158241

159242
for (var t = oldKeys.length - 1; t >= 0; t--) {

src/lodash-difference.ts

Lines changed: 71 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
2+
function baseIsNaN(value) {
3+
return value !== value
4+
}
5+
6+
function strictIndexOf(array, value, fromIndex) {
7+
let index = fromIndex - 1
8+
const { length } = array
9+
10+
while (++index < length) {
11+
if (array[index] === value) {
12+
return index
13+
}
14+
}
15+
return -1
16+
}
17+
18+
function baseFindIndex(array, predicate, fromIndex, fromRight) {
19+
const { length } = array
20+
let index = fromIndex + (fromRight ? 1 : -1)
21+
22+
while ((fromRight ? index-- : ++index < length)) {
23+
if (predicate(array[index], index, array)) {
24+
return index
25+
}
26+
}
27+
return -1
28+
}
29+
30+
function baseIndexOf(array, value, fromIndex) {
31+
return value === value
32+
? strictIndexOf(array, value, fromIndex)
33+
: baseFindIndex(array, baseIsNaN, fromIndex, false)
34+
}
35+
36+
function arrayIncludes(array, value) {
37+
const length = array == null ? 0 : array.length
38+
return !!length && baseIndexOf(array, value, 0) > -1
39+
}
40+
41+
function lodashDifference(array, values ) {
42+
let includes = arrayIncludes
43+
let isCommon = true
44+
const result = []
45+
const valuesLength = values.length
46+
47+
if (!array.length) {
48+
return result
49+
}
50+
outer:
51+
for (let value of array) {
52+
const computed = value
53+
54+
value = ( value !== 0) ? value : 0
55+
if (isCommon && computed === computed) {
56+
let valuesIndex = valuesLength
57+
while (valuesIndex--) {
58+
if (values[valuesIndex] === computed) {
59+
continue outer
60+
}
61+
}
62+
result.push(value)
63+
}
64+
else if (!includes(values, computed)) {
65+
result.push(value)
66+
}
67+
}
68+
return result
69+
}
70+
71+
export default lodashDifference;

0 commit comments

Comments
 (0)