Rekursive Funktion zum Generieren eines mehrdimensionalen Arrays aus dem Datenbankergebnis

Lesezeit: 10 Minuten

Rekursive Funktion zum Generieren eines mehrdimensionalen Arrays aus dem Datenbankergebnis
David Hanhill

Ich möchte eine Funktion schreiben, die ein Array von Seiten/Kategorien (aus einem flachen Datenbankergebnis) nimmt und ein Array von verschachtelten Seiten-/Kategorieelementen basierend auf den übergeordneten IDs generiert. Ich möchte dies rekursiv tun, damit jede Verschachtelungsebene durchgeführt werden kann.

Zum Beispiel: Ich rufe alle Seiten in einer Abfrage ab, und so sieht die Datenbanktabelle aus

+-------+---------------+---------------------------+
|   id  |   parent_id   |           title           |
+-------+---------------+---------------------------+
|   1   |       0       |   Parent Page             |
|   2   |       1       |   Sub Page                |
|   3   |       2       |   Sub Sub Page            |
|   4   |       0       |   Another Parent Page     |
+-------+---------------+---------------------------+

Und dies ist das Array, das ich am Ende in meinen Ansichtsdateien verarbeiten möchte:

Array
(
    [0] => Array
        (
            [id] => 1
            [parent_id] => 0
            Recursive function to generate multidimensional array from database result => Parent Page
            [children] => Array
                        (
                            [0] => Array
                                (
                                    [id] => 2
                                    [parent_id] => 1
                                    Recursive function to generate multidimensional array from database result => Sub Page
                                    [children] => Array
                                                (
                                                    [0] => Array
                                                        (
                                                            [id] => 3
                                                            [parent_id] => 1
                                                            Recursive function to generate multidimensional array from database result => Sub Sub Page
                                                        )
                                                )
                                )
                        )
        )
    [1] => Array
        (
            [id] => 4
            [parent_id] => 0
            Recursive function to generate multidimensional array from database result => Another Parent Page
        )
)

Ich habe fast jede Lösung gesucht und ausprobiert, auf die ich gestoßen bin (es gibt viele davon hier bei Stack Overflow, aber ich hatte kein Glück, etwas generisches zu bekommen, das sowohl für Seiten als auch für Kategorien funktioniert.

Hier ist das nächste, was ich bekommen habe, aber es funktioniert nicht, weil ich die Kinder dem übergeordneten Element der ersten Ebene zuweise.

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);
}

  • Können Sie nicht einfach mit einem Array aller parent_ids und einem weiteren Array für Ihre Seiten arbeiten?

    – djot

    21. Dezember 2011 um 9:17 Uhr

Rekursive Funktion zum Generieren eines mehrdimensionalen Arrays aus dem Datenbankergebnis
verzeihen

Einige sehr einfache, generische Baumkonstruktionen:

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);

Der Algorithmus ist ziemlich einfach:

  1. Nehmen Sie das Array aller Elemente und die ID des aktuellen Elternteils (anfänglich 0/nichts/null/wie auch immer).
  2. Alle Elemente durchlaufen.
  3. Wenn die parent_id eines Elements mit der aktuellen übergeordneten ID übereinstimmt, die Sie in 1. erhalten haben, ist das Element ein untergeordnetes Element des übergeordneten Elements. Tragen Sie es in Ihre Liste der aktuellen Kinder ein (hier: $branch).
  4. Rufen Sie die Funktion rekursiv mit der ID des Elements auf, das Sie gerade in 3. identifiziert haben, dh finden Sie alle untergeordneten Elemente dieses Elements und fügen Sie sie als hinzu children Element.
  5. Geben Sie Ihre Liste der gefundenen Kinder zurück.

Mit anderen Worten, eine Ausführung dieser Funktion gibt eine Liste von Elementen zurück, die Kinder der gegebenen Eltern-ID sind. Nennen Sie es mit buildTree($myArray, 1), wird eine Liste von Elementen zurückgegeben, die die Eltern-ID 1 haben. Anfangs wird diese Funktion mit der Eltern-ID 0 aufgerufen, sodass Elemente ohne Eltern-ID zurückgegeben werden, die Wurzelknoten sind. Die Funktion ruft sich selbst rekursiv auf, um Kinder von Kindern zu finden.

  • Schön, dass es hilft. Hinweis: Dies ist etwas ineffizient, da es immer das Ganze passiert $elements Array nach unten. Bei kleinen Arrays spielt das kaum eine Rolle, aber bei großen Datensätzen sollten Sie das bereits übereinstimmende Element daraus entfernen, bevor Sie es weitergeben. Das wird jedoch etwas chaotisch, also habe ich es für Ihr leichteres Verständnis einfach gelassen. 🙂

    – verzeihen

    21. Dezember 2011 um 9:36 Uhr

  • @deceze Ich würde auch gerne die unordentliche Version sehen. Vielen Dank im Voraus!

    – Jens Törnell

    24. Januar 2013 um 13:37 Uhr

  • Kann bitte jemand erklären, was das erste Argument ‘array’ in buildTree() ist? Sollte das die Variable sein, die Sie dem anfänglichen Array von Seiten usw. geben, z. B. ‘$tree = array’? Kann jemand auch die letzte Zeile ‘$tree = buildTree($rows)’ erklären, da ‘$rows’ nirgendwo definiert ist? Zuletzt kämpfe ich mit dem HTML-Markup, um die verschachtelte Liste zu generieren.

    – Brauner Reis

    27. November 2013 um 22:53 Uhr

  • @Benutzer array ist ein geben Sie einen Hinweis ein zum $elementsist das erste Argument einfach array $elements. $rows ist ein Array von Datenbankergebnissen wie das aus der Frage.

    – verzeihen

    28. November 2013 um 7:12 Uhr

  • @user Ich habe eine Erklärung hinzugefügt. Ignoriere das $children = buildTree(...) Teil und die Funktion sollte ziemlich offensichtlich und einfach sein.

    – verzeihen

    29. November 2013 um 13:15 Uhr

Ich weiß, dass diese Frage alt ist, aber ich stand vor einem sehr ähnlichen Problem – außer mit einer sehr großen Datenmenge. Nach einigem Kampf gelang es mir, den Baum in einem Durchgang der Ergebnismenge zu erstellen – unter Verwendung von Referenzen. Dieser Code ist nicht schön, aber er funktioniert und er funktioniert ziemlich schnell. Es ist nicht rekursiv – das heißt, es gibt nur einen Durchgang über die Ergebnismenge und dann einen array_filter Am Ende:

$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);

Bei Ausführung auf diesen Daten:

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 |
+------+-------------+

Das Letzte print_r erzeugt diese Ausgabe:

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
                                (
                                )

                        )

                )

        )

)

Das ist genau das, wonach ich gesucht habe.

  • Die Lösung ist zwar intelligent, aber dieser Code hat einen Fehler, er hat mir in verschiedenen Situationen unterschiedliche Ergebnisse geliefert

    – Mohammadhzp

    14. August 2015 um 2:47 Uhr

  • @Mohammadhzp Ich habe diese Lösung im letzten Jahr in der Produktion verwendet und hatte kein Problem damit. Wenn Ihre Daten unterschiedlich sind, erhalten Sie unterschiedliche Ergebnisse 🙂

    – Alexander G

    14. August 2015 um 13:42 Uhr

  • @AleksG: Ich stimme Ihrer Antwort vor dem Kommentar zu, ich musste isset($elem[‘children’]) an den array_filter-Callback so etwas wie dieses isset($elem[‘children’]) && is_null($elem[‘n_parent_id’]); damit es richtig funktioniert

    – Mohammadhzp

    14. August 2015 um 14:22 Uhr

  • @Mohammadhzp Mit Ihrer Änderung funktioniert der Code nicht, wenn ein Element der obersten Ebene keine untergeordneten Elemente hat – es wird vollständig aus dem Array entfernt.

    – Alexander G

    14. August 2015 um 14:24 Uhr


  • @AleksG, bei der Verwendung der neuesten Version von PHP passiert das nicht, wenn ich isset($elem[‘children’]) und das Element der obersten Ebene Kinder enthält, würde ich zwei verschiedene Arrays dieses Elements der obersten Ebene erhalten (eines mit Kindern und eines ohne) “Ich habe gerade vor 2 Minuten erneut getestet und ohne isset() bekomme ich ein anderes (falsches) Ergebnis”

    – Mohammadhzp

    14. August 2015 um 14:43 Uhr

public function testTree(){
    $array = [
        ['id'=>7,'parent_id'=>3],
        ['id'=>1,'parent_id'=>0],
        ['id'=>2,'parent_id'=>0],
        ['id'=>3,'parent_id'=>1],
        ['id'=>4,'parent_id'=>1],
        ['id'=>5,'parent_id'=>2],
        ['id'=>6,'parent_id'=>1],
        ['id'=>8,'parent_id'=>4],
        ['id'=>9,'parent_id'=>4],
        ['id'=>10,'parent_id'=>0]
    ];
    $res = $this->buildTree($array);
    print_r($res);
}

public function buildTree($array,$id_key = 'id',$parent_key = 'parent_id'){
    $res = [];
    foreach($array as $y){
        $array_with_id[$y[$id_key]] = $y;
    }
    foreach($array_with_id as $key => $element){
        if($element[$parent_key]){
            $array_with_id[$element[$parent_key]]['childrens'][$key] = &$array_with_id[$key];
        }else{
            $res[$element[$id_key]] = &$array_with_id[$key];
        }
    }
    return $res;
}

Zu viele Operationen sind rekursiv vorgesehen, ich denke, es ist der beste Weg.

Inspiriert von anderen Antworten hier, habe ich mir eine eigene Version zum Gruppieren von an ausgedacht Array von zugeordneten Arrays rekursiv (bis zu einer beliebigen Tiefe), indem Sie Liste der benutzerdefinierten Funktionen zu erhalten Schlüssel gruppieren auf jeder Ebene.

Hier ist eine vereinfachte Version der ursprüngliche komplexere Variante (mit mehr Parametern zum Anpassen von Knöpfen). Beachten Sie, dass es eine einfache verwendet Iterative Funktion groupByFn als Subroutine zum Durchführen der Gruppierung auf einzelnen Ebenen.

/**
 * - 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"https://stackoverflow.com/"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
        );
    }
}

Überprüfen Sie das Wesentliche für größer Sammlung von Array-Hilfsfunktionen zusammen mit Unit-Tests

Es ist möglich, PHP zu verwenden, um das MySQL-Ergebnis in ein Array zu bekommen und es dann zu verwenden.

$categoryArr = Array();
while($categoryRow = mysql_fetch_array($category_query_result)){
    $categoryArr[] = array('parentid'=>$categoryRow['parent_id'],
            'id'=>$categoryRow['id']);
   }

986960cookie-checkRekursive Funktion zum Generieren eines mehrdimensionalen Arrays aus dem Datenbankergebnis

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy