2011-01-30 28 views
31

Tôi đang cố gắng tạo danh sách các danh mục với bất kỳ số danh mục phụ nào, trong đó các danh mục con cũng có thể có danh mục phụ của riêng chúng.Cấu trúc cây PHP cho các danh mục và danh mục phụ mà không lặp lại truy vấn

Tôi đã chọn tất cả các danh mục từ db Mysql, mèo nằm trong danh sách mảng liên kết tiêu chuẩn, mỗi danh mục có id, tên, parentid trong đó parentid là 0 nếu là cấp cao nhất. Về cơ bản tôi muốn có thể lấy mảng đơn của mèo và biến nó thành một cấu trúc mảng đa chiều, trong đó mỗi thể loại có thể có một phần tử sẽ chứa một mảng các subcats.

Bây giờ, tôi có thể dễ dàng đạt được điều này bằng cách lặp lại truy vấn cho từng danh mục nhưng điều này là xa lý tưởng, tôi đang cố gắng làm điều đó mà không cần thêm bất kỳ lần truy cập nào trên db.

Tôi hiểu rằng tôi cần một hàm đệ quy cho việc này. Bất cứ ai có thể chỉ cho tôi đi đúng hướng cho cấu trúc kiểu cây này?

Cheers

+0

Bạn có thể sử dụng lớp TreeNode cho mục đích này. Trong đó bạn có thể nhận được bất kỳ nút con nào và sau đó lặp lại thành con của nó. Tải xuống tại đây: http://asimishaq.com/resources/tree-data-structure-in-php –

Trả lời

77

này không được công việc:

$items = array(
     (object) array('id' => 42, 'parent_id' => 1), 
     (object) array('id' => 43, 'parent_id' => 42), 
     (object) array('id' => 1, 'parent_id' => 0), 
); 

$childs = array(); 

foreach($items as $item) 
    $childs[$item->parent_id][] = $item; 

foreach($items as $item) if (isset($childs[$item->id])) 
    $item->childs = $childs[$item->id]; 

$tree = $childs[0]; 

print_r($tree); 

này hoạt động bằng cách loại lập chỉ mục đầu tiên của PARENT_ID. Sau đó, đối với mỗi danh mục, chúng tôi chỉ cần đặt category->childs thành childs[category->id] và cây được xây dựng!

Vì vậy, bây giờ $tree là cây danh mục. Nó chứa một loạt các mục có PARENT_ID = 0, mà bản thân có chứa một loạt các Childs của họ, mà bản thân ...

Sản lượng print_r($tree):

stdClass Object 
(
    [id] => 1 
    [parent_id] => 0 
    [childs] => Array 
     (
      [0] => stdClass Object 
       (
        [id] => 42 
        [parent_id] => 1 
        [childs] => Array 
         (
          [0] => stdClass Object 
           (
            [id] => 43 
            [parent_id] => 42 
           ) 

         ) 

       ) 

     ) 

) 

Vì vậy, đây là chức năng cuối cùng:

function buildTree($items) { 

    $childs = array(); 

    foreach($items as $item) 
     $childs[$item->parent_id][] = $item; 

    foreach($items as $item) if (isset($childs[$item->id])) 
     $item->childs = $childs[$item->id]; 

    return $childs[0]; 
} 

$tree = buildTree($items); 


Dưới đây là phiên bản tương tự, với mảng, mà là một chút khó khăn như chúng ta cần phải chơi với sự tham khảo (còn hoạt động tốt như nhau):

$items = array(
     array('id' => 42, 'parent_id' => 1), 
     array('id' => 43, 'parent_id' => 42), 
     array('id' => 1, 'parent_id' => 0), 
); 

$childs = array(); 
foreach($items as &$item) $childs[$item['parent_id']][] = &$item; 
unset($item); 

foreach($items as &$item) if (isset($childs[$item['id']])) 
     $item['childs'] = $childs[$item['id']]; 
unset($item); 

$tree = $childs[0]; 

Vì vậy, các phiên bản hàng loạt các chức năng cuối cùng:

function buildTree($items) { 

    $childs = array(); 

    foreach($items as &$item) $childs[$item['parent_id']][] = &$item; 
    unset($item); 

    foreach($items as &$item) if (isset($childs[$item['id']])) 
      $item['childs'] = $childs[$item['id']]; 

    return $childs[0]; 
} 

$tree = buildTree($items); 
+3

Câu trả lời hay, công việc tuyệt vời! – Phil

+16

Một vài dấu ngoặc nhọn sẽ không bị tổn thương;) – jens

+0

Câu trả lời hay, cảm ơn :) – Goodbytes

15

Bạn có thể lấy tất cả các loại cùng một lúc.

Giả sử bạn có một kết quả phẳng từ cơ sở dữ liệu, như thế này:

$categories = array(
    array('id' => 1, 'parent' => 0, 'name' => 'Category A'), 
    array('id' => 2, 'parent' => 0, 'name' => 'Category B'), 
    array('id' => 3, 'parent' => 0, 'name' => 'Category C'), 
    array('id' => 4, 'parent' => 0, 'name' => 'Category D'), 
    array('id' => 5, 'parent' => 0, 'name' => 'Category E'), 
    array('id' => 6, 'parent' => 2, 'name' => 'Subcategory F'), 
    array('id' => 7, 'parent' => 2, 'name' => 'Subcategory G'), 
    array('id' => 8, 'parent' => 3, 'name' => 'Subcategory H'), 
    array('id' => 9, 'parent' => 4, 'name' => 'Subcategory I'), 
    array('id' => 10, 'parent' => 9, 'name' => 'Subcategory J'), 
); 

Bạn có thể tạo một hàm đơn giản mà biến rằng danh sách phẳng thành một cấu trúc, tốt nhất là bên trong một hàm. Tôi sử dụng pass-by-reference để chỉ có một mảng cho mỗi thể loại và không có nhiều bản sao của mảng cho một thể loại.

function categoriesToTree(&$categories) { 

Bản đồ được sử dụng để tra cứu nhanh các danh mục. Ở đây, tôi cũng tạo ra một mảng giả cho mức "root".

$map = array(
     0 => array('subcategories' => array()) 
    ); 

Tôi đã thêm một trường, danh mục con khác vào từng mảng danh mục và thêm nó vào bản đồ.

foreach ($categories as &$category) { 
     $category['subcategories'] = array(); 
     $map[$category['id']] = &$category; 
    } 

Lặp lại từng danh mục, thêm chính nó vào danh sách danh mục phụ của phụ huynh. Tham chiếu quan trọng ở đây, nếu không các danh mục đã được thêm sẽ không được cập nhật khi có nhiều danh mục con hơn.

foreach ($categories as &$category) { 
     $map[$category['parent']]['subcategories'][] = &$category; 
    } 

Cuối cùng, trả lại tiểu thể loại đó loại hình nộm mà tham khảo tất cả các cấp cao nhất categories._

return $map[0]['subcategories']; 

} 

Cách sử dụng:

$tree = categoriesToTree($categories); 

Và đây là mã trong hành động trên Codepad.

+1

Giải pháp tốt! Điều này có thể tiêu thụ bộ nhớ máy chủ nhiều hơn nhưng nên được hiệu quả hơn khi xử lý tập hợp lớn các loại. – Andreyco

+0

Xin lỗi nếu nó là một câu hỏi ngu ngốc, để thiết lập các mảng từ cơ sở dữ liệu, tôi chỉ cần thực hiện truy vấn và sau đó gán cho một biến, và sau đó asign biến đó để array(). Và cũng có thể, làm thế nào bạn có thể in cây như một hộp chọn http://stackoverflow.com/questions/16470154/undefined-offset-0-how-can-i-set-this-array –

0

Tôi đã có cùng một vấn đề và giải quyết nó theo cách này: lấy hàng mèo từ DB và cho mỗi loại gốc, xây dựng cây, bắt đầu với mức độ (độ sâu) 0. Có thể không phải là giải pháp hiệu quả nhất, nhưng làm việc cho tôi.

$globalTree = array(); 
$fp = fopen("/tmp/taxonomy.csv", "w"); 

// I get categories from command line, but if you want all, you can fetch from table 
$categories = $db->fetchCol("SELECT id FROM categories WHERE parentid = '0'"); 

foreach ($categories as $category) { 
    buildTree($category, 0); 
    printTree($category); 
    $globalTree = array(); 
} 

fclose($file); 

function buildTree($categoryId, $level) 
{ 
    global $db, $globalTree; 
    $rootNode = $db->fetchRow("SELECT id, name FROM categories WHERE id=?", $categoryId); 
    $childNodes = $db->fetchAll("SELECT * FROM categories WHERE parentid = ? AND id <> ? ORDER BY id", array($rootNode['id'], $rootNode['id'])); 
    if(count($childNodes) < 1) { 
     return 0; 
    } else { 
     $childLvl = $level + 1; 
     foreach ($childNodes as $childNode) { 
      $id = $childNode['id']; 
      $childLevel = isset($globalTree[$id])? max($globalTree[$id]['depth'], $level): $level; 
      $globalTree[$id] = array_merge($childNode, array('depth' => $childLevel)); 
      buildTree($id, $childLvl); 
     } 
    } 
} 

function printTree($categoryId) { 
    global $globalTree, $fp, $db; 
    $rootNode = $db->fetchRow("SELECT id, name FROM categories WHERE id=?", $categoryId); 
    fwrite($fp, $rootNode['id'] . " : " . $rootNode['name'] . "\n"); 
    foreach ($globalTree as $node) { 
     for ($i=0; $i <= $node['depth']; $i++) { 
      fwrite($fp, ","); 
     } 
     fwrite($fp, $node['id'] " : " . $node['name'] . "\n"); 
    } 
} 

ps. Tôi biết rằng OP đang tìm kiếm giải pháp không có truy vấn DB, nhưng điều này liên quan đến đệ quy và sẽ giúp bất kỳ ai vấp phải câu hỏi này tìm kiếm giải pháp đệ quy cho loại câu hỏi này và không quan tâm đến truy vấn DB.

1

Xem phương pháp:

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['id']] = $element; 
     } 
    } 
    return $branch; 
} 
+0

SWET !! điều này làm việc –

+0

chính xác giống như [câu trả lời này] (http://stackoverflow.com/a/8587437/1713660) ngoại trừ tham chiếu vô dụng trong tham số '& $ elements' và' id' trong chi nhánh – vladkras

Các vấn đề liên quan