sort multilevel array php - php

I have the following array:
array(
array(
id: 4,
name: car,
pid: 0
),
array(
id: 5,
name: lights,
pid: 4
),
array(
id: 6,
name: fog,
pid: 5
)
),
array(
array(
id: 1,
name: bike,
pid: 0
),
array(
id: 2,
name: wheel,
pid: 1
),
array(
id: 3,
name: tire,
pid: 2
)
),
array(
id: 7,
name: car,
pid: 0
),
array(
id: 8,
name: lights,
pid: 7
),
array(
id: 9,
name: brake,
pid: 8
)
),
array(
id: 10,
name: car,
pid: 0
),
array(
id: 11,
name: engine,
pid: 10
)
),
)
These multidimesnional array represent the specifications of a car. In a table, it looks like:
| car -> lights -> fog |
| bike -> wheel -> tire |
| car -> lights -> brake |
| car -> engine |
And now I would like to sort the array, for each level on name. So first, sort on the first column, than sort on the second column etc...
Our sorted array should look like:
| bike -> wheel -> tire |
| car -> engine |
| car -> lights -> brake |
| car -> lights -> fog |
Using usort I can sort on the first column:
usort($characteristics, function($a, $b){
return $a[0]['name'] > $b[0]['name'];
});
But offcourse this only affects the first column and in this case, the array looks like:
| bike -> wheel -> tire |
| car -> lights -> fog |
| car -> lights -> brake |
| car -> engine |
It is also possible to have different lengths (like the engine, that has 2 values, and the bike, which has 3). Normally the length varies between 1 and 5.
I rather wouldn't use too much for/foreach loops, because that would slow my program too much. Also sorry for the long question, but I couldn't find a shorter way to explain my problem.
I hope someone can give me e simple clean solution.

I believe this is what you are looking for:
$arr =
array(
array(
array(
"id" => 4,
"name" => "car",
"pid" => 0
),
array(
"id" => 5,
"name" => "lights",
"pid" => 4
),
array(
"id" => 6,
"name" => "fog",
"pid" => 5
)
),
array(
array(
"id" => 1,
"name" => "bike",
"pid" => 0
),
array(
"id" => 2,
"name" => "wheel",
"pid" => 1
),
array(
"id" => 3,
"name" => "tire",
"pid" => 2
)
),
array(
array(
"id" => 7,
"name" => "car",
"pid" => 0
),
array(
"id" => 8,
"name" => "lights",
"pid" => 7
),
array(
"id" => 9,
"name" => "brake",
"pid" => 8
)
),
array(
array(
"id" => 10,
"name" => "car",
"pid" => 0
),
array(
"id" => 11,
"name" => "engine",
"pid" => 10
)
)
);
for ($i = 0; $i < count($arr); $i++) {
foreach ($arr[$i] as $a) {
foreach($a as $key => $value) {
if ($key == 'name') {
$newArr[$i][] = $value;
}
}
}
}
usort($newArr, function($a, $b) {
return $a[0] > $b[0];
});
echo "<pre>", print_r($newArr), "</pre>";

Related

Laravel Eloquent Nested Group by its Relation

Please help me how to add another nested group by based on game category relation. I have 3 tables related using laravel 5.4 eloquent.
Game Providers Table
+----+-------------+--------------+------------------+
| id | game_name | game_type_id | game_category_id |
+----+-------------+--------------+------------------+
| 1 | Sample | 1 | 1 |
| 1 | Sample 0 | 1 | 2 |
| 2 | Sample 1 | 2 | 1 |
| 3 | Sample 2 | 2 | 2 |
+----+-------------+--------------+------------------+
Game Types
+----+-------------+
| id | name |
+----+-------------+
| 1 | Chess |
| 2 | Poker |
+----+-------------+
Game Categories
+----+-------------+
| id | name |
+----+-------------+
| 1 | Recommended |
| 2 | Optional |
+----+-------------+
I'm done of for first nested for game type relation but i dont know how to add another group by for game categories
$game_providers = GameProvider::all();
$game_providers = $game_providers->groupBy(function ($game_provider) {
return $game_provider->game_type->name;
})->all();
and its output
Array{
'Chess' => Array{
0 => Array{
'id' => 1,
'game_name' => 'Sample',
'game_type_id' => 1,
'game_category_id' => 2
},
1 => Array{
'id' => 2,
'game_name' => 'Sample 0',
'game_type_id' => 1,
'game_category_id' => 2
},
},
'Poker' => Array{
0 => Array{
'id' => 3,
'game_name' => 'Sample 1',
'game_type_id' => 2,
'game_category_id' => 1
},
1 => Array{
'id' => 4,
'game_name' => 'Sample 2',
'game_type_id' => 2,
'game_category_id' => 2
},
},
}
My expected output
Array{
'Chess' => Array{
'Recommended' => Array{
0 => Array{
'id' => 1,
'game_name' => 'Sample',
'game_type_id' => 1,
'game_category_id' => 2
}
},
'Optional' => Array{
0 => Array{
'id' => 2,
'game_name' => 'Sample 0',
'game_type_id' => 1,
'game_category_id' => 2
}
}
},
'Poker' => Array{
'Recommended' => Array{
0 => Array{
'id' => 3,
'game_name' => 'Sample 1',
'game_type_id' => 2,
'game_category_id' => 1
}
},
'Optional' => Array{
0 => Array{
'id' => 4,
'game_name' => 'Sample 2',
'game_type_id' => 2,
'game_category_id' => 2
}
}
}
}
With L5, one way of acieving this can be:
GameProvider::with('game_categories.game_types')
->get()
->groupBy([
'gameTypes.name',
'gameCategories.name'
]);
Should be something along the lines of :
GameProvider::with('game_categories.game_types')->get()->map(function ($game) {
return $game->game_categories->groupBy('name')->map(function ($catGame) {
return $catGame->map(function ($category) {
return $category->game_types->groupBy('name');
});
});
});

Calculate values of deep nested multi dimensional arrays in reverse

I need to calculate reward for employees in my company.
I have this structure (for example):
And in PHP it represents the following array:
<?php
$structure = [
"A" => [
"B" => [
"E" => [
"M" => null
],
"F" => [
"N" => [
"T" => null
],
"O" => null
],
"G" => [
"P" => null,
"Q" => [
"U" => null,
"V" => [
"X" => null,
"Y" => [
"3" => [
"4" => [
"4" => null,
"6" => null,
"7" => [
"8" => null
],
]
]
],
"Z" => null
]
]
],
"H" => null
],
"C" => [
"I" => null,
"J" => [
"R" => null
],
"K" => [
"S" => [
"W" => [
"1" => null,
"2" => null,
]
]
]
],
"D" => [
"L" => null
]
]
];
I need to calculate the reward for each employee. The end subordinates have reward only from their own work. But the seniors who have other subordinates have reward from their own work + works their subordinates.
For example:
Person A has own reward 10.
Person D has own reward 20.
Person L has own reward 15.
In the final,
Person L has final reward 15 (is final).
Person D has final reward 20 + 15 = 35 (D + L).
Person A has final reward 10 + 35 (A + D).
The calculation must be carried out below, however, the network can be arbitrarily deep. Calculating I would like to split into several parts. (For performance reasons)
The spider, which revises the structure to the appropriate format.
Calculate reward for each node.
Send information about each node via email.
I do not know how to proceed across the structure. Or to reorganize the structure to undergo easier. Can you think of anything?
I am grateful for you. Thanks!
#Martin
// EDIT: raw database
| id | parent | name
---------------------
| 1 | null | Martin
| 2 | null | Peter
| 3 | 1 | John
| 4 | 3 | Jack
// EDIT: new data structure:
[
"A" => [
"points" => 20,
"childs" => [
"B" => [
"points" => 10,
"childs" => [
"C" => [
"points" => 50,
"childs" => null
]
]
],
"D" => [
"points" => 30,
"childs" => [
"4" => [
"points" => 40,
"childs" => null
]
]
]
]
]
]
This can be done by traversing through the structure recursively from the inside-out, and stores the rewards for each employee it can find in a flattened 2D array.
It is dependent on each leaf of the structure having a starting value, as it needs a base value to calculate back up the tree on.
Using RecursiveIteratorIterator with the CHILD_FIRST flag allows you to loop through the array 'backwards' which is what we want in this case as that is where the starting rewards are. As we go through the tree, we obtain the subordinates rewards, add it to the current employee, and continue .. so by the time we get back to the top of the structure, we have calculated all employees.
Storing the result in a flattened array is then much easier to use and manipulate further along your logic.
Assumed starting structure:
// Each leaf has a value (random for example sake)
$structure = [
"A" => [
"B" => [
"E" => [
"M" => 10
],
"F" => [
"N" => [
"T" => 15
],
"O" => 5
],
"G" => [
"P" => 40,
"Q" => [
"U" => 30,
"V" => [
"X" => 35,
"Y" => [
"3" => [
"4" => [
"5" => 5,
"6" => 10,
"7" => [
"8" => 20
],
]
]
],
"Z" => 30
]
]
],
"H" => 15
],
"C" => [
"I" => 25,
"J" => [
"R" => 25
],
"K" => [
"S" => [
"W" => [
"1" => 40,
"2" => 50,
]
]
]
],
"D" => [
"L" => 15
]
]
];
Calculation:
// Iterate through the structure from the outside-in (child/leaves first)
$data = new RecursiveArrayIterator($structure);
$dataIt = new RecursiveIteratorIterator($data, RecursiveIteratorIterator::CHILD_FIRST);
$rewards = [];
foreach ($dataIt as $value) {
$subKeys = [];
$rewards[$dataIt->key()] ?? $rewards[$dataIt->key()] = 0; // Suppress any undefined index errors
if (is_array($value)) {
$subKeys = array_keys($value);
// traverse through all branches to obtain the existing reward values for subordinates
array_walk_recursive($value, function($reward, $person) use (&$subKeys) {
$subKeys[] = $person;
});
$subKeys = array_unique($subKeys);
foreach ($subKeys as $employee) {
$rewards[$dataIt->key()] += $rewards[$employee];
}
} else {
$rewards[$dataIt->key()] += $value;
}
}
print_r($rewards);
Return/final array:
Array
(
[M] => 10
[E] => 10
[T] => 15
[N] => 15
[O] => 5
[F] => 35
[P] => 40
[U] => 30
[X] => 35
[4] => 60
[6] => 10
[8] => 20
[7] => 20
[3] => 90
[Y] => 180
[Z] => 30
[V] => 335
[Q] => 520
[G] => 745
[H] => 15
[B] => 1060
[I] => 25
[R] => 25
[J] => 25
[1] => 40
[2] => 50
[W] => 90
[S] => 180
[K] => 270
[C] => 435
[L] => 15
[D] => 15
[A] => 1935
)
To get the number of employees under each node, you can do that easily in one line using count:
$employeesPersonA = count($structure['A'], COUNT_RECURSIVE); // 33
$employeesPersonC = count($structure['A']['C'], COUNT_RECURSIVE); // 8
$employeesPersonK = count($structure['A']['C']['K'], COUNT_RECURSIVE); // 4
EDIT:
For your database structure, you cannot get a multi dimensional result set out of the database so your only option there is to go through the result set and build your structure in PHP from that.
Here's one example, simplified to consider only nodes, 'A','B','C',D', & 'L'...
DROP TABLE IF EXISTS my_table;
CREATE TABLE my_table
(user CHAR(1) NOT NULL PRIMARY KEY
,lft INT NOT NULL
,rgt INT NOT NULL
);
INSERT INTO my_table VALUES
('A',1,10),
('B',2,3),
('C',4,5),
('D',6,9),
('L',7,8);
SELECT y.user
, GROUP_CONCAT(x.user ORDER BY x.lft) nodes
FROM my_table x
JOIN my_table y
ON x.lft BETWEEN y.lft AND y.rgt
GROUP
BY y.user
ORDER
BY y.lft;
+------+-----------+
| user | nodes |
+------+-----------+
| A | A,B,C,D,L |
| B | B |
| C | C |
| D | D,L |
| L | L |
+------+-----------+

mysql pattern select based on values

I cant think up some good solution. My database structure is:
id name
----------
9 beer
10 beer {cold}
11 beer {hot}
12 juice
13 juice {orange}
14 juice {green}
15 juice {black}
I need select so possible output to have:
- one row of main product
- and all same products containing any text between characters {%} will be an alternatives of main product.
I need this output: (2 rows with alternatives -- but no 7 rows)
array(
array(
id => 9,
name => beer,
alternatives => array (
array(
id => 10,
name => cold
),
array(
id => 11,
name => hot
)
)
),
array(
id => 12,
name => juice
alternatives => array (
array(
id => 13,
name => orange
),
array(
id => 14,
name => green
),
array(
id => 15,
name => black
)
)
)
);
Do you think, is this possible with ONE Query?

MySQL entries with self parent_id into sorted cascade array

I need help with an application I'm creating.
I have a table, looks kind like this:
+----+-----------+---------+
| id | parent_id | name |
+----+-----------+---------+
| 1 | null | test |
+----+-----------+---------+
| 2 | null | test2 |
+----+-----------+---------+
| 4 | 1 | test3 |
+----+-----------+---------+
| 5 | 2 | test4 |
+----+-----------+---------+
And now, I get all the data in one array. I would like to get kinda this structure (php array as an cascade):
array(
0 => array(
'id' => 1,
'parent_id' => null,
'name' => 'test',
'children' => array(
'id' => 4,
'parent_id' => 1,
'name' => 'test3'
)
),
1 => array(
'id' => 2,
'parent_id' => null,
'name' => 'test2',
'children' => array(
'id' => 5,
'parent_id' => 2,
'name' => 'test4'
)
)
)
So there will every entry with a "parent_id=null" be a parent, and every entry with an id in "parent_id" will be in a child array.
I started it like this:
$newArray = array();
foreach($names as $name) {
if($name['parent_id'] == null || $name['parent_id'] == 0) {
// entry is parent
$newArray[$name['id']] = $name['name'];
} else {
// entry is child
}
}
But here is also my end, I don't know how to do that. I think i have to use some kind of recursive loop function, but I don't know how to start.
Would be awesome if somebody could help me.
Kind regards,
Matt.
You can use a recursive function like this (I only added the code which is relevant for understanding):
function get_children($parentId) {
$array = array();
//Load/Find $children
foreach($children as $child) {
$array[] = array(
'id' => $child->id,
'name' => 'YourName',
'children' => get_children($child->id)
);
}
return $array;
}
If you save the data in such an array, it isn't necessary to save the parent_id, because you can get it by searching for the parent elements id.

How to build a "child-parent" tree/nested array from database?

TABLE `people`
+----+------------+-------+
| sn | name | upper |
+----+------------+-------+
| 1 | Clement | 0 |
| 2 | Jean | 1 |
| 3 | Annie | 1 |
| 4 | Yuan | 2 |
| 5 | Mei | 2 |
| 6 | Blue | 3 |
| 7 | Yang | 5 |
| 8 | Lorinda | 0 |
+----+------------+-------+
The structure is like:
Clement
Jean
Yuan
Mei
Yang
Annie
Blue
Lorinda
The column upper states the upper person of himself/herself.
The problem is: How can I get a nested/multi-dimensional array from MySQL?
I thought I could use loops to fetch, but I failed to automated fetch all the lowers.
The array could be like this:
Array
(
[1]=>Array
(
[self]=>Clement
[2]=>Array
(
[self]=>Jean
[4]=>Array
(
[self]=>Yuan
)
[5]=>Array
(
[self]=>Mei
[7]=>Array
(
[self]=>Yang
)
)
)
[3]=>Array
(
[self]=>Annie
[6]=>Array
(
[self]=>Blue
)
)
)
[8]=>Array
(
[self]=>Lorinda
)
)
Since we don't know how many 'upper' persons does one have, the solution should be an automated function that build a complete array, not just for three or four dimension.
In other word, the function should deep into all the lower person from a top person.
Given your input as:
$input = array(
array('sn' => 1, 'name' => 'Clement', 'upper' => 0),
array('sn' => 2, 'name' => 'Jean', 'upper' => 1),
array('sn' => 3, 'name' => 'Annie', 'upper' => 1),
array('sn' => 4, 'name' => 'Yuan', 'upper' => 2),
array('sn' => 5, 'name' => 'Mei', 'upper' => 2),
array('sn' => 6, 'name' => 'Blue', 'upper' => 3),
array('sn' => 7, 'name' => 'Yang', 'upper' => 5),
array('sn' => 8, 'name' => 'Lorinda', 'upper' => 0),
);
using references you can build a tree with the following loop:
$map = array();
foreach ($input as $node) {
// init self
if (!array_key_exists($node['sn'], $map)) {
$map[$node['sn']] = array('self' => $node['name']);
}
else {
$map[$node['sn']]['self'] = $node['name'];
}
// init parent
if (!array_key_exists($node['upper'], $map)) {
$map[$node['upper']] = array();
}
// add to parent
$map[$node['upper']][$node['sn']] = & $map[$node['sn']];
}
print_r($map[0]);
demo: http://3v4l.org/vuVPu
Assuming the data is like this
$data = array(
array(1, 'Clement', 0),
array(2, 'Jean ', 1),
array(3, 'Annie ', 1),
array(4, 'Yuan ', 2),
array(5, 'Mei ', 2),
array(6, 'Blue ', 3),
array(7, 'Yang ', 5),
array(8, 'Lorinda', 0),
);
this recursive function might work:
function tree($data, $node) {
foreach($data as $e)
if($e[2] == $node[0])
$node['children'] []= tree($data, $e);
return $node;
}
Use it like this:
$tree = tree($data, array(0));
print_r($tree);
using a reference map
$input = array(
array('sn' => 1, 'name' => 'Clement', 'upper' => 0),
array('sn' => 2, 'name' => 'Jean', 'upper' => 1),
array('sn' => 3, 'name' => 'Annie', 'upper' => 1),
array('sn' => 4, 'name' => 'Yuan', 'upper' => 2),
array('sn' => 5, 'name' => 'Mei', 'upper' => 2),
array('sn' => 6, 'name' => 'Blue', 'upper' => 3),
array('sn' => 7, 'name' => 'Yang', 'upper' => 5),
array('sn' => 8, 'name' => 'Lorinda', 'upper' => 0),
);
$map = []; // map a reference by id for each item
foreach ($input as &$inp) {
$map[$inp['sn']] = &$inp;
}
foreach ($map as &$_inp) { // assign each item to its parent, with help of the map
if ($_inp['upper']) {
$map[$_inp['upper']]['children'] = &$map[$_inp['upper']]['children'] ?? [];
$map[$_inp['upper']]['children'][] = &$_inp;
}
}
$result = array_filter($map, fn($item) => !$item['upper']);
print_r($result);```

Categories