Hàm đệ quy để tạo mảng nhiều chiều từ kết quả cơ sở dữ liệu


81

Tôi đang tìm cách viết một hàm lấy một mảng trang / danh mục (từ kết quả cơ sở dữ liệu phẳng) và tạo một mảng các mục trang / danh mục lồng nhau dựa trên id mẹ. Tôi muốn thực hiện điều này một cách đệ quy, để có thể thực hiện bất kỳ mức độ lồng nào.

Ví dụ: Tôi đang tìm nạp tất cả các trang trong một truy vấn và đây là bảng cơ sở dữ liệu trông như thế nào

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

Và đây là mảng tôi muốn kết thúc để xử lý trong tệp chế độ xem của mình:

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

Tôi đã xem xét và thử gần như mọi giải pháp mà tôi đã gặp (có rất nhiều giải pháp ở đây trên Stack Overflow, nhưng không may mắn có được thứ gì đó đủ chung chung sẽ hoạt động cho cả trang và danh mục.

Đây là kết quả gần nhất mà tôi nhận được, nhưng nó không hoạt động vì tôi đang giao con cái cho phụ huynh cấp một.

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

1
Bạn không thể chỉ làm việc với một mảng gồm tất cả các parent_ids và một mảng khác cho các trang của bạn?
djot 21/12/11

Câu trả lời:


233

Một số cách xây dựng cây rất đơn giản, chung chung:

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

Thuật toán khá đơn giản:

  1. Lấy mảng của tất cả các phần tử và id của phần tử gốc hiện tại (ban đầu 0 / nothing / null/ anything).
  2. Vòng qua tất cả các phần tử.
  3. Nếu parent_idphần tử của một phần tử khớp với id cha hiện tại mà bạn nhận được trong 1., phần tử là con của phần tử cha. Đưa nó vào danh sách những đứa trẻ hiện tại của bạn (tại đây:$branch :).
  4. Gọi hàm một cách đệ quy với id của phần tử bạn vừa xác định trong 3., tức là tìm tất cả các phần tử con của phần tử đó và thêm chúng dưới dạng children phần tử.
  5. Trả lại danh sách những đứa trẻ được tìm thấy của bạn.

Nói cách khác, một lần thực thi hàm này trả về danh sách các phần tử là con của id cha đã cho. Gọi nó với buildTree($myArray, 1), nó sẽ trả về một danh sách các phần tử có id cha 1. Ban đầu, hàm này được gọi với id cha là 0, vì vậy các phần tử không có id cha được trả về, đó là các nút gốc. Hàm tự gọi đệ quy để tìm con của con.


1
Rất vui vì nó có ích. Cần lưu ý: điều này hơi kém hiệu quả vì nó luôn chuyển toàn bộ $elementsmảng xuống. Đối với các mảng nhỏ hầu như không quan trọng, nhưng đối với các tập dữ liệu lớn, bạn sẽ muốn xóa phần tử đã khớp khỏi nó trước khi chuyển nó xuống. Tuy nhiên, điều đó trở nên hơi lộn xộn, vì vậy tôi để nó đơn giản để bạn dễ hiểu hơn. :)
dối trá

6
@deceze Tôi cũng muốn xem phiên bản lộn xộn. Cảm ơn trước!
Jens Törnell

Xin ai đó có thể giải thích đối số đầu tiên 'mảng' trong buildTree () là gì? Đó có phải là var mà bạn cung cấp cho mảng trang ban đầu, v.v. ví dụ: '$ tree = array' không? Ai đó cũng có thể giải thích dòng cuối cùng '$ tree = buildTree ($ lines)', vì '$ lines' không được định nghĩa ở bất kỳ đâu? Cuối cùng, tôi đang đấu tranh với đánh dấu HTML để tạo danh sách lồng nhau.
Brownrice

1
@user arraylà một gợi ý kiểu cho $elements, đối số đầu tiên là đơn giản array $elements. $rowslà một mảng kết quả cơ sở dữ liệu giống như kết quả từ câu hỏi.
dối trá

1
@ người dùng Tôi đã thêm một lời giải thích. Bỏ qua $children = buildTree(...)phần và chức năng sẽ khá rõ ràng và đơn giản.
deceze

13

Tôi biết câu hỏi này đã cũ, nhưng tôi đã gặp phải một vấn đề tương tự - ngoại trừ một lượng rất lớn dữ liệu. Sau một số cuộc đấu tranh, tôi đã cố gắng xây dựng cây trong một lần của tập kết quả - sử dụng các tham chiếu. Mã này không đẹp, nhưng nó hoạt động và hoạt động khá nhanh. Nó không đệ quy - nghĩa là chỉ có một lần chuyển qua tập kết quả và sau đó là một lần chuyển array_filtervào cuối:

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

Khi thực thi trên dữ liệu này:

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

Cuối cùng print_rtạo ra kết quả này:

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

                        )

                )

        )

)

Đó chính xác là những gì tôi đang tìm kiếm.


trong khi giải pháp là thông minh, nhưng mã này có lỗi, nó đã cho tôi kết quả khác nhau về tình huống khác nhau
Mohammadhzp

@Mohammadhzp Tôi đã sử dụng giải pháp này trong sản xuất năm ngoái và không gặp vấn đề gì với nó. Nếu dữ liệu của bạn là khác nhau, bạn sẽ nhận được kết quả khác nhau :)
Aleks G

@AleksG: Tôi ủng hộ câu trả lời của bạn trước khi nhận xét, tôi phải thêm Isset ($ elem ['children']) vào lệnh gọi lại array_filter, tương tự như thế này trả về Isset ($ elem ['children']) && is_null ($ elem [' n_parent_id ']); để làm cho nó làm việc đúng
Mohammadhzp

@Mohammadhzp Với thay đổi của bạn, mã sẽ không hoạt động nếu phần tử cấp cao nhất không có bất kỳ phần tử nào - nó sẽ bị xóa hoàn toàn khỏi mảng.
Aleks G

@AleksG, với việc sử dụng phiên bản php mới nhất, điều đó không xảy ra với tôi, nếu tôi loại bỏ Isset ($ elem ['children']) và phần tử cấp cao nhất chứa con, tôi sẽ nhận được hai mảng khác nhau của cấp cao nhất đó yếu tố (một với trẻ em và một không có) "tôi chỉ kiểm tra một lần nữa 2 phút trước và không có isset () tôi nhận được kết quả khác nhau (sai)"
Mohammadhzp

0

Lấy cảm hứng từ các câu trả lời khác ở đây, tôi đã đưa ra phiên bản của riêng mình để nhóm một loạt các mảng assoc một cách đệ quy (với bất kỳ độ sâu tùy ý nào), bằng cách sử dụng danh sách các hàm tùy chỉnh để lấy các khóa nhóm ở mỗi cấp .

Đây là phiên bản đơn giản hóa của biến thể phức tạp hơn ban đầu (với nhiều thông số hơn để điều chỉnh các nút). Lưu ý rằng nó sử dụng một hàm lặpgroupByFn đơn giản như một chương trình con để thực hiện nhóm ở các cấp độ riêng lẻ.

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

Kiểm tra ý chính để có bộ sưu tập lớn hơn các hàm tiện ích mảng cùng với các bài kiểm tra đơn vị


-1

Có thể sử dụng php để lấy kết quả mysql vào mảng và sau đó sử dụng nó.

$categoryArr = Array();
while($categoryRow = mysql_fetch_array($category_query_result)){
    $categoryArr[] = array('parentid'=>$categoryRow['parent_id'],
            'id'=>$categoryRow['id']);
   }
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.