跳轉到內容

迭代器

25% developed
來自 Wikibooks,開放世界中的開放書籍

直譯器 計算機科學設計模式
迭代器
中介者

提供一種方法來順序訪問聚合物件中的元素,而無需公開其底層表示。

示例

在 Java 中,介面 java.util.Iterator<E> 是迭代器模式的一種實現。這樣,所有實現 java.lang.Iterable<T> 介面的物件都不需要此模式的便捷實現。

成本

此模式有一定的成本。僅對重要數量的程式碼實現此模式。IDE 重構幫不上你太多忙。

建立

此模式的建立也有一定的成本。

維護

此模式易於維護。

移除

此模式的移除也有一定的成本。

建議

  • 在迭代器類的名稱中使用“iterator”一詞,以便向其他開發人員指示模式的使用。

實現

Java 中的實現

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 中的實現

JavaScript 作為 ECMAScript 6 的一部分,支援任何提供 next() 方法的物件的迭代器模式,該方法返回一個具有兩個特定屬性的物件:donevalue。以下是一個顯示反向陣列迭代器的示例

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 的一些內建型別(如 ArrayMapSet)已經定義了自己的迭代行為。可以透過定義物件的元 @@iterator 方法(也稱為 Symbol.iterator)來實現相同的效果。這將建立一個 Iterable 物件。

以下是一個範圍函式的示例,該函式使用常規 for 迴圈生成從 startend(不包括 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
C# 中的實現

.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 5 中的實現

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 中的實現

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 中的實現

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 中的實現

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 中的實現

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 關鍵字遍歷整數陣列。在內部迭代的情況下,使用者可以向迭代器提供一個操作,以便在集合的每個元素上執行該操作並隱式返回相應的輸出陣列。此外,arrayfuncellfun 函式可以分別用於在“原生”陣列和 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 迴圈中進行顯式正向列表遍歷的功能。

參考文獻

  1. “迭代器和生成器”. 檢索於 2016年3月18日.
  2. “C# foreach 語句”.
  3. “PHP:迭代器”. 檢索於 2013年6月23日.
  4. 檔案控制代碼物件實現了這一點,以便逐行讀取其內容
  5. 在 Perl 5.12 中,陣列和 繫結陣列 可以像雜湊一樣使用 each 進行迭代
  6. “Python v2.7.1 文件:Python 標準庫:5. 內建型別”. 檢索於 2011年5月2日.
  7. “Raku 文件:角色 Iterable”. 檢索於 2020年12月9日.
  8. “Raku 文件:角色 Iterator”. 檢索於 2020年12月9日.
  9. “MATLAB 軟體版本 7.6 引入的新類定義語法”. MathWorks 公司,2009年3月. 檢索於 2009年9月22日.


Clipboard

待辦事項
新增更多插圖。


直譯器 計算機科學設計模式
迭代器
中介者


您對本頁面有任何疑問?
在這裡提問


在本手冊中建立新頁面


華夏公益教科書