Come RecursiveIteratorIterator
funziona?
Il manuale PHP non ha molto di documentato o spiegato. Qual'è la differenza tra IteratorIterator
e RecursiveIteratorIterator
?
Come RecursiveIteratorIterator
funziona?
Il manuale PHP non ha molto di documentato o spiegato. Qual'è la differenza tra IteratorIterator
e RecursiveIteratorIterator
?
RecursiveIteratorIterator
funziona, hai già capito come IteratorIterator
funziona? Voglio dire che è fondamentalmente lo stesso, solo l'interfaccia che viene consumata dai due è diversa. E sei più interessato ad alcuni esempi o vuoi vedere il diff dell'implementazione del codice C sottostante?
IteratorIterator
mappe Iterator
e IteratorAggregate
in un Iterator
, dove REcusiveIteratorIterator
viene utilizzato per attraversare in modo ricusivo aRecursiveIterator
Risposte:
RecursiveIteratorIterator
è un attraversamento dell'albero diIterator
implementazione concreta . Consente a un programmatore di attraversare un oggetto contenitore che implementa l' interfaccia, vedere Iterator in Wikipedia per i principi generali, i tipi, la semantica e i modelli degli iteratori.RecursiveIterator
A differenza di IteratorIterator
quale sia un Iterator
oggetto di implementazione concreto che attraversa in ordine lineare (e per impostazione predefinita accetta qualsiasi tipo di Traversable
nel suo costruttore), il RecursiveIteratorIterator
consente il ciclo su tutti i nodi in un albero ordinato di oggetti e il suo costruttore prende un file RecursiveIterator
.
In breve: RecursiveIteratorIterator
ti permette di scorrere un albero, IteratorIterator
ti permette di scorrere un elenco. Lo mostro presto con alcuni esempi di codice qui sotto.
Tecnicamente questo funziona rompendo la linearità attraversando tutti i figli di un nodo (se presenti). Questo è possibile perché per definizione tutti i figli di un nodo sono di nuovo a RecursiveIterator
. Il livello superiore Iterator
quindi impila internamente i diversi RecursiveIterator
s in base alla loro profondità e mantiene un puntatore al sub attivo corrente Iterator
per l'attraversamento.
Ciò consente di visitare tutti i nodi di un albero.
I principi di base sono gli stessi di IteratorIterator
: Un'interfaccia specifica il tipo di iterazione e la classe iteratore di base è l'implementazione di queste semantiche. Confronta con gli esempi seguenti, per i cicli lineari con foreach
te normalmente non pensi molto ai dettagli di implementazione a meno che tu non abbia bisogno di definirne uno nuovo Iterator
(ad esempio quando un certo tipo concreto stesso non implementa Traversable
).
Per l'attraversamento ricorsivo, a meno che non si utilizzi un predefinito Traversal
che ha già un'iterazione trasversale ricorsiva, normalmente è necessario istanziare l' RecursiveIteratorIterator
iterazione esistente o anche scrivere un'iterazione trasversale ricorsiva che è Traversable
tua per avere questo tipo di iterazione trasversale foreach
.
Suggerimento: probabilmente non hai implementato né l'uno né l'altro, quindi potrebbe essere qualcosa che vale la pena fare per la tua esperienza pratica delle differenze che hanno. Alla fine della risposta trovi un suggerimento fai da te.
Differenze tecniche in breve:
IteratorIterator
prenda qualsiasi Traversable
per l'attraversamento lineare, RecursiveIteratorIterator
necessita di un più specifico RecursiveIterator
per eseguire il ciclo su un albero.IteratorIterator
espone la sua Iterator
via principale getInnerIerator()
, RecursiveIteratorIterator
fornisce l'attuale sub-attivo Iterator
solo tramite quel metodo.IteratorIterator
sia totalmente a conoscenza di nulla come genitore o figli, RecursiveIteratorIterator
sa anche come ottenere e attraversare i bambini.IteratorIterator
non ha bisogno di uno stack di iteratori, RecursiveIteratorIterator
ha un tale stack e conosce il subiteratore attivo.IteratorIterator
ha il suo ordine dovuto alla linearità e nessuna scelta, RecursiveIteratorIterator
ha una scelta per un ulteriore attraversamento e deve decidere per ogni nodo (deciso tramite modalità perRecursiveIteratorIterator
).RecursiveIteratorIterator
ha più metodi di IteratorIterator
.Per riassumere: RecursiveIterator
è un tipo concreto di iterazione (loop su un albero) che funziona sui propri iteratori, vale a dire RecursiveIterator
. Questo è lo stesso principio di base di IteratorIerator
, ma il tipo di iterazione è diverso (ordine lineare).
Idealmente puoi anche creare il tuo set. L'unica cosa necessaria è che il tuo iteratore implementa il Traversable
che è possibile tramite Iterator
o IteratorAggregate
. Quindi puoi usarlo con foreach
. Ad esempio una sorta di oggetto di iterazione ricorsiva di attraversamento dell'albero ternario insieme all'interfaccia di iterazione corrispondente per gli oggetti contenitore.
Rivediamo con alcuni esempi di vita reale che non sono così astratti. Tra interfacce, iteratori concreti, oggetti contenitore e semantica di iterazione questa forse non è una cattiva idea.
Prendi un elenco di directory come esempio. Considera di avere il seguente albero di file e directory su disco:
Mentre un iteratore con ordine lineare attraversa semplicemente la cartella ei file di primo livello (un singolo elenco di directory), l'iteratore ricorsivo attraversa anche le sottocartelle ed elenca tutte le cartelle ei file (un elenco di directory con elenchi delle sue sottodirectory):
Non-Recursive Recursive
============= =========
[tree] [tree]
├ dirA ├ dirA
└ fileA │ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Puoi facilmente confrontare questo con il IteratorIterator
quale non fa ricorsione per attraversare l'albero delle directory. E il RecursiveIteratorIterator
quale può attraversare l'albero come mostra l'elenco ricorsivo.
All'inizio un esempio molto semplice con un DirectoryIterator
that implements Traversable
che permette foreach
di iterare su di esso:
$path = 'tree';
$dir = new DirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
L'output esemplare per la struttura della directory sopra è quindi:
[tree]
├ .
├ ..
├ dirA
├ fileA
Come vedi questo non sta ancora usando IteratorIterator
o RecursiveIteratorIterator
. Invece è solo usando foreach
che opera Traversable
sull'interfaccia.
Poiché foreach
per impostazione predefinita conosce solo il tipo di iterazione denominato ordine lineare, potremmo voler specificare esplicitamente il tipo di iterazione. A prima vista potrebbe sembrare troppo prolisso, ma a scopo dimostrativo (e per fare la differenza RecursiveIteratorIterator
più visibile in seguito), specifichiamo il tipo lineare di iterazione specificando esplicitamente il IteratorIterator
tipo di iterazione per l'elenco delle directory:
$files = new IteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Questo esempio è quasi identico al primo, la differenza è che $files
ora è un IteratorIterator
tipo di iterazione per Traversable
$dir
:
$files = new IteratorIterator($dir);
Come al solito, l'atto di iterazione viene eseguito da foreach
:
foreach ($files as $file) {
L'output è esattamente lo stesso. Allora cosa c'è di diverso? Diverso è l'oggetto utilizzato all'interno di foreach
. Nel primo esempio è un DirectoryIterator
nel secondo esempio è il IteratorIterator
. Questo mostra la flessibilità che hanno gli iteratori: puoi sostituirli tra loro, il codice all'interno foreach
continua a funzionare come previsto.
Iniziamo per ottenere l'intero elenco, comprese le sottodirectory.
Poiché ora abbiamo specificato il tipo di iterazione, consideriamo di cambiarlo in un altro tipo di iterazione.
Sappiamo che ora dobbiamo attraversare l'intero albero, non solo il primo livello. Per avere che il lavoro con un semplice foreach
abbiamo bisogno di un diverso tipo di iteratore: RecursiveIteratorIterator
. E quello può solo iterare su oggetti contenitore che hanno l' RecursiveIterator
interfaccia .
L'interfaccia è un contratto. Qualsiasi classe che lo implementa può essere utilizzata insieme a RecursiveIteratorIterator
. Un esempio di una tale classe è il RecursiveDirectoryIterator
, che è qualcosa come la variante ricorsiva di DirectoryIterator
.
Vediamo un primo esempio di codice prima di scrivere qualsiasi altra frase con la parola I:
$dir = new RecursiveDirectoryIterator($path);
echo "[$path]\n";
foreach ($dir as $file) {
echo " ├ $file\n";
}
Questo terzo esempio è quasi identico al primo, tuttavia crea un output diverso:
[tree]
├ tree\.
├ tree\..
├ tree\dirA
├ tree\fileA
Ok, non è così diverso, il nome del file ora contiene il percorso davanti, ma anche il resto sembra simile.
Come mostra l'esempio, anche l'oggetto directory implementa già l' RecursiveIterator
interfaccia, questo non è ancora sufficiente per far foreach
attraversare l'intero albero delle directory. È qui che RecursiveIteratorIterator
entra in azione. L'esempio 4 mostra come:
$files = new RecursiveIteratorIterator($dir);
echo "[$path]\n";
foreach ($files as $file) {
echo " ├ $file\n";
}
Usare il RecursiveIteratorIterator
invece del solo $dir
oggetto precedente farà foreach
attraversare tutti i file e le directory in modo ricorsivo. Questo quindi elenca tutti i file, poiché il tipo di iterazione dell'oggetto è stato specificato ora:
[tree]
├ tree\.
├ tree\..
├ tree\dirA\.
├ tree\dirA\..
├ tree\dirA\dirB\.
├ tree\dirA\dirB\..
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
Questo dovrebbe già dimostrare la differenza tra attraversamento piatto e albero. È in RecursiveIteratorIterator
grado di attraversare qualsiasi struttura ad albero come un elenco di elementi. Poiché ci sono più informazioni (come il livello in cui avviene attualmente l'iterazione), è possibile accedere all'oggetto iteratore mentre si itera su di esso e ad esempio indentare l'output:
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
E output dell'Esempio 5 :
[tree]
├ tree\.
├ tree\..
├ tree\dirA\.
├ tree\dirA\..
├ tree\dirA\dirB\.
├ tree\dirA\dirB\..
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
Sicuramente questo non vince un concorso di bellezza, ma mostra che con l'iteratore ricorsivo sono disponibili più informazioni oltre all'ordine lineare di chiave e valore . Anche foreach
solo può esprimere questo tipo di linearità, l'accesso all'iteratore stesso permette di ottenere maggiori informazioni.
Simile alle meta-informazioni ci sono anche diversi modi possibili per attraversare l'albero e quindi ordinare l'output. Questa è la modalità diRecursiveIteratorIterator
e può essere impostata con il costruttore.
Il prossimo esempio dirà RecursiveDirectoryIterator
a di rimuovere le voci di punto ( .
e ..
) poiché non ne abbiamo bisogno. Ma anche la modalità di ricorsione verrà modificata per prendere l'elemento genitore (la sottodirectory) prima ( SELF_FIRST
) prima dei figli (i file e le sottodirectory nella sottodirectory):
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir, RecursiveIteratorIterator::SELF_FIRST);
echo "[$path]\n";
foreach ($files as $file) {
$indent = str_repeat(' ', $files->getDepth());
echo $indent, " ├ $file\n";
}
L'output ora mostra le voci della sottodirectory correttamente elencate, se si confronta con l'output precedente quelle non c'erano:
[tree]
├ tree\dirA
├ tree\dirA\dirB
├ tree\dirA\dirB\fileD
├ tree\dirA\fileB
├ tree\dirA\fileC
├ tree\fileA
La modalità di ricorsione controlla quindi cosa e quando viene restituito un brach o una foglia nell'albero, per l'esempio di directory:
LEAVES_ONLY
(impostazione predefinita): elenca solo i file, nessuna directory.SELF_FIRST
(sopra): elenca la directory e poi i file al suo interno.CHILD_FIRST
(senza esempio): elenca prima i file nella sottodirectory, quindi la directory.Risultato dell'esempio 5 con le altre due modalità:
LEAVES_ONLY CHILD_FIRST
[tree] [tree]
├ tree\dirA\dirB\fileD ├ tree\dirA\dirB\fileD
├ tree\dirA\fileB ├ tree\dirA\dirB
├ tree\dirA\fileC ├ tree\dirA\fileB
├ tree\fileA ├ tree\dirA\fileC
├ tree\dirA
├ tree\fileA
Quando lo confronti con l'attraversamento standard, tutte queste cose non sono disponibili. L'iterazione ricorsiva quindi è un po 'più complessa quando è necessario avvolgere la testa attorno ad essa, tuttavia è facile da usare perché si comporta proprio come un iteratore, lo metti in un foreach
e fatto.
Penso che questi siano esempi sufficienti per una risposta. Puoi trovare il codice sorgente completo e un esempio per visualizzare alberi ascii di bell'aspetto in questo riassunto: https://gist.github.com/3599532
Fai da te: crea il
RecursiveTreeIterator
lavoro riga per riga.
L'esempio 5 ha dimostrato che sono disponibili meta-informazioni sullo stato dell'iteratore. Tuttavia, questo è stato volutamente dimostrata all'interno della foreach
iterazione. Nella vita reale questo appartiene naturalmente al RecursiveIterator
.
Un esempio migliore è il RecursiveTreeIterator
, si occupa del rientro, del prefisso e così via. Vedere il seguente frammento di codice:
$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$lines = new RecursiveTreeIterator($dir);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Il RecursiveTreeIterator
è destinato alla linea di lavoro per riga, l'uscita è abbastanza semplice con un piccolo problema:
[tree]
├ tree\dirA
│ ├ tree\dirA\dirB
│ │ └ tree\dirA\dirB\fileD
│ ├ tree\dirA\fileB
│ └ tree\dirA\fileC
└ tree\fileA
Quando viene utilizzato in combinazione con a RecursiveDirectoryIterator
, visualizza l'intero percorso e non solo il nome del file. Il resto sembra buono. Questo perché i nomi dei file vengono generati da SplFileInfo
. Quelli dovrebbero essere visualizzati come nome di base invece. L'output desiderato è il seguente:
/// Solved ///
[tree]
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA
Crea una classe decoratore che può essere utilizzata al RecursiveTreeIterator
posto di RecursiveDirectoryIterator
. Dovrebbe fornire il nome di base della corrente SplFileInfo
invece del percorso. Il frammento di codice finale potrebbe quindi essere simile a:
$lines = new RecursiveTreeIterator(
new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));
Questi frammenti, inclusi, $unicodeTreePrefix
fanno parte dell'essenza nell'Appendice: Fai da te: crea il RecursiveTreeIterator
lavoro linea per linea. .
RecursiveIteratorIterator
perché questo è in comune con altri tipi, ma ho fornito alcune informazioni tecniche su come funziona effettivamente. Gli esempi credo mostrino bene le differenze: il tipo di iterazione è la differenza principale tra i due. Non ho idea se acquisti il tipo di iterazione che conio in modo leggermente diverso, ma IMHO non è facile con i tipi di iterazione semantici.
Qual è la differenza tra
IteratorIterator
eRecursiveIteratorIterator
?
Per comprendere la differenza tra questi due iteratori, è innanzitutto necessario comprendere un po 'le convenzioni di denominazione utilizzate e cosa si intende per iteratori "ricorsivi".
PHP ha iteratori non "ricorsivi", come ArrayIterator
e FilesystemIterator
. Esistono anche iteratori "ricorsivi" come RecursiveArrayIterator
e RecursiveDirectoryIterator
. Questi ultimi hanno metodi che consentono loro di essere approfonditi, i primi no.
Quando le istanze di questi iteratori vengono ripetute da sole, anche quelle ricorsive, i valori provengono solo dal livello "superiore" anche se si ripetono su un array annidato o una directory con sottodirectory.
Gli iteratori ricorsivi implementano il comportamento ricorsivo (tramite hasChildren()
, getChildren()
) ma non lo sfruttano .
Potrebbe essere meglio pensare agli iteratori ricorsivi come iteratori "ricorsibili", hanno la capacità di essere iterati in modo ricorsivo ma semplicemente iterare su un'istanza di una di queste classi non lo farà. Per sfruttare il comportamento ricorsivo, continua a leggere.
È qui che RecursiveIteratorIterator
entra in gioco. Ha la conoscenza di come chiamare gli iteratori "ricorsibili" in modo tale da approfondire la struttura in un ciclo normale e piatto. Mette in azione il comportamento ricorsivo. Svolge essenzialmente il lavoro di scavalcare ciascuno dei valori nell'iteratore, cercando di vedere se ci sono "figli" in cui ricorrere o meno, ed entrare e uscire da quelle raccolte di bambini. Metti un'istanza di RecursiveIteratorIterator
in un foreach e si tuffa nella struttura in modo che tu non debba farlo.
Se RecursiveIteratorIterator
non fosse stato usato, dovresti scrivere i tuoi cicli ricorsivi per sfruttare il comportamento ricorsivo, confrontandoti con l'iteratore "ricorsivo" hasChildren()
e usando getChildren()
.
Quindi questa è una breve panoramica di RecursiveIteratorIterator
, in cosa è diverso da IteratorIterator
? Bene, in pratica stai facendo lo stesso tipo di domanda di Qual è la differenza tra un gattino e un albero? Solo perché entrambi appaiono nella stessa enciclopedia (o manuale, per gli iteratori) non significa che dovresti confonderti tra i due.
Il compito di IteratorIterator
è quello di prendere qualsiasi Traversable
oggetto e avvolgerlo in modo tale che soddisfi l' Iterator
interfaccia. Un utilizzo per questo è quindi essere in grado di applicare un comportamento specifico dell'iteratore sull'oggetto non iteratore.
Per fare un esempio pratico, la DatePeriod
classe è Traversable
ma non un file Iterator
. In quanto tale, possiamo eseguire il ciclo sui suoi valori con foreach()
ma non possiamo fare altre cose che normalmente faremmo con un iteratore, come il filtraggio.
COMPITO : Loop nei lunedì, mercoledì e venerdì delle prossime quattro settimane.
Sì, questo è banale foreach
passando sopra DatePeriod
e usando un if()
all'interno del ciclo; ma non è questo il punto di questo esempio!
$period = new DatePeriod(new DateTime, new DateInterval('P1D'), 28);
$dates = new CallbackFilterIterator($period, function ($date) {
return in_array($date->format('l'), array('Monday', 'Wednesday', 'Friday'));
});
foreach ($dates as $date) { … }
Lo snippet di cui sopra non funzionerà perché si CallbackFilterIterator
aspetta un'istanza di una classe che implementa l' Iterator
interfaccia, che DatePeriod
non lo fa. Tuttavia, poiché lo è Traversable
, possiamo facilmente soddisfare tale requisito utilizzando IteratorIterator
.
$period = new IteratorIterator(new DatePeriod(…));
Come puoi vedere, questo non ha nulla a che fare con l'iterazione su classi iteratori né con la ricorsione, e qui sta la differenza tra IteratorIterator
e RecursiveIteratorIterator
.
RecursiveIteraratorIterator
serve per iterare su un RecursiveIterator
(iteratore "ricorsibile"), sfruttando il comportamento ricorsivo disponibile.
IteratorIterator
serve per applicare il Iterator
comportamento a Traversable
oggetti non iteratori .
IteratorIterator
solo il tipo standard di attraversamento dell'ordine lineare per gli Traversable
oggetti? Quelli che potrebbero essere usati senza di esso solo foreach
com'è? E ancora di più, non è RecursiveIterator
sempre un Traversable
e quindi non solo IteratorIterator
ma anche RecursiveIteratorIterator
sempre "per applicare Iterator
comportamenti a oggetti non iteratori, Traversable" ? (Ora direi che foreach
applica il tipo di iterazione tramite l'oggetto iteratore su oggetti contenitore che implementano un'interfaccia di tipo iteratore, quindi questi sono oggetti-contenitore-iteratore, sempre Traversable
)
IteratorIterator
è una classe che riguarda il wrapping di Traversable
oggetti in un file Iterator
. Niente di più . Sembra che tu stia applicando il termine in modo più generale.
Recursive
in RecursiveIterator
implica comportamento, mentre un nome più adatto sarebbe stato quello che descrive capacità, come RecursibleIterator
.
Se usato con iterator_to_array()
, RecursiveIteratorIterator
percorrerà in modo ricorsivo l'array per trovare tutti i valori. Significa che appiattirà l'array originale.
IteratorIterator
manterrà la struttura gerarchica originale.
Questo esempio ti mostrerà chiaramente la differenza:
$array = array(
'ford',
'model' => 'F150',
'color' => 'blue',
'options' => array('radio' => 'satellite')
);
$recursiveIterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
var_dump(iterator_to_array($recursiveIterator, true));
$iterator = new IteratorIterator(new ArrayIterator($array));
var_dump(iterator_to_array($iterator,true));
new IteratorIterator(new ArrayIterator($array))
è equivalente a new ArrayIterator($array)
, cioè, l'esterno IteratorIterator
non fa nulla. Inoltre, l'appiattimento dell'output non ha nulla a che fare con iterator_to_array
: converte semplicemente l'iteratore in un array. L'appiattimento è una proprietà del modo in cui RecursiveArrayIterator
percorre il suo iteratore interno.
RecursiveDirectoryIterator mostra l'intero percorso e non solo il nome del file. Il resto sembra buono. Questo perché i nomi dei file vengono generati da SplFileInfo. Quelli dovrebbero invece essere visualizzati come nome di base. L'output desiderato è il seguente:
$path =__DIR__;
$dir = new RecursiveDirectoryIterator($path, FilesystemIterator::SKIP_DOTS);
$files = new RecursiveIteratorIterator($dir,RecursiveIteratorIterator::SELF_FIRST);
while ($files->valid()) {
$file = $files->current();
$filename = $file->getFilename();
$deep = $files->getDepth();
$indent = str_repeat('│ ', $deep);
$files->next();
$valid = $files->valid();
if ($valid and ($files->getDepth() - 1 == $deep or $files->getDepth() == $deep)) {
echo $indent, "├ $filename\n";
} else {
echo $indent, "└ $filename\n";
}
}
produzione:
tree
├ dirA
│ ├ dirB
│ │ └ fileD
│ ├ fileB
│ └ fileC
└ fileA