迭代器
提供一種方法來順序訪問聚合物件中的元素,而無需公開其底層表示。
示例
在 Java 中,介面 java.util.Iterator<E> 是迭代器模式的一種實現。這樣,所有實現 java.lang.Iterable<T> 介面的物件都不需要此模式的便捷實現。
成本
此模式有一定的成本。僅對重要數量的程式碼實現此模式。IDE 重構幫不上你太多忙。
建立
此模式的建立也有一定的成本。
維護
此模式易於維護。
移除
此模式的移除也有一定的成本。
建議
- 在迭代器類的名稱中使用“iterator”一詞,以便向其他開發人員指示模式的使用。
實現
Java 擁有 Iterator 介面。
一個簡單的示例,展示瞭如何使用 Iterator 返回 [start, end] 之間的整數
import java.util.Iterator;
import java.util.NoSuchElementException;
public class RangeIteratorExample {
public static Iterator<Integer> range(int start, int end) {
return new Iterator<>() {
private int index = start;
@Override
public boolean hasNext() {
return index < end;
}
@Override
public Integer next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
return index++;
}
};
}
public static void main(String[] args) {
var iterator = range(0, 10);
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
// or using a lambda
iterator.forEachRemaining(System.out::println);
}
}
從 Java 5 開始,實現 Template:Javadoc:SE 介面的物件(該介面從其唯一方法返回一個 Iterator)可以使用 Java 的 foreach 迴圈 語法進行遍歷。Template:Javadoc:SE 介面來自 Java 集合框架,擴充套件了 Iterable。
Family 類實現 Iterable 介面的示例import java.util.Iterator;
import java.util.Set;
class Family<E> implements Iterable<E> {
private final Set<E> elements;
public Family(Set<E> elements) {
this.elements = Set.copyOf(elements);
}
@Override
public Iterator<E> iterator() {
return elements.iterator();
}
}
IterableExample 類演示了 Family 類的用法public class IterableExample {
public static void main(String[] args) {
var weasleys = Set.of(
"Arthur", "Molly", "Bill", "Charlie",
"Percy", "Fred", "George", "Ron", "Ginny"
);
var family = new Family<>(weasleys);
for (var name : family) {
System.out.println(name + " Weasley");
}
}
}
Ron Weasley
Molly Weasley
Percy Weasley
Fred Weasley
Charlie Weasley
George Weasley
Arthur Weasley
Ginny Weasley
Bill Weasley
以下是 Java 中的另一個示例
import java.util.BitSet;
import java.util.Iterator;
public class BitSetIterator implements Iterator<Boolean> {
private final BitSet bitset;
private int index = 0;
public BitSetIterator(BitSet bitset) {
this.bitset = bitset;
}
public boolean hasNext() {
return index < bitset.length();
}
public Boolean next() {
if (index >= bitset.length()) {
throw new NoSuchElementException();
}
return bitset.get(index++);
}
public void remove() {
throw new UnsupportedOperationException();
}
}
兩個不同的使用示例
import java.util.BitSet;
public class TestClientBitSet {
public static void main(String[] args) {
// create BitSet and set some bits
BitSet bitset = new BitSet();
bitset.set(1);
bitset.set(3400);
bitset.set(20);
bitset.set(47);
for (BitSetIterator iter = new BitSetIterator(bitset); iter.hasNext(); ) {
Boolean b = iter.next();
String tf = (b.booleanValue() ? "T" : "F");
System.out.print(tf);
}
System.out.println();
}
}
import java.util.ArrayList;
import java.util.Collection;
public class TestClientIterator {
public static void main(String[] args) {
Collection<Object> al = new ArrayList<Object>();
al.add(new Integer(42));
al.add("test");
al.add(new Double("-12.34"));
for (Iterator<Object> iter = al.iterator(); iter.hasNext(); ) {
System.out.println(iter.next());
}
for (Object o : al) {
System.out.println(o);
}
}
}
JavaScript 作為 ECMAScript 6 的一部分,支援任何提供 next() 方法的物件的迭代器模式,該方法返回一個具有兩個特定屬性的物件:done 和 value。以下是一個顯示反向陣列迭代器的示例
function reverseArrayIterator(array) {
var index = array.length - 1;
return {
next: () =>
index >= 0 ?
{value: array[index--], done: false} :
{done: true}
}
}
const it = reverseArrayIterator(['three', 'two', 'one']);
console.log(it.next().value); //-> 'one'
console.log(it.next().value); //-> 'two'
console.log(it.next().value); //-> 'three'
console.log(`Are you done? ${it.next().done}`); //-> true
但是,大多數情況下,希望在物件上提供 Iterator[1] 語義,以便可以透過 for...of 迴圈自動迭代它們。JavaScript 的一些內建型別(如 Array、Map 或 Set)已經定義了自己的迭代行為。可以透過定義物件的元 @@iterator 方法(也稱為 Symbol.iterator)來實現相同的效果。這將建立一個 Iterable 物件。
以下是一個範圍函式的示例,該函式使用常規 for 迴圈生成從 start 到 end(不包括 end)的值列表,以生成數字
function range(start, end) {
return {
[Symbol.iterator]() { // #A
return this;
},
next() {
if (start < end) {
return { value: start++, done: false }; // #B
}
return { done: true, value: end }; // #B
}
}
}
for (number of range(1, 5)) {
console.log(number); // -> 1, 2, 3, 4
}
還可以操作內建型別的迭代機制,例如字串
let iter = ['I', 't', 'e', 'r', 'a', 't', 'o', 'r'][Symbol.iterator]();
iter.next().value; //-> I
iter.next().value; //-> t
.NET Framework 具有支援簡單迭代的特殊介面:System.Collections.IEnumerator 用於非泛型集合,System.Collections.Generic.IEnumerator<T> 用於泛型集合。
C# 語句 foreach 旨在輕鬆迭代實現 System.Collections.IEnumerator 和/或 System.Collections.Generic.IEnumerator<T> 介面的集合。從 C# v2 開始,foreach 也能夠迭代實現 System.Collections.Generic.IEnumerable<T> 和 System.Collections.Generic.IEnumerator<T> 的型別[2]
使用 foreach 語句的示例
var primes = new List<int>{ 2, 3, 5, 7, 11, 13, 17, 19 };
long m = 1;
foreach (var prime in primes)
m *= prime;
以下是 C# 中的另一個示例
using System;
using System.Collections;
class MainApp
{
static void Main()
{
ConcreteAggregate a = new ConcreteAggregate();
a[0] = "Item A";
a[1] = "Item B";
a[2] = "Item C";
a[3] = "Item D";
// Create Iterator and provide aggregate
ConcreteIterator i = new ConcreteIterator(a);
Console.WriteLine("Iterating over collection:");
object item = i.First();
while (item != null)
{
Console.WriteLine(item);
item = i.Next();
}
// Wait for user
Console.Read();
}
}
// "Aggregate"
abstract class Aggregate
{
public abstract Iterator CreateIterator();
}
// "ConcreteAggregate"
class ConcreteAggregate : Aggregate
{
private ArrayList items = new ArrayList();
public override Iterator CreateIterator()
{
return new ConcreteIterator(this);
}
// Property
public int Count
{
get{ return items.Count; }
}
// Indexer
public object this[int index]
{
get{ return items[index]; }
set{ items.Insert(index, value); }
}
}
// "Iterator"
abstract class Iterator
{
public abstract object First();
public abstract object Next();
public abstract bool IsDone();
public abstract object CurrentItem();
}
// "ConcreteIterator"
class ConcreteIterator : Iterator
{
private ConcreteAggregate aggregate;
private int current = 0;
// Constructor
public ConcreteIterator(ConcreteAggregate aggregate)
{
this.aggregate = aggregate;
}
public override object First()
{
return aggregate[0];
}
public override object Next()
{
object ret = null;
if (current < aggregate.Count - 1)
{
ret = aggregate[++current];
}
return ret;
}
public override object CurrentItem()
{
return aggregate[current];
}
public override bool IsDone()
{
return current >= aggregate.Count ? true : false ;
}
}
PHP 透過 Iterator 介面支援迭代器模式,作為標準發行版的一部分。[3] 實現該介面的物件可以使用 foreach 語言結構進行迭代。
使用 PHP 的模式示例
<?php
// BookIterator.php
namespace DesignPatterns;
class BookIterator implements \Iterator
{
private int $i_position = 0;
private BookCollection $booksCollection;
public function __construct(BookCollection $booksCollection)
{
$this->booksCollection = $booksCollection;
}
public function current()
{
return $this->booksCollection->getTitle($this->i_position);
}
public function key(): int
{
return $this->i_position;
}
public function next(): void
{
$this->i_position++;
}
public function rewind(): void
{
$this->i_position = 0;
}
public function valid(): bool
{
return !is_null($this->booksCollection->getTitle($this->i_position));
}
}
<?php
// BookCollection.php
namespace DesignPatterns;
class BookCollection implements \IteratorAggregate
{
private array $a_titles = array();
public function getIterator()
{
return new BookIterator($this);
}
public function addTitle(string $string): void
{
$this->a_titles[] = $string;
}
public function getTitle($key)
{
if (isset($this->a_titles[$key])) {
return $this->a_titles[$key];
}
return null;
}
public function is_empty(): bool
{
return empty($this->$a_titles);
}
}
<?php
// index.php
require 'vendor/autoload.php';
use DesignPatterns\BookCollection;
$booksCollection = new BookCollection();
$booksCollection->addTitle('Design Patterns');
$booksCollection->addTitle('PHP7 is the best');
$booksCollection->addTitle('Laravel Rules');
$booksCollection->addTitle('DHH Rules');
foreach ($booksCollection as $book) {
var_dump($book);
}
輸出
string(15) "Design Patterns" string(16) "PHP7 is the best" string(13) "Laravel Rules" string(9) "DHH Rules"
另一個示例:作為 PHP 5 中的預設行為,在 foreach 結構中使用物件將遍歷所有公共值。PHP 提供多個 Iterator 類,允許你迭代常見的列表,例如目錄、XML 結構和遞迴陣列。可以透過實現 Iterator 介面來定義你自己的 Iterator 類,這將覆蓋預設行為。Iterator 介面定義
interface Iterator
{
// Returns the current value
function current();
// Returns the current key
function key();
// Moves the internal pointer to the next element
function next();
// Moves the internal pointer to the first element
function rewind();
// If the current element is valid (boolean)
function valid();
}
這些方法都在完整的 foreach( $object as $key=>$value ) 序列中使用。方法按以下順序執行
rewind()
while valid() {
current() in $value
key() in $key
next()
}
End of Loop
根據 Zend 的說法,current() 方法在 valid() 方法之前和之後都被呼叫。
在 Perl 中,提供迭代器介面的物件要麼 過載 <>(迭代器運算子),[4] 要麼提供一個雜湊或 繫結雜湊 介面,可以使用 each 進行迭代。[5] <> 和 each 都在迭代完成時返回 undef。過載的 <> 運算子
# fibonacci sequence
package FibIter;
use overload
'<>' => 'next_fib';
sub new {
my $class = shift;
bless { index => 0, values => [0, 1] }, $class
}
sub next_fib {
my $self = shift;
my $i = $self->{index}++;
$self->{values}[$i] ||=
$i > 1 ? $self->{values}[-2]+$self->{values}[-1]
: ($self->{values}[$i]);
}
# reset iterator index
sub reset {
my $self = shift;
$self->{index} = 0
}
package main;
my $iter = FibIter->new;
while (my $fib = <$iter>) {
print "$fib","\n";
}
迭代雜湊(或繫結雜湊)
# read from a file like a hash
package HashIter;
use base 'Tie::Hash';
sub new {
my ($class, $fname) = @_;
my $obj = bless {}, $class;
tie %$obj, $class, $fname;
bless $obj, $class;
}
sub TIEHASH {
# tie hash to a file
my $class = shift;
my $fname = shift or die 'Need filename';
die "File $fname must exist"
unless [-f $fname];
open my $fh, '<', $fname or die "open $!";
bless { fname => $fname, fh => $fh }, $class;
}
sub FIRSTKEY {
# (re)start iterator
my $self = shift;
my $fh = $self->{fh};
if (not fileno $self->{fh}) {
open $fh, '<', $self->{fname} or die "open $!";
}
# reset file pointer
seek( $fh, 0, 0 );
chomp(my $line = <$fh>);
$line
}
sub NEXTKEY {
# next item from iterator
my $self = shift;
my $fh = $self->{fh};
return if eof($fh);
chomp(my $line = <$fh>);
$line
}
sub FETCH {
# get value for key, in this case we don't
# care about the values, just return
my ($self, $key) = @_;
return
}
sub main;
# iterator over a word file
my $word_iter = HashIter->new('/usr/share/dict/words');
# iterate until we get to abacus
while (my $word = each( %$word_iter )) {
print "$word\n";
last if $word eq 'abacus'
}
# call keys %tiedhash in void context to reset iterator
keys %$word_iter;
Python 將迭代器的語法作為語言本身的一部分進行規定,以便語言關鍵字(如 for)與 Python 所稱的可迭代物件一起使用。可迭代物件具有一個 __iter__() 方法,該方法返回一個迭代器物件。“迭代器協議”要求 next() 返回下一個元素,或者在到達序列末尾時引發 StopIteration 異常。迭代器還提供一個 __iter__() 方法,該方法返回自身,以便它們也可以被迭代;例如,使用 for 迴圈。從 2.2 版開始提供生成器。
在 Python 3 中,next() 重新命名為 __next__()。[6]
在 Python 中,迭代器是遵循迭代器協議的物件。你可以使用 iter() 方法從任何序列(即集合:列表、元組、字典、集合等)獲取迭代器。獲取迭代器的另一種方法是建立生成器,生成器是一種迭代器。要從迭代器獲取下一個元素,可以使用 next() 方法(Python 2)/ next() 函式(Python 3)。當沒有更多元素時,它會引發 StopIteration 異常。要實現你自己的迭代器,你只需要一個實現 next() 方法(Python 2)/ __next__() 方法(Python 3)的物件。以下是兩個用例
# from a sequence
x = [42, "test", -12.34]
it = iter(x)
try:
while True:
x = next(it) # in Python 2, you would use it.next()
print x
except StopIteration:
pass
# a generator
def foo(n):
for i in range(n):
yield i
it = foo(5)
try:
while True:
x = next(it) # in Python 2, you would use it.next()
print x
except StopIteration:
pass
Raku 提供了迭代器的 API,作為語言本身的一部分,用於可以使用 for 和相關迭代結構(如分配給 Positional 變數)進行迭代的物件。[7][8] 可迭代物件必須至少實現一個 iterator 方法,該方法返回一個迭代器物件。“迭代器協議”要求 pull-one 方法在可能的情況下返回下一個元素,或者如果無法生成更多值則返回哨兵值 IterationEnd。迭代 API 是透過組合 Iterable 角色、Iterator 或兩者,並實現所需的方法來提供的。
要檢查型別物件或物件例項是否可迭代,可以使用 does 方法
say Array.does(Iterable); #=> True
say [1, 2, 3].does(Iterable); #=> True
say Str.does(Iterable); #=> False
say "Hello".does(Iterable); #=> False
當且僅當呼叫者符合引數型別時,does 方法才返回 True。
以下是一個 range 子例程的示例,它模擬了 Python 的 range 函式
multi range(Int:D $start, Int:D $end where * <= $start, Int:D $step where * < 0 = -1) {
(class {
also does Iterable does Iterator;
has Int ($.start, $.end, $.step);
has Int $!count = $!start;
method iterator { self }
method pull-one(--> Mu) {
if $!count > $!end {
my $i = $!count;
$!count += $!step;
return $i;
}
else {
$!count = $!start;
return IterationEnd;
}
}
}).new(:$start, :$end, :$step)
}
multi range(Int:D $start, Int:D $end where * >= $start, Int:D $step where * > 0 = 1) {
(class {
also does Iterable does Iterator;
has Int ($.start, $.end, $.step);
has Int $!count = $!start;
method iterator { self }
method pull-one(--> Mu) {
if $!count < $!end {
my $i = $!count;
$!count += $!step;
return $i;
}
else {
$!count = $!start;
return IterationEnd;
}
}
}).new(:$start, :$end, :$step)
}
my \x = range(1, 5);
.say for x;
# OUTPUT:
# 1
# 2
# 3
# 4
say x.map(* ** 2).sum;
# OUTPUT:
# 30
my \y = range(-1, -5);
.say for y;
# OUTPUT:
# -1
# -2
# -3
# -4
say y.map(* ** 2).sum;
# OUTPUT:
# 30
MATLAB 支援使用“原生”陣列或 cell 陣列進行外部和內部隱式迭代。在外部迭代的情況下,使用者有責任推進遍歷並請求下一個元素,可以在陣列儲存結構中定義一組元素,並使用 for 迴圈結構遍歷這些元素。例如,
% Define an array of integers
myArray = [1,3,5,7,11,13];
for n = myArray
% ... do something with n
disp(n) % Echo integer to Command Window
end
使用 for 關鍵字遍歷整數陣列。在內部迭代的情況下,使用者可以向迭代器提供一個操作,以便在集合的每個元素上執行該操作並隱式返回相應的輸出陣列。此外,arrayfun 和 cellfun 函式可以分別用於在“原生”陣列和 cell 陣列上執行自定義或使用者定義的操作。例如,
function simpleFun
% Define an array of integers
myArray = [1,3,5,7,11,13];
% Perform a custom operation over each element
myNewArray = arrayfun(@(a)myCustomFun(a),myArray);
% Echo resulting array to Command Window
myNewArray
function outScalar = myCustomFun(inScalar)
% Simply multiply by 2
outScalar = 2*inScalar;
定義一個主函式 simpleFun,該函式使用內建函式 arrayfun 將自定義子函式 myCustomFun 隱式應用於陣列的每個元素。
或者,可能希望透過定義迭代器模式的自定義面向物件 MATLAB 實現來從使用者那裡抽象陣列儲存容器的機制。在 MATLAB Central 檔案交換專案 設計模式:迭代器(行為型) 中演示了這種支援外部迭代的實現。這是在 MATLAB 軟體版本 7.6 (R2008a) 中引入的新類定義語法[9] 中編寫的,其特點是使用列表抽象資料型別 (ADT) 的一維 cell 陣列實現作為儲存異構(資料型別)元素集的機制。它提供了使用 hasNext()、next() 和 reset() 方法在 while 迴圈中進行顯式正向列表遍歷的功能。
參考文獻
- ↑ “迭代器和生成器”. 檢索於 2016年3月18日.
- ↑ “C# foreach 語句”.
- ↑ “PHP:迭代器”. 檢索於 2013年6月23日.
- ↑ 檔案控制代碼物件實現了這一點,以便逐行讀取其內容
- ↑ 在 Perl 5.12 中,陣列和 繫結陣列 可以像雜湊一樣使用
each進行迭代 - ↑ “Python v2.7.1 文件:Python 標準庫:5. 內建型別”. 檢索於 2011年5月2日.
- ↑ “Raku 文件:角色 Iterable”. 檢索於 2020年12月9日.
- ↑ “Raku 文件:角色 Iterator”. 檢索於 2020年12月9日.
- ↑ “MATLAB 軟體版本 7.6 引入的新類定義語法”. MathWorks 公司,2009年3月. 檢索於 2009年9月22日.
