RecursiveIteratorIterator ทำงานอย่างไรใน PHP?


89

วิธีการRecursiveIteratorIteratorทำงานหรือไม่

คู่มือ PHP ไม่มีเอกสารหรือคำอธิบายมากนัก อะไรคือความแตกต่างระหว่างIteratorIteratorและRecursiveIteratorIterator?


2
มีตัวอย่างที่php.net/manual/en/recursiveiteratoriterator.construct.phpและยังมี Introduction ที่php.net/manual/en/class.iteratoriterator.php - คุณช่วยชี้ให้เห็นสิ่งที่คุณมีปัญหาในการทำความเข้าใจ . คู่มือควรมีอะไรบ้างเพื่อให้เข้าใจได้ง่ายขึ้น
Gordon

1
ถ้าคุณถามว่าRecursiveIteratorIteratorทำงานอย่างไรคุณเข้าใจวิธีการIteratorIteratorทำงานแล้วหรือยัง? ฉันหมายความว่ามันเหมือนกันโดยทั่วไปมีเพียงอินเทอร์เฟซที่ใช้โดยทั้งสองเท่านั้นที่แตกต่างกัน และคุณสนใจตัวอย่างบางส่วนมากขึ้นหรือคุณต้องการเห็นความแตกต่างของการใช้งานโค้ด C หรือไม่?
hakre

@ Gordon ฉันไม่แน่ใจว่า single foreach loop สามารถข้ามองค์ประกอบทั้งหมดในโครงสร้างต้นไม้ได้อย่างไร
varuog

@hakra ตอนนี้ฉันกำลังพยายามศึกษาอินเทอร์เฟซในตัวทั้งหมดรวมถึงอินเทอร์เฟซ spl และการใช้งานตัววนซ้ำฉันสนใจที่จะรู้ว่ามันทำงานในพื้นหลังอย่างไรกับ forach loop พร้อมตัวอย่างบางส่วน
varuog

@hakre ทั้งคู่ต่างกันมาก IteratorIteratorแผนที่IteratorและIteratorAggregateเข้าไปในIteratorตำแหน่งที่REcusiveIteratorIteratorใช้ในการสำรวจซ้ำRecursiveIterator
อดัม

คำตอบ:


253

RecursiveIteratorIteratorเป็นรูปธรรมIteratorการดำเนินการสำรวจเส้นทางต้นไม้ ช่วยให้โปรแกรมเมอร์สามารถสำรวจวัตถุคอนเทนเนอร์ที่ใช้RecursiveIteratorอินเทอร์เฟซได้โปรดดูIterator ใน Wikipediaสำหรับหลักการทั่วไปประเภทความหมายและรูปแบบของตัววนซ้ำ

ในความแตกต่างIteratorIteratorที่เป็นคอนกรีตที่Iteratorใช้การข้ามผ่านวัตถุตามลำดับเชิงเส้น (และโดยค่าเริ่มต้นจะยอมรับชนิดใด ๆTraversableในตัวสร้าง) การRecursiveIteratorIteratorอนุญาตให้วนซ้ำบนโหนดทั้งหมดในโครงสร้างของวัตถุที่เรียงลำดับและตัวสร้างจะใช้เวลา a RecursiveIterator.

ในระยะสั้น: RecursiveIteratorIteratorช่วยให้คุณวนรอบต้นไม้IteratorIteratorช่วยให้คุณวนซ้ำรายการได้ ฉันแสดงให้เห็นพร้อมตัวอย่างโค้ดด้านล่างเร็ว ๆ นี้

ในทางเทคนิคจะได้ผลโดยการแยกออกจากความเป็นเชิงเส้นโดยการข้ามลูกของโหนดทั้งหมด (ถ้ามี) สิ่งนี้เป็นไปได้เพราะตามนิยามชายด์ทั้งหมดของโหนดเป็น a RecursiveIterator. Iteratorจากนั้นระดับบนสุดจะเรียงซ้อนRecursiveIterators ที่แตกต่างกันภายในโดยความลึกและเก็บตัวชี้ไปยังส่วนย่อยที่ใช้งานอยู่ในปัจจุบันIteratorสำหรับการข้ามผ่าน

สิ่งนี้ช่วยให้สามารถเยี่ยมชมโหนดทั้งหมดของต้นไม้ได้

หลักการพื้นฐานจะเหมือนกับIteratorIterator: อินเทอร์เฟซระบุประเภทของการวนซ้ำและคลาสตัววนซ้ำพื้นฐานคือการนำความหมายเหล่านี้ไปใช้ เปรียบเทียบกับตัวอย่างด้านล่างสำหรับการวนซ้ำเชิงเส้นโดยforeachปกติคุณไม่ได้คิดถึงรายละเอียดการใช้งานมากนักเว้นแต่คุณจะต้องกำหนดใหม่Iterator(เช่นเมื่อคอนกรีตบางประเภทไม่ได้ใช้งานTraversable)

สำหรับการสำรวจเส้นทาง recursive - ถ้าคุณไม่ได้ใช้ที่กำหนดไว้ล่วงหน้าTraversalที่มีอยู่แล้ว recursive สำรวจเส้นทางซ้ำ - โดยปกติคุณจะต้องอินสแตนซ์ที่มีอยู่RecursiveIteratorIteratorซ้ำหรือแม้กระทั่งเขียน recursive สำรวจเส้นทางย้ำว่าเป็นของคุณเองที่จะมีประเภทของการสำรวจเส้นทางนี้ซ้ำกับTraversableforeach

เคล็ดลับ:คุณอาจไม่ได้ใช้อย่างใดอย่างหนึ่งหรือใช้ของคุณเองดังนั้นนี่อาจเป็นสิ่งที่คุ้มค่าที่จะทำเพื่อประสบการณ์จริงของความแตกต่างที่พวกเขามี คุณจะพบคำแนะนำ DIY ในตอนท้ายของคำตอบ

ความแตกต่างทางเทคนิคโดยย่อ:

  • แม้ว่าIteratorIteratorจะใช้เวลาใด ๆTraversableสำหรับการส่งผ่านเชิงเส้น แต่RecursiveIteratorIteratorต้องการความเฉพาะเจาะจงมากขึ้นRecursiveIteratorในการวนซ้ำต้นไม้
  • ที่ไหนIteratorIteratorexposes หลักIteratorผ่านgetInnerIerator(), RecursiveIteratorIteratorให้ย่อยที่ใช้งานอยู่ในปัจจุบันIteratorเพียงผ่านวิธีการที่
  • ในขณะที่IteratorIteratorไม่รู้อะไรเลยเหมือนพ่อแม่หรือลูก แต่RecursiveIteratorIteratorก็รู้วิธีรับและสำรวจเด็กด้วยเช่นกัน
  • IteratorIteratorไม่จำเป็นต้องมีสแต็กของตัวทำซ้ำRecursiveIteratorIteratorมีสแต็กดังกล่าวและรู้จักตัวทำซ้ำย่อยที่ใช้งานอยู่
  • ที่ไหนIteratorIteratorมีลำดับเนื่องจากความเป็นเชิงเส้นและไม่มีทางเลือกRecursiveIteratorIteratorมีทางเลือกสำหรับการข้ามผ่านเพิ่มเติมและจำเป็นต้องตัดสินใจต่อแต่ละโหนด (ตัดสินใจผ่านโหมดต่อRecursiveIteratorIterator )
  • RecursiveIteratorIteratorIteratorIteratorมีวิธีการมากกว่า

เพื่อสรุป: RecursiveIteratorเป็นชนิดที่เป็นรูปธรรมของการทำซ้ำ (วนลูปกับต้นไม้) ที่ทำงานบน iterators RecursiveIteratorของตัวเองคือ นั่นเป็นหลักการพื้นฐานเช่นเดียวกับIteratorIeratorแต่ประเภทของการวนซ้ำแตกต่างกัน (ลำดับเชิงเส้น)

คุณสามารถสร้างชุดของคุณเองได้เช่นกัน สิ่งเดียวที่จำเป็นก็คือว่าการดำเนินการของคุณ iterator Traversableซึ่งเป็นไปได้ทางหรือIterator IteratorAggregateจากนั้นคุณสามารถใช้กับforeach. ตัวอย่างเช่นออบเจ็กต์การวนซ้ำแบบวนซ้ำแบบวนซ้ำแบบ ternary tree ร่วมกับอินเทอร์เฟซการวนซ้ำตามสำหรับอ็อบเจ็กต์คอนเทนเนอร์


ลองทบทวนกับตัวอย่างชีวิตจริงที่ไม่ได้เป็นนามธรรม ระหว่างอินเทอร์เฟซตัวทำซ้ำคอนกรีตวัตถุคอนเทนเนอร์และความหมายการวนซ้ำนี่อาจไม่ใช่ความคิดที่ไม่ดี

ยกตัวอย่างรายการไดเร็กทอรี พิจารณาว่าคุณมีไฟล์และแผนผังไดเร็กทอรีต่อไปนี้บนดิสก์:

แผนผังไดเรกทอรี

ในขณะที่ตัววนซ้ำที่มีลำดับเชิงเส้นจะเคลื่อนที่ผ่านโฟลเดอร์และไฟล์ระดับบนสุด (รายการไดเร็กทอรีเดียว) ตัววนซ้ำแบบวนซ้ำจะข้ามผ่านโฟลเดอร์ย่อยเช่นกันและแสดงรายการโฟลเดอร์และไฟล์ทั้งหมด (รายการไดเร็กทอรีพร้อมรายการไดเร็กทอรีย่อย):

Non-Recursive        Recursive
=============        =========

   [tree]            [tree]
    ├ dirA            ├ dirA
    └ fileA           │ ├ dirB
                      │ │ └ fileD
                      │ ├ fileB
                      │ └ fileC
                      └ fileA

คุณสามารถเปรียบเทียบสิ่งนี้ได้อย่างง่ายดายโดยIteratorIteratorที่ไม่มีการเรียกซ้ำสำหรับการข้ามโครงสร้างไดเรกทอรี และสิ่งRecursiveIteratorIteratorที่สามารถทะลุเข้าไปในต้นไม้ได้ตามที่รายการเรียกซ้ำแสดง

ที่เป็นตัวอย่างที่พื้นฐานมากเป็นครั้งแรกด้วยDirectoryIteratorที่ดำเนินการTraversableซึ่งจะช่วยให้foreachการย้ำมากกว่านั้น:

$path = 'tree';
$dir  = new DirectoryIterator($path);

echo "[$path]\n";
foreach ($dir as $file) {
    echo " ├ $file\n";
}

ผลลัพธ์ที่เป็นแบบอย่างสำหรับโครงสร้างไดเร็กทอรีด้านบนคือ:

[tree]
 ├ .
 ├ ..
 ├ dirA
 ├ fileA

อย่างที่คุณเห็นนี้ยังไม่ได้ใช้IteratorIteratorหรือRecursiveIteratorIterator. แต่เพียงแค่ใช้foreachที่ทำงานบนTraversableอินเทอร์เฟซ

ในฐานะที่foreachเป็นค่าเริ่มต้นเท่านั้นที่รู้ประเภทของการทำซ้ำชื่อยาวเพื่อที่เราอาจต้องการที่จะระบุชนิดของการย้ำอย่างชัดเจน เมื่อมองแวบแรกอาจดูเหมือนละเอียดเกินไป แต่เพื่อวัตถุประสงค์ในการสาธิต (และเพื่อสร้างความแตกต่างโดยRecursiveIteratorIteratorให้มองเห็นได้มากขึ้นในภายหลัง) ให้ระบุประเภทของการวนซ้ำเชิงเส้นโดยระบุประเภทของการวนซ้ำอย่างชัดเจนIteratorIteratorสำหรับรายการไดเร็กทอรี:

$files = new IteratorIterator($dir);

echo "[$path]\n";
foreach ($files as $file) {
    echo " ├ $file\n";
}

ตัวอย่างนี้เกือบจะเหมือนกันกับตัวอย่างแรกความแตกต่าง$filesคือตอนนี้เป็นIteratorIteratorประเภทของการวนซ้ำสำหรับTraversable $dir:

$files = new IteratorIterator($dir);

ตามปกติการทำซ้ำจะดำเนินการโดยforeach:

foreach ($files as $file) {

ผลลัพธ์จะเหมือนกันทุกประการ แล้วอะไรที่แตกต่างกัน? สิ่งที่แตกต่างคือวัตถุที่ใช้ภายในไฟล์foreach. ในตัวอย่างแรกเป็นตัวอย่างDirectoryIteratorที่สองเป็นไฟล์IteratorIterator. สิ่งนี้แสดงให้เห็นถึงความยืดหยุ่นที่ตัวทำซ้ำมี: คุณสามารถแทนที่ด้วยกันได้โค้ดที่อยู่ภายในforeachจะยังคงทำงานตามที่คาดไว้

มาเริ่มรับรายชื่อทั้งหมดรวมถึงไดเรกทอรีย่อย

เนื่องจากตอนนี้เราได้ระบุประเภทของการทำซ้ำแล้วให้พิจารณาเปลี่ยนเป็นการทำซ้ำประเภทอื่น

เรารู้ว่าเราต้องสำรวจต้นไม้ทั้งต้นไม่ใช่แค่ระดับแรกเท่านั้น ให้มีการทำงานที่เรียบง่ายกับforeachเราต้องมีชนิดที่แตกต่างกันของ RecursiveIteratorIteratoriterator: และสามารถทำซ้ำได้เฉพาะวัตถุคอนเทนเนอร์ที่มีRecursiveIteratorอินเทอร์เฟซเท่านั้น

อินเทอร์เฟซเป็นสัญญา คลาสใด ๆ ที่ใช้งานได้สามารถใช้ร่วมกับRecursiveIteratorIterator. ตัวอย่างของชั้นดังกล่าวเป็นซึ่งเป็นสิ่งที่ต้องการที่แตกต่างของRecursiveDirectoryIterator recursiveDirectoryIterator

มาดูตัวอย่างโค้ดแรกก่อนที่จะเขียนประโยคอื่นด้วย I-word:

$dir  = new RecursiveDirectoryIterator($path);

echo "[$path]\n";
foreach ($dir as $file) {
    echo " ├ $file\n";
}

ตัวอย่างที่สามนี้เกือบจะเหมือนกันกับตัวอย่างแรก แต่จะสร้างผลลัพธ์ที่แตกต่างกัน:

[tree]
 ├ tree\.
 ├ tree\..
 ├ tree\dirA
 ├ tree\fileA

โอเคไม่ต่างกันตอนนี้ชื่อไฟล์มีชื่อพา ธ อยู่ข้างหน้า แต่ส่วนที่เหลือก็ดูคล้ายกันเช่นกัน

ดังตัวอย่างที่แสดงให้เห็นแม้วัตถุไดเร็กทอรีจะใช้RecursiveIteratorอินเทอร์เฟซอยู่แล้วแต่ก็ยังไม่เพียงพอที่จะทำการforeachสำรวจโครงสร้างไดเร็กทอรีทั้งหมด นี่คือที่RecursiveIteratorIteratorมาของการดำเนินการ ตัวอย่างที่ 4แสดงให้เห็นว่า:

$files = new RecursiveIteratorIterator($dir);

echo "[$path]\n";
foreach ($files as $file) {
    echo " ├ $file\n";
}

การใช้RecursiveIteratorIteratorแทน$dirอ็อบเจ็กต์ก่อนหน้านี้จะทำให้foreachสำรวจไฟล์และไดเร็กทอรีทั้งหมดในลักษณะวนซ้ำ จากนั้นจะแสดงรายการไฟล์ทั้งหมดตามที่ระบุประเภทของการวนซ้ำของวัตถุในขณะนี้:

[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

สิ่งนี้ควรแสดงให้เห็นถึงความแตกต่างระหว่างการลัดเลาะบนพื้นราบกับต้นไม้ RecursiveIteratorIteratorสามารถสำรวจโครงสร้างเหมือนใด ๆ ที่เป็นรายการขององค์ประกอบ เนื่องจากมีข้อมูลเพิ่มเติม (เช่นระดับการวนซ้ำที่เกิดขึ้นในปัจจุบัน) จึงเป็นไปได้ที่จะเข้าถึงออบเจ็กต์ตัววนซ้ำในขณะที่วนซ้ำอยู่และตัวอย่างเช่นเยื้องผลลัพธ์:

echo "[$path]\n";
foreach ($files as $file) {
    $indent = str_repeat('   ', $files->getDepth());
    echo $indent, " ├ $file\n";
}

และผลลัพธ์ของตัวอย่างที่ 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

แน่ใจว่านี้ไม่ชนะการประกวดความงาม แต่มันแสดงให้เห็นว่ามีการ iterator recursive มีข้อมูลมากขึ้นกว่าเพียงแค่การสั่งการเชิงเส้นของคีย์และคุ้มค่า แม้จะforeachสามารถแสดงความเป็นเชิงเส้นแบบนี้ได้เท่านั้น แต่การเข้าถึงตัววนซ้ำจะช่วยให้ได้รับข้อมูลเพิ่มเติม

เช่นเดียวกับข้อมูลเมตานอกจากนี้ยังมีวิธีต่างๆที่เป็นไปได้ในการสำรวจต้นไม้และสั่งเอาต์พุต นี่คือโหมดของRecursiveIteratorIteratorและสามารถตั้งค่าได้ด้วยตัวสร้าง

ตัวอย่างต่อไปจะบอกRecursiveDirectoryIteratorให้ลบรายการจุด ( .และ..) เนื่องจากเราไม่ต้องการ แต่โหมดการเรียกซ้ำจะถูกเปลี่ยนเพื่อรับองค์ประกอบหลัก (ไดเรกทอรีย่อย) ก่อน ( SELF_FIRST) ก่อนลูก (ไฟล์และไดเร็กทอรีย่อยในไดเร็กทอรีย่อย):

$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";
}

ตอนนี้เอาต์พุตจะแสดงรายการไดเร็กทอรีย่อยที่แสดงรายการอย่างถูกต้องหากคุณเปรียบเทียบกับเอาต์พุตก่อนหน้าซึ่งไม่มีอยู่:

[tree]
 ├ tree\dirA
    ├ tree\dirA\dirB
       ├ tree\dirA\dirB\fileD
    ├ tree\dirA\fileB
    ├ tree\dirA\fileC
 ├ tree\fileA

ดังนั้นโหมดการเรียกซ้ำจึงควบคุมสิ่งที่และเมื่อใดที่จะส่งคืนวงเล็บหรือใบไม้ในทรีสำหรับตัวอย่างไดเร็กทอรี:

  • LEAVES_ONLY (ค่าเริ่มต้น): แสดงเฉพาะไฟล์ไม่มีไดเร็กทอรี
  • SELF_FIRST (ด้านบน): แสดงรายการไดเร็กทอรีและไฟล์ในนั้น
  • CHILD_FIRST (ไม่มีตัวอย่าง): แสดงรายการไฟล์ในไดเร็กทอรีย่อยก่อนจากนั้นไดเร็กทอรี

ผลลัพธ์ของตัวอย่างที่ 5พร้อมโหมดอื่น ๆ อีกสองโหมด:

  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

เมื่อคุณเปรียบเทียบกับการส่งผ่านมาตรฐานสิ่งเหล่านี้ทั้งหมดจะไม่มีให้ การวนซ้ำแบบวนซ้ำจึงซับซ้อนกว่าเล็กน้อยเมื่อคุณต้องพันศีรษะของคุณรอบ ๆ อย่างไรก็ตามมันใช้งานง่ายเพราะมันทำงานเหมือนกับตัววนซ้ำคุณใส่ลงใน a foreachและทำเสร็จแล้ว

ฉันคิดว่านี่เป็นตัวอย่างเพียงพอสำหรับคำตอบเดียว คุณสามารถค้นหาซอร์สโค้ดแบบเต็มรวมทั้งตัวอย่างในการแสดงต้นไม้ ascii ที่ดูดีได้ในส่วนสำคัญนี้: https://gist.github.com/3599532

ทำด้วยตัวเอง:สร้างRecursiveTreeIteratorงานทีละบรรทัด

ตัวอย่างที่ 5แสดงให้เห็นว่ามีข้อมูลเมตาเกี่ยวกับสถานะของตัววนซ้ำที่พร้อมใช้งาน อย่างไรก็ตามสิ่งนี้แสดงให้เห็นอย่างมีจุดมุ่งหมายภายในการforeachทำซ้ำ ในชีวิตจริงสิ่งนี้อยู่ภายในRecursiveIterator.

ตัวอย่างที่ดีกว่าคือRecursiveTreeIteratorมันดูแลการเยื้องคำนำหน้าและอื่น ๆ ดูส่วนของรหัสต่อไปนี้:

$dir   = new RecursiveDirectoryIterator($path, RecursiveDirectoryIterator::SKIP_DOTS);
$lines = new RecursiveTreeIterator($dir);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));

RecursiveTreeIteratorมีจุดมุ่งหมายเพื่อการทำงานสายโดยสายการส่งออกจะสวยตรงไปตรงมาที่มีปัญหาเล็ก ๆ น้อย ๆ ที่หนึ่ง:

[tree]
 ├ tree\dirA
 │ ├ tree\dirA\dirB
 │ │ └ tree\dirA\dirB\fileD
 │ ├ tree\dirA\fileB
 │ └ tree\dirA\fileC
 └ tree\fileA

เมื่อใช้ร่วมกับRecursiveDirectoryIteratorมันจะแสดงชื่อพา ธ ทั้งหมดไม่ใช่แค่ชื่อไฟล์ ที่เหลือก็ดูดี เนื่องจากชื่อไฟล์ถูกสร้างขึ้นโดยSplFileInfo. สิ่งเหล่านี้ควรแสดงเป็นชื่อฐานแทน ผลลัพธ์ที่ต้องการมีดังต่อไปนี้:

/// Solved ///

[tree]
 ├ dirA
 │ ├ dirB
 │ │ └ fileD
 │ ├ fileB
 │ └ fileC
 └ fileA

สร้างคลาสมัณฑนากรที่สามารถใช้RecursiveTreeIteratorแทนRecursiveDirectoryIterator. ควรระบุชื่อฐานของกระแสSplFileInfoแทนชื่อพา ธ ส่วนรหัสสุดท้ายอาจมีลักษณะดังนี้:

$lines = new RecursiveTreeIterator(
    new DiyRecursiveDecorator($dir)
);
$unicodeTreePrefix($lines);
echo "[$path]\n", implode("\n", iterator_to_array($lines));

ชิ้นส่วนเหล่านี้รวมถึง$unicodeTreePrefixส่วนสำคัญในภาคผนวก: ทำด้วยตัวเอง: สร้างRecursiveTreeIteratorงานทีละบรรทัด .


ไม่ตอบคำถามที่วางไว้มีข้อผิดพลาดที่เป็นข้อเท็จจริงและพลาดประเด็นหลักเมื่อคุณเปลี่ยนไปปรับแต่งการทำซ้ำ สรุปแล้วดูเหมือนเป็นความพยายามที่ไม่ดีในการได้รับรางวัลในหัวข้อที่คุณไม่รู้มากนักหรือถ้าคุณทำจะไม่สามารถกลั่นกรองเป็นคำตอบสำหรับคำถามที่ตั้งขึ้นได้
โรงเกลือ

2
ซึ่งไม่ตอบคำถาม "ทำไม" ของฉันคุณก็แค่พูดมากขึ้นโดยไม่ต้องพูดอะไรมาก บางทีคุณอาจเริ่มต้นด้วยข้อผิดพลาดที่เกิดขึ้นจริง? ชี้ไปที่มันอย่าเก็บเป็นความลับ
hakre

ประโยคแรกไม่ถูกต้อง: "RecursiveIteratorIterator เป็น IteratorIterator ที่รองรับ ... " ซึ่งไม่เป็นความจริง
โรงเกลือ

1
@salathe: ถังสำหรับข้อเสนอแนะของคุณ ฉันแก้ไขคำตอบเพื่อแก้ไข ประโยคแรกผิดและไม่สมบูรณ์ ฉันยังคงทิ้งรายละเอียดการใช้งานที่เป็นรูปธรรมRecursiveIteratorIteratorเนื่องจากสิ่งนี้เหมือนกันกับประเภทอื่น ๆ แต่ฉันได้ให้ข้อมูลทางเทคนิคเกี่ยวกับวิธีการทำงานจริง ตัวอย่างที่ฉันคิดว่าแสดงให้เห็นถึงความแตกต่าง: ประเภทของการวนซ้ำเป็นความแตกต่างหลักระหว่างสองสิ่งนี้ ไม่รู้ว่าถ้าคุณซื้อประเภทของการทำซ้ำคุณจะให้เหรียญแตกต่างกันเล็กน้อย แต่ IMHO ไม่ง่ายเลยที่จะมีการทำซ้ำประเภทความหมาย
hakre

1
ส่วนแรกได้รับการปรับปรุงให้ดีขึ้นบ้าง แต่เมื่อคุณเริ่มดำเนินการตามตัวอย่างมันก็ยังเปลี่ยนเป็นความไม่ถูกต้องตามข้อเท็จจริง หากคุณตัดคำตอบที่กฎแนวนอนจะดีขึ้นมาก
ละหมาด

32

อะไรคือความแตกต่างของIteratorIteratorและRecursiveIteratorIterator?

เพื่อให้เข้าใจถึงความแตกต่างระหว่างตัวทำซ้ำสองตัวนี้ก่อนอื่นเราต้องเข้าใจหลักการตั้งชื่อที่ใช้และความหมายของตัววนซ้ำ

ตัวทำซ้ำแบบวนซ้ำและไม่วนซ้ำ

PHP มีตัววนซ้ำที่ไม่ "เรียกซ้ำ" เช่นArrayIteratorและFilesystemIterator. นอกจากนี้ยังมีตัวทำซ้ำแบบ "เรียกซ้ำ" เช่นRecursiveArrayIteratorและRecursiveDirectoryIterator. อย่างหลังมีวิธีการที่ทำให้สามารถเจาะลงไปได้ แต่เดิมทำไม่ได้

เมื่ออินสแตนซ์ของตัววนซ้ำเหล่านี้ถูกวนซ้ำด้วยตัวเองแม้กระทั่งตัววนซ้ำค่าจะมาจากระดับ "บนสุด" เท่านั้นแม้ว่าจะวนลูปเหนืออาร์เรย์ที่ซ้อนกันหรือไดเร็กทอรีที่มีไดเร็กทอรีย่อยก็ตาม

iterators recursive ใช้พฤติกรรม recursive (ผ่านhasChildren(), getChildren()) แต่ไม่ได้ใช้ประโยชน์จากมัน

มันอาจจะดีกว่าถ้าคิดว่าตัววนซ้ำแบบวนซ้ำเป็นตัวทำซ้ำแบบ "เรียกซ้ำ" พวกมันมีความสามารถในการวนซ้ำแบบวนซ้ำ แต่เพียงแค่วนซ้ำในอินสแตนซ์ของคลาสใดคลาสหนึ่งเหล่านี้จะไม่ทำเช่นนั้น หากต้องการใช้ประโยชน์จากพฤติกรรมที่เกิดซ้ำให้อ่านต่อไป

RecursiveIteratorIterator

นี่คือที่RecursiveIteratorIteratorมาในการเล่น มันมีความรู้เกี่ยวกับวิธีการเรียกตัววนซ้ำแบบ "เรียกซ้ำ" ในลักษณะที่จะเจาะลึกลงไปในโครงสร้างในแบบปกติแบนลูป ทำให้พฤติกรรมที่เกิดขึ้นซ้ำ ๆ เป็นการกระทำ โดยพื้นฐานแล้วเป็นการทำงานในการก้าวข้ามค่าแต่ละค่าในตัววนซ้ำโดยมองหาว่ามี "เด็ก" ที่จะฟื้นขึ้นมาอีกหรือไม่และก้าวเข้าและออกจากคอลเลกชันของเด็กเหล่านั้น คุณติดอินสแตนซ์RecursiveIteratorIteratorไว้ใน foreach แล้วมันก็ดำดิ่งลงไปในโครงสร้างเพื่อที่คุณจะได้ไม่ต้องทำ

ถ้าRecursiveIteratorIteratorไม่ได้ใช้คุณจะต้องเขียนลูป recursive ของคุณเองเพื่อใช้ประโยชน์จากพฤติกรรมการเวียนเกิดการตรวจสอบกับ "recursible" iterator เป็นและการใช้hasChildren()getChildren()

เพื่อให้เป็นช่วงสั้น ๆภาพรวมของRecursiveIteratorIteratorวิธีการที่แตกต่างจากIteratorIterator? โดยพื้นฐานแล้วคุณกำลังถามคำถามประเภทเดียวกันว่าลูกแมวกับต้นไม้ต่างกันอย่างไร? เพียงเพราะทั้งสองอย่างปรากฏในสารานุกรมเดียวกัน (หรือคู่มือสำหรับผู้วนซ้ำ) ไม่ได้หมายความว่าคุณควรสับสนระหว่างทั้งสอง

IteratorIterator

หน้าที่ของวัตถุIteratorIteratorคือการใช้Traversableวัตถุใด ๆและรวมเข้าด้วยกันเพื่อให้เป็นไปตามIteratorอินเทอร์เฟซ การใช้สำหรับสิ่งนี้คือเพื่อให้สามารถใช้ลักษณะการทำงานเฉพาะของตัววนซ้ำกับวัตถุที่ไม่ใช่ตัววนซ้ำ

เพื่อให้เป็นตัวอย่างที่ใช้ได้จริงDatePeriodชั้นเรียนนี้เป็นTraversableแต่ไม่ใช่Iteratorไฟล์. ด้วยเหตุนี้เราจึงสามารถวนซ้ำค่าของมันด้วยforeach()แต่ไม่สามารถทำสิ่งอื่น ๆ ที่เราทำโดยปกติด้วยตัววนซ้ำเช่นการกรอง

TASK : วนรอบวันจันทร์วันพุธและวันศุกร์ของสี่สัปดาห์ถัดไป

ใช่นี่เป็นเรื่องเล็กน้อยโดยการforeachข้ามDatePeriodและใช้if()ภายในลูป แต่นั่นไม่ใช่ประเด็นของตัวอย่างนี้!

$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) { … }

ตัวอย่างข้อมูลข้างต้นใช้ไม่ได้เนื่องจากCallbackFilterIteratorคาดว่าจะมีอินสแตนซ์ของคลาสที่ใช้Iteratorอินเทอร์เฟซซึ่งDatePeriodไม่ได้ แต่เนื่องจากมันเป็นเราสามารถตอบสนองความต้องการว่าด้วยการใช้TraversableIteratorIterator

$period = new IteratorIterator(new DatePeriod(…));

ในขณะที่คุณสามารถดูนี้มีอะไรใด ๆ จะทำอย่างไรกับการวนไปเรียนหรือการเรียกซ้ำ iterator และอยู่ในนั้นความแตกต่างระหว่างและIteratorIteratorRecursiveIteratorIterator

สรุป

RecursiveIteraratorIteratorมีไว้สำหรับการวนซ้ำบนตัววนซ้ำRecursiveIterator("เรียกซ้ำ") โดยใช้ประโยชน์จากพฤติกรรมการเรียกซ้ำที่พร้อมใช้งาน

IteratorIteratorมีไว้สำหรับการใช้IteratorพฤติกรรมกับTraversableอ็อบเจ็กต์ที่ไม่ใช่ตัววนซ้ำ


ไม่ใช่IteratorIteratorแค่ประเภทมาตรฐานของการส่งผ่านคำสั่งเชิงเส้นสำหรับTraversableวัตถุใช่หรือไม่? สิ่งที่สามารถใช้งานได้โดยไม่ต้องใช้มันforeach? และยิ่งไม่ได้เป็นRecursiveIterator เสมอTraversableและดังนั้นจึงไม่เพียงIteratorIteratorแต่ยังRecursiveIteratorIteratorเสมอ"สำหรับการใช้Iteratorพฤติกรรมที่ไม่ iterator, ทะลุวัตถุ" ? (ตอนนี้ฉันจะบอกว่าforeachใช้ประเภทของการวนซ้ำผ่านอ็อบเจ็กต์ตัววนซ้ำบนอ็อบเจ็กต์คอนเทนเนอร์ที่ใช้อินเทอร์เฟซประเภทตัววนซ้ำดังนั้นนี่คือตัววนซ้ำคอนเทนเนอร์ - อ็อบเจกต์เสมอTraversable)
hakre

ตามคำตอบของฉันIteratorIteratorเป็นคลาสที่เกี่ยวกับการห่อTraversableวัตถุในIteratorไฟล์. ไม่มีอะไรมาก . ดูเหมือนคุณจะใช้คำนี้โดยทั่วไปมากกว่า
ละหมาด

ดูเหมือนคำตอบที่ให้ข้อมูล คำถามหนึ่งคือ RecursiveIteratorIterator จะไม่ห่อวัตถุด้วยเพื่อที่พวกเขาจะสามารถเข้าถึงพฤติกรรม Iterator ได้หรือไม่? ข้อแตกต่างเพียงอย่างเดียวระหว่างทั้งสองคือ RecursiveIteratorIterator สามารถเจาะลึกลงไปได้ในขณะที่ IteratorIterator ไม่สามารถ?
Mike Purcell

@salathe คุณรู้หรือไม่ว่าเหตุใดตัววนซ้ำแบบวนซ้ำ (RecursiveDirectoryIterator) จึงไม่ใช้พฤติกรรม hasChildren (), getChildren ()?
anru

8
+1 สำหรับการพูดว่า "เรียกซ้ำ" ชื่อที่นำฉันหลงผิดเป็นเวลานานเพราะRecursiveในหมายถึงพฤติกรรมในขณะที่ชื่อที่เหมาะสมมากขึ้นจะได้รับหนึ่งซึ่งอธิบายความสามารถเช่นRecursiveIterator RecursibleIterator
แพะ

0

เมื่อใช้กับiterator_to_array(), RecursiveIteratorIteratorซ้ำจะเดินอาร์เรย์เพื่อหาค่าทั้งหมด หมายความว่ามันจะแบนอาร์เรย์เดิม

IteratorIterator จะคงโครงสร้างลำดับชั้นเดิมไว้

ตัวอย่างนี้จะแสดงให้คุณเห็นความแตกต่างอย่างชัดเจน:

$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))เทียบเท่ากับnew ArrayIterator($array)นั่นคือด้านนอกIteratorIteratorไม่ได้ทำอะไรเลย ยิ่งไปกว่านั้นการแบนเอาต์พุตไม่ได้เกี่ยวข้องอะไรเลยiterator_to_array- เพียงแค่แปลงตัววนซ้ำเป็นอาร์เรย์ การแบนเป็นคุณสมบัติของวิธีที่RecursiveArrayIteratorเดินวนซ้ำภายใน
คำถาม Quolonel

0

RecursiveDirectoryIterator จะแสดงชื่อพา ธ ทั้งหมดไม่ใช่แค่ชื่อไฟล์ ที่เหลือก็ดูดี เนื่องจากชื่อไฟล์ถูกสร้างขึ้นโดย SplFileInfo สิ่งเหล่านี้ควรแสดงเป็นชื่อฐานแทน ผลลัพธ์ที่ต้องการมีดังต่อไปนี้:

$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";
    }
}

เอาต์พุต:

tree
 ├ dirA
 │ ├ dirB
 │ │ └ fileD
 │ ├ fileB
 │ └ fileC
 └ fileA
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.