I have as input a 2 dimensional array. Example:
$input = [
[1,2,3],
[11, "a" => "val_a"],
[12,'b' => 'val_b', 'a' => 'val2_a'],
[2 => 22]
];
I want to convert it to an array with a tabular structure. In the table-like structure, all sub-arrays (rows) have exactly the same key in the same order. These keys are the union of all keys of the 2nd level.
New Keys: [0, 1, 2, 'a', 'b']
Elements that do not exist in the input array receive the value NULL. I expect the following result:
$expected = [
[ 0 => 1, 1 => 2, 2 => 3, 'a' => NULL, 'b' => NULL],
[ 0 => 11, 1 => NULL, 2 => NULL, 'a' => "val_a", 'b' => NULL],
[ 0 => 12, 1 => NULL, 2 => NULL, 'a' => "val2_a", 'b' => "val_b"],
[ 0 => NULL, 1 => NULL, 2 => 22, 'a' => NULL, 'b' => NULL]
];
What I've tried so far:
function rectify($array){
$maxRow = [];
foreach($array as $row){
$maxRow += $row;
}
$new = [];
foreach($array as $key => $row){
foreach($maxRow as $mKey => $mVal){
$new[$key][$mKey] = array_key_exists($mKey,$row) ? $row[$mKey] : NULL;
}
}
return $new;
}
The function delivers correct results. The solution is very complex due to 2 nested loops with one query.
Another attempt returned the same keys, but not in the same order.
This code first identifies all of the columns in the data. It then creates an empty template array (using array_unique to remove duplicates).
It then loops over the rows again and uses array_replace to fill the values in for each row...
$headers = [];
foreach ( $input as $row ) {
$headers = array_merge($headers, array_keys($row));
}
$headers = array_fill_keys(array_unique($headers), null);
$output = [];
foreach ( $input as $row ) {
$output[] = array_replace($headers, $row);
}
Related
I have two arrays one contains ids that's going to check if it exists on the second array which is an associative array:
Array 1: [1,2,11, 4]
Array 2:
[["id" => 1, "name" => "abc"], ["id" => 2, "name"=> "xyz"]]
Currently using a nested foreach to iterate over and match them but I wanted to know if there was anything more efficient as the arrays will be a lot larger.
$item = [1,2,11, 4];
$data = [["id" => 1, "name" => "abc"], ["id" => 2, "name"=> "xyz"]];
foreach($items as $item)
{
foreach($data as $key => $products)
{
foreach($products as $product)
{
if($product['id'] == $item)
{
echo $product['name'];
}
}
}
}
Had a look at this and this but none really fit my scenario.
Any help appreciated.
array_filter would be an option:
$ids = [ 1, 2, 11, 4 ];
$data = [ [ 'id' => 1, 'name' => 'abc' ], [ 'id' => 2, 'name' => 'xyz' ], [ 'id' => 3, 'name' => 'nono' ] ];
$result = array_filter($data, fn($value) => (in_array($value['id'], $ids)));
You can use array_column() to get all product ids as an indexed array.
Then you can use array_intersect() to fetch the id's that exists in both arrays.
$item = [1,2,11, 4];
$products = [["id" => 1, "name" => "abc"], ["id" => 2, "name"=> "xyz"]];
// Get the ids of all products as an indexed array
$prodIds = array_column($products, 'id');
// Check the id's that exists in both
$existingIds = array_intersect($item, $prodIds);
Demo: https://3v4l.org/HJSoq
Of if you rather do it as a one-liner:
$existingIds = array_intersect($item, array_column($products, 'id'));
You can also use the array_map and the anonymous function.
$item = [1, 2, 11, 4];
$products = [["id" => 1, "name" => "abc"], ["id" => 2, "name" => "xyz"]];
$result = array_map(function ($row) use ($item) {
return (in_array($row['id'], $item))?$row:false;
}, $products);
Result dump:
Array
(
[0] => Array
(
[id] => 1
[name] => abc
)
[1] => Array
(
[id] => 2
[name] => xyz
)
)
In PHP, array_replace_recursive() does two things according to the documentation:
If a key from the first array exists in the second array, its value will be replaced by the value from the second array.
If the key exists in the second array, and not the first, it will be created in the first array.
Is there an alternative that only does the replacement, and doesn't create new keys?
For example:
$array = [
'apple' => TRUE,
'pear' => TRUE,
'basket' => [
'banana' => TRUE,
],
'punnet' => [
'strawberry' => TRUE,
],
];
$replacement = [
'banana' => [
'REPLACEMENT!'
],
];
The result should be:
$array = [
'apple' => TRUE,
'pear' => TRUE,
'basket' => [
'banana' => [
'REPLACEMENT!'
],
],
'punnet' => [
'strawberry' => TRUE,
],
];
You will need to use array_intersect_key() to create an array that contains only the keys that are in the two arrays, then you can merge.
$array1 = [
'a' => 1,
'b' => 2,
'c' => 3,
'd' => 4,
];
$array2 = [
'b' => 14,
'c' => 70,
'f' => 5,
];
// $array2 has to be the first arguments for $inter to have its value instead of the value of $array1
$inter = array_intersect_key($array2, $array1);
$merged = array_replace_recursive($array1, $inter);
// Merged will be:
[
'a' => 1,
'b' => 14,
'c' => 70,
'd' => 4,
];
array_intersect_key
EDIT
For this to work recursively, you can use this function found here
/**
* Recursively computes the intersection of arrays using keys for comparison.
*
* #param array $array1 The array with master keys to check.
* #param array $array2 An array to compare keys against.
* #return array associative array containing all the entries of array1 which have keys that are present in array2.
**/
function array_intersect_key_recursive(array $array1, array $array2) {
$array1 = array_intersect_key($array1, $array2);
foreach ($array1 as $key => &$value) {
if (is_array($value) && is_array($array2[$key])) {
$value = array_intersect_key_recursive($value, $array2[$key]);
}
}
return $array1;
}
If you don't like creation of non existant keys, you can use array_replace_recursive() as is and then you can roll out your own recursive version which would remove all extra keys. You may however roll out your own recursive replace version instead of the below 2 pass solution, but I would prefer the below described to avoid re-inventing the wheel of array_replace_recursive()(since you know what the wheel is about).
function removeNonExistantKeys(&$basket,&$base){
$keys = array_diff_key($basket, $base);
foreach($keys as $unwanted_key => $value){
unset($basket[ $unwanted_key ]);
}
foreach($basket as $key => &$value){
if(is_array($value) && is_array( $base[ $key ] )){
removeNonExistantKeys($value , $base[ $key ]);
}
}
}
We use & references wherever needed to edit the same copy of the array.
The above function uses array_diff_key to find difference between 2 sets of array in terms of keys and unsets all of them in the next foreach.
We then walk recursively to both modified and initial arrays into it's sub children performing the same task.
Driver code:
<?php
$base = array('citrus' => array( "orange") , 'berries' => array("blackberry", "raspberry"),'a' => ['b' => 'f']);
$replacements = array('citrus' => array('pineapple'), 'berries' => array('blueberry'),'a' => ['b' => 'd','e' => 'ab']);
$basket = array_replace_recursive($base, $replacements);
function removeNonExistantKeys(&$basket,&$base){
$keys = array_diff_key($basket, $base);
foreach($keys as $unwanted_key => $value){
unset($basket[ $unwanted_key ]);
}
foreach($basket as $key => &$value){
if(is_array($value) && is_array( $base[ $key ] )){
removeNonExistantKeys($value , $base[ $key ]);
}
}
}
removeNonExistantKeys($basket,$base);
print_r($basket);
This does what I needed:
array_walk_recursive($array, function(&$value, $key, $replacements) {
if (isset($replacements[$key])) {
$value = $replacements[$key];
}
}, ['replace' => 'replacement']);
Please, consider the following arrays:
$reference = array(
'080604' => 4,
'080703' => 4,
'080734' => 2,
'080819' => 2,
'088341' => 2,
'805238' => 20,
'805283' => 4,
'805290' => 2,
'805849' => 2,
'806051' => 2,
'806068' => 2,
);
$test = array(
'080604' => 2,
'080703' => 4,
'080819' => 1,
'088341' => 2,
'805238' => 20,
'805283' => 4,
'805290' => 2,
'805849' => 2,
'806051' => 2,
'806068' => 2,
);
They are quite similar, but can have some various differences, e.g. it's possible that:
- some keys of $reference are not present in $test at all
- some keys of $test are not present in $reference at all
- all keys are present, but the values in $reference and $test are different (sometimes $reference value is bigger than $test and sometimes the value of $test is bigger than $reference)
I need to find out the differences automatically and to output them in a way, that not only the difference in count itself, but also a description is provided, e.g.
$result = [
'080604' => [
'reference' => 4,
'test' => 2
]
];
If some value is in only one of the lists:
$result = [
'1234567890' => [
'reference' => 0,
'test' => 2
]
];
or something like that.
Does someone have an idea, which is the best way to accomplish this in an elegant way? Thank you very much!
Iterate over each and populate the array with values if present:
$combined = [];
foreach ($reference as $key => $val) {
$combined[$key] = [
'test' => 0,
'reference' => $val,
];
}
foreach ($test as $key => $val) {
if (!isset($combined[$key])) {
$combined[$key] = [
'reference' => 0,
'test' => 0,
]
}
$combined[$key]['test'] = $val;
}
$combined will contain both values from both arrays with reference to both the elements from $reference and $test.
try
$result = array_diff($reference, $test);
print_r($result)
This question already has answers here:
Group 2d array data by one column and sum other columns in each group (separately)
(4 answers)
Closed last month.
I have 2 arrays:
$array1 = [
['amount' => 21600.00, 'rows' => 2, 'student_id' => 1],
];
$array2 = [
['amount' => 541990.00, 'rows' => 512, 'student_id' => 1],
['amount' => 347480.00, 'rows' => 281, 'student_id' => 2],
['amount' => 507400.00, 'rows' => 214, 'student_id' => 3],
];
I want to merge both arrays based on the same student_id value. When a student_id is found in both arrays, I want to sum the two amount values and the two rows values.
Expected result:
array (
0 =>
array (
'amount' => 563590.0,
'rows' => 514,
'student_id' => 1,
),
1 =>
array (
'amount' => 347480.0,
'rows' => 281,
'student_id' => 2,
),
2 =>
array (
'amount' => 507400.0,
'rows' => 214,
'student_id' => 3,
),
)
I have tried using array_merge() and array_merge_recursive() but they didn't give me the expected result.
Here is simple code to solve your problem,
count($arr) > count($arr0) ? ($greater = $arr AND $smaller = $arr0) : ($smaller = $arr AND $greater = $arr0);
foreach ($greater as $key => &$value) {
foreach ($smaller as $key1 => &$value1) {
if($value['student_id'] == $value1['student_id']){
$value['amount'] +=$value1['amount'];
$value['rows'] +=$value1['rows'];
}
}
}
Check output here
The comment about combining the queries is probably the better approach.
SELECT student_id, sum(amount) as amount, sum(rows) as rows
from students
group by student_id;
However, if you can't do it via a query, PHP doesn't natively add array values together (it doesn't make sense)
Here's what you'd have to do
function arrayMergeMatch(&$a, &$b)
{
foreach ($a as $key => $value) {
if (array_key_exists($key, $b)) {
// there's a matching index in both a & b
$a[$key] = [
$a[$key]['amount'] += $b[$key]['amount'],
$a[$key]['rows'] += $b[$key]['rows'],
$a[$key]['student_id'],
];
}
}
}
$firstArray = $secondArray = [];
$firstArray[0] = [
'amount' => 21600.00,
'rows' => 2,
'student_id' => 1
];
$secondArray[0] = [
'amount' => 541990.00,
'rows' => 512,
'student_id' => 1,
];
$secondArray[1] = [
'amount' => 347480.00,
'rows' => 281,
'student_id' => 2,
];
$secondArray[2] = [
'amount' => 507400.00,
'rows' => 214,
'student_id' => 3,
];
$firstArrayLength = count($x);
$secondArrayLength = count($y);
$combinedArray = null;
if ($firstArrayLength > $secondArrayLength) {
// loop through x checking to see if there's a matching y
arrayMergeMatch($firstArray, $secondArray);
$combinedArray = $firstArray;
}
else {
// y is longer than or equal to x, so loop through y, looking for matching
// index in x
arrayMergeMatch($secondArray, $firstArray);
$combinedArray = $secondArray;
}
print_r($combinedArray);
Nested loops are not necessary. There will be several different styles, but the crux of the task is to create a result array that temporarily serves as a lookup array while you iterate the incoming data.
To do this, assign temporary keys using student_id values. When a student_id is encountered after it is found in the result array, just add the values to the stored related values in the group. When finished iterating, re-index the result with array_values().
Functional style: (Demo1) (Demo2 - likely slower)
var_export(
array_values(
array_reduce(
$array2,
function($result, $row) {
if (!isset($result[$row['student_id']])) {
$result[$row['student_id']] = $row;
} else {
$result[$row['student_id']]['amount'] += $row['amount'];
$result[$row['student_id']]['rows'] += $row['rows'];
}
return $result;
},
array_column($array1, null, 'student_id')
)
)
);
Language construct loop: (Demo1) (Demo2 - likely slower)
$result = array_column($array1, null, 'student_id');
foreach ($array2 as $row) {
if (!isset($result[$row['student_id']])) {
$result[$row['student_id']] = $row;
} else {
$result[$row['student_id']]['amount'] += $row['amount'];
$result[$row['student_id']]['rows'] += $row['rows'];
}
}
var_export(array_values($result));
All of the above snippets should be used instead of the other posted answers because they will never iterate more than m + n. Here's a critique of the other answers:
Rahul's answer is using a nested loop -- this means the number of iterations will be m*n (number of rows in array1 multiplied by number of rows in array2) -- this is very indirect/inefficient. Even if a break was used, it still wouldn't be as direct/efficient as my snippets.
Kearney's answer is completely ignoring the requirement to sort on student_id values (even after the $x and $y typos are fixed). This php-based answer is simply incorrect. Proof. Less importantly, $b does not need to be made modifiable by reference.
I need to remove rows from my input array where duplicate values occur in a specific column.
Sample array:
$array = [
['user_id' => 82, 'ac_type' => 1],
['user_id' => 80, 'ac_type' => 5],
['user_id' => 76, 'ac_type' => 1],
['user_id' => 82, 'ac_type' => 1],
['user_id' => 80, 'ac_type' => 5]
];
I'd like to filter by user_id to ensure uniqueness and achieve this result:
So, my output will be like this:
[
['user_id' => 82, 'ac_type' => 1],
['user_id' => 80, 'ac_type' => 5],
['user_id' => 76, 'ac_type' => 1]
]
I've already tried with:
$result = array_unique($array, SORT_REGULAR);
and
$result = array_map("unserialize", array_unique(array_map("serialize", $array)));
and
$result = array();
foreach ($array as $k => $v) {
$results[implode($v)] = $v;
}
$results = array_values($results);
print_r($results);
but duplicate rows still exist.
For a clearer "minimal, complete, verifiable example", I'll use the following input array in my demos:
$array = [
['user_id' => 82, 'ac_type' => 1],
['user_id' => 80, 'ac_type' => 5],
['user_id' => 76, 'ac_type' => 1],
['user_id' => 82, 'ac_type' => 2],
['user_id' => 80, 'ac_type' => 5]
];
// elements [0] and [3] have the same user_id, but different ac_type
// elements [1] and [4] have identical row data
Unconditionally push rows into a result array and assign associative first-level keys, then re-index with array_values(). This approach overwrites earlier duplicate rows with later occurring ones.
array_column demo:
var_export(array_values(array_column($array, null, 'user_id')));
foreach demo:
$result = [];
foreach ($array as $row) {
$result[$row['user_id']] = $row;
}
var_export(array_values($result));
Output:
[
['user_id' => 82, 'ac_type' => 2], // was input row [3]
['user_id' => 80, 'ac_type' => 5], // was input row [4]
['user_id' => 76, 'ac_type' => 1] // was input row [2]
]
Use a condition or the null coalescing assignment operator to preserve the first occurring row while removing duplicates.
foreach null coalescing assignment demo:
foreach ($array as $a) {
$result[$a['user_id']] ??= $a; // only store if first occurrence of user_id
}
var_export(array_values($result)); // re-index and print
foreach isset demo:
foreach ($array as $a) {
if (!isset($result[$a['user_id']])) {
$result[$a['user_id']] = $a; // only store if first occurrence of user_id
}
}
var_export(array_values($result)); // re-index and print
Output:
[
['user_id' => 82, 'ac_type' => 1], // was input row [0]
['user_id' => 80, 'ac_type' => 5], // was input row [1]
['user_id' => 76, 'ac_type' => 1] // was input row [2]
]
It is also possible to unconditionally push data AND avoid a condition, but the row order may differ between the input and output (if it matters to you).
array_reverse, array_column demo:
var_export(array_values(array_column(array_reverse($array), null, 'user_id')));
array_reduce demo:
var_export(
array_values(
array_reduce(
$array,
fn($res, $row) => array_replace([$row['user_id'] => $row], $res),
[]
)
)
);
foreach array_reverse demo:
$result = [];
foreach (array_reverse($array) as $row) {
$result[$row['user_id']] = $row;
}
var_export(array_values($result));
Output:
[
['user_id' => 80, 'ac_type' => 5], // was input row [1]
['user_id' => 82, 'ac_type' => 1], // was input row [0]
['user_id' => 76, 'ac_type' => 1] // was input row [2]
]
A warning about a fringe case not expressed in this example: if you are using row values as identifiers that may be corrupted upon being used as keys, the above techniques will give unreliable results. For instance, PHP does not allow float values as keys (they will cause an error or be truncated, depending on your PHP version). Only in these fringe cases might you consider using inefficient, iterated calls of in_array() to evaluate uniqueness.
Using array_unique(..., SORT_REGULAR) is only suitable when determining uniqueness by ENTIRE rows of data.
array_unique demo:
var_export(array_unique($array, SORT_REGULAR));
Output:
[
['user_id' => 82, 'ac_type' => 1], // was input row [0]
['user_id' => 80, 'ac_type' => 5], // was input row [1]
['user_id' => 76, 'ac_type' => 1] // was input row [2]
['user_id' => 82, 'ac_type' => 2], // was input row [3]
]
As a slight extension of requirements, if uniqueness must be determined based on more than one column, but not all columns, then use a "composite key" composed of the meaningful column values. The following uses the null coalescing assignment operator, but the other techniques from #2 and #3 can also be implemented.
Code: (Demo)
foreach ($array as $row) {
$compositeKey = $row['user_id'] . '_' . $row['ac_type'];
$result[$compositeKey] ??= $row; // only store if first occurrence of compositeKey
}
$array = [
['user_id'=>82,'ac_type'=>1],
['user_id'=>80,'ac_type'=>5],
['user_id'=>76,'ac_type'=>1],
['user_id'=>82,'ac_type'=>2],
['user_id'=>80,'ac_type'=>6]
];
$array = array_reverse($array);
$v = array_reverse(
array_values(
array_combine(
array_column($array, 'user_id'),
$array
)
)
);
echo '<pre>';
var_dump($v);
Result:
array(3) {
[0]=>
array(2) {
["user_id"]=>
int(76)
["ac_type"]=>
int(1)
}
[1]=>
array(2) {
["user_id"]=>
int(82)
["ac_type"]=>
int(1)
}
[2]=>
array(2) {
["user_id"]=>
int(80)
["ac_type"]=>
int(5)
}
}
Took me a while, but this should work (explanation in comments):
<?php
/* Example array */
$result = array(
0 => array(
"user_id" => 82,
"ac_type" => 1
),
1 => array(
"user_id" => 80,
"ac_type" => 5
),
2 => array(
"user_id" => 76,
"ac_type" => 1
),
3 => array(
"user_id" => 82,
"ac_type" => 2
),
4 => array(
"user_id" => 80,
"ac_type" => 2
)
);
/* Function to get the keys of duplicate values */
function get_keys_for_duplicate_values($my_arr, $clean = false) {
if ($clean) {
return array_unique($my_arr);
}
$dups = $new_arr = array();
foreach ($my_arr as $key => $val) {
if (!isset($new_arr[$val])) {
$new_arr[$val] = $key;
} else {
if (isset($dups[$val])) {
$dups[$val][] = $key;
} else {
//$dups[$val] = array($key);
$dups[] = $key;
// Comment out the previous line, and uncomment the following line to
// include the initial key in the dups array.
// $dups[$val] = array($new_arr[$val], $key);
}
}
}
return $dups;
}
/* Create a new array with only the user_id values in it */
$userids = array_combine(array_keys($result), array_column($result, "user_id"));
/* Search for duplicate values in the newly created array and return their keys */
$dubs = get_keys_for_duplicate_values($userids);
/* Unset all the duplicate keys from the original array */
foreach($dubs as $key){
unset($result[$key]);
}
/* Re-arrange the original array keys */
$result = array_values($result);
echo '<pre>';
print_r($result);
echo '</pre>';
?>
Function was taken from this the answer to this question: Get the keys for duplicate values in an array
Output:
Array
(
[0] => Array
(
[user_id] => 82
[ac_type] => 1
)
[1] => Array
(
[user_id] => 80
[ac_type] => 5
)
[2] => Array
(
[user_id] => 76
[ac_type] => 1
)
)
Tested and working example.
<?php
$details = array('0'=> array('user_id'=>'82', 'ac_type'=>'1'), '1'=> array('user_id'=>'80', 'ac_type'=>'5'), '2'=>array('user_id'=>'76', 'ac_type'=>'1'), '3'=>array('user_id'=>'82', 'ac_type'=>'1'), '4'=>array('user_id'=>'80', 'ac_type'=>'5'));
function unique_multidim_array($array, $key) {
$temp_array = array();
$i = 0;
$key_array = array();
foreach($array as $val) {
if (!in_array($val[$key], $key_array)) {
$key_array[$i] = $val[$key];
$temp_array[$i] = $val;
}
$i++;
}
return $temp_array;
}
?>
<?php
$details = unique_multidim_array($details,'user_id');
?>
<pre>
<?php print_r($details); ?>
</pre>
Will output:
Array
(
[0] => Array
(
[user_id] => 82
[ac_type] => 1
)
[1] => Array
(
[user_id] => 80
[ac_type] => 5
)
[2] => Array
(
[user_id] => 76
[ac_type] => 1
)
)
taken from here http://php.net/manual/en/function.array-unique.php in the user contributed notes.