Line data Source code
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 : import "dart:math" as math;
6 :
7 : import "utils.dart";
8 :
9 : /// Returns a position of the [value] in [sortedList], if it is there.
10 : ///
11 : /// If the list isn't sorted according to the [compare] function, the result
12 : /// is unpredictable.
13 : ///
14 : /// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
15 : /// the objects. If any object is not [Comparable], this throws a [CastError].
16 : ///
17 : /// Returns -1 if [value] is not in the list by default.
18 : int binarySearch<T>(List<T> sortedList, T value, {int compare(T a, T b)}) {
19 0 : compare ??= defaultCompare<T>();
20 : int min = 0;
21 0 : int max = sortedList.length;
22 0 : while (min < max) {
23 0 : int mid = min + ((max - min) >> 1);
24 0 : var element = sortedList[mid];
25 0 : int comp = compare(element, value);
26 0 : if (comp == 0) return mid;
27 0 : if (comp < 0) {
28 0 : min = mid + 1;
29 : } else {
30 : max = mid;
31 : }
32 : }
33 : return -1;
34 : }
35 :
36 : /// Returns the first position in [sortedList] that does not compare less than
37 : /// [value].
38 : ///
39 : /// If the list isn't sorted according to the [compare] function, the result
40 : /// is unpredictable.
41 : ///
42 : /// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
43 : /// the objects. If any object is not [Comparable], this throws a [CastError].
44 : ///
45 : /// Returns [sortedList.length] if all the items in [sortedList] compare less
46 : /// than [value].
47 : int lowerBound<T>(List<T> sortedList, T value, {int compare(T a, T b)}) {
48 0 : compare ??= defaultCompare<T>();
49 : int min = 0;
50 0 : int max = sortedList.length;
51 0 : while (min < max) {
52 0 : int mid = min + ((max - min) >> 1);
53 0 : var element = sortedList[mid];
54 0 : int comp = compare(element, value);
55 0 : if (comp < 0) {
56 0 : min = mid + 1;
57 : } else {
58 : max = mid;
59 : }
60 : }
61 : return min;
62 : }
63 :
64 : /// Shuffles a list randomly.
65 : ///
66 : /// A sub-range of a list can be shuffled by providing [start] and [end].
67 : void shuffle(List list, [int start = 0, int end = null]) {
68 0 : var random = new math.Random();
69 0 : if (end == null) end = list.length;
70 0 : int length = end - start;
71 0 : while (length > 1) {
72 0 : int pos = random.nextInt(length);
73 0 : length--;
74 0 : var tmp1 = list[start + pos];
75 0 : list[start + pos] = list[start + length];
76 0 : list[start + length] = tmp1;
77 : }
78 : }
79 :
80 : /// Reverses a list, or a part of a list, in-place.
81 : void reverse(List list, [int start = 0, int end = null]) {
82 0 : if (end == null) end = list.length;
83 0 : _reverse(list, start, end);
84 : }
85 :
86 : /// Internal helper function that assumes valid arguments.
87 : void _reverse(List list, int start, int end) {
88 0 : for (int i = start, j = end - 1; i < j; i++, j--) {
89 0 : var tmp = list[i];
90 0 : list[i] = list[j];
91 0 : list[j] = tmp;
92 : }
93 : }
94 :
95 : /// Sort a list between [start] (inclusive) and [end] (exclusive) using
96 : /// insertion sort.
97 : ///
98 : /// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
99 : /// the objects. If any object is not [Comparable], this throws a [CastError].
100 : ///
101 : /// Insertion sort is a simple sorting algorithm. For `n` elements it does on
102 : /// the order of `n * log(n)` comparisons but up to `n` squared moves. The
103 : /// sorting is performed in-place, without using extra memory.
104 : ///
105 : /// For short lists the many moves have less impact than the simple algorithm,
106 : /// and it is often the favored sorting algorithm for short lists.
107 : ///
108 : /// This insertion sort is stable: Equal elements end up in the same order
109 : /// as they started in.
110 : void insertionSort<T>(List<T> list,
111 : {int compare(T a, T b), int start: 0, int end}) {
112 : // If the same method could have both positional and named optional
113 : // parameters, this should be (list, [start, end], {compare}).
114 0 : compare ??= defaultCompare<T>();
115 0 : end ??= list.length;
116 :
117 0 : for (int pos = start + 1; pos < end; pos++) {
118 : int min = start;
119 : int max = pos;
120 0 : var element = list[pos];
121 0 : while (min < max) {
122 0 : int mid = min + ((max - min) >> 1);
123 0 : int comparison = compare(element, list[mid]);
124 0 : if (comparison < 0) {
125 : max = mid;
126 : } else {
127 0 : min = mid + 1;
128 : }
129 : }
130 0 : list.setRange(min + 1, pos + 1, list, min);
131 0 : list[min] = element;
132 : }
133 : }
134 :
135 : /// Limit below which merge sort defaults to insertion sort.
136 : const int _MERGE_SORT_LIMIT = 32;
137 :
138 : /// Sorts a list between [start] (inclusive) and [end] (exclusive) using the
139 : /// merge sort algorithm.
140 : ///
141 : /// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
142 : /// the objects. If any object is not [Comparable], this throws a [CastError].
143 : ///
144 : /// Merge-sorting works by splitting the job into two parts, sorting each
145 : /// recursively, and then merging the two sorted parts.
146 : ///
147 : /// This takes on the order of `n * log(n)` comparisons and moves to sort
148 : /// `n` elements, but requires extra space of about the same size as the list
149 : /// being sorted.
150 : ///
151 : /// This merge sort is stable: Equal elements end up in the same order
152 : /// as they started in.
153 : void mergeSort<T>(List<T> list,
154 : {int start: 0, int end, int compare(T a, T b)}) {
155 0 : end ??= list.length;
156 0 : compare ??= defaultCompare<T>();
157 :
158 0 : int length = end - start;
159 0 : if (length < 2) return;
160 0 : if (length < _MERGE_SORT_LIMIT) {
161 0 : insertionSort(list, compare: compare, start: start, end: end);
162 : return;
163 : }
164 : // Special case the first split instead of directly calling
165 : // _mergeSort, because the _mergeSort requires its target to
166 : // be different from its source, and it requires extra space
167 : // of the same size as the list to sort.
168 : // This split allows us to have only half as much extra space,
169 : // and it ends up in the original place.
170 0 : int middle = start + ((end - start) >> 1);
171 0 : int firstLength = middle - start;
172 0 : int secondLength = end - middle;
173 : // secondLength is always the same as firstLength, or one greater.
174 0 : var scratchSpace = new List<T>(secondLength);
175 0 : _mergeSort(list, compare, middle, end, scratchSpace, 0);
176 0 : int firstTarget = end - firstLength;
177 0 : _mergeSort(list, compare, start, middle, list, firstTarget);
178 0 : _merge(compare, list, firstTarget, end, scratchSpace, 0, secondLength, list,
179 : start);
180 : }
181 :
182 : /// Performs an insertion sort into a potentially different list than the
183 : /// one containing the original values.
184 : ///
185 : /// It will work in-place as well.
186 : void _movingInsertionSort<T>(List<T> list, int compare(T a, T b), int start,
187 : int end, List<T> target, int targetOffset) {
188 0 : int length = end - start;
189 0 : if (length == 0) return;
190 0 : target[targetOffset] = list[start];
191 0 : for (int i = 1; i < length; i++) {
192 0 : var element = list[start + i];
193 : int min = targetOffset;
194 0 : int max = targetOffset + i;
195 0 : while (min < max) {
196 0 : int mid = min + ((max - min) >> 1);
197 0 : if (compare(element, target[mid]) < 0) {
198 : max = mid;
199 : } else {
200 0 : min = mid + 1;
201 : }
202 : }
203 0 : target.setRange(min + 1, targetOffset + i + 1, target, min);
204 0 : target[min] = element;
205 : }
206 : }
207 :
208 : /// Sorts [list] from [start] to [end] into [target] at [targetOffset].
209 : ///
210 : /// The `target` list must be able to contain the range from `start` to `end`
211 : /// after `targetOffset`.
212 : ///
213 : /// Allows target to be the same list as [list], as long as it's not
214 : /// overlapping the `start..end` range.
215 : void _mergeSort<T>(List<T> list, int compare(T a, T b), int start, int end,
216 : List<T> target, int targetOffset) {
217 0 : int length = end - start;
218 0 : if (length < _MERGE_SORT_LIMIT) {
219 0 : _movingInsertionSort(list, compare, start, end, target, targetOffset);
220 : return;
221 : }
222 0 : int middle = start + (length >> 1);
223 0 : int firstLength = middle - start;
224 0 : int secondLength = end - middle;
225 : // Here secondLength >= firstLength (differs by at most one).
226 0 : int targetMiddle = targetOffset + firstLength;
227 : // Sort the second half into the end of the target area.
228 0 : _mergeSort(list, compare, middle, end, target, targetMiddle);
229 : // Sort the first half into the end of the source area.
230 0 : _mergeSort(list, compare, start, middle, list, middle);
231 : // Merge the two parts into the target area.
232 0 : _merge(compare, list, middle, middle + firstLength, target, targetMiddle,
233 0 : targetMiddle + secondLength, target, targetOffset);
234 : }
235 :
236 : /// Merges two lists into a target list.
237 : ///
238 : /// One of the input lists may be positioned at the end of the target
239 : /// list.
240 : ///
241 : /// For equal object, elements from [firstList] are always preferred.
242 : /// This allows the merge to be stable if the first list contains elements
243 : /// that started out earlier than the ones in [secondList]
244 : void _merge<T>(
245 : int compare(T a, T b),
246 : List<T> firstList,
247 : int firstStart,
248 : int firstEnd,
249 : List<T> secondList,
250 : int secondStart,
251 : int secondEnd,
252 : List<T> target,
253 : int targetOffset) {
254 : // No empty lists reaches here.
255 : assert(firstStart < firstEnd);
256 : assert(secondStart < secondEnd);
257 : int cursor1 = firstStart;
258 : int cursor2 = secondStart;
259 0 : var firstElement = firstList[cursor1++];
260 0 : var secondElement = secondList[cursor2++];
261 : while (true) {
262 0 : if (compare(firstElement, secondElement) <= 0) {
263 0 : target[targetOffset++] = firstElement;
264 0 : if (cursor1 == firstEnd) break; // Flushing second list after loop.
265 0 : firstElement = firstList[cursor1++];
266 : } else {
267 0 : target[targetOffset++] = secondElement;
268 0 : if (cursor2 != secondEnd) {
269 0 : secondElement = secondList[cursor2++];
270 : continue;
271 : }
272 : // Second list empties first. Flushing first list here.
273 0 : target[targetOffset++] = firstElement;
274 0 : target.setRange(targetOffset, targetOffset + (firstEnd - cursor1),
275 : firstList, cursor1);
276 : return;
277 : }
278 : }
279 : // First list empties first. Reached by break above.
280 0 : target[targetOffset++] = secondElement;
281 0 : target.setRange(
282 0 : targetOffset, targetOffset + (secondEnd - cursor2), secondList, cursor2);
283 : }
|