blob: 70430b95cd8f1315c60c52b8e80b4b5fb5fe5ee4 [file] [log] [blame]
// Copyright (c) 2013, 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.
/// A selection of data manipulation algorithms.
library;
import 'dart:math' show Random;
import 'utils.dart';
/// Returns a position of the [value] in [sortedList], if it is there.
///
/// If the list isn't sorted according to the [compare] function, the result
/// is unpredictable.
///
/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
/// the objects. In this case, the objects must be [Comparable].
///
/// Returns -1 if [value] is not in the list.
int binarySearch<E>(
List<E> sortedList,
E value, {
int Function(E, E)? compare,
}) {
compare ??= defaultCompare;
return binarySearchBy<E, E>(sortedList, identity, compare, value);
}
/// Returns a position of the [value] in [sortedList], if it is there.
///
/// If the list isn't sorted according to the [compare] function on the [keyOf]
/// property of the elements, the result is unpredictable.
///
/// Returns -1 if [value] is not in the list by default.
///
/// If [start] and [end] are supplied, only that range is searched,
/// and only that range need to be sorted.
int binarySearchBy<E, K>(
List<E> sortedList,
K Function(E element) keyOf,
int Function(K, K) compare,
E value, [
int start = 0,
int? end,
]) {
end = RangeError.checkValidRange(start, end, sortedList.length);
var min = start;
var max = end;
var key = keyOf(value);
while (min < max) {
var mid = min + ((max - min) >> 1);
var element = sortedList[mid];
var comp = compare(keyOf(element), key);
if (comp == 0) return mid;
if (comp < 0) {
min = mid + 1;
} else {
max = mid;
}
}
return -1;
}
/// Returns the first position in [sortedList] that does not compare less than
/// [value].
///
/// Uses binary search to find the location of [value].
/// This takes on the order of `log(n)` comparisons.
/// If the list isn't sorted according to the [compare] function, the result
/// is unpredictable.
///
/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
/// the objects. In this case, the objects must be [Comparable].
///
/// Returns the length of [sortedList] if all the items in [sortedList] compare
/// less than [value].
int lowerBound<E>(List<E> sortedList, E value, {int Function(E, E)? compare}) {
compare ??= defaultCompare;
return lowerBoundBy<E, E>(sortedList, identity, compare, value);
}
/// Returns the first position in [sortedList] that is not before [value].
///
/// Uses binary search to find the location of [value].
/// This takes on the order of `log(n)` comparisons.
/// Elements are compared using the [compare] function of the [keyOf] property
/// of the elements.
/// If the list isn't sorted according to this order, the result is
/// unpredictable.
///
/// Returns the length of [sortedList] if all the items in [sortedList] are
/// before [value].
///
/// If [start] and [end] are supplied, only that range is searched,
/// and only that range need to be sorted.
int lowerBoundBy<E, K>(
List<E> sortedList,
K Function(E element) keyOf,
int Function(K, K) compare,
E value, [
int start = 0,
int? end,
]) {
end = RangeError.checkValidRange(start, end, sortedList.length);
var min = start;
var max = end;
var key = keyOf(value);
while (min < max) {
var mid = min + ((max - min) >> 1);
var element = sortedList[mid];
var comp = compare(keyOf(element), key);
if (comp < 0) {
min = mid + 1;
} else {
max = mid;
}
}
return min;
}
/// Shuffles a list randomly.
///
/// A sub-range of a list can be shuffled by providing [start] and [end].
///
/// If [start] or [end] are omitted,
/// they default to the start and end of the list.
///
/// If [random] is omitted, it defaults to a new instance of [Random].
void shuffle(List elements, [int start = 0, int? end, Random? random]) {
random ??= Random();
end ??= elements.length;
var length = end - start;
while (length > 1) {
var pos = random.nextInt(length);
length--;
var tmp1 = elements[start + pos];
elements[start + pos] = elements[start + length];
elements[start + length] = tmp1;
}
}
/// Reverses a list, or a part of a list, in-place.
void reverse<E>(List<E> elements, [int start = 0, int? end]) {
end = RangeError.checkValidRange(start, end, elements.length);
_reverse<E>(elements, start, end);
}
/// Internal helper function that assumes valid arguments.
void _reverse<E>(List<E> elements, int start, int end) {
for (var i = start, j = end - 1; i < j; i++, j--) {
var tmp = elements[i];
elements[i] = elements[j];
elements[j] = tmp;
}
}
/// Sort a list between [start] (inclusive) and [end] (exclusive) using
/// insertion sort.
///
/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
/// the objects. In this case, the objects must be [Comparable].
///
/// Insertion sort is a simple sorting algorithm. For `n` elements it does on
/// the order of `n * log(n)` comparisons but up to `n` squared moves. The
/// sorting is performed in-place, without using extra memory.
///
/// For short lists the many moves have less impact than the simple algorithm,
/// and it is often the favored sorting algorithm for short lists.
///
/// This insertion sort is stable: Equal elements end up in the same order
/// as they started in.
void insertionSort<E>(
List<E> elements, {
int Function(E, E)? compare,
int start = 0,
int? end,
}) {
// If the same method could have both positional and named optional
// parameters, this should be (list, [start, end], {compare}).
compare ??= defaultCompare;
end ??= elements.length;
for (var pos = start + 1; pos < end; pos++) {
var min = start;
var max = pos;
var element = elements[pos];
while (min < max) {
var mid = min + ((max - min) >> 1);
var comparison = compare(element, elements[mid]);
if (comparison < 0) {
max = mid;
} else {
min = mid + 1;
}
}
elements.setRange(min + 1, pos + 1, elements, min);
elements[min] = element;
}
}
/// Generalized insertion sort.
///
/// Performs insertion sort on the [elements] range from [start] to [end].
/// Ordering is the [compare] of the [keyOf] of the elements.
void insertionSortBy<E, K>(
List<E> elements,
K Function(E element) keyOf,
int Function(K a, K b) compare, [
int start = 0,
int? end,
]) {
end = RangeError.checkValidRange(start, end, elements.length);
_movingInsertionSort(elements, keyOf, compare, start, end, elements, start);
}
/// Limit below which merge sort defaults to insertion sort.
const int _mergeSortLimit = 32;
/// Sorts a list between [start] (inclusive) and [end] (exclusive) using the
/// merge sort algorithm.
///
/// If [compare] is omitted, this defaults to calling [Comparable.compareTo] on
/// the objects. If any object is not [Comparable], that throws a [TypeError].
///
/// Merge-sorting works by splitting the job into two parts, sorting each
/// recursively, and then merging the two sorted parts.
///
/// This takes on the order of `n * log(n)` comparisons and moves to sort
/// `n` elements, but requires extra space of about the same size as the list
/// being sorted.
///
/// This merge sort is stable: Equal elements end up in the same order
/// as they started in.
void mergeSort<E>(
List<E> elements, {
int start = 0,
int? end,
int Function(E, E)? compare,
}) {
end = RangeError.checkValidRange(start, end, elements.length);
compare ??= defaultCompare;
var length = end - start;
if (length < 2) return;
if (length < _mergeSortLimit) {
insertionSort(elements, compare: compare, start: start, end: end);
return;
}
// Special case the first split instead of directly calling
// _mergeSort, because the _mergeSort requires its target to
// be different from its source, and it requires extra space
// of the same size as the list to sort.
// This split allows us to have only half as much extra space,
// and allows the sorted elements to end up in the original list.
var firstLength = (end - start) >> 1;
var middle = start + firstLength;
var secondLength = end - middle;
// secondLength is always the same as firstLength, or one greater.
var scratchSpace = elements.sublist(0, secondLength);
_mergeSort(elements, identity<E>, compare, middle, end, scratchSpace, 0);
var firstTarget = end - firstLength;
_mergeSort(
elements,
identity<E>,
compare,
start,
middle,
elements,
firstTarget,
);
_merge(
identity<E>,
compare,
elements,
firstTarget,
end,
scratchSpace,
0,
secondLength,
elements,
start,
);
}
/// Sort [elements] using a merge-sort algorithm.
///
/// The elements are compared using [compare] on the value provided by [keyOf]
/// on the element.
/// If [start] and [end] are provided, only that range is sorted.
///
/// Uses insertion sort for smaller sublists.
void mergeSortBy<E, K>(
List<E> elements,
K Function(E element) keyOf,
int Function(K a, K b) compare, [
int start = 0,
int? end,
]) {
end = RangeError.checkValidRange(start, end, elements.length);
var length = end - start;
if (length < 2) return;
if (length < _mergeSortLimit) {
_movingInsertionSort(elements, keyOf, compare, start, end, elements, start);
return;
}
// Special case the first split instead of directly calling
// _mergeSort, because the _mergeSort requires its target to
// be different from its source, and it requires extra space
// of the same size as the list to sort.
// This split allows us to have only half as much extra space,
// and it ends up in the original place.
var middle = start + (length >> 1);
var firstLength = middle - start;
var secondLength = end - middle;
// secondLength is always the same as firstLength, or one greater.
var scratchSpace = elements.sublist(0, secondLength);
_mergeSort(elements, keyOf, compare, middle, end, scratchSpace, 0);
var firstTarget = end - firstLength;
_mergeSort(elements, keyOf, compare, start, middle, elements, firstTarget);
_merge(
keyOf,
compare,
elements,
firstTarget,
end,
scratchSpace,
0,
secondLength,
elements,
start,
);
}
/// Performs an insertion sort into a potentially different list than the
/// one containing the original values.
///
/// It will work in-place as well.
void _movingInsertionSort<E, K>(
List<E> list,
K Function(E element) keyOf,
int Function(K, K) compare,
int start,
int end,
List<E> target,
int targetOffset,
) {
var length = end - start;
if (length == 0) return;
target[targetOffset] = list[start];
for (var i = 1; i < length; i++) {
var element = list[start + i];
var elementKey = keyOf(element);
var min = targetOffset;
var max = targetOffset + i;
while (min < max) {
var mid = min + ((max - min) >> 1);
if (compare(elementKey, keyOf(target[mid])) < 0) {
max = mid;
} else {
min = mid + 1;
}
}
target.setRange(min + 1, targetOffset + i + 1, target, min);
target[min] = element;
}
}
/// Sorts [elements] from [start] to [end] into [target] at [targetOffset].
///
/// The `target` list must be able to contain the range from `start` to `end`
/// after `targetOffset`.
///
/// Allows target to be the same list as [elements], as long as it's not
/// overlapping the `start..end` range.
void _mergeSort<E, K>(
List<E> elements,
K Function(E element) keyOf,
int Function(K, K) compare,
int start,
int end,
List<E> target,
int targetOffset,
) {
var length = end - start;
if (length < _mergeSortLimit) {
_movingInsertionSort<E, K>(
elements,
keyOf,
compare,
start,
end,
target,
targetOffset,
);
return;
}
var middle = start + (length >> 1);
var firstLength = middle - start;
var secondLength = end - middle;
// Here secondLength >= firstLength (differs by at most one).
var targetMiddle = targetOffset + firstLength;
// Sort the second half into the end of the target area.
_mergeSort(elements, keyOf, compare, middle, end, target, targetMiddle);
// Sort the first half into the end of the source area.
_mergeSort(elements, keyOf, compare, start, middle, elements, middle);
// Merge the two parts into the target area.
_merge(
keyOf,
compare,
elements,
middle,
middle + firstLength,
target,
targetMiddle,
targetMiddle + secondLength,
target,
targetOffset,
);
}
/// Merges two lists into a target list.
///
/// One of the input lists may be positioned at the end of the target
/// list.
///
/// For equal object, elements from [firstList] are always preferred.
/// This allows the merge to be stable if the first list contains elements
/// that started out earlier than the ones in [secondList]
void _merge<E, K>(
K Function(E element) keyOf,
int Function(K, K) compare,
List<E> firstList,
int firstStart,
int firstEnd,
List<E> secondList,
int secondStart,
int secondEnd,
List<E> target,
int targetOffset,
) {
// No empty lists reaches here.
assert(firstStart < firstEnd);
assert(secondStart < secondEnd);
var cursor1 = firstStart;
var cursor2 = secondStart;
var firstElement = firstList[cursor1++];
var firstKey = keyOf(firstElement);
var secondElement = secondList[cursor2++];
var secondKey = keyOf(secondElement);
while (true) {
if (compare(firstKey, secondKey) <= 0) {
target[targetOffset++] = firstElement;
if (cursor1 == firstEnd) break; // Flushing second list after loop.
firstElement = firstList[cursor1++];
firstKey = keyOf(firstElement);
} else {
target[targetOffset++] = secondElement;
if (cursor2 != secondEnd) {
secondElement = secondList[cursor2++];
secondKey = keyOf(secondElement);
continue;
}
// Second list empties first. Flushing first list here.
target[targetOffset++] = firstElement;
target.setRange(
targetOffset,
targetOffset + (firstEnd - cursor1),
firstList,
cursor1,
);
return;
}
}
// First list empties first. Reached by break above.
target[targetOffset++] = secondElement;
target.setRange(
targetOffset,
targetOffset + (secondEnd - cursor2),
secondList,
cursor2,
);
}
/// Sorts a list between [start] (inclusive) and [end] (exclusive).
///
/// The sorting algorithm is a Pattern-defeating Quicksort (pdqsort), a
/// hybrid of Quicksort, Heapsort, and Insertion Sort.
void quickSort<E>(
List<E> elements,
int Function(E a, E b) compare, [
int start = 0,
int? end,
]) {
end = RangeError.checkValidRange(start, end, elements.length);
if (end - start < 2) return;
_pdqSortImpl(elements, compare, start, end, _log2(end - start));
}
/// Sorts a list between [start] (inclusive) and [end] (exclusive) by key.
///
/// Elements are ordered by the [compare] function applied to the result of
/// the [keyOf] function.
void quickSortBy<E, K>(
List<E> elements,
K Function(E element) keyOf,
int Function(K a, K b) compare, [
int start = 0,
int? end,
]) {
end = RangeError.checkValidRange(start, end, elements.length);
if (end - start < 2) return;
_pdqSortByImpl(elements, keyOf, compare, start, end, _log2(end - start));
}
/// Minimum list size below which pdqsort uses insertion sort.
const int _pdqInsertionSortThreshold = 32;
/// Computes the base-2 logarithm of [n].
///
/// Uses bitLength to compute the floor(log2(n)) efficiently.
/// For n == 0 we return 0.
int _log2(int n) => n > 0 ? n.bitLength - 1 : 0;
// ==========================================
// Implementation: Direct Comparison
// ==========================================
void _pdqSortImpl<E>(List<E> elements, int Function(E, E) compare, int start,
int end, int badAllowed) {
while (true) {
final size = end - start;
if (size < _pdqInsertionSortThreshold) {
_insertionSort(elements, compare, start, end);
return;
}
if (_handlePresorted(elements, compare, start, end)) return;
if (badAllowed == 0) {
_heapSort(elements, compare, start, end);
return;
}
final mid = start + size ~/ 2;
_selectPivot(elements, compare, start, mid, end, size);
final pivot = elements[start];
var less = start;
var equal = start + 1;
var greater = end;
while (equal < greater) {
final element = elements[equal];
final comparison = compare(element, pivot);
if (comparison < 0) {
elements[equal] = elements[less];
elements[less] = element;
less++;
equal++;
} else if (comparison > 0) {
greater--;
elements[equal] = elements[greater];
elements[greater] = element;
} else {
equal++;
}
}
if ((less - start) < size ~/ 8 || (end - greater) < size ~/ 8) {
badAllowed--;
}
if (less - start < end - greater) {
_pdqSortImpl(elements, compare, start, less, badAllowed);
start = greater;
} else {
_pdqSortImpl(elements, compare, greater, end, badAllowed);
end = less;
}
}
}
bool _handlePresorted<E>(
List<E> elements, int Function(E, E) compare, int start, int end) {
if (compare(elements[start], elements[start + 1]) > 0) {
// Check strictly decreasing
var i = start + 1;
while (i < end && compare(elements[i - 1], elements[i]) >= 0) {
i++;
}
if (i == end) {
_reverseRange(elements, start, end);
return true;
}
} else {
// Check non-decreasing
var i = start + 1;
while (i < end && compare(elements[i - 1], elements[i]) <= 0) {
i++;
}
if (i == end) return true;
}
return false;
}
void _reverseRange<E>(List<E> elements, int start, int end) {
var left = start;
var right = end - 1;
while (left < right) {
final temp = elements[left];
elements[left] = elements[right];
elements[right] = temp;
left++;
right--;
}
}
void _insertionSort<E>(
List<E> elements, int Function(E, E) compare, int start, int end) {
for (var i = start + 1; i < end; i++) {
var current = elements[i];
var j = i - 1;
while (j >= start && compare(elements[j], current) > 0) {
elements[j + 1] = elements[j];
j--;
}
elements[j + 1] = current;
}
}
void _heapSort<E>(
List<E> elements, int Function(E, E) compare, int start, int end) {
final n = end - start;
for (var i = n ~/ 2 - 1; i >= 0; i--) {
_siftDown(elements, compare, i, n, start);
}
for (var i = n - 1; i > 0; i--) {
final temp = elements[start];
elements[start] = elements[start + i];
elements[start + i] = temp;
_siftDown(elements, compare, 0, i, start);
}
}
void _siftDown<E>(
List<E> elements, int Function(E, E) compare, int i, int n, int start) {
var root = i;
while (true) {
final left = 2 * root + 1;
if (left >= n) break;
var largest = root;
if (compare(elements[start + left], elements[start + largest]) > 0) {
largest = left;
}
final right = left + 1;
if (right < n &&
compare(elements[start + right], elements[start + largest]) > 0) {
largest = right;
}
if (largest == root) break;
final temp = elements[start + root];
elements[start + root] = elements[start + largest];
elements[start + largest] = temp;
root = largest;
}
}
void _selectPivot<E>(List<E> elements, int Function(E, E) compare, int start,
int mid, int end, int size) {
if (size > 80) {
final s = size ~/ 8;
_sort3(elements, compare, start, start + s, start + 2 * s);
_sort3(elements, compare, mid - s, mid, mid + s);
_sort3(elements, compare, end - 1 - 2 * s, end - 1 - s, end - 1);
_sort3(elements, compare, start + s, mid, end - 1 - s);
} else {
_sort3(elements, compare, start, mid, end - 1);
}
final temp = elements[start];
elements[start] = elements[mid];
elements[mid] = temp;
}
void _sort3<E>(
List<E> elements, int Function(E, E) compare, int a, int b, int c) {
if (compare(elements[a], elements[b]) > 0) {
final t = elements[a];
elements[a] = elements[b];
elements[b] = t;
}
if (compare(elements[b], elements[c]) > 0) {
final t = elements[b];
elements[b] = elements[c];
elements[c] = t;
if (compare(elements[a], elements[b]) > 0) {
final t2 = elements[a];
elements[a] = elements[b];
elements[b] = t2;
}
}
}
// ==========================================
// Implementation: Keyed Comparison (By)
// ==========================================
/// [badAllowed] tracks how many bad pivot selections are allowed before
/// falling back to heap sort.
void _pdqSortByImpl<E, K>(List<E> elements, K Function(E) keyOf,
int Function(K, K) compare, int start, int end, int badAllowed) {
while (true) {
final size = end - start;
if (size < _pdqInsertionSortThreshold) {
_insertionSortBy(elements, keyOf, compare, start, end);
return;
}
if (_handlePresortedBy(elements, keyOf, compare, start, end)) return;
if (badAllowed == 0) {
_heapSortBy(elements, keyOf, compare, start, end);
return;
}
final mid = start + size ~/ 2;
_selectPivotBy(elements, keyOf, compare, start, mid, end, size);
final pivotElement = elements[start];
final pivotKey = keyOf(pivotElement);
var less = start;
var equal = start + 1;
var greater = end;
while (equal < greater) {
final element = elements[equal];
final elementKey = keyOf(element);
final comparison = compare(elementKey, pivotKey);
if (comparison < 0) {
elements[equal] = elements[less];
elements[less] = element;
less++;
equal++;
} else if (comparison > 0) {
greater--;
elements[equal] = elements[greater];
elements[greater] = element;
} else {
equal++;
}
}
if ((less - start) < size ~/ 8 || (end - greater) < size ~/ 8) {
badAllowed--;
}
if (less - start < end - greater) {
_pdqSortByImpl(elements, keyOf, compare, start, less, badAllowed);
start = greater;
} else {
_pdqSortByImpl(elements, keyOf, compare, greater, end, badAllowed);
end = less;
}
}
}
bool _handlePresortedBy<E, K>(List<E> elements, K Function(E) keyOf,
int Function(K, K) compare, int start, int end) {
if (compare(keyOf(elements[start]), keyOf(elements[start + 1])) > 0) {
var i = start + 1;
while (
i < end && compare(keyOf(elements[i - 1]), keyOf(elements[i])) >= 0) {
i++;
}
if (i == end) {
_reverseRange(elements, start, end);
return true;
}
} else {
var i = start + 1;
while (
i < end && compare(keyOf(elements[i - 1]), keyOf(elements[i])) <= 0) {
i++;
}
if (i == end) return true;
}
return false;
}
void _insertionSortBy<E, K>(List<E> elements, K Function(E) keyOf,
int Function(K, K) compare, int start, int end) {
for (var i = start + 1; i < end; i++) {
final current = elements[i];
final currentKey = keyOf(current);
var j = i - 1;
while (j >= start && compare(keyOf(elements[j]), currentKey) > 0) {
elements[j + 1] = elements[j];
j--;
}
elements[j + 1] = current;
}
}
void _heapSortBy<E, K>(List<E> elements, K Function(E) keyOf,
int Function(K, K) compare, int start, int end) {
final n = end - start;
for (var i = n ~/ 2 - 1; i >= 0; i--) {
_siftDownBy(elements, keyOf, compare, i, n, start);
}
for (var i = n - 1; i > 0; i--) {
final temp = elements[start];
elements[start] = elements[start + i];
elements[start + i] = temp;
_siftDownBy(elements, keyOf, compare, 0, i, start);
}
}
void _siftDownBy<E, K>(List<E> elements, K Function(E) keyOf,
int Function(K, K) compare, int i, int n, int start) {
var root = i;
while (true) {
final left = 2 * root + 1;
if (left >= n) break;
var largest = root;
var largestKey = keyOf(elements[start + largest]);
final leftKey = keyOf(elements[start + left]);
if (compare(leftKey, largestKey) > 0) {
largest = left;
largestKey = leftKey;
}
final right = left + 1;
if (right < n) {
final rightKey = keyOf(elements[start + right]);
if (compare(rightKey, largestKey) > 0) {
largest = right;
}
}
if (largest == root) break;
final temp = elements[start + root];
elements[start + root] = elements[start + largest];
elements[start + largest] = temp;
root = largest;
}
}
void _selectPivotBy<E, K>(List<E> elements, K Function(E) keyOf,
int Function(K, K) compare, int start, int mid, int end, int size) {
if (size > 80) {
final s = size ~/ 8;
_sort3By(elements, keyOf, compare, start, start + s, start + 2 * s);
_sort3By(elements, keyOf, compare, mid - s, mid, mid + s);
_sort3By(elements, keyOf, compare, end - 1 - 2 * s, end - 1 - s, end - 1);
_sort3By(elements, keyOf, compare, start + s, mid, end - 1 - s);
} else {
_sort3By(elements, keyOf, compare, start, mid, end - 1);
}
final temp = elements[start];
elements[start] = elements[mid];
elements[mid] = temp;
}
void _sort3By<E, K>(List<E> elements, K Function(E) keyOf,
int Function(K, K) compare, int a, int b, int c) {
if (compare(keyOf(elements[a]), keyOf(elements[b])) > 0) {
final t = elements[a];
elements[a] = elements[b];
elements[b] = t;
}
if (compare(keyOf(elements[b]), keyOf(elements[c])) > 0) {
final t = elements[b];
elements[b] = elements[c];
elements[c] = t;
if (compare(keyOf(elements[a]), keyOf(elements[b])) > 0) {
final t2 = elements[a];
elements[a] = elements[b];
elements[b] = t2;
}
}
}