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 : /// A selection of data manipulation algorithms.
6 : library pkg.collection.algorithms;
7 :
8 : import 'dart:math' show Random;
9 :
10 : import 'utils.dart';
11 :
12 : /// Returns a position of the [value] in [sortedList], if it is there.
13 : ///
14 : /// If the list isn't sorted according to the [compare] function, the result
15 : /// is unpredictable.
16 : ///
17 : /// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
18 : /// the objects. In this case, the objects must be [Comparable].
19 : ///
20 : /// Returns -1 if [value] is not in the list.
21 0 : int binarySearch<E>(List<E> sortedList, E value,
22 : {int Function(E, E)? compare}) {
23 : compare ??= defaultCompare;
24 0 : return binarySearchBy<E, E>(sortedList, identity, compare, value);
25 : }
26 :
27 : /// Returns a position of the [value] in [sortedList], if it is there.
28 : ///
29 : /// If the list isn't sorted according to the [compare] function on the [keyOf]
30 : /// property of the elements, the result is unpredictable.
31 : ///
32 : /// Returns -1 if [value] is not in the list by default.
33 : ///
34 : /// If [start] and [end] are supplied, only that range is searched,
35 : /// and only that range need to be sorted.
36 0 : int binarySearchBy<E, K>(List<E> sortedList, K Function(E element) keyOf,
37 : int Function(K, K) compare, E value,
38 : [int start = 0, int? end]) {
39 0 : end = RangeError.checkValidRange(start, end, sortedList.length);
40 : var min = start;
41 : var max = end;
42 : var key = keyOf(value);
43 0 : while (min < max) {
44 0 : var mid = min + ((max - min) >> 1);
45 0 : var element = sortedList[mid];
46 : var comp = compare(keyOf(element), key);
47 0 : if (comp == 0) return mid;
48 0 : if (comp < 0) {
49 0 : min = mid + 1;
50 : } else {
51 : max = mid;
52 : }
53 : }
54 0 : return -1;
55 : }
56 :
57 : /// Returns the first position in [sortedList] that does not compare less than
58 : /// [value].
59 : ///
60 : /// If the list isn't sorted according to the [compare] function, the result
61 : /// is unpredictable.
62 : ///
63 : /// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
64 : /// the objects. In this case, the objects must be [Comparable].
65 : ///
66 : /// Returns [sortedList.length] if all the items in [sortedList] compare less
67 : /// than [value].
68 0 : int lowerBound<E>(List<E> sortedList, E value, {int Function(E, E)? compare}) {
69 : compare ??= defaultCompare;
70 0 : return lowerBoundBy<E, E>(sortedList, identity, compare, value);
71 : }
72 :
73 : /// Returns the first position in [sortedList] that is not before [value].
74 : ///
75 : /// Elements are compared using the [compare] function of the [keyOf] property of
76 : /// the elements.
77 : /// If the list isn't sorted according to this order, the result is unpredictable.
78 : ///
79 : /// Returns [sortedList.length] if all the items in [sortedList] are before [value].
80 : ///
81 : /// If [start] and [end] are supplied, only that range is searched,
82 : /// and only that range need to be sorted.
83 0 : int lowerBoundBy<E, K>(List<E> sortedList, K Function(E element) keyOf,
84 : int Function(K, K) compare, E value,
85 : [int start = 0, int? end]) {
86 0 : end = RangeError.checkValidRange(start, end, sortedList.length);
87 : var min = start;
88 : var max = end;
89 : var key = keyOf(value);
90 0 : while (min < max) {
91 0 : var mid = min + ((max - min) >> 1);
92 0 : var element = sortedList[mid];
93 : var comp = compare(keyOf(element), key);
94 0 : if (comp < 0) {
95 0 : min = mid + 1;
96 : } else {
97 : max = mid;
98 : }
99 : }
100 : return min;
101 : }
102 :
103 : /// Shuffles a list randomly.
104 : ///
105 : /// A sub-range of a list can be shuffled by providing [start] and [end].
106 : ///
107 : /// If [start] or [end] are omitted,
108 : /// they default to the start and end of the list.
109 : ///
110 : /// If [random] is omitted, it defaults to a new instance of [Random].
111 0 : void shuffle(List elements, [int start = 0, int? end, Random? random]) {
112 0 : random ??= Random();
113 0 : end ??= elements.length;
114 0 : var length = end - start;
115 0 : while (length > 1) {
116 0 : var pos = random.nextInt(length);
117 0 : length--;
118 0 : var tmp1 = elements[start + pos];
119 0 : elements[start + pos] = elements[start + length];
120 0 : elements[start + length] = tmp1;
121 : }
122 : }
123 :
124 : /// Reverses a list, or a part of a list, in-place.
125 0 : void reverse<E>(List<E> elements, [int start = 0, int? end]) {
126 0 : end = RangeError.checkValidRange(start, end, elements.length);
127 0 : _reverse<E>(elements, start, end);
128 : }
129 :
130 : /// Internal helper function that assumes valid arguments.
131 0 : void _reverse<E>(List<E> elements, int start, int end) {
132 0 : for (var i = start, j = end - 1; i < j; i++, j--) {
133 0 : var tmp = elements[i];
134 0 : elements[i] = elements[j];
135 0 : elements[j] = tmp;
136 : }
137 : }
138 :
139 : /// Sort a list between [start] (inclusive) and [end] (exclusive) using
140 : /// insertion sort.
141 : ///
142 : /// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
143 : /// the objects. In this case, the objects must be [Comparable].
144 : ///
145 : /// Insertion sort is a simple sorting algorithm. For `n` elements it does on
146 : /// the order of `n * log(n)` comparisons but up to `n` squared moves. The
147 : /// sorting is performed in-place, without using extra memory.
148 : ///
149 : /// For short lists the many moves have less impact than the simple algorithm,
150 : /// and it is often the favored sorting algorithm for short lists.
151 : ///
152 : /// This insertion sort is stable: Equal elements end up in the same order
153 : /// as they started in.
154 0 : void insertionSort<E>(List<E> elements,
155 : {int Function(E, E)? compare, int start = 0, int? end}) {
156 : // If the same method could have both positional and named optional
157 : // parameters, this should be (list, [start, end], {compare}).
158 : compare ??= defaultCompare;
159 0 : end ??= elements.length;
160 :
161 0 : for (var pos = start + 1; pos < end; pos++) {
162 : var min = start;
163 : var max = pos;
164 0 : var element = elements[pos];
165 0 : while (min < max) {
166 0 : var mid = min + ((max - min) >> 1);
167 0 : var comparison = compare(element, elements[mid]);
168 0 : if (comparison < 0) {
169 : max = mid;
170 : } else {
171 0 : min = mid + 1;
172 : }
173 : }
174 0 : elements.setRange(min + 1, pos + 1, elements, min);
175 0 : elements[min] = element;
176 : }
177 : }
178 :
179 : /// Generalized insertion sort.
180 : ///
181 : /// Performs insertion sort on the [elements] range from [start] to [end].
182 : /// Ordering is the [compare] of the [keyOf] of the elements.
183 0 : void insertionSortBy<E, K>(List<E> elements, K Function(E element) keyOf,
184 : int Function(K a, K b) compare,
185 : [int start = 0, int? end]) {
186 0 : end = RangeError.checkValidRange(start, end, elements.length);
187 0 : _movingInsertionSort(elements, keyOf, compare, start, end, elements, start);
188 : }
189 :
190 : /// Limit below which merge sort defaults to insertion sort.
191 : const int _mergeSortLimit = 32;
192 :
193 : /// Sorts a list between [start] (inclusive) and [end] (exclusive) using the
194 : /// merge sort algorithm.
195 : ///
196 : /// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
197 : /// the objects. If any object is not [Comparable], that throws a [TypeError].
198 : ///
199 : /// Merge-sorting works by splitting the job into two parts, sorting each
200 : /// recursively, and then merging the two sorted parts.
201 : ///
202 : /// This takes on the order of `n * log(n)` comparisons and moves to sort
203 : /// `n` elements, but requires extra space of about the same size as the list
204 : /// being sorted.
205 : ///
206 : /// This merge sort is stable: Equal elements end up in the same order
207 : /// as they started in.
208 0 : void mergeSort<E>(List<E> elements,
209 : {int start = 0, int? end, int Function(E, E)? compare}) {
210 0 : end = RangeError.checkValidRange(start, end, elements.length);
211 : compare ??= defaultCompare;
212 :
213 0 : var length = end - start;
214 0 : if (length < 2) return;
215 0 : if (length < _mergeSortLimit) {
216 0 : insertionSort(elements, compare: compare, start: start, end: end);
217 : return;
218 : }
219 : // Special case the first split instead of directly calling
220 : // _mergeSort, because the _mergeSort requires its target to
221 : // be different from its source, and it requires extra space
222 : // of the same size as the list to sort.
223 : // This split allows us to have only half as much extra space,
224 : // and allows the sorted elements to end up in the original list.
225 0 : var firstLength = (end - start) >> 1;
226 0 : var middle = start + firstLength;
227 0 : var secondLength = end - middle;
228 : // secondLength is always the same as firstLength, or one greater.
229 0 : var scratchSpace = List<E>.filled(secondLength, elements[start]);
230 : // TODO(linter/2097): Remove ignore when no longer required by linter.
231 : // See: https://github.com/dart-lang/linter/issues/2097
232 : E Function(E) id = identity; // ignore: omit_local_variable_types
233 0 : _mergeSort(elements, id, compare, middle, end, scratchSpace, 0);
234 0 : var firstTarget = end - firstLength;
235 0 : _mergeSort(elements, id, compare, start, middle, elements, firstTarget);
236 0 : _merge(id, compare, elements, firstTarget, end, scratchSpace, 0, secondLength,
237 : elements, start);
238 : }
239 :
240 : /// Sort [elements] using a merge-sort algorithm.
241 : ///
242 : /// The elements are compared using [compare] on the value provided by [keyOf]
243 : /// on the element.
244 : /// If [start] and [end] are provided, only that range is sorted.
245 : ///
246 : /// Uses insertion sort for smaller sublists.
247 0 : void mergeSortBy<E, K>(List<E> elements, K Function(E element) keyOf,
248 : int Function(K a, K b) compare,
249 : [int start = 0, int? end]) {
250 0 : end = RangeError.checkValidRange(start, end, elements.length);
251 0 : var length = end - start;
252 0 : if (length < 2) return;
253 0 : if (length < _mergeSortLimit) {
254 0 : _movingInsertionSort(elements, keyOf, compare, start, end, elements, start);
255 : return;
256 : }
257 : // Special case the first split instead of directly calling
258 : // _mergeSort, because the _mergeSort requires its target to
259 : // be different from its source, and it requires extra space
260 : // of the same size as the list to sort.
261 : // This split allows us to have only half as much extra space,
262 : // and it ends up in the original place.
263 0 : var middle = start + (length >> 1);
264 0 : var firstLength = middle - start;
265 0 : var secondLength = end - middle;
266 : // secondLength is always the same as firstLength, or one greater.
267 0 : var scratchSpace = List<E>.filled(secondLength, elements[start]);
268 0 : _mergeSort(elements, keyOf, compare, middle, end, scratchSpace, 0);
269 0 : var firstTarget = end - firstLength;
270 0 : _mergeSort(elements, keyOf, compare, start, middle, elements, firstTarget);
271 0 : _merge(keyOf, compare, elements, firstTarget, end, scratchSpace, 0,
272 : secondLength, elements, start);
273 : }
274 :
275 : /// Performs an insertion sort into a potentially different list than the
276 : /// one containing the original values.
277 : ///
278 : /// It will work in-place as well.
279 0 : void _movingInsertionSort<E, K>(
280 : List<E> list,
281 : K Function(E element) keyOf,
282 : int Function(K, K) compare,
283 : int start,
284 : int end,
285 : List<E> target,
286 : int targetOffset) {
287 0 : var length = end - start;
288 0 : if (length == 0) return;
289 0 : target[targetOffset] = list[start];
290 0 : for (var i = 1; i < length; i++) {
291 0 : var element = list[start + i];
292 : var elementKey = keyOf(element);
293 : var min = targetOffset;
294 0 : var max = targetOffset + i;
295 0 : while (min < max) {
296 0 : var mid = min + ((max - min) >> 1);
297 0 : if (compare(elementKey, keyOf(target[mid])) < 0) {
298 : max = mid;
299 : } else {
300 0 : min = mid + 1;
301 : }
302 : }
303 0 : target.setRange(min + 1, targetOffset + i + 1, target, min);
304 0 : target[min] = element;
305 : }
306 : }
307 :
308 : /// Sorts [elements] from [start] to [end] into [target] at [targetOffset].
309 : ///
310 : /// The `target` list must be able to contain the range from `start` to `end`
311 : /// after `targetOffset`.
312 : ///
313 : /// Allows target to be the same list as [elements], as long as it's not
314 : /// overlapping the `start..end` range.
315 0 : void _mergeSort<E, K>(
316 : List<E> elements,
317 : K Function(E element) keyOf,
318 : int Function(K, K) compare,
319 : int start,
320 : int end,
321 : List<E> target,
322 : int targetOffset) {
323 0 : var length = end - start;
324 0 : if (length < _mergeSortLimit) {
325 0 : _movingInsertionSort<E, K>(
326 : elements, keyOf, compare, start, end, target, targetOffset);
327 : return;
328 : }
329 0 : var middle = start + (length >> 1);
330 0 : var firstLength = middle - start;
331 0 : var secondLength = end - middle;
332 : // Here secondLength >= firstLength (differs by at most one).
333 0 : var targetMiddle = targetOffset + firstLength;
334 : // Sort the second half into the end of the target area.
335 0 : _mergeSort(elements, keyOf, compare, middle, end, target, targetMiddle);
336 : // Sort the first half into the end of the source area.
337 0 : _mergeSort(elements, keyOf, compare, start, middle, elements, middle);
338 : // Merge the two parts into the target area.
339 0 : _merge(keyOf, compare, elements, middle, middle + firstLength, target,
340 0 : targetMiddle, targetMiddle + secondLength, target, targetOffset);
341 : }
342 :
343 : /// Merges two lists into a target list.
344 : ///
345 : /// One of the input lists may be positioned at the end of the target
346 : /// list.
347 : ///
348 : /// For equal object, elements from [firstList] are always preferred.
349 : /// This allows the merge to be stable if the first list contains elements
350 : /// that started out earlier than the ones in [secondList]
351 0 : void _merge<E, K>(
352 : K Function(E element) keyOf,
353 : int Function(K, K) compare,
354 : List<E> firstList,
355 : int firstStart,
356 : int firstEnd,
357 : List<E> secondList,
358 : int secondStart,
359 : int secondEnd,
360 : List<E> target,
361 : int targetOffset) {
362 : // No empty lists reaches here.
363 0 : assert(firstStart < firstEnd);
364 0 : assert(secondStart < secondEnd);
365 : var cursor1 = firstStart;
366 : var cursor2 = secondStart;
367 0 : var firstElement = firstList[cursor1++];
368 : var firstKey = keyOf(firstElement);
369 0 : var secondElement = secondList[cursor2++];
370 : var secondKey = keyOf(secondElement);
371 : while (true) {
372 0 : if (compare(firstKey, secondKey) <= 0) {
373 0 : target[targetOffset++] = firstElement;
374 0 : if (cursor1 == firstEnd) break; // Flushing second list after loop.
375 0 : firstElement = firstList[cursor1++];
376 : firstKey = keyOf(firstElement);
377 : } else {
378 0 : target[targetOffset++] = secondElement;
379 0 : if (cursor2 != secondEnd) {
380 0 : secondElement = secondList[cursor2++];
381 : secondKey = keyOf(secondElement);
382 : continue;
383 : }
384 : // Second list empties first. Flushing first list here.
385 0 : target[targetOffset++] = firstElement;
386 0 : target.setRange(targetOffset, targetOffset + (firstEnd - cursor1),
387 : firstList, cursor1);
388 : return;
389 : }
390 : }
391 : // First list empties first. Reached by break above.
392 0 : target[targetOffset++] = secondElement;
393 0 : target.setRange(
394 0 : targetOffset, targetOffset + (secondEnd - cursor2), secondList, cursor2);
395 : }
396 :
397 : /// Sort [elements] using a quick-sort algorithm.
398 : ///
399 : /// The elements are compared using [compare] on the elements.
400 : /// If [start] and [end] are provided, only that range is sorted.
401 : ///
402 : /// Uses insertion sort for smaller sublists.
403 0 : void quickSort<E>(List<E> elements, int Function(E a, E b) compare,
404 : [int start = 0, int? end]) {
405 0 : end = RangeError.checkValidRange(start, end, elements.length);
406 0 : _quickSort<E, E>(elements, identity, compare, Random(), start, end);
407 : }
408 :
409 : /// Sort [elements] using a quick-sort algorithm.
410 : ///
411 : /// The elements are compared using [compare] on the value provided by [keyOf]
412 : /// on the element.
413 : /// If [start] and [end] are provided, only that range is sorted.
414 : ///
415 : /// Uses insertion sort for smaller sublists.
416 0 : void quickSortBy<E, K>(
417 : List<E> list, K Function(E element) keyOf, int Function(K a, K b) compare,
418 : [int start = 0, int? end]) {
419 0 : end = RangeError.checkValidRange(start, end, list.length);
420 0 : _quickSort(list, keyOf, compare, Random(), start, end);
421 : }
422 :
423 0 : void _quickSort<E, K>(List<E> list, K Function(E element) keyOf,
424 : int Function(K a, K b) compare, Random random, int start, int end) {
425 : const minQuickSortLength = 24;
426 0 : var length = end - start;
427 0 : while (length >= minQuickSortLength) {
428 0 : var pivotIndex = random.nextInt(length) + start;
429 0 : var pivot = list[pivotIndex];
430 : var pivotKey = keyOf(pivot);
431 : var endSmaller = start;
432 : var startGreater = end;
433 0 : var startPivots = end - 1;
434 0 : list[pivotIndex] = list[startPivots];
435 0 : list[startPivots] = pivot;
436 0 : while (endSmaller < startPivots) {
437 0 : var current = list[endSmaller];
438 : var relation = compare(keyOf(current), pivotKey);
439 0 : if (relation < 0) {
440 0 : endSmaller++;
441 : } else {
442 0 : startPivots--;
443 : var currentTarget = startPivots;
444 0 : list[endSmaller] = list[startPivots];
445 0 : if (relation > 0) {
446 0 : startGreater--;
447 : currentTarget = startGreater;
448 0 : list[startPivots] = list[startGreater];
449 : }
450 0 : list[currentTarget] = current;
451 : }
452 : }
453 0 : if (endSmaller - start < end - startGreater) {
454 0 : _quickSort(list, keyOf, compare, random, start, endSmaller);
455 : start = startGreater;
456 : } else {
457 0 : _quickSort(list, keyOf, compare, random, startGreater, end);
458 : end = endSmaller;
459 : }
460 0 : length = end - start;
461 : }
462 0 : _movingInsertionSort<E, K>(list, keyOf, compare, start, end, list, start);
463 : }
|