Count elements in adjacent array - php
I have an array that has a hierarchic structure. In this example there are two elements in the first level, 83 and 82. 82 has children 84 and 87 . 87 is a child of 84.
Array
(
[_children] => Array
(
[0] => Array
(
[id] => 11
[uid] => 24
[rid] => 83
[date] => 2011-06-18 15:08:10
)
[1] => Array
(
[id] => 10
[uid] => 24
[rid] => 82
[date] => 2011-06-18 15:08:07
[_children] => Array
(
[0] => Array
(
[id] => 12
[uid] => 82
[rid] => 84
[date] => 2011-06-18 15:30:34
[_children] => Array
(
[0] => Array
(
[id] => 13
[uid] => 84
[rid] => 87
[date] => 2011-06-18 16:11:50
)
)
)
)
)
)
)
I want to loop through that array and store some information for each level in another array. This is how my array should look like at the end.
Array
(
[0] => Array
(
[elements] => 2
)
[1] => Array
(
[elements] => 1
)
)
E.g. this shows that at level 0 are 3 nodes and at level 1 there are 5 nodes. How can I do that? Any ideas?
I would just count the numbers.
This means each item of the base key plus the same for it's children.
In opposite to recursion, I decided to use a stack for this.
To have a value per level, the counter needs a level context as well, which is added to the stack next to the node:
$count = array();
$stack[] = array(-1, $arr);
while($item = array_shift($stack)) {
list($level, $node) = $item;
$count[$level] = isset($count[$level]) ? $count[$level]+1 : 1;
if (!isset($node['_children']))
continue;
foreach($node['_children'] as $item)
$stack[] = array($level+1, $item);
}
var_dump($count);
This does output:
array(4) {
[-1]=> int(1)
[0] => int(2)
[1] => int(1)
[2] => int(1)
}
where -1 is the root node only containing children. So you might want to remove it after processing:
array_shift($count);
Edit: Storing the nodes
The following code adds a collector of nodes per level as well, called $nodes. It's a bit redundant as per count($nodes[$level]) is $count[$level], but it's just to show how to collect nodes as well:
$count = array();
$nodes = array(); // nodes per level go here.
$stack[] = array(-1, $arr);
while($item = array_shift($stack)) {
list($level, $node) = $item;
$count[$level] = isset($count[$level]) ? $count[$level]+1 : 1;
$nodes[$level][] = $node; // set here!
if (!isset($node['_children']))
continue;
foreach($node['_children'] as $item)
$stack[] = array($level+1, $item);
}
var_dump($count, $nodes);
Discussion
Using a stack prevents to make recursion function calls and reduces complexity. The idea behind it is fairly simple:
The stack is initialized with the first node (which only contains children) - the tree's root node.
An entry on the stack contains the node to be counted and it's level
Each element on the stack is treated the same:
An item gets removed from the beginning (or end) of the stack.
The counter for it's level is increased by one.
If it has children, each child-node is added to the stack with it's level.
The stack will be processed until all elements are consumed.
This ensures that every node will be counted with a minimal overhead. The strategy of consuming the stack can be fully controlled. Either FIFO (First In, First Out), LIFO (Last In, First Out) - which is easy to implement - or even a weighted strategy (first process child-nodes that have no/the most children to consume them fast) or even random stack consumption to distribute the constraints the data-structure implies accros the whole stack.
Comparing Stack to Recursion Function Stack
Comparing using an own stack to the PHP's function stack first of all shows that both ways have things in common. Both make use of a stack.
But the main difference is, that an own stack prevents using the function calls. Calling a function, especially recursively has multiple implications that can be avoided.
Another key difference is, that an own stack always allows to interrogate with it while there is no control over the PHP language's own stack.
Next to that, an own stack can be processed sequentially, while the function stack is a recursive construct that maps the processing of the data 1:1 into the program flow.
While recursion will hide away the recursive nature of the data (tree) for the code within the function (which can simplify processing), there is the need to offer the state that gets hidden away by function calls to be introduced as function parameters.
Managing the data to solve the problem therefore is not less but equal or more complex with recursion.
So next to the overhead of the function calls, one can already smell an overhead for data.
It's worth to see this in the light of the PHP language itself.
To make recursion work, PHP needs to add additional variable tables onto the function stack. These tables need to be managed before and after each function call.
The recursion also needs you to pass variables by references. This is an additional overhead, as those values need to be set/processed before and after the function ends by PHP.
Such a reference is needed in the recursion to share the data structure for the counter. This overhead in a recursive implementation could be reduced by implementing the recursion into a class of it's own in PHP, which has not been suggested so far and is probably worth to consider.
Like the counter array in a user stack implementation, such a class could share the counter between all function calls, providing a hash accessible to the class's member function(s) w/o making use of variable references. Further on, complexity in solving the problem can be distributed over different functions with in the class or even injected as a dependency. PHP offers a default recursive iterator interface already.
Another place to reduce one could make use of is the global variable table ($GLOBALS) but with the downside of making the code less re-useable which signals a design flaw. Even if design is not an issue, that so called superglobals array needs to be managed between function calls as as well. That results in a quite-like behavior to passing by reference for which it was considered to prevent it. Therefore it is not a preferable solution.
Recursive Implementation by Class
This is a recursive implementation based on the idea raised in the discussion above to get more grip on it:
/**
* class implementation of a recursive child counter
*
* Usage:
*
* Object use:
*
* $counter = new LevelChildCounter($arr, '_children');
* $count = $counter->countPerLevel();
*
* Functional use:
*
* $count = LevelChildCounter::count($arr, '_children');
*/
class LevelChildCounter {
private $tree;
private $childKey;
private $counter;
public function __construct(array $tree, $childKey) {
$this->tree = $tree;
$this->childKey = $childKey;
}
/**
* functional interface of countPerLevel
*/
public static function count(array $tree, $childKey) {
$counter = new self($tree, $childKey);
return $counter->countPerLevel();
}
private function countUp($level) {
isset($this->counter[$level])
? $this->counter[$level]++
: $this->counter[$level] = 1;
}
private function countNode($node, $level) {
// count node
$this->countUp($level);
// children to handle?
if (!isset($node[$this->childKey]))
return;
// recursively count child nodes
foreach($node[$this->childKey] as $childNode)
$this->countNode($childNode, $level+1)
;
}
/**
* Count all nodes per level
*
* #return array
*/
public function countPerLevel() {
$this->counter = array();
$this->countNode($this->tree, -1);
return $this->counter;
}
}
$count = LevelChildCounter::count($arr, '_children');
array_shift($count);
var_dump($count);
And it's output:
array(3) {
[0]=> int(2)
[1]=> int(1)
[2]=> int(1)
}
It seems that a recursive function is the easiest way to implement this.
The function should have as an argument the _children array and should keep the results, a "results" array and level counter.
The function should be called like getStructure($myarray['_children']), which calls the base function, _getStructure($array, &$results, $depth): $results is an empty array at first and $depth set to zero.
The function _getStructure then loops over the array (foreach) and increases the $results[$depth]++. If a child contains the _children field, this function calls _getStructure again, with an increased $detph++.
After _getStructure returns, the $results array is filled with your given structure. This is due to the Pass by Reference, which will pass the $results array to functions without copying it: all changes to it are seen by the scope which called it. See PHP docs.
I'm assuming you have a fixed depth, it would be quite simple to adapt it for unlimited depth:
<?php
function count_child( $node, &$results, $depth, $curr_depth = 0){
if( $curr_depth >= $depth)
return;
if( is_set( $results[$curr_depth] ))
$results[$curr_depth] += count( $node['_children'] );
else
$results[$curr_depth] = 1;
if( is_array( $node ) && is_set( $node['_children']) )
foreach( $node['_children'] as $next_node){
count_child( $next_node, $result , $depth , $curr_depth + 1);
}
}
?>
Related
array_walk not changing value
function values($id,$col) { $vals = [1=>['name'=>'Lifting Heavy Boxes']]; return $vals[$id][$col]; } $complete = [1=>["id"=>"2","sid"=>"35","material_completed"=>"1","date"=>"2017-12-18"]]; $form = 'my_form'; array_walk($complete, function(&$d,$k) use($form) { $k = values($k, 'name').' ['.date('m/d/y',strtotime($d['date'])).'] ('.$form.')'; echo 'in walk '.$k."\n"; }); print_r($complete); the echo outputs: in walk Lifting Heavy Boxes [12/18/17] (my_form) the print_r outputs: Array ( [1] => Array ( [id] => 2 [sid] => 35 [material_completed] => 1 [date] => 2017-12-18 ) ) I have another array walk that is very similar that is doing just fine. The only difference I can perceive between them is in the one that's working, the value $d is already a string before it goes through the walk, whereas in the one that's not working, $d is an array that is converted to a string inside the walk (successfully, but ultimately unsuccessfully). Something I'm missing? And here's the fixed version: array_walk($complete, function(&$d,$k) use($form) { $d = values($k, 'name').' ['.date('m/d/y',strtotime($d['date'])).'] ('.$form.')'; }); That's what I was trying to do anyway. I wasn't trying to change the key. I was under the mistaken impression that to change the value you had to set the key to the new value.
You cannot change the key of the array inside the callback of array_walk(): Only the values of the array may potentially be changed; its structure cannot be altered, i.e., the programmer cannot add, unset or reorder elements. If the callback does not respect this requirement, the behavior of this function is undefined, and unpredictable. This is also mentioned in the first comment: It's worth nothing that array_walk can not be used to change keys in the array. The function may be defined as (&$value, $key) but not (&$value, &$key). Even though PHP does not complain/warn, it does not modify the key.
Sorting multi-dimensional array by weighted value
There are numerous questions here asking how to sort a multi-dimensional array in PHP. The answer is usort(). I know that. But I have a question that takes it a bit further, and I couldn't see a similar answer here. I have an array of records, each of which includes a country ID (or a country name if you prefer; it's not relevant). My task is to sort the array in such a way as to favour certain countries. This is dynamic -- ie the choice of countries to favour is determined by the user's config. I have a separate array which specifies the required sort order for the first few countries; results from other countries would just be left unsorted at the end of the list. So the question is: how do I get the this sort criteria into usort() without resorting to using a global variable. And preferably without injecting the criteria array into every element of the main array ('coz if I'm going to loop it anyway, what's the point in using usort() at all?) Please note: Since it's going to be relevant to the answers here, I'm stuck on PHP 5.2 for the time being, so I can't use an anonymous function. We are in the process of upgrading, but for now I need answers that will work for 5.2. (answers for 5.3/5.4 will be welcome too, especially if they make it significantly easier, but I won't be able to use them)
You explicitly write that you do not want to have global variables, so I do not make you a suggestion with static variables as well because those are actually global variables - and those are not needed at all. In PHP 5.2 (and earlier) if you need call context within the callback, you can create your context by making use of a class of it's own that carries it: class CallContext { } For example you have the compare function for sort: class CallContext { ... public function compare($a, $b) { return $this->weight($a) - $this->weight($b); } public function getCallback() { return array($this, 'compare'); } ... } That function can be used as the following as a callback with usort then: $context = new CallContext(); usort($array, $context->getCallback()); Pretty straight forward. The private implementation of CallContext::weight is still missing, and from your question we know it needs some sort data and information. For example the name of the key of the country id in each record. Lets assume records are Stdclass objects so to get the weight of one record the context class needs to know the name of the property, the sort-order you define your own and a sort-value for those country-ids that are not defined in the custom sort order (the others, the rest). These configuration values are given with the constructor function (ctor in short) and are stored as private members. The missing weight function then converts a record into the sort-value based on that information: class CallContext { private $property, $sortOrder, $sortOther; public function __construct($property, $sortOrder, $sortOther = 9999) { $this->property = $property; $this->sortOrder = $sortOrder; $this->sortOther = $sortOther; } private function weight($object) { if (!is_object($object)) { throw new InvalidArgumentException(sprintf('Not an object: %s.', print_r($object, 1))); } if (!isset($object->{$this->property})) { throw new InvalidArgumentException(sprintf('Property "%s" not found in object: %s.', $this->property, print_r($object, 1))); } $value = $object->{$this->property}; return isset($this->sortOrder[$value]) ? $this->sortOrder[$value] : $this->sortOther; } ... The usage now extends to the following: $property = 'country'; $order = array( # country ID => sort key (lower is first) 46 => 1, 45 => 2 ); $context = new CallContext('country', $order); usort($array, $context->getCallback()); With the same principle you can very often convert any PHP 5.3 closure with use clauses to PHP 5.2. The variables from the use clause become private members injected with construction. This variant does not only prevent the usage of static, it also makes visible that you have got some mapping per each element and as both elements are treated equal, it makes use of a private implementation of some weight function which works very well with usort. I hope this is helpful.
You might not want a global variable, but you need something that behaves like one. You could use a class with static methods and parameters. It won't pollute the global scope that much and it would still function the way you need it. class CountryCompare { public static $country_priorities; public static function compare( $a, $b ) { // Some custom sorting criteria // Work with self::country_priorities } public static function sort( $countries ) { return usort( $countries, array( 'CountryCompare', 'compare' ) ); } } Then just call it like this: CountryCompare::country_priorities = loadFromConfig(); CountryCompare::sort( $countries );
You can use closures (PHP >= 5.3): $weights = array( ... ); usort($records, function($a, $b) use ($weights) { // use $weights in here as usual and perform your sort logic });
See Demo : http://codepad.org/vDI2k4n6 $arrayMonths = array( 'jan' => array(1, 8, 5,4), 'feb' => array(10,12,15,11), 'mar' => array(12, 7, 4, 3), 'apr' => array(10,16,7,17), ); $position = array("Foo1","Foo2","Foo3","FooN"); $set = array(); foreach($arrayMonths as $key => $value) { $max = max($value); $pos = array_search($max, $value); $set[$key][$position[$pos]] = $max ; } function cmp($a, $b) { foreach($a as $key => $value ) { foreach ($b as $bKey => $bValue) { return $bValue - $value ; } } } uasort($set,"cmp"); var_dump($set); output array 'apr' => array 'FooN' => int 17 'feb' => array 'Foo3' => int 15 'mar' => array 'Foo1' => int 12 'jan' => array 'Foo2' => int 8 another example:- Sorting a Multi-Dimensional Array with PHP http://www.firsttube.com/read/sorting-a-multi-dimensional-array-with-php/ Every so often I find myself with a multidimensional array that I want to sort by a value in a sub-array. I have an array that might look like this: //an array of some songs I like $songs = array( '1' => array('artist'=>'The Smashing Pumpkins', 'songname'=>'Soma'), '2' => array('artist'=>'The Decemberists', 'songname'=>'The Island'), '3' => array('artist'=>'Fleetwood Mac', 'songname' =>'Second-hand News') ); The problem is thus: I’d like to echo out the songs I like in the format “Songname (Artist),” and I’d like to do it alphabetically by artist. PHP provides many functions for sorting arrays, but none will work here. ksort() will allow me to sort by key, but the keys in the $songs array are irrelevant. asort() allows me to sort and preserves keys, but it will sort $songs by the value of each element, which is also useless, since the value of each is “array()”. usort() is another possible candidate and can do multi-dimensional sorting, but it involves building a callback function and is often pretty long-winded. Even the examples in the PHP docs references specific keys. So I developed a quick function to sort by the value of a key in a sub-array. Please note this version does a case-insensitive sort. See subval_sort() below. function subval_sort($a,$subkey) { foreach($a as $k=>$v) { $b[$k] = strtolower($v[$subkey]); } asort($b); foreach($b as $key=>$val) { $c[] = $a[$key]; } return $c; } To use it on the above, I would simply type: $songs = subval_sort($songs,'artist'); print_r($songs); This is what you should expect see: Array ( [0] => Array ( [artist] => Fleetwood Mac [song] => Second-hand News ) [1] => Array ( [artist] => The Decemberists [song] => The Island ) [2] => Array ( [artist] => The Smashing Pumpkins [song] => Cherub Rock ) ) The songs, sorted by artist.
The answer to your question is indeed in the usort() function. However, what you need to do is write the function that you pass to it is doing the weighting for you properly. Most of the time, you have something like if($a>$b) { return $a; } But what you need to do is something along the lines of if($a>$b || $someCountryID != 36) { return $a; } else { return $b; }
You need to use ksort to sort by weight, not usort. That will be much cleaner. Arrange your data in an associative array $weighted_data in the format weight => country_data_struct. This is a very intuitive form of presentation for weighted data. Then run krsort($weighted_data)
Given an array of values corresponding to the location of a leaf node, how would I access said node?
Note: I am unfamiliar with terminology regarding tree structures. Please forgive any oversights that may be a result of my ignorance! Practical Example Given an array as such: Array ( [0] => 0 [1] => 2 [2] => 8 [3] => 9 ) The tree node with the key "9" would be found at $tree[2][8][9] (with 0 being the root). Given the above array, how would I construct a statement in PHP that would access the leaf node? Target Code /* Let's say I am given a $leafNodeID of 9, and I'd like to save some data ($dataToSave) into said leaf node */ $leafNodeID = 9; $dataToSave = array("name" => "foobar"); $tree_path = $this->findPathToRootNode($tree, $leafNodeID); // This returns the array found above. ${?????} = $dataToSave; // <-- Here be dragons Thanks in advance! Edit: For those wondering, my findPathToRootNode function just recursively finds the parent node, and saves it in the array format found above. If there's a better way to represent said data (especially if it solves my problem), that would be even better. Edit: On a read through, it seems this question is less about trees, but rather how to access an array given its structure in a separate array. Tagging as such.
Make a self-targeting function. This should do the trick (untested) function getLeaf($tree, $targetleaf, $depth = 0){ if (isset($targetleaf[$depth+1]) return getLeaf($tree[$targetleaf[$depth]], $targetleaf, $depth + 1) else return $tree[$depth]; } With $tree being the data, $tree the path to the array, and $depth speaks for itself. Call the function with $leaf = getLeaf($tree,$targetleaf);
How to access the same element of an array that has only one element or multiple elements?
I access an API that returns an array of elements. If there is only one element, it will return the array as: Array { [response] => Array { [name] => Frank } } However, if there are multiple results, it goes one level deeper to account for each result: Array { [response] => Array { [0] = > Array { [name] => Frank } [1] = > Array { [name] => John } } } This is quite frustrating as it means I have to first check if there is just one element or more than one, and then code each one separately. Is there a better solution that automatically takes care of both scenarios (e.g. one result vs. multiple results) and always retrieves the name's that are available regardless ?
You could either write an iterator that would deal with your special case, or you iterate over it an deal with the special case: foreach ($array['response'] as $responses) { isset ($responses[0]) || $responses = array($responses); foreach ($repsonses as $response) { # standard processing code per each item. } } Special cases can be very annoying, so take care of them early and ideally make them disappear.
(If you can) Tweak the original API to always return the array with the indexed keys even when there are only one item in the array. Else, add this one after you fetch the result from the API. if(count($result['response'])==1) { $newResult['response'][0]=$result; } else { $newResult=$result; }
Create Multi-Dimensional Array With Algorithm Using Data in Single-Dimensional Array
I have an single-dimensional array of PHP objects. Each object has two attributes, one attribute is the object's unique ID and the other is the unique ID of another object in the array that is its parent. For example: array(3) { [0]=> object(stdClass)#1 (2) { ["ID"]=> int(1) ["parentID"]=> int(0) } [1]=> object(stdClass)#2 (2) { ["ID"]=> int(3) ["parentID"]=> int(2) } [2]=> object(stdClass)#3 (2) { ["ID"]=> int(2) ["parentID"]=> int(1) } } I need to convert this single-dimensional array into a multi-dimensional array. I have taken a few stabs at this but I can't find a way to get it done without having a loop for each level of nesting. The algorithm needs to be able to adapt to hypothetically infinite levels of nesting. I've tried using some recursion techniques but I've never gotten it quite right. To add a bit of complexity, the objects in the array that I am getting are not always in a sensical order. I tried to replicate this in my example above; you'll notice that the object with the ID of 3 comes in the array before the object with the ID of 2. So their will probably be a sorting algorithm involved as well. Ideally the example above would turn out something like this: Array ( [0] => Array ( [ID] => 1 [parentID] => 0 [0] => Array ( [ID] => 2 [parentID] => 1 [0] => Array ( [ID] => 3 [parentID] => 2 ) ) ) )
Try this algorithm: // sort objects by parentID function cmpNodes($a, $b) { return $a->parentID - $b->parentID; } usort($objects, 'cmpNodes'); // define first node as root of tree $tree = (array) array_shift($objects); // lookup table for direct jumps $idTable = array($tree['ID'] => &$tree); foreach ($objects as $object) { $node = (array) $object; // test if parent node exists if (!isset($idTable[$node['parentID']])) { // Error: parent node does not exist break; } // append new node to the parent node $idTable[$node['parentID']][] = $node; // set a reference in the lookup table to the new node $idTable[$node['ID']] = &$idTable[$node['parentID']][count($idTable[$node['parentID']])-3]; } // unset($idTable); var_dump($tree); I used a lookup table ($idtable) for the IDs to jump directly to the nodes.
So, just as a precursor - I do not know php, really at all. I am primarily a c-style language developer (aka c, Objective c and Java). So some of this may be harder to do in php, but here is the attempt I would make: //the original input array oldArray; //the output array array[] newArray = new array[]; foreach (element : oldArray) { //if the element is at the top, put it at the top of the array if (element.parentId == 0) { newArray.add(element); } else { //otherwise, find it's parent and put it in the child array of the parent for (potentialParent : oldArray) { if (potentialParent.id = element.parentId) { potentialParent.array.add(element); break; } } } } A couple of notes: I am assuming you are passing everything around with pointers. If you are making copies of the objects, it is harder, but not impossible. I am also assuming you can change the size of the array dynamically. Again, I am not too aware of php - if you cannot do that, then you would need a procedural way to do this behavior. In Java I would use a list type, or just copy the array and reset it again. The key to this algorithm working is that the children are placed under their parent - wherever they are - in one pass. This means that, no matter the order, the hierarchy will be created in that single pass. If you need the wrapper array around the parent that you show in your example, you can simply add something like this to the end of the code: finalArray = new array[]; finalArray[0] = newArray; Hope this helps.