How to replace key in multidimensional array and maintain order - php

Given this array:
$list = array(
'one' => array(
'A' => 1,
'B' => 100,
'C' => 1234,
),
'two' => array(
'A' => 1,
'B' => 100,
'C' => 1234,
'three' => array(
'A' => 1,
'B' => 100,
'C' => 1234,
),
'four' => array(
'A' => 1,
'B' => 100,
'C' => 1234,
),
),
'five' => array(
'A' => 1,
'B' => 100,
'C' => 1234,
),
);
I need a function(replaceKey($array, $oldKey, $newKey)) to replace any key 'one', 'two', 'three', 'four' or 'five' with a new key independently of the depth of that key. I need the function to return a new array, with the same order and structure.
I already tried working with answers from this questions but I can't find a way to keep the order and access the second level in the array:
Changing keys using array_map on multidimensional arrays using PHP
Change array key without changing order
PHP rename array keys in multidimensional array
This is my attempt that doesn't work:
function replaceKey($array, $newKey, $oldKey){
foreach ($array as $key => $value){
if (is_array($value))
$array[$key] = replaceKey($value,$newKey,$oldKey);
else {
$array[$oldKey] = $array[$newKey];
}
}
return $array;
}
Regards

This function should replace all instances of $oldKey with $newKey.
function replaceKey($subject, $newKey, $oldKey) {
// if the value is not an array, then you have reached the deepest
// point of the branch, so return the value
if (!is_array($subject)) return $subject;
$newArray = array(); // empty array to hold copy of subject
foreach ($subject as $key => $value) {
// replace the key with the new key only if it is the old key
$key = ($key === $oldKey) ? $newKey : $key;
// add the value with the recursive call
$newArray[$key] = replaceKey($value, $newKey, $oldKey);
}
return $newArray;
}

Related

Group rows in a multidimensional array and sum the "count" elements in each group [duplicate]

This question already has answers here:
Group multidimensional array data based on two column values and sum values of one column in each group
(5 answers)
Closed 7 months ago.
I have an array of associative arrays and I would like to group them by the rows by the values for A, B, and C and sum the count value for each group.
$array = [
['A' => 'O', 'B' => 'O', 'C' => 1, 'count' => 1],
['A' => 'Test', 'B' => 'Test', 'C' => 1, 'count' => 1],
['A' => 'O', 'B' => 'O', 'C' => 1, 'count' => 1],
['A' => 'Test', 'B' => 'Test', 'C' => 1, 'count' => 1],
['A' => 'Test', 'B' => 'test1', 'C' => 2, 'count' => 1],
];
I need a result like this:
[
["A" => "O", "B" => "O", "C" => 1, "count" => 2],
["A" => "Test", "B" => "Test", "C" => 1, "count" => 2],
["A" => "Test", "B" => "test1", "C" => 2, "count" => 1]
]
In order to make this you need to loop through the array and check where the attributes "A", "B", "C" are equal. I tried doing this, but I couldn't fix it.
$countedArray[0] = $array[0];
foreach ($array as $item) {
$occKey = array_filter(
$countedArray,
function ($countedItem, $key) use ($array) {
if ($countedItem['A'] == $item['A']
&& $countedItem['B'] == $item['B']
&& $countedItem['C'] == $item['C']
) {
$countedItem[$key]['count'] = countedItem[$key]['count'] + 1
} else {
array_push(
$countedArray,
[
'A' => $item['A'],
'B' => $item['B'],
'C' => $item['C'],
'count' => 1
]
);
}
},
ARRAY_FILTER_USE_BOTH
);
}
I've done my best to make it less verbose. I welcome any suggestions. Here's my proposed solution:
function sumOccurrences(array $original): array
{
$summed = [];
foreach ($original as $value) {
// here we get the array without the 'count' key - everything we need to compare
$comparisonElement = array_filter($value, function ($key) {
return $key !== 'count';
}, ARRAY_FILTER_USE_KEY);
// we search with strict comparison (third param - true) - see reasoning below
$foundAt = array_search($comparisonElement, array_column($summed, 'element'), true);
if ($foundAt === false) {
// we separate the values we compare and the count for easier handling
$summed[] = ['element' => $comparisonElement, 'count' => $value['count']];
} else {
// if we've run into an existing element, just increase the count
$summed[$foundAt]['count'] += $value['count'];
}
}
// since we separated count from the values for comparison, we have to merge them now
return array_map(function ($a) {
// $a['count'] is wrapped in an array as it's just an integer
return array_merge($a['element'], ['count' => $a['count']]);
}, $summed);
}
In order to make it less verbose, I've opted to compare arrays directly. Other than being less verbose, another benefit is that this will work if additional key => value pairs are introduced to the array without any addition to logic. Everything that is not count gets compared, no matter how many pairs exist. It will also cover any nested arrays (for example 'C' => ['D' => 1]).
But, this comes at a price - we must use strict comparison because loose can give undesired results (for example, ['a'] == [0] will return true). Strict comparison also means that it won't work if any values are objects (strict comparison means it's checking for the same instance) and that arrays will only be matched if they have the same key => value pairs in the same order. This solution assumes that your array (and any nested ones) are already sorted.
If that is not the case, we'd have to sort it before comparing. Normally, ksort would do the job, but to support nested arrays, we'd have to devise a recursive sort by key:
function ksortRecursive(array &$array): void
{
ksort($array);
foreach ($array as &$value) {
if (is_array($value)) {
ksortRecursive($value);
}
}
}
and call it before we do array_search.
Now if we assume a starting array like in your example, the following should give you the desired result:
$original = [
['A' => 'O', 'B' => 'O', 'C' => 1, 'count' => 1],
['A' => 'Test', 'B' => 'Test', 'C' => 1, 'count' => 1],
['A' => 'O', 'B' => 'O', 'C' => 1, 'count' => 1],
['A' => 'Test', 'B' => 'Test', 'C' => 1, 'count' => 1],
['A' => 'Test', 'B' => 'test1', 'C' => 2, 'count' => 1],
];
var_dump(sumOccurrences($original));
The accepted answer is working waaaaay too hard for what is a basic task.
You merely need to use temporary, composite keys (based on the first three elements' values) to form grouped results. When a new row matches a pre-existing group, simply add its count to the stored count for the group. When the loop finishes, call array_values() to re-index the first level of the result array.
Code: (Demo)
$array = [
['A' => 'O', 'B' => 'O', 'C' => 1, 'count' => 1],
['A' => 'Test', 'B' => 'Test', 'C' => 1, 'count' => 1],
['A' => 'O', 'B' => 'O', 'C' => 1, 'count' => 1],
['A' => 'Test', 'B' => 'Test', 'C' => 1, 'count' => 1],
['A' => 'Test', 'B' => 'test1', 'C' => 2, 'count' => 1],
];
$result = [];
foreach ($array as $row) {
$key = implode('~', array_slice($row, 0, 3));
if (!isset($result[$key])) {
$result[$key] = $row;
} else {
$result[$key]['count'] += $row['count'];
}
}
var_export(array_values($result));

Add item to parent item within a multi dimensional array

I have a multi dimensional array, something like this:
$items = array(
'one' => array(
'a' => array(),
'b' => array(),
'c' => array()
),
'two' => array(
'd' => array(),
'e' => array(),
'f' => array()
),
'three' => array(
'g' => array(),
'h' => array(),
'i' => array()
)
);
So, I could add another array within the tree structure as:
$items['one']['c'][] = array( 'j' => array() );
But if I don't know that c is within one, how can I achieve this? I've looked at array_search but that just returns false. I think that is because what I'm looking for isn't at the top level of the array perhaps?
I know that c exists, but I don't know which sub-array it's in and how far down it goes. Is this possible?
If I understand you correctly, this function will do what you want:
<?php
$items = array(
'one' => array(
'a' => array(),
'b' => array(),
'c' => array()
),
'two' => array(
'd' => array(),
'e' => array(),
'f' => array()
),
'three' => array(
'g' => array(),
'h' => array(),
'i' => array()
)
);
function addToSub($items, $key, $addItem) {
foreach($items as &$item) {
if (in_array($key, array_keys($item))) {
$item[$key][] = $addItem;
}
}
return $items;
}
$items = addToSub($items, 'c', array( 'j' => array() ));
echo "<pre>";
var_dump($items);
echo "</pre>";
output:
array(3) {
["one"]=>
array(3) {
["a"]=>
array(0) {
}
["b"]=>
array(0) {
}
["c"]=>
array(1) {
[0]=>
array(1) {
["j"]=>
array(0) {
}
}
}
}
I think you are implementing a tree.
Your first thing to do is to find the node to append a child.
(In your example, the node is c and the child is j)
Implementing DFS search by recursion, I wrote following code.
(Make sure the node you're searching exists, or the function will return empty array)
<?php
$arr = [
'one' => [
'a' => [],
'b' => [],
'c' => [],
],
'two' => [
'd' => [],
'e' => [],
'f' => [],
],
'three' => [
'g' => [],
'h' => [],
'i' => [],
],
];
$arr = insertArr($arr, 'c', 'j');
print_r($arr);
function insertArr(array $arr, string $key, string $new): array {
// If key is found in $arr, append the child and return it.
if (array_key_exists($key, $arr)) {
$arr[$key][$new] = [];
return $arr;
}
// search the key in child node(next dimention)
foreach ($arr as $subArrKey => $subArr) {
// result will be empty if the key is not in child $subArr
$result = insertArr($subArr, $key, $new);
if (!empty($result)) {
// If the key is found in $subArr,
// replace $subArr with $result,
// which is the same as the $subArr but appended with child.
$arr[$subArrKey] = $result;
return $arr;
}
}
return [];
}
Now, you can append 'k' to either 'one', 'c' or 'j'.
To append 'k' to 'j', simply write:
$arr = insertArr($arr, 'j', 'k');
array_search will look for a value and in your example all your sub arrays are empty.
If I am not mistaken you could use array_map and for each subarray check if the key exists that you are looking for with array_key_exists:
$result = array_map(function($item) {
if (array_key_exists("c", $item)) {
$item["c"]["j"] = [];
}
return $item;
}, $items);
Demo

How do I combine two values from the same associative array?

I have an array that I need to get a value from within the same array that is unassigned to a variable:
return ['a' => 1, 'b'=> 'a', 'c' => 2];
So in this case I need 'b' to return the same value as 'a'. Which would be 1
Thanks for the help.
edit
I intend on running a function on b's value so the value of b is slightly different than a
return ['a' => 1, 'b'=> myFunction('a'), 'c' => 2];
You can try this way.
foreach ($array as $key => $agent) {
$array[$key]['agent_address_1'] = $agent['agent_company_1'] . ', ' .$agent['agent_address_1'];
unset($array[$key]['agent_company_1']);
}
What you want is not clear.
But i am assuming that you are trying to get the 'b' element of an array to be assigned a value similar to the value of 'a' element of that same array
If that is what you need, this will do it.
<?php
$a = array('a' => 1, 'b' => null, 'c' => 2);
$a['b'] = myFunction($a, 'a');
function myFunction($a, $b)
{
return $a[$b];
}
var_dump($a);
You can then return the array, or do what you want with it.
Maybe something like
<?php
function resolve(array $arr) {
foreach($arr as &$v) {
if ( isset($arr[$v])) {
$v = $arr[$v];
}
}
return $arr;
}
function foo() {
return resolve( ['a' => '5', 'b'=>'a', 'c' => '1'] );
}
var_export( foo() );
will do, prints
array (
'a' => '5',
'b' => '5',
'c' => '1',
)
But keep in mind that resolve( ['b'=>'a', 'a' => 'c', 'c' => '1'] ); will return
array (
'b' => 'c',
'a' => '1',
'c' => '1',
)
(you could resolve that with while( isset($arr[$v])) { instead of if ( isset($arr[$v]) ) { ...but there are most likely more elegant/performant ways to do that)

php custom array_replace_recursive method

I need a custom array_replace_recursive($array, $array1) method which does what the original array_replace_recursive($array, $array1) method does except that for indexed array it should use the array in the second array and overwrite the first array recursively.
example:
$a = array (
'a' => array(1,2,3),
'b' => array('a' => 1, 'b' => 2, 'c' => 3)
);
$b = array (
'a' => array(4),
'b' => array('d' => 1, 'e' => 2, 'f' => 3)
);
$c = array_replace_recursive($a, $b);
current behaviour:
$c = array (
'a' => array(4,2,3),
'b' => array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 1, 'e' => 2, 'f' => 3)
);
desired behaviour:
$c = array (
'a' => array(4),
'b' => array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 1, 'e' => 2, 'f' => 3)
);
as you can see element 'a' is an indexed array so the element in the second array has overwritten the element in the first array. element 'b' is an associative array so it maintains the original behaviour.
Below worked for me:
<?php
/**
* This method finds all the index arrays in array2 and replaces array in array1. it checks for indexed arrays recursively only within associative arrays.
* #param $array1
* #param $array2
*/
function customMerge(&$array1, &$array2) {
foreach ($array2 as $key => $val) {
if(is_array($val)) {
if(!isAssoc($val)) {
if($array1[$key] != $val) {
$array1[$key] = $val;
}
} else {
$array1_ = &$array1[$key];
$array2_ = &$array2[$key];
customMerge($array1_, $array2_);
}
}
}
}
function isAssoc($arr)
{
return array_keys($arr) !== range(0, count($arr) - 1);
}
$a = array (
'a' => array(1,2,3),
'b' => array('a' => 1, 'b' => 2, 'c' => 3, 'g' => array(
4,5,6
))
);
$b = array (
'a' => array(4),
'b' => array('d' => 1, 'e' => 2, 'f' => 3, 'g' => array(
7
))
);
$c = array_replace_recursive($a, $b); // first apply the original method
$expected = array (
'a' => array(4),
'b' => array('a' => 1, 'b' => 2, 'c' => 3, 'd' => 1, 'e' => 2, 'f' => 3, 'g'=> array(
7
)),
);
$d = $c; // create copy
customMerge($d, $b); // do the custom merge
echo $d == $expected;
The isAssoc() method in the first answer of this post is what your looking for:
How to check if PHP array is associative or sequential?
That method will check if the array is index and return true if it's the case.

replace array keys with given respective keys

I have an array like below
$old = array(
'a' => 'blah',
'b' => 'key',
'c' => 'amazing',
'd' => array(
0 => 'want to replace',
1 => 'yes I want to'
)
);
I have another array having keys to replace with key information.
$keyReplaceInfoz = array('a' => 'newA', 'b' => 'newB', 'c' => 'newC', 'd' => 'newD');
I need to replace all keys of array $old with respective values in array $keyReplaceInfo.
Output should be like this
$old = array(
'newA' => 'blah',
'newB' => 'key',
'newC' => 'amazing',
'newD' => array(
0 => 'want to replace',
1 => 'yes I want to'
)
);
I had to do it manually as below. I am expecting better option. can anyone suggest better way to accomplish this?
$new = array();
foreach ($old as $key => $value)
{
$new[$keyReplaceInfoz[$key]] = $value;
}
I know this can be more simpler.
array_combine(array_merge($old, $keyReplaceInfoz), $old)
I think this looks easier than what you posed.
array_combine(
['newKey1', 'newKey2', 'newKey3'],
array_values(['oldKey1' => 1, 'oldKey2' => 2, 'oldKey3' => 3])
);
This should do the trick as long as you have the same number of values and the same order.
IMO using array_combine, array_merge, even array_intersect_key is overkill.
The original code is good enough, and very fast.
Adapting #shawn-k solution, here is more cleaner code using array_walk, it will only replace desired keys, of course you can modify as per your convenience
array_walk($old, function($value,$key)use ($keyReplaceInfoz,&$old){
$newkey = array_key_exists($key,$keyReplaceInfoz)?$keyReplaceInfoz[$key]:false;
if($newkey!==false){$old[$newkey] = $value;unset($old[$key]);
}
});
print_r($old);
I just solved this same problem in my own application, but for my application $keyReplaceInfoz acts like the whitelist- if a key is not found, that whole element is removed from the resulting array, while the matching whitelisted keys get translated to the new values.
I suppose you could apply this same algorithm maybe with less total code by clever usage of array_map (http://php.net/manual/en/function.array-map.php), which perhaps another generous reader will do.
function filterOldToAllowedNew($key_to_test){
return isset($keyReplaceInfoz[$key_to_test])?$keyReplaceInfoz[$key_to_test]:false;
}
$newArray = array();
foreach($old as $key => $value){
$newkey = filterOldToAllowedNew($key);
if($newkey){
$newArray[$newkey] = $value;
}
}
print_r($newArray);
This question is old but since it comes up first on Google I thought I'd add solution.
// Subject
$old = array('foo' => 1, 'baz' => 2, 'bar' => 3));
// Translations
$tr = array('foo'=>'FOO', 'bar'=>'BAR');
// Get result
$new = array_combine(preg_replace(array_map(function($s){return "/^$s$/";},
array_keys($tr)),$tr, array_keys($old)), $old);
// Output
print_r($new);
Result:
Array
(
[FOO] => 1
[baz] => 2
[BAR] => 3
)
This the solution i have implemented for the same subject:
/**
* Replace keys of given array by values of $keys
* $keys format is [$oldKey=>$newKey]
*
* With $filter==true, will remove elements with key not in $keys
*
* #param array $array
* #param array $keys
* #param boolean $filter
*
* #return $array
*/
function array_replace_keys(array $array,array $keys,$filter=false)
{
$newArray=[];
foreach($array as $key=>$value)
{
if(isset($keys[$key]))
{
$newArray[$keys[$key]]=$value;
}
elseif(!$filter)
{
$newArray[$key]=$value;
}
}
return $newArray;
}
This works irrespective of array order & array count. Output order & value will be based on replaceKey.
$replaceKey = array('a' => 'newA', 'b' => 'newB', 'c' => 'newC', 'd' => 'newD', 'e' => 'newE','f'=>'newF');
$array = array(
'a' => 'blah',
'd' => array(
0 => 'want to replace',
1 => 'yes I want to'
),
'noKey'=>'RESIDUAL',
'c' => 'amazing',
'b' => 'key',
);
$filterKey = array_intersect_key($replaceKey,$array);
$filterarray = array_intersect_key(array_merge($filterKey,$array),$filterKey);
$replaced = array_combine($filterKey,$filterarray);
//output
var_export($replaced);
//array ( 'newA' => 'blah', 'newB' => 'key', 'newC' => 'amazing', 'newD' => array ( 0 => 'want to replace', 1 => 'yes I want to' ) )
If you're looking for a recursive solution to use on a multidimensional array, have a look at the below method. It will replace all keys requested, and leave all other keys alone.
/**
* Given an array and a set of `old => new` keys,
* will recursively replace all array keys that
* are old with their corresponding new value.
*
* #param mixed $array
* #param array $old_to_new_keys
*
* #return array
*/
function array_replace_keys($array, array $old_to_new_keys)
{
if(!is_array($array)){
return $array;
}
$temp_array = [];
$ak = array_keys($old_to_new_keys);
$av = array_values($old_to_new_keys);
foreach($array as $key => $value){
if(array_search($key, $ak, true) !== false){
$key = $av[array_search($key, $ak)];
}
if(is_array($value)){
$value = array_replace_keys($value, $old_to_new_keys);
}
$temp_array[$key] = $value;
}
return $temp_array;
}
Using OP's example array:
$old = array(
'a' => 'blah',
'b' => 'key',
'c' => 'amazing',
'd' => array(
0 => 'want to replace',
1 => 'yes I want to'
)
);
$replace = ["a" => "AA", 1 => 11];
var_export(array_replace_keys($old, $replace));
Gives the following output:
array (
'AA' => 'blah',
'b' => 'key',
'c' => 'amazing',
'd' =>
array (
0 => 'want to replace',
11 => 'yes I want to',
),
)
DEMO
Inspired by the following snippet.
This uses #Summoner's example but keeps #Leigh's hint in mind:
$start = microtime();
$array = [ "a" => 1, "b" => 2, "c" => 3 ];
function array_replace_key($array, $oldKey, $newKey) {
$keys = array_keys($array);
$idx = array_search($oldKey, $keys);
array_splice($keys, $idx, 1, $newKey);
return array_combine($keys, array_values($array));
}
print_r(array_replace_key($array, "b", "z"));
<?php
$new = array();
foreach ($old as $key => $value)
{
$new[$keyReplaceInfoz][$key] = $value;
}
?>

Categories