How do I format Nested Set Model data into an array?
Asked Answered
A

5

7

Let's dig in the main problem right away, I have the input like this

$category = array(
  'A' => array('left' => 1, 'right' => 8),
  'B' => array('left' => 2, 'right' => 3),
  'C' => array('left' => 4, 'right' => 7),
  'D' => array('left' => 5, 'right' => 6),
  'E' => array('left' => 9, 'right' => 10),
);

I want the output to be something like this

$tree = array(
  array('A', 'B'),
  array('A', 'C', 'D'),
  array('E'),
);

which one is the best and fast function to loop though the input array and create the output result like this ?

Arrowy answered 8/6, 2013 at 12:27 Comment(10)
You should include the business logic of conversion from the first structure to second. It is not very clear right now.Scold
Your desired output does not reflect a nested set. should be more like array(A => array(B => null, C => array(D => null), E => null)Riess
@hw the business logic here is en.wikipedia.org/wiki/Nested_set_modelArrowy
@Steven Moseley: that output is what make the function hard thought.Arrowy
@Arrowy - I don't understand your comment. Are you saying 1) that it's difficult to create output in the format I showed above, or 2) that using output in that format is difficult? I don't see why it would make sense to output nested set data in something other than a nested set. Your desired output above looks like it would be more difficult to use than properly structured dataRiess
@StevenMoseley what i mean is the "required output" is what make the problem seem hard, actually i put more than 60 mins of my life into that and no result at all.Arrowy
@StevenMoseley Your output make more sense to me, but is it easy to use your output to print out category breadcrumb like that A > B A > B > C EArrowy
Your model is missing nr 3.Collyer
@Collyer thanks for remind me, i will edit the question.Arrowy
Provided the solution to your problem belowRiess
R
19

Working with a nested set is a perfect case for recursion.

Given your data:

$category = array(
    'A' => array('left' => 1, 'right' => 9),
    'B' => array('left' => 2, 'right' => 4),
    'C' => array('left' => 5, 'right' => 8),
    'D' => array('left' => 6, 'right' => 7),
    'E' => array('left' => 10, 'right' => 11),
);

The following will break your nested set data down into a properly nested array in PHP:

function createTree($category, $left = 0, $right = null) {
    $tree = array();
    foreach ($category as $cat => $range) {
        if ($range['left'] == $left + 1 && (is_null($right) || $range['right'] < $right)) {
            $tree[$cat] = createTree($category, $range['left'], $range['right']);
            $left = $range['right'];
        }
    }
    return $tree;
}

$tree = createTree($category);
print_r($tree);

Output:

Array
(
    [A] => Array
        (
            [B] => Array
                (
                )

            [C] => Array
                (
                    [D] => Array
                        (
                        )

                )

        )

    [E] => Array
        (
        )

)

Then you can flatten your proper tree into the format you want with the following:

function flattenTree($tree, $parent_tree = array()) {
    $out = array();
    foreach ($tree as $key => $children) {
        $new_tree = $parent_tree;
        $new_tree[] = $key;
        if (count($children)) {
             $child_trees = flattenTree($children, $new_tree);
            foreach ($child_trees as $tree) {
                $out[] = $tree;
            }
        } else {
            $out[] = $new_tree;
        }
    }
    return $out;
}

$tree = flattenTree($tree);
print_r($tree);

Output:

Array
(
    [0] => Array
        (
            [0] => A
            [1] => B
        )

    [1] => Array
        (
            [0] => A
            [1] => C
            [2] => D
        )

    [2] => Array
        (
            [0] => E
        )

)
Riess answered 8/6, 2013 at 13:12 Comment(1)
Your $category array doesn't represent the nested sets. You missed index = 3. And your output is wrong.Peacoat
V
0

Another solution, without recursion (test please)

$result = array();

    foreach($category as $key => $value) {

        /*Get current row index*/
        $i = count($result);

        if($i == 0) {
            $result[] = array($key);
        } else {

            $iParent = -1;

            /*Find parent index*/
            for($j = count($result[$i-1]) - 1; $j >= 0; $j--) {
                if($value['left'] > $category[$result[$i-1][$j]]['left'] 
                    && $value['right'] < $category[$result[$i-1][$j]]['right']) {
                    $iParent = $j;
                    break;
                }
            }

            if($iParent == -1) { $result[] = array($key);}

            if($iParent == count($result[$i-1]) - 1) {
                // append to last
                $result[$i-1][] = $key;
            } else {
                // make new list
                $result[$i] = array_slice($result[$i-1], 0, $iParent + 1);
                $result[$i][] = $key;
            }
        }
    }

    print_r($result);
Varied answered 10/6, 2013 at 4:19 Comment(0)
T
0

There is a bug with above function. The top category of second array of the @tree is removed. This is the fix:

foreach ($category as $name => $range) {
    $line[$range['left']] = $name;
    $line[$range['right']] = $name;
}

ksort($line);
$tree = array();
$count = 0;

foreach ($line as $name) {
    if (!isset($open[$name])) {
        $open[$name] = true;
        $count++;
    }
    else {
        if ($count > 0) {
            $count = 0;
            $tree[] = array_keys($open);
        }
        unset($open[$name]);
    }
}
Tetragonal answered 20/3, 2014 at 8:21 Comment(0)
A
0

I little modified Stiven's code.

public function createTree($category, $left = 0, $right = null) {
    $tree = array();
    foreach ($category as $cat => $range) {
        if ($range['clf'] == $left + 1 && (is_null($right) || $range['crt'] < $right)) {
            $tree[$cat]= array();
            $tree[$cat]['title']=$range['title'];
            if($range['crt']-$range['clf']>1){
                $tree[$cat]['sub'] = $this->createTree($category, $range['clf'], $range['crt']);
            }
            $left = $range['crt'];
        }
    }
    return $tree;
}
Alumnus answered 5/4, 2014 at 9:56 Comment(0)
I
-1

If you dont want to use recursion:

foreach ($category as $name => $range) {
    $line[$range['left']] = $name;
    $line[$range['right']] = $name;
}

ksort($line);
$count = 0;

foreach($line as $name) {
    if ( ! isset($open[$name])) {
        $open[$name] = true;
        $result[$name] = true;
        $count++;
    } else {
        unset($open[$name]);
        if ($count > 0) {
            $count = 0;
            $tree[] = array_keys($result);
            $result = $open;
        } else {
            $result = array();
        }
    }
}
Ichthyic answered 8/6, 2013 at 16:25 Comment(1)
Recursion is necessary to handle potentially infinite levels of nesting. Did you test this?Riess

© 2022 - 2024 — McMap. All rights reserved.