Iterator - Iterator

Na programação de computadores , um iterador é um objeto que permite a um programador percorrer um contêiner , principalmente listas . Vários tipos de iteradores são frequentemente fornecidos por meio da interface de um contêiner . Embora a interface e a semântica de um determinado iterador sejam fixas, os iteradores são frequentemente implementados em termos das estruturas subjacentes a uma implementação de contêiner e geralmente são fortemente acoplados ao contêiner para permitir a semântica operacional do iterador. Um iterador realiza travessia e também dá acesso a elementos de dados em um contêiner, mas não realiza a iteração por si mesmo (ou seja, não sem alguma liberdade significativa obtida com esse conceito ou com o uso trivial da terminologia). Um iterador é comportamentalmente semelhante a um cursor de banco de dados . Os iteradores datam da linguagem de programação CLU em 1974.

Descrição

Iteradores Internos

Os iteradores internos são funções de ordem superior (geralmente assumindo funções anônimas ), como mapear , reduzir etc., implementando a travessia em um contêiner, aplicando a função fornecida a cada elemento por vez.

Iteradores externos e o padrão do iterador

Um iterador externo pode ser considerado um tipo de ponteiro que tem duas operações principais: fazer referência a um elemento específico na coleção de objetos (chamado de acesso de elemento ) e modificar a si mesmo para apontar para o próximo elemento (chamado de passagem de elemento ). Também deve haver uma maneira de criar um iterador para que aponte para algum primeiro elemento, bem como alguma maneira de determinar quando o iterador esgotou todos os elementos do contêiner. Dependendo do idioma e do uso pretendido, os iteradores também podem fornecer operações adicionais ou exibir comportamentos diferentes.

O objetivo principal de um iterador é permitir que um usuário processe cada elemento de um contêiner enquanto isola o usuário da estrutura interna do contêiner. Isso permite que o contêiner armazene elementos de qualquer maneira que desejar, enquanto permite ao usuário tratá-los como se fosse uma sequência ou lista simples. Uma classe iteradora geralmente é projetada em estreita coordenação com a classe contêiner correspondente. Normalmente, o contêiner fornece os métodos para a criação de iteradores.

Às vezes, um contador de loop também é conhecido como iterador de loop. Um contador de loop , entretanto, fornece apenas a funcionalidade de passagem e não a funcionalidade de acesso ao elemento.

Geradores

Uma forma de implementar iteradores é usar uma forma restrita de co-rotina , conhecida como gerador . Em contraste com uma sub - rotina , uma co- rotina do gerador pode gerar valores para seu chamador várias vezes, em vez de retornar apenas uma vez. A maioria dos iteradores pode ser expressa naturalmente como geradores, mas como os geradores preservam seu estado local entre as invocações, eles são particularmente adequados para iteradores complicados e com estado, como atravessadores de árvore . Existem diferenças e distinções sutis no uso dos termos "gerador" e "iterador", que variam entre os autores e as línguas. Em Python , um gerador é um construtor de iterador : uma função que retorna um iterador. Um exemplo de gerador Python que retorna um iterador para os números de Fibonacci usando a yieldinstrução Python a seguir:

def fibonacci(limit):
    a, b = 0, 1
    for _ in range(limit):
        yield a
        a, b = b, a + b

for number in fibonacci(100): # The generator constructs an iterator
    print(number)

Iteradores implícitos

Algumas linguagens orientadas a objetos, como C # , C ++ (versões posteriores), Delphi (versões posteriores), Go , Java (versões posteriores), Lua , Perl , Python , Ruby fornecem uma maneira intrínseca de iterar através dos elementos de um objeto contêiner sem a introdução de um objeto iterador explícito. Um objeto iterador real pode existir na realidade, mas se existir, não será exposto no código-fonte da linguagem.

Os iteradores implícitos geralmente são manifestados por uma instrução " foreach " (ou equivalente), como no seguinte exemplo Python:

for value in iterable:
    print(value)

Em Python, um iterável é um objeto que pode ser convertido em um iterador, que é então iterado durante o loop for; isso é feito implicitamente.

Ou outras vezes, eles podem ser criados pelo próprio objeto de coleção, como neste exemplo Ruby:

iterable.each do |value|
  puts value
end

Esse estilo de iteração às vezes é chamado de "iteração interna" porque seu código é executado totalmente dentro do contexto do objeto iterável (que controla todos os aspectos da iteração) e o programador fornece apenas a operação a ser executada em cada etapa (usando uma função anônima ).

Linguagens que suportam compreensões de lista ou construções semelhantes também podem fazer uso de iteradores implícitos durante a construção da lista de resultados, como em Python:

names = [person.name for person in roster if person.male]

Às vezes, a natureza oculta implícita é apenas parcial. A linguagem C ++ possui alguns modelos de função para iteração implícita, como for_each(). Essas funções ainda requerem objetos iteradores explícitos como sua entrada inicial, mas a iteração subsequente não expõe um objeto iterador ao usuário.

Streams

Iteradores são uma abstração útil de fluxos de entrada - eles fornecem um objeto iterável potencialmente infinito (mas não necessariamente indexável). Diversas linguagens, como Perl e Python, implementam streams como iteradores. Em Python, os iteradores são objetos que representam fluxos de dados. Implementações alternativas de stream incluem linguagens baseadas em dados , como AWK e sed .

Comparando com indexação

Em linguagens procedurais, é comum usar o operador subscrito e um contador de loop para percorrer todos os elementos em uma sequência, como um array. Embora a indexação também possa ser usada com alguns contêineres orientados a objetos, o uso de iteradores pode ter algumas vantagens:

  • Os loops de contagem não são adequados para todas as estruturas de dados, em particular para estruturas de dados com nenhum ou lento acesso aleatório , como listas ou árvores .
  • Os iteradores podem fornecer uma maneira consistente de iterar em estruturas de dados de todos os tipos e, portanto, tornar o código mais legível, reutilizável e menos sensível a uma mudança na estrutura de dados.
  • Um iterador pode impor restrições adicionais ao acesso, como garantir que os elementos não possam ser ignorados ou que um elemento visitado anteriormente não possa ser acessado uma segunda vez.
  • Um iterador pode permitir que o objeto contêiner seja modificado sem invalidar o iterador. Por exemplo, uma vez que um iterador avançou além do primeiro elemento, pode ser possível inserir elementos adicionais no início do contêiner com resultados previsíveis. Com a indexação, isso é problemático, pois os números do índice devem mudar.

A capacidade de um contêiner ser modificado durante a iteração por meio de seus elementos tornou-se necessária na programação orientada a objetos moderna , onde as inter-relações entre os objetos e os efeitos das operações podem não ser óbvios. Ao usar um iterador, fica-se isolado desses tipos de consequências. Essa afirmação deve, entretanto, ser tomada com cautela, porque mais frequentemente do que não, por razões de eficiência, a implementação do iterador está tão fortemente ligada ao contêiner que impede a modificação do contêiner subjacente sem se invalidar.

Para contêineres que podem mover seus dados na memória, a única maneira de não invalidar o iterador é, para o contêiner, de alguma forma controlar todos os iteradores atualmente ativos e atualizá-los rapidamente. Como o número de iteradores em um determinado momento pode ser arbitrariamente grande em comparação ao tamanho do contêiner vinculado, atualizar todos eles prejudicará drasticamente a garantia de complexidade nas operações do contêiner.

Uma maneira alternativa de manter o número de atualizações vinculado ao tamanho do contêiner seria usar um tipo de mecanismo de manipulação, que é uma coleção de ponteiros indiretos para os elementos do contêiner que devem ser atualizados com o contêiner, e permitir que os iteradores apontem para essas alças em vez de diretamente para os elementos de dados. Mas essa abordagem terá um impacto negativo no desempenho do iterador, uma vez que deve efetuar um ponteiro duplo seguindo para acessar o elemento de dados real. Isso geralmente não é desejável, porque muitos algoritmos que usam os iteradores invocam a operação de acesso a dados dos iteradores com mais freqüência do que o método avançado. Portanto, é especialmente importante ter iteradores com acesso de dados muito eficiente.

Resumindo, isso é sempre uma troca entre segurança (os iteradores permanecem sempre válidos) e eficiência. Na maioria das vezes, a segurança adicional não vale o preço de eficiência a pagar por ela. Usar um contêiner alternativo (por exemplo, uma lista vinculada individualmente em vez de um vetor) seria uma escolha melhor (globalmente mais eficiente) se a estabilidade dos iteradores for necessária.

Classificando iteradores

Categorias de iterador

Os iteradores podem ser categorizados de acordo com sua funcionalidade. Aqui está uma lista (não exaustiva) de categorias de iteradores:

Categoria línguas
Iterador bidirecional C ++
Iterador de encaminhamento C ++
Iterador de entrada C ++
Iterador de saída C ++
Iterador de acesso aleatório C ++
Iterador trivial C ++ ( STL antigo )

Tipos de iterador

Diferentes linguagens ou bibliotecas usadas com essas linguagens definem os tipos de iterador. Alguns deles são

Modelo línguas
Iterador de Lista PHP , R
Iterador de cache PHP
Iterador constante C ++ , PHP
Iterador de diretório PHP, Python
Filtro iterador PHP, R
Limitar iterador PHP
Listar iterador Java , R
Iterador de matriz recursiva PHP
Iterador XML PHP

Em diferentes linguagens de programação

C # e outras linguagens .NET

Os iteradores no .NET Framework são chamados de "enumeradores" e representados pela IEnumeratorinterface. IEnumeratorfornece um MoveNext()método que avança para o próximo elemento e indica se o fim da coleção foi alcançado; uma Currentpropriedade, para obter o valor do elemento que está sendo apontado; e um Reset()método opcional , para retroceder o enumerador de volta à sua posição inicial. O enumerador inicialmente aponta para um valor especial antes do primeiro elemento, portanto, uma chamada para MoveNext()é necessária para começar a iteração.

Os enumeradores são normalmente obtidos chamando o GetEnumerator()método de um objeto que implementa a IEnumerableinterface. As classes de contêiner geralmente implementam essa interface. No entanto, a instrução foreach em C # pode operar em qualquer objeto que forneça tal método, mesmo que não implemente IEnumerable( digitação de pato ). Ambas as interfaces foram expandidas para versões genéricas no .NET 2.0 .

O seguinte mostra um uso simples de iteradores em C # 2.0:

// explicit version
IEnumerator<MyType> iter = list.GetEnumerator();
while (iter.MoveNext())
    Console.WriteLine(iter.Current);

// implicit version
foreach (MyType value in list)
    Console.WriteLine(value);

C # 2.0 também suporta geradores : um método que é declarado como retornando IEnumerator(ou IEnumerable), mas usa a yield returninstrução " " para produzir uma sequência de elementos em vez de retornar uma instância de objeto, será transformado pelo compilador em uma nova classe implementando a interface apropriada .

C ++

A linguagem C ++ faz amplo uso de iteradores em sua Biblioteca Padrão e descreve várias categorias de iteradores que diferem no repertório de operações que permitem. Isso inclui iteradores de encaminhamento , iteradores bidirecionais e iteradores de acesso aleatório , em ordem de possibilidades crescentes. Todos os tipos de modelo de contêiner padrão fornecem iteradores de uma dessas categorias. Iteradores generalizam ponteiros para elementos de uma matriz (que de fato podem ser usados ​​como iteradores), e sua sintaxe é projetada para se parecer com a da aritmética de ponteiro C , onde os operadores e são usados ​​para fazer referência ao elemento para o qual o iterador aponta e operadores aritméticos de ponteiro like são usados ​​para modificar iteradores no percurso de um contêiner. *->++

Traversal usando iteradores geralmente envolve um único iterador variável e dois iteradores fixos que servem para delimitar um intervalo a ser percorrido. A distância entre os iteradores limitantes, em termos do número de aplicações do operador ++necessárias para transformar o limite inferior em superior, é igual ao número de itens na faixa designada; o número de valores distintos de iteradores envolvidos é mais um do que isso. Por convenção, o iterador limitante inferior "aponta para" o primeiro elemento no intervalo, enquanto o iterador limitador superior não aponta para nenhum elemento no intervalo, mas apenas além do final do intervalo. Para atravessar um contêiner inteiro, o begin()método fornece o limite inferior e end()o limite superior. O último não faz referência a nenhum elemento do contêiner, mas é um valor de iterador válido que pode ser comparado.

O exemplo a seguir mostra um uso típico de um iterador.

std::vector<int> items;
items.push_back(5); // Append integer value '5' to vector 'items'.
items.push_back(2); // Append integer value '2' to vector 'items'.
items.push_back(9); // Append integer value '9' to vector 'items'.

for (auto it = items.begin(); it != items.end(); ++it) { // Iterate through 'items'.
  std::cout << *it; // And print value of 'items' for current index.
}
// In C++11, the same can be done without using any iterators:
for (auto x : items) {
  std::cout << x; // Print value of each element 'x' of 'items'.
}

// Each loops print "529".

Os tipos de iterador são separados dos tipos de contêiner com os quais são usados, embora os dois sejam frequentemente usados ​​em conjunto. A categoria do iterador (e, portanto, as operações definidas para ele) geralmente depende do tipo de contêiner, com, por exemplo, arrays ou vetores fornecendo iteradores de acesso aleatório, mas conjuntos (que usam uma estrutura vinculada como implementação) fornecendo apenas iteradores bidirecionais. Um mesmo tipo de contêiner pode ter mais de um tipo de iterador associado; por exemplo, o std::vector<T>tipo de contêiner permite a travessia usando ponteiros (brutos) para seus elementos (do tipo *<T>), ou valores de um tipo especial std::vector<T>::iterator, e ainda outro tipo é fornecido para "iteradores reversos", cujas operações são definidas de tal forma que um o algoritmo que realiza uma travessia normal (para frente) realmente fará a travessia em ordem reversa quando chamado com iteradores reversos. A maioria dos contêineres também fornece um const_iteratortipo separado , para o qual as operações que permitiriam alterar os valores apontados não são definidas intencionalmente.

A travessia simples de um objeto contêiner ou de um intervalo de seus elementos (incluindo a modificação desses elementos, a menos que um const_iteratorseja usado) pode ser feita usando apenas iteradores. Mas os tipos de contêiner também podem fornecer métodos como insertou eraseque modificam a estrutura do próprio contêiner; esses são métodos da classe contêiner, mas, além disso, requerem um ou mais valores de iterador para especificar a operação desejada. Embora seja possível ter vários iteradores apontando para o mesmo contêiner simultaneamente, as operações de modificação de estrutura podem invalidar certos valores do iterador (o padrão especifica para cada caso se isso pode ser assim); usar um iterador invalidado é um erro que levará a um comportamento indefinido e esses erros não precisam ser sinalizados pelo sistema de tempo de execução.

A iteração implícita também é parcialmente suportada por C ++ por meio do uso de modelos de função padrão, como std::for_each(), std::copy() e std::accumulate().

Quando usados, eles devem ser inicializados com iteradores existentes, geralmente begine end, que definem o intervalo no qual a iteração ocorre. Mas nenhum objeto iterador explícito é subsequentemente exposto à medida que a iteração prossegue. Este exemplo mostra o uso de for_each.

ContainerType<ItemType> c; // Any standard container type of ItemType elements.

void ProcessItem(const ItemType& i) { // Function that will process each item of the collection.
  std::cout << i << std::endl;
}

std::for_each(c.begin(), c.end(), ProcessItem); // A for-each iteration loop.

O mesmo pode ser conseguido usando std::copy, passando um std::ostream_iteratorvalor como terceiro iterador:

std::copy(c.begin(), c.end(), std::ostream_iterator<ItemType>(std::cout, "\n"));

Desde C ++ 11 , a sintaxe da função lambda pode ser usada para especificar a operação a ser iterada sequencialmente, evitando a necessidade de definir uma função nomeada. Aqui está um exemplo de para cada iteração usando uma função lambda:

ContainerType<ItemType> c; // Any standard container type of ItemType elements.

// A for-each iteration loop with a lambda function.
std::for_each(c.begin(), c.end(), [](const ItemType& i) { std::cout << i << std::endl; });

Java

Introduzida na versão Java JDK 1.2, a java.util.Iteratorinterface permite a iteração de classes de contêiner. Cada Iteratorfornece um next()e hasNext()método, e pode opcionalmente suportar um remove()método. Os iteradores são criados pela classe de contêiner correspondente, normalmente por um método denominado iterator().

O next()método avança o iterador e retorna o valor apontado pelo iterador. O primeiro elemento é obtido na primeira chamada para next(). Para determinar quando todos os elementos do contêiner foram visitados, o hasNext()método de teste é usado. O exemplo a seguir mostra um uso simples de iteradores:

Iterator iter = list.iterator();
// Iterator<MyType> iter = list.iterator(); // in J2SE 5.0
while (iter.hasNext()) {
    System.out.print(iter.next());
    if (iter.hasNext())
        System.out.print(", ");
}

Para mostrar que hasNext()pode ser chamado repetidamente, usamos para inserir vírgulas entre os elementos, mas não após o último elemento.

Essa abordagem não separa adequadamente a operação de avanço do acesso real aos dados. Se o elemento de dados deve ser usado mais de uma vez para cada avanço, ele precisa ser armazenado em uma variável temporária. Quando um avanço é necessário sem acesso aos dados (ou seja, para pular um determinado elemento de dados), o acesso é realizado, embora o valor retornado seja ignorado neste caso.

Para tipos de coleção que o suportam, o remove()método do iterador remove o elemento visitado mais recentemente do contêiner, enquanto mantém o iterador utilizável. Adicionar ou remover elementos chamando os métodos do contêiner (também do mesmo encadeamento ) torna o iterador inutilizável. Uma tentativa de obter o próximo elemento lança a exceção. Uma exceção também é lançada se não houver mais elementos restantes ( hasNext()retornou falso anteriormente).

Além disso, pois java.util.Listexiste um java.util.ListIteratorcom uma API semelhante, mas que permite a iteração para frente e para trás, fornece seu índice atual na lista e permite a configuração do elemento da lista em sua posição.

O lançamento J2SE 5.0 do Java introduziu a Iterableinterface para suportar um loop aprimorado for( foreach ) para iteração em coleções e arrays. Iterabledefine o iterator()método que retorna um Iterator. Usando o forloop aprimorado , o exemplo anterior pode ser reescrito como

for (MyType obj : list) {
    System.out.print(obj);
}

Alguns contêineres também usam a Enumerationclasse mais antiga (desde 1.0) . Ele fornece hasMoreElements()e nextElement()métodos, mas não tem métodos para modificar o contêiner.

Scala

No Scala , os iteradores têm um rico conjunto de métodos semelhantes às coleções e podem ser usados ​​diretamente em loops for. Na verdade, tanto os iteradores quanto as coleções herdam de um traço de base comum - scala.collection.TraversableOnce. No entanto, por causa do rico conjunto de métodos disponíveis na biblioteca coleções Scala, tais como map, collect, filteretc., muitas vezes não é necessário para lidar com iteradores diretamente ao programar em Scala.

Os iteradores e coleções Java podem ser convertidos automaticamente em iteradores e coleções Scala, respectivamente, simplesmente adicionando uma única linha

import scala.collection.JavaConversions._

para o arquivo. O JavaConversionsobjeto fornece conversões implícitas para fazer isso. As conversões implícitas são um recurso do Scala: métodos que, quando visíveis no escopo atual, inserem automaticamente chamadas para si mesmos em expressões relevantes no local apropriado para fazê-las verificar quando, de outra forma, não o fariam.

MATLAB

O MATLAB suporta iteração implícita externa e interna usando arrays ou cellarrays "nativos" . No caso de iteração externa em que o ônus recai sobre o usuário para avançar a travessia e solicitar os próximos elementos, pode-se definir um conjunto de elementos dentro de uma estrutura de armazenamento de matriz e percorrer os elementos usando a forconstrução -loop. Por exemplo,

% 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

atravessa uma matriz de inteiros usando a forpalavra - chave.

No caso de iteração interna em que o usuário pode fornecer uma operação ao iterador para executar em cada elemento de uma coleção, muitos operadores integrados e funções MATLAB são sobrecarregados para executar em cada elemento de uma matriz e retornar uma matriz de saída correspondente implicitamente . Além disso, as funções arrayfune cellfunpodem ser aproveitadas para executar operações personalizadas ou definidas pelo usuário em matrizes e cellmatrizes "nativas", respectivamente. Por exemplo,

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;

define uma função primária simpleFunque aplica implicitamente uma subfunção personalizada myCustomFuna cada elemento de uma matriz usando uma função integrada arrayfun.

Alternativamente, pode ser desejável abstrair os mecanismos do contêiner de armazenamento de matriz do usuário, definindo uma implementação MATLAB orientada a objeto customizada do Padrão de Repetidor. Tal implementação de suporte à iteração externa é demonstrada no item Design Pattern do item MATLAB Central File Exchange : Iterator (Behavioral) . Isso é escrito na nova sintaxe de definição de classe introduzida com o software MATLAB versão 7.6 (R2008a) e apresenta uma cellrealização de array unidimensional do List Abstract Data Type (ADT) como o mecanismo para armazenar um conjunto heterogêneo (no tipo de dados) de elementos Ele fornece a funcionalidade para encaminhamento explícito de List traversal com os métodos hasNext(), next()e reset()para uso em um while-loop.

PHP

O foreachloop do PHP foi introduzido na versão 4.0 e tornou-se compatível com objetos como valores na 4.0 Beta 4. No entanto, o suporte para iteradores foi adicionado no PHP 5 através da introdução da Traversableinterface interna . As duas interfaces principais para implementação em scripts PHP que permitem que os objetos sejam iterados por meio do foreachloop são Iteratore IteratorAggregate. O último não requer que a classe de implementação declare todos os métodos necessários; em vez disso, implementa um método acessador ( getIterator) que retorna uma instância de Traversable. A Biblioteca PHP padrão fornece várias classes para trabalhar com iteradores especiais. PHP também suporta Geradores desde 5.5.

A implementação mais simples é envolver um array, isso pode ser útil para dicas de tipo e ocultação de informações .

namespace Wikipedia\Iterator;

final class ArrayIterator extends \Iterator
{
    private array $array;

    public function __construct(array $array)
    {
        $this->array = $array;
    }

    public function rewind(): void
    {
        echo 'rewinding' , PHP_EOL;
        reset($this->array);
    }

    public function current()
    {
        $value = current($this->array);
        echo "current: {$value}", PHP_EOL;
        return $value;
    }

    public function key()
    {
        $key = key($this->array);
        echo "key: {$key}", PHP_EOL;
        return $key;
    }

    public function next()
    {
        $value = next($this->array);
        echo "next: {$value}", PHP_EOL;
        return $value;
    }

    public function valid(): bool
    {
        $valid = $this->current() !== false;
        echo 'valid: ', ($valid ? 'true' : 'false'), PHP_EOL;
        return $valid;
    }
}

Todos os métodos da classe de exemplo são usados ​​durante a execução de um loop foreach completo ( foreach ($iterator as $key => $current) {}). Os métodos do iterador são executados na seguinte ordem:

  1. $iterator->rewind() garante que a estrutura interna comece desde o início.
  2. $iterator->valid()retorna verdadeiro neste exemplo.
  3. $iterator->current()o valor retornado é armazenado em $value.
  4. $iterator->key()o valor retornado é armazenado em $key.
  5. $iterator->next() avança para o próximo elemento na estrutura interna.
  6. $iterator->valid()retorna falso e o loop é abortado.

O próximo exemplo ilustra uma classe PHP que implementa a Traversableinterface, que pode ser envolvida em uma IteratorIteratorclasse para agir sobre os dados antes de serem retornados ao foreachloop. O uso junto com a MYSQLI_USE_RESULTconstante permite que os scripts PHP iterem conjuntos de resultados com bilhões de linhas com muito pouco uso de memória. Esses recursos não são exclusivos do PHP nem de suas implementações de classe MySQL (por exemplo, a PDOStatementclasse também implementa a Traversableinterface).

mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
$mysqli = new \mysqli('host.example.com', 'username', 'password', 'database_name');

// The \mysqli_result class that is returned by the method call implements the internal Traversable interface.
foreach ($mysqli->query('SELECT `a`, `b`, `c` FROM `table`', MYSQLI_USE_RESULT) as $row) {
    // Act on the returned row, which is an associative array.
}

Pitão

Iteradores em Python são uma parte fundamental da linguagem e, em muitos casos, passam despercebidos, pois são usados ​​implicitamente na instrução for( foreach ), em compreensões de lista e em expressões geradoras . Todos os tipos de coleção internos padrão do Python suportam iteração, bem como muitas classes que fazem parte da biblioteca padrão. O exemplo a seguir mostra a iteração implícita típica em uma sequência:

for value in sequence:
    print(value)

Os dicionários Python (uma forma de array associativo ) também podem ser iterados diretamente, quando as chaves do dicionário são retornadas; ou o items()método de um dicionário pode ser iterado sobre onde produz pares de chave e valor correspondentes como uma tupla:

for key in dictionary:
    value = dictionary[key]
    print(key, value)
for key, value in dictionary.items():
    print(key, value)

No entanto, os iteradores podem ser usados ​​e definidos explicitamente. Para qualquer tipo ou classe de sequência iterável, a função interna iter()é usada para criar um objeto iterador. O objeto iterador pode então ser iterado com a next()função, que usa o __next__()método internamente, que retorna o próximo elemento no contêiner. (A instrução anterior se aplica ao Python 3.x. No Python 2.x, o next()método é equivalente.) Uma StopIterationexceção será gerada quando não houver mais elementos restantes. O exemplo a seguir mostra uma iteração equivalente em uma sequência usando iteradores explícitos:

it = iter(sequence)
while True:
    try:
        value = it.next() # in Python 2.x
        value = next(it) # in Python 3.x
    except StopIteration:
        break
    print(value)

Qualquer classe definida pelo usuário pode oferecer suporte à iteração padrão (implícita ou explícita), definindo um __iter__()método que retorna um objeto iterador. O objeto iterador então precisa definir um __next__()método que retorne o próximo elemento.

Os geradores do Python implementam esse protocolo de iteração .

Raku

Iteradores em Raku são uma parte fundamental da linguagem, embora normalmente os usuários não precisem se preocupar com iteradores. A sua utilização está escondido atrás de iteração APIs, como a fordeclaração, map, grep, lista de indexação com .[$idx], etc.

O exemplo a seguir mostra a iteração implícita típica em uma coleção de valores:

my @values = 1, 2, 3;
for @values -> $value {
    say $value
}
# OUTPUT:
# 1
# 2
# 3

Os hashes Raku também podem ser iterados diretamente; isso produz Pairobjetos de valor-chave . O kvmétodo pode ser chamado no hash para iterar a chave e os valores; o keysmétodo para iterar nas chaves do hash; e o valuesmétodo para iterar sobre os valores do hash.

my %word-to-number = 'one' => 1, 'two' => 2, 'three' => 3;
for %word-to-number -> $pair {
    say $pair;
}
# OUTPUT:
# three => 3
# one => 1
# two => 2

for %word-to-number.kv -> $key, $value {
    say "$key: $value" 
}
# OUTPUT:
# three: 3
# one: 1
# two: 2

for %word-to-number.keys -> $key {
    say "$key => " ~ %word-to-number{$key};
}
# OUTPUT:
# three => 3
# one => 1
# two => 2

No entanto, os iteradores podem ser usados ​​e definidos explicitamente. Para qualquer tipo iterável, existem vários métodos que controlam diferentes aspectos do processo de iteração. Por exemplo, o iteratormétodo deve retornar um Iteratorobjeto e o pull-onemétodo deve produzir e retornar o próximo valor, se possível, ou retornar o valor sentinela IterationEndse nenhum outro valor puder ser produzido. O exemplo a seguir mostra uma iteração equivalente em uma coleção usando iteradores explícitos:

my @values = 1, 2, 3;
my $it := @values.iterator;          # grab iterator for @values

loop {
    my $value := $it.pull-one;       # grab iteration's next value
    last if $value =:= IterationEnd; # stop if we reached iteration's end
    say $value;
}
# OUTPUT:
# 1
# 2
# 3

Todos os tipos iteráveis ​​em Raku compõem a Iterablefunção, a Iteratorfunção ou ambos. O Iterableé bastante simples e requer apenas iteratorque seja implementado pela classe que o compõe. O Iteratoré mais complexo e fornece uma série de métodos como pull-one, que permite uma operação mais precisa de iteração em vários contextos, como adicionar ou eliminar itens ou ignorá-los para acessar outros itens. Portanto, qualquer classe definida pelo usuário pode oferecer suporte à iteração padrão, compondo essas funções e implementando os métodos iteratore / ou pull-one.

A DNAclasse representa uma fita de DNA e a implementa ao iteratorcompor o Iterablepapel. A fita de DNA é dividida em um grupo de trinucleotídeos quando iterada sobre:

subset Strand of Str where { .match(/^^ <[ACGT]>+ $$/) and .chars %% 3 };
class DNA does Iterable {
    has $.chain;
    method new(Strand:D $chain) {
        self.bless: :$chain
    }
 
    method iterator(DNA:D:){ $.chain.comb.rotor(3).iterator }
};

for DNA.new('GATTACATA') {
    .say
}
# OUTPUT:
# (G A T)
# (T A C)
# (A T A)

say DNA.new('GATTACATA').map(*.join).join('-');
# OUTPUT:
# GAT-TAC-ATA

A Repeaterclasse compõe as funções Iterablee Iterator:

class Repeater does Iterable does Iterator {
    has Any $.item  is required;
    has Int $.times is required;
    has Int $!count = 1;
    
    multi method new($item, $times) {
        self.bless: :$item, :$times;
    }
    
    method iterator { self }
    method pull-one(--> Mu){ 
        if $!count <= $!times {
            $!count += 1;
            return $!item
        }
        else {
            return IterationEnd
        }
    }
}

for Repeater.new("Hello", 3) {
    .say
}

# OUTPUT:
# Hello
# Hello
# Hello

Rubi

Ruby implementa iteradores de maneira bem diferente; todas as iterações são feitas por meio da passagem de fechamentos de retorno de chamada para métodos de contêiner - desta forma, Ruby não apenas implementa a iteração básica, mas também vários padrões de iteração, como mapeamento de função, filtros e redução. Ruby também oferece suporte a uma sintaxe alternativa para o método de iteração básico each, os três exemplos a seguir são equivalentes:

(0...42).each do |n|
  puts n
end

...e...

for n in 0...42
  puts n
end

ou ainda mais curto

42.times do |n|
  puts n
end

Ruby também pode iterar em listas fixas usando Enumerators e chamando seu #nextmétodo ou fazendo um para cada um deles, como acima.

Ferrugem

Com Rust, pode-se iterar em elementos de vetores ou criar seus próprios iteradores. Cada iterador tem adaptadores ( map, filter, skip, take, ...).

for n in 0..42 {
    println!("{}", n);
}

Abaixo, a fibonacci()função retorna um iterador personalizado.

for i in fibonacci().skip(4).take(4) {
    println!("{}", i);
}

Veja também

Referências

links externos