Line data Source code
1 : // Copyright (c) 2020, 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 : // Extension methods on common collection types.
6 : import 'dart:collection';
7 : import 'dart:math';
8 :
9 : import 'algorithms.dart';
10 : import 'algorithms.dart' as algorithms;
11 : import 'equality.dart';
12 : import 'utils.dart';
13 :
14 : /// Various extensions on lists of arbitrary elements.
15 : extension ListExtensions<E> on List<E> {
16 : /// Returns the index of [element] in this sorted list.
17 : ///
18 : /// Uses binary search to find the location of [element].
19 : /// The list *must* be sorted according to [compare],
20 : /// otherwise the result is unspecified
21 : ///
22 : /// Returns -1 if [element] does not occur in this list.
23 0 : int binarySearch(E element, int Function(E, E) compare) =>
24 0 : algorithms.binarySearchBy<E, E>(this, identity, compare, element);
25 :
26 : /// Returns the index of [element] in this sorted list.
27 : ///
28 : /// Uses binary search to find the location of [element].
29 : /// The list *must* be sorted according to [compare] on the [keyOf] of elements,
30 : /// otherwise the result is unspecified.
31 : ///
32 : /// Returns -1 if [element] does not occur in this list.
33 : ///
34 : /// If [start] and [end] are supplied, only the list range from [start] to [end]
35 : /// is searched, and only that range needs to be sorted.
36 0 : int binarySearchByCompare<K>(
37 : E element, K Function(E element) keyOf, int Function(K, K) compare,
38 : [int start = 0, int? end]) =>
39 0 : algorithms.binarySearchBy<E, K>(
40 : this, keyOf, compare, element, start, end);
41 :
42 : /// Returns the index of [element] in this sorted list.
43 : ///
44 : /// Uses binary search to find the location of [element].
45 : /// The list *must* be sorted according to the natural ordering of
46 : /// the [keyOf] of elements, otherwise the result is unspecified.
47 : ///
48 : /// Returns -1 if [element] does not occur in this list.
49 : ///
50 : /// If [start] and [end] are supplied, only the list range from [start] to [end]
51 : /// is searched, and only that range needs to be sorted.
52 0 : int binarySearchBy<K extends Comparable<K>>(
53 : E element, K Function(E element) keyOf, [int start = 0, int? end]) =>
54 0 : algorithms.binarySearchBy<E, K>(
55 0 : this, keyOf, (a, b) => a.compareTo(b), element, start, end);
56 :
57 : /// Returns the index where [element] should be in this sorted list.
58 : ///
59 : /// Uses binary search to find the location of [element].
60 : /// The list *must* be sorted according to [compare],
61 : /// otherwise the result is unspecified.
62 : ///
63 : /// If [element] is in the list, its index is returned,
64 : /// otherwise returns the first position where adding [element]
65 : /// would keep the list sorted. This may be the [length] of
66 : /// the list if all elements of the list compare less than
67 : /// [element].
68 0 : int lowerBound(E element, int Function(E, E) compare) =>
69 0 : algorithms.lowerBoundBy<E, E>(this, identity, compare, element);
70 :
71 : /// Returns the index where [element] should be in this sorted list.
72 : ///
73 : /// Uses binary search to find the location of [element].
74 : /// The list *must* be sorted according to [compare] of
75 : /// the [keyOf] of the elements, otherwise the result is unspecified.
76 : ///
77 : /// If [element] is in the list, its index is returned,
78 : /// otherwise returns the first position where adding [element]
79 : /// would keep the list sorted. This may be the [length] of
80 : /// the list if all elements of the list compare less than
81 : /// [element].
82 : ///
83 : /// If [start] and [end] are supplied, only that range is searched,
84 : /// and only that range need to be sorted.
85 0 : int lowerBoundByCompare<K>(
86 : E element, K Function(E) keyOf, int Function(K, K) compare,
87 : [int start = 0, int? end]) =>
88 0 : algorithms.lowerBoundBy(this, keyOf, compare, element, start, end);
89 :
90 : /// Returns the index where [element] should be in this sorted list.
91 : ///
92 : /// Uses binary search to find the location of [element].
93 : /// The list *must* be sorted according to the
94 : /// natural ordering of the [keyOf] of the elements,
95 : /// otherwise the result is unspecified.
96 : ///
97 : /// If [element] is in the list, its index is returned,
98 : /// otherwise returns the first position where adding [element]
99 : /// would keep the list sorted. This may be the [length] of
100 : /// the list if all elements of the list compare less than
101 : /// [element].
102 : ///
103 : /// If [start] and [end] are supplied, only that range is searched,
104 : /// and only that range need to be sorted.
105 0 : int lowerBoundBy<K extends Comparable<K>>(E element, K Function(E) keyOf,
106 : [int start = 0, int? end]) =>
107 0 : algorithms.lowerBoundBy<E, K>(
108 : this, keyOf, compareComparable, element, start, end);
109 :
110 : /// Takes an action for each element.
111 : ///
112 : /// Calls [action] for each element along with the index in the
113 : /// iteration order.
114 0 : void forEachIndexed(void Function(int index, E element) action) {
115 0 : for (var index = 0; index < length; index++) {
116 0 : action(index, this[index]);
117 : }
118 : }
119 :
120 : /// Takes an action for each element as long as desired.
121 : ///
122 : /// Calls [action] for each element.
123 : /// Stops iteration if [action] returns `false`.
124 0 : void forEachWhile(bool Function(E element) action) {
125 0 : for (var index = 0; index < length; index++) {
126 0 : if (!action(this[index])) break;
127 : }
128 : }
129 :
130 : /// Takes an action for each element and index as long as desired.
131 : ///
132 : /// Calls [action] for each element along with the index in the
133 : /// iteration order.
134 : /// Stops iteration if [action] returns `false`.
135 0 : void forEachIndexedWhile(bool Function(int index, E element) action) {
136 0 : for (var index = 0; index < length; index++) {
137 0 : if (!action(index, this[index])) break;
138 : }
139 : }
140 :
141 : /// Maps each element and its index to a new value.
142 : Iterable<R> mapIndexed<R>(R Function(int index, E element) convert) sync* {
143 : for (var index = 0; index < length; index++) {
144 : yield convert(index, this[index]);
145 : }
146 : }
147 :
148 : /// The elements whose value and index satisfies [test].
149 : Iterable<E> whereIndexed(bool Function(int index, E element) test) sync* {
150 : for (var index = 0; index < length; index++) {
151 : var element = this[index];
152 : if (test(index, element)) yield element;
153 : }
154 : }
155 :
156 : /// The elements whose value and index do not satisfy [test].
157 : Iterable<E> whereNotIndexed(bool Function(int index, E element) test) sync* {
158 : for (var index = 0; index < length; index++) {
159 : var element = this[index];
160 : if (!test(index, element)) yield element;
161 : }
162 : }
163 :
164 : /// Expands each element and index to a number of elements in a new iterable.
165 : ///
166 : /// Like [Iterable.expand] except that the callback function is supplied with
167 : /// both the index and the element.
168 : Iterable<R> expandIndexed<R>(
169 : Iterable<R> Function(int index, E element) expand) sync* {
170 : for (var index = 0; index < length; index++) {
171 : yield* expand(index, this[index]);
172 : }
173 : }
174 :
175 : /// Sort a range of elements by [compare].
176 0 : void sortRange(int start, int end, int Function(E a, E b) compare) {
177 0 : quickSortBy<E, E>(this, identity, compare, start, end);
178 : }
179 :
180 : /// Sorts elements by the [compare] of their [keyOf] property.
181 : ///
182 : /// Sorts elements from [start] to [end], defaulting to the entire list.
183 0 : void sortByCompare<K>(
184 : K Function(E element) keyOf, int Function(K a, K b) compare,
185 : [int start = 0, int? end]) {
186 0 : quickSortBy(this, keyOf, compare, start, end);
187 : }
188 :
189 : /// Sorts elements by the natural order of their [keyOf] property.
190 : ///
191 : /// Sorts elements from [start] to [end], defaulting to the entire list.
192 0 : void sortBy<K extends Comparable<K>>(K Function(E element) keyOf,
193 : [int start = 0, int? end]) {
194 0 : quickSortBy<E, K>(this, keyOf, compareComparable, start, end);
195 : }
196 :
197 : /// Shuffle a range of elements.
198 0 : void shuffleRange(int start, int end, [Random? random]) {
199 0 : RangeError.checkValidRange(start, end, length);
200 0 : shuffle(this, start, end, random);
201 : }
202 :
203 : /// Reverses the elements in a range of the list.
204 0 : void reverseRange(int start, int end) {
205 0 : RangeError.checkValidRange(start, end, length);
206 0 : while (start < --end) {
207 0 : var tmp = this[start];
208 0 : this[start] = this[end];
209 0 : this[end] = tmp;
210 0 : start += 1;
211 : }
212 : }
213 :
214 : /// Swaps two elements of this list.
215 0 : void swap(int index1, int index2) {
216 0 : RangeError.checkValidIndex(index1, this, 'index1');
217 0 : RangeError.checkValidIndex(index2, this, 'index2');
218 0 : var tmp = this[index1];
219 0 : this[index1] = this[index2];
220 0 : this[index2] = tmp;
221 : }
222 :
223 : /// A fixed length view of a range of this list.
224 : ///
225 : /// The view is backed by this this list, which must not
226 : /// change its length while the view is being used.
227 : ///
228 : /// The view can be used to perform specific whole-list
229 : /// actions on a part of the list.
230 : /// For example, to see if a list contains more than one
231 : /// "marker" element, you can do:
232 : /// ```dart
233 : /// someList.slice(someList.indexOf(marker) + 1).contains(marker)
234 : /// ```
235 0 : ListSlice<E> slice(int start, [int? end]) {
236 0 : end = RangeError.checkValidRange(start, end, length);
237 : var self = this;
238 0 : if (self is ListSlice) return self.slice(start, end);
239 0 : return ListSlice<E>(this, start, end);
240 : }
241 :
242 : /// Whether [other] has the same elements as this list.
243 : ///
244 : /// Returns true iff [other] has the same [length]
245 : /// as this list, and the elemets of this list and [other]
246 : /// at the same indices are equal according to [equality],
247 : /// which defaults to using `==`.
248 0 : bool equals(List<E> other, [Equality<E> equality = const DefaultEquality()]) {
249 0 : if (length != other.length) return false;
250 0 : for (var i = 0; i < length; i++) {
251 0 : if (!equality.equals(this[i], other[i])) return false;
252 : }
253 : return true;
254 : }
255 : }
256 :
257 : /// Various extensions on lists of comparable elements.
258 : extension ListComparableExtensions<E extends Comparable<E>> on List<E> {
259 : /// Returns the index of [element] in this sorted list.
260 : ///
261 : /// Uses binary search to find the location of [element].
262 : /// The list *must* be sorted according to [compare],
263 : /// otherwise the result is unspecified.
264 : /// If [compare] is omitted, it uses the natural order of the elements.
265 : ///
266 : /// Returns -1 if [element] does not occur in this list.
267 0 : int binarySearch(E element, [int Function(E, E)? compare]) =>
268 0 : algorithms.binarySearchBy<E, E>(
269 : this, identity, compare ?? compareComparable, element);
270 :
271 : /// Returns the index where [element] should be in this sorted list.
272 : ///
273 : /// Uses binary search to find the location of where [element] should be.
274 : /// The list *must* be sorted according to [compare],
275 : /// otherwise the result is unspecified.
276 : /// If [compare] is omitted, it uses the natural order of the elements.
277 : ///
278 : /// If [element] does not occur in this list, the returned index is
279 : /// the first index where inserting [element] would keep the list
280 : /// sorted.
281 0 : int lowerBound(E element, [int Function(E, E)? compare]) =>
282 0 : algorithms.lowerBoundBy<E, E>(
283 : this, identity, compare ?? compareComparable, element);
284 :
285 : /// Sort a range of elements by [compare].
286 : ///
287 : /// If [compare] is omitted, the range is sorted according to the
288 : /// natural ordering of the elements.
289 0 : void sortRange(int start, int end, [int Function(E a, E b)? compare]) {
290 0 : RangeError.checkValidRange(start, end, length);
291 0 : algorithms.quickSortBy<E, E>(
292 : this, identity, compare ?? compareComparable, start, end);
293 : }
294 : }
295 :
296 : /// A list view of a range of another list.
297 : ///
298 : /// Wraps the range of the [source] list from [start] to [end]
299 : /// and acts like a fixed-length list view of that range.
300 : /// The source list must not change length while a list slice is being used.
301 : class ListSlice<E> extends ListBase<E> {
302 : /// Original length of [source].
303 : ///
304 : /// Used to detect modifications to [source] which may invalidate
305 : /// the slice.
306 : final int _initialSize;
307 :
308 : /// The original list backing this slice.
309 : final List<E> source;
310 :
311 : /// The start index of the slice.
312 : final int start;
313 :
314 : @override
315 : final int length;
316 :
317 : /// Creates a slice of [source] from [start] to [end].
318 0 : ListSlice(this.source, this.start, int end)
319 0 : : length = end - start,
320 0 : _initialSize = source.length {
321 0 : RangeError.checkValidRange(start, end, source.length);
322 : }
323 :
324 : // No argument checking, for internal use.
325 0 : ListSlice._(this._initialSize, this.source, this.start, this.length);
326 :
327 : /// The end index of the slice.
328 0 : int get end => start + length;
329 :
330 0 : @override
331 : E operator [](int index) {
332 0 : if (source.length != _initialSize) {
333 0 : throw ConcurrentModificationError(source);
334 : }
335 0 : RangeError.checkValidIndex(index, this, null, length);
336 0 : return source[start + index];
337 : }
338 :
339 0 : @override
340 : void operator []=(int index, E value) {
341 0 : if (source.length != _initialSize) {
342 0 : throw ConcurrentModificationError(source);
343 : }
344 0 : RangeError.checkValidIndex(index, this, null, length);
345 0 : source[start + index] = value;
346 : }
347 :
348 0 : @override
349 : void setRange(int start, int end, Iterable<E> iterable, [int skipCount = 0]) {
350 0 : if (source.length != _initialSize) {
351 0 : throw ConcurrentModificationError(source);
352 : }
353 0 : RangeError.checkValidRange(start, end, length);
354 0 : source.setRange(start + start, start + end, iterable, skipCount);
355 : }
356 :
357 : /// A fixed length view of a range of this list.
358 : ///
359 : /// The view is backed by this this list, which must not
360 : /// change its length while the view is being used.
361 : ///
362 : /// The view can be used to perform specific whole-list
363 : /// actions on a part of the list.
364 : /// For example, to see if a list contains more than one
365 : /// "marker" element, you can do:
366 : /// ```dart
367 : /// someList.slice(someList.indexOf(marker) + 1).contains(marker)
368 : /// ```
369 0 : ListSlice<E> slice(int start, [int? end]) {
370 0 : end = RangeError.checkValidRange(start, end, length);
371 0 : return ListSlice._(_initialSize, source, start + start, end - start);
372 : }
373 :
374 0 : @override
375 : void shuffle([Random? random]) {
376 0 : if (source.length != _initialSize) {
377 0 : throw ConcurrentModificationError(source);
378 : }
379 0 : algorithms.shuffle(source, start, end, random);
380 : }
381 :
382 0 : @override
383 : void sort([int Function(E a, E b)? compare]) {
384 0 : if (source.length != _initialSize) {
385 0 : throw ConcurrentModificationError(source);
386 : }
387 : compare ??= defaultCompare;
388 0 : quickSort(source, compare, start, start + length);
389 : }
390 :
391 : /// Sort a range of elements by [compare].
392 0 : void sortRange(int start, int end, int Function(E a, E b) compare) {
393 0 : if (source.length != _initialSize) {
394 0 : throw ConcurrentModificationError(source);
395 : }
396 0 : source.sortRange(start, end, compare);
397 : }
398 :
399 : /// Shuffles a range of elements.
400 : ///
401 : /// If [random] is omitted, a new instance of [Random] is used.
402 0 : void shuffleRange(int start, int end, [Random? random]) {
403 0 : if (source.length != _initialSize) {
404 0 : throw ConcurrentModificationError(source);
405 : }
406 0 : RangeError.checkValidRange(start, end, length);
407 0 : algorithms.shuffle(source, this.start + start, this.start + end, random);
408 : }
409 :
410 : /// Reverses a range of elements.
411 0 : void reverseRange(int start, int end) {
412 0 : RangeError.checkValidRange(start, end, length);
413 0 : source.reverseRange(this.start + start, this.start + end);
414 : }
415 :
416 : // Act like a fixed-length list.
417 :
418 0 : @override
419 : set length(int newLength) {
420 0 : throw UnsupportedError('Cannot change the length of a fixed-length list');
421 : }
422 :
423 0 : @override
424 : void add(E element) {
425 0 : throw UnsupportedError('Cannot add to a fixed-length list');
426 : }
427 :
428 0 : @override
429 : void insert(int index, E element) {
430 0 : throw UnsupportedError('Cannot add to a fixed-length list');
431 : }
432 :
433 0 : @override
434 : void insertAll(int index, Iterable<E> iterable) {
435 0 : throw UnsupportedError('Cannot add to a fixed-length list');
436 : }
437 :
438 0 : @override
439 : void addAll(Iterable<E> iterable) {
440 0 : throw UnsupportedError('Cannot add to a fixed-length list');
441 : }
442 :
443 0 : @override
444 : bool remove(Object? element) {
445 0 : throw UnsupportedError('Cannot remove from a fixed-length list');
446 : }
447 :
448 0 : @override
449 : void removeWhere(bool Function(E element) test) {
450 0 : throw UnsupportedError('Cannot remove from a fixed-length list');
451 : }
452 :
453 0 : @override
454 : void retainWhere(bool Function(E element) test) {
455 0 : throw UnsupportedError('Cannot remove from a fixed-length list');
456 : }
457 :
458 0 : @override
459 : void clear() {
460 0 : throw UnsupportedError('Cannot clear a fixed-length list');
461 : }
462 :
463 0 : @override
464 : E removeAt(int index) {
465 0 : throw UnsupportedError('Cannot remove from a fixed-length list');
466 : }
467 :
468 0 : @override
469 : E removeLast() {
470 0 : throw UnsupportedError('Cannot remove from a fixed-length list');
471 : }
472 :
473 0 : @override
474 : void removeRange(int start, int end) {
475 0 : throw UnsupportedError('Cannot remove from a fixed-length list');
476 : }
477 :
478 0 : @override
479 : void replaceRange(int start, int end, Iterable<E> newContents) {
480 0 : throw UnsupportedError('Cannot remove from a fixed-length list');
481 : }
482 : }
|