Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(354)

Side by Side Diff: pkg/mdv/lib/src/list_diff.dart

Issue 19492018: [mdv] Avoid observing placeholder arrays in TemplateIterator (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file
2 // for details. All rights reserved. Use of this source code is governed by a
3 // BSD-style license that can be found in the LICENSE file.
4
5 library mdv.src.list_diff;
6
7 import 'dart:math' as math;
8 import 'package:observe/observe.dart' show ListChangeRecord;
9
10 /**
11 * A summary of an individual change to a [List].
12 *
13 * Each delta represents that at the [index], [removed] sequence of items were
14 * removed, and counting forward from [index], [addedCount] items were added.
15 *
16 * See also: [summarizeListChanges].
17 */
18 class ListChangeDelta implements ListChangeRecord {
19 /** The index of the change. */
20 final int index;
21
22 List _removed;
23
24 // Note: conceptually final, but for convenience we increment it as we build
25 // the object. It will be "frozen" by the time it is returned the the user.
26 int _addedCount = 0;
27
28 ListChangeDelta(this.index, {List removed, int addedCount: 0})
29 : _removed = removed != null ? removed : [],
30 _addedCount = addedCount;
31
32 // TODO(jmesserly): freeze remove list before handing it out?
33 /** The items removed, if any. Otherwise this will be an empty list. */
34 List get removed => _removed;
35
36 /** The number of items added. */
37 int get addedCount => _addedCount;
38
39 int get removedCount => _removed.length;
40
41 String toString() => '#<$runtimeType index: $index, '
42 'removed: $removed, addedCount: $addedCount>';
43 }
44
45 // Note: This function is *based* on the computation of the Levenshtein
46 // "edit" distance. The one change is that "updates" are treated as two
47 // edits - not one. With List splices, an update is really a delete
48 // followed by an add. By retaining this, we optimize for "keeping" the
49 // maximum array items in the original array. For example:
50 //
51 // 'xxxx123' -> '123yyyy'
52 //
53 // With 1-edit updates, the shortest path would be just to update all seven
54 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
55 // leaves the substring '123' intact.
56 List<List<int>> _calcEditDistances(List current, int currentStart,
57 int currentEnd, List old, int oldStart, int oldEnd) {
58 // "Deletion" columns
59 var rowCount = oldEnd - oldStart + 1;
60 var columnCount = currentEnd - currentStart + 1;
61 var distances = new List(rowCount);
62
63 // "Addition" rows. Initialize null column.
64 for (var i = 0; i < rowCount; i++) {
65 distances[i] = new List(columnCount);
66 distances[i][0] = i;
67 }
68
69 // Initialize null row
70 for (var j = 0; j < columnCount; j++) {
71 distances[0][j] = j;
72 }
73
74 for (var i = 1; i < rowCount; i++) {
75 for (var j = 1; j < columnCount; j++) {
76 if (identical(old[oldStart + i - 1], current[currentStart + j - 1])) {
77 distances[i][j] = distances[i - 1][j - 1];
78 } else {
79 var north = distances[i - 1][j] + 1;
80 var west = distances[i][j - 1] + 1;
81 distances[i][j] = math.min(north, west);
82 }
83 }
84 }
85
86 return distances;
87 }
88
89 const _EDIT_LEAVE = 0;
90 const _EDIT_UPDATE = 1;
91 const _EDIT_ADD = 2;
92 const _EDIT_DELETE = 3;
93
94 // This starts at the final weight, and walks "backward" by finding
95 // the minimum previous weight recursively until the origin of the weight
96 // matrix.
97 List<int> _spliceOperationsFromEditDistances(List<List<int>> distances) {
98 var i = distances.length - 1;
99 var j = distances[0].length - 1;
100 var current = distances[i][j];
101 var edits = [];
102 while (i > 0 || j > 0) {
103 if (i == 0) {
104 edits.add(_EDIT_ADD);
105 j--;
106 continue;
107 }
108 if (j == 0) {
109 edits.add(_EDIT_DELETE);
110 i--;
111 continue;
112 }
113 var northWest = distances[i - 1][j - 1];
114 var west = distances[i - 1][j];
115 var north = distances[i][j - 1];
116
117 var min = math.min(math.min(west, north), northWest);
118
119 if (min == northWest) {
120 if (northWest == current) {
121 edits.add(_EDIT_LEAVE);
122 } else {
123 edits.add(_EDIT_UPDATE);
124 current = northWest;
125 }
126 i--;
127 j--;
128 } else if (min == west) {
129 edits.add(_EDIT_DELETE);
130 i--;
131 current = west;
132 } else {
133 edits.add(_EDIT_ADD);
134 j--;
135 current = north;
136 }
137 }
138
139 return edits.reversed.toList();
140 }
141
142 int _sharedPrefix(List arr1, List arr2, int searchLength) {
143 for (var i = 0; i < searchLength; i++) {
144 if (!identical(arr1[i], arr2[i])) {
145 return i;
146 }
147 }
148 return searchLength;
149 }
150
151 int _sharedSuffix(List arr1, List arr2, int searchLength) {
152 var index1 = arr1.length;
153 var index2 = arr2.length;
154 var count = 0;
155 while (count < searchLength && identical(arr1[--index1], arr2[--index2])) {
156 count++;
157 }
158 return count;
159 }
160
161 /**
162 * Lacking individual splice mutation information, the minimal set of
163 * splices can be synthesized given the previous state and final state of an
164 * array. The basic approach is to calculate the edit distance matrix and
165 * choose the shortest path through it.
166 *
167 * Complexity: O(l * p)
168 * l: The length of the current array
169 * p: The length of the old array
170 */
171 List<ListChangeDelta> calculateSplices(List current, List previous) =>
172 _calcSplices(current, 0, current.length, previous, 0, previous.length);
173
174 List<ListChangeDelta> _calcSplices(List current, int currentStart,
175 int currentEnd, List old, int oldStart, int oldEnd) {
176
177 var prefixCount = 0;
178 var suffixCount = 0;
179
180 var minLength = math.min(currentEnd - currentStart, oldEnd - oldStart);
181 if (currentStart == 0 && oldStart == 0) {
182 prefixCount = _sharedPrefix(current, old, minLength);
183 }
184
185 if (currentEnd == current.length && oldEnd == old.length) {
186 suffixCount = _sharedSuffix(current, old, minLength - prefixCount);
187 }
188
189 currentStart += prefixCount;
190 oldStart += prefixCount;
191 currentEnd -= suffixCount;
192 oldEnd -= suffixCount;
193
194 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) {
195 return const [];
196 }
197
198 if (currentStart == currentEnd) {
199 var splice = new ListChangeDelta(currentStart);
200 while (oldStart < oldEnd) {
201 splice.removed.add(old[oldStart++]);
202 }
203
204 return [splice ];
205 } else if (oldStart == oldEnd)
206 return [new ListChangeDelta(currentStart,
207 addedCount: currentEnd - currentStart)];
208
209 var ops = _spliceOperationsFromEditDistances(
210 _calcEditDistances(current, currentStart, currentEnd, old, oldStart,
211 oldEnd));
212
213 ListChangeDelta splice = null;
214 var splices = <ListChangeDelta>[];
215 var index = currentStart;
216 var oldIndex = oldStart;
217 for (var i = 0; i < ops.length; i++) {
218 switch(ops[i]) {
219 case _EDIT_LEAVE:
220 if (splice != null) {
221 splices.add(splice);
222 splice = null;
223 }
224
225 index++;
226 oldIndex++;
227 break;
228 case _EDIT_UPDATE:
229 if (splice == null) splice = new ListChangeDelta(index);
230
231 splice._addedCount++;
232 index++;
233
234 splice.removed.add(old[oldIndex]);
235 oldIndex++;
236 break;
237 case _EDIT_ADD:
238 if (splice == null) splice = new ListChangeDelta(index);
239
240 splice._addedCount++;
241 index++;
242 break;
243 case _EDIT_DELETE:
244 if (splice == null) splice = new ListChangeDelta(index);
245
246 splice.removed.add(old[oldIndex]);
247 oldIndex++;
248 break;
249 }
250 }
251
252 if (splice != null) {
253 splices.add(splice);
254 }
255 return splices;
256 }
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698