如何使用Dart将列表分割或切块成相等的部分?

61
假设我有一个列表,如下所示:
var letters = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h'];

我需要一个包含2个元素的列表的列表:
var chunks = [['a', 'b'], ['c', 'd'], ['e', 'f'], ['g', 'h']];

使用Dart语言,有什么好的方法可以实现这个功能?
21个回答

3

我建议创建一组成对的可迭代对象,并且如果您确实需要将其作为列表,则使用 .toList。这个解决方案也可以应用于任何可迭代对象,而不仅仅是列表。首先,一个只适用于列表(偶数长度)的简单解决方案(类似于Robert King提供的解决方案):

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

较为通用的解决方案比较复杂:
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))

似乎从流中生成一对的流要比从可迭代对象中生成一对的可迭代对象更容易。


我喜欢 Python 的一件事情就是它的 zip 函数,它可以让你轻松地创建成对的迭代器:pairs_iter = zip(it, it)。 - Rusty Rob
Dart 可能需要一个 zip 函数。columns = zip(rows) 或 rows = zip(columns) 是相当常见的用法。 - Rusty Rob
通常情况下,可以使用zip(repeat(it, n))来获取长度为n的块。 - Rusty Rob
你可以在Quiver的iterables库中找到zip函数。具体实现请参见此处 - cbracken

2

子列表

您可以使用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]

注意事项:

  • sublist 接受两个参数,start(包含)和 end(不包含)。
  • end 是可选的。如果您没有指定 end,则默认为列表结尾。
  • sublist 返回给定范围内的新列表。

哎呀,我错过了 Seth Ladd 的答案,他已经使用了 sublist。不过我会保留这个更通用的例子。 - Suragch

2

以下是使用索引for循环和泛型的旧式解决方案:

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;
}

使用这个例子

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

输出结果为:
[[a, b], [c, d], [e, f], [g, h]]

1

使用take和skip的子列表的替代方法。即使原始列表更大,每次取N个元素。

  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;
  }

输出:

(Link, Alloy, Mario)
(Hollow, Leon, Claire)
(Steve, Terry, Iori)
(King K. rool)

1

因为有些解决方案看起来比必要的复杂,所以再提供一种解决方案:

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;
  }
}

0
受到@Alan上面的回答和扩展列表的影响,F#中chunkedBySizewindowedaverage的等效方法可能是:
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));
}

实现可以在这里看到


0

将列表分割成大小为n的等长块(最后一个块是余数)

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);
  }
}

使用示例:

chunks([2, 3, 4, 5, 6, 7, 5, 20, 33], 4).forEach(print);
chunks(['a', 'b', 'c'], 2).forEach(print);

0

对于这个问题,我想要提供我的2分钱意见,我希望有一种解决方案可以接受负数(以允许逆序块),因此在这里:

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)),
    ];
  }
}

使用方法:

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]]
  • 完全类型化。
  • 支持任何类型。
  • 支持负数。
  • 在线尝试

0

现在Dart在列表文字中有了for循环,另一种可能的方法是:

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]
  ]
];

或者稍微短一些,但不如效率高:

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

这些通常也可以作为扩展方法来实现,例如:

extension ListChunk<T> on List<T> {
  List<List<T>> chunk(int chunkSize) =>
    ... `this` instead of `elements` ...
}

0
来晚了,但对于需要这个的人:一个基于扩展的解决方案:
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;
    }
  }
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接