@@ -117,3 +117,241 @@ int binarySearch<T extends Comparable<Object>>(List<T> sortedList, T value) {
117117 }
118118 return - 1 ;
119119}
120+
121+ /// Limit below which merge sort defaults to insertion sort.
122+ const int _kMergeSortLimit = 32 ;
123+
124+ /// Sorts a list between `start` (inclusive) and `end` (exclusive) using the
125+ /// merge sort algorithm.
126+ ///
127+ /// If `compare` is omitted, this defaults to calling [Comparable.compareTo] on
128+ /// the objects. If any object is not [Comparable] , this throws a [TypeError]
129+ /// (The stack trace may call it `_CastError` or `_TypeError` , but to catch it,
130+ /// use [TypeError] ).
131+ ///
132+ /// Merge-sorting works by splitting the job into two parts, sorting each
133+ /// recursively, and then merging the two sorted parts.
134+ ///
135+ /// This takes on the order of `n * log(n)` comparisons and moves to sort `n`
136+ /// elements, but requires extra space of about the same size as the list being
137+ /// sorted.
138+ ///
139+ /// This merge sort is stable: Equal elements end up in the same order as they
140+ /// started in.
141+ ///
142+ /// For small lists (less than 32 elements), `mergeSort` automatically uses an
143+ /// insertion sort instead, as that is more efficient for small lists. The
144+ /// insertion sort is also stable.
145+ void mergeSort <T >(
146+ List <T > list, {
147+ int start = 0 ,
148+ int end,
149+ int Function (T , T ) compare,
150+ }) {
151+ end ?? = list.length;
152+ compare ?? = _defaultCompare <T >();
153+
154+ final int length = end - start;
155+ if (length < 2 ) {
156+ return ;
157+ }
158+ if (length < _kMergeSortLimit) {
159+ _insertionSort (list, compare: compare, start: start, end: end);
160+ return ;
161+ }
162+ // Special case the first split instead of directly calling _mergeSort,
163+ // because the _mergeSort requires its target to be different from its source,
164+ // and it requires extra space of the same size as the list to sort. This
165+ // split allows us to have only half as much extra space, and it ends up in
166+ // the original place.
167+ final int middle = start + ((end - start) >> 1 );
168+ final int firstLength = middle - start;
169+ final int secondLength = end - middle;
170+ // secondLength is always the same as firstLength, or one greater.
171+ final List <T > scratchSpace = List <T >(secondLength);
172+ _mergeSort (list, compare, middle, end, scratchSpace, 0 );
173+ final int firstTarget = end - firstLength;
174+ _mergeSort (list, compare, start, middle, list, firstTarget);
175+ _merge (compare, list, firstTarget, end, scratchSpace, 0 , secondLength, list, start);
176+ }
177+
178+ /// Returns a [Comparator] that asserts that its first argument is comparable.
179+ Comparator <T > _defaultCompare <T >() {
180+ // If we specify Comparable<T> here, it fails if the type is an int, because
181+ // int isn't a subtype of comparable. Leaving out the type implicitly converts
182+ // it to a num, which is a comparable.
183+ return (T value1, T value2) => (value1 as Comparable <dynamic >).compareTo (value2);
184+ }
185+
186+ /// Sort a list between `start` (inclusive) and `end` (exclusive) using
187+ /// insertion sort.
188+ ///
189+ /// If `compare` is omitted, this defaults to calling [Comparable.compareTo] on
190+ /// the objects. If any object is not [Comparable] , this throws a [TypeError]
191+ /// (The stack trace may call it `_CastError` or `_TypeError` , but to catch it,
192+ /// use [TypeError] ).
193+ ///
194+ /// Insertion sort is a simple sorting algorithm. For `n` elements it does on
195+ /// the order of `n * log(n)` comparisons but up to `n` squared moves. The
196+ /// sorting is performed in-place, without using extra memory.
197+ ///
198+ /// For short lists the many moves have less impact than the simple algorithm,
199+ /// and it is often the favored sorting algorithm for short lists.
200+ ///
201+ /// This insertion sort is stable: Equal elements end up in the same order as
202+ /// they started in.
203+ void _insertionSort <T >(
204+ List <T > list, {
205+ int Function (T , T ) compare,
206+ int start = 0 ,
207+ int end,
208+ }) {
209+ // If the same method could have both positional and named optional
210+ // parameters, this should be (list, [start, end], {compare}).
211+ compare ?? = _defaultCompare <T >();
212+ end ?? = list.length;
213+
214+ for (int pos = start + 1 ; pos < end; pos++ ) {
215+ int min = start;
216+ int max = pos;
217+ final T element = list[pos];
218+ while (min < max) {
219+ final int mid = min + ((max - min) >> 1 );
220+ final int comparison = compare (element, list[mid]);
221+ if (comparison < 0 ) {
222+ max = mid;
223+ } else {
224+ min = mid + 1 ;
225+ }
226+ }
227+ list.setRange (min + 1 , pos + 1 , list, min);
228+ list[min] = element;
229+ }
230+ }
231+
232+ /// Performs an insertion sort into a potentially different list than the one
233+ /// containing the original values.
234+ ///
235+ /// It will work in-place as well.
236+ void _movingInsertionSort <T >(
237+ List <T > list,
238+ int Function (T , T ) compare,
239+ int start,
240+ int end,
241+ List <T > target,
242+ int targetOffset,
243+ ) {
244+ final int length = end - start;
245+ if (length == 0 ) {
246+ return ;
247+ }
248+ target[targetOffset] = list[start];
249+ for (int i = 1 ; i < length; i++ ) {
250+ final T element = list[start + i];
251+ int min = targetOffset;
252+ int max = targetOffset + i;
253+ while (min < max) {
254+ final int mid = min + ((max - min) >> 1 );
255+ if (compare (element, target[mid]) < 0 ) {
256+ max = mid;
257+ } else {
258+ min = mid + 1 ;
259+ }
260+ }
261+ target.setRange (min + 1 , targetOffset + i + 1 , target, min);
262+ target[min] = element;
263+ }
264+ }
265+
266+ /// Sorts `list` from `start` to `end` into `target` at `targetOffset` .
267+ ///
268+ /// The `target` list must be able to contain the range from `start` to `end`
269+ /// after `targetOffset` .
270+ ///
271+ /// Allows target to be the same list as `list` , as long as it's not overlapping
272+ /// the `start..end` range.
273+ void _mergeSort <T >(
274+ List <T > list,
275+ int Function (T , T ) compare,
276+ int start,
277+ int end,
278+ List <T > target,
279+ int targetOffset,
280+ ) {
281+ final int length = end - start;
282+ if (length < _kMergeSortLimit) {
283+ _movingInsertionSort (list, compare, start, end, target, targetOffset);
284+ return ;
285+ }
286+ final int middle = start + (length >> 1 );
287+ final int firstLength = middle - start;
288+ final int secondLength = end - middle;
289+ // Here secondLength >= firstLength (differs by at most one).
290+ final int targetMiddle = targetOffset + firstLength;
291+ // Sort the second half into the end of the target area.
292+ _mergeSort (list, compare, middle, end, target, targetMiddle);
293+ // Sort the first half into the end of the source area.
294+ _mergeSort (list, compare, start, middle, list, middle);
295+ // Merge the two parts into the target area.
296+ _merge (
297+ compare,
298+ list,
299+ middle,
300+ middle + firstLength,
301+ target,
302+ targetMiddle,
303+ targetMiddle + secondLength,
304+ target,
305+ targetOffset,
306+ );
307+ }
308+
309+ /// Merges two lists into a target list.
310+ ///
311+ /// One of the input lists may be positioned at the end of the target list.
312+ ///
313+ /// For equal object, elements from `firstList` are always preferred. This
314+ /// allows the merge to be stable if the first list contains elements that
315+ /// started out earlier than the ones in `secondList` .
316+ void _merge <T >(
317+ int Function (T , T ) compare,
318+ List <T > firstList,
319+ int firstStart,
320+ int firstEnd,
321+ List <T > secondList,
322+ int secondStart,
323+ int secondEnd,
324+ List <T > target,
325+ int targetOffset,
326+ ) {
327+ // No empty lists reaches here.
328+ assert (firstStart < firstEnd);
329+ assert (secondStart < secondEnd);
330+ int cursor1 = firstStart;
331+ int cursor2 = secondStart;
332+ T firstElement = firstList[cursor1++ ];
333+ T secondElement = secondList[cursor2++ ];
334+ while (true ) {
335+ if (compare (firstElement, secondElement) <= 0 ) {
336+ target[targetOffset++ ] = firstElement;
337+ if (cursor1 == firstEnd) {
338+ // Flushing second list after loop.
339+ break ;
340+ }
341+ firstElement = firstList[cursor1++ ];
342+ } else {
343+ target[targetOffset++ ] = secondElement;
344+ if (cursor2 != secondEnd) {
345+ secondElement = secondList[cursor2++ ];
346+ continue ;
347+ }
348+ // Second list empties first. Flushing first list here.
349+ target[targetOffset++ ] = firstElement;
350+ target.setRange (targetOffset, targetOffset + (firstEnd - cursor1), firstList, cursor1);
351+ return ;
352+ }
353+ }
354+ // First list empties first. Reached by break above.
355+ target[targetOffset++ ] = secondElement;
356+ target.setRange (targetOffset, targetOffset + (secondEnd - cursor2), secondList, cursor2);
357+ }
0 commit comments