Create an array from the keys of an array - php

Let's pretend I have this:
$str = "/a/b/c/d/";
$arr = array_filter( explode("/", $str );
At this point $arr contains 4 elements. Is there a way I could create a path in an array with those 4 elements, such as:
$result = [
"a" => [
"b" => [
"c" => [
"d" => [
]
]
]
]
]
...without iterating over $arr?
I know that
$x["a"]["b"]["c"]["d"] = 1;
is perfectly valid and it will create a 4 levels array even if $x wasn't declared as an array, so what I'm asking should be possible.

I DO NOT recommend this as there are security implications when using eval(). However, because I stated in the comments that it couldn't be done without iteration, I felt compelled to post this as an answer (yes, I know implode() iterates internally).
$str = "/a/b/c/d/";
$arr = array_filter( explode("/", $str ));
$keys = '["'.implode('"]["', $arr).'"]';
eval('$x'.$keys.' = 1;');
print_r($x);
For a more practical way see How to write getter/setter to access multi-leveled array by dot separated key names?

I wrote a function once, that had this behaviour as a side effect. It doesn't iterate, but uses recursion.
See: https://github.com/feeela/php-utils/blob/master/function.getArrayValueReference.php
You may call like that:
<?php
$newArray = array();
$keys = 'a/b/c/d';
$referenceToD =& getArrayValueReference( $newArray, explode( '/', $keys ), true );
$referenceToD[0] = 'foo';
$referenceToD[1] = 'bar';
print_r( $newArray );
This modifies the array $newArray and creates all the levels. The functions return value is a reference to the last key ('d' in that example).
…which results in:
Array (
[a] => Array (
[b] => Array (
[c] => Array (
[d] => Array (
[0] => foo
[1] => bar
)
)
)
)
)

There is no way to use all the values of $arr without iterating over it.
I guess you don't want to write a foreach loop but use some PHP function that does the iteration for you.
A simple solution that iterates two times over the array (behind the scene)
This is a possible solution:
$x = array_reduce(
array_reverse($arr),
function ($carry, $item) {
return [$item => $carry];
},
1
);
It generates the same result as:
$x = [];
$x['a']['b']['c']['d'] = 1;
Unfortunately it iterates over $arr two times (array_reverse() and array_reduce()).
Another solution that generates a hierarchy of objects
Another approach that generates the required embedding using objects (stdClass) instead of arrays:
$out = new stdClass;
array_reduce(
$arr,
function ($carry, $item) {
$v = new stdClass;
$carry->{$item} = $v;
return $v;
},
$out
);
It works using a single iteration over $arr but it relies on the way the objects are handled in PHP to work (and this doesn't work with arrays).
PHP handles the objects in a way that makes them look like they are passed by reference. It's a common misconception that the objects are "passed by reference" in PHP but this is not true. A double indirection is responsible for this behaviour.
A recursive solution
function makeArray(array $arr, $initial)
{
if (! count($arr)) {
return $initial;
} else {
$key = array_shift($arr);
return [ $key => makeArray($arr, $initial) ];
}
}
$out = makeArray($arr, 1);
This solution iterates only once over the array and generates a hierarchy of arrays but recursivity is disastrous for large input arrays because it uses a lot of memory.

Related

Is there a way to copy an array without reference PHP? [duplicate]

I have the following code:
$data['x'] = $this->x->getResults();
$data['y'] = $data['x'];
//some code here to modify $data['y']
//this causes (undesirably) $data['x] to be modified as well
I guess since all the elements of $data are themselves references, modifying $data['y'] also modifies $data['x']..which is NOT what I want. I want $data['x'] to remain the same. Is there any way to dereference the elements here so that I can copy the elements by value?
Thanks.
Update: $this->x->getResults(); returns an object array. So I can then do something like: $data['x'][0]->date_create ...
Update:
my latest attempt to clone the array looks something like this:
$data['x'] = $this->x->getResults();
$data['y'] = $data['y'];
foreach($data['x'] as $key=>$row) {
$data['y'][$key]->some_attr = clone $row->some_attr;
}
Am I way off here? I keep getting a "__clone method called on non-object" error. From reading the responses it seems like my best option is to iterate over each element and clone it (which is what I was trying to do with that code..).
UPDATE: Just solved it!: inside the foreach loop I just needed to change the line to:
$data['y'][$key] = clone $row;
And it works! Thanks to everyone for the help.
You can take advantage of the fact that PHP will dereference the results of a function call.
Here's some example code I whipped up:
$x = 'x';
$y = 'y';
$arr = array(&$x,&$y);
print_r($arr);
echo "<br/>";
$arr2 = $arr;
$arr2[0] = 'zzz';
print_r($arr);
print_r($arr2);
echo "<br/>";
$arr2 = array_flip(array_flip($arr));
$arr2[0] = '123';
print_r($arr);
print_r($arr2);
The results look like this:
Array ( [0] => x [1] => y )
Array ( [0] => zzz [1] => y ) Array ( [0] => zzz [1] => y )
Array ( [0] => zzz [1] => y ) Array ( [0] => 123 [1] => y )
You can see that the results of using array_flip() during the assigment of $arr to $arr2 results in differences in the subsequent changes to $arr2, as the array_flip() calls forces a dereference.
It doesn't seem terribly efficient, but it might work for you if $this->x->getResults() is returning an array:
$data['x'] = array_flip(array_flip($this->x->getResults()));
$data['y'] = $data['x'];
See this (unanswered) thread for another example.
If everything in your returned array is an object however, then the only way to copy an object is to use clone(), and you would have to iterate through $data['x'] and clone each element into $data['y'].
Example:
$data['x'] = $this->x->getResults();
$data['y'] = array();
foreach($data['x'] as $key => $obj) {
$data['y'][$key] = clone $obj;
}
array_merge() can accept any number of parameters, even 1, then produce a new array. So just do following:
$new_array = array_merge($existing_array);
array_flip() won't work when array values are not strings nor integers.
I found a simple solution, however:
$clonedArr = (array)clone(object)$arr;
This works thanks to the properties of clone on an object.
Not simple.
Read about clone
BUT! if your elements are not objects and not refence type variables you have no problem.
Example for reference types:
$v=11;
$arr[]=&$v;
If you are working with objects, you might want to take a look at clone, to create a copy of an object, instead of a reference.
Here is a very short example :
First, with an array, it works by value :
$data['x'] = array(
'a' => 'test',
'b' => 'glop',
);
$data['y'] = $data['x'];
$data['y'][0] = 'Hello, world!';
var_dump($data['x']); // a => test : no problem with arrays
By default, with objects, it works by reference :
$data['x'] = (object)array(
'a' => 'test',
'b' => 'glop',
);
$data['y'] = $data['x'];
$data['y']->a = 'Hello, world!';
var_dump($data['x']); // a => Hello, world! : objects are by ref
But, if you clone the object, you work on a copy :
I guess this is your case ?
$data['x'] = (object)array(
'a' => 'test',
'b' => 'glop',
);
$data['y'] = clone $data['x'];
$data['y']->a = 'Hello, world!';
var_dump($data['x']); // a => test : no ref, because of cloning
Hope this helps,
I just discovered that if you simply want a copy of an array of values (no references) from a constant then you can just write:
$new_array = (array) (object) self::old_array;
Not an exact answer to the OP's question but it helped me and might help someone else.
You could use this function to copy multidimensional arrays containing objects.
<?php
function arrayCopy( array $array ) {
$result = array();
foreach( $array as $key => $val ) {
if( is_array( $val ) ) {
$result[$key] = arrayCopy( $val );
} elseif ( is_object( $val ) ) {
$result[$key] = clone $val;
} else {
$result[$key] = $val;
}
}
return $result;
}
?>

PHP: Updating an associative array though a class function?

I want to be able to update an associative array within an object based on function input, but I'm at a loss for how to do this. Here's my problem. Let's say there's this data container in my object:
private $vars = Array
(
['user_info'] = Array
(
['name'] => 'John Doe'
['id'] => 46338945
['email'] => 'johndoe#example.com'
['age'] => 35
)
['session_info'] = Array
(
['name'] => 'session_name'
['id'] => 'mGh44Jf0nfNNFmm'
)
)
and I have a public function update to change these values:
public function update($keys, $changeValueTo) {
if (!is_array($keys)) {
$keys = array($keys);
}
nowWhat();
}
Ultimately, I just want to be able to convert something like this
array('user_info', 'name')
into this:
$this->vars['user_info']['name']
so I can set it equal to the $equalTo parameter.
This usually wouldn't be a problem, but in this scenario, I don't know the schema of the $vars array so I can't write a foreach statement based on a fixed number of keys. I also can't just make $vars public, because the function needs to do something within the object every time something is changed.
I'm worried that this is a recursive scenario that will involve resorting to eval(). What would you recommend?
It is not hard to do it. You need passing variable by reference and a loop to achieve. No need eval().
$var = array(
"user_info" => array(
"name" => "Visal"
)
);
function update(&$var, $key, $value) {
// assuming the $key is an array
$t = &$var;
foreach ($key as $v) {
$t = &$t[$v];
}
$t = $value;
}
update($var, array("user_info", "name"), "Hello World");
var_dump($var);

PHP - Reorganize Query String Parameters

Suppose I have a query string like this:
?foo1bar1=a&foo1bar2=b&foo1bar3=c&foo2bar1=d&cats1dogs1=z
The parameters in this string could be arbitrary, and could have any number of indexes (so you could have just foo=, you could have foo1bar1= or something like foo1bar1baz1=. However, the parameters and their relevant indexes will be known ahead of time.
I'd like to be able to take this query string, plus a configuration, and re-structure it... The configuration might look something like this:
$indexes = array('foodex', 'bardex');
$columns = array('foo<foodex>bar<bardex>', 'cats<foodex>dogs<bardex>');
And the desired output would be the "columns" reorganized into rows indexed by the appropriate indexes, ready for storing in database rows. Something like this...
array(
array(
'foodex' => 1,
'bardex' => 1,
'foo<foodex>bar<bardex>' => 'a',
'cats<foodex>dogs<bardex>' => 'z'
),
array(
'foodex' => 1,
'bardex' => 2,
'foo<foodex>bar<bardex>' => 'b',
'cats<foodex>dogs<bardex>' => null
),
etc.
)
I've thought of a couple ideas for solving this problem, but nothing seems terribly elegant... I could:
Write a recursive function that loops through all possible values of a known index, and then calls itself to loop through all possible values of the next known index, then records the results. This would be super slow... you might loop through thousands or millions of possible index values only to find a handful in the query string.
Loop through each actual value in the query string, do some sort of regex check to see if it matches one of the columns I'm looking for including wildcards for each index that's listed within it. Then I could build some sort of multi-dimensional array using the indexes and eventually flatten it for the output. This would run much faster, but seems awfully complex.
Is there an elegant solution staring me in the face? I'd love to hear suggestions.
here is quick sample you can start with:
// your configuration
$indexes = array ('foodex', 'bardex');
$columns = array ('foo<foodex>bar<bardex>', 'cats<foodex>dogs<bardex>');
// column names converted into regexps
$columns_re = array_map ( function ($v) {
global $indexes;
return '/^' . str_replace ( array_map ( function ($v) {
return '<' . $v . '>';
}, $indexes ), '(\d+)', $v ) . '$/';
}, $columns );
// output array
$array = array ();
foreach ( $_GET as $key => $value ) {
foreach ( $columns_re as $reIdx => $re ) {
$matches = array ();
if (preg_match_all ( $re, $key, $matches )) {
// generate unique row id as combination of all indexes
$rowIdx = '';
foreach ( $indexes as $i => $idxName )
$rowIdx .= $matches [$i + 1] [0] . '_';
// fill output row with default values
if (! isset ( $array [$rowIdx] )) {
$array [$rowIdx] = array ();
foreach ( $indexes as $i => $idxName )
$array [$rowIdx] [$idxName] = $matches [$i + 1] [0];
foreach ( $columns as $name )
$array [$rowIdx] [$name] = null;
}
// fill actually found value
$array [$rowIdx] [$columns [$reIdx]] = $value;
}
}
}
tested with php 5.3, with some modifications can be run under any version

php copying array elements by value, not by reference

I have the following code:
$data['x'] = $this->x->getResults();
$data['y'] = $data['x'];
//some code here to modify $data['y']
//this causes (undesirably) $data['x] to be modified as well
I guess since all the elements of $data are themselves references, modifying $data['y'] also modifies $data['x']..which is NOT what I want. I want $data['x'] to remain the same. Is there any way to dereference the elements here so that I can copy the elements by value?
Thanks.
Update: $this->x->getResults(); returns an object array. So I can then do something like: $data['x'][0]->date_create ...
Update:
my latest attempt to clone the array looks something like this:
$data['x'] = $this->x->getResults();
$data['y'] = $data['y'];
foreach($data['x'] as $key=>$row) {
$data['y'][$key]->some_attr = clone $row->some_attr;
}
Am I way off here? I keep getting a "__clone method called on non-object" error. From reading the responses it seems like my best option is to iterate over each element and clone it (which is what I was trying to do with that code..).
UPDATE: Just solved it!: inside the foreach loop I just needed to change the line to:
$data['y'][$key] = clone $row;
And it works! Thanks to everyone for the help.
You can take advantage of the fact that PHP will dereference the results of a function call.
Here's some example code I whipped up:
$x = 'x';
$y = 'y';
$arr = array(&$x,&$y);
print_r($arr);
echo "<br/>";
$arr2 = $arr;
$arr2[0] = 'zzz';
print_r($arr);
print_r($arr2);
echo "<br/>";
$arr2 = array_flip(array_flip($arr));
$arr2[0] = '123';
print_r($arr);
print_r($arr2);
The results look like this:
Array ( [0] => x [1] => y )
Array ( [0] => zzz [1] => y ) Array ( [0] => zzz [1] => y )
Array ( [0] => zzz [1] => y ) Array ( [0] => 123 [1] => y )
You can see that the results of using array_flip() during the assigment of $arr to $arr2 results in differences in the subsequent changes to $arr2, as the array_flip() calls forces a dereference.
It doesn't seem terribly efficient, but it might work for you if $this->x->getResults() is returning an array:
$data['x'] = array_flip(array_flip($this->x->getResults()));
$data['y'] = $data['x'];
See this (unanswered) thread for another example.
If everything in your returned array is an object however, then the only way to copy an object is to use clone(), and you would have to iterate through $data['x'] and clone each element into $data['y'].
Example:
$data['x'] = $this->x->getResults();
$data['y'] = array();
foreach($data['x'] as $key => $obj) {
$data['y'][$key] = clone $obj;
}
array_merge() can accept any number of parameters, even 1, then produce a new array. So just do following:
$new_array = array_merge($existing_array);
array_flip() won't work when array values are not strings nor integers.
I found a simple solution, however:
$clonedArr = (array)clone(object)$arr;
This works thanks to the properties of clone on an object.
Not simple.
Read about clone
BUT! if your elements are not objects and not refence type variables you have no problem.
Example for reference types:
$v=11;
$arr[]=&$v;
If you are working with objects, you might want to take a look at clone, to create a copy of an object, instead of a reference.
Here is a very short example :
First, with an array, it works by value :
$data['x'] = array(
'a' => 'test',
'b' => 'glop',
);
$data['y'] = $data['x'];
$data['y'][0] = 'Hello, world!';
var_dump($data['x']); // a => test : no problem with arrays
By default, with objects, it works by reference :
$data['x'] = (object)array(
'a' => 'test',
'b' => 'glop',
);
$data['y'] = $data['x'];
$data['y']->a = 'Hello, world!';
var_dump($data['x']); // a => Hello, world! : objects are by ref
But, if you clone the object, you work on a copy :
I guess this is your case ?
$data['x'] = (object)array(
'a' => 'test',
'b' => 'glop',
);
$data['y'] = clone $data['x'];
$data['y']->a = 'Hello, world!';
var_dump($data['x']); // a => test : no ref, because of cloning
Hope this helps,
I just discovered that if you simply want a copy of an array of values (no references) from a constant then you can just write:
$new_array = (array) (object) self::old_array;
Not an exact answer to the OP's question but it helped me and might help someone else.
You could use this function to copy multidimensional arrays containing objects.
<?php
function arrayCopy( array $array ) {
$result = array();
foreach( $array as $key => $val ) {
if( is_array( $val ) ) {
$result[$key] = arrayCopy( $val );
} elseif ( is_object( $val ) ) {
$result[$key] = clone $val;
} else {
$result[$key] = $val;
}
}
return $result;
}
?>

PHP Change Array Keys

Is there a way to change all the numeric keys to "Name" without looping through the array (so a php function)?
[
0 => 'blabla',
1 => 'blabla',
2 => 'blblll',
// etc ...
]
If you have an array of keys that you want to use then use array_combine
Given $keys = array('a', 'b', 'c', ...) and your array, $list, then do this:
$list = array_combine($keys, array_values($list));
List will now be array('a' => 'blabla 1', ...) etc.
You have to use array_values to extract just the values from the array and not the old, numeric, keys.
That's nice and simple looking but array_values makes an entire copy of the array so you could have space issues. All we're doing here is letting php do the looping for us, not eliminate the loop. I'd be tempted to do something more like:
foreach ($list as $k => $v) {
unset ($list[$k]);
$new_key = *some logic here*
$list[$new_key] = $v;
}
I don't think it's all that more efficient than the first code but it provides more control and won't have issues with the length of the arrays.
No, there is not, for starters, it is impossible to have an array with elements sharing the same key
$x =array();
$x['foo'] = 'bar' ;
$x['foo'] = 'baz' ; #replaces 'bar'
Secondarily, if you wish to merely prefix the numbers so that
$x[0] --> $x['foo_0']
That is computationally implausible to do without looping. No php functions presently exist for the task of "key-prefixing", and the closest thing is "extract" which will prefix numeric keys prior to making them variables.
The very simplest way is this:
function rekey( $input , $prefix ) {
$out = array();
foreach( $input as $i => $v ) {
if ( is_numeric( $i ) ) {
$out[$prefix . $i] = $v;
continue;
}
$out[$i] = $v;
}
return $out;
}
Additionally, upon reading XMLWriter usage, I believe you would be writing XML in a bad way.
<section>
<foo_0></foo_0>
<foo_1></foo_1>
<bar></bar>
<foo_2></foo_2>
</section>
Is not good XML.
<section>
<foo></foo>
<foo></foo>
<bar></bar>
<foo></foo>
</section>
Is better XML, because when intrepreted, the names being duplicate don't matter because they're all offset numerically like so:
section => {
0 => [ foo , {} ]
1 => [ foo , {} ]
2 => [ bar , {} ]
3 => [ foo , {} ]
}
This is an example prefixing all the keys with an underscore.
We use array_combine to combine the array keys with the array values, but we first run an array_map function on the array keys, which takes a simple function that adds the prefix.
$prefix = '_';
$arr = array_combine(
array_map(function($v) use ($prefix){
return $prefix.$v;
}, array_keys($arr)),
array_values($arr)
);
See a live example here https://3v4l.org/HABl7
I added this for an answer to another question and seemed relevant. Hopefully might help someone that needs to change the value of the keys in an array. Uses built-in functions for php.
$inputArray = array('app_test' => 'test', 'app_two' => 'two');
/**
* Used to remap keys of an array by removing the prefix passed in
*
* Example:
* $inputArray = array('app_test' => 'test', 'app_two' => 'two');
* $keys = array_keys($inputArray);
* array_walk($keys, 'removePrefix', 'app_');
* $remappedArray = array_combine($keys, $inputArray);
*
* #param $value - key value to replace, should be from array_keys
* #param $omit - unused, needed for prefix call
* #param $prefix - prefix to string replace in keys
*/
function removePrefix(&$value, $omit, $prefix) {
$value = str_replace($prefix, '', $value);
}
// first get all the keys to remap
$keys = array_keys($inputArray);
// perform internal iteration with prefix passed into walk function for dynamic replace of key
array_walk($keys, 'removePrefix', 'app_');
// combine the rewritten keys and overwrite the originals
$remappedArray = array_combine($keys, $inputArray);
// see full output of comparison
var_dump($inputArray);
var_dump($remappedArray);
Output:
array(2) {
'attr_test' =>
string(4) "test"
'attr_two' =>
string(3) "two"
}
array(2) {
'test' =>
string(4) "test"
'two' =>
string(3) "two"
}
I think that he want:
$a = array(1=>'first_name', 2=>'last_name');
$a = array_flip($a);
$a['first_name'] = 3;
$a = array_flip($a);
print_r($a);
The solution to when you're using XMLWriter (native to PHP 5.2.x<) is using $xml->startElement('itemName'); this will replace the arrays key.
change array key name "group" to "children".
<?php
echo json_encode($data);
function array_change_key_name( $orig, $new, &$array ) {
foreach ( $array as $k => $v ) {
$res[ $k === $orig ? $new : $k ] = ( (is_array($v)||is_object($v)) ? array_change_key_name( $orig, $new, $v ) : $v );
}
return $res;
}
echo '<br>=====change "group" to "children"=====<br>';
$new = array_change_key_name("group" ,"children" , $data);
echo json_encode($new);
?>
result:
{"benchmark":[{"idText":"USGCB-Windows-7","title":"USGCB: Guidance for Securing Microsoft Windows 7 Systems for IT Professional","profile":[{"idText":"united_states_government_configuration_baseline_version_1.2.0.0","title":"United States Government Configuration Baseline 1.2.0.0","group":[{"idText":"security_components_overview","title":"Windows 7 Security Components Overview","group":[{"idText":"new_features","title":"New Features in Windows 7"}]},{"idText":"usgcb_security_settings","title":"USGCB Security Settings","group":[{"idText":"account_policies_group","title":"Account Policies group"}]}]}]}]}
=====change "group" to "children"=====
{"benchmark":[{"idText":"USGCB-Windows-7","title":"USGCB: Guidance for Securing Microsoft Windows 7 Systems for IT Professional","profile":[{"idText":"united_states_government_configuration_baseline_version_1.2.0.0","title":"United States Government Configuration Baseline 1.2.0.0","children":[{"idText":"security_components_overview","title":"Windows 7 Security Components Overview","children":[{"idText":"new_features","title":"New Features in Windows 7"}]},{"idText":"usgcb_security_settings","title":"USGCB Security Settings","children":[{"idText":"account_policies_group","title":"Account Policies group"}]}]}]}]}
Use array array_flip in php
$array = array ( [1] => Sell [2] => Buy [3] => Rent [4] => Jobs )
print_r(array_flip($array));
Array ( [Sell] => 1 [Buy] => 2 [Rent] => 3 [Jobs] => 4 )
I did this for an array of objects. Its basically creating new keys in the same array and unsetting the old keys.
public function transform($key, $results)
{
foreach($results as $k=>$result)
{
if( property_exists($result, $key) )
{
$results[$result->$key] = $result;
unset($results[$k]);
}
}
return $results;
}
<?php
$array[$new_key] = $array[$old_key];
unset($array[$old_key]);
?>
To have the same key I think they must be in separate nested arrays.
for ($i = 0; $i < count($array); $i++) {
$newArray[] = ['name' => $array[$i]];
};
Output:
0 => array:1 ["name" => "blabla"]
1 => array:1 ["name" => "blabla"]
2 => array:1 ["name" => "blblll"]
You could create a new array containing that array, so:
<?php
$array = array();
$array['name'] = $oldArray;
?>

Categories