Home > Software engineering >  How to slice a list with a custom step/increment in Dart?
How to slice a list with a custom step/increment in Dart?

Time:11-17

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 nth element.

  • Related