0

I have a array with a string hierarchy like so:

table, parent_table
test, NULL
test, NULL
test2, test
test4, NULL
test5, test3
test6, test5
test3, test

I want to sort with a function that goes something like this:

usort($array, function($a,$b) {
    return ($a['table'] === $b['parent_table']) ? -1 : 1;
});

Ideal result would be

table, parent_table
test, NULL
test, NULL
test2, test
test3, test
test5, test3
test6, test5
test4, NULL

This would sort parents above children tables. I've been trying to find a good solution for this problem. Is there a usort for string hierarchies?

4
  • why is test 2 times? Commented Jan 25, 2019 at 6:28
  • This is a simplified version of the array. The array contains different information with other variables that are separate to each row but have the same table and parent table. Commented Jan 25, 2019 at 7:08
  • So how do you differentiate the children of one set of test, NULL from the other? Commented Jan 25, 2019 at 7:11
  • Its like having two parents who are married and have children. The point is to have children appear after parents regardless of parent. Commented Jan 25, 2019 at 8:11

2 Answers 2

1
<?php

$data = [
    [
        'table' => 'test',
        'parent_table' => NULL,
    ],
    [
        'table' => 'test',
        'parent_table' => NULL,
    ],
    [
        'table' => 'test2',
        'parent_table' => 'test',
    ],
    [
        'table' => 'test4',
        'parent_table' => NULL,
    ],
    [
        'table' => 'test5',
        'parent_table' => 'test3',
    ],
    [
        'table' => 'test6',
        'parent_table' => 'test5',
    ],
    [
        'table' => 'test3',
        'parent_table' => 'test',
    ],
];


function reorderHierarchy($data){

    $hierarchy = [];
    $top_level_parents = [];

    foreach($data as $each_data){
        $hierarchy[$each_data['table']] = array();
        if(is_null($each_data['parent_table'])){
            if(!isset($top_level_parents[$each_data['table']])){
                $top_level_parents[$each_data['table']] = 0;
            }
            $top_level_parents[$each_data['table']]++;
        }
    }

    foreach($data as $each_data){
        if(!is_null($each_data['parent_table'])){
            $hierarchy[$each_data['parent_table']][] = $each_data['table'];
        }
    }

    $result = [];
    traverseHierarchy($hierarchy,$top_level_parents,$result);
    return $result;
}

function traverseHierarchy($hierarchy,$top_level_parents,&$result){
    foreach($top_level_parents as $each_parent => $occurrences){
        while($occurrences-- > 0){
            $result[] = [
                'table' => $each_parent,
                'parent_table' => NULL
            ];
        }       

        traverseChildren($hierarchy,$each_parent,$result);
    }
}

function traverseChildren($hierarchy,$parent,&$result){
    foreach($hierarchy[$parent] as $each_child){
        $result[] = [
            'table' => $each_child,
            'parent_table' => $parent
        ];

        traverseChildren($hierarchy,$each_child,$result);
    }
}


foreach(reorderHierarchy($data) as $each_data){
    echo $each_data['table']," , ",(is_null($each_data['parent_table']) ? "NULL" : $each_data['parent_table']),"<br/>";
}

Output:

test , NULL
test , NULL
test2 , test
test3 , test
test5 , test3
test6 , test5
test4 , NULL

Demo: https://3v4l.org/AmJpY

Explanation:

Part 1:

function reorderHierarchy($data){

    $hierarchy = [];
    $top_level_parents = [];

    foreach($data as $each_data){
        $hierarchy[$each_data['table']] = array();
        if(is_null($each_data['parent_table'])){
            if(!isset($top_level_parents[$each_data['table']])){
                $top_level_parents[$each_data['table']] = 0;
            }
            $top_level_parents[$each_data['table']]++;
        }
    }

    foreach($data as $each_data){
        if(!is_null($each_data['parent_table'])){
            $hierarchy[$each_data['parent_table']][] = $each_data['table'];
        }
    }

    $result = [];
    traverseHierarchy($hierarchy,$top_level_parents,$result);
    return $result;
}
  • In the above function, we are creating 2 kinds of arrays, namely, $hierarchy and $top_level_parents. $hierarchy is an array where every key has it's children keys inside it. $top_level_parents is an array which collects all tables who doesn't have any parent with key being table name and value being it's occurrences.

  • We then call another function traverseHierarchy to traverse over all of these top level parents and get their children as well. This way, we will always visit parents before children, since we are iterating parents first(even valid for it's children who would be in turn parents for other tables).

  • To better explain, both arrays would look something like below:

$hierarchy:

Array
(
    [test] => Array
        (
            [0] => test2
            [1] => test3
        )

    [test2] => Array
        (
        )

    [test4] => Array
        (
        )

    [test5] => Array
        (
            [0] => test6
        )

    [test6] => Array
        (
        )

    [test3] => Array
        (
            [0] => test5
        )
)

$top_level_parents:

Array
(
    [test] => 2
    [test4] => 1
)

Part 2:

function traverseHierarchy($hierarchy,$top_level_parents,&$result){
    foreach($top_level_parents as $each_parent => $occurrences){
        while($occurrences-- > 0){
            $result[] = [
                'table' => $each_parent,
                'parent_table' => NULL
            ];
        }       

        traverseChildren($hierarchy,$each_parent,$result);
    }
}
  • Here, we are iterating over all top level parents, storing them in the result array with the no. of times they occurred in the original array.

  • Once done, we would now traverse it's children and include all it's children in the result array as well.

Part 3:

function traverseChildren($hierarchy,$parent,&$result){
    foreach($hierarchy[$parent] as $each_child){
        $result[] = [
            'table' => $each_child,
            'parent_table' => $parent
        ];

        traverseChildren($hierarchy,$each_child,$result);
    }
}
  • Here, we iterate over all children and include them in the result. It is quite possible that this child could have it's own children as well. So, we would recursively collect them all using depth first search. This way we are always ensuring parent comes before child.

  • In the last section, we just print the result.

Sign up to request clarification or add additional context in comments.

Comments

1

Fundamentally you need to process the data recursively to parse the tree structure out of it and into order. This function will do that. It looks for children of the current parent (selecting them via an array_filter) and then iterates through the current children, merging all of their children into the output array. Because of the need to skip matching parents, we have to check that a child is not the same as the previous one before adding it to the output:

function hsort($array, $parent) {
    $output = array();
    $children = array_filter($array, function ($v) use ($parent) { return $v['parent_table'] === $parent; });
    sort($children);
    $lastchild = NULL;
    foreach ($children as $child) {
        if ($child != $lastchild && !is_null($lastchild)) {
            $output[] = $lastchild;
            $output = array_merge($output, hsort($array, $lastchild['table']));
        }
        elseif ($lastchild != NULL) {
            $output[] = $lastchild;
        }
        $lastchild = $child;
    }
    if (!is_null($lastchild)) {
        $output[] = $lastchild;
        $output = array_merge($output, hsort($array, $lastchild['table']));
    }
    return $output;
}

echo "table   | parent_table\n";
foreach (hsort($array, NULL) as $v) {
    printf("%-8s| %s\n", $v['table'], $v['parent_table'] ?? 'NULL');
}

Output

table | parent_table 
test  | NULL
test  | NULL
test2 | test 
test3 | test 
test5 | test3 
test6 | test5 
test4 | NULL

Demo on 3v4l.org

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.