Related
I start with a nested array of some arbitrary depth. Within that array, some keys are a series of tokens separated by dots. For example "billingAddress.street" or "foo.bar.baz". I would like to expand those keyed elements to arrays, so the result is a nested array with all those keys expanded.
For example:
[
'billingAddress.street' => 'My Street',
'foo.bar.baz' => 'biz',
]
should be expanded to:
[
'billingAddress' => [
'street' => 'My Street',
],
'foo' => [
'bar' => [
'baz' => 'biz',
]
]
]
The original "billingAddress.street" can be left alongside the new "billingAddress" array, but it does not need to be (so the solution may operate on the original array or create a new array). Other elements such as "billingAddress.city" may need to be added to the same expanded portion of the array.
Some keys may have more than two tokens separated by dots, so will need to be expanded deeper.
I've looked at array_walk_recursive() but that only operates on elements. For each matching element key, I actually want to modify the parent array those elements are in.
I've looked at array_map, but that does not provide access to the keys, and as far as I know is not recursive.
An example array to expand:
[
'name' => 'Name',
'address.city' => 'City',
'address.street' => 'Street',
'card' => [
'type' => 'visa',
'details.last4' => '1234',
],
]
This is to be expanded to:
[
'name' => 'Name',
'address.city' => 'City', // Optional
'address' => [
'city' => 'City',
'street' => 'Street',
],
'address.street' => 'Street', // Optional
'card' => [
'type' => 'visa',
'details.last4' => '1234', // Optional
'details' => [
'last4' => '1234',
],
],
]
What I think I need, is something that walks to each array in the nested array and can apply a user function to it. But I do suspect I'm missing something obvious. The payment gateway I am working with sends me this mix of arrays and "pretend arrays" using the dot-notation, and my objective is to normalize it into an array for extracting portions.
I believe the problem differs from similar questions on SO due to this mix of arrays and non-arrays for expanding. Conceptually it is a nested array where sound groups of elements at any level need to be replaced with new arrays, so there are two levels of recursion happening here: the tree walking, and the expansion, and then the walking of the expanded trees to see if there is more expansion needed.
You could find it useful to reverse the order of the keys you get from exploding the combined (dotted) key. In that reversed order it is easier to progressively wrap a previous result into a new array, thereby creating the nested result for one dotted key/value pair.
Finally, that partial result can be merged into the accumulated "grand" result with the built-in array_merge_recursive function:
function expandKeys($arr) {
$result = [];
foreach($arr as $key => $value) {
if (is_array($value)) $value = expandKeys($value);
foreach(array_reverse(explode(".", $key)) as $key) $value = [$key => $value];
$result = array_merge_recursive($result, $value);
}
return $result;
}
See it run on repl.it
Here's a recursive attempt. Note that this doesn't delete old keys, doesn't maintain any key ordering and ignores keys of the type foo.bar.baz.
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
if (count($e) == 2) {
[$a, $b] = $e;
$data[$a][$b]= $v;
}
expand($data[$k]);
}
}
}
Result:
Array
(
[name] => Name
[address.city] => City
[address.street] => Street
[card] => Array
(
[type] => visa
[details.last4] => 1234
[details] => Array
(
[last4] => 1234
)
)
[address] => Array
(
[city] => City
[street] => Street
)
)
Explanation:
On any call of the function, if the parameter is an array, iterate through the keys and values looking for keys with a . in them. For any such keys, expand them out. Recursively call this function on all keys in the array.
Full version:
Here's a full version that supports multiple .s and cleans up keys afterwards:
function expand(&$data) {
if (is_array($data)) {
foreach ($data as $k => $v) {
$e = explode(".", $k);
$a = array_shift($e);
if (count($e) == 1) {
$data[$a][$e[0]] = $v;
}
else if (count($e) > 1) {
$data[$a][implode(".", $e)] = $v;
}
}
foreach ($data as $k => $v) {
expand($data[$k]);
if (preg_match('/\./', $k)) {
unset($data[$k]);
}
}
}
}
Another solution by #trincot has been accepted as being more elegant, and is the solution I am using now.
Here is my solution, which expands on the solution and tips given by #ggorlen
The approach I have taken is:
Create a new array rather than operate on the initial array.
No need to keep the old pre-expanded elements. They can be added easily if needed.
Expanding the keys is done one level at a time, from the root array, with the remaining expansions passed back in recursively.
The class method:
protected function expandKeys($arr)
{
$result = [];
while (count($arr)) {
// Shift the first element off the array - both key and value.
// We are treating this like a stack of elements to work through,
// and some new elements may be added to the stack as we go.
$value = reset($arr);
$key = key($arr);
unset($arr[$key]);
if (strpos($key, '.') !== false) {
list($base, $ext) = explode('.', $key, 2);
if (! array_key_exists($base, $arr)) {
// This will be another array element on the end of the
// arr stack, to recurse into.
$arr[$base] = [];
}
// Add the value nested one level in.
// Value at $arr['bar.baz.biz'] is now at $arr['bar']['baz.biz']
// We may also add to this element before we get to processing it,
// for example $arr['bar.baz.bam']
$arr[$base][$ext] = $value;
} elseif (is_array($value)) {
// We already have an array value, so give the value
// the same treatment in case any keys need expanding further.
$result[$key] = $this->expandKeys($value);
} else {
// A scalar value with no expandable key.
$result[$key] = $value;
}
}
return $result;
}
$result = $this->expandKeys($sourceArray)
I use this function like you'd use angular.extend() or $.extend() in my controllers.
function extend($base = array(), $replacements = array())
{
$base = !is_array($base) ? array() : $base;
$replacements = !is_array($replacements) ? array() : $replacements;
return array_replace_recursive($base, $replacements);
}
Standard use:
$inputs = extend([
'name' => 'test',
'empty'
], getInputs());
// getInputs() grabs the data from a form encoded or json body request.
I want empty to be a key so I can use this array with Laravel models or other objects later on.
I get:
[
[name] => 'test',
[0] => 'empty'
]
I want:
[
[name] => 'test',
[empty] => null
]
This should output: OK (1 test, 2 assertions)
public function testInputs()
{
$inputs = \Api::inputs([
'name' => 'test',
'empty'
]);
$this->assertArrayHasKey('name', $inputs);
$this->assertArrayHasKey('empty', $inputs);
}
Is there a native way to do this or will I be rolling my own?
In PHP, non-associative elements in arrays are treated as values for an associative array with automatic integer keys. The documentation states:
The key is optional. If it is not specified, PHP will use the
increment of the largest previously used integer key.
What that means is that ['abc', 'def'] is the same as [0 => 'abc', 1 => 'def'].
If you want your function to use values as keys, there is no way you will be able to differentiate an array that actually uses integers for keys from those that PHP automatically assigned due to missing keys.
I personally believe it is easier just to change the input to the function so that it is in the right format: ['name' => 'test', 'empty' => null].
If that is not an option and you know the values you receive will never have numbers as keys you need to change your function to something like the following:
function extend($base, $replacements) {
$base = !is_array($base) ? array() : $base;
$replacements = !is_array($replacements) ? array() : $replacements;
$ret = array();
foreach ($base as $key => $val) {
if (is_int($key)) { // value without key
$ret[$val] = null;
} else {
$ret[$key] = $val;
}
}
return array_replace_recursive($ret, $replacements);
}
I suspect I'm doing something stupid here, but I'm confused by what seems like a simple problem with SPL:
How do I modified the contents of an array (the values in this example), using a RecursiveArrayIterator / RecursiveIteratorIterator?
Using the follow test code, I can alter the value within the loop using getInnerIterator() and offsetSet(), and dump out the modified array while I'm within the loop.
But when I leave the loop and dump the array from the iterator, it's back to the original values. What's happening?
$aNestedArray = array();
$aNestedArray[101] = range(100, 1000, 100);
$aNestedArray[201] = range(300, 25, -25);
$aNestedArray[301] = range(500, 0, -50);
$cArray = new ArrayObject($aNestedArray);
$cRecursiveIter = new RecursiveIteratorIterator(new RecursiveArrayIterator($cArray), RecursiveIteratorIterator::LEAVES_ONLY);
// Zero any array elements under 200
while ($cRecursiveIter->valid())
{
if ($cRecursiveIter->current() < 200)
{
$cInnerIter = $cRecursiveIter->getInnerIterator();
// $cInnerIter is a RecursiveArrayIterator
$cInnerIter->offsetSet($cInnerIter->key(), 0);
}
// This returns the modified array as expected, with elements progressively being zeroed
print_r($cRecursiveIter->getArrayCopy());
$cRecursiveIter->next();
}
$aNestedArray = $cRecursiveIter->getArrayCopy();
// But this returns the original array. Eh??
print_r($aNestedArray);
It seems that values in plain arrays aren't modifiable because they can't be passed by reference to the constructor of ArrayIterator (RecursiveArrayIterator inherits its offset*() methods from this class, see SPL Reference). So all calls to offsetSet() work on a copy of the array.
I guess they chose to avoid call-by-reference because it doesn't make much sense in an object-oriented environment (i. e. when passing instances of ArrayObject which should be the default case).
Some more code to illustrate this:
$a = array();
// Values inside of ArrayObject instances will be changed correctly, values
// inside of plain arrays won't
$a[] = array(new ArrayObject(range(100, 200, 100)),
new ArrayObject(range(200, 100, -100)),
range(100, 200, 100));
$a[] = new ArrayObject(range(225, 75, -75));
// The array has to be
// - converted to an ArrayObject or
// - returned via $it->getArrayCopy()
// in order for this field to get handled properly
$a[] = 199;
// These values won't be modified in any case
$a[] = range(100, 200, 50);
// Comment this line for testing
$a = new ArrayObject($a);
$it = new RecursiveIteratorIterator(new RecursiveArrayIterator($a));
foreach ($it as $k => $v) {
// getDepth() returns the current iterator nesting level
echo $it->getDepth() . ': ' . $it->current();
if ($v < 200) {
echo "\ttrue";
// This line is equal to:
// $it->getSubIterator($it->getDepth())->offsetSet($k, 0);
$it->getInnerIterator()->offsetSet($k, 0);
}
echo ($it->current() == 0) ? "\tchanged" : '';
echo "\n";
}
// In this context, there's no real point in using getArrayCopy() as it only
// copies the topmost nesting level. It should be more obvious to work with $a
// itself
print_r($a);
//print_r($it->getArrayCopy());
You need to call getSubIterator at the current depth, use offsetSet at that depth, and do the same for all depths going back up the tree.
This is really useful for doing unlimited level array merge and replacements, on arrays or values within arrays. Unfortunately, array_walk_recursive will NOT work in this case as that function only visits leaf nodes.. so the 'replace_this_array' key in $array below will never be visited.
As an example, to replace all values within an array unknown levels deep, but only those that contain a certain key, you would do the following:
$array = [
'test' => 'value',
'level_one' => [
'level_two' => [
'level_three' => [
'replace_this_array' => [
'special_key' => 'replacement_value',
'key_one' => 'testing',
'key_two' => 'value',
'four' => 'another value'
]
],
'ordinary_key' => 'value'
]
]
];
$arrayIterator = new \RecursiveArrayIterator($array);
$completeIterator = new \RecursiveIteratorIterator($arrayIterator, \RecursiveIteratorIterator::SELF_FIRST);
foreach ($completeIterator as $key => $value) {
if (is_array($value) && array_key_exists('special_key', $value)) {
// Here we replace ALL keys with the same value from 'special_key'
$replaced = array_fill(0, count($value), $value['special_key']);
$value = array_combine(array_keys($value), $replaced);
// Add a new key?
$value['new_key'] = 'new value';
// Get the current depth and traverse back up the tree, saving the modifications
$currentDepth = $completeIterator->getDepth();
for ($subDepth = $currentDepth; $subDepth >= 0; $subDepth--) {
// Get the current level iterator
$subIterator = $completeIterator->getSubIterator($subDepth);
// If we are on the level we want to change, use the replacements ($value) other wise set the key to the parent iterators value
$subIterator->offsetSet($subIterator->key(), ($subDepth === $currentDepth ? $value : $completeIterator->getSubIterator(($subDepth+1))->getArrayCopy()));
}
}
}
return $completeIterator->getArrayCopy();
// return:
$array = [
'test' => 'value',
'level_one' => [
'level_two' => [
'level_three' => [
'replace_this_array' => [
'special_key' => 'replacement_value',
'key_one' => 'replacement_value',
'key_two' => 'replacement_value',
'four' => 'replacement_value',
'new_key' => 'new value'
]
],
'ordinary_key' => 'value'
]
]
];
Not using the Iterator classes (which seem to be copying data on the RecursiveArrayIterator::beginChildren() instead of passing by reference.)
You can use the following to achieve what you want
function drop_200(&$v) { if($v < 200) { $v = 0; } }
$aNestedArray = array();
$aNestedArray[101] = range(100, 1000, 100);
$aNestedArray[201] = range(300, 25, -25);
$aNestedArray[301] = range(500, 0, -50);
array_walk_recursive ($aNestedArray, 'drop_200');
print_r($aNestedArray);
or use create_function() instead of creating the drop_200 function, but your mileage may vary with the create_function and memory usage.
Looks like getInnerIterator creates a copy of the sub-iterator.
Maybe there is a different method? (stay tuned..)
Update: after hacking at it for a while, and pulling in 3 other engineers, it doesn't look like PHP gives you a way to alter the values of the subIterator.
You can always use the old stand by:
<?php
// Easy to read, if you don't mind references (and runs 3x slower in my tests)
foreach($aNestedArray as &$subArray) {
foreach($subArray as &$val) {
if ($val < 200) {
$val = 0;
}
}
}
?>
OR
<?php
// Harder to read, but avoids references and is faster.
$outherKeys = array_keys($aNestedArray);
foreach($outherKeys as $outerKey) {
$innerKeys = array_keys($aNestedArray[$outerKey]);
foreach($innerKeys as $innerKey) {
if ($aNestedArray[$outerKey][$innerKey] < 200) {
$aNestedArray[$outerKey][$innerKey] = 0;
}
}
}
?>
Convert the array to an object first and it works as expected..
$array = [
'one' => 'One',
'two' => 'Two',
'three' => [
'four' => 'Four',
'five' => [
'six' => 'Six',
'seven' => 'Seven'
]
]
];
// Convert to object (using whatever method you want)
$array = json_decode(json_encode($array));
$iterator = new RecursiveIteratorIterator(new RecursiveArrayIterator($array));
foreach($iterator as $key => $value) {
$iterator->getInnerIterator()->offsetSet($key, strtoupper($value));
}
var_dump($iterator->getArrayCopy());
I know this doesn't answer your question directly, but it's not a good practice to modify the object under iteration while iterating over it.
Could it come down to passing by reference vs passing by value?
For example try changing:
$cArray = new ArrayObject($aNestedArray);
to:
$cArray = new ArrayObject(&$aNestedArray);
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;
?>
I have an associative array in the form key => value where key is a numerical value, however it is not a sequential numerical value. The key is actually an ID number and the value is a count. This is fine for most instances, however I want a function that gets the human-readable name of the array and uses that for the key, without changing the value.
I didn't see a function that does this, but I'm assuming I need to provide the old key and new key (both of which I have) and transform the array. Is there an efficient way of doing this?
$arr[$newkey] = $arr[$oldkey];
unset($arr[$oldkey]);
The way you would do this and preserve the ordering of the array is by putting the array keys into a separate array, find and replace the key in that array and then combine it back with the values.
Here is a function that does just that:
function change_key( $array, $old_key, $new_key ) {
if( ! array_key_exists( $old_key, $array ) )
return $array;
$keys = array_keys( $array );
$keys[ array_search( $old_key, $keys ) ] = $new_key;
return array_combine( $keys, $array );
}
if your array is built from a database query, you can change the key directly from the mysql statement:
instead of
"select ´id´ from ´tablename´..."
use something like:
"select ´id´ **as NEWNAME** from ´tablename´..."
The answer from KernelM is nice, but in order to avoid the issue raised by Greg in the comment (conflicting keys), using a new array would be safer
$newarr[$newkey] = $oldarr[$oldkey];
$oldarr=$newarr;
unset($newarr);
$array = [
'old1' => 1
'old2' => 2
];
$renameMap = [
'old1' => 'new1',
'old2' => 'new2'
];
$array = array_combine(array_map(function($el) use ($renameMap) {
return $renameMap[$el];
}, array_keys($array)), array_values($array));
/*
$array = [
'new1' => 1
'new2' => 2
];
*/
You could use a second associative array that maps human readable names to the id's. That would also provide a Many to 1 relationship. Then do something like this:
echo 'Widgets: ' . $data[$humanreadbleMapping['Widgets']];
If you want also the position of the new array key to be the same as the old one you can do this:
function change_array_key( $array, $old_key, $new_key) {
if(!is_array($array)){ print 'You must enter a array as a haystack!'; exit; }
if(!array_key_exists($old_key, $array)){
return $array;
}
$key_pos = array_search($old_key, array_keys($array));
$arr_before = array_slice($array, 0, $key_pos);
$arr_after = array_slice($array, $key_pos + 1);
$arr_renamed = array($new_key => $array[$old_key]);
return $arr_before + $arr_renamed + $arr_after;
}
Simple benchmark comparison of both solution.
Solution 1 Copy and remove (order lost, but way faster) https://stackoverflow.com/a/240676/1617857
<?php
$array = ['test' => 'value', ['etc...']];
$array['test2'] = $array['test'];
unset($array['test']);
Solution 2 Rename the key https://stackoverflow.com/a/21299719/1617857
<?php
$array = ['test' => 'value', ['etc...']];
$keys = array_keys( $array );
$keys[array_search('test', $keys, true)] = 'test2';
array_combine( $keys, $array );
Benchmark:
<?php
$array = ['test' => 'value', ['etc...']];
for ($i =0; $i < 100000000; $i++){
// Solution 1
}
for ($i =0; $i < 100000000; $i++){
// Solution 2
}
Results:
php solution1.php 6.33s user 0.02s system 99% cpu 6.356 total
php solution1.php 6.37s user 0.01s system 99% cpu 6.390 total
php solution2.php 12.14s user 0.01s system 99% cpu 12.164 total
php solution2.php 12.57s user 0.03s system 99% cpu 12.612 total
If your array is recursive you can use this function:
test this data:
$datos = array
(
'0' => array
(
'no' => 1,
'id_maquina' => 1,
'id_transaccion' => 1276316093,
'ultimo_cambio' => 'asdfsaf',
'fecha_ultimo_mantenimiento' => 1275804000,
'mecanico_ultimo_mantenimiento' =>'asdfas',
'fecha_ultima_reparacion' => 1275804000,
'mecanico_ultima_reparacion' => 'sadfasf',
'fecha_siguiente_mantenimiento' => 1275804000,
'fecha_ultima_falla' => 0,
'total_fallas' => 0,
),
'1' => array
(
'no' => 2,
'id_maquina' => 2,
'id_transaccion' => 1276494575,
'ultimo_cambio' => 'xx',
'fecha_ultimo_mantenimiento' => 1275372000,
'mecanico_ultimo_mantenimiento' => 'xx',
'fecha_ultima_reparacion' => 1275458400,
'mecanico_ultima_reparacion' => 'xx',
'fecha_siguiente_mantenimiento' => 1275372000,
'fecha_ultima_falla' => 0,
'total_fallas' => 0,
)
);
here is the function:
function changekeyname($array, $newkey, $oldkey)
{
foreach ($array as $key => $value)
{
if (is_array($value))
$array[$key] = changekeyname($value,$newkey,$oldkey);
else
{
$array[$newkey] = $array[$oldkey];
}
}
unset($array[$oldkey]);
return $array;
}
I like KernelM's solution, but I needed something that would handle potential key conflicts (where a new key may match an existing key). Here is what I came up with:
function swapKeys( &$arr, $origKey, $newKey, &$pendingKeys ) {
if( !isset( $arr[$newKey] ) ) {
$arr[$newKey] = $arr[$origKey];
unset( $arr[$origKey] );
if( isset( $pendingKeys[$origKey] ) ) {
// recursion to handle conflicting keys with conflicting keys
swapKeys( $arr, $pendingKeys[$origKey], $origKey, $pendingKeys );
unset( $pendingKeys[$origKey] );
}
} elseif( $newKey != $origKey ) {
$pendingKeys[$newKey] = $origKey;
}
}
You can then cycle through an array like this:
$myArray = array( '1970-01-01 00:00:01', '1970-01-01 00:01:00' );
$pendingKeys = array();
foreach( $myArray as $key => $myArrayValue ) {
// NOTE: strtotime( '1970-01-01 00:00:01' ) = 1 (a conflicting key)
$timestamp = strtotime( $myArrayValue );
swapKeys( $myArray, $key, $timestamp, $pendingKeys );
}
// RESULT: $myArray == array( 1=>'1970-01-01 00:00:01', 60=>'1970-01-01 00:01:00' )
Here is a helper function to achieve that:
/**
* Helper function to rename array keys.
*/
function _rename_arr_key($oldkey, $newkey, array &$arr) {
if (array_key_exists($oldkey, $arr)) {
$arr[$newkey] = $arr[$oldkey];
unset($arr[$oldkey]);
return TRUE;
} else {
return FALSE;
}
}
pretty based on #KernelM answer.
Usage:
_rename_arr_key('oldkey', 'newkey', $my_array);
It will return true on successful rename, otherwise false.
this code will help to change the oldkey to new one
$i = 0;
$keys_array=array("0"=>"one","1"=>"two");
$keys = array_keys($keys_array);
for($i=0;$i<count($keys);$i++) {
$keys_array[$keys_array[$i]]=$keys_array[$i];
unset($keys_array[$i]);
}
print_r($keys_array);
display like
$keys_array=array("one"=>"one","two"=>"two");
Easy stuff:
this function will accept the target $hash and $replacements is also a hash containing newkey=>oldkey associations.
This function will preserve original order, but could be problematic for very large (like above 10k records) arrays regarding performance & memory.
function keyRename(array $hash, array $replacements) {
$new=array();
foreach($hash as $k=>$v)
{
if($ok=array_search($k,$replacements))
$k=$ok;
$new[$k]=$v;
}
return $new;
}
this alternative function would do the same, with far better performance & memory usage, at the cost of losing original order (which should not be a problem since it is hashtable!)
function keyRename(array $hash, array $replacements) {
foreach($hash as $k=>$v)
if($ok=array_search($k,$replacements))
{
$hash[$ok]=$v;
unset($hash[$k]);
}
return $hash;
}
This page has been peppered with a wide interpretation of what is required because there is no minimal, verifiable example in the question body. Some answers are merely trying to solve the "title" without bothering to understand the question requirements.
The key is actually an ID number and the value is a count. This is
fine for most instances, however I want a function that gets the
human-readable name of the array and uses that for the key, without
changing the value.
PHP keys cannot be changed but they can be replaced -- this is why so many answers are advising the use of array_search() (a relatively poor performer) and unset().
Ultimately, you want to create a new array with names as keys relating to the original count. This is most efficiently done via a lookup array because searching for keys will always outperform searching for values.
Code: (Demo)
$idCounts = [
3 => 15,
7 => 12,
8 => 10,
9 => 4
];
$idNames = [
1 => 'Steve',
2 => 'Georgia',
3 => 'Elon',
4 => 'Fiona',
5 => 'Tim',
6 => 'Petra',
7 => 'Quentin',
8 => 'Raymond',
9 => 'Barb'
];
$result = [];
foreach ($idCounts as $id => $count) {
if (isset($idNames[$id])) {
$result[$idNames[$id]] = $count;
}
}
var_export($result);
Output:
array (
'Elon' => 15,
'Quentin' => 12,
'Raymond' => 10,
'Barb' => 4,
)
This technique maintains the original array order (in case the sorting matters), doesn't do any unnecessary iterating, and will be very swift because of isset().
If you want to replace several keys at once (preserving order):
/**
* Rename keys of an array
* #param array $array (asoc)
* #param array $replacement_keys (indexed)
* #return array
*/
function rename_keys($array, $replacement_keys) {
return array_combine($replacement_keys, array_values($array));
}
Usage:
$myarr = array("a" => 22, "b" => 144, "c" => 43);
$newkeys = array("x","y","z");
print_r(rename_keys($myarr, $newkeys));
//must return: array("x" => 22, "y" => 144, "z" => 43);
You can use this function based on array_walk:
function mapToIDs($array, $id_field_name = 'id')
{
$result = [];
array_walk($array,
function(&$value, $key) use (&$result, $id_field_name)
{
$result[$value[$id_field_name]] = $value;
}
);
return $result;
}
$arr = [0 => ['id' => 'one', 'fruit' => 'apple'], 1 => ['id' => 'two', 'fruit' => 'banana']];
print_r($arr);
print_r(mapToIDs($arr));
It gives:
Array(
[0] => Array(
[id] => one
[fruit] => apple
)
[1] => Array(
[id] => two
[fruit] => banana
)
)
Array(
[one] => Array(
[id] => one
[fruit] => apple
)
[two] => Array(
[id] => two
[fruit] => banana
)
)
This basic function handles swapping array keys and keeping the array in the original order...
public function keySwap(array $resource, array $keys)
{
$newResource = [];
foreach($resource as $k => $r){
if(array_key_exists($k,$keys)){
$newResource[$keys[$k]] = $r;
}else{
$newResource[$k] = $r;
}
}
return $newResource;
}
You could then loop through and swap all 'a' keys with 'z' for example...
$inputs = [
0 => ['a'=>'1','b'=>'2'],
1 => ['a'=>'3','b'=>'4']
]
$keySwap = ['a'=>'z'];
foreach($inputs as $k=>$i){
$inputs[$k] = $this->keySwap($i,$keySwap);
}
This function will rename an array key, keeping its position, by combining with index searching.
function renameArrKey($arr, $oldKey, $newKey){
if(!isset($arr[$oldKey])) return $arr; // Failsafe
$keys = array_keys($arr);
$keys[array_search($oldKey, $keys)] = $newKey;
$newArr = array_combine($keys, $arr);
return $newArr;
}
Usage:
$arr = renameArrKey($arr, 'old_key', 'new_key');
this works for renaming the first key:
$a = ['catine' => 'cat', 'canine' => 'dog'];
$tmpa['feline'] = $a['catine'];
unset($a['catine']);
$a = $tmpa + $a;
then, print_r($a) renders a repaired in-order array:
Array
(
[feline] => cat
[canine] => dog
)
this works for renaming an arbitrary key:
$a = ['canine' => 'dog', 'catine' => 'cat', 'porcine' => 'pig']
$af = array_flip($a)
$af['cat'] = 'feline';
$a = array_flip($af)
print_r($a)
Array
(
[canine] => dog
[feline] => cat
[porcine] => pig
)
a generalized function:
function renameKey($oldkey, $newkey, $array) {
$val = $array[$oldkey];
$tmp_A = array_flip($array);
$tmp_A[$val] = $newkey;
return array_flip($tmp_A);
}
There is an alternative way to change the key of an array element when working with a full array - without changing the order of the array.
It's simply to copy the array into a new array.
For instance, I was working with a mixed, multi-dimensional array that contained indexed and associative keys - and I wanted to replace the integer keys with their values, without breaking the order.
I did so by switching key/value for all numeric array entries - here: ['0'=>'foo']. Note that the order is intact.
<?php
$arr = [
'foo',
'bar'=>'alfa',
'baz'=>['a'=>'hello', 'b'=>'world'],
];
foreach($arr as $k=>$v) {
$kk = is_numeric($k) ? $v : $k;
$vv = is_numeric($k) ? null : $v;
$arr2[$kk] = $vv;
}
print_r($arr2);
Output:
Array (
[foo] =>
[bar] => alfa
[baz] => Array (
[a] => hello
[b] => world
)
)
best way is using reference, and not using unset (which make another step to clean memory)
$tab = ['two' => [] ];
solution:
$tab['newname'] = & $tab['two'];
you have one original and one reference with new name.
or if you don't want have two names in one value is good make another tab and foreach on reference
foreach($tab as $key=> & $value) {
if($key=='two') {
$newtab["newname"] = & $tab[$key];
} else {
$newtab[$key] = & $tab[$key];
}
}
Iterration is better on keys than clone all array, and cleaning old array if you have long data like 100 rows +++ etc..
One which preservers ordering that's simple to understand:
function rename_array_key(array $array, $old_key, $new_key) {
if (!array_key_exists($old_key, $array)) {
return $array;
}
$new_array = [];
foreach ($array as $key => $value) {
$new_key = $old_key === $key
? $new_key
: $key;
$new_array[$new_key] = $value;
}
return $new_array;
}
Here is an experiment (test)
Initial array (keys like 0,1,2)
$some_array[] = '6110';//
$some_array[] = '6111';//
$some_array[] = '6210';//
I must change key names to for example human_readable15, human_readable16, human_readable17
Something similar as already posted. During each loop i set necessary key name and remove corresponding key from the initial array.
For example, i inserted into mysql $some_array got lastInsertId and i need to send key-value pair back to jquery.
$first_id_of_inserted = 7;//lastInsertId
$last_loop_for_some_array = count($some_array);
for ($current_loop = 0; $current_loop < $last_loop_for_some_array ; $current_loop++) {
$some_array['human_readable'.($first_id_of_inserted + $current_loop)] = $some_array[$current_loop];//add new key for intial array
unset( $some_array[$current_loop] );//remove already renamed key from array
}
And here is the new array with renamed keys
echo '<pre>', print_r($some_array, true), '</pre>$some_array in '. basename(__FILE__, '.php'). '.php <br/>';
If instead of human_readable15, human_readable16, human_readable17 need something other. Then could create something like this
$arr_with_key_names[] = 'human_readable';
$arr_with_key_names[] = 'something_another';
$arr_with_key_names[] = 'and_something_else';
for ($current_loop = 0; $current_loop < $last_loop_for_some_array ; $current_loop++) {
$some_array[$arr_with_key_names[$current_loop]] = $some_array[$current_loop];//add new key for intial array
unset( $some_array[$current_loop] );//remove already renamed key from array
}
Hmm, I'm not test before, but I think this code working
function replace_array_key($data) {
$mapping = [
'old_key_1' => 'new_key_1',
'old_key_2' => 'new_key_2',
];
$data = json_encode($data);
foreach ($mapping as $needed => $replace) {
$data = str_replace('"'.$needed.'":', '"'.$replace.'":', $data);
}
return json_decode($data, true);
}
You can write simple function that applies the callback to the keys of the given array. Similar to array_map
<?php
function array_map_keys(callable $callback, array $array) {
return array_merge([], ...array_map(
function ($key, $value) use ($callback) { return [$callback($key) => $value]; },
array_keys($array),
$array
));
}
$array = ['a' => 1, 'b' => 'test', 'c' => ['x' => 1, 'y' => 2]];
$newArray = array_map_keys(function($key) { return 'new' . ucfirst($key); }, $array);
echo json_encode($array); // {"a":1,"b":"test","c":{"x":1,"y":2}}
echo json_encode($newArray); // {"newA":1,"newB":"test","newC":{"x":1,"y":2}}
Here is a gist https://gist.github.com/vardius/650367e15abfb58bcd72ca47eff096ca#file-array_map_keys-php.