Coverage report for lib/src/line_splitting/solve_state_queue.dart

Line coverage: 80 / 80 (100.0%)

All files > lib/src/line_splitting/solve_state_queue.dart

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
349
  bool get isNotEmpty => _length != 0;
35
363
  void bindSplitter(LineSplitter splitter) {
37
    // Only do this once.
383
    assert(_splitter == null);
39
403
    _splitter = splitter;
41
  }
42
43
  /// Add [state] to the queue.
44
  ///
45
  /// Grows the capacity if the backing list is full.
463
  void add(SolveState state) {
473
    if (_tryOverlap(state)) return;
48
4912
    if (_length == _queue.length) {
508
      var newCapacity = _queue.length * 2 + 1;
512
      if (newCapacity < _INITIAL_CAPACITY) newCapacity = _INITIAL_CAPACITY;
52
532
      var newQueue = new List<SolveState>(newCapacity);
546
      newQueue.setRange(0, _length, _queue);
552
      _queue = newQueue;
56
    }
57
589
    _bubbleUp(state, _length++);
59
  }
60
613
  SolveState removeFirst() {
626
    assert(_length > 0);
63
64
    // Remove the highest priority state.
656
    var result = _queue[0];
666
    _length--;
67
68
    // Fill the gap with the one at the end of the list and re-heapify.
696
    if (_length > 0) {
706
      var last = _queue[_length];
716
      _queue[_length] = null;
722
      _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.
832
  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
892
    var comparison = _compareScore(a, b);
902
    if (comparison != 0) return comparison;
91
922
    return _compareRules(a, b);
93
  }
94
95
  /// Compares the overflow and cost of [a] to [b].
962
  int _compareScore(SolveState a, SolveState b) {
9710
    if (a.splits.cost != b.splits.cost) {
9810
      return a.splits.cost.compareTo(b.splits.cost);
99
    }
100
1016
    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.
1072
  int _compareRules(SolveState a, SolveState b) {
1086
    for (var rule in _splitter.rules) {
1092
      var aValue = a.getValue(rule);
1102
      var bValue = b.getValue(rule);
111
1124
      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`.
1243
  bool _tryOverlap(SolveState state) {
1256
    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 {
1372
      var index = position - 1;
1384
      var enqueued = _queue[index];
139
1402
      var comparison = _compareScore(enqueued, state);
141
1422
      if (comparison == 0) {
1432
        var overlap = enqueued.compareOverlap(state);
1442
        if (overlap < 0) {
145
          // The old state is better, so just discard the new one.
146
          return true;
1472
        } else if (overlap > 0) {
148
          // The new state is better than the enqueued one, so replace it.
1494
          _queue[index] = state;
150
          return true;
151
        } else {
152
          // We can't merge them, so sort by their bound rule values.
1532
          comparison = _compareRules(enqueued, state);
154
        }
155
      }
156
1572
      if (comparison < 0) {
158
        // Element may be in subtree. Continue with the left child, if any.
1592
        var leftChildPosition = position * 2;
1604
        if (leftChildPosition <= _length) {
161
          position = leftChildPosition;
162
          continue;
163
        }
164
      }
165
166
      // Find the next right sibling or right ancestor sibling.
167
      do {
1682
        while (position.isOdd) {
169
          // While position is a right child, go to the parent.
1702
          position >>= 1;
171
        }
172
173
        // Then go to the right sibling of the left child.
1742
        position += 1;
1754
      } while (position > _length); // Happens if last element is a left child.
1762
    } 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.
1853
  void _bubbleUp(SolveState element, int index) {
1863
    while (index > 0) {
1874
      var parentIndex = (index - 1) ~/ 2;
1884
      var parent = _queue[parentIndex];
189
1904
      if (_compare(element, parent) > 0) break;
191
1924
      _queue[index] = parent;
193
      index = parentIndex;
194
    }
195
1966
    _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.
2032
  void _bubbleDown(SolveState element, int index) {
2044
    var rightChildIndex = index * 2 + 2;
205
2064
    while (rightChildIndex < _length) {
2072
      var leftChildIndex = rightChildIndex - 1;
2084
      var leftChild = _queue[leftChildIndex];
2094
      var rightChild = _queue[rightChildIndex];
210
2112
      var comparison = _compare(leftChild, rightChild);
212
      var minChildIndex;
213
      var minChild;
214
2152
      if (comparison < 0) {
216
        minChild = leftChild;
217
        minChildIndex = leftChildIndex;
218
      } else {
219
        minChild = rightChild;
220
        minChildIndex = rightChildIndex;
221
      }
222
2232
      comparison = _compare(element, minChild);
224
2252
      if (comparison <= 0) {
2264
        _queue[index] = element;
227
        return;
228
      }
229
2304
      _queue[index] = minChild;
231
      index = minChildIndex;
2324
      rightChildIndex = index * 2 + 2;
233
    }
234
2352
    var leftChildIndex = rightChildIndex - 1;
2364
    if (leftChildIndex < _length) {
2374
      var child = _queue[leftChildIndex];
2382
      var comparison = _compare(element, child);
239
2402
      if (comparison > 0) {
2414
        _queue[index] = child;
242
        index = leftChildIndex;
243
      }
244
    }
245
2464
    _queue[index] = element;
247
  }
248
}