问题
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?
回答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.
回答2:
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
来源:https://stackoverflow.com/questions/54359864/php-usort-for-string-hierarchy