อีกวิธีหนึ่งที่ง่ายกว่าในการแปลงโครงสร้างแบบแบนใน$tree
ลำดับชั้น ต้องใช้อาร์เรย์ชั่วคราวเพียงชุดเดียวเพื่อแสดง:
// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)
{
$flat[$name]['name'] = $name; # self
if (NULL === $parent)
{
# no parent, is root element, assign it to $tree
$tree = &$flat[$name];
}
else
{
# has parent, add self as child
$flat[$parent]['children'][] = &$flat[$name];
}
}
unset($flat);
นั่นคือทั้งหมดที่ทำให้ลำดับชั้นเป็นอาร์เรย์หลายมิติ:
Array
(
[children] => Array
(
[0] => Array
(
[children] => Array
(
[0] => Array
(
[name] => H
)
[1] => Array
(
[name] => F
)
)
[name] => G
)
[1] => Array
(
[name] => E
[children] => Array
(
[0] => Array
(
[name] => A
)
[1] => Array
(
[children] => Array
(
[0] => Array
(
[name] => B
)
)
[name] => C
)
)
)
)
[name] => D
)
ผลลัพธ์จะไม่สำคัญน้อยกว่าหากคุณต้องการหลีกเลี่ยงการเรียกซ้ำ (อาจเป็นภาระกับโครงสร้างขนาดใหญ่)
ฉันต้องการแก้ปัญหา "ภาวะที่กลืนไม่เข้าคายไม่ออก" ของ UL / LI เสมอสำหรับการส่งออกอาร์เรย์ ประเด็นที่กลืนไม่เข้าคายไม่ออกคือแต่ละรายการไม่ทราบว่าเด็ก ๆ จะติดตามหรือไม่หรือต้องปิดองค์ประกอบก่อนหน้านี้กี่รายการ ในคำตอบอื่นฉันได้แก้ไขแล้วโดยใช้RecursiveIteratorIterator
และค้นหาgetDepth()
และข้อมูลเมตาอื่น ๆ ที่ฉันเขียนขึ้นเองIterator
: นำโมเดลชุดที่ซ้อนกันไปไว้ในทรี<ul>
ย่อย“ ปิด” แต่ซ่อนไว้ ที่คำตอบแสดงให้เห็นเช่นกันว่าด้วยตัวทำซ้ำคุณค่อนข้างยืดหยุ่น
อย่างไรก็ตามนี่เป็นรายการที่จัดเรียงไว้ล่วงหน้าดังนั้นจึงไม่เหมาะกับตัวอย่างของคุณ นอกจากนี้ฉันต้องการแก้ปัญหานี้เสมอสำหรับโครงสร้างแบบต้นไม้มาตรฐานและ HTML <ul>
และ<li>
องค์ประกอบ
แนวคิดพื้นฐานที่ฉันคิดขึ้นมีดังต่อไปนี้:
TreeNode
- บทคัดย่อแต่ละองค์ประกอบเป็นTreeNode
ประเภทง่ายๆที่สามารถให้คุณค่า (เช่นName
) และมีลูกหรือไม่
TreeNodesIterator
- เป็นRecursiveIterator
ที่สามารถย้ำกว่าชุด (อาร์เรย์) TreeNodes
เหล่านี้ มันค่อนข้างง่ายเพราะTreeNode
ประเภทรู้อยู่แล้วว่ามีลูกหรือไม่และคนไหน
RecursiveListIterator
- A RecursiveIteratorIterator
ที่มีเหตุการณ์ทั้งหมดที่จำเป็นเมื่อมันวนซ้ำซ้ำไปซ้ำมาทุกประเภทRecursiveIterator
:
beginIteration
/ endIteration
- จุดเริ่มต้นและจุดสิ้นสุดของรายการหลัก
beginElement
/ endElement
- จุดเริ่มต้นและจุดสิ้นสุดของแต่ละองค์ประกอบ
beginChildren
/ endChildren
- จุดเริ่มต้นและจุดสิ้นสุดของรายชื่อเด็กแต่ละคน สิ่งนี้RecursiveListIterator
จัดเตรียมเฉพาะเหตุการณ์เหล่านี้ในรูปแบบของการเรียกใช้ฟังก์ชัน รายการเด็กตามปกติสำหรับ<ul><li>
รายการจะเปิดและปิดภายใน<li>
องค์ประกอบหลัก ดังนั้นendElement
เหตุการณ์จึงถูกยิงหลังจากที่ตามendChildren
เหตุการณ์สิ่งนี้สามารถเปลี่ยนแปลงหรือกำหนดค่าได้เพื่อขยายการใช้คลาสนี้ เหตุการณ์จะถูกแจกจ่ายตามการเรียกใช้ฟังก์ชันไปยังวัตถุมัณฑนากรเพื่อแยกสิ่งต่างๆออกจากกัน
ListDecorator
- คลาส "มัณฑนากร" ที่เป็นเพียงผู้รับเหตุการณ์ของ RecursiveListIterator
ชั้นที่เป็นเพียงตัวรับสัญญาณของเหตุการณ์ที่เกิดขึ้นที่
ฉันเริ่มต้นด้วยตรรกะผลลัพธ์หลัก ดำเนินการตามลำดับชั้นในขณะนี้$tree
อาร์เรย์รหัสสุดท้ายมีลักษณะดังนี้:
$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
ก่อนอื่นเรามาดูสิ่งListDecorator
ที่ห่อ<ul>
และ<li>
องค์ประกอบและและกำลังตัดสินใจว่าจะส่งออกโครงสร้างรายการอย่างไร:
class ListDecorator
{
private $iterator;
public function __construct(RecursiveListIterator $iterator)
{
$this->iterator = $iterator;
}
public function inset($add = 0)
{
return str_repeat(' ', $this->iterator->getDepth()*2+$add);
}
ตัวสร้างรับตัววนซ้ำรายการที่กำลังทำงานอยู่ inset
เป็นเพียงฟังก์ชันตัวช่วยสำหรับการเยื้องเอาต์พุตที่ดี ส่วนที่เหลือเป็นเพียงฟังก์ชันเอาต์พุตสำหรับแต่ละเหตุการณ์:
public function beginElement()
{
printf("%s<li>\n", $this->inset());
}
public function endElement()
{
printf("%s</li>\n", $this->inset());
}
public function beginChildren()
{
printf("%s<ul>\n", $this->inset(-1));
}
public function endChildren()
{
printf("%s</ul>\n", $this->inset(-1));
}
public function beginIteration()
{
printf("%s<ul>\n", $this->inset());
}
public function endIteration()
{
printf("%s</ul>\n", $this->inset());
}
}
เมื่อคำนึงถึงฟังก์ชั่นเอาต์พุตเหล่านี้นี่คือการสรุป / วนรอบเอาต์พุตหลักอีกครั้งฉันจะทำตามขั้นตอน:
$root = new TreeNode($tree);
สร้างรูทTreeNode
ที่จะใช้เพื่อเริ่มการทำซ้ำเมื่อ:
$it = new TreeNodesIterator(array($root));
นี่TreeNodesIterator
คือสิ่งRecursiveIterator
ที่เปิดใช้งานการวนซ้ำแบบวนซ้ำบน$root
โหนดเดียว มันถูกส่งผ่านเป็นอาร์เรย์เนื่องจากคลาสนั้นต้องการบางสิ่งที่จะวนซ้ำและอนุญาตให้ใช้ซ้ำกับชุดเด็กซึ่งเป็นอาร์เรย์ของTreeNode
องค์ประกอบด้วย
$rit = new RecursiveListIterator($it);
นี่RecursiveListIterator
คือสิ่งRecursiveIteratorIterator
ที่จัดให้มีเหตุการณ์ดังกล่าว ในการใช้ประโยชน์ListDecorator
จำเป็นต้องระบุ (ชั้นเรียนด้านบน) และกำหนดด้วยaddDecorator
:
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
จากนั้นทุกอย่างจะถูกตั้งค่าให้อยู่foreach
เหนือและส่งออกแต่ละโหนด:
foreach($rit as $item)
{
$inset = $decor->inset(1);
printf("%s%s\n", $inset, $item->getName());
}
ดังตัวอย่างนี้แสดงตรรกะเอาต์พุตทั้งหมดถูกห่อหุ้มในListDecorator
คลาสและซิงเกิ้ลนี้foreach
นี้ การส่งผ่านแบบวนซ้ำทั้งหมดได้รับการห่อหุ้มไว้อย่างสมบูรณ์ในตัววนซ้ำ SPL ซึ่งจัดให้มีขั้นตอนแบบเรียงซ้อนซึ่งหมายความว่าภายในจะไม่มีการเรียกฟังก์ชันการเรียกซ้ำภายใน
ตามเหตุการณ์ListDecorator
ช่วยให้คุณแก้ไขเอาต์พุตโดยเฉพาะและจัดเตรียมรายการหลายประเภทสำหรับโครงสร้างข้อมูลเดียวกัน เป็นไปได้ที่จะเปลี่ยนอินพุตเนื่องจากข้อมูลอาร์เรย์ถูกห่อหุ้มไว้TreeNode
มันเป็นไปได้ที่จะเปลี่ยนการรับสัญญาณเป็นข้อมูลอาร์เรย์ได้รับการห่อหุ้มเข้าไป
ตัวอย่างโค้ดแบบเต็ม:
<?php
namespace My;
$tree = array('H' => 'G', 'F' => 'G', 'G' => 'D', 'E' => 'D', 'A' => 'E', 'B' => 'C', 'C' => 'E', 'D' => null);
// add children to parents
$flat = array(); # temporary array
foreach ($tree as $name => $parent)
{
$flat[$name]['name'] = $name; # self
if (NULL === $parent)
{
# no parent, is root element, assign it to $tree
$tree = &$flat[$name];
}
else
{
# has parent, add self as child
$flat[$parent]['children'][] = &$flat[$name];
}
}
unset($flat);
class TreeNode
{
protected $data;
public function __construct(array $element)
{
if (!isset($element['name']))
throw new InvalidArgumentException('Element has no name.');
if (isset($element['children']) && !is_array($element['children']))
throw new InvalidArgumentException('Element has invalid children.');
$this->data = $element;
}
public function getName()
{
return $this->data['name'];
}
public function hasChildren()
{
return isset($this->data['children']) && count($this->data['children']);
}
/**
* @return array of child TreeNode elements
*/
public function getChildren()
{
$children = $this->hasChildren() ? $this->data['children'] : array();
$class = get_called_class();
foreach($children as &$element)
{
$element = new $class($element);
}
unset($element);
return $children;
}
}
class TreeNodesIterator implements \RecursiveIterator
{
private $nodes;
public function __construct(array $nodes)
{
$this->nodes = new \ArrayIterator($nodes);
}
public function getInnerIterator()
{
return $this->nodes;
}
public function getChildren()
{
return new TreeNodesIterator($this->nodes->current()->getChildren());
}
public function hasChildren()
{
return $this->nodes->current()->hasChildren();
}
public function rewind()
{
$this->nodes->rewind();
}
public function valid()
{
return $this->nodes->valid();
}
public function current()
{
return $this->nodes->current();
}
public function key()
{
return $this->nodes->key();
}
public function next()
{
return $this->nodes->next();
}
}
class RecursiveListIterator extends \RecursiveIteratorIterator
{
private $elements;
/**
* @var ListDecorator
*/
private $decorator;
public function addDecorator(ListDecorator $decorator)
{
$this->decorator = $decorator;
}
public function __construct($iterator, $mode = \RecursiveIteratorIterator::SELF_FIRST, $flags = 0)
{
parent::__construct($iterator, $mode, $flags);
}
private function event($name)
{
// event debug code: printf("--- %'.-20s --- (Depth: %d, Element: %d)\n", $name, $this->getDepth(), @$this->elements[$this->getDepth()]);
$callback = array($this->decorator, $name);
is_callable($callback) && call_user_func($callback);
}
public function beginElement()
{
$this->event('beginElement');
}
public function beginChildren()
{
$this->event('beginChildren');
}
public function endChildren()
{
$this->testEndElement();
$this->event('endChildren');
}
private function testEndElement($depthOffset = 0)
{
$depth = $this->getDepth() + $depthOffset;
isset($this->elements[$depth]) || $this->elements[$depth] = 0;
$this->elements[$depth] && $this->event('endElement');
}
public function nextElement()
{
$this->testEndElement();
$this->event('{nextElement}');
$this->event('beginElement');
$this->elements[$this->getDepth()] = 1;
}
public function beginIteration()
{
$this->event('beginIteration');
}
public function endIteration()
{
$this->testEndElement();
$this->event('endIteration');
}
}
class ListDecorator
{
private $iterator;
public function __construct(RecursiveListIterator $iterator)
{
$this->iterator = $iterator;
}
public function inset($add = 0)
{
return str_repeat(' ', $this->iterator->getDepth()*2+$add);
}
public function beginElement()
{
printf("%s<li>\n", $this->inset(1));
}
public function endElement()
{
printf("%s</li>\n", $this->inset(1));
}
public function beginChildren()
{
printf("%s<ul>\n", $this->inset());
}
public function endChildren()
{
printf("%s</ul>\n", $this->inset());
}
public function beginIteration()
{
printf("%s<ul>\n", $this->inset());
}
public function endIteration()
{
printf("%s</ul>\n", $this->inset());
}
}
$root = new TreeNode($tree);
$it = new TreeNodesIterator(array($root));
$rit = new RecursiveListIterator($it);
$decor = new ListDecorator($rit);
$rit->addDecorator($decor);
foreach($rit as $item)
{
$inset = $decor->inset(2);
printf("%s%s\n", $inset, $item->getName());
}
Outpupt:
<ul>
<li>
D
<ul>
<li>
G
<ul>
<li>
H
</li>
<li>
F
</li>
</ul>
</li>
<li>
E
<ul>
</li>
<li>
A
</li>
<li>
C
<ul>
<li>
B
</li>
</ul>
</li>
</ul>
</li>
</ul>
</li>
</ul>
การสาธิต (ตัวแปร PHP 5.2)
ตัวแปรที่เป็นไปได้จะเป็นตัววนซ้ำที่วนซ้ำRecursiveIterator
และให้การวนซ้ำสำหรับเหตุการณ์ทั้งหมดที่สามารถเกิดขึ้นได้ สวิตช์ / เคสภายใน foreach loop สามารถจัดการกับเหตุการณ์ได้
ที่เกี่ยวข้อง: