I have two arrays as shown below. I need to merge the content of the arrays so that I can get the structure as shown in the third array at last. I have checked array_merge but can't figure out the way this is possible. Any help appreciated. Thanks.
[
['gross_value' => '100', 'quantity' => '1'],
['gross_value' => '200', 'quantity' => '1']
]
and
[
['item_title_id' => '1', 'order_id' => '4'],
['item_title_id' => '2', 'order_id' => '4']
];
I should get a merged array like this:
[
[
'gross_value' => '100',
'quantity' => '1',
'item_title_id' => '1',
'order_id' => 4
],
[
'gross_value' => '200',
'quantity' => '1',
'item_title_id' => '2',
'order_id' => 4
]
]
Use array_merge_recursive :
Convert all numeric key to strings, (make is associative array)
$result = array_merge_recursive($ar1, $ar2);
print_r($result);
See live demo here
how about:
$arr1 = array(
0 => array(
'gross_value' => '100',
'quantity' => '1'
),
1 => array(
'gross_value' => '200',
'quantity' => '1'
)
);
$arr2 = array(
0 => array(
'item_title_id' => '1',
'order_id' => '4'
),
1 => array(
'item_title_id' => '2',
'order_id' => '4'
)
);
$arr = array();
foreach($arr1 as $k => $v) {
array_push($arr, array_merge($v, $arr2[$k]));
}
print_r($arr);
output:
Array
(
[0] => Array
(
[gross_value] => 100
[quantity] => 1
[item_title_id] => 1
[order_id] => 4
)
[1] => Array
(
[gross_value] => 200
[quantity] => 1
[item_title_id] => 2
[order_id] => 4
)
)
Have a look at array_merge
I would probably iterate over the arrays and merge them manually.
$result = array();
foreach ( $array1 as $key => $item )
{
$result[$key] = array_merge($array1[$key], $array2[$key]);
}
You will have an issue if the top-level arrays don't have strictly matching keys though.
If you have $array1 and $array2, try this:
foreach($array1 as $key1=>$innerArray){
$array1[$key1]['item_title_id'] = $array2[$key1]['item_title_id'];
$array1[$key1]['order_id'] = $array2[$key1]['order_id'];
}
The problem with things like merge recursive is that they don't know when to stop.
In some scenarios you want to stop traversing down an array and simply take a given value if it exists.
For instance if you have to override a nested config array you might not want the default keys to stick around at a a specific level.
here is my solution:
public static function merge_lvl2(){
$args = func_get_args();
return static::merge($args, 2);
}
public static function merge($args, $maxDepth = null, $depth = 1)
{
$merge = [];
foreach($args as $arg) {
if (is_array($arg)) {
if (is_array($merge)) {
if ($maxDepth == $depth) {
$arg += $merge;
$merge = $arg;
} else {
$merge = array_merge($merge, $arg);
}
} else {
$merge = $arg;
}
}
}
if ($maxDepth !== $depth) {
foreach($args as $a) {
if (is_array($a)) {
foreach($a as $k => $v) {
if (isset($merge[$k]) && is_array($merge[$k])) {
$merge[$k] = static::merge([$merge[$k], $v], $maxDepth, $depth + 1);
}
}
}
}
}
return $merge;
}
You can pass as many arrays to merge as you want to.
$merged = ClassName::merge_lvl2([..array1..], [..array2..], [..array3..], etc...);
It will stop merging at level 2 and accept the last instance of the key as an override instead of a merge.
You can also call merge directly with an array of args and setting the max depth.
If no max depth is set it will traverse the entire array.
The most modern, elegant, concise way to merge rows from two or more arrays (or the rows from a multidimensional array with 3 or more levels of depth) is to call array_merge() on each row (array_replace() can also be used). array_map() can call array_merge by its string name and the input data can be split into individual arguments with the "spread operator" (...) when needed.
Code for the OP's arrays: (Demo)
var_export(
array_map('array_merge', $arr1, $arr2)
);
The above technique will return a newly indexed array (though you might not notice because the sample input arrays were indexed to begin with). If your input data has associative first-level keys, they will be ignored and destroyed by this technique. If you have non-numeric first-level keys and want to merge on those, then array_merge_recursive() is likely to be the ideal native function - Demo.
However, it must be said, that for the OP's sample data array_merge_recursive() IS NOT a correct technique.
My first snippet is conveniently extended if you have more than two arrays which need their rows to be merge based on their positions. (Demo)
var_export(
array_map('array_merge', $arr1, $arr2, $arr3)
);
And as mentioned earlier, the spread operator can be used to unpack deeper arrays with the same result. Again, the number of subarrays containing rows can be dynamic. If your deep array only has one subarray containing rows, then the result will be a "flattening" effect where the top level is removed.
Code with new data structure: (Demo)
$masterArray = [
[
['gross_value' => '100', 'quantity' => '5'],
['gross_value' => '200', 'quantity' => '6']
],
[
['item_title_id' => '1', 'order_id' => '3'],
['item_title_id' => '2', 'order_id' => '4']
],
[
['foo' => 'bar1'],
['foo' => 'bar2']
]
];
var_export(
array_map('array_merge', ...$masterArray)
);
To be fair, array_replace_recursive() does provide the desired result using the OP's sample data, but I find the technique to be semantically misleading. (Demo)
All that said, you are not forced to use PHP's native array functions; you can use classic loops as well -- you will have several ways to "unite" the rows inside the loop. This approach is a little less D.R.Y. because you need to explicitly specific the separate arrays that you wish to synchronously iterate. Just make sure that you understand the nuanced differences in using array_merge(), array_replace(), and the union operator (+) with different qualities of data. Hint: associative, non-numeric keyed data in the respective rows will be affected when key collisions occur. Be careful to use a merging technique that will not overwrite the wrong data when rows share associative keys.
array_merge() to build a new array: (array_merge() Demo) (array_replace() Demo) (array union operator + Demo)
$result = [];
foreach ($arr1 as $i => $row) {
$result[] = array_merge($row, $arr2[$i]);
}
var_export($result);
Alternative, you can use the same general approach, but instead of populating a new $result array, you can merge data into the first array.
(array_merge() Demo) (array_replace() Demo) (array union assignment operator += Demo)
foreach ($arr1 as $i => &$row) {
$row = array_merge($row, $arr2[$i]);
}
var_export($arr1);
For the sake of completeness, if you have an indexed array of indexed arrays, then you might even use iterated calls of array_push() with the spread operator to achieve a similar functionality. This quite literally, indicatively appends the row data from subsequent arrays to the first array's rows.
Code: (Demo)
$arr1 = [
['A', 'B', 'C'],
['F', 'G']
];
$arr2 = [
['D', 'E'],
['H', 'I', 'J', 'L']
];
foreach ($arr1 as $i => &$row) {
array_push($row, ...$arr2[$i]);
}
var_export($arr1);
Related non-duplicate content on Stack Overflow:
Partially merge one array's row data with another another array:Add column of values from one array to another
Merge two flat arrays to create an array of merged rows:Transforming array values in elements of a subarray using PHP
Merge rows with indexed elements, remove duplicates and reindex:Merge two multidimensional arrays, preserve numeric keys, and combine values inside array
Merge arrays containing objects:Merge rows of two arrays containing objects by first level index
Push single elements from one array to rows in another array:Push elements from one array into rows of another array (one element per row)
On the above pages, the rabbit hole goes further because I've linked other related pages to them. Keep researching until you find what you need.
If you are using Laravel, you might be interested in its combine() and collect() methods.
Related
I wrote this function to get a subset of an array. Does php have a built in function for this. I can't find one in the docs. Seems like a waste if I'm reinventing the wheel.
function array_subset($array, $keys) {
$result = array();
foreach($keys as $key){
$result[$key] = $array[$key];
}
return $result;
}
I always want this too. Like a PHP version of Underscore's pick.
It's ugly and counter-intuitive, but what I sometimes do is this (I think this may be what prodigitalson was getting at):
$a = ['foo'=>'bar', 'zam'=>'baz', 'zoo'=>'doo'];
// Extract foo and zoo but not zam
print_r(array_intersect_key($a, array_flip(['foo', 'zoo'])));
/*
Array
(
[foo] => bar
[zoo] => doo
)
*/
array_intersect_key returns all the elements of the first argument whose keys are present in the 2nd argument (and all subsequent arguments, if any). But, since it compares keys to keys, I use array_flip for convenience. I could also have just used ['foo' => null, 'zoo' => null] but that's even uglier.
array_diff_key and array_intersect_key are probably what you want.
There is no direct function I think in PHP to get a subset from an array1 with compare to another array2 where the values are the list of key name which we fetch.
Like: array_only($array1, 'field1','field2');
But this way can be achieved the same.
<?php
$associative_array = ['firstname' => 'John', 'lastname' => 'Smith', 'DOB' => '2000-10-10', 'country' => 'Ireland' ];
$subset = array_intersect_key( $associative_array, array_flip( [ 'lastname', 'country' ] ) );
print_r( $subset );
// Outputs...
// Array ( [lastname] => Smith [country] => Ireland );
I have three arrays, say multiarray, valsarray, and otherarray. otherarray is a multidimensional array that supplies values to multiarray and valsarray, but besides that it is unimportant here. valsarray takes values from a subarray of each value in otherarray and multiarray takes straight values from otherarray, as demonstrated below:
foreach($otherarray as $other){
foreach($other as $sub){
$valsarray[] = $sub
}
$multiarray[] = array('Val1' => $other['Val1'], 'Val2' => $other['Val2']);
}
Now what I would like to do is append each key/value pair in valsarray to the current array entry of multiarray, to achieve a result similar to:
$multiarray = array('Val1' => $other['Val1'], 'Val2' => $other['Val2'],
'VALSARRAY_KEY1' => VALSARRAY_VALUE1, ..., 'VALSARRAY_KEYN' => VALSARRAY_VALUEN)
I have attempted to solve this using current in the following fashion:
foreach($valsarray as $key => $val){
current($multiarray)[$key] = $val;
}
But the multiarray remained unaltered. I may be misunderstanding how current works, or how to approach this problem, so any help or direction would be appreciated.
EDIT- EXAMPLE
otherarray = array(...prior array entries...,
array('Val1' => 'abc',
'Val2' => 'cde',
'Val3' => 'not important',
'Val4' => array(0 => 'subA', 1 => 'subB'),
...next array entries...);
BEFORE MERGE:
multiarray = array(...prior entries...,
array('Val1' => 'abc',
'Val2' => 'cde'));
valsarray = array(0 => 'subA', 1 => 'subB');
AFTER MERGE:
multiarray = array(...prior entries...,
array('Val1' => 'abc',
'Val2' => 'cde',
0 => 'subA',
1 => 'subB'));
So if multiarray was a regular array instead of a multidimensional one, I would do something like:
foreach($valsarray as $key => $val){
$multiarray[$key] = $val;
}
To achieve the end result.
I am not 100% sure what you are trying to accomplish a Minimal, Complete, and Verifiable example may help if I have misunderstood something.
It appears that the current() function does not work as you assume. (Or more specifically, the internal pointer.)
If you look at the example in the PHP documentation: Current(), you will see that for current($array) to change elements, you need to call next($array) or prev($array).
These function move the internal pointer of the array.
Note that in PHP 5, foreach loops use the internal pointer (and reset it when you start a loop), but in PHP 7, foreach loops do not use the internal pointer.
Anyway, here is my best guess at what could help you.
$valsarray_index = 0;
foreach ($otherarray as $other) {
$multiarray_value = array('Val1' => $other['Val1'], 'Val2' => $other['Val2']);
foreach ($other as $sub) {
$multiarray_value[$valsarray_index] = $sub;
// $multiarray_value["VALSARRAY_KEY" . $valsarray_index] = $sub;
$valsarray[] = $sub;
$valsarray_index += 1; // This stays in lockstep with the last index of $valsarray
}
$multiarray[] = $multiarray_value;
}
I am not exactly sure about what you want the final output to look like. If this produces incorrect information, then if would be helpful to provide some specific arrays for input and what you expect as output.
So I have a result from a form post that looks like this:
$data = [
'id_1' => [
'0' => 1,
'1' => 2
],
'id_2' => [
'0' => 3,
'1' => 4
],
'id_3' => [
'0' => 5,
'1' => 6
]
];
What I want to achieve is to split this array into two different arrays like this:
$item_1 = [
'id_1' => 1,
'id_2' => 3,
'id_3' => 5
]
$item_2 = [
'id_1' => 2,
'id_2' => 4,
'id_3' => 6
]
I've tried using all of the proper array methods such as array_chunk, array_merge with loops but I can't seem to get my mind wrapped around how to achieve this. I've seen a lot of similar posts where the first keys doesn't have names like my array does (id_1, id_2, id_3). But in my case the names of the keys are crucial since they need to be set as the names of the keys in the individual arrays.
Much shorter than this will be hard to find:
$item1 = array_map('reset', $data);
$item2 = array_map('end', $data);
Explanation
array_map expects a callback function as its first argument. In the first line this is reset, so reset will be called on every element of $data, effectively taking the first element values of the sub arrays. array_map combines these results in a new array, keeping the original keys.
The second line does the same, but with the function end, which effectively grabs the last element's values of the sub-arrays.
The fact that both reset and end move the internal array pointer, is of no concern. The only thing that matters here is that they also return the value of the element where they put that pointer to.
Solution without loop and just for fun:
$result = [[], []];
$keys = array_keys($data);
array_map(function($item) use(&$result, &$keys) {
$key = array_shift($keys);
$result[0][$key] = $item[0];
$result[1][$key] = $item[1];
}, $data);
Just a normal foreach loop will do.
$item_1 = [];
$item_2 = [];
foreach ($data as $k => $v){
$item_1[$k] = $v[0];
$item_2[$k] = $v[1];
}
Hope this helps.
I have an array that looks like this:
Array([0]=>Array([id]=>7 [name]=foo) [1]=>Array([id]=>10 [name]=bar) [2]=>Array([id]=>15 [name]=baz))
Each index contains an another array with various elements including an 'id'. I would like to "go up" a level, such that my top-level array is indexed by the ID element of the corresponding nested arrays, but that index still contains an array with all of the elements that were in the sub arrays?
In other words, how can I use PHP to turn the above array into this:
Array([7]=>Array([id]=>7 [name]=foo) [10]=>Array([id]=>10 [name]=bar) [15]=>Array([id]=>15 [name]=baz))
What you need to do here is extract the ids from each sub-array in your input. If you have these as an array of ids, you are just an array_combine call away from re-indexing your original array to use these ids as the keys.
You can produce such an array of ids using array_map, which leads to:
// input data
$array = array(array('id' => '7', 'name' => 'foo'),array('id' => 10, 'name' => 'bar'));
// extract ids from the input array
$ids = array_map(function($arr) { return $arr['id']; }, $array);
// "reindex" original array using ids as array keys, keep original values
$result = array_combine($ids, $array);
print_r($result);
The syntax I 've used for the anonymous function (first argument to array_map) requires PHP >= 5.3, but you can achieve the same (although a bit less conveniently) with create_function in any PHP version you 'd not be ashamed of using.
See it in action.
In modern, supported versions of PHP, this whole task can be achieved with array_column() alone.
Using null as the second parameter will leave the rows unchanged.
Using id as the 3rd parameter will assign those columnar values as the new first level keys. Be aware that if these columnar values are not unique, subsequently encountered duplicates will overwrite previously encountered rows with the same id value -- this is because keys cannot be duplicates on a given level in an array.
DO NOT bother calling array_combine(), it is simply unnecessary/indirect.
Code: (Demo)
$array = [
['id' => 7, 'name' => 'foo'],
['id' => 10, 'name' => 'bar'],
['id' => 15, 'name' => 'baz'],
];
var_export(
array_column($array, null, 'id')
);
Output:
array (
7 =>
array (
'id' => 7,
'name' => 'foo',
),
10 =>
array (
'id' => 10,
'name' => 'bar',
),
15 =>
array (
'id' => 15,
'name' => 'baz',
),
)
Try this:
$newArray = array();
foreach($oldArray as $key => $value) {
$newArray[$value['id']] = $value;
}
Since PHP 5.5.0, you can shorten the code by using array_column() instead of array_map().
$result = array_combine(array_column($array, 'id'), $array);
I'm trying to sort any array with array_multisort() and everything is working great. However, based on conditions in my script, I need to change the options.
What I have so far is this:
array_multisort(
$sort1,
SORT_ASC,
$sort2,
SORT_ASC,
$sort3,
SORT_ASC,
$arraytosort
);
I would like to write something that will allow a more flexible/dynamic payload of sorting data/rules. Something like this:
$dynamicSort = "$sort1,SORT_ASC,$sort2,SORT_ASC,$sort3,SORT_ASC,";
array_multisort(
$dynamicSort,
$arraytosort
);
How can I feed an unknown number of parameters to array_multisort() and have it modify the $arraytosort array?
You could try to use call_user_func_array. But I've never tried it on a built-in function before. Here is an example:
$dynamicSort = "$sort1,SORT_ASC,$sort2,SORT_ASC,$sort3,SORT_ASC";
$param = array_merge(explode(",", $dynamicSort), array($arrayToSort))
call_user_func_array('array_multisort', $param)
I had the same problem with this answer: "Argument #1 is expected to be an array or a sort flag"
For anyone having the same problem try this instead:
$dynamicSort = array(&$sort1, SORT_ASC, &$sort2, SORT_ASC, &$sort3, SORT_ASC);
$param = array_merge($dynamicSort, array(&$arrayToSort));
call_user_func_array('array_multisort', $param);
Note that i have used the reference to my variables "&$" instead of $.
This works great in php 5.3 but may cause error in 5.2 due to a bug.
It is important to understand that the array sent to call_user_func_array() must consist only of references; it is not important whether the array itself is passed by reference. I spent the better part of a day troubleshooting this; the fact that the examples on the function page at php.net all used literal arrays led me to this page: php Bug #49353. Problem solved.
This doesn't seem to be very well (or consistently) documented, so here goes....
These DO NOT WORK (PHP 5.3.3):
$multisort_array = array($arr1, SORT_DESC, SORT_STRING, $arr2); // array of values
call_user_func_array('array_multisort', $multisort_array); // array passed by value
$multisort_array = array($arr1, SORT_DESC, SORT_STRING, $arr2); // array of values
call_user_func_array('array_multisort', &$multisort_array); // array passed by reference
$multisort_array = array(&$arr1, SORT_DESC, SORT_STRING, &$arr2); // non-constants by reference
call_user_func_array('array_multisort', $multisort_array); // array passed by value
$multisort_array = array(&$arr1, SORT_DESC, SORT_STRING, &$arr2); // non-constants by reference
call_user_func_array('array_multisort', &$multisort_array); // array passed by reference
These DO WORK:
$sort = array('desc' => SORT_DESC, 'string' => SORT_STRING);
$multisort_array = array(&$arr1, &$sort['desc'], &$sort['string'], &$arr2); // all by reference
call_user_func_array('array_multisort', $multisort_array); // array passed by value
$sort = array('desc' => SORT_DESC, 'string' => SORT_STRING);
$multisort_array = array(&$arr1, &$sort['desc'], &$sort['string'], &$arr2); // all by reference
call_user_func_array('array_multisort', &$multisort_array); // array passed by reference
To add onto the existing answers, just thought I would add a little something. For anyone passing the desired "sort by" as a comma-separated $_POST variable (or any comma-separated variable for that matter):
//$_POST["sort_by"] = "column_A DESC, column_B ASC, columns_C DESC";
$sort_bys = explode(",", $_POST["sort_by"]);
$dynamicSort = array();
foreach($sort_bys as $sort_by){
$sort_by2 = trim(str_replace('DESC','',$sort_by));
$direction = (strpos($sort_by, 'DESC') !== false)?SORT_DESC:SORT_ASC;
$$sort_by2 = array_column($array_to_sort, $sort_by2);
$dynamicSort[] = &$$sort_by2;
$dynamicSort[] = $direction;
$dynamicSort[] = SORT_NUMERIC; //or SORT_STRING or SORT_REGULAR ...
}
$param = array_merge($dynamicSort, array(&$array_to_sort));
call_user_func_array('array_multisort', $param);
From PHP5.6, you can use a variadic technique. Simply push all of your sorting data and sorting logic into an indexed array, then use the splat operator to unpack the parameters into array_multisort(). Be sure to make the array that you wish to modify -- modifiable by reference before pushing it into the parameters array.
The pushing of parameters into $sortingParams below can be written more succinctly as a single declaration, but I think it will be easier to conceptualize this way. These individual pushes would be suitable inside of an iterating process (e.g. foreach()).
For every column of data used to sort the parent array, you may elect to push zero, one, or two additional elements to best signify the sorting logic.
Code: (Demo)
$array = [
['number' => 2, 'letter' => 'a', 'price' => 9.99],
['number' => 3, 'letter' => 'b', 'price' => 9.99],
['number' => 1, 'letter' => 'c', 'price' => 9.50],
['number' => 1, 'letter' => 'd', 'price' => 10],
['number' => 1, 'letter' => 'e', 'price' => 9.99],
];
$sortingParams[] = array_column($array, 'number'); // 1-dimensional
$sortingParams[] = SORT_ASC; // this is omittable as well because it is assumed (just for demo)
$sortingParams[] = array_column($array, 'price'); // 1-dimensional
$sortingParams[] = SORT_DESC;
$sortingParams[] = SORT_NUMERIC; // this is omittable as well because it is assumed (just for demo)
$sortingParams[] = &$array; // this is the actual master array which should be modified
array_multisort(...$sortingParams); // unpack with splat operator
var_export($array);
Output:
array (
0 =>
array (
'number' => 1,
'letter' => 'd',
'price' => 10,
),
1 =>
array (
'number' => 1,
'letter' => 'e',
'price' => 9.99,
),
2 =>
array (
'number' => 1,
'letter' => 'c',
'price' => 9.5,
),
3 =>
array (
'number' => 2,
'letter' => 'a',
'price' => 9.99,
),
4 =>
array (
'number' => 3,
'letter' => 'b',
'price' => 9.99,
),
)
This technique is super powerful if you have dynamic rules being passed to your process. In my case, I needed to collect filters from my DataTables UI and regenerate the data as a .csv. I merely needed to iterate through DataTable's order data and derive my set of rules - done.
I find this syntax much kinder on the eyes versus call_user_func_array().
Here is a more complex implementation: Sort array of associative arrays on multiple columns using specified sorting rules