php custom array_replace_recursive method - php

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.

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));

Merge column values from two arrays to form an indexed array of associative arrays

I have two arrays:
$a = ['0' => 1, '1' => 2, '2' => 3]
$b = ['0' => 4, '1' => 5, '2' => 6]
I want to create a new array like this:
[
['a' => 1, 'b' => '4'],
['a' => '2', 'b' => '5']
]
I have tried using array_merge and array_merge_recursive, but I wasn't able to get the right results.
$data = array_merge_recursive(array_values($urls), array_values($id));
You have to apply array_map() with custom function:
$newArray = array_map('combine',array_map(null, $a, $b));
function combine($n){
return array_combine(array('a','b'),$n);
}
print_r($newArray);
Output:-https://3v4l.org/okML7
Try this one
$c = array_merge($a,$b)
$d[] = array_reduce($d, 'array_merge', []);
It will merge the two array and reduce and remerge it.
You can use foreach to approach this
$a = ['0' => 1, '1' => 2, '2' => 3];
$b = ['0' => 4, '1' => 5, '2' => 6];
$res = [];
$i = 0;
$total = 2;
foreach($a as $k => $v){
$res[$i]['a'] = $v;
$res[$i]['b'] = $b[$k];
$i++;
if($i == $total) break;
}
The idea is to have an array $ab = ['a','b'] and a array from your both arrays like this $merged_array = [[1,4],[2,5],[3,6]]. Now we can combine array $ab with each element of $merged_array and that will be the result we need.
$first = ['0' => 1, '1' => 2, '2' => 3];
$second = ['0' => 4, '1' => 5, '2' => 6];
$merged_array = [];
for($i=0;$i<count($first);$i++)
{
array_push($merged_array,[$first[$i],$second[$i]]);
}
$final = [];
$ab = ['a','b'];
foreach($merged_array as $arr)
{
array_push($final,array_combine($ab, $arr));
}
print_r($final);
All earlier answers are working too hard. I see excessive iterations, iterated function calls, and counter variables.
Because there are only two arrays and the keys between the two arrays are identical, a simple foreach loop will suffice. Just push associative arrays into the result array.
Code: (Demo)
$a = ['0' => 1, '1' => 2, '2' => 3];
$b = ['0' => 4, '1' => 5, '2' => 6];
$result = [];
foreach ($a as $k => $v) {
$result[] = ['a' => $v, 'b' => $b[$k]];
}
var_export($result);
Output:
array (
0 =>
array (
'a' => 1,
'b' => 4,
),
1 =>
array (
'a' => 2,
'b' => 5,
),
2 =>
array (
'a' => 3,
'b' => 6,
),
)

How to replace key in multidimensional array and maintain order

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;
}

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)

How can I group same values in a multidimention array?

How can I group same values in a multidimention array?
I want this
array(
array('a' => 1, 'b' => 'hello'),
array('a' => 1, 'b' => 'world'),
array('a' => 2, 'b' => 'you')
)
to become
array(
array(
array('a' => 1, 'b' => 'hello'),
array('a' => 1, 'b' => 'world')
),
array('a' => 2, 'b' => 'you')
)
function array_gather(array $orig, $equality) {
$result = array();
foreach ($orig as $elem) {
foreach ($result as &$relem) {
if ($equality($elem, reset($relem))) {
$relem[] = $elem;
continue 2;
}
}
$result[] = array($elem);
}
return $result;
}
then
array_gather($arr,
function ($a, $b) { return $a['a'] == $b['a']; }
);
This could be implemented in a more efficient matter if all your groups could be reduced to a string value (in this case they can, but if your inner arrays were something like array('a' => ArbitraryObject) they could not be).

Categories