OLD | NEW |
(Empty) | |
| 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 compare ??= defaultCompare<T>(); |
| 20 int min = 0; |
| 21 int max = sortedList.length; |
| 22 while (min < max) { |
| 23 int mid = min + ((max - min) >> 1); |
| 24 var element = sortedList[mid]; |
| 25 int comp = compare(element, value); |
| 26 if (comp == 0) return mid; |
| 27 if (comp < 0) { |
| 28 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 compare ??= defaultCompare<T>(); |
| 49 int min = 0; |
| 50 int max = sortedList.length; |
| 51 while (min < max) { |
| 52 int mid = min + ((max - min) >> 1); |
| 53 var element = sortedList[mid]; |
| 54 int comp = compare(element, value); |
| 55 if (comp < 0) { |
| 56 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 var random = new math.Random(); |
| 69 if (end == null) end = list.length; |
| 70 int length = end - start; |
| 71 while (length > 1) { |
| 72 int pos = random.nextInt(length); |
| 73 length--; |
| 74 var tmp1 = list[start + pos]; |
| 75 list[start + pos] = list[start + length]; |
| 76 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 if (end == null) end = list.length; |
| 83 _reverse(list, start, end); |
| 84 } |
| 85 |
| 86 /// Internal helper function that assumes valid arguments. |
| 87 void _reverse(List list, int start, int end) { |
| 88 for (int i = start, j = end - 1; i < j; i++, j--) { |
| 89 var tmp = list[i]; |
| 90 list[i] = list[j]; |
| 91 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 compare ??= defaultCompare<T>(); |
| 115 end ??= list.length; |
| 116 |
| 117 for (int pos = start + 1; pos < end; pos++) { |
| 118 int min = start; |
| 119 int max = pos; |
| 120 var element = list[pos]; |
| 121 while (min < max) { |
| 122 int mid = min + ((max - min) >> 1); |
| 123 int comparison = compare(element, list[mid]); |
| 124 if (comparison < 0) { |
| 125 max = mid; |
| 126 } else { |
| 127 min = mid + 1; |
| 128 } |
| 129 } |
| 130 list.setRange(min + 1, pos + 1, list, min); |
| 131 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 end ??= list.length; |
| 156 compare ??= defaultCompare<T>(); |
| 157 |
| 158 int length = end - start; |
| 159 if (length < 2) return; |
| 160 if (length < _MERGE_SORT_LIMIT) { |
| 161 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 int middle = start + ((end - start) >> 1); |
| 171 int firstLength = middle - start; |
| 172 int secondLength = end - middle; |
| 173 // secondLength is always the same as firstLength, or one greater. |
| 174 var scratchSpace = new List<T>(secondLength); |
| 175 _mergeSort(list, compare, middle, end, scratchSpace, 0); |
| 176 int firstTarget = end - firstLength; |
| 177 _mergeSort(list, compare, start, middle, list, firstTarget); |
| 178 _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 int length = end - start; |
| 189 if (length == 0) return; |
| 190 target[targetOffset] = list[start]; |
| 191 for (int i = 1; i < length; i++) { |
| 192 var element = list[start + i]; |
| 193 int min = targetOffset; |
| 194 int max = targetOffset + i; |
| 195 while (min < max) { |
| 196 int mid = min + ((max - min) >> 1); |
| 197 if (compare(element, target[mid]) < 0) { |
| 198 max = mid; |
| 199 } else { |
| 200 min = mid + 1; |
| 201 } |
| 202 } |
| 203 target.setRange(min + 1, targetOffset + i + 1, target, min); |
| 204 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 int length = end - start; |
| 218 if (length < _MERGE_SORT_LIMIT) { |
| 219 _movingInsertionSort(list, compare, start, end, target, targetOffset); |
| 220 return; |
| 221 } |
| 222 int middle = start + (length >> 1); |
| 223 int firstLength = middle - start; |
| 224 int secondLength = end - middle; |
| 225 // Here secondLength >= firstLength (differs by at most one). |
| 226 int targetMiddle = targetOffset + firstLength; |
| 227 // Sort the second half into the end of the target area. |
| 228 _mergeSort(list, compare, middle, end, target, targetMiddle); |
| 229 // Sort the first half into the end of the source area. |
| 230 _mergeSort(list, compare, start, middle, list, middle); |
| 231 // Merge the two parts into the target area. |
| 232 _merge(compare, list, middle, middle + firstLength, target, targetMiddle, |
| 233 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 var firstElement = firstList[cursor1++]; |
| 260 var secondElement = secondList[cursor2++]; |
| 261 while (true) { |
| 262 if (compare(firstElement, secondElement) <= 0) { |
| 263 target[targetOffset++] = firstElement; |
| 264 if (cursor1 == firstEnd) break; // Flushing second list after loop. |
| 265 firstElement = firstList[cursor1++]; |
| 266 } else { |
| 267 target[targetOffset++] = secondElement; |
| 268 if (cursor2 != secondEnd) { |
| 269 secondElement = secondList[cursor2++]; |
| 270 continue; |
| 271 } |
| 272 // Second list empties first. Flushing first list here. |
| 273 target[targetOffset++] = firstElement; |
| 274 target.setRange(targetOffset, targetOffset + (firstEnd - cursor1), |
| 275 firstList, cursor1); |
| 276 return; |
| 277 } |
| 278 } |
| 279 // First list empties first. Reached by break above. |
| 280 target[targetOffset++] = secondElement; |
| 281 target.setRange( |
| 282 targetOffset, targetOffset + (secondEnd - cursor2), secondList, cursor2); |
| 283 } |
OLD | NEW |