데이터베이스 결과에서 다차원 배열을 생성하는 재귀 함수


81

(플랫 데이터베이스 결과에서) 페이지 / 카테고리의 배열을 취하고 상위 ID를 기반으로 중첩 된 페이지 / 카테고리 항목의 배열을 생성하는 함수를 작성하려고합니다. 모든 수준의 중첩을 수행 할 수 있도록이 작업을 재귀 적으로 수행하고 싶습니다.

예 : 하나의 쿼리로 모든 페이지를 가져오고 있는데 이것이 데이터베이스 테이블의 모습입니다.

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

그리고 이것은 내 뷰 파일에서 처리하기 위해 끝내고 싶은 배열입니다.

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

나는 내가 만난 거의 모든 솔루션을 살펴보고 시도했습니다 (여기 Stack Overflow에 많은 솔루션이 있지만 페이지와 범주 모두에서 작동하는 일반적인 것을 얻지 못했습니다.

여기에 가장 가까운 것이 있지만 첫 번째 수준의 부모에게 자식을 할당하기 때문에 작동하지 않습니다.

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
모든 parent_id의 배열과 페이지에 대한 다른 배열로 작업 할 수 없습니까?
djot

답변:


233

매우 간단하고 일반적인 트리 구축 :

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

알고리즘은 매우 간단합니다.

  1. 모든 요소의 배열과 현재 부모의 ID를 가져옵니다 (처음에는 0/ nothing / null/ whatever).
  2. 모든 요소를 ​​반복합니다.
  3. 는 IF parent_id요소의 당신이 1에있어 현재의 부모 ID를 일치 요소는 부모의 자식입니다. 현재 자녀 목록에 넣으십시오 (여기 :) $branch.
  4. 3.에서 방금 식별 한 요소의 ID를 사용하여 함수를 재귀 적으로 호출합니다. 즉, 해당 요소의 모든 자식을 찾아 요소로 추가합니다 children.
  5. 찾은 아이들의 목록을 반환하십시오.

즉,이 함수를 한 번 실행하면 주어진 부모 ID의 자식 요소 목록이 반환됩니다. 로 호출 buildTree($myArray, 1)하면 부모 ID가 1 인 요소 목록이 반환됩니다. 처음에는이 함수가 부모 ID가 0 인 상태로 호출되므로 부모 ID가없는 요소 인 루트 노드가 반환됩니다. 이 함수는 자식의 자식을 찾기 위해 자신을 재귀 적으로 호출합니다.


1
도움이되어 기쁩니다. 참고 : 이것은 항상 전체 $elements배열을 아래로 전달하기 때문에 다소 비효율적 입니다. 거의 중요하지 않은 작은 배열의 경우 큰 데이터 세트의 경우 전달하기 전에 이미 일치하는 요소를 제거하는 것이 좋습니다. 그러나 그것은 다소 지저분 해지므로 이해하기 쉽도록 간단하게 남겨 두었습니다. :)
deceze

6
@deceze 지저분한 버전도보고 싶습니다. 미리 감사드립니다!
Jens Törnell 2013 년

누군가 buildTree ()에서 첫 번째 인수 'array'가 무엇인지 설명해 주시겠습니까? 페이지 등의 초기 배열에 제공하는 변수 여야합니다. 예 : '$ tree = array'? '$ rows'가 어디에도 정의되어 있지 않기 때문에 누군가가 마지막 줄 '$ tree = buildTree ($ rows)'를 설명 할 수 있습니까? 마지막으로 중첩 된 목록을 생성하기 위해 HTML 마크 업에 어려움을 겪고 있습니다.
Brownrice 2013

1
@user array는에 대한 유형 힌트 이며 $elements첫 번째 인수는 간단 array $elements합니다. $rows질문에서 나온 것과 같은 데이터베이스 결과의 배열입니다.
deceze

1
@user 설명을 추가했습니다. $children = buildTree(...)부분을 무시하면 기능이 매우 명확하고 간단해야합니다.
deceze

13

이 질문이 오래되었다는 것을 알고 있지만 매우 많은 양의 데이터를 제외하고는 매우 유사한 문제에 직면했습니다. 약간의 어려움을 겪은 후, 참조를 사용하여 결과 집합의 한 단계에서 트리를 구축 할 수있었습니다. 이 코드는 예쁘지는 않지만 작동하며 매우 빠르게 작동합니다. 비재 귀적입니다. 즉, 결과 집합에 대해 하나의 패스 만 array_filter있고 끝에 하나만 있습니다 .

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

이 데이터에서 실행되는 경우 :

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

마지막 print_r은 다음 출력을 생성합니다.

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

                        )

                )

        )

)

내가 찾던 바로 그거야.


솔루션은 스마트이지만,이 코드는 버그가 있지만, 그것은 나에게 다른 상황에서 다른 결과 준
Mohammadhzp을

@Mohammadhzp 저는 작년에이 솔루션을 프로덕션에 사용해 왔으며 문제가 없었습니다. 데이터가 다르면 다른 결과를 얻을 수 있습니다. :)
Aleks G

@AleksG : 댓글 전에 답변을 찬성했습니다. array_filter 콜백에 isset ($ elem [ 'children'])을 추가해야했습니다. return isset ($ elem [ 'children']) && is_null ($ elem [ ' n_parent_id ']); 제대로 작동하도록하기 위해
Mohammadhzp

@Mohammadhzp 변경 사항으로 최상위 요소에 자식이 없으면 코드가 작동하지 않습니다. 배열에서 완전히 제거됩니다.
Aleks G

@AleksG, 결과가 아닌 최신 버전의 PHP를 사용하여 isset ($ elem [ 'children'])을 제거하고 최상위 요소에 자식이 포함되어 있으면 해당 최상위 수준의 두 가지 다른 배열을 얻을 수 있습니다. 요소 (아이들과 하나가없는 한) "난 그냥 다른 (잘못된) 결과를 얻을 수 ()는 isset없이 3 분 전 다시 테스트"
Mohammadhzp

0

여기에서 다른 답변에서 영감 을 얻어 각 수준에서 그룹화 키얻기 위해 사용자 정의 함수 목록을 사용하여 assoc 배열의 배열을 재귀 적으로 (임의의 깊이로) 그룹화하는 자체 버전을 만들었습니다 .

다음은 원래의 더 복잡한 변형 의 단순화 된 버전입니다 (노브 조정을위한 더 많은 매개 변수 포함). 개별 레벨에서 그룹화를 수행하기위한 서브 루틴으로 간단한 반복 함수groupByFn 를 사용합니다.

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

단위 테스트 와 함께 배열 유틸리티 함수의 더 큰 컬렉션에 대한 요점을 확인하십시오.


-1

php를 사용하여 mysql 결과를 배열로 가져온 다음 사용할 수 있습니다.

$categoryArr = Array();
while($categoryRow = mysql_fetch_array($category_query_result)){
    $categoryArr[] = array('parentid'=>$categoryRow['parent_id'],
            'id'=>$categoryRow['id']);
   }
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.