In Python, you can specify a "step" argument to a list slice that specifies the separation between indices that are selected to be in the slice:
my_list[start:stop:step]
However, none of the list methods in Dart seem to offer this functionality: sublist
and getRange
just take the start and end index.
How can I do this in Dart without using an ugly for-loop?
For example, to select only the even indices of a list I currently see no alternative to this:
List<Object> myList = ...;
List<Object> slice = [];
for (var i = 0; i < myList.length; i = 2) {
slice.add(myList[i]);
}
Or slightly less ugly with a list comprehension:
[for (var i = 0; i < myList.length; i = 2) myList[i]]
I could write my own function or extension method, but that defeats the purpose, I'm looking for ideally a built-in or a third package solution.
CodePudding user response:
For this you can create extension on list to return custom result.
List<T> slice([int? start, int? end, int? step]) {
if (start == null && end == null && step == null) {
return this!;
} else if (start != null && end == null && step == null) {
return this!.sublist(start);
} else if (start != null && end != null && step == null) {
return this!.sublist(start, end);
} else if (start != null && end != null && step != null) {
// iterate over the list and return the list
// iterator start from start index
// iterator end at end index
// iterator step by step
final list = <T>[];
for (var i = start; i < end; i = step) {
list.add(this![i]);
}
return list;
} else {
return this!;
}
}
You can use the slice extension on any list. Below are examples of how to use it.
Example 1
This example will return the slice list of the list depending starting and ending index.
final list1 = [1, 2, 3, 4, 5];
final result = list1.slice(1, 4);
print(result); // [2, 3, 4]
Example 2
This example will return the slice list of the list depending starting index.
final list1 = [1, 2, 3, 4, 5];
final result = list1.slice(1);
print(result); // [2, 3, 4, 5]
Complate program.
You can run this example in Dartpad to check results.
void main() {
final list1 = [1, 2, 3, 4, 5,6,7,8,9,10,11,12,13,14,15,15,17,18,19,20];
// Example - 1
final result = list1.slice(1, 4);
print(result); // [2, 3, 4]
//Example - 2
final result2 = list1.slice(10);
print(result2); // [11, 12, 13, 14, 15, 15, 17, 18, 19, 20]
//Example - 3
final result4 = list1.slice(4, 10, 2);
print(result4); // [5, 7, 9]
//Example - 4
final result3 = list1.slice();
print(result3); // [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 15, 17, 18, 19, 20]
}
extension ListHelper<T> on List<T>? {
List<T> slice([int? start, int? end, int? step]) {
if (start == null && end == null && step == null) {
return this!;
} else if (start != null && end == null && step == null) {
return this!.sublist(start);
} else if (start != null && end != null && step == null) {
return this!.sublist(start, end);
} else if (start != null && end != null && step != null) {
// iterate over the list and return the list
// iterator start from start index
// iterator end at end index
// iterator step by step
final list = <T>[];
for (var i = start; i < end; i = step) {
list.add(this![i]);
}
return list;
} else {
return this!;
}
}
}
CodePudding user response:
You can easily create your own slice
method in Dart.
The first thing to decide is whether you want it to be lazy or eager—does it create a list or an iterable. The traditional Dart way would be an iterable, created from another iterable, which is also slightly more complicated to write.
extension LazySlice<T> on Iterable<T> {
/// A sub-sequence ("slice") of the elements of this iterable.
///
/// The elements of this iterable starting at the [start]th
/// element, and ending before the [end]th element, or sooner
/// if this iterable has fewer than [end] elements.
/// If [end] is omitted, the sequence continues
/// to the end of this iterable.
/// If [step] is provided, only each [step]th element of the
/// [start]..[end] range is included, starting with the first,
/// and skipping `step - 1` elements after each that is included.
Iterable<T> slice([int start = 0, int? end, int step = 1]) {
// Check inputs.
RangeError.checkNotNegative(start, "start");
if (end != null && end < start) {
throw RangeError.range(end, start, null, "end");
}
if (step < 1) {
throw RangeError.range(step, 1, null, "step");
}
// Then return an iterable.
var iterable = this;
if (end != null) iterable = iterable.take(end);
if (start > 0) iterable = iterable.skip(start);
if (step != 1) iterable = iterable.step(step);
return iterable;
} // slice
/// Every [step] element.
///
/// The first element of this iterable, and then every
/// [step]th element after that (skipping `step - 1`
/// elements of this iterable between each element of
/// the returned iterable).
Iterable<T> step(int step) {
if (step == 1) return this;
if (step < 1) {
throw RangeError.range(step, 1, null, "step");
}
return _step(step);
}
/// [step] without parameter checking.
Iterable<T> _step(int step) sync* {
var it = iterator;
if (!it.moveNext()) return;
while (true) {
yield it.current;
for (var i = 0; i < step; i ) {
if (!it.moveNext()) return;
}
}
} // _step
} // extension LazySLice
Working with a list is much easier:
extension EagerSlice<T> on List<T> {
List<T> slice([int start = 0, int? end, int step = 1]) {
if (step == 1) return sublist(start, end); // Checks parameters.
end = RangeError.checkValidRange(start, end, length);
if (step < 1) {
throw RangeError.range(step, 1, null, "step");
}
return <T>[for (var i = start; i < end; i = step) this[i]];
}
}
(Effectively the same approach proposed by @Anakhand in the comments above, just with better parameter checking.)
The list approach is easier, mainly because we don't already have a step
method on iterables, which picks every n
th element.