Jika Anda bertanya bagaimana cara RecursiveIteratorIteratorkerjanya, apakah Anda sudah mengerti cara IteratorIteratorkerjanya? Maksud saya pada dasarnya sama, hanya antarmuka yang dikonsumsi oleh keduanya yang berbeda. Dan apakah Anda lebih tertarik pada beberapa contoh atau Anda ingin melihat perbedaan dari implementasi kode C yang mendasarinya?
hakre
@Gordon Saya tidak yakin bagaimana loop foreach tunggal dapat melintasi semua elemen dalam struktur pohon
varuog
@hakra Saya sekarang mencoba untuk mempelajari semua built-in antarmuka serta antarmuka spl dan implementasi iterator. Saya tertarik untuk mengetahui bagaimana cara kerjanya di latar belakang dengan forach loop dengan beberapa contoh.
varuog
@hakre Mereka berdua sebenarnya sangat berbeda. IteratorIteratorpeta Iteratordan IteratorAggregatemenjadi Iterator, di mana REcusiveIteratorIteratordigunakan untuk melintasi recusivly aRecursiveIterator
Dalam perbedaan IteratorIteratoryang merupakan Iteratortraversal objek implementasi konkret dalam urutan linier (dan secara default menerima semua jenis Traversabledalam konstruktornya), RecursiveIteratorIteratorperulangan memungkinkan atas semua node dalam pohon objek yang diurutkan dan konstruktornya mengambil a RecursiveIterator.
Singkatnya: RecursiveIteratorIteratormemungkinkan Anda untuk melakukan loop di atas pohon, IteratorIteratormemungkinkan Anda untuk melakukan loop di atas daftar. Saya tunjukkan dengan beberapa contoh kode di bawah ini segera.
Secara teknis, ini bekerja dengan mendobrak linieritas dengan melintasi semua turunan node (jika ada). Hal ini dimungkinkan karena menurut definisi semua anak dari sebuah node kembali a RecursiveIterator. Tingkat atas Iteratorkemudian secara internal menumpuk RecursiveIterators yang berbeda berdasarkan kedalamannya dan menyimpan penunjuk ke sub aktif saat ini Iteratoruntuk traversal.
Ini memungkinkan untuk mengunjungi semua simpul pohon.
Prinsip dasarnya sama dengan IteratorIterator: Antarmuka menentukan jenis iterasi dan kelas iterator dasar adalah implementasi dari semantik ini. Bandingkan dengan contoh di bawah ini, untuk perulangan linier dengan foreachAnda biasanya tidak terlalu memikirkan detail implementasi kecuali Anda perlu mendefinisikan yang baru Iterator(misalnya ketika beberapa jenis konkret itu sendiri tidak diimplementasikan Traversable).
Untuk traversal rekursif - kecuali jika Anda tidak menggunakan Traversaliterasi traversal yang ditentukan sebelumnya yang sudah memiliki iterasi traversal rekursif - Anda biasanya perlu membuat instance RecursiveIteratorIteratoriterasi yang ada atau bahkan menulis iterasi traversal rekursif yang TraversableAnda miliki untuk memiliki jenis iterasi traversal ini foreach.
Tip: Anda mungkin tidak menerapkan yang satu atau yang lain milik Anda, jadi ini mungkin sesuatu yang layak dilakukan untuk pengalaman praktis Anda tentang perbedaan yang mereka miliki. Anda menemukan saran DIY di akhir jawaban.
Perbedaan teknis singkatnya:
Sementara IteratorIteratormengambil apapun Traversableuntuk linier traversal, RecursiveIteratorIteratormembutuhkan lebih spesifik RecursiveIteratoruntuk melakukan loop di atas pohon.
Dimana IteratorIteratormengekspos utamanya Iteratormelalui getInnerIerator(), RecursiveIteratorIteratormenyediakan sub- Iteratorhanya aktif saat ini melalui metode itu.
Sementara IteratorIteratorsama sekali tidak menyadari apa pun seperti orang tua atau anak, RecursiveIteratorIteratortahu bagaimana cara mendapatkan dan melintasi anak juga.
IteratorIteratortidak membutuhkan tumpukan iterator, RecursiveIteratorIteratormemiliki tumpukan seperti itu dan mengetahui sub-iterator yang aktif.
Dimana IteratorIteratormemiliki urutannya karena linieritas dan tidak ada pilihan, RecursiveIteratorIteratormemiliki pilihan untuk traversal lebih lanjut dan perlu memutuskan per setiap node (diputuskan melalui mode perRecursiveIteratorIterator ).
RecursiveIteratorIteratormemiliki lebih banyak metode daripada IteratorIterator.
Untuk meringkas: RecursiveIteratoradalah jenis konkret dari iterasi (perulangan di atas pohon) yang bekerja pada iteratornya sendiri, yaitu RecursiveIterator. Itu adalah prinsip dasar yang sama seperti dengan IteratorIerator, tetapi jenis iterasinya berbeda (urutan linier).
Idealnya, Anda juga dapat membuat set sendiri. Satu-satunya hal yang perlu adalah bahwa iterator Anda mengimplementasikan Traversableyang mungkin dilakukan melalui Iteratoratau IteratorAggregate. Kemudian Anda bisa menggunakannya dengan foreach. Misalnya beberapa jenis objek iterasi rekursif traversal terner pohon bersama dengan antarmuka iterasi yang sesuai untuk objek kontainer.
Mari kita ulas dengan beberapa contoh kehidupan nyata yang tidak terlalu abstrak. Antara antarmuka, iterator beton, objek kontainer, dan semantik iterasi, ini mungkin bukan ide yang buruk.
Ambil daftar direktori sebagai contoh. Pertimbangkan Anda telah mendapatkan file dan pohon direktori berikut pada disk:
Sementara iterator dengan urutan linier hanya melintasi folder dan file tingkat atas (daftar direktori tunggal), iterator rekursif juga melintasi subfolder dan mencantumkan semua folder dan file (daftar direktori dengan daftar subdirektorinya):
Anda dapat dengan mudah membandingkan ini dengan IteratorIteratoryang tidak melakukan rekursi untuk melintasi pohon direktori. Dan RecursiveIteratorIteratoryang dapat melintasi pohon seperti yang ditunjukkan daftar Rekursif.
Pada awalnya contoh yang sangat mendasar dengan DirectoryIteratoryang mengimplementasikan Traversableyang memungkinkan foreachuntuk mengulanginya :
Keluaran contoh untuk struktur direktori di atas adalah:
[tree]
├ .
├ ..
├ dirA
├ fileA
Seperti yang Anda lihat, ini belum menggunakan IteratorIteratoratau RecursiveIteratorIterator. Sebaliknya itu hanya menggunakan foreachyang beroperasi pada Traversableantarmuka.
Karena foreachsecara default hanya mengetahui jenis iterasi bernama urutan linier, kita mungkin ingin menentukan jenis iterasi secara eksplisit. Sekilas mungkin tampak terlalu bertele-tele, tetapi untuk tujuan demonstrasi (dan untuk membuat perbedaan dengan RecursiveIteratorIteratorlebih terlihat nanti), mari kita tentukan tipe linier dari iterasi secara eksplisit menentukan IteratorIteratorjenis iterasi untuk daftar direktori:
Contoh ini hampir identik dengan yang pertama, perbedaannya $filesadalah sekarang menjadi IteratorIteratorjenis iterasi untuk Traversable$dir:
$files = newIteratorIterator($dir);
Seperti biasa tindakan iterasi dilakukan oleh foreach:
foreach ($files as $file) {
Outputnya persis sama. Jadi apa bedanya? Yang berbeda adalah objek yang digunakan di dalam foreach. Dalam contoh pertama ini adalah a, DirectoryIteratordalam contoh kedua itu adalah IteratorIterator. Ini menunjukkan fleksibilitas yang dimiliki iterator: Anda dapat menggantinya satu sama lain, kode di dalamnya foreachterus berfungsi seperti yang diharapkan.
Mari mulai mendapatkan seluruh daftar, termasuk subdirektori.
Karena sekarang kita telah menentukan jenis iterasi, mari pertimbangkan untuk mengubahnya ke jenis iterasi lain.
Kami tahu kami perlu melintasi seluruh pohon sekarang, tidak hanya tingkat pertama. Untuk memiliki pekerjaan dengan sederhana foreachkita membutuhkan berbagai jenis iterator: RecursiveIteratorIterator. Dan yang satu itu hanya dapat melakukan iterasi pada objek kontainer yang memiliki RecursiveIteratorantarmuka .
Antarmuka adalah kontrak. Setiap kelas yang mengimplementasikannya dapat digunakan bersama dengan RecursiveIteratorIterator. Contoh dari kelas tersebut adalah the RecursiveDirectoryIterator, yang merupakan varian rekursif dari DirectoryIterator.
Mari kita lihat contoh kode pertama sebelum menulis kalimat lain dengan kata-I:
Oke, tidak jauh berbeda, nama file sekarang berisi nama jalur di depan, tetapi sisanya terlihat serupa juga.
Seperti yang ditunjukkan contoh, bahkan objek direktori sudah mengimplementasikan RecursiveIteratorantarmuka, ini belum cukup untuk foreachmelintasi seluruh pohon direktori. Di sinilah RecursiveIteratorIteratorberaksi. Contoh 4 menunjukkan bagaimana:
Menggunakan RecursiveIteratorIteratoralih - alih hanya $dirobjek sebelumnya akan membuat foreachmelintasi semua file dan direktori secara rekursif. Ini kemudian mencantumkan semua file, karena jenis iterasi objek telah ditentukan sekarang:
Ini seharusnya sudah menunjukkan perbedaan antara traversal datar dan pohon. The RecursiveIteratorIteratormampu melintasi setiap struktur seperti pohon sebagai daftar elemen. Karena terdapat lebih banyak informasi (seperti level yang dilakukan iterasi saat ini), dimungkinkan untuk mengakses objek iterator sambil mengulanginya dan misalnya mengindentasi output:
Tentu ini tidak memenangkan kontes kecantikan, tetapi ini menunjukkan bahwa dengan iterator rekursif ada lebih banyak informasi yang tersedia daripada hanya urutan linier kunci dan nilai . Bahkan foreachhanya dapat mengekspresikan linieritas semacam ini, mengakses iterator itu sendiri memungkinkan untuk memperoleh lebih banyak informasi.
Mirip dengan meta-informasi, ada juga cara berbeda yang memungkinkan bagaimana melintasi pohon dan karenanya mengurutkan keluaran. Ini adalah mode dariRecursiveIteratorIterator dan dapat diatur dengan konstruktor.
Contoh selanjutnya akan memberitahu RecursiveDirectoryIteratoruntuk menghapus entri titik ( .dan ..) karena kita tidak membutuhkannya. Tetapi juga mode rekursi akan diubah untuk mengambil elemen induk (subdirektori) terlebih dahulu ( SELF_FIRST) sebelum anak-anak (file dan sub-subdirektori di subdirektori):
Jika Anda membandingkannya dengan traversal standar, semua hal ini tidak tersedia. Oleh karena itu, iterasi rekursif sedikit lebih kompleks ketika Anda perlu membungkusnya, namun mudah digunakan karena berperilaku seperti iterator, Anda memasukkannya ke dalam foreachdan selesai.
Saya pikir ini adalah contoh yang cukup untuk satu jawaban. Anda dapat menemukan kode sumber lengkap serta contoh untuk menampilkan ascii-tree yang bagus di intinya: https://gist.github.com/3599532
Lakukan Sendiri: Buat RecursiveTreeIteratorPekerjaan Baris demi Baris.
Contoh 5 menunjukkan bahwa ada meta-informasi tentang status iterator yang tersedia. Namun, ini sengaja ditunjukkan dalam yang foreachiterasi. Dalam kehidupan nyata, ini secara alami termasuk di dalam RecursiveIterator.
Contoh yang lebih baik adalah RecursiveTreeIterator, ini menangani indentasi, awalan, dan sebagainya. Lihat fragmen kode berikut:
Saat digunakan dalam kombinasi dengan a, RecursiveDirectoryIteratorini akan menampilkan seluruh nama jalur dan bukan hanya nama file. Sisanya terlihat bagus. Ini karena nama file dibuat oleh SplFileInfo. Itu harus ditampilkan sebagai nama dasar sebagai gantinya. Output yang diinginkan adalah sebagai berikut:
Buat kelas dekorator yang bisa digunakan dengan RecursiveTreeIteratorsebagai pengganti RecursiveDirectoryIterator. Ini harus memberikan nama dasar saat ini, SplFileInfobukan nama jalur. Fragmen kode terakhir akan terlihat seperti ini:
$lines = newRecursiveTreeIterator(
new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo"[$path]\n", implode("\n", iterator_to_array($lines));
Fragmen-fragmen ini termasuk $unicodeTreePrefixmerupakan bagian dari inti dalam Lampiran: Lakukan Sendiri: Buat RecursiveTreeIteratorPekerjaan Baris demi Baris. .
Itu tidak menjawab pertanyaan yang diajukan, mengandung kesalahan faktual dan kehilangan poin inti saat Anda melanjutkan untuk menyesuaikan iterasi. Secara keseluruhan, ini tampak seperti upaya yang buruk untuk mendapatkan hadiah pada topik yang tidak Anda ketahui banyak atau, jika Anda tahu, tidak dapat menyaring menjadi jawaban atas pertanyaan yang diajukan.
salathe
2
Yang tidak menjawab pertanyaan "mengapa" saya, Anda hanya membuat lebih banyak kata tanpa banyak bicara. Mungkin Anda mulai dengan Kesalahan yang diperhitungkan? Tunjuklah, jangan merahasiakannya.
hakre
Kalimat pertama salah: "RecursiveIteratorIterator adalah IteratorIterator yang mendukung…", ini tidak benar.
salathe
1
@ salathe: Terima kasih atas tanggapan Anda. Saya mengedit jawaban untuk alamat itu. Kalimat pertama memang salah dan tidak lengkap. Saya masih meninggalkan detail implementasi konkret RecursiveIteratorIteratorkarena ini sama dengan jenis lain tetapi saya memberikan beberapa info teknis tentang cara kerjanya sebenarnya. Contoh yang menurut saya menunjukkan perbedaannya dengan baik: jenis iterasi adalah perbedaan utama di antara keduanya. Tidak tahu jika Anda membeli jenis iterasi yang Anda koin itu sedikit berbeda tetapi IMHO tidak mudah dengan jenis iterasi semantik yang dimiliki.
hakre
1
Bagian pertama agak diperbaiki, tetapi begitu Anda mulai beralih ke contoh, itu masih berubah menjadi ketidakakuratan faktual. Jika Anda memotong jawaban pada aturan horizontal, itu akan jauh lebih baik.
salathe
32
Apa perbedaan dari IteratorIteratordan RecursiveIteratorIterator?
Untuk memahami perbedaan antara kedua iterator ini, pertama-tama kita harus memahami sedikit tentang konvensi penamaan yang digunakan dan apa yang kami maksud dengan iterator "rekursif".
Iterator rekursif dan non-rekursif
PHP memiliki iterator non- "rekursif", seperti ArrayIteratordan FilesystemIterator. Ada juga iterator "rekursif" seperti RecursiveArrayIteratordan RecursiveDirectoryIterator. Yang terakhir memiliki metode yang memungkinkan mereka untuk dibor, yang pertama tidak.
Ketika instance dari iterator ini di-loop sendiri, bahkan yang rekursif, nilainya hanya datang dari level "top" meskipun melakukan looping pada array atau direktori bersarang dengan sub-direktori.
Iterator rekursif mengimplementasikan perilaku rekursif (via hasChildren(), getChildren()) tetapi tidak mengeksploitasinya .
Mungkin lebih baik untuk menganggap iterator rekursif sebagai iterator "rekursif", mereka memiliki kemampuan untuk diiterasi secara rekursif tetapi hanya melakukan iterasi pada sebuah instance dari salah satu kelas ini tidak akan melakukannya. Untuk memanfaatkan perilaku rekursif, teruslah membaca.
RecursiveIteratorIterator
Di sinilah RecursiveIteratorIteratormasuk untuk bermain. Ia memiliki pengetahuan tentang bagaimana memanggil iterator yang "berulang" sedemikian rupa untuk menelusuri ke dalam struktur dalam loop normal, datar. Ini menempatkan perilaku rekursif ke dalam tindakan. Ini pada dasarnya melakukan pekerjaan untuk melangkahi setiap nilai dalam iterator, mencari untuk melihat apakah ada "anak-anak" untuk muncul kembali atau tidak, dan masuk dan keluar dari koleksi anak-anak itu. Anda memasukkan contoh RecursiveIteratorIteratorke depan, dan itu menyelam ke dalam struktur sehingga Anda tidak perlu melakukannya.
Jika RecursiveIteratorIteratortidak digunakan, Anda harus menulis loop rekursif Anda sendiri untuk mengeksploitasi perilaku rekursif, memeriksa iterator "rekursif" hasChildren()dan menggunakan getChildren().
Nah itulah gambaran singkat tentang RecursiveIteratorIterator, apa bedanya dengan IteratorIterator? Nah, pada dasarnya Anda menanyakan pertanyaan yang sama seperti Apa perbedaan antara anak kucing dan pohon? Hanya karena keduanya muncul dalam ensiklopedia yang sama (atau manual, untuk iterator) tidak berarti Anda harus bingung di antara keduanya.
IteratorIterator
Tugasnya IteratorIteratoradalah mengambil Traversableobjek apa pun , dan membungkusnya sedemikian rupa sehingga memenuhi Iteratorantarmuka. Kegunaannya adalah untuk kemudian dapat menerapkan perilaku khusus iterator pada objek non-iterator.
Untuk memberikan contoh praktis, DatePeriodkelas tersebut Traversablebukan Iterator. Dengan demikian, kita dapat mengulang nilainya dengan foreach()tetapi tidak dapat melakukan hal-hal lain yang biasanya kita lakukan dengan iterator, seperti pemfilteran.
TUGAS : Ulangi hari Senin, Rabu, dan Jumat dalam empat minggu berikutnya.
Ya, ini sepele dengan- foreachmelewati DatePerioddan menggunakan if()dalam loop; tapi bukan itu inti dari contoh ini!
$period = new DatePeriod(new DateTime, new DateInterval('P1D'), 28);
$dates = newCallbackFilterIterator($period, function ($date) {
return in_array($date->format('l'), array('Monday', 'Wednesday', 'Friday'));
});
foreach ($dates as $date) { … }
Cuplikan di atas tidak akan berfungsi karena CallbackFilterIteratormengharapkan instance dari kelas yang mengimplementasikan Iteratorantarmuka, DatePeriodpadahal tidak. Namun, karena itu Traversablekami dapat dengan mudah memenuhi kebutuhan itu dengan menggunakan IteratorIterator.
$period = newIteratorIterator(new DatePeriod(…));
Seperti yang Anda lihat, ini tidak ada hubungannya sama sekali dengan iterasi kelas iterator atau rekursi, dan di situlah letak perbedaan antara IteratorIteratordan RecursiveIteratorIterator.
Ringkasan
RecursiveIteraratorIteratoradalah untuk melakukan iterasi RecursiveIterator(iterator "berulang"), mengeksploitasi perilaku rekursif yang tersedia.
IteratorIteratoradalah untuk menerapkan Iteratorperilaku ke objek non-iterator Traversable.
Bukankah IteratorIteratorhanya tipe standar dari traversal tatanan linier untuk Traversableobjek? Apa yang bisa digunakan tanpa itu foreachapa adanya? Dan lebih jauh lagi, bukankah RecursiveIteratorselalu a Traversabledan karena itu tidak hanya IteratorIteratortetapi juga RecursiveIteratorIteratorselalu "untuk menerapkan Iteratorperilaku ke non-iterator, objek Traversable" ? (Sekarang saya akan mengatakan foreachmenerapkan tipe iterasi melalui objek iterator pada objek kontainer yang mengimplementasikan antarmuka tipe iterator jadi ini adalah objek-kontainer-iterator, selalu Traversable)
hakre
Seperti jawaban saya menyatakan, IteratorIteratoradalah kelas yang semuanya tentang membungkus Traversableobjek dalam file Iterator. Tidak lebih . Anda tampaknya menerapkan istilah tersebut secara lebih umum.
salathe
Jawaban yang tampak informatif. Satu pertanyaan, bukankah RecursiveIteratorIterator juga membungkus objek sehingga mereka juga memiliki akses ke perilaku Iterator? Satu-satunya perbedaan antara keduanya adalah bahwa RecursiveIteratorIterator dapat menelusuri, sedangkan IteratorIterator tidak bisa?
Mike Purcell
@salathe, tahukah Anda mengapa rekursif iterator (RecursiveDirectoryIterator) tidak mengimplementasikan perilaku hasChildren (), getChildren ()?
Anru
8
1 untuk mengatakan "recursible". Nama itu menyesatkan saya untuk waktu yang lama karena Recursivein RecursiveIteratormenyiratkan perilaku, sedangkan nama yang lebih cocok adalah yang menggambarkan kemampuan, seperti RecursibleIterator.
kambing
0
Saat digunakan dengan iterator_to_array(), RecursiveIteratorIteratorakan secara rekursif menjalankan array untuk menemukan semua nilai. Artinya itu akan meratakan array aslinya.
IteratorIterator akan mempertahankan struktur hierarki asli.
Contoh ini akan menunjukkan dengan jelas perbedaannya:
Ini sangat menyesatkan. new IteratorIterator(new ArrayIterator($array))setara dengan new ArrayIterator($array), yaitu, bagian luar IteratorIteratortidak melakukan apa pun. Selain itu, perataan output tidak ada hubungannya dengan iterator_to_array- itu hanya mengubah iterator menjadi array. Perataan adalah sifat dari cara RecursiveArrayIteratorberjalan iterator bagian dalamnya.
Pertanyaan Quolonel
0
RecursiveDirectoryIterator menampilkan seluruh nama jalur dan bukan hanya nama file. Sisanya terlihat bagus. Ini karena nama file dibuat oleh SplFileInfo. Itu harus ditampilkan sebagai nama dasar sebagai gantinya. Output yang diinginkan adalah sebagai berikut:
RecursiveIteratorIterator
kerjanya, apakah Anda sudah mengerti caraIteratorIterator
kerjanya? Maksud saya pada dasarnya sama, hanya antarmuka yang dikonsumsi oleh keduanya yang berbeda. Dan apakah Anda lebih tertarik pada beberapa contoh atau Anda ingin melihat perbedaan dari implementasi kode C yang mendasarinya?IteratorIterator
petaIterator
danIteratorAggregate
menjadiIterator
, di manaREcusiveIteratorIterator
digunakan untuk melintasi recusivly aRecursiveIterator
Jawaban:
RecursiveIteratorIterator
adalah traversal pohonIterator
pelaksana beton . Ini memungkinkan programmer untuk melintasi objek kontainer yang mengimplementasikan antarmuka, lihat Iterator di Wikipedia untuk prinsip umum, jenis, semantik, dan pola iterator.RecursiveIterator
Dalam perbedaan
IteratorIterator
yang merupakanIterator
traversal objek implementasi konkret dalam urutan linier (dan secara default menerima semua jenisTraversable
dalam konstruktornya),RecursiveIteratorIterator
perulangan memungkinkan atas semua node dalam pohon objek yang diurutkan dan konstruktornya mengambil aRecursiveIterator
.Singkatnya:
RecursiveIteratorIterator
memungkinkan Anda untuk melakukan loop di atas pohon,IteratorIterator
memungkinkan Anda untuk melakukan loop di atas daftar. Saya tunjukkan dengan beberapa contoh kode di bawah ini segera.Secara teknis, ini bekerja dengan mendobrak linieritas dengan melintasi semua turunan node (jika ada). Hal ini dimungkinkan karena menurut definisi semua anak dari sebuah node kembali a
RecursiveIterator
. Tingkat atasIterator
kemudian secara internal menumpukRecursiveIterator
s yang berbeda berdasarkan kedalamannya dan menyimpan penunjuk ke sub aktif saat iniIterator
untuk traversal.Ini memungkinkan untuk mengunjungi semua simpul pohon.
Prinsip dasarnya sama dengan
IteratorIterator
: Antarmuka menentukan jenis iterasi dan kelas iterator dasar adalah implementasi dari semantik ini. Bandingkan dengan contoh di bawah ini, untuk perulangan linier denganforeach
Anda biasanya tidak terlalu memikirkan detail implementasi kecuali Anda perlu mendefinisikan yang baruIterator
(misalnya ketika beberapa jenis konkret itu sendiri tidak diimplementasikanTraversable
).Untuk traversal rekursif - kecuali jika Anda tidak menggunakan
Traversal
iterasi traversal yang ditentukan sebelumnya yang sudah memiliki iterasi traversal rekursif - Anda biasanya perlu membuat instanceRecursiveIteratorIterator
iterasi yang ada atau bahkan menulis iterasi traversal rekursif yangTraversable
Anda miliki untuk memiliki jenis iterasi traversal iniforeach
.Perbedaan teknis singkatnya:
IteratorIterator
mengambil apapunTraversable
untuk linier traversal,RecursiveIteratorIterator
membutuhkan lebih spesifikRecursiveIterator
untuk melakukan loop di atas pohon.IteratorIterator
mengekspos utamanyaIterator
melaluigetInnerIerator()
,RecursiveIteratorIterator
menyediakan sub-Iterator
hanya aktif saat ini melalui metode itu.IteratorIterator
sama sekali tidak menyadari apa pun seperti orang tua atau anak,RecursiveIteratorIterator
tahu bagaimana cara mendapatkan dan melintasi anak juga.IteratorIterator
tidak membutuhkan tumpukan iterator,RecursiveIteratorIterator
memiliki tumpukan seperti itu dan mengetahui sub-iterator yang aktif.IteratorIterator
memiliki urutannya karena linieritas dan tidak ada pilihan,RecursiveIteratorIterator
memiliki pilihan untuk traversal lebih lanjut dan perlu memutuskan per setiap node (diputuskan melalui mode perRecursiveIteratorIterator
).RecursiveIteratorIterator
memiliki lebih banyak metode daripadaIteratorIterator
.Untuk meringkas:
RecursiveIterator
adalah jenis konkret dari iterasi (perulangan di atas pohon) yang bekerja pada iteratornya sendiri, yaituRecursiveIterator
. Itu adalah prinsip dasar yang sama seperti denganIteratorIerator
, tetapi jenis iterasinya berbeda (urutan linier).Idealnya, Anda juga dapat membuat set sendiri. Satu-satunya hal yang perlu adalah bahwa iterator Anda mengimplementasikan
Traversable
yang mungkin dilakukan melaluiIterator
atauIteratorAggregate
. Kemudian Anda bisa menggunakannya denganforeach
. Misalnya beberapa jenis objek iterasi rekursif traversal terner pohon bersama dengan antarmuka iterasi yang sesuai untuk objek kontainer.Mari kita ulas dengan beberapa contoh kehidupan nyata yang tidak terlalu abstrak. Antara antarmuka, iterator beton, objek kontainer, dan semantik iterasi, ini mungkin bukan ide yang buruk.
Ambil daftar direktori sebagai contoh. Pertimbangkan Anda telah mendapatkan file dan pohon direktori berikut pada disk:
Sementara iterator dengan urutan linier hanya melintasi folder dan file tingkat atas (daftar direktori tunggal), iterator rekursif juga melintasi subfolder dan mencantumkan semua folder dan file (daftar direktori dengan daftar subdirektorinya):
Anda dapat dengan mudah membandingkan ini dengan
IteratorIterator
yang tidak melakukan rekursi untuk melintasi pohon direktori. DanRecursiveIteratorIterator
yang dapat melintasi pohon seperti yang ditunjukkan daftar Rekursif.Pada awalnya contoh yang sangat mendasar dengan
DirectoryIterator
yang mengimplementasikanTraversable
yang memungkinkanforeach
untuk mengulanginya :$path = 'tree'; $dir = new DirectoryIterator($path); echo "[$path]\n"; foreach ($dir as $file) { echo " ├ $file\n"; }
Keluaran contoh untuk struktur direktori di atas adalah:
Seperti yang Anda lihat, ini belum menggunakan
IteratorIterator
atauRecursiveIteratorIterator
. Sebaliknya itu hanya menggunakanforeach
yang beroperasi padaTraversable
antarmuka.Karena
foreach
secara default hanya mengetahui jenis iterasi bernama urutan linier, kita mungkin ingin menentukan jenis iterasi secara eksplisit. Sekilas mungkin tampak terlalu bertele-tele, tetapi untuk tujuan demonstrasi (dan untuk membuat perbedaan denganRecursiveIteratorIterator
lebih terlihat nanti), mari kita tentukan tipe linier dari iterasi secara eksplisit menentukanIteratorIterator
jenis iterasi untuk daftar direktori:$files = new IteratorIterator($dir); echo "[$path]\n"; foreach ($files as $file) { echo " ├ $file\n"; }
Contoh ini hampir identik dengan yang pertama, perbedaannya
$files
adalah sekarang menjadiIteratorIterator
jenis iterasi untukTraversable
$dir
:$files = new IteratorIterator($dir);
Seperti biasa tindakan iterasi dilakukan oleh
foreach
:foreach ($files as $file) {
Outputnya persis sama. Jadi apa bedanya? Yang berbeda adalah objek yang digunakan di dalam
foreach
. Dalam contoh pertama ini adalah a,DirectoryIterator
dalam contoh kedua itu adalahIteratorIterator
. Ini menunjukkan fleksibilitas yang dimiliki iterator: Anda dapat menggantinya satu sama lain, kode di dalamnyaforeach
terus berfungsi seperti yang diharapkan.Mari mulai mendapatkan seluruh daftar, termasuk subdirektori.
Karena sekarang kita telah menentukan jenis iterasi, mari pertimbangkan untuk mengubahnya ke jenis iterasi lain.
Kami tahu kami perlu melintasi seluruh pohon sekarang, tidak hanya tingkat pertama. Untuk memiliki pekerjaan dengan sederhana
foreach
kita membutuhkan berbagai jenis iterator:RecursiveIteratorIterator
. Dan yang satu itu hanya dapat melakukan iterasi pada objek kontainer yang memilikiRecursiveIterator
antarmuka .Antarmuka adalah kontrak. Setiap kelas yang mengimplementasikannya dapat digunakan bersama dengan
RecursiveIteratorIterator
. Contoh dari kelas tersebut adalah theRecursiveDirectoryIterator
, yang merupakan varian rekursif dariDirectoryIterator
.Mari kita lihat contoh kode pertama sebelum menulis kalimat lain dengan kata-I:
$dir = new RecursiveDirectoryIterator($path); echo "[$path]\n"; foreach ($dir as $file) { echo " ├ $file\n"; }
Contoh ketiga ini hampir identik dengan yang pertama, namun menghasilkan beberapa keluaran yang berbeda:
Oke, tidak jauh berbeda, nama file sekarang berisi nama jalur di depan, tetapi sisanya terlihat serupa juga.
Seperti yang ditunjukkan contoh, bahkan objek direktori sudah mengimplementasikan
RecursiveIterator
antarmuka, ini belum cukup untukforeach
melintasi seluruh pohon direktori. Di sinilahRecursiveIteratorIterator
beraksi. Contoh 4 menunjukkan bagaimana:$files = new RecursiveIteratorIterator($dir); echo "[$path]\n"; foreach ($files as $file) { echo " ├ $file\n"; }
Menggunakan
RecursiveIteratorIterator
alih - alih hanya$dir
objek sebelumnya akan membuatforeach
melintasi semua file dan direktori secara rekursif. Ini kemudian mencantumkan semua file, karena jenis iterasi objek telah ditentukan sekarang:Ini seharusnya sudah menunjukkan perbedaan antara traversal datar dan pohon. The
RecursiveIteratorIterator
mampu melintasi setiap struktur seperti pohon sebagai daftar elemen. Karena terdapat lebih banyak informasi (seperti level yang dilakukan iterasi saat ini), dimungkinkan untuk mengakses objek iterator sambil mengulanginya dan misalnya mengindentasi output:echo "[$path]\n"; foreach ($files as $file) { $indent = str_repeat(' ', $files->getDepth()); echo $indent, " ├ $file\n"; }
Dan keluaran dari Contoh 5 :
Tentu ini tidak memenangkan kontes kecantikan, tetapi ini menunjukkan bahwa dengan iterator rekursif ada lebih banyak informasi yang tersedia daripada hanya urutan linier kunci dan nilai . Bahkan
foreach
hanya dapat mengekspresikan linieritas semacam ini, mengakses iterator itu sendiri memungkinkan untuk memperoleh lebih banyak informasi.Mirip dengan meta-informasi, ada juga cara berbeda yang memungkinkan bagaimana melintasi pohon dan karenanya mengurutkan keluaran. Ini adalah mode dari
RecursiveIteratorIterator
dan dapat diatur dengan konstruktor.Contoh selanjutnya akan memberitahu
RecursiveDirectoryIterator
untuk menghapus entri titik (.
dan..
) karena kita tidak membutuhkannya. Tetapi juga mode rekursi akan diubah untuk mengambil elemen induk (subdirektori) terlebih dahulu (SELF_FIRST
) sebelum anak-anak (file dan sub-subdirektori di subdirektori):$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"; }
Output sekarang menunjukkan entri subdirektori terdaftar dengan benar, jika Anda membandingkan dengan output sebelumnya yang tidak ada:
Karena itu, mode rekursi mengontrol apa dan kapan brach atau leaf di pohon dikembalikan, untuk contoh direktori:
LEAVES_ONLY
(default): Hanya daftar file, tidak ada direktori.SELF_FIRST
(atas): Daftar direktori dan kemudian file-file di sana.CHILD_FIRST
(tanpa contoh): Buat daftar file di subdirektori terlebih dahulu, lalu direktori.Output dari Contoh 5 dengan dua mode lainnya:
Jika Anda membandingkannya dengan traversal standar, semua hal ini tidak tersedia. Oleh karena itu, iterasi rekursif sedikit lebih kompleks ketika Anda perlu membungkusnya, namun mudah digunakan karena berperilaku seperti iterator, Anda memasukkannya ke dalam
foreach
dan selesai.Saya pikir ini adalah contoh yang cukup untuk satu jawaban. Anda dapat menemukan kode sumber lengkap serta contoh untuk menampilkan ascii-tree yang bagus di intinya: https://gist.github.com/3599532
Contoh 5 menunjukkan bahwa ada meta-informasi tentang status iterator yang tersedia. Namun, ini sengaja ditunjukkan dalam yang
foreach
iterasi. Dalam kehidupan nyata, ini secara alami termasuk di dalamRecursiveIterator
.Contoh yang lebih baik adalah
RecursiveTreeIterator
, ini menangani indentasi, awalan, dan sebagainya. Lihat fragmen kode berikut:$dir = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS); $lines = new RecursiveTreeIterator($dir); $unicodeTreePrefix($lines); echo "[$path]\n", implode("\n", iterator_to_array($lines));
Ini
RecursiveTreeIterator
dimaksudkan untuk bekerja baris demi baris, hasilnya cukup lurus ke depan dengan satu masalah kecil:Saat digunakan dalam kombinasi dengan a,
RecursiveDirectoryIterator
ini akan menampilkan seluruh nama jalur dan bukan hanya nama file. Sisanya terlihat bagus. Ini karena nama file dibuat olehSplFileInfo
. Itu harus ditampilkan sebagai nama dasar sebagai gantinya. Output yang diinginkan adalah sebagai berikut:/// Solved /// [tree] ├ dirA │ ├ dirB │ │ └ fileD │ ├ fileB │ └ fileC └ fileA
Buat kelas dekorator yang bisa digunakan dengan
RecursiveTreeIterator
sebagai penggantiRecursiveDirectoryIterator
. Ini harus memberikan nama dasar saat ini,SplFileInfo
bukan nama jalur. Fragmen kode terakhir akan terlihat seperti ini:$lines = new RecursiveTreeIterator( new DiyRecursiveDecorator($dir) ); $unicodeTreePrefix($lines); echo "[$path]\n", implode("\n", iterator_to_array($lines));
Fragmen-fragmen ini termasuk
$unicodeTreePrefix
merupakan bagian dari inti dalam Lampiran: Lakukan Sendiri: BuatRecursiveTreeIterator
Pekerjaan Baris demi Baris. .sumber
RecursiveIteratorIterator
karena ini sama dengan jenis lain tetapi saya memberikan beberapa info teknis tentang cara kerjanya sebenarnya. Contoh yang menurut saya menunjukkan perbedaannya dengan baik: jenis iterasi adalah perbedaan utama di antara keduanya. Tidak tahu jika Anda membeli jenis iterasi yang Anda koin itu sedikit berbeda tetapi IMHO tidak mudah dengan jenis iterasi semantik yang dimiliki.Untuk memahami perbedaan antara kedua iterator ini, pertama-tama kita harus memahami sedikit tentang konvensi penamaan yang digunakan dan apa yang kami maksud dengan iterator "rekursif".
Iterator rekursif dan non-rekursif
PHP memiliki iterator non- "rekursif", seperti
ArrayIterator
danFilesystemIterator
. Ada juga iterator "rekursif" sepertiRecursiveArrayIterator
danRecursiveDirectoryIterator
. Yang terakhir memiliki metode yang memungkinkan mereka untuk dibor, yang pertama tidak.Ketika instance dari iterator ini di-loop sendiri, bahkan yang rekursif, nilainya hanya datang dari level "top" meskipun melakukan looping pada array atau direktori bersarang dengan sub-direktori.
Iterator rekursif mengimplementasikan perilaku rekursif (via
hasChildren()
,getChildren()
) tetapi tidak mengeksploitasinya .Mungkin lebih baik untuk menganggap iterator rekursif sebagai iterator "rekursif", mereka memiliki kemampuan untuk diiterasi secara rekursif tetapi hanya melakukan iterasi pada sebuah instance dari salah satu kelas ini tidak akan melakukannya. Untuk memanfaatkan perilaku rekursif, teruslah membaca.
RecursiveIteratorIterator
Di sinilah
RecursiveIteratorIterator
masuk untuk bermain. Ia memiliki pengetahuan tentang bagaimana memanggil iterator yang "berulang" sedemikian rupa untuk menelusuri ke dalam struktur dalam loop normal, datar. Ini menempatkan perilaku rekursif ke dalam tindakan. Ini pada dasarnya melakukan pekerjaan untuk melangkahi setiap nilai dalam iterator, mencari untuk melihat apakah ada "anak-anak" untuk muncul kembali atau tidak, dan masuk dan keluar dari koleksi anak-anak itu. Anda memasukkan contohRecursiveIteratorIterator
ke depan, dan itu menyelam ke dalam struktur sehingga Anda tidak perlu melakukannya.Jika
RecursiveIteratorIterator
tidak digunakan, Anda harus menulis loop rekursif Anda sendiri untuk mengeksploitasi perilaku rekursif, memeriksa iterator "rekursif"hasChildren()
dan menggunakangetChildren()
.Nah itulah gambaran singkat tentang
RecursiveIteratorIterator
, apa bedanya denganIteratorIterator
? Nah, pada dasarnya Anda menanyakan pertanyaan yang sama seperti Apa perbedaan antara anak kucing dan pohon? Hanya karena keduanya muncul dalam ensiklopedia yang sama (atau manual, untuk iterator) tidak berarti Anda harus bingung di antara keduanya.IteratorIterator
Tugasnya
IteratorIterator
adalah mengambilTraversable
objek apa pun , dan membungkusnya sedemikian rupa sehingga memenuhiIterator
antarmuka. Kegunaannya adalah untuk kemudian dapat menerapkan perilaku khusus iterator pada objek non-iterator.Untuk memberikan contoh praktis,
DatePeriod
kelas tersebutTraversable
bukanIterator
. Dengan demikian, kita dapat mengulang nilainya denganforeach()
tetapi tidak dapat melakukan hal-hal lain yang biasanya kita lakukan dengan iterator, seperti pemfilteran.TUGAS : Ulangi hari Senin, Rabu, dan Jumat dalam empat minggu berikutnya.
Ya, ini sepele dengan-
foreach
melewatiDatePeriod
dan menggunakanif()
dalam loop; tapi bukan itu inti dari contoh ini!$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) { … }
Cuplikan di atas tidak akan berfungsi karena
CallbackFilterIterator
mengharapkan instance dari kelas yang mengimplementasikanIterator
antarmuka,DatePeriod
padahal tidak. Namun, karena ituTraversable
kami dapat dengan mudah memenuhi kebutuhan itu dengan menggunakanIteratorIterator
.$period = new IteratorIterator(new DatePeriod(…));
Seperti yang Anda lihat, ini tidak ada hubungannya sama sekali dengan iterasi kelas iterator atau rekursi, dan di situlah letak perbedaan antara
IteratorIterator
danRecursiveIteratorIterator
.Ringkasan
RecursiveIteraratorIterator
adalah untuk melakukan iterasiRecursiveIterator
(iterator "berulang"), mengeksploitasi perilaku rekursif yang tersedia.IteratorIterator
adalah untuk menerapkanIterator
perilaku ke objek non-iteratorTraversable
.sumber
IteratorIterator
hanya tipe standar dari traversal tatanan linier untukTraversable
objek? Apa yang bisa digunakan tanpa ituforeach
apa adanya? Dan lebih jauh lagi, bukankahRecursiveIterator
selalu aTraversable
dan karena itu tidak hanyaIteratorIterator
tetapi jugaRecursiveIteratorIterator
selalu "untuk menerapkanIterator
perilaku ke non-iterator, objek Traversable" ? (Sekarang saya akan mengatakanforeach
menerapkan tipe iterasi melalui objek iterator pada objek kontainer yang mengimplementasikan antarmuka tipe iterator jadi ini adalah objek-kontainer-iterator, selaluTraversable
)IteratorIterator
adalah kelas yang semuanya tentang membungkusTraversable
objek dalam fileIterator
. Tidak lebih . Anda tampaknya menerapkan istilah tersebut secara lebih umum.Recursive
inRecursiveIterator
menyiratkan perilaku, sedangkan nama yang lebih cocok adalah yang menggambarkan kemampuan, sepertiRecursibleIterator
.Saat digunakan dengan
iterator_to_array()
,RecursiveIteratorIterator
akan secara rekursif menjalankan array untuk menemukan semua nilai. Artinya itu akan meratakan array aslinya.IteratorIterator
akan mempertahankan struktur hierarki asli.Contoh ini akan menunjukkan dengan jelas perbedaannya:
$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));
sumber
new IteratorIterator(new ArrayIterator($array))
setara dengannew ArrayIterator($array)
, yaitu, bagian luarIteratorIterator
tidak melakukan apa pun. Selain itu, perataan output tidak ada hubungannya denganiterator_to_array
- itu hanya mengubah iterator menjadi array. Perataan adalah sifat dari caraRecursiveArrayIterator
berjalan iterator bagian dalamnya.RecursiveDirectoryIterator menampilkan seluruh nama jalur dan bukan hanya nama file. Sisanya terlihat bagus. Ini karena nama file dibuat oleh SplFileInfo. Itu harus ditampilkan sebagai nama dasar sebagai gantinya. Output yang diinginkan adalah sebagai berikut:
$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"; } }
keluaran:
sumber