I want a function that
searches through my array, and
returns all the
children to a specific node. What is
the most appropriate way to do this?
Will recursion be necessary in this case?
I have previously constructed a few quite complex functions that iterates with or without the help of recursion through multi-dimensional arrays and re-arranging them, but this problem makes me completely stuck and I can't just get my head around it...
Here's my array:
Array
(
[1] => Array (
[id] => 1
[parent] => 0
)
[2] => Array (
[id] => 2
[parent] => 1
)
[3] => Array (
[id] => 3
[parent] => 2
)
)
UPDATE:
The output which I want to get. Sorry for the bad example, but I'll blame it on lack of knowledge on how to format the stuff I need to do :)
function getAllChildren($id) {
// Psuedocode
return $array;
}
getAllChildren(1); // Outputs the following:
Array
(
[2] => Array (
[id] => 2
[parent] => 1
)
[3] => Array (
[id] => 3
[parent] => 2
)
)
$nodes = array( 1 => array ( 'id' => 1,
'parent' => 0
),
2 => array ( 'id' => 2,
'parent' => 1
),
3 => array ( 'id' => 3,
'parent' => 2
)
);
function searchItem($needle,$haystack) {
$nodes = array();
foreach ($haystack as $key => $item) {
if ($item['parent'] == $needle) {
$nodes[$key] = $item;
$nodes = $nodes + searchItem($item['id'],$haystack);
}
}
return $nodes;
}
$result = searchItem('1',$nodes);
echo '<pre>';
var_dump($result);
echo '</pre>';
Non-recursive version of the searchItem() function:
function searchItem($needle,$haystack) {
$nodes = array();
foreach ($haystack as $key => $item) {
if (($item['parent'] == $needle) || array_key_exists($item['parent'],$nodes)) {
$nodes[$key] = $item;
}
}
return $nodes;
}
(assumes ordering of the parents/children, so a child node isn't included in the array unless the parent is already there)
<?php
function searchItem($needle)
{
foreach ($data as $key => $item)
{
if ($item['id'] == $needle)
{
return $key;
}
}
return null;
}
?>
Check out the array_walk_recursive() function in PHP:
http://www.php.net/manual/en/function.array-walk-recursive.php
Related
I am trying to sort an array to ensure that the parent of any item always exists before it in the array. For example:
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
)
)
In the above array this "fails" as [1][2] (Sam) does not yet exist and nor does [4][2] (Tom).
The correct output would be as, in this case, as both Sam and Tom's parents already exist before they appear in the array:
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
)
)
I found an answer https://stackoverflow.com/a/12961400/1278201 which was very close but it only seems to go one level deep (i.e. there is only ever one parent) whereas in my case there could be 1 or 10 levels deep in the hierarchy.
How do I sort the array so no value can appear unless its parent already exists before it?
This will trivially order the array (in O(n)) putting first all those with no parent, then these whose parent is already in the array, iteratively, until there's no children having the current element as parent.
# 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;
}
}
}
This code requires PHP 7, it may not work in some cases under PHP 5. - for PHP 5 compatibility you will have to swap the foreach ($sorted as &$val) with 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
I have two different version for you.
a) Using a "walk the tree" approach with recursion and references to minimize memory consumption
$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) Using phps built in uusort()function (seems only to work up to php 5.x and not with 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
}
I checked this works in PHP 5.6 and PHP 7
Sample array:
$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;
}
Find the below code that might be helpful.So, your output is stored in $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);
What about this approach:
Create an empty array result.
Loop over your array and only take the items out of it where [2] is empty and insert them into result.
When this Loop is done you use a foreach-Loop inside a while-loop. With the foreach-Loop you take every item out of your array where [2] is already part of result. And you do this as long as your array contains anything.
$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: You may run into trouble by removing parts of an array while walking over it. If you do so ... try to solve this :)
PPS: Think about a break condition if your array have an unsolved loop or a child without an parent.
you can use your array in variable $arr and use this code it will give you required output.
function check($a, $b) {
return ($a[0] == $b[2]) ? -1 : 1;
}
uasort($arr, 'check');
echo '<pre>';
print_r(array_values($arr));
echo '</pre>';
This question already has answers here:
Count specific values in multidimensional array
(4 answers)
Closed 9 years ago.
I'm looking for a way to count occurence on an array of array.
This is my array :
Array
(
[0] => Array
(
[id] => 671
[title] => BIEND
[img] =>
[ville] => marseille
)
[1] => Array
(
[id] => 670
[title] => BIENC
[img] =>
[ville] => avignon
)
[2] => Array
(
[id] => 669
[title] => BIENB
[img] =>
[ville] => avignon
)
)
And what I would like to have :
Array
(
[avignon] => 2
[marseille] => 1
)
I tried with array_count_values, but it dont seems to be the good way.
Any idea?
You could just go through it manually:
$result = array();
foreach($input as $item)
{
$result[$item['ville']]++;
}
or, slightly nicer perhaps,
$result = array();
foreach($input as $item)
{
$city = $item['ville'];
if(!array_key_exists($city, $result)) {
$result[$city] = 1;
} else {
$result[$city]++;
}
}
Alternatively, you could do some array_map magic to first get an array with all the cities, and then use array_count_values as you planned:
$cities = array_count_values( array_map( function($a) { return $a['ville']; } ) );
Note, I haven't tested this last solution, I personally think the first one expresses the intention better. If you would like to use this one because it is shorter (i.e. less readable) I'll leave it to you to debug and comment it
You can use array_reduce():
$data = Array
(
0 => Array
(
'id' => 671,
'title' => 'BIEND',
'img' => '',
'ville' => 'marseille'
)
,
1 => Array
(
'id' => 670,
'title' => 'BIENC',
'img' => '',
'ville' => 'avignon'
)
,
2 => Array
(
'id' => 669,
'title' => 'BIENB',
'img' => '',
'ville' => 'avignon'
)
);
$result = array_reduce($data, function(&$cur, $x)
{
$cur[$x['ville']] = isset($cur[$x['ville']])?$cur[$x['ville']]+1:1;
return $cur;
}, []);
$my_array = array(...);
$result = array();
foreach ($my_array as $arr) {
$key = $arr['ville'];
if (! array_key_exists($key, $result){
$result[$key] = 1;
continue;
}
$result[$key] += 1;
}
I would write something like this. Array and subArray should be renamed according to their content.
$villes = array();
foreach($yourArray as $subArray) {
if(!in_array($subArray['ville'], $villes)) {
$villes[$subArray['ville']] = 1;
} else {
$villes[$subArray['ville']]++;
}
}
var_dump($villes);
I have an array in the following format:
[8106] => Array (
[id1] => 210470
[id2] => 216298
)
[8107] => Array (
[id1] => 210470
[id2] => 187145
)
[8108] => Array (
[id1] => 187145
[id2] => 216298
)
[8109] => Array (
[id1] => 187145
[id2] => 210470
)
[8110] => Array (
[id1] => 266533
[id2] => 249612
)
[8111] => Array (
[id1] => 249612
[id2] => 266533
)
I need to get it into the following format:
[0] => Array (
[0] => 266533
[1] => 249612
)
[1] => Array (
[0] => 187145
[1] => 210470
[2] => 216298
)
Basically, I need to extract all the ids, keep the relationships, but group them all together. I have a function to do this, but it takes forever (I am up to 30+ minutes on the number of rows I have to run through). Keys and order are unimportant. The relationship is all that is important. I am looking for a faster method. The function(s) I am using are below:
function getMatchingIDs($filteredArray)
{
$result = array();
$resultCount = 0;
foreach ($filteredArray as $details) {
$imaId1 = inMultiArray($details['id1'], $result);
$imaId2 = inMultiArray($details['id2'], $result);
if ($imaId1 === false && $imaId2 === false) {
$result[$resultCount++] = array(
$details['id1'],
$details['id2'],
);
} elseif (is_numeric($imaId1) === true && $imaId2 === false) {
$result[$imaId1][] = $details['id2'];
} elseif ($imaId1 === false && is_numeric($imaId2) === true) {
$result[$imaId2][] = $details['id1'];
} elseif ($imaId2 != $imaId1) {
$result[$imaId1] = array_merge($result[$imaId1], $result[$imaId2]);
unset($result[$imaId2]);
}
}
return $result;
}
function inMultiArray($elem, $array)
{
if (is_array($array) === true) {
// if the variable $elem is in the variable $array return true
if (is_array($array) === true && in_array($elem, $array) === true) {
return true;
}
// if $elem isn't in $array, then check foreach element
foreach ($array as $key => $arrayElement) {
// if $arrayElement is an array call the inMultiArray function to this element
// if inMultiArray returns true, than return is in array, else check next element
if (is_array($arrayElement) === true) {
$value = inMultiArray($elem, $arrayElement);
if ($value === true) {
return $key;
}
}
}
}
// if isn't in array return false
return false;
}
$filtered = getMatchingIDs($unfiltered);
EDIT: The original array describes relations between pairs of ids (not shown in the array). The desired output is that the relations are further defined. If you look in the original array, elements 8106-8109 are simply paired combinations of three ids. I need those three grouped together. Elements 8110 and 8111 are a distinct pair, just in a different order.
$newArray = array();
foreach ($array as $k => $v) {
$newArray[0][] = $v['id1'];
$newArray[1][] = $v['id2'];
}
What I finally ended up doing was in essence creating an index array. This array held all the positions of each value in the primary array.
So the following array
[0] => Array (
[0] => 266533
[1] => 249612
)
[1] => Array (
[0] => 187145
[1] => 210470
[2] => 216298
)
has an index of:
[187145] => 1
[210470] => 1
[216298] => 1
[249612] => 0
[266533] => 0
So instead of looking for the value in the primary multidimensional array, I check to see if it exists in the index array and process the data based on that. The results are that it now runs the entire process in <5 seconds instead of > 1 hour.
Thank you for your help.
Array
(
[updateCategories] => Array
(
[products] => Array
(
[0] => Array
(
[cat_id] => 3
[position] => 2
[product_id] => 8
)
[1] => Array
(
[cat_id] => 4
[position] => 11
[product_id] => 8
)
[2] => Array
(
[cat_id] => 3
[position] => 4
[product_id] => 39
)
[3] => Array
(
[cat_id] => 4
[position] => 9
[product_id] => 8
)
[4] => Array
(
[cat_id] => 3
[position] => 6
[product_id] => 41
)
[5] => Array
(
[cat_id] => 11
[position] => 7
[product_id] => 8
)
The above array is my output array but I need to get all cat_id of product_id=8. How can I do this?
$newarr = array();
foreach( $arr['updateCategories']['products'] as $myarr)
{
if($myarr['product_id'] == 8)
$newarr[] = $myarr['cat_id'];
}
The simpliest solution is
$result = array();
foreach($yourArray['updateCategories']['products'] as $product)
if($product['product_id] == 8)
$product[] = $product['cat_id'];
where $yourArray is the array which dump you have published.
Try something like this:
$arr = array();
foreach ($products as $key => $value)
{
if($value['product_id'] == 8)
{
$arr[] = $key;
}
}
print_r($arr); // <-- this should output the array of products with key as 8
Use this
foreach($array['updateCategories']['products'] as $product) {
if(isset($product['product_id']) && $product['product_id']==8) {
//do anything you want i am echoing
echo $product['cat_id'];
}
}
You can use array_filter.
function filterProducts($product) {
return ($product['product_id'] == 8);
}
$myProducts = array_filter(
$myArray['updateCategories']['products'],
'filterProducts'
);
Where $myArray is the array displayed in your post.
Can handle this by doing something like this
$matching_products = array();
foreach ($products as $key => $value) {
if($value['product_id'] == 8) {
$matching_products[] = $value['cat_id'];
}
}
which'll leave you with an array of cat ids that have a product id of 8
This should be able to retrieve all of the cat_id's from a given product_id. This function yields an object that can be iterated over to retrieve all the values it contains.
<?PHP
public function GetCatIdsByProductId($productId)
{
foreach($updateCategories=>products as $key=>$product)
{
if (isset($product=>product_id) && $product=>product_id == 8)
{
yield $product=>cat_id;
}
}
}
//Usage
$catIds = GetCatIdsByProductId(8);
var_dump($catIds);
A more generic version of this function can be constructed to retrieve a given key from a comparison on a given property value.
public function GetPropertyByPropertyComparison(array $list, $propRet, $propCompare, $compValue)
{
foreach($list as $key=>$product)
{
if (isset($product=>{$propCompare}) && $product=>{$propCompare} == $compValue)
{
yield $product=>{$propRet};
}
}
}
//usage
$cats = GetPropertyByPropertyComparison($updateCategories=>products, "cat_id", "product_id", 8);
var_dump($cats);
?>
Let me explain my problem. I try to generate an array of categories.
Here is my function.
private function recursiveCategoriesTree($id_parent, $level, $categories)
{
$tree = array();
foreach($categories as $categorie)
{
if($id_parent == $categorie['id_parent'])
{
$tree[$level][] = $categorie;
$this->recursiveCategoriesTree($categorie['id_category'], ($level+1), $categories);
}
}
return $tree;
}
When I trace the loop with echo, everything seems to work, all the parent categories and girls are covered, but are not pushed into the array.
Here is the print_r of my categories
array(
[0] => Array
(
[id_category] => 4
[name] => Pièces détachées
[id_parent] => 1
)
[1] => Array
(
[id_category] => 5
[name] => Excavateur
[id_parent] => 4
)
[2] => Array
(
[id_category] => 6
[name] => série 100
[id_parent] => 5
)
[3] => Array
(
[id_category] => 7
[name] => above
[id_parent] => 6
)
[4] => Array
(
[id_category] => 8
[name] => système hydraulique
[id_parent] => 7
)
[5] => Array
(
[id_category] => 9
[name] => série 200
[id_parent] => 5
)
[6] => Array
(
[id_category] => 10
[name] => thru
[id_parent] => 6
)
[7] => Array
(
[id_category] => 11
[name] => Compaction
[id_parent] => 4
)
)
Here is the result of print_r generated
Array(
[0] => Array
(
[0] => Array
(
[id_category] => 5
[name] => Excavateur
[id_parent] => 4
)
[1] => Array
(
[id_category] => 11
[name] => Compaction
[id_parent] => 4
)
)
)
I call my function like that
$tree = $this->recursiveCategoriesTree(4, 0, $categories)
What is the problem ? thank you =)
Either get the return value from your recursive call and push that onto the array, or make a private property of the class called tree and push values onto that instead. You are not passing the variable $tree across recursive function calls.
E.g. if you do this it will work: (EDIT: Fixed... [again])
private $catTree = array();
private $catStartLevel = FALSE;
private function recursiveCategoriesTree($id_parent, $level, $categories) {
if ($this->catStartLevel !== FALSE) $this->catStartLevel = $level;
foreach($categories as $categorie) {
if($id_parent == $categorie['id_parent']) {
$this->catTree[$level][] = $categorie;
$this->recursiveCategoriesTree($categorie['id_category'], ($level+1), $categories);
}
}
if ($this->catStartLevel === $level) {
$tree = $this->catTree;
$this->catTree = array();
$this->catStartLevel = FALSE;
}
return $tree;
}
...however this is not great, because you now have a 'pointless' private property in your class. You would be better to change you array structure, and catch the return values from $this->recursiveCategoriesTree()...
EDIT
Thinking about it, if you really want the array in that structure, you would probably be better to pass the variable to be populated with the array by reference:
private function recursiveCategoriesTree($id_parent, $level, $categories, &$tree) {
foreach($categories as $categorie) {
if($id_parent == $categorie['id_parent']) {
$tree[$level][] = $categorie;
$this->recursiveCategoriesTree($categorie['id_category'], ($level+1), $categories, $tree);
}
}
}
...and then you would call it like this...
$myTree = array();
$obj->recursiveCategoriesTree($id_parent, $level, $categories, $myTree);
print_r($myTree);
recursiveCategoriesTree() returns the $tree, but you're not doing anything with that return value when you're calling the method recursively. You're only storing the $tree returned from the initial call to the method.
Perhaps you want something like this?
$categorie['children'] = $this->recursiveCategoriesTree($categorie['id_category'], ($level+1), $categories);
You should first fetch all the childs of a category, and add them to its array, before appending the category to the tree. Kind of like this:
foreach($categories as $categorie)
{
$categorie['childs'] = $this->recursiveCategoriesTree($categorie['id_category'], ($level+1), $categories);
$tree[$level][] = $categorie;
}