Chcę napisać funkcję, która pobiera tablicę stron / kategorii (z płaskiego wyniku bazy danych) i generuje tablicę zagnieżdżonych elementów strony / kategorii na podstawie identyfikatorów nadrzędnych. Chciałbym to zrobić rekurencyjnie, aby można było wykonać zagnieżdżenie na dowolnym poziomie.
Na przykład: Pobieram wszystkie strony w jednym zapytaniu i tak wygląda tabela bazy danych
+-------+---------------+---------------------------+
| id | parent_id | title |
+-------+---------------+---------------------------+
| 1 | 0 | Parent Page |
| 2 | 1 | Sub Page |
| 3 | 2 | Sub Sub Page |
| 4 | 0 | Another Parent Page |
+-------+---------------+---------------------------+
A to jest tablica, którą chciałbym ostatecznie przetworzyć w moich plikach widoku:
Array
(
[0] => Array
(
[id] => 1
[parent_id] => 0
[title] => Parent Page
[children] => Array
(
[0] => Array
(
[id] => 2
[parent_id] => 1
[title] => Sub Page
[children] => Array
(
[0] => Array
(
[id] => 3
[parent_id] => 1
[title] => Sub Sub Page
)
)
)
)
)
[1] => Array
(
[id] => 4
[parent_id] => 0
[title] => Another Parent Page
)
)
Sprawdziłem i wypróbowałem prawie każde rozwiązanie, które napotkałem (jest ich dużo tutaj w Stack Overflow, ale nie udało mi się uzyskać czegoś wystarczająco ogólnego, który będzie działał zarówno dla stron, jak i kategorii.
Oto najbliższe, jakie uzyskałem, ale nie działa, ponieważ przydzielam dzieci rodzicowi pierwszego poziomu.
function page_walk($array, $parent_id = FALSE)
{
$organized_pages = array();
$children = array();
foreach($array as $index => $page)
{
if ( $page['parent_id'] == 0) // No, just spit it out and you're done
{
$organized_pages[$index] = $page;
}
else // If it does,
{
$organized_pages[$parent_id]['children'][$page['id']] = $this->page_walk($page, $parent_id);
}
}
return $organized_pages;
}
function page_list($array)
{
$fakepages = array();
$fakepages[0] = array('id' => 1, 'parent_id' => 0, 'title' => 'Parent Page');
$fakepages[1] = array('id' => 2, 'parent_id' => 1, 'title' => 'Sub Page');
$fakepages[2] = array('id' => 3, 'parent_id' => 2, 'title' => 'Sub Sub Page');
$fakepages[3] = array('id' => 4, 'parent_id' => 3, 'title' => 'Another Parent Page');
$pages = $this->page_walk($fakepages, 0);
print_r($pages);
}
Odpowiedzi:
Bardzo proste, ogólne budowanie drzewka:
function buildTree(array $elements, $parentId = 0) { $branch = array(); foreach ($elements as $element) { if ($element['parent_id'] == $parentId) { $children = buildTree($elements, $element['id']); if ($children) { $element['children'] = $children; } $branch[] = $element; } } return $branch; } $tree = buildTree($rows);
Algorytm jest dość prosty:
0
/ nic /null
/ cokolwiek).parent_id
element elementu pasuje do bieżącego identyfikatora rodzica, który otrzymałeś w 1., element jest dzieckiem rodzica. Umieść to na swojej liście obecnych dzieci (tutaj$branch
:).children
element.Innymi słowy, jedno wykonanie tej funkcji zwraca listę elementów, które są dziećmi o podanym identyfikatorze rodzica. Wywołaj ją za pomocą
buildTree($myArray, 1)
, zwróci listę elementów, które mają identyfikator rodzica 1. Początkowo ta funkcja jest wywoływana z identyfikatorem rodzica równym 0, więc zwracane są elementy bez identyfikatora rodzica, które są węzłami głównymi. Funkcja wywołuje się rekurencyjnie, aby znaleźć dzieci dzieci.źródło
$elements
tablicę w dół. W przypadku małych tablic, które nie mają znaczenia, ale w przypadku dużych zestawów danych będziesz chciał usunąć z niego już dopasowany element przed przekazaniem go. Staje się to jednak trochę bałaganiarskie, więc zostawiłem to proste dla łatwiejszego zrozumienia. :)array
to wskazówka dotycząca typu$elements
, pierwszy argument to po prostuarray $elements
.$rows
jest tablicą wyników z bazy danych, podobną do tej z pytania.$children = buildTree(...)
część, a funkcja powinna być dość oczywista i prosta.Wiem, że to pytanie jest stare, ale miałem podobny problem - z wyjątkiem bardzo dużej ilości danych. Po pewnych zmaganiach udało mi się zbudować drzewo w jednym przejściu zestawu wyników - używając referencji. Ten kod nie jest ładny, ale działa i działa dość szybko. Jest nierekurencyjny - to znaczy, istnieje tylko jedno przejście przez zbiór wyników, a następnie jedno
array_filter
przejście na końcu:$dbh = new PDO(CONNECT_STRING, USERNAME, PASSWORD); $dbs = $dbh->query("SELECT n_id, n_parent_id from test_table order by n_parent_id, n_id"); $elems = array(); while(($row = $dbs->fetch(PDO::FETCH_ASSOC)) !== FALSE) { $row['children'] = array(); $vn = "row" . $row['n_id']; ${$vn} = $row; if(!is_null($row['n_parent_id'])) { $vp = "parent" . $row['n_parent_id']; if(isset($data[$row['n_parent_id']])) { ${$vp} = $data[$row['n_parent_id']]; } else { ${$vp} = array('n_id' => $row['n_parent_id'], 'n_parent_id' => null, 'children' => array()); $data[$row['n_parent_id']] = &${$vp}; } ${$vp}['children'][] = &${$vn}; $data[$row['n_parent_id']] = ${$vp}; } $data[$row['n_id']] = &${$vn}; } $dbs->closeCursor(); $result = array_filter($data, function($elem) { return is_null($elem['n_parent_id']); }); print_r($result);
Podczas wykonywania na tych danych:
mysql> select * from test_table; +------+-------------+ | n_id | n_parent_id | +------+-------------+ | 1 | NULL | | 2 | NULL | | 3 | 1 | | 4 | 1 | | 5 | 2 | | 6 | 2 | | 7 | 5 | | 8 | 5 | +------+-------------+
Ostatni
print_r
generuje ten wynik:Array ( [1] => Array ( [n_id] => 1 [n_parent_id] => [children] => Array ( [3] => Array ( [n_id] => 3 [n_parent_id] => 1 [children] => Array ( ) ) [4] => Array ( [n_id] => 4 [n_parent_id] => 1 [children] => Array ( ) ) ) ) [2] => Array ( [n_id] => 2 [n_parent_id] => [children] => Array ( [5] => Array ( [n_id] => 5 [n_parent_id] => 2 [children] => Array ( [7] => Array ( [n_id] => 7 [n_parent_id] => 5 [children] => Array ( ) ) [8] => Array ( [n_id] => 8 [n_parent_id] => 5 [children] => Array ( ) ) ) ) [6] => Array ( [n_id] => 6 [n_parent_id] => 2 [children] => Array ( ) ) ) ) )
Właśnie tego szukałem.
źródło
Czerpiąc inspirację z innych odpowiedzi tutaj, wymyśliłem własną wersję grupowania tablicy asocjacyjnych tablic rekurencyjnie (do dowolnej głębokości), używając listy funkcji niestandardowych w celu uzyskania kluczy grupujących na każdym poziomie .
Oto uproszczona wersja oryginalnego, bardziej złożonego wariantu (z większą liczbą parametrów do regulacji pokręteł). Zauważ, że wykorzystuje prostą iteracyjną funkcję
groupByFn
jako podprogram do wykonywania grupowania na poszczególnych poziomach./** * - Groups a (non-associative) array items recursively, essentially converting it into a nested * tree or JSON like structure. Inspiration taken from: https://stackoverflow.com/a/8587437/3679900 * OR * - Converts an (non-associative) array of items into a multi-dimensional array by using series * of callables $key_retrievers and recursion * * - This function is an extension to above 'groupByFn', which also groups array but only till 1 (depth) level * (whereas this one does it till any number of depth levels by using recursion) * - Check unit-tests to understand further * @param array $data Array[mixed] (non-associative) array of items that has to be grouped / converted to * multi-dimensional array * @param array $key_retrievers Array[Callable[[mixed], int|string]] * - A list of functions applied to item one-by-one, to determine which * (key) bucket an item goes into at different levels * OR * - A list of callables each of which takes an item or input array as input and returns an int * or string which is to be used as a (grouping) key for generating multi-dimensional array. * @return array A nested assoc-array / multi-dimensional array generated by 'grouping' items of * input $data array at different levels by application of $key_retrievers on them (one-by-one) */ public static function groupByFnRecursive( array $data, array $key_retrievers ): array { // in following expression we are checking for array-length = 0 (and not nullability) // why empty is better than count($arr) == 0 https://stackoverflow.com/a/2216159/3679900 if (empty($data)) { // edge-case: if the input $data array is empty, return it unmodified (no need to check for other args) return $data; // in following expression we are checking for array-length = 0 (and not nullability) // why empty is better than count($arr) == 0 https://stackoverflow.com/a/2216159/3679900 } elseif (empty($key_retrievers)) { // base-case of recursion: when all 'grouping' / 'nesting' into multi-dimensional array has been done, return $data; } else { // group the array by 1st key_retriever $grouped_data = self::groupByFn($data, $key_retrievers[0]); // remove 1st key_retriever from list array_shift($key_retrievers); // and then recurse into further levels // note that here we are able to use array_map (and need not use array_walk) because array_map can preserve // keys as told here: // https://www.php.net/manual/en/function.array-map.php#refsect1-function.array-map-returnvalues return array_map( static function (array $item) use ($key_retrievers): array { return self::groupByFnRecursive($item, $key_retrievers); }, $grouped_data ); } }
Zapoznaj się z sednem, aby uzyskać większy zbiór funkcji narzędziowych dla tablic wraz z testami jednostkowymi
źródło
Możliwe jest użycie php, aby pobrać wynik mysql do tablicy, a następnie go użyć.
$categoryArr = Array(); while($categoryRow = mysql_fetch_array($category_query_result)){ $categoryArr[] = array('parentid'=>$categoryRow['parent_id'], 'id'=>$categoryRow['id']); }
źródło