Index: lib/src/line_splitting/solve_state_queue.dart |
diff --git a/lib/src/line_splitting/solve_state_queue.dart b/lib/src/line_splitting/solve_state_queue.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..2247cb3cb3c3aeebf8de15021163b8a954d4ab0c |
--- /dev/null |
+++ b/lib/src/line_splitting/solve_state_queue.dart |
@@ -0,0 +1,248 @@ |
+// Copyright (c) 2015, the Dart project authors. Please see the AUTHORS file |
+// for details. All rights reserved. Use of this source code is governed by a |
+// BSD-style license that can be found in the LICENSE file. |
+ |
+library dart_style.src.line_splitting.solve_state_queue; |
+ |
+import 'line_splitter.dart'; |
+import 'solve_state.dart'; |
+ |
+/// A priority queue of [SolveStates] to consider while line splitting. |
+/// |
+/// This is based on the [HeapPriorityQueue] class from the "collection" |
+/// package, but is modified to handle the "overlap" logic that allows one |
+/// [SolveState] to supercede another. |
+/// |
+/// States are stored internally in a heap ordered by cost, the number of |
+/// overflow characters. When a new state is added to the heap, it will be |
+/// discarded, or a previously enqueued one will be discarded, if two overlap. |
+class SolveStateQueue { |
+ /// Initial capacity of a queue when created, or when added to after a [clear]. |
+ /// Number can be any positive value. Picking a size that gives a whole |
+ /// number of "tree levels" in the heap is only done for aesthetic reasons. |
+ static const int _INITIAL_CAPACITY = 7; |
+ |
+ LineSplitter _splitter; |
+ |
+ /// List implementation of a heap. |
+ List<SolveState> _queue = new List<SolveState>(_INITIAL_CAPACITY); |
+ |
+ /// Number of elements in queue. |
+ /// The heap is implemented in the first [_length] entries of [_queue]. |
+ int _length = 0; |
+ |
+ bool get isNotEmpty => _length != 0; |
+ |
+ void bindSplitter(LineSplitter splitter) { |
+ // Only do this once. |
+ assert(_splitter == null); |
+ |
+ _splitter = splitter; |
+ } |
+ |
+ /// Add [state] to the queue. |
+ /// |
+ /// Grows the capacity if the backing list is full. |
+ void add(SolveState state) { |
+ if (_tryOverlap(state)) return; |
+ |
+ if (_length == _queue.length) { |
+ var newCapacity = _queue.length * 2 + 1; |
+ if (newCapacity < _INITIAL_CAPACITY) newCapacity = _INITIAL_CAPACITY; |
+ |
+ var newQueue = new List<SolveState>(newCapacity); |
+ newQueue.setRange(0, _length, _queue); |
+ _queue = newQueue; |
+ } |
+ |
+ _bubbleUp(state, _length++); |
+ } |
+ |
+ SolveState removeFirst() { |
+ assert(_length > 0); |
+ |
+ // Remove the highest priority state. |
+ var result = _queue[0]; |
+ _length--; |
+ |
+ // Fill the gap with the one at the end of the list and re-heapify. |
+ if (_length > 0) { |
+ var last = _queue[_length]; |
+ _queue[_length] = null; |
+ _bubbleDown(last, 0); |
+ } |
+ |
+ return result; |
+ } |
+ |
+ /// Orders this state relative to [other]. |
+ /// |
+ /// This is a best-first ordering that prefers cheaper states even if they |
+ /// overflow because this ensures it finds the best solution first as soon as |
+ /// it finds one that fits in the page so it can early out. |
+ int _compare(SolveState a, SolveState b) { |
+ // TODO(rnystrom): It may be worth sorting by the estimated lowest number |
+ // of overflow characters first. That doesn't help in cases where there is |
+ // a solution that fits, but may help in corner cases where there is no |
+ // fitting solution. |
+ |
+ var comparison = _compareScore(a, b); |
+ if (comparison != 0) return comparison; |
+ |
+ return _compareRules(a, b); |
+ } |
+ |
+ /// Compares the overflow and cost of [a] to [b]. |
+ int _compareScore(SolveState a, SolveState b) { |
+ if (a.splits.cost != b.splits.cost) { |
+ return a.splits.cost.compareTo(b.splits.cost); |
+ } |
+ |
+ return a.overflowChars.compareTo(b.overflowChars); |
+ } |
+ |
+ /// Distinguish states based on the rule values just so that states with the |
+ /// same cost range but different rule values don't get considered identical |
+ /// and inadvertantly merged. |
+ int _compareRules(SolveState a, SolveState b) { |
+ for (var rule in _splitter.rules) { |
+ var aValue = a.getValue(rule); |
+ var bValue = b.getValue(rule); |
+ |
+ if (aValue != bValue) return aValue.compareTo(bValue); |
+ } |
+ |
+ // The way SolveStates are expanded should guarantee that we never generate |
+ // the exact same state twice. Getting here implies that that failed. |
+ throw "unreachable"; |
+ } |
+ |
+ /// Determines if any already enqueued state overlaps [state]. |
+ /// |
+ /// If so, chooses the best and discards the other. Returns `true` in this |
+ /// case. Otherwise, returns `false`. |
+ bool _tryOverlap(SolveState state) { |
+ if (_length == 0) return false; |
+ |
+ // Count positions from one instead of zero. This gives the numbers some |
+ // nice properties. For example, all right children are odd, their left |
+ // sibling is even, and the parent is found by shifting right by one. |
+ // Valid range for position is [1.._length], inclusive. |
+ var position = 1; |
+ |
+ // Pre-order depth first search, omit child nodes if the current node has |
+ // lower priority than [object], because all nodes lower in the heap will |
+ // also have lower priority. |
+ do { |
+ var index = position - 1; |
+ var enqueued = _queue[index]; |
+ |
+ var comparison = _compareScore(enqueued, state); |
+ |
+ if (comparison == 0) { |
+ var overlap = enqueued.compareOverlap(state); |
+ if (overlap < 0) { |
+ // The old state is better, so just discard the new one. |
+ return true; |
+ } else if (overlap > 0) { |
+ // The new state is better than the enqueued one, so replace it. |
+ _queue[index] = state; |
+ return true; |
+ } else { |
+ // We can't merge them, so sort by their bound rule values. |
+ comparison = _compareRules(enqueued, state); |
+ } |
+ } |
+ |
+ if (comparison < 0) { |
+ // Element may be in subtree. Continue with the left child, if any. |
+ var leftChildPosition = position * 2; |
+ if (leftChildPosition <= _length) { |
+ position = leftChildPosition; |
+ continue; |
+ } |
+ } |
+ |
+ // Find the next right sibling or right ancestor sibling. |
+ do { |
+ while (position.isOdd) { |
+ // While position is a right child, go to the parent. |
+ position >>= 1; |
+ } |
+ |
+ // Then go to the right sibling of the left child. |
+ position += 1; |
+ } while (position > _length); // Happens if last element is a left child. |
+ } while (position != 1); // At root again. Happens for right-most element. |
+ |
+ return false; |
+ } |
+ |
+ /// Place [element] in heap at [index] or above. |
+ /// |
+ /// Put element into the empty cell at `index`. While the `element` has |
+ /// higher priority than the parent, swap it with the parent. |
+ void _bubbleUp(SolveState element, int index) { |
+ while (index > 0) { |
+ var parentIndex = (index - 1) ~/ 2; |
+ var parent = _queue[parentIndex]; |
+ |
+ if (_compare(element, parent) > 0) break; |
+ |
+ _queue[index] = parent; |
+ index = parentIndex; |
+ } |
+ |
+ _queue[index] = element; |
+ } |
+ |
+ /// Place [element] in heap at [index] or above. |
+ /// |
+ /// Put element into the empty cell at `index`. While the `element` has lower |
+ /// priority than either child, swap it with the highest priority child. |
+ void _bubbleDown(SolveState element, int index) { |
+ var rightChildIndex = index * 2 + 2; |
+ |
+ while (rightChildIndex < _length) { |
+ var leftChildIndex = rightChildIndex - 1; |
+ var leftChild = _queue[leftChildIndex]; |
+ var rightChild = _queue[rightChildIndex]; |
+ |
+ var comparison = _compare(leftChild, rightChild); |
+ var minChildIndex; |
+ var minChild; |
+ |
+ if (comparison < 0) { |
+ minChild = leftChild; |
+ minChildIndex = leftChildIndex; |
+ } else { |
+ minChild = rightChild; |
+ minChildIndex = rightChildIndex; |
+ } |
+ |
+ comparison = _compare(element, minChild); |
+ |
+ if (comparison <= 0) { |
+ _queue[index] = element; |
+ return; |
+ } |
+ |
+ _queue[index] = minChild; |
+ index = minChildIndex; |
+ rightChildIndex = index * 2 + 2; |
+ } |
+ |
+ var leftChildIndex = rightChildIndex - 1; |
+ if (leftChildIndex < _length) { |
+ var child = _queue[leftChildIndex]; |
+ var comparison = _compare(element, child); |
+ |
+ if (comparison > 0) { |
+ _queue[index] = child; |
+ index = leftChildIndex; |
+ } |
+ } |
+ |
+ _queue[index] = element; |
+ } |
+} |