OLD | NEW |
| (Empty) |
1 // Copyright (c) 2015, 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 dart_style.src.line_splitting.solve_state_queue; | |
6 | |
7 import 'line_splitter.dart'; | |
8 import 'solve_state.dart'; | |
9 | |
10 /// A priority queue of [SolveStates] to consider while line splitting. | |
11 /// | |
12 /// This is based on the [HeapPriorityQueue] class from the "collection" | |
13 /// package, but is modified to handle the "overlap" logic that allows one | |
14 /// [SolveState] to supercede another. | |
15 /// | |
16 /// States are stored internally in a heap ordered by cost, the number of | |
17 /// overflow characters. When a new state is added to the heap, it will be | |
18 /// discarded, or a previously enqueued one will be discarded, if two overlap. | |
19 class SolveStateQueue { | |
20 /// Initial capacity of a queue when created, or when added to after a [clear]
. | |
21 /// Number can be any positive value. Picking a size that gives a whole | |
22 /// number of "tree levels" in the heap is only done for aesthetic reasons. | |
23 static const int _INITIAL_CAPACITY = 7; | |
24 | |
25 LineSplitter _splitter; | |
26 | |
27 /// List implementation of a heap. | |
28 List<SolveState> _queue = new List<SolveState>(_INITIAL_CAPACITY); | |
29 | |
30 /// Number of elements in queue. | |
31 /// The heap is implemented in the first [_length] entries of [_queue]. | |
32 int _length = 0; | |
33 | |
34 bool get isNotEmpty => _length != 0; | |
35 | |
36 void bindSplitter(LineSplitter splitter) { | |
37 // Only do this once. | |
38 assert(_splitter == null); | |
39 | |
40 _splitter = splitter; | |
41 } | |
42 | |
43 /// Add [state] to the queue. | |
44 /// | |
45 /// Grows the capacity if the backing list is full. | |
46 void add(SolveState state) { | |
47 if (_tryOverlap(state)) return; | |
48 | |
49 if (_length == _queue.length) { | |
50 var newCapacity = _queue.length * 2 + 1; | |
51 if (newCapacity < _INITIAL_CAPACITY) newCapacity = _INITIAL_CAPACITY; | |
52 | |
53 var newQueue = new List<SolveState>(newCapacity); | |
54 newQueue.setRange(0, _length, _queue); | |
55 _queue = newQueue; | |
56 } | |
57 | |
58 _bubbleUp(state, _length++); | |
59 } | |
60 | |
61 SolveState removeFirst() { | |
62 assert(_length > 0); | |
63 | |
64 // Remove the highest priority state. | |
65 var result = _queue[0]; | |
66 _length--; | |
67 | |
68 // Fill the gap with the one at the end of the list and re-heapify. | |
69 if (_length > 0) { | |
70 var last = _queue[_length]; | |
71 _queue[_length] = null; | |
72 _bubbleDown(last, 0); | |
73 } | |
74 | |
75 return result; | |
76 } | |
77 | |
78 /// Orders this state relative to [other]. | |
79 /// | |
80 /// This is a best-first ordering that prefers cheaper states even if they | |
81 /// overflow because this ensures it finds the best solution first as soon as | |
82 /// it finds one that fits in the page so it can early out. | |
83 int _compare(SolveState a, SolveState b) { | |
84 // TODO(rnystrom): It may be worth sorting by the estimated lowest number | |
85 // of overflow characters first. That doesn't help in cases where there is | |
86 // a solution that fits, but may help in corner cases where there is no | |
87 // fitting solution. | |
88 | |
89 var comparison = _compareScore(a, b); | |
90 if (comparison != 0) return comparison; | |
91 | |
92 return _compareRules(a, b); | |
93 } | |
94 | |
95 /// Compares the overflow and cost of [a] to [b]. | |
96 int _compareScore(SolveState a, SolveState b) { | |
97 if (a.splits.cost != b.splits.cost) { | |
98 return a.splits.cost.compareTo(b.splits.cost); | |
99 } | |
100 | |
101 return a.overflowChars.compareTo(b.overflowChars); | |
102 } | |
103 | |
104 /// Distinguish states based on the rule values just so that states with the | |
105 /// same cost range but different rule values don't get considered identical | |
106 /// and inadvertantly merged. | |
107 int _compareRules(SolveState a, SolveState b) { | |
108 for (var rule in _splitter.rules) { | |
109 var aValue = a.getValue(rule); | |
110 var bValue = b.getValue(rule); | |
111 | |
112 if (aValue != bValue) return aValue.compareTo(bValue); | |
113 } | |
114 | |
115 // The way SolveStates are expanded should guarantee that we never generate | |
116 // the exact same state twice. Getting here implies that that failed. | |
117 throw "unreachable"; | |
118 } | |
119 | |
120 /// Determines if any already enqueued state overlaps [state]. | |
121 /// | |
122 /// If so, chooses the best and discards the other. Returns `true` in this | |
123 /// case. Otherwise, returns `false`. | |
124 bool _tryOverlap(SolveState state) { | |
125 if (_length == 0) return false; | |
126 | |
127 // Count positions from one instead of zero. This gives the numbers some | |
128 // nice properties. For example, all right children are odd, their left | |
129 // sibling is even, and the parent is found by shifting right by one. | |
130 // Valid range for position is [1.._length], inclusive. | |
131 var position = 1; | |
132 | |
133 // Pre-order depth first search, omit child nodes if the current node has | |
134 // lower priority than [object], because all nodes lower in the heap will | |
135 // also have lower priority. | |
136 do { | |
137 var index = position - 1; | |
138 var enqueued = _queue[index]; | |
139 | |
140 var comparison = _compareScore(enqueued, state); | |
141 | |
142 if (comparison == 0) { | |
143 var overlap = enqueued.compareOverlap(state); | |
144 if (overlap < 0) { | |
145 // The old state is better, so just discard the new one. | |
146 return true; | |
147 } else if (overlap > 0) { | |
148 // The new state is better than the enqueued one, so replace it. | |
149 _queue[index] = state; | |
150 return true; | |
151 } else { | |
152 // We can't merge them, so sort by their bound rule values. | |
153 comparison = _compareRules(enqueued, state); | |
154 } | |
155 } | |
156 | |
157 if (comparison < 0) { | |
158 // Element may be in subtree. Continue with the left child, if any. | |
159 var leftChildPosition = position * 2; | |
160 if (leftChildPosition <= _length) { | |
161 position = leftChildPosition; | |
162 continue; | |
163 } | |
164 } | |
165 | |
166 // Find the next right sibling or right ancestor sibling. | |
167 do { | |
168 while (position.isOdd) { | |
169 // While position is a right child, go to the parent. | |
170 position >>= 1; | |
171 } | |
172 | |
173 // Then go to the right sibling of the left child. | |
174 position += 1; | |
175 } while (position > _length); // Happens if last element is a left child. | |
176 } while (position != 1); // At root again. Happens for right-most element. | |
177 | |
178 return false; | |
179 } | |
180 | |
181 /// Place [element] in heap at [index] or above. | |
182 /// | |
183 /// Put element into the empty cell at `index`. While the `element` has | |
184 /// higher priority than the parent, swap it with the parent. | |
185 void _bubbleUp(SolveState element, int index) { | |
186 while (index > 0) { | |
187 var parentIndex = (index - 1) ~/ 2; | |
188 var parent = _queue[parentIndex]; | |
189 | |
190 if (_compare(element, parent) > 0) break; | |
191 | |
192 _queue[index] = parent; | |
193 index = parentIndex; | |
194 } | |
195 | |
196 _queue[index] = element; | |
197 } | |
198 | |
199 /// Place [element] in heap at [index] or above. | |
200 /// | |
201 /// Put element into the empty cell at `index`. While the `element` has lower | |
202 /// priority than either child, swap it with the highest priority child. | |
203 void _bubbleDown(SolveState element, int index) { | |
204 var rightChildIndex = index * 2 + 2; | |
205 | |
206 while (rightChildIndex < _length) { | |
207 var leftChildIndex = rightChildIndex - 1; | |
208 var leftChild = _queue[leftChildIndex]; | |
209 var rightChild = _queue[rightChildIndex]; | |
210 | |
211 var comparison = _compare(leftChild, rightChild); | |
212 var minChildIndex; | |
213 var minChild; | |
214 | |
215 if (comparison < 0) { | |
216 minChild = leftChild; | |
217 minChildIndex = leftChildIndex; | |
218 } else { | |
219 minChild = rightChild; | |
220 minChildIndex = rightChildIndex; | |
221 } | |
222 | |
223 comparison = _compare(element, minChild); | |
224 | |
225 if (comparison <= 0) { | |
226 _queue[index] = element; | |
227 return; | |
228 } | |
229 | |
230 _queue[index] = minChild; | |
231 index = minChildIndex; | |
232 rightChildIndex = index * 2 + 2; | |
233 } | |
234 | |
235 var leftChildIndex = rightChildIndex - 1; | |
236 if (leftChildIndex < _length) { | |
237 var child = _queue[leftChildIndex]; | |
238 var comparison = _compare(element, child); | |
239 | |
240 if (comparison > 0) { | |
241 _queue[index] = child; | |
242 index = leftChildIndex; | |
243 } | |
244 } | |
245 | |
246 _queue[index] = element; | |
247 } | |
248 } | |
OLD | NEW |