How to create a tree-like array from a loop? - php

Suppose I have a one-dimensional array $arr like this:
Array
(
['key1'] => 1
['key2'] => 1
['key3'] => 1
['key4'] => 1
)
I want to loop through $arr like this:
foreach($arr as $key => $a) {
//$tree[????] = ????? Here is my concern
}
such that, doing print_r($tree) produces
Array
(
['key1'] => Array
(
['key2'] => Array
(
['key3'] => Array
(
['key4'] => 1
)
)
)
)
My concern is how to increase the dimension (not the values) of the array $tree inside the loop from a single dimension array $numbers such that $tree['key1']['key2']['key3'] is an array and $tree['key1']['key2']['key3']['key4'] is equal to 1.
Furthermore, if I have a single dimensional array $foo with 10 elements. I should produce another array $bar that expands to 10 dimensional array similar to the output above.
What should I do inside the foreach loop? Or instead of using loop, is there a way to produce an output like that of above from a one dimension array?
EDIT:
OK well you obviously need some kind of recursive function, but can
you explain how you know that key2 should be a child of key1, key3
should be a child of key2 etc based on the data you show?
The next element of a 1D array is a child of the previous element. So if the 1D array is like this:
Array
(
['bar'] => 1 // I don't care of the values as of the moment
['foo'] => 1
['baz'] => 1
)
Element foo should be the child of bar, and element baz should be the child of foo for the tree array.
Ok so the actual values of everything except the last element are
irrelevant?
Actually all values of the 1D array is irrelevant as of this moment. I am only concerned on constructing the tree array.

The way to do this is to use references.
function create_tree($arr) {
$result = array();
$ref = &$result;
foreach ($arr as $key => $el) {
$ref = array($key => $el);
$ref =& $ref[$key];
}
return $result;
}
How it works:
$result = array(); // an array to hold the result
$ref = &$result; // start with a reference to the top level
foreach ($arr as $key => $el) { // iterate over the input array
$ref = array($key => $el); // create this level in the array
$ref =& $ref[$key]; // change the reference to be the new deepest level
}
return $result; // return the result
See it working

Assignment by reference appears to be useful for this.
$tree = array();
$node =& $tree;
foreach ($arr as $key => $a) {
$node =& $node[$key];
$node = array();
}
$node = end($arr);

Related

PHP: Convert a standard array into associate array keys?

I'm working on a function that takes an array as a parameter, and then calls the value of a different associative array using the input array as keys. So for example,
array('level_1', 'level_2', 'level_3')
should become
$variable_defined_within_function['level_1']['level_2']['level_3']
I have a way to do this that I think will work, but it feels hacky and weird and I don't really want to use eval() unless I absolutely must.
function fetch($keys) {
if (!is_array($keys)) { $variable = array($keys); }
foreach ($keys as $key) {
$assoc_string .= '[' . str_replace('\'' . '\\\'' . $key) . ']';
}
$reqstring = 'if (isset($this->vars' . $assoc_string . ')) { return $this->vars' . $assoc_string . '; } else { return false; }';
eval($reqstring);
}
That just doesn't seem right, does it? How could I convert a list of keys into an associative array?
How about something like this:
function fetch($keys) {
if (!is_array($keys))
$keys = array($keys);
$arr = $this->vars;
foreach($keys as $key)
{
if (!isset($arr[$key]))
return FALSE;
$arr = $arr[$key];
}
return $arr;
}
Please consider this function as a starting point:
function fetch(array $keys, array $array) {
$pointer = &$array;
foreach ($keys as $key) {
if (!isset($pointer[$key]))
break;
$pointer = &$pointer[$key];
}
return $pointer;
}
it will loop through $array with provided $keys and return the value of the last existing key. You can use it as a base and add your logic for keys that not exists or something
Here is the solution, very simple, but yet, still very confusing sometimes.
$arr = array('level_1', 'level_2', 'level_3');
function fetch(array $array){
$numberOfDimensions = count($array);
// the value of array['level_1']['level_2']['level_3']
$value = "something";
for($i = $numberOfDimensions-1; $i >= 0; $i--){
$value = array($array[$i] => $value);
}
return $value;
}
print_r(fetch($arr));
Output:
Array ( [level_1] => Array ( [level_2] => Array ( [level_3] => something )))
As you can see, the solution is very simple, but to understand what is going on, you must understand how array works.
Every array has index, or hash when talking about associative arrays, and for each of those keys there is only exactly one value. The value can be of any type, so if we add an array as value of another array's element, you get 2-dimensional array. If you add 2-dimensional array as value of another arrays's element, you get 3-dimensional array. By repeating the process, you get N-dimensional array.
The algorithm works by going from the deepest key (the last element inside keys array) and assigning a new associative array to the $value variable, which is the value prepared to be set as array value of dimension above, all until the end of loop.
Lets have a look at the changes made to variable $value inside for loop, before and after change.
The initial value of variable $value is "something". "something" is value of array level_3, and so on...
So, running
print_r(array['level_1']['level_2']['level_3']);
will produce
something
Here is a full state view of the $value variable inside for loop:
Key: level_3
something
Array ( [level_3] => something )
Key: level_2
Array ( [level_3] => something )
Array ( [level_2] => Array ( [level_3] => something ) )
Key: level_1
Array ( [level_2] => Array ( [level_3] => something ) )
Array ( [level_1] => Array ( [level_2] => Array ( [level_3] => something ) ) )

How to skip iteration in foreach inside foreach

I have an array of values, and i want to insert the values to another array but with an if condition, if the "if" is true I want to skip the iteration.
Code:
$array=array(array(1=>11,2=>22,3=>23,4=>44,5=>55));
$insert=array();
foreach($array as $k1=>$v1)
{
foreach($v1 as $k2=>$v2)
{
if($v2==23)
{
break;
}
}
$insert[]=$v1;
}
final result should look like that
Array
(
[0] => Array
(
[1] => 11
[2] => 22
[3] => 44
[4] => 55
)
)
I tried using: break,return,continue...
Thanks
There are a few ways to do this. You can loop over the outer array and use array_filter on the inner array to remove where the value is 23 like this (IMO preferred; this also uses an array of $dontWant numbers so it is easier to add or change numbers later):
<?php
$array = array(array(1=>11,2=>22,3=>23,4=>44,5=>55));
$insert = array();
//array of numbers you don't want
$dontWant = array(23);
//loop over outer array
foreach($array as $subArray){
//add to $insert a filtered array
//subArray is filtered to remove where value is in $dontWant
$insert[] = array_filter($subArray, function($val) uses ($dontWant) {
//returns true if the value is not in the array of numbers we dont want
return !in_array($val, $dontWant);
});
}
//display final array
echo '<pre>'.print_r($insert,1).'</pre>';
Or you can reference the first key to add to a sub array in $insert like (which is a little more like what your code is trying to do and show that you are not too far off):
<?php
$array = array(array(1=>11,2=>22,3=>23,4=>44,5=>55));
$insert = array();
//loop over outer array
foreach($array as $k1=>$v1){
//add an empty array to $insert
$insert[$k1] = array();
//loop over inner array
foreach($v1 as $k2=>$v2){
//if the inner array value is not 23
if($v2 != 23){
//add to inner array in insert
$insert[$k1][] = $v2;
}
}
}
//display the result
echo '<pre>'.print_r($insert,1).'</pre>';
Both of these methods would produce the same result. IMO using array_filter is the preferred method, but the second method might be a little easier to understand for someone new to programming.
Why don't you just try it like this?
foreach($v1 as $k2=>$v2)
{
if($v2!=23)
{
$insert[]=$v2;
}
}
EDIT:
Explanation: You check with the if($v2!=23) if the value of the variable $v2 is not equal to (that is the != sign) any given number that stands after the inequality operator, and if so, it will insert that value to the array $insert.
I hope it is clear now.
Sorry, I've written $v1 instead of $v2, the code should work now.
To add variants :)
$array=array(array(1=>11,2=>22,3=>23,4=>44,5=>55));
$insert=array();
foreach($array as $a)
{
while (($i = array_search(23, $a)) !== false)
{ unset($a[$i]); sort($a); }
$insert[] = $a;
}
print_r($a);
result:
Array ( [0] => Array ( [0] => 11 [1] => 22 [2] => 44 [3] => 55 ) )

PHP address array tree node using string or array

Say, I have data in a tree structure implemented as an array of arrays to an arbitrary depth, something like
print_r($my_array);
Array
(
[id] => 123
[value] => Hello, World!
[child] => Array
(
[name] => Foo
[bar] => baz
)
[otherchild] => Array
(
[status] => fubar
[list] => Array
(
[one] => 1
[two] => 3
)
)
[sanity] => unchecked
)
Now, using a single string as key I would like to address a node at an arbitrary depth, Let's say I have a key something like this:
$key = 'otherchild|list|two';
Using this key I want to be able to address the value stored in
$my_array['otherchild']['list']['two']
Obviously I can explode('|', $key) to get an array of keys, and shifting values off of this and using those to address sub-arrays makes it easy to obtain the value I'm looking for, something like
$value = $my_array;
$keys = explode('|', $key);
while ($k = array_shift($keys)) {
if (isset($value[$k])) {
$value = $value[$k];
} else {
// handle failure
}
} // Here, if all keys exist, $value holds value of addressed node
But I'm stuck trying to update values in a generic way, ie without having to resort to something like
$keys = explode('|', $key);
if (count($keys) == 1) {
$my_array[$keys[0]] = $new_value;
} else if (count($keys) == 2) {
$my_array[$keys[0]][$keys[1]] = $new_value;
} else if ...
Any ideas?
function setAt(array & $a, $key, $value)
{
$keys = explode('|', $key);
// Start with the root node (the array itself)
$node = & $a;
// Walk the tree, create nodes as needed
foreach ($keys as $k) {
// Create node if it does not exist
if (! isset($node[$k])) {
$node[$k] = array();
}
// Walk to the node
$node = & $node[$k];
}
// Position found; store the value
$node = $value;
}
// Test
$array = array();
// Add new values
setAt($array, 'some|key', 'value1');
setAt($array, 'some|otherkey', 'val2');
setAt($array, 'key3', 'value3');
print_r($array);
// Overwrite existing values
setAt($array, 'some|key', 'new-value');
print_r($array);
setAt($array, 'some', 'thing');
print_r($array);
If you're looking for a short answer, you can also use eval().
$elem = "\$array['" . str_replace("|", "']['", $key) . "']";
$val = eval("return isset($elem) ? $elem : null;");

Convert CSV string to Array keys and a value of 1 or true

Please could someone help find a better solution to the code below.
Here is my existing solution:
$list = '54,78,3,5';
$list = explode(",",$list);
foreach($list as $k => $v) { $compare[$v] = 1; }
when i run array_flip instead of the foreach on $list it returns an array like this:
Array(
54 => 0,
78 => 1,
...
)
I need this so another array which is already in this format can be compared with an IF statment:
Array(
54 => 1,
78 => 1,
...
)
$list = '54,78,3,5';
$list = explode(",",$list);
$array = array_combine($list, array_fill(0, count($list), 1));
print_r($array);
Array
(
[54] => 1
[78] => 1
[3] => 1
[5] => 1
)
array_fill() will create an array with all of its values being the number 1 at the same size as the $list array. array_combine() then creates a new array with the values of $list as the keys and the values created by array_fill();
Demo
Do you need the original $list to be a variable? Can't you just make it an array from the start wherever the data comes from, and append 1 or true to the value?
Otherwise, before your current foreach, add a new loop and go through $list (which you made into an array) and make a new array appending the required value to each key (keys taken from $list):
foreach ($list as $key)
{
$new_array[$key] = 1;
}

Multidimenssion array compare two arrays and update first array value

I want to filter a array by a number and update its status in the first array.
I have two array $arr1,$arr2
$arr1 = array(
0=>array('number'=>100,name=>'john'),
1=>array('number'=>200,name=>'johnny')
);
$arr2= array(
0=>array('number'=>300,name=>'r'),
1=>array('number'=>100,name=>'b'),
2=>array('number'=>200,name=>'c')
);
Final output should be an array like this
$arr1 = array(
0=>array('number'=>100,name=>'b'),
1=>array('number'=>200,name=>'c')
);
Any ideas to start off please ?
For specialized array modifications like this, the method of choice is array walk. It allows you to apply a custom function to each element in a given array.
Now, because of your data format, you will have to do a loop. Wrikken is asking if you can retrieve or transform the data to provide faster access. The algorithm below is O(n^2): it will require as many cycles as there are elements in the first array times the number of elements in the second array, or exactly count($arr1) * count($arr2).
function updateNameFromArray($element, $key, $arr2) {
foreach($arr2 as $value) {
if($value['number'] == $element['number']) {
$element['name'] == $value['name'];
break;
}
}
}
array_walk($arr1, "updateNameFromArray", $arr2);
Now, what Wrikken is suggesting is that if your arrays can be changed to be keyed on the 'number' property instead, then the search/replace operation is much easier. So if this were your data instead:
$arr1 = array(
100=>array('number'=>100,name=>'john'),
200=>array('number'=>200,name=>'johnny')
);
// notice the keys are 100 and 200 instead of 0,1
$arr2= array(
300=>array('number'=>300,name=>'r'),
100=>array('number'=>100,name=>'b'),
200=>array('number'=>200,name=>'c')
);
// notice the keys are 300, 100 and 200 instead of 0,1, 2
Then you could do this in O(n) time, with only looping over the first array.
foreach($arr1 as $key => $value) {
if(isset($arr2[$key])) {
$value['number'] = $arr2[$key]['number'];
}
}
Try this. It's not that clean but i think it would work.
<?php
$arr1 = array(0=>array('number'=>100,'name'=>'john'),1=>array('number'=>200,'name'=>'johnny'));
$arr2= array(0=>array('number'=>300,'name'=>'r'),1=>array('number'=>100,'name'=>'b'),2=>array('number'=>200,'name'=>'c'));
foreach( $arr1 as $key=>$data1 )
{
foreach( $arr2 as $key2=>$data2 )
{
if( $data1['number'] == $data2['number'] )
{
$arr1[$key]['name'] = $arr2[$key2]['name'];
}
}
}
print_r( $arr1 );
?>
the output would be :
Array
(
[0] => Array
(
[number] => 100
[name] => b
)
[1] => Array
(
[number] => 200
[name] => c
)
)
There isn't really a simple way for this to be accomplished with generic PHP functions, so, You might need to create mapping arrays.
The way I would approach this, is creating a loop that goes through the first array, and maps the number value as a key to the index of it's place in $arr1 giving you:
$tmp1 = array();
foreach ($arr1 as $key => $number_name) {
$tmp1[$number_name['number']] = $key;
}
This should give you an array that looks like
$tmp1 [
100 => 0,
200 => 1
];
Then I would loop through the second array, get the number value, if that existed as a key in $tmp1, get the associated value (being the key for $arr1), and use that to update the name in $arr1.
// Loop through $arr2
foreach ($arr2 as $number_name) {
// Get the number value
$number = $number_name['number'];
// Find the $arr1 index
if (isset($tmp1[$number])) {
$arr1_key = $tmp1[$number];
// Set the $arr1 name value
$arr1[$arr1_key]['name'] = $number_name['name'];
}
}
<?php
//Set the arrays
$arr1 = array(
array('number'=>100,'name'=>'john'),
array('number'=>200,'name'=>'johnny')
);
$arr2= array(
array('number'=>300,'name'=>'r'),
array('number'=>100,'name'=>'b'),
array('number'=>200,'name'=>'c')
);
// use a nested for loop to iterate and compare both arrays
for ($i=0;$i<count($arr1);$i++):
for ($j=0;$j<count($arr2);$j++):
if ($arr2[$j]['number']==$arr1[$i]['number'])
$arr1[$i]['name']=$arr2[$j]['name'];
endfor;
endfor;
print_r($arr1);
OUTPUT:
Array (
[0] => Array ( [number] => 100 [name] => b )
[1] => Array ( [number] => 200 [name] => c )
)
That being said, you should probably reconsider the very way your data is structured. Do you really need a multi-dimensional array or can you use a simple associative array, like so:
// set the arrays
$arr1 = array(
'john'=>100,
'johnny'=>200
);
$arr2 = array(
'r'=>300,
'b'=>100,
'c'=>200
);
// find values in arr2 common to both arrays
$arr3 = array_intersect($arr2, $arr1);
// change the key of arr1 to match the corresponding key in arr2
foreach ($arr3 as $key=>$value) {
$old_key = array_search($value, $arr1);
$arr1[$key]=$arr1[$old_key];
unset($arr1[$old_key]);
}
print_r($arr1);
OUTPUT:
Array (
[b] => 100
[c] => 200
)

Categories