Array tree with parent strings - php

I have an array list with multiple filenames:
Array (
[0] - FILENAME_01.txt,
[1] - FILENAME_02.txt,
[2] - FILENAME_03.txt,
[3] - FILENAME_03_REJ.txt,
[4] - FILENAME_03_REJ_REJ.txt,
[5] - FILENAME_02_REJ.txt
)
At times a file may contain others. Example is the FILENAME_03 which contains FILENAME_03_REJ and another level FILENAME_03_REJ_REJ. I need to display them hierarchically, not looking for "REJ" as it is variable, but if one contains the other. The end result should be:
Array (
[0] - "filename" => FILENAME_01.txt, "parent_id" => 0,
[1] - "filename" => FILENAME_02.txt, "parent_id" => 0,
[2] - "filename" => FILENAME_03.txt, "parent_id" => 0,
[3] - "filename" => FILENAME_03_REJ.txt, "parent_id" => 2,
[4] - "filename" => FILENAME_03_REJ_REJ.txt, "parent_id" => 3,
[5] - "filename" => FILENAME_02_REJ.txt, "parent_id" => 1,
)
The problem should be solved in PHP.

The solution is below:
<?php
$itens = array(
array('filename' => 'FILENAME_01.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_02.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_03.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_03_REJ.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_03_REJ_REJ.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_02_REJ.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_03_REJ_REJ_REJ.txt', 'parent_id' => 0),
);
foreach($itens as $key1 => $value1){
$string_replace = str_replace(".txt", "", $value1['filename']);
foreach($itens as $key => $value){
if((preg_match('/'.$string_replace.'/',$value['filename'])) AND ($value['filename'] != $string_replace . '.txt')){
$itens[$key]['parent_id'] = $key1;
}
}
}
print '<pre>';
print_r($itens);
print '</pre>';
?>

Related

Creating multidimensional array and removing empty categories

I have a single-dimensional array of categories; some of which are children of other categories, and some of which contain 'elements'. I need to turn it into a multidimensional array, and remove any categories which have no elements in them or any of their children (or children's children...).
I have the following array:
$category_array = array(
1 => array(
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
),
4 => array(
'elementcount' => 0,
'parentcat' => 1,
'depth' => 2
),
8 => array(
'elementcount' => 0,
'parentcat' => 4,
'depth' => 3
),
9 => array(
'elementcount' => 2,
'parentcat' => 4,
'depth' => 3
),
11 => array(
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
),
12 => array(
'elementcount' => 0,
'parentcat' => 11,
'depth' => 2
),
21 => array(
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
)
);
and I need the following array:
$multidimensional_array = array(
1 => array(
'elementcount' => 3,
'children' => array(
4 => array(
'elementcount' => 0,
'children' => array(
9 => array(
'elementcount' => 2
)
)
)
)
),
11 => array(
'elementcount' => 3,
),
21 => array(
'elementcount' => 3,
)
);
How can this be achieved?
This would be an approach:
<?php
$input = [
1 => [
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
],
4 => [
'elementcount' => 0,
'parentcat' => 1,
'depth' => 2
],
8 => [
'elementcount' => 0,
'parentcat' => 4,
'depth' => 3
],
9 => [
'elementcount' => 2,
'parentcat' => 4,
'depth' => 3
],
11 => [
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
],
12 => [
'elementcount' => 0,
'parentcat' => 11,
'depth' => 2
],
21 => [
'elementcount' => 3,
'parentcat' => 0,
'depth' => 1
]
];
$maxDepth = max(array_column($input, 'depth'));
// handle elements from higher to lower depth
for ($d = $maxDepth; $d >= 0; $d--) {
array_walk($input, function(&$entry, $index) use (&$input, $d) {
if (isset($entry['depth']) && $entry['depth'] == $d) {
// omit entries without elements or elements in children
if ($entry['elementcount'] < 1 && empty($entry['children'])) {
unset($input[$index]);
// handle as child entry of a parent entry
} else if (array_key_exists($entry['parentcat'], $input)) {
$input[$entry['parentcat']]['children'][$index] = [
'elementcount' => $entry['elementcount'],
'children' => isset($entry['children']) ? $entry['children'] : []
];
unset($input[$index]);
// handle as ordinary entry
} else {
$input[$index] = [
'elementcount' => $entry['elementcount'],
'children' => isset($entry['children']) ? $entry['children'] : []
];
}
}
});
}
print_r($input);
The strategy:
handle higher depths first so that the order of input elements does not matter
for each element check if the parent exists, if so stuff it in there
redefine all handled elements
The obvious output is:
(
[1] => Array
(
[elementcount] => 3
[children] => Array
(
[4] => Array
(
[elementcount] => 0
[children] => Array
(
[9] => Array
(
[elementcount] => 2
[children] => Array
(
)
)
)
)
)
)
[11] => Array
(
[elementcount] => 3
[children] => Array
(
)
)
[21] => Array
(
[elementcount] => 3
[children] => Array
(
)
)
)
I took the liberty to create a slightly modified result compared to your suggestion:
The 'children' property always exists as an array. That makes the usage of the result easier and more robust later on. I'd say that in general all elements inside a structure should have identical structure themselves if possible ...
function isParent($id, $list) : bool {
foreach($list as $item) {
if ($item['parentcat'] === $id) {
return true;
}
}
return false;
}
function buildLevel($parent, $catsByParent) : array {
$result = $catsByParent[$parent] ?? [];
foreach($result as $id => $cat) {
if (isset($catsByParent[$id])) {
$result[$id]['children'] = buildLevel($id, $catsByParent);
unset($catsByParent[$id]);
}
}
return $result;
}
// Filter out empty categories
$cats = array_filter(
$category_array,
function($cat, $id) use($category_array) {
return $cat['elementcount']>0 || isParent($id, $category_array);
},
ARRAY_FILTER_USE_BOTH
);
$catsByParent = [];
// Build cats list keyed by parentcat
foreach($cats as $id => $cat) {
$parent = $cat['parentcat'];
unset($cat['parentcat'], $cat['depth']);
$catsByParent[$parent] = ($catsByParent[$parent] ?? []) + [$id => $cat];
}
// Build result
$multidimensional_array = buildLevel(0, $catsByParent);
print_r($multidimensional_array);
First one filters out empty elements, i.e "categories which have no elements in them or any of their children". (children's children requirement sounds strange, wouldn't that be "any of their children" one level further down?)
Then the remaining categories are grouped/sorted by parentcat, aka "level id", to make the list workable :).
Then that list is traversed, starting with level id 0 at the top, and recursively processed(the children) as deep down as needed.

Print parent child category path in "/" format like "parentCatId/childCatId/subChildCatId/so on"

I want to print category path in "/" format like "parentCatId/childCatId/subChildCatId/so on".
I want to print all parent child categoryId in single path format.
$cats = array(
array("catId" => 1, "parentId" => 0),
array("catId" => 2, "parentId" => 1),
array("catId" => 3, "parentId" => 1),
array("catId" => 4, "parentId" => 2),
array("catId" => 5, "parentId" => 2),
array("catId" => 6, "parentId" => 3),
array("catId" => 7, "parentId" => 3),
array("catId" => 8, "parentId" => 4),
);
function getCatIdByParent($catArr, $catId, $catPath="/") {
$parentCatId = $catArr[$catId]['parentId'];
if($parentCatId == 0) {
$catPath .= $catId."/";
return $catPath;
} else {
$catPath .= $catId."/";
$a = getCatIdByParent($catArr, $parentCatId, $catPath);
}
return $a;
}
foreach($cats as $catInfo) {
$catArr[$catInfo['catId']] = $catInfo;
}
foreach($catArr as $catId => $catInfo) {
$catArr2[] = getCatIdByParent($catArr, $catId);
}
This gives out :
Array
(
[0] => /1/
[1] => /2/1/
[2] => /3/1/
[3] => /4/2/1/
[4] => /5/2/1/
[5] => /6/3/1/
[6] => /7/3/1/
[7] => /8/4/2/1/
)
But I want to bellow output:
Array
(
[0] => /5/2/1/
[1] => /6/3/1/
[2] => /7/3/1/
[3] => /8/4/2/1/
)
You will have to make the 'rootParents' (0,1,2,3) as seperate indexes, with their id being the key, you then can use the 'outter' array keys as the rootParentID's and you get the wanted result.
Example:
$catsParents[0] = array(
array("catId" => 1, "parentId" => 0),
array("catId" => 2, "parentId" => 1),
);
$catsParents[1] = array (etc
foreach($catParents as $parent){
$parentId = key($parent);
// do your magic
}

Merging arrays based on a value of the key [duplicate]

This question already has answers here:
Merge arrays of associative arrays by shared column values [duplicate]
(3 answers)
Closed 5 months ago.
I have two arrays of arrays that have an id key, and I'd like to merge the data together based on that array's key and key value. The data would look something like:
$color = [
['id' => 1, 'color' => 'red'],
['id' => 2, 'color' => 'green'],
['id' => 3, 'color' => 'blue'],
];
$size = [
['id' => 1, 'size' => 'SM'],
['id' => 2, 'size' => 'XL'],
['id' => 3, 'size' => 'MD'],
['id' => 4, 'size' => 'LG'],
];
$combined = [
['id' => 1, 'color' => 'red', 'size' => 'SM'],
['id' => 2, 'color' => 'green', 'size' => 'XL'],
['id' => 3, 'color' => 'blue', 'size' => 'MD'],
['id' => 4, 'size' => 'LG'],
];
Is there a particularly efficient function or trick for handling something like this? Or should I just loop through the elements of one array and push the contents to the other?
I'm also using Laravel, and the data is a result of an eloquent query, so I can also utilize the collections if it would make the code cleaner.
Use array_replace_recursive function for easy and fast way
array_replace_recursive($color, $size)
Pure php solution is to use array_replace_recursive like this:
array_replace_recursive(
array_combine(array_column($color, "id"), $color),
array_combine(array_column($size, "id"), $size)
);
You should notice that array_replace_recursive merge arrays by keys.
So, if you get such data from database:
$color = [
['id' => 1, 'color' => 'red'],
['id' => 2, 'color' => 'red']
];
$size = [
['id' => 2, 'size' => 'SM']
];
array_replace_recursive will return corrupted merge:
$combined = [
['id' => 2, 'color' => 'red', 'size' => 'SM'],
['id' => 2, 'color' => 'red']
];
The solution is to combine array_replace_recursive with array_column and array_combine for merging arrays by their's id field:
array_replace_recursive(
array_combine(array_column($color, "id"), $color),
array_combine(array_column($size, "id"), $size)
);
array_combine(array_column($color, "id"), $color) creates associative array with id as keys.
So, in your case it will return:
$combined = [
1 => ['id' => 1, 'color' => 'red', 'size' => 'SM'],
2 => ['id' => 2, 'color' => 'green', 'size' => 'XL'],
3 => ['id' => 3, 'color' => 'blue', 'size' => 'MD'],
4 => ['id' => 4, 'size' => 'LG'],
];
You can use array_replace_recursive to merge the arrays in your particular situation.
$color = array(
array('id' => 1, 'color' => 'red'),
array('id' => 2, 'color' => 'green'),
array('id' => 3, 'color' => 'blue'),
);
$size = array(
array('id' => 1, 'size' => 'SM'),
array('id' => 2, 'size' => 'XL'),
array('id' => 3, 'size' => 'MD'),
array('id' => 4, 'size' => 'LG'),
);
$merged = array_replace_recursive($color, $size);
Output:
array(4) {
[0]=>
array(3) {
["id"]=>
int(1)
["color"]=>
string(3) "red"
["size"]=>
string(2) "SM"
}
[1]=>
array(3) {
["id"]=>
int(2)
["color"]=>
string(5) "green"
["size"]=>
string(2) "XL"
}
[2]=>
array(3) {
["id"]=>
int(3)
["color"]=>
string(4) "blue"
["size"]=>
string(2) "MD"
}
[3]=>
array(2) {
["id"]=>
int(4)
["size"]=>
string(2) "LG"
}
}
Note: I used the traditional array layout because my PHP version won't support the new one yet :)
Second option
You can also use array_map. This will let you add as much arrays as you want with a little tweaking.
$merged = array_map(function ($c, $s) {
return array_merge($c, $s);
}, $color, $size);
var_dump($merged); // See output above
I'd suggest using laravel's collections, since this question has the laravel tag.
$color = collect(
['id' => 1, 'color' => 'red'],
['id' => 2, 'color' => 'green'],
['id' => 3, 'color' => 'blue']
);
$size = collect(
['id' => 1, 'size' => 'SM'],
['id' => 2, 'size' => 'XL'],
['id' => 3, 'size' => 'MD'],
['id' => 4, 'size' => 'LG']
);
$combined = $color->merge($size);
Folllow this:
array_replace_recursive() is recursive : it will recurse into arrays and apply the same process to the inner value.
$combined = array_replace_recursive($color, $size);
then you can print to see the result as bellow:
print_r($combined);
Simple nested loop would solve the purpose.
foreach($size as $key => $value1) {
foreach($color as $value2) {
if($value1['id'] === $value2['id']){
$size[$key]['color'] = $value2['color'];
}
}
}
echo '<pre>';
print_r($size);
Output:
Array
(
[0] => Array
(
[id] => 1
[size] => SM
[title] => red
)
[1] => Array
(
[id] => 2
[size] => XL
[title] => green
)
[2] => Array
(
[id] => 3
[size] => MD
[title] => blue
)
[3] => Array
(
[id] => 4
[size] => LG
)
)
You need to traverse all elements from both arrays one by one and merge any duplicate elements. To do this you need to perform these steps.
Glue both arrays together.
$arr = [
['id' => 1, 'color' => 'red'],
['id' => 2, 'color' => 'green'],
['id' => 3, 'color' => 'blue'],
['id' => 1, 'size' => 'SM'],
['id' => 2, 'size' => 'XL'],
['id' => 4, 'size' => 'LG'],
['id' => 3, 'size' => 'MD'],
];
Loop the combined array copying the items into a new array.
If the ID has been seen before don't append the row, but merge it with an existing one.
You can achieve all of this with a single foreach loop and array_merge. There is no need for recursive functions or nested loops.
// Loops on merged arrays and copied elements into a new array
foreach(array_merge($color, $size) as $el){
// If the element with this id is already in new array then add the elements together
$merged[$el['id']] = ($merged[$el['id']] ?? []) + $el;
}
The only downside is that you lose the original indexes, but it looks from your question that this was not important to you. If you want, you can reindex the array with
$merged = array_values($merged);
Live Demo
Try:
$out = array();
foreach ($size as $key => $value){
if(!isset($color[$key])) { $color[$key] = array(); }
$out[] = array_merge((array)$value,(array)$color[$key]);
}
Output:
Array
(
[0] => Array
(
[id] => 1
[size] => SM
[color] => red
)
[1] => Array
(
[id] => 2
[size] => XL
[color] => green
)
[2] => Array
(
[id] => 3
[size] => MD
[color] => blue
)
[3] => Array
(
[id] => 4
[size] => LG
)
)
Better way using loop. First calculate the max array the by count this number run a loop. It will work.

Extract a row by value from php array

This is my array:
Array (
[0] => Array ( [SocketID] => 1 [SocketName] => Name [SocketDecimal] => 0 [SocketHex] => 00 [SocketAtt] => 1 [Category] => 1 [Value] => 100 [Procentage] => 0 )
[1] => Array ( [SocketID] => 2 [SocketName] => Name2 [SocketDecimal] => 50 [SocketHex] => 32 [SocketAtt] => 1 [Category] => 1 [Value] => 800 [Procentage] => 0 )
[2] => Array ( [SocketID] => 3 [SocketName] => Name3 [SocketDecimal] => 100 [SocketHex] => 64 [SocketAtt] => 1 [Category] => 1 [Value] => 60 [Procentage] => 0 )
)
How can I extract a row by SocketDecimal?
For example: I want to extract row where SocketDecimal = 50 and make new an array only with that row.
foreach($array as $entry) {
if($entry['SocketDecimal'] == 50)
$newArr[] = $entry;
}
$newArr will contain the desired "row". Of course you can manipulate the if-statement depending on which "row" (I'd just call it array entry) you want to extract.
It's not the best way for big data! It's easy for deep multiarrays.
$arr = array(
array('socket_id'=>1,'name'=>'test1'),
array('socket_id'=>2,'name'=>'test2'),
array('socket_id'=>3,'name'=>'test3'),
array('socket_id'=>2,'name'=>'test4')
);
$newArr = array();
foreach($arr as $row){
foreach($row as $key=>$r){
if($key == 'socket_id' && $r==2)
$newArr[] = $row;
}
}
print_r($newArr);
$result = array();
foreach($input as $i){
if($i['SocketDecimal']==50)
$result[]=$i;
}
You can do it by this method
foreach ($yourarray as $key => $value){
$newarray = array("SocketDecimal"=>$value["SocketDecimal"];
}
print_r($newarray);
If your result array is like given below
$arr = array(
array( 'SocketID' => 1, 'SocketName' => 'Name', 'SocketDecimal' => 0, 'SocketHex' => 0, 'SocketAtt' => 1, 'Category' => 1, 'Value' => 100, 'Procentage' => 0 ),
array ( 'SocketID' => 2, 'SocketName' => 'Name2', 'SocketDecimal' => 50, 'SocketHex' => 32, 'SocketAtt' => 1, 'Category' => 1, 'Value' => 800, 'Procentage' => 0 ),
array ( 'SocketID' => 3, 'SocketName' => 'Name3', 'SocketDecimal' => 100, 'SocketHex' => 64, 'SocketAtt' => 1, 'Category' => 1, 'Value' => 60, 'Procentage' => 0 )
);
print_r($arr);
Get row for SocketDecimal=50 by following loop:
<pre>
$resultArr = '';
foreach($arr as $recordSet)
{
if($recordSet['SocketDecimal'] == 50)
{
$resultArr[] = $recordSet;
break;
}
}
</pre>
print_r($resultArr);
break foreach loop so that it will not traverse for all the array when SocketDecimal(50) founded.
You can use array_column + array_search combo
$array = Array (
"0" => Array ( "SocketID" => 1, "SocketName" => "Name", "SocketDecimal" => 0, "SocketHex" => 00, "SocketAtt" => 1, "Category" => 1, "Value" => 100, "Procentage" => 0 ) ,
"1" => Array ( "SocketID" => 2, "SocketName" => "Name2", "SocketDecimal" => 50, "SocketHex" => 32, "SocketAtt" => 1, "Category" => 1, "Value" => 800, "Procentage" => 0 ),
"2" => Array ( "SocketID" => 3, "SocketName" => "Name3", "SocketDecimal" => 100, "SocketHex" => 64, "SocketAtt" => 1, "Category" => 1, "Value" => 60 ,"Procentage" => 0 )
);
var_dump($array[array_search(50,array_column($array,'SocketDecimal'))]);

How to get a tree-like array from another array?

With a table on my database that stores items of a menu, where every item has an ID, a NAME, and a FATHER ID, I need to arrange it and get a tree-like structure of multiple levels. What I need is an array with the top level menus, then every element with his 'childs' array that contains the sub menus, and this sub menus with their 'childs' array containing their respective sub sub menus an so for. English is no my native language so bear with me :)
An example for better understanding.
I have the following menu in array form:
1- System
2- Profile
3- Account
4- Info
5- Security
6- Logout
With the following array:
$array = array(
array('id' => 1, 'item'=>'System', 'id_father' => null),
array('id' => 2, 'item'=>'Profile', 'id_father' => 1),
array('id' => 3, 'item'=>'Account', 'id_father' => 2),
array('id' => 4, 'item'=>'Info', 'id_father' => 3),
array('id' => 5, 'item'=>'Security', 'id_father' => 3),
array('id' => 6, 'item'=>'Logout', 'id_father' => 1)
);
How can I get the following ? :
array(
array('id' => 1, 'item'=>'System', 'id_father' => null,
'childs' => array(
array('id' => 2, 'item'=>'Profile', 'id_father' => 1),
array('id' => 3, 'item'=>'Account', 'id_father' => 2,
'childs' => array(
array('id' => 4, 'item'=>'Info', 'id_father' => 3),
array('id' => 5, 'item'=>'Security', 'id_father' => 3)
),
),
),
),
array('id' => 6, 'item'=>'Logout', 'id_father' => 1)
);
Change $array to :
$array = array(
array('id' => 1, 'item'=>'System', 'id_father' => null),
array('id' => 2, 'item'=>'Profile', 'id_father' => 1),
array('id' => 3, 'item'=>'Account', 'id_father' => 1), // set id_father = 1
array('id' => 4, 'item'=>'Info', 'id_father' => 3),
array('id' => 5, 'item'=>'Security', 'id_father' => 3),
array('id' => 6, 'item'=>'Logout', 'id_father' => null) // edited to set id_father = null
);
Do it:
function tree( $ar, $pid = null ) {
$op = array();
foreach( $ar as $item ) {
if( $item['id_father'] == $pid ) {
$op[$item['id']] = array(
'item' => $item['item'],
'id_father' => $item['id_father'],
'id' => $item['id']
);
// using recursion
$children = tree( $ar, $item['id'] );
if( $children ) {
$op[$item['id']]['childs'] = $children;
}
}
}
return $op;
}
$tree = tree($array);
echo '<pre>';
print_r( $tree);
echo '</pre>';
// OUTPUT
Array
(
[1] => Array
(
[item] => System
[id_father] =>
[id] => 1
[childs] => Array
(
[2] => Array
(
[item] => Profile
[id_father] => 1
[id] => 2
)
[3] => Array
(
[item] => Account
[id_father] => 1
[id] => 3
[childs] => Array
(
[4] => Array
(
[item] => Info
[id_father] => 3
[id] => 4
)
[5] => Array
(
[item] => Security
[id_father] => 3
[id] => 5
)
)
)
)
)
[6] => Array
(
[item] => Logout
[id_father] =>
[id] => 6
)
)
There is no need to recursion in this way :
$pool = array();
foreach ($array as $value) {
$pool[$value['id']] = $value;
$pool[$value['id']]['children'] = array();
}
foreach ($pool as $k => $v) {
if ($v['id_father']) {
$pool[$v['id_father']]['children'][] = &$pool[$k];
}
else
$parent[] = $v['id'];
}
$result = array();
foreach ($parent as $val) {
$result = $result + $pool[$val];
}
print_r($result);

Categories