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

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

Issue 53743002: introduce ObservableList.listChanges to keep list changes separate from property changes (Closed) Base URL: https://dart.googlecode.com/svn/branches/bleeding_edge/dart
Patch Set: Created 7 years, 1 month 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 template_binding.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 /** Returns true if the provided index was changed by this operation. */
42 bool indexChanged(key) {
43 // If key isn't an int, or before the index, then it wasn't changed.
44 if (key is! int || key < index) return false;
45
46 // If this was a shift operation, anything after index is changed.
47 if (addedCount != removedCount) return true;
48
49 // Otherwise, anything in the update range was changed.
50 return key < index + addedCount;
51 }
52
53 String toString() => '#<$runtimeType index: $index, '
54 'removed: $removed, addedCount: $addedCount>';
55 }
56
57 // Note: This function is *based* on the computation of the Levenshtein
58 // "edit" distance. The one change is that "updates" are treated as two
59 // edits - not one. With List splices, an update is really a delete
60 // followed by an add. By retaining this, we optimize for "keeping" the
61 // maximum array items in the original array. For example:
62 //
63 // 'xxxx123' -> '123yyyy'
64 //
65 // With 1-edit updates, the shortest path would be just to update all seven
66 // characters. With 2-edit updates, we delete 4, leave 3, and add 4. This
67 // leaves the substring '123' intact.
68 List<List<int>> _calcEditDistances(List current, int currentStart,
69 int currentEnd, List old, int oldStart, int oldEnd) {
70 // "Deletion" columns
71 var rowCount = oldEnd - oldStart + 1;
72 var columnCount = currentEnd - currentStart + 1;
73 var distances = new List(rowCount);
74
75 // "Addition" rows. Initialize null column.
76 for (var i = 0; i < rowCount; i++) {
77 distances[i] = new List(columnCount);
78 distances[i][0] = i;
79 }
80
81 // Initialize null row
82 for (var j = 0; j < columnCount; j++) {
83 distances[0][j] = j;
84 }
85
86 for (var i = 1; i < rowCount; i++) {
87 for (var j = 1; j < columnCount; j++) {
88 if (identical(old[oldStart + i - 1], current[currentStart + j - 1])) {
89 distances[i][j] = distances[i - 1][j - 1];
90 } else {
91 var north = distances[i - 1][j] + 1;
92 var west = distances[i][j - 1] + 1;
93 distances[i][j] = math.min(north, west);
94 }
95 }
96 }
97
98 return distances;
99 }
100
101 const _EDIT_LEAVE = 0;
102 const _EDIT_UPDATE = 1;
103 const _EDIT_ADD = 2;
104 const _EDIT_DELETE = 3;
105
106 // This starts at the final weight, and walks "backward" by finding
107 // the minimum previous weight recursively until the origin of the weight
108 // matrix.
109 List<int> _spliceOperationsFromEditDistances(List<List<int>> distances) {
110 var i = distances.length - 1;
111 var j = distances[0].length - 1;
112 var current = distances[i][j];
113 var edits = [];
114 while (i > 0 || j > 0) {
115 if (i == 0) {
116 edits.add(_EDIT_ADD);
117 j--;
118 continue;
119 }
120 if (j == 0) {
121 edits.add(_EDIT_DELETE);
122 i--;
123 continue;
124 }
125 var northWest = distances[i - 1][j - 1];
126 var west = distances[i - 1][j];
127 var north = distances[i][j - 1];
128
129 var min = math.min(math.min(west, north), northWest);
130
131 if (min == northWest) {
132 if (northWest == current) {
133 edits.add(_EDIT_LEAVE);
134 } else {
135 edits.add(_EDIT_UPDATE);
136 current = northWest;
137 }
138 i--;
139 j--;
140 } else if (min == west) {
141 edits.add(_EDIT_DELETE);
142 i--;
143 current = west;
144 } else {
145 edits.add(_EDIT_ADD);
146 j--;
147 current = north;
148 }
149 }
150
151 return edits.reversed.toList();
152 }
153
154 int _sharedPrefix(List arr1, List arr2, int searchLength) {
155 for (var i = 0; i < searchLength; i++) {
156 if (!identical(arr1[i], arr2[i])) {
157 return i;
158 }
159 }
160 return searchLength;
161 }
162
163 int _sharedSuffix(List arr1, List arr2, int searchLength) {
164 var index1 = arr1.length;
165 var index2 = arr2.length;
166 var count = 0;
167 while (count < searchLength && identical(arr1[--index1], arr2[--index2])) {
168 count++;
169 }
170 return count;
171 }
172
173 /**
174 * Lacking individual splice mutation information, the minimal set of
175 * splices can be synthesized given the previous state and final state of an
176 * array. The basic approach is to calculate the edit distance matrix and
177 * choose the shortest path through it.
178 *
179 * Complexity: O(l * p)
180 * l: The length of the current array
181 * p: The length of the old array
182 */
183 List<ListChangeDelta> calculateSplices(List current, List previous) =>
184 _calcSplices(current, 0, current.length, previous, 0, previous.length);
185
186 List<ListChangeDelta> _calcSplices(List current, int currentStart,
187 int currentEnd, List old, int oldStart, int oldEnd) {
188
189 var prefixCount = 0;
190 var suffixCount = 0;
191
192 var minLength = math.min(currentEnd - currentStart, oldEnd - oldStart);
193 if (currentStart == 0 && oldStart == 0) {
194 prefixCount = _sharedPrefix(current, old, minLength);
195 }
196
197 if (currentEnd == current.length && oldEnd == old.length) {
198 suffixCount = _sharedSuffix(current, old, minLength - prefixCount);
199 }
200
201 currentStart += prefixCount;
202 oldStart += prefixCount;
203 currentEnd -= suffixCount;
204 oldEnd -= suffixCount;
205
206 if (currentEnd - currentStart == 0 && oldEnd - oldStart == 0) {
207 return const [];
208 }
209
210 if (currentStart == currentEnd) {
211 var splice = new ListChangeDelta(currentStart);
212 while (oldStart < oldEnd) {
213 splice.removed.add(old[oldStart++]);
214 }
215
216 return [splice ];
217 } else if (oldStart == oldEnd)
218 return [new ListChangeDelta(currentStart,
219 addedCount: currentEnd - currentStart)];
220
221 var ops = _spliceOperationsFromEditDistances(
222 _calcEditDistances(current, currentStart, currentEnd, old, oldStart,
223 oldEnd));
224
225 ListChangeDelta splice = null;
226 var splices = <ListChangeDelta>[];
227 var index = currentStart;
228 var oldIndex = oldStart;
229 for (var i = 0; i < ops.length; i++) {
230 switch(ops[i]) {
231 case _EDIT_LEAVE:
232 if (splice != null) {
233 splices.add(splice);
234 splice = null;
235 }
236
237 index++;
238 oldIndex++;
239 break;
240 case _EDIT_UPDATE:
241 if (splice == null) splice = new ListChangeDelta(index);
242
243 splice._addedCount++;
244 index++;
245
246 splice.removed.add(old[oldIndex]);
247 oldIndex++;
248 break;
249 case _EDIT_ADD:
250 if (splice == null) splice = new ListChangeDelta(index);
251
252 splice._addedCount++;
253 index++;
254 break;
255 case _EDIT_DELETE:
256 if (splice == null) splice = new ListChangeDelta(index);
257
258 splice.removed.add(old[oldIndex]);
259 oldIndex++;
260 break;
261 }
262 }
263
264 if (splice != null) {
265 splices.add(splice);
266 }
267 return splices;
268 }
OLDNEW
« no previous file with comments | « pkg/polymer_expressions/lib/eval.dart ('k') | pkg/template_binding/lib/src/template_iterator.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698