首页 > 解决方案 > 意外的迭代器函数和 Iterable.take 行为

问题描述

我正在编写一个名为批处理的简单函数,它应该将可迭代拆分为可迭代的大小大小的可迭代。然后我遇到了生成器函数和 Iterable.take 方法的奇怪行为(当期望/知道 python 生成器行为时)。

这段代码:

Iterable<T> iterate<T>(Iterable<T> iterable) sync* {
  print('generator started');
  for (var item in iterable)
    yield item;
}

void main() {
  List l = [1, 2, 3, 4, 5];
  final it = iterate(l);
  print(it.take(2));
  print(it.take(2));
}

输出:

generator started
(1, 2)
generator started
(1, 2)

而预期的输出是:

generator started
(1, 2)
(3, 4)
  1. 为什么iterate调用两次而不是在产量之后继续下一次迭代?
  2. 编写以下解决方法的任何内置或更优雅的方式?我的解决方法:
Iterable<List<T>> batches<T>(Iterable<T> iterable, int size) sync* {
  final iter = iterable.iterator;
  List group = takeN(iter, size).toList();
  while (group.length > 0){
    yield group;
    group = takeN(iter, size).toList();
  }
}


Iterable<T> takeN<T>(Iterator<T> iterator, int n) sync* {
  for (var i = 0; i < n && iterator.moveNext(); i++)
    yield iterator.current;
}


void main() {
  List l = [1, 2, 3, 4, 5];
  print(batches(l, 2));
}

标签: dart

解决方案


这就是可迭代对象和迭代器的工作方式。

AnIterable是一个简单的对象,它在您开始迭代之前什么都不做。AnIterator是保持迭代状态的那个。

当你调用一个sync*函数时,它会立即返回一个Iterable. 当您开始迭代该可迭代对象时,通过读取它的iteratorgetter 并使用返回Iterator的 ,sync*函数体开始运行。每次调用moveNext都会运行身体,直到下一次yield

每次你得到一个新iterator的,函数体都是从头开始的。这就是为什么你的两个调用it.take(2)都做同样的事情,每个都通过获取一个新的迭代器并调用moveNext两次来工作。

至于您想要做的更简单的方法,也许是:

Iterable<List<T>> batch<T>(Iterable<T> source, int size) {
  List<T> accumulator;
  for (var value in source) {
    (accumulator ??= []).add(value);
    if (accumulator.length == size) {
      yield accumulator;
      accumulator = null;
    }
  }
  if (accumulator != null) yield accumulator;
}

推荐阅读