Сортировка значений массива на основе отношений родителя/ребенка
Я пытаюсь сортировать массив, чтобы гарантировать, что родительский элемент какого-либо элемента всегда существует до него в массиве. Например:
Array
(
[0] => Array
(
[0] => 207306
[1] => Bob
[2] =>
)
[1] => Array
(
[0] => 199730
[1] => Sam
[2] => 199714
)
[2] => Array
(
[0] => 199728
[1] => Simon
[2] => 207306
)
[3] => Array
(
[0] => 199714
[1] => John
[2] => 207306
)
[4] => Array
(
[0] => 199716
[1] => Tom
[2] => 199718
)
[5] => Array
(
[0] => 199718
[1] => Phillip
[2] => 207306
)
[6] => Array
(
[0] => 199720
[1] => James
[2] => 207306
)
)
В вышеприведенном массиве это "не удается", поскольку [1] [2] (Sam) еще не существует и не работает [4] [2] (Tom).
Правильный вывод был бы таким же, как и в этом случае, поскольку оба родителя Sam и Tom уже существуют до того, как они появятся в массиве:
Array
(
[0] => Array
(
[0] => 207306
[1] => Bob
[2] =>
)
[1] => Array
(
[0] => 199714
[1] => John
[2] => 207306
)
[2] => Array
(
[0] => 199730
[1] => Sam
[2] => 199714
)
[3] => Array
(
[0] => 199728
[1] => Simon
[2] => 207306
)
[4] => Array
(
[0] => 199718
[1] => Phillip
[2] => 207306
)
[5] => Array
(
[0] => 199716
[1] => Tom
[2] => 199718
)
[6] => Array
(
[0] => 199720
[1] => James
[2] => 207306
)
)
Я нашел ответ qaru.site/info/503428/..., который был очень близок, но он, кажется, только на одном уровне (т.е. есть только один родитель), тогда как в моем случае в иерархии может быть 1 или 10 уровней.
Как отсортировать массив так, чтобы значение не могло появиться, если его родительский объект уже существует до него?
Ответы
Ответ 1
Это тривиально упорядочивает массив (в O (n)), помещая сначала всех без родителя, а затем те, чей родитель уже находится в массиве, итеративно, пока нет дочерних элементов, которые имеют текущий элемент как родительский.
# map the children by parent
$parents = ['' => []];
foreach ($array as $val) {
$parents[$val[2]][] = $val;
}
# start with those with no parent
$sorted = $parents[''];
# add the children the current nodes are parent of until the array is empty
foreach ($sorted as &$val) {
if (isset($parents[$val[0]])) {
foreach ($parents[$val[0]] as $next) {
$sorted[] = $next;
}
}
}
Этот код требует PHP 7, он может не работать в некоторых случаях в PHP 5. - для совместимости с PHP 5 вам придется поменять foreach ($sorted as &$val)
на for ($val = reset($sorted); $val; $val = next($sorted))
:
# a bit slower loop which works in all versions
for ($val = reset($sorted); $val; $val = next($sorted)) {
if (isset($parents[$val[0]])) {
foreach ($parents[$val[0]] as $next) {
$sorted[] = $next;
}
}
}
Live demo: https://3v4l.org/Uk6Gs
Ответ 2
У меня для вас две разные версии.
a) Использование подхода "ходить по дереву" с рекурсией и ссылками для минимизации потребления памяти
$data = [
[207306,'Bob',''], [199730,'Sam',199714],
[199728,'Simon',207306], [199714,'John',207306],
[199716, 'Tom',199718], [199718,'Phillip',207306],
[199720,'James',207306]
];
$list = [];
generateList($data, '', $list);
var_dump($list);
function generateList($data, $id, &$list) {
foreach($data as $d) {
if($d[2] == $id) {
$list[] = $d; // Child found, add it to list
generateList($data, $d[0], $list); // Now search for childs of this child
}
}
}
b) Использование phps, встроенного в функцию u usort()
(похоже, работает только с php 5.x, а не с php7 +)
$data = [
[207306,'Bob',''], [199730,'Sam',199714],
[199728,'Simon',207306], [199714,'John',207306],
[199716, 'Tom',199718], [199718,'Phillip',207306],
[199720,'James',207306]
];
usort($data, 'cmp');
var_dump($data);
function cmp($a, $b) {
if($a[2] == '' || $a[0] == $b[2]) return -1; //$a is root element or $b is child of $a
if($b[2] == '' || $b[0] == $a[2]) return 1; //$b is root element or $a is child of $b
return 0; // both elements have no direct relation
}
Ответ 3
Я проверил это в PHP 5.6 и PHP 7
Пример массива:
$array = Array(0 => Array(
0 => 207306,
1 => 'Bob',
2 => '',
),
1 => Array
(
0 => 199730,
1 => 'Sam',
2 => 199714,
),
2 => Array
(
0 => 199728,
1 => 'Simon',
2 => 207306,
),
3 => Array
(
0 => 199714,
1 => 'John',
2 => 207306,
),
4 => Array
(
0 => 199716,
1 => 'Tom',
2 => 199718,
),
5 => Array
(
0 => 199718,
1 => 'Phillip',
2 => 207306,
),
6 => Array
(
0 => 199720,
1 => 'James',
2 => 207306,
),
);
echo "<pre>";
$emp = array();
//form the array with parent and child
foreach ($array as $val) {
$manager = ($val[2] == '') ? 0 : $val[2];
$exist = array_search_key($val[2], $emp);
if ($exist)
$emp[$exist[0]][$val[0]] = $val;
else
//print_R(array_search_key(199714,$emp));
$emp[$manager][$val[0]] = $val;
}
$u_emp = $emp[0];
unset($emp[0]);
//associate the correct child/emp after the manager
foreach ($emp as $k => $val) {
$exist = array_search_key($k, $u_emp);
$pos = array_search($k, array_keys($u_emp));
$u_emp = array_slice($u_emp, 0, $pos+1, true) +
$val +
array_slice($u_emp, $pos-1, count($u_emp) - 1, true);
}
print_R($u_emp); //print the final result
// key search function from the array
function array_search_key($needle_key, $array, $parent = array())
{
foreach ($array AS $key => $value) {
$parent = array();
if ($key == $needle_key)
return $parent;
if (is_array($value)) {
array_push($parent, $key);
if (($result = array_search_key($needle_key, $value, $parent)) !== false)
return $parent;
}
}
return false;
}
Ответ 4
Найдите приведенный ниже код, который может быть полезен. Итак, ваш вывод хранится в $sortedarray.
$a=array(array(207306,'Bob',''),
array (199730,'Sam',199714),
array(199728,'Simon',207306),
array(199714,'John',207306),
array(199716,'Tom',199718),
array(199718,'Phillip',207306),
array(199720,'James',207306));
$sortedarray=$a;
foreach($a as $key=>$value){
$checkvalue=$value[2];
$checkkey=$key;
foreach($a as $key2=>$value2){
if($key<$key2){
if ($value2[0]===$checkvalue){
$sortedarray[$key]=$value2;
$sortedarray[$key2]=$value;
}else{
}
}
}
}
print_r($sortedarray);
Ответ 5
Как насчет этого подхода:
Создайте пустой массив result
.
Перемещайтесь по массиву и вынимайте из него только те места, где [2] пуст и вставляем их в result
.
Когда этот цикл завершен, вы используете foreach
-Loop внутри a while
-loop. С помощью foreach
-Loop вы берете каждый элемент из своего массива, где [2] уже является частью result
. И вы делаете это, пока ваш массив содержит что-либо.
$result = array();
$result[''] = 'root';
while(!empty($yourArray)){
foreach($yourArray as $i=>$value){
if(isset($result[$value[2]])){
// use the next line only to show old order
$value['oldIndex'] = $i;
$result[$value[0]] = $value;
unset($yourArray[$i]);
}
}
}
unset($result['']);
PS: У вас могут возникнуть проблемы, удалив части массива, прогуливаясь по нему. Если вы это сделаете... попытайтесь решить эту проблему:)
PPS: подумайте о состоянии прерывания, если ваш массив имеет неразрешенный цикл или дочерний элемент без родителя.
Ответ 6
вы можете использовать свой массив в переменной $arr и использовать этот код, он даст вам необходимый результат.
function check($a, $b) {
return ($a[0] == $b[2]) ? -1 : 1;
}
uasort($arr, 'check');
echo '<pre>';
print_r(array_values($arr));
echo '</pre>';