LCOV - code coverage report
Current view: top level - collection-1.15.0/lib/src - algorithms.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 0 151 0.0 %
Date: 2021-11-28 14:37:50 Functions: 0 0 -

          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             : }

Generated by: LCOV version 1.14