phparraysrecursionmultidimensional-arrayhierarchical-data

Convert a flat array with parent-child expressions as keys into a hierarchical multidimensional array


I have this array:

Array
(
    [1] => animal
    [1-1] => turtle
    [1-1-1] => sea turtle
    [1-1-2] => box turtle
    [1-1-3] => green turtle
    [1-1-3-1] => green turtle with brown tail
)

and I want some how to convert it into:

Array
(
    [1-title] => animal
    [1-sons] => array(
            [1-1-title] => turtle
            [1-1-sons] => array(
                    [1-1-1] => sea turtle
                        [1-1-2] => box turtle
                    [1-1-3-title] => green turtle
                    [1-1-3-sons] => array(
                            [1-1-3-title] => green turtle
                                  )
                    )
              )
)

or maybe you can suggest a better way for organizing the result array..

but how to do that?

I know that's not easy task at all, I'm writing a parser that will walk on data and make tree out of them.


Solution

  • The easiest way of organizing your data would be in such a way:

    array (
      'Animal' =>
      array (
        'Turtle' =>
        array (
          'Sea Turtle',
          'Box Turtle',
          'Green Turtle' =>
          array (
            'Green Turtle With Brown Tail',
          ),
          'Common Turtle',
        ),
      ),
    );
    
    // Or, otherwise written (equivalent to the above)
    
    $animals = array();
    $animals['Animal'] = array();
    $animals['Animal']['Turtle'] = array();
    $animals['Animal']['Turtle'][] = 'Sea Turtle';
    $animals['Animal']['Turtle'][] = 'Box Turtle';
    $animals['Animal']['Turtle']['Green Turtle'] = array();
    $animals['Animal']['Turtle']['Green Turtle'][] = 'Green Turtle With Brown Tail';
    $animals['Animal']['Turtle'][] = 'Common Turtle';
    

    Essentially, the name of the animal is the value, unless it has children, then the value is an array and the key is the animal name.


    That way, you can easily parse the values by doing the following:

    parse_animals($animals);
    
    function parse_animals($array, $indent = 0) {
      if(!is_array($array)) return;    // A little safe guard in case.
    
      foreach($array as $key => $value) {
        echo str_repeat('  ', $indent) . "- ";
    
        if(is_array($value)) {
          echo $key . "\n";
          parse_animals($value, $indent + 1);
        } else {
          echo $value . "\n";
        }
      }
    }
    

    The above in the console will output the following:

    - Animal
      - Turtle
        - Sea Turtle
        - Box Turtle
        - Green Turtle
          - Green Turtle With Brown Tail
        - Common Turtle
    

    EDIT: And here is a version that will output it for a webpage.

    function parse_animals_web($array) {
      if(!is_array($array)) return;    // A little safe guard in case.
    
      foreach($array as $key => $value) {
        echo '<ul>';
    
        if(is_array($value)) {
          echo '<li>' . htmlentities($key) . "</li>";
          parse_animals_web($value);
        } else {
          echo '<li>' . htmlentities($value) . "</li>";
        }
    
        echo '</ul>';
      }
    }
    

    The output is:

    • Animal
      • Turtle
      • Sea Turtle
      • Box Turtle
      • Green Turtle
        • Green Turtle With Brown Tail
      • Common Turtle

    Maybe you want to get the children of an animal.

    function get_children_of($array, $name) {
      foreach($array as $key => $value) {
        if(is_array($value)) {
          if($key === $name) {
            return $value;
          } else {
            return get_children_of($value, $name);
          }
        }
      }
    
      return array();
    }
    

    Now we can get all the children of the Green Turtle and output them.

    $green_turtle = get_children_of($animals, 'Green Turtle');
    parse_array($green_turtle);
    

    The output is:

    - Green Turtle With Brown Tail
    

    EDIT: Since you say you are stuck with the input array being in that weird format, here is a function that will convert your array into the format I specified above:

    function convert_array($array) {
      $new_array = array();
    
      $keys = array_keys($array);
      foreach($keys as $key) {
        $level = explode('-', $key);
        $cur_level = &$new_array;
        $cur_key = '';
    
        foreach($level as $o_key) {
          $cur_key = ltrim($cur_key . '-' . $o_key, '-');
          $next_key = $cur_key . '-1';
          $value = $array[$cur_key];
          $has_child = array_key_exists($next_key, $array);
    
          if($has_child) {
            if(!array_key_exists($value, $cur_level)) {
              $cur_level[$value] = array();
            }
            $cur_level = &$cur_level[$value];
          } else {
            $cur_level[] = $value;
          }
        }
      }
    
      return $new_array;
    }