59

Assume I have a list like:

var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];

I would like a list of lists of 2 elements each:

var chunks = [['a', 'b'], ['c', 'd'], ['e', 'f'], ['g', 'h']];

What's a good way to do this with Dart?

Andrew
  • 36,676
  • 11
  • 141
  • 113
Seth Ladd
  • 112,095
  • 66
  • 196
  • 279

21 Answers21

50

Here is another way:

  var chunks = [];
  int chunkSize = 2;
  for (var i = 0; i < letters.length; i += chunkSize) {
    chunks.add(letters.sublist(i, i+chunkSize > letters.length ? letters.length : i + chunkSize)); 
  }
  return chunks;

Run it on dartpad

genericUser
  • 4,417
  • 1
  • 28
  • 73
Seth Ladd
  • 112,095
  • 66
  • 196
  • 279
  • 4
    This answer does not work when the list does not contain a pair number of elements. You should add a check of whether you reached the end before calling sublist, otherwise you will get an error like `RangeError (end): Invalid value: Not in range 14..15, inclusive: 16` – Pom12 May 26 '20 at 16:32
  • 1
    chunks.add(letters.sublist(i, i+2 > letters.length ? letters.length : i + 2)); – Galeen Jul 24 '20 at 09:34
  • 1
    What about using ListComprehension? [for (int i = 0; i < list.length; i += chunkSize) list.sublist(i, min(i + chunkSize, list.length))] By using min(), you don't need to do any ternary check, and you are sure to get the remaining elements as a sublist, even though there are not enough to satisfy chunkSize. The result of above listComprehension looks very much like the result of quiver's partition function, as described by @cbracken. – Jebiel Nov 23 '21 at 14:16
  • @Pom12 I have edited the answer to support different sizes of chunks and added a dartpad link. Enjoy! – genericUser Dec 08 '21 at 13:52
37

Quiver (version >= 0.18) supplies partition() as part of its iterables library (import 'package:quiver/iterables.dart'). The implementation returns lazily-computed Iterable, making it pretty efficient. Use as:

var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
var pairs = partition(letters, 2);

The returned pairs will be an Iterable<List> that looks like:

[['a', 'b'], ['c', 'd'], ['e', 'f'], ['g', 'h']]
cbracken
  • 3,610
  • 2
  • 21
  • 20
24

A slight improvement on Seth's answer to make it work with any list or chunk size:

var len = letters.length;
var size = 2;
var chunks = [];

for(var i = 0; i< len; i+= size)
{    
    var end = (i+size<len)?i+size:len;
    chunks.add(letters.sublist(i,end));
}
Erik K.
  • 1,024
  • 12
  • 13
23

The official Dart's collection package has slices extension method, used like this:

final letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
final chunks = letters.slices(2); // [['a', 'b'], ['c', 'd'], ['e', 'f'], ['g', 'h']]
TheSpixxyQ
  • 744
  • 1
  • 6
  • 16
  • This is *the* go-to solution. Clean, simple, easy to read, and maintained by the dart team (package:collection). – venir Sep 01 '23 at 11:50
15
  pairs(list) => list.isEmpty ? list : ([list.take(2)]..addAll(pairs(list.skip(2))));
Alan Knight
  • 2,759
  • 1
  • 15
  • 13
8

I found a simple solution:

var subList = mylist.take(3); // take 3 items first
var subList = mylist.skip(2).take(3); // take [2..5] items
Mol0ko
  • 2,938
  • 1
  • 18
  • 45
7

another solution;

List chunk(List list, int chunkSize) {
  List chunks = [];
  int len = list.length;
  for (var i = 0; i < len; i += chunkSize) {
    int size = i+chunkSize;
    chunks.add(list.sublist(i, size > len ? len : size));
  }
  return chunks;
}

List nums = [1,2,3,4,5];

print(chunk(nums, 2));

// [[1,2], [3,4], [5]]
Ahmet Şimşek
  • 1,391
  • 1
  • 14
  • 24
5

Here is one way:

letters.fold([[]], (list, x) {    
  return list.last.length == 2 ? (list..add([x])) : (list..last.add(x));
});
Seth Ladd
  • 112,095
  • 66
  • 196
  • 279
5

another way:

extension IterableExtensions<E> on Iterable<E> {
  Iterable<List<E>> chunked(int chunkSize) sync* {
    if (length <= 0) {
      yield [];
      return;
    }
    int skip = 0;
    while (skip < length) {
      final chunk = this.skip(skip).take(chunkSize);
      yield chunk.toList(growable: false);
      skip += chunkSize;
      if (chunk.length < chunkSize) return;
    }
  }
}

tests:

void main() {
  test("list chunked", () {
    final emptyList = [];
    final letters = ['a', 'b', 'c', 'd', 'e', 'f'];
    final digits = List.generate(32, (index) => index);
    print(emptyList.chunked(2));
    print(letters.chunked(2));
    print(digits.chunked(2));

    print(emptyList.chunked(3));
    print(letters.chunked(3));
    print(digits.chunked(3));

    print(emptyList.chunked(5));
    print(letters.chunked(5));
    print(digits.chunked(5));
  });
}

output:

([])
([a, b], [c, d], [e, f])
([0, 1], [2, 3], [4, 5], [6, 7], [8, 9], [10, 11], ..., [28, 29], [30, 31])
([])
([a, b, c], [d, e, f])
([0, 1, 2], [3, 4, 5], [6, 7, 8], [9, 10, 11], ..., [27, 28, 29], [30, 31])
([])
([a, b, c, d, e], [f])
([0, 1, 2, 3, 4], [5, 6, 7, 8, 9], [10, 11, 12, 13, 14], ..., [25, 26, 27, 28, 29], [30, 31])
Autocrab
  • 3,474
  • 1
  • 15
  • 15
4

This way works with odd length lists:

  var nums = [1, 2, 3, 4, 5];
  var pairs = new List.generate(nums.length~/2, (i) => [nums[2 * i], nums[2 * i + 1]]);

Perhaps you might want to throw an error or provide a filler value if the list length is not even.

Rusty Rob
  • 16,489
  • 8
  • 100
  • 116
3

I would suggest creating an iterable of the pairs, and using .toList if you really need it as a list. This solution can also be applied to any iterable, not just a list. First, a simple solution that only works on lists (with even length)(Like the solution provided from Robert King):

new Iterable.generate(letters.length ~/ 2,
                      (i) => [letters[2*i], letters[2*i + 1]])

The more general solution is complex:

class mappedIterable extends Object implements Iterable with IterableMixin {
  Function generator;

  mappedIterable(Iterable source, Iterator this.generator(Iterator in));

  Iterator get iterator => generator(source.iterator);
}

class Pairs implements Iterator {
  Iterator _source;
  List _current = null;
  Pairs(Iterator this._source);

  List get current => _current;
  bool moveNext() {
    bool result = _source.moveNext();
    _current = [_source.current, (_source..moveNext()).current];
    return result;
  }
}

Iterable makePairs(Iterable source) =>
  new mappedIterable(source, (sourceIterator) => new Pairs(sourceIterator));

print(makePairs(letters))

It seems like it is actually easier to make a stream of pairs from a stream, than to make an iterable of pairs from an iterable.

whessedk
  • 31
  • 3
  • One thing I like about python is the zip function which allows you to go pairs_iter = zip(it, it) – Rusty Rob Mar 10 '14 at 00:29
  • dart could probably do with a zip function. columns = zip(rows) or rows = zip(columns) is fairly common. – Rusty Rob Mar 10 '14 at 00:30
  • also the general case would be zip(repeat(it, n)) to get chunks of length n. – Rusty Rob Mar 10 '14 at 00:30
  • You can find a zip function in [Quiver](https://pub.dartlang.org/packages/quiver)'s iterables library. Implementation is [here](https://github.com/google/quiver-dart/blob/master/lib/src/iterables/zip.dart). – cbracken Mar 10 '14 at 17:25
2

Here's the old style solution using indexed for loops and generics:

List<List<T>> _generateChunks<T>(List<T> inList, int chunkSize) {
  List<List<T>> outList = [];
  List<T> tmpList = [];
  int counter = 0;

  for (int current = 0; current < inList.length; current++) {
    if (counter != chunkSize) {
      tmpList.add(inList[current]);
      counter++;
    }
    if (counter == chunkSize || current == inList.length - 1) {
      outList.add(tmpList.toList());
      tmpList.clear();
      counter = 0;
    }
  }

  return outList;
}

Using the example

main() {
  var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
  int chunkSize = 2;
  List<List<String>> chunks = _generateChunks(letters, chunkSize);
  print(chunks);
}

The output is:

[[a, b], [c, d], [e, f], [g, h]]
Roberto Manfreda
  • 2,345
  • 3
  • 25
  • 39
2

Sublist

You can also extract part of a list using sublist:

var list = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];
final middle = list.length ~/ 2;

final part1 = list.sublist(0, middle);
final part2 = list.sublist(middle);

print(part1); // [a, b, c, d]
print(part2); // [e, f, g, h]

Notes:

  • sublist takes two parameters, start (inclusive) and end (exclusive).
  • end is optional. If you don't specify an end, then the default is the end of the list.
  • sublist returns a new list from the given range.
Suragch
  • 484,302
  • 314
  • 1,365
  • 1,393
  • Oops, I missed that Seth Ladd's answer already used `sublist`. I'll keep this as a more generalized example, though. – Suragch Nov 15 '21 at 04:13
1

One more solution because some of these look a bit more complicated than necessary:

extension _IterableExtensions<T> on Iterable<T> {
  Iterable<List<T>> chunks(int chunkSize) sync* {
    final chunk = <T>[];
    for (T item in this) {
      chunk.add(item);
      if (chunk.length == chunkSize) {
        yield chunk;
        chunk.clear();
      }
    }
    if (chunk.isNotEmpty) yield chunk;
  }
}
Gábor
  • 9,466
  • 3
  • 65
  • 79
1

Alternative to sublist using take and skip. Take N elements each time even if the original list is bigger.

  List<String> names = [
    "Link",
    "Alloy",
    "Mario",
    "Hollow",
    "Leon",
    "Claire",
    "Steve",
    "Terry",
    "Iori",
    "King K. rool"
  ];
  int length = names.length;
  int chunkSize = 3;
  int index = 0;
  while (index < length) {
    var chunk = names.skip(index).take(chunkSize);
    print(chunk);
    index += chunkSize;
  }

Output:

(Link, Alloy, Mario)
(Hollow, Leon, Claire)
(Steve, Terry, Iori)
(King K. rool)
Luis Cabrera Benito
  • 1,558
  • 13
  • 21
0

Influenced by @Alan's answer above and extending List, the equivalent of F# chunkedBySize and windowed and average could be:

import 'dart:collection';

class functionalList<E> extends ListBase<E> {
  final List<E> l = [];
  functionalList();

  void set length(int newLength) { l.length = newLength; }
  int get length => l.length;
  E operator [](int index) => l[index];
  void operator []=(int index, E value) { l[index] = value; }

  chunkBySize(int size) => _chunkBySize(l, size);

  windowed(int size) => _windowed(l, size);

  get average => l.isEmpty 
    ? 0 
    : l.fold(0, (t, e) => t + e) / l.length;

  _chunkBySize(List list, int size) => list.isEmpty
      ? list
      : ([list.take(size)]..addAll(_chunkBySize(list.skip(size), size)));

  _windowed(List list, int size) => list.isEmpty
    ? list
    : ([list.take(size)]..addAll(_windowed(list.skip(1), size)));
}

void main() {
  var list = new functionalList();

  list.addAll([1,2,3]);
  print(list.chunkBySize(2));
}

The implementation can be seen here

Hasan A Yousef
  • 22,789
  • 24
  • 132
  • 203
0

Late to the party, but to whomever needing this: an extension-based solution:

extension Windowed<E> on Iterable<E> {
  Iterable<List<E>> window(int size) sync* {
    if (size <= 0) throw ArgumentError.value(size, 'size', "can't be negative");
    final Iterator<E> iterator = this.iterator;
    while (iterator.moveNext()) {
      final List<E> slice = [iterator.current];
      for (int i = 1; i < size; i++) {
        if (!iterator.moveNext()) break;
        slice.add(iterator.current);
      }
      yield slice;
    }
  }
}
nyarian
  • 4,085
  • 1
  • 19
  • 51
0

Split list on equal chunks of size n (the last chunk is the remainder)

Iterable<List<T>> chunks<T>(List<T> lst, int n) sync* {
  final gen = List.generate(lst.length ~/ n + 1, (e) => e * n);
  for (int i in gen) {
    if (i < lst.length)
      yield lst.sublist(i, i + n < lst.length ? i + n : lst.length);
  }
}

Usage example:

chunks([2, 3, 4, 5, 6, 7, 5, 20, 33], 4).forEach(print);
chunks(['a', 'b', 'c'], 2).forEach(print);
Andrew
  • 36,676
  • 11
  • 141
  • 113
0

Now that Dart has for loops inside list literals, another possible approach is:

List<List<T>> chunk<T>(List<T> elements, int chunkSize) => [
  for (var i = 0; i < elements.length; i+= chunkSize) [
    for (var j = 0; j < chunkSize && i + j < elements.length; j++)
      elements[i + j]
  ]
];

or, slightly shorter, but not as efficient:

List<List<T>> chunk<T>(List<T> elements, int chunkSize) => [
  for (var i = 0; i < elements.length; i+= chunkSize) [
    ...elements.getRange(i, i + j)
  ]
];

Those can, as usual, also be made as extension methods instead, as:

extension ListChunk<T> on List<T> {
  List<List<T>> chunk(int chunkSize) =>
    ... `this` instead of `elements` ...
}
lrn
  • 64,680
  • 7
  • 105
  • 121
0

Adding my 2 cents on this question, I wish there was a solution that accepts negative numbers (to allow chunk in reverse order), so here we are:

import 'dart:math';

extension ChunkedList<T> on List<T> {
  List<List<T>> chunked(int size, {bool incomplete = false}) {
    if (size == 0) {
      throw ArgumentError.value(
        size,
        'chunked',
        '[size] must be a non-zero integer.',
      );
    }

    final List<T> target = size.isNegative ? reversed.toList() : toList();

    final int n = size.abs();

    final int base = incomplete ? (length / n).ceil() : (length / n).floor();

    return <List<T>>[
      for (int i = 0; i < base; i++)
        target.sublist(i * n, min((i + 1) * n, length)),
    ];
  }
}

Usage:

print(<int>[1, 2, 3, 4, 5].chunked(2, incomplete: false));  // [[1, 2], [3, 4]]
print(<int>[1, 2, 3, 4, 5].chunked(2, incomplete: true));   // [[1, 2], [3, 4], [5]]
print(<int>[1, 2, 3, 4, 5].chunked(-2, incomplete: false)); // [[5, 4], [3, 2]]
print(<int>[1, 2, 3, 4, 5].chunked(-2, incomplete: true));  // [[5, 4], [3, 2], [1]]
  • Fully-typed.
  • Supports any type.
  • Support negative numbers.
  • Try it online.
Alex Rintt
  • 1,618
  • 1
  • 11
  • 18
-1

This function returns the sublist from an original array given the chunk size.

chunkArray(List<dynamic> original, int size) {
        var sublist = List.generate((original.length ~/ size) + 1, (e) => []);
        for (var i = 0; i < sublist.length; i++) {
         int remaining=(original.length - i * size);
          sublist[i] = original.sublist(
              i * size,
              i * size +
                  ( remaining> size
                      ? size
                      : remaining));
    }
        return sublist;
      }
SabinK
  • 1