Array merge with same keys - php

I have these three arrays:
Array
(
[1] => sadsad#fsdf.fgh
[2] => rtt#RERT.FDG
[3] => WQEWQ#fgdg.h
)
Array
(
[1] =>
[2] => 4234235
[3] =>
)
Array
(
[2] => 1
)
And I want to generate this output:
Array
(
[1] => array(
[0] => sadsad#fsdf.fgh
)
[2] => array(
[0] => rtt#RERT.FDG
[1] => 4234235
[2] => 1
)
[3] => array(
[0] => WQEWQ#fgdg.h
)
)
I need some assistance because I already researched array_merge_recursive() and array_merge(), but I can't get the correct result.
If I need to use foreach() what must I do to merge these 3 arrays.

Wrote a little script:
$a = array
(
1=>"sadsad#fsdf.fgh",
2=>"rtt#RERT.FDG",
3=>"WQEWQ#fgdg.h",
);
$b = array
(
2 => 4234235
);
$c = array
(
2 => 1
);
$arrayKeys = array_unique(
array_merge(
array_keys($a),
array_keys($b),
array_keys($c)
)
);
$d = array_combine(
$arrayKeys,
array_fill(
0,
count($arrayKeys),
array()
)
);
foreach($a as $key => $value) {
if(!empty($a[$key])) {
$d[$key][] = $a[$key];
}
if(!empty($b[$key])) {
$d[$key][] = $b[$key];
}
if(!empty($c[$key])) {
$d[$key][] = $c[$key];
}
}
var_dump($d);
Also if you want to you can merge together the arrays using the variable names only
//names of the variables to merge together
$arrayVariableNames = array("a","b","c");
//merging array keys together
$arrayKeys = array();
foreach($arrayVariableNames as $variableName) {
$arrayKeys = array_merge(
$arrayKeys,
array_keys(${$variableName})
);
}
$arrayKeys = array_unique($arrayKeys);
//initialize the result array with empty arrays
$resultArray = array_combine(
$arrayKeys,
array_fill(
0,
count($arrayKeys),
array()
)
);
//loop through all the keys and add the elements from all the arrays
foreach($resultArray as $key => &$value) {
foreach($arrayVariableNames as $variableName) {
if(!empty(${$variableName}[$key])) {
$value[] = ${$variableName}[$key];
}
}
}

As you have no doubt discovered, array_merge_recursive() stubbornly smashes all numeric or "numeric string" keys into a 1-dimensional array. To avoid this behavior, you need to cast each of your arrays' initial keys as strings in a way that will not be assumed to be a number by array_merge_recursive().
Additionally you want to filter out all elements that have empty values.
I initially wrote a one-liner that performed the key re-casting then filtered the values, but it is less efficient that way. For your case, you should only use array_filter() on arrays that may possibly contain empty values.
Input Arrays:
$a=[1=>"sadsad#fsdf.fgh",2=>"rtt#RERT.FDG",3=>"WQEWQ#fgdg.h"];
$b=[1=>"",2=>"4234235",3=>""];
$c=[2=>1];
Code:
// remove empty values from all arrays that may have them
$b=array_filter($b,'strlen');
// for all arrays, cast numeric keys to string by prepending with a space
function addK($v){return " $v";}
$a=array_combine(array_map('addK',array_keys($a)),$a);
$b=array_combine(array_map('addK',array_keys($b)),$b);
$c=array_combine(array_map('addK',array_keys($c)),$c);
// merge arrays recursively
$merged=array_merge_recursive($a,$b,$c);
// cast keys back to numeric
$merged=array_combine(array_map('trim',array_keys($merged)),$merged);
// force all top-level elements to be arrays
foreach($merged as $k=>$v){
if(is_string($merged[$k])){$merged[$k]=[$v];}
}
var_export($merged);
Output:
array (
1 => array (
0 => 'sadsad#fsdf.fgh',
),
2 => array (
0 => 'rtt#RERT.FDG',
1 => '4234235',
2 => 1,
),
3 => array (
0 => 'WQEWQ#fgdg.h',
),
)
For readers who want to know the difference when array_merge_recursive() is run with no preparation:
array (
0 => 'sadsad#fsdf.fgh',
1 => 'rtt#RERT.FDG',
2 => 'WQEWQ#fgdg.h',
3 => '',
4 => '4234235',
5 => '',
6 => 1,
)
Notice the 1d array and the re-indexed keys? ...totally useless for the OP.
Finally, for anyone who wants to re-cast the keys to all arrays and would like to make my process more DRY, there may be an opportunity to set up a variadic function or similar. I merely didn't bother to pursue the notion because I didn't want to make my answer anymore complex and it is not a terrible amount of Repeating Myself.

Related

Group associative row data based on shared column value

I have a multidimensional array like:
Array
(
[0] => Array
(
[division] => Mymensingh
[A] => 1
)
[1] => Array
(
[division] => Dhaka
[A] => 5
)
[2] => Array
(
[division] => Mymensingh
[B] => 2
[C] => 5
)
)
I need to find the rows with matching division values and merge them in one array.
From this array, I want the output as:
Array
(
[0] => Array
(
[division] => Mymensingh
[A] => 1
[B] => 2
[C] => 5
)
[1] => Array
(
[division] => Dhaka
[A] => 5
)
)
Keys in the subarrays can be different and the subarrays may have a differing number of elements.
I think it's relatively simple to just iterate through the array and continuously merge the entries separated by "division":
function mergeByDiscriminator($input, $discriminator = 'division') {
$result = [];
foreach ($input as $array) {
$key = $array[$discriminator];
$result[$key] = array_merge(
array_key_exists($key, $result) ? $result[$key] : [],
$array
);
}
return array_values($result);
}
$result = mergeByDiscriminator($input); // $input is your array
The only solution that I can think of is as below. Of course there might be other feasable solutions, but I am giving one from my end.
$array = array(
'0' => array (
'division' => 'Mymensingh',
'A' => 1
),
'1' => array (
'division' => 'Dhaka',
'A' => 5
),
'2' => array (
'division' => 'Mymensingh',
'B' => 2,
'C' => 5
),
);
$result = array();
foreach ($array as $arr) {
if (!is_array($result[$arr['division']])) $result[$arr['division']] = array();
foreach ($arr as $key => $value) {
$result[$arr['division']][$key] = $value;
}
}
echo "<pre>"; print_r($result);
The above code is giving you the desired output. Please give it a try.
Hope this helps.
This task is concisely completed with zero iterated function calls thanks to the null coalescing operator and the union operator.
Merge each iterated row with the pre-existing data in that keyed-group. If the keyed-group has not yet been encountered merge the row with an empty array.
The union operator is suitable/reliable in this case because it is writing one associative array into another associative array.
Language Construct Iteration: (Demo)
$result = [];
foreach ($array as $row) {
$result[$row['division']] = ($result[$row['division']] ?? []) + $row;
}
var_export(array_values($result));
Functional Iteration: (Demo)
var_export(
array_values(
array_reduce(
$array,
function($result, $row) {
$result[$row['division']] = ($result[$row['division']] ?? []) + $row;
return $result;
},
[]
)
)
);

Move specific array items to beginning of array without altering order of the rest

I have an array:
Array
(
[product1] => Array
(
[id] => 1
[title] => 'p1'
[extra] => Array(
[date] => '1990-02-04 16:40:26'
)
)
[product2] => Array
(
[id] => 2
[title] => 'p2'
[extra] => Array(
[date] => '1980-01-04 16:40:26'
)
)
[product3] => Array
(
[id] => 3
[title] => 'p3'
[extra] => Array(
[date] => '2000-01-04 16:40:26'
)
)
[product4] => Array
(
[id] => 4
[title] => 'p4'
[extra] => Array(
[date] => '1995-01-04 16:40:26'
)
)
[product5] => Array
(
[id] => 5
[title] => 'p5'
[extra] => Array(
[date] => '1960-01-04 16:40:26'
)
)
...
I need to get 2 products with the latest date and move them to the start of the array.
I've looked into the multisort function, and I could sort the array like this, but then the entire array would be arranged by date, I want to maintain the order of the array but just bump up the latest 2 rows.
I need to pick out the 2 latest (order by date) from the array, then move these to the start of the array. So the order of the ids should be:
3,4,1,2,5
The latest 2 have been moved to the front of the array, the remainder are still ordered by id.
Not the most optimal implementation, but the most straight forward:
$array = /* your data */;
$latest = $array;
uasort($latest, function (array $a, array $b) {
return strtotime($a['extra']['date']) - strtotime($b['extra']['date']);
});
array_splice($latest, 2);
$latestOnTop = array_merge($latest, array_diff_key($array, $latest));
The array_splice operation requires that your array keys are actually product1 or similar; won't work with numeric indices, as they'll be renumbered. Use another truncation mechanism if that's the case.
If your array is really big, a complete sort will be unnecessarily slow. In that case, you should rather loop over the array once, keeping track of the two latest items (and their keys) you could find, then array_diff_key and array_merge on that. That's a bit more difficult to implement (left as exercise for the reader), but much more efficient.
// Making array with only dates
$dates = array();
foreach ($arr as $key => $item)
$dates[$key] = $item['extra']['date'];
// Sort it by date saving keys
uasort($dates, function($i1, $i2) { return strtotime($i1) - strtotime($i2); });
// Take keys
$dates = array_keys($dates);
// Create array with two needed items
$newarray = array( $dates[0] => $arr[$dates[0]], $dates[1] => $arr[$dates[1]]);
// remove these items
unset($arr[$dates[0]]); unset($arr[$dates[1]]);
// put them in array start
$arr = array_merge($newarray, $arr);
var_dump($arr);
// copy current array for new array
$temp = $input;
// sort temp array by latest date
uasort($temp, function($a,$b) {
return (strtotime($a['extra']['date']) < strtotime($b['extra']['date']));
});
// for 2 key value pairs to get on top
$sorted_keys = array_keys($temp);
// initialize your required array
$final = [];
// two keys to move on top
$final [ $sorted_keys[0] ] = $temp [ $sorted_keys[0] ];
$final [ $sorted_keys[1] ] = $temp [ $sorted_keys[1] ];
foreach ($input as $k => $v)
{
// insert your other array values except two latest
if(!array_key_exists($k, $final))
{
$final[$k]=$v;
}
}
unset($temp); // free up resource
$final is your required array

Shuffling the first level of the array in PHP

PHP's shuffle() is not randomizing an array the way I need it. I have a two dimensional array and when I use shuffle() on it it only randomizes the 2nd dimension of the array, but I need the opposite.
Lets assume this is the array I need to shuffle:
Array
(
[0] => Array
(
[key1] => 199
[key2] => 6
)
[1] => Array
(
[key1] => 195
[key2] => 3
)
)
The way the shuffle() shuffles it is like this:
Array
(
[0] => Array
(
[key1] => 195
[key2] => 3
)
[1] => Array
(
[key1] => 199
[key2] => 6
)
)
But this is not what I'm after. What I need as an end result is this:
Array
(
[1] => Array
(
[key1] => 195
[key2] => 6
)
[0] => Array
(
[key1] => 199
[key2] => 6
)
)
I know that this can be achieved this using a random key with rand() or mt_rand(), but it also could be possible that for a small amount of keys, we could receive the same rand() key twice, leading to NOT have a nicely shuffled array.
I also know that adding more if else logic would be a possibility, but I'm looking to do this with already implemented stuff - I don't wanna reinvent the wheel.
How can I achieve my desired shuffle?
shuffle() is working as intended. It is not "randomizing the 2nd dimension", it is not recursive.
It is reordering the elements of the array (which just happen to be arrays). The issue you are seeing is because shuffle() resets the array's keys.
From the docs (http://php.net/shuffle):
Note: This function assigns new keys to the elements in array. It will remove any existing keys that may have been assigned, rather than
just reordering the keys.
To get what you want, you need to use array_rand() to randomize the keys, then reorder the elements in the array based on that.
$randKeys = array_rand($array, count($array));
// This is needed because array_rand was changed
// and now returns the keys in order
shuffle($randKeys);
uksort($array, function($a, $b) use($randKeys){
return array_search($a, $randKeys) - array_search($b, $randKeys);
});
DEMO: https://eval.in/101265
In the comment section of the PHP manual for shuffle you find:
<?php
function shuffle_assoc(&$array) {
$keys = array_keys($array);
shuffle($keys);
foreach($keys as $key) {
$new[$key] = $array[$key];
}
$array = $new;
return true;
}
?>
$yourArray = array(
array('key1' => 199, 'key2' => 6),
array('key1' => 195, 'key2' => 3),
array('key1' => 205, 'key2' => 8)
);
$helperArr = array();
foreach($yourArray as $subArr)
{
foreach($subArr as $key => $value)
$helperArr[$key][] = $value;
}
foreach($helperArr as &$shuffleArr)
shuffle($shuffleArr);
$shuffledArr = array();
foreach($helperArr as $key => $value)
{
for($i = 0; $i < count($value); $i++)
$shuffledArr[$i][$key] = $value[$i];
}
echo '<pre>';
print_r($helperArr);
print_r($shuffledArr);
echo '</pre>';
DEMO

How to combine an array of keys with corresponding values from another array?

I have 2 arrays
Array ( [1] => Manufacturer [2] => Location [3] => Hours [4] => Model )
and
Array ( [Manufacturer] => John Deere [Location] => NSW [Hours] => 6320 )
I need to combine them and associate the values from the first array( Manufacturer, Location, hours , Model) as names in the 2nd array and if a specific values from the first array doesn't find associative name in the 2nd array to associate empty . For the example the result that I need from the above arrays is an array like this
Array ( [Manufacturer] => John Deere [Location] => NSW [Hours] => 6320 [Model] => )
If i use simple array_combine it says that PHP Warning: array_combine() [function.array-combine]: Both parameters should have equal number of elements
You can use a simple foreach loop:
$combined = array();
foreach ($keys as $key) {
$combined[$key] = isset($values[$key]) ? $values[$key] : null;
}
Where $keys is your first array with the keys and $values is your second array with the corresponding values. If there is no corresponding value in $values the value will be null.
Assuming $array1 and $array2 in order as you listed them, I think this would work:
$newArray = array();
while (list(,$data) = each($array1)) {
if (isset($array2[$data])) {
$newArray[$data] = $array2[$data];
} else {
$newArray[$data] = "";
}
}
Try array_merge(). This example appears to do what you want:
<?php
$keys = array( 1 => "Manufacturer", 2 => "Location", 3 => "Hours", 4 => "Model") ;
$canonical = array_combine(array_values($keys), array_fill(0, count($keys), null));
$data = array("Manufacturer" => "John Deere", "Location" => "NSW", "Hours" => 6320);
print_r(array_merge($canonical, $data));
If the array of keys is $array1, and the associative array with values is $array2:
$new_array = array_merge(array_fill_keys(array_flip($array1), ''), $array2));
This inverts your key array, filling it with '' values. Then when you merge the arrays, any duplicate keys will be overwritten by the second array, but unfilled keys will remain.

Intersecting multidimensional array of varying size

I have a multidimensional array that looks like this:
Array
(
[0] => Array
(
[0] => Array
(
[id] => 3
)
[1] => Array
(
[id] => 1
)
[2] => Array
(
[id] => 2
)
[3] => Array
(
[id] => 5
)
[4] => Array
(
[id] => 4
)
)
[1] => Array
(
[0] => Array
(
[id] => 1
)
[1] => Array
(
[id] => 3
)
[2] => Array
(
[id] => 4
)
[3] => Array
(
[id] => 5
)
)
[2] => Array
(
[0] => Array
(
[id] => 3
)
)
)
I need to find a way to return the intersecting value(s). In this case that would be
[id] => 3
The length of the array could be different, so I can't just use array_intersect().
This would be simple if your arrays contained only integers, but as they contain an another array, it gets a bit more complicated. But this should do it:
function custom_intersect($arrays) {
$comp = array_shift($arrays);
$values = array();
// The other arrays are compared to the first array:
// Get all the values from the first array for comparison
foreach($comp as $k => $v) {
// Set amount of matches for value to 1.
$values[$v['id']] = 1;
}
// Loop through the other arrays
foreach($arrays as $array) {
// Loop through every value in array
foreach($array as $k => $v) {
// If the current ID exists in the compare array
if(isset($values[$v['id']])) {
// Increase the amount of matches
$values[$v['id']]++;
}
}
}
$result = array();
// The amount of matches for certain value must be
// equal to the number of arrays passed, that's how
// we know the value is present in all arrays.
$n = count($arrays) + 1;
foreach($values as $k => $v) {
if($v == $n) {
// The value was found in all arrays,
// thus it's in the intersection
$result[] = $v;
}
}
return $result;
}
Usage:
$arrays = array(
array(array('id' => 3), array('id' => 1), array('id' => 2), array('id' => 5), array('id' => 4)),
array(array('id' => 1), array('id' => 3), array('id' => 4), array('id' => 5)),
array(array('id' => 3))
);
print_r(custom_intersect($arrays));
Result:
Array
(
[0] => 3
)
This function isn't perfect: if you have duplicate ID's in one array, it will not work. That would require a bit more code to first make the array values unique, but this will probably work in your case.
You can use array_uintersect() to get the intersection of arrays using a custom comparison function. You have to call it with call_user_func_array() though, as it expects each array as a separate argument:
//build list of parameters for array_uintersect()
$params = array_merge($input, array('compare_func'));
$result = call_user_func_array('array_uintersect', $params);
function compare_func($a, $b) {
return $a['id'] - $b['id'];
}
You can't simply call array_intersect() with call_user_func_array(), because it seems to compare arrays by comparing their string representation (which will always be 'Array').
$a = array(4,3);
$b = array(4,3,2,1);
$c = array(1,2,3,4,5);
$d = array(5,4,3);
$array = array($a,$b,$c,$d);
for ($i=0; $i<count($array); $i++){
if ($i==0){
$array2 = $array[$i];
} else {
$array2 = array_intersect($array2, $array[$i]);
}
}
print_r($array2);`
Result:
3,4
As mentioned in one of the comments of the php.net website (array_intersect function).
$a = array(1,2,3,4,5,2,6,1); /* repeated elements --> $a is not a set */
$b = array(0,2,4,6,8,5,7,9,2,1); /* repeated elements --> $b is not a set */
$ua = array_merge(array_unique($a)); /* now, $a is a set */
$ub = array_merge(array_unique($b)); /* now, $b is a set */
$intersect = array_merge(array_intersect($ua,$ub));
This will return this array:
Array
(
[0] => 1
[1] => 2
[2] => 4
[3] => 5
[4] => 6
)

Categories