Merging arrays based on a value of the key [duplicate] - php

This question already has answers here:
Merge arrays of associative arrays by shared column values [duplicate]
(3 answers)
Closed 5 months ago.
I have two arrays of arrays that have an id key, and I'd like to merge the data together based on that array's key and key value. The data would look something like:
$color = [
['id' => 1, 'color' => 'red'],
['id' => 2, 'color' => 'green'],
['id' => 3, 'color' => 'blue'],
];
$size = [
['id' => 1, 'size' => 'SM'],
['id' => 2, 'size' => 'XL'],
['id' => 3, 'size' => 'MD'],
['id' => 4, 'size' => 'LG'],
];
$combined = [
['id' => 1, 'color' => 'red', 'size' => 'SM'],
['id' => 2, 'color' => 'green', 'size' => 'XL'],
['id' => 3, 'color' => 'blue', 'size' => 'MD'],
['id' => 4, 'size' => 'LG'],
];
Is there a particularly efficient function or trick for handling something like this? Or should I just loop through the elements of one array and push the contents to the other?
I'm also using Laravel, and the data is a result of an eloquent query, so I can also utilize the collections if it would make the code cleaner.

Use array_replace_recursive function for easy and fast way
array_replace_recursive($color, $size)

Pure php solution is to use array_replace_recursive like this:
array_replace_recursive(
array_combine(array_column($color, "id"), $color),
array_combine(array_column($size, "id"), $size)
);
You should notice that array_replace_recursive merge arrays by keys.
So, if you get such data from database:
$color = [
['id' => 1, 'color' => 'red'],
['id' => 2, 'color' => 'red']
];
$size = [
['id' => 2, 'size' => 'SM']
];
array_replace_recursive will return corrupted merge:
$combined = [
['id' => 2, 'color' => 'red', 'size' => 'SM'],
['id' => 2, 'color' => 'red']
];
The solution is to combine array_replace_recursive with array_column and array_combine for merging arrays by their's id field:
array_replace_recursive(
array_combine(array_column($color, "id"), $color),
array_combine(array_column($size, "id"), $size)
);
array_combine(array_column($color, "id"), $color) creates associative array with id as keys.
So, in your case it will return:
$combined = [
1 => ['id' => 1, 'color' => 'red', 'size' => 'SM'],
2 => ['id' => 2, 'color' => 'green', 'size' => 'XL'],
3 => ['id' => 3, 'color' => 'blue', 'size' => 'MD'],
4 => ['id' => 4, 'size' => 'LG'],
];

You can use array_replace_recursive to merge the arrays in your particular situation.
$color = array(
array('id' => 1, 'color' => 'red'),
array('id' => 2, 'color' => 'green'),
array('id' => 3, 'color' => 'blue'),
);
$size = array(
array('id' => 1, 'size' => 'SM'),
array('id' => 2, 'size' => 'XL'),
array('id' => 3, 'size' => 'MD'),
array('id' => 4, 'size' => 'LG'),
);
$merged = array_replace_recursive($color, $size);
Output:
array(4) {
[0]=>
array(3) {
["id"]=>
int(1)
["color"]=>
string(3) "red"
["size"]=>
string(2) "SM"
}
[1]=>
array(3) {
["id"]=>
int(2)
["color"]=>
string(5) "green"
["size"]=>
string(2) "XL"
}
[2]=>
array(3) {
["id"]=>
int(3)
["color"]=>
string(4) "blue"
["size"]=>
string(2) "MD"
}
[3]=>
array(2) {
["id"]=>
int(4)
["size"]=>
string(2) "LG"
}
}
Note: I used the traditional array layout because my PHP version won't support the new one yet :)
Second option
You can also use array_map. This will let you add as much arrays as you want with a little tweaking.
$merged = array_map(function ($c, $s) {
return array_merge($c, $s);
}, $color, $size);
var_dump($merged); // See output above

I'd suggest using laravel's collections, since this question has the laravel tag.
$color = collect(
['id' => 1, 'color' => 'red'],
['id' => 2, 'color' => 'green'],
['id' => 3, 'color' => 'blue']
);
$size = collect(
['id' => 1, 'size' => 'SM'],
['id' => 2, 'size' => 'XL'],
['id' => 3, 'size' => 'MD'],
['id' => 4, 'size' => 'LG']
);
$combined = $color->merge($size);

Folllow this:
array_replace_recursive() is recursive : it will recurse into arrays and apply the same process to the inner value.
$combined = array_replace_recursive($color, $size);
then you can print to see the result as bellow:
print_r($combined);

Simple nested loop would solve the purpose.
foreach($size as $key => $value1) {
foreach($color as $value2) {
if($value1['id'] === $value2['id']){
$size[$key]['color'] = $value2['color'];
}
}
}
echo '<pre>';
print_r($size);
Output:
Array
(
[0] => Array
(
[id] => 1
[size] => SM
[title] => red
)
[1] => Array
(
[id] => 2
[size] => XL
[title] => green
)
[2] => Array
(
[id] => 3
[size] => MD
[title] => blue
)
[3] => Array
(
[id] => 4
[size] => LG
)
)

You need to traverse all elements from both arrays one by one and merge any duplicate elements. To do this you need to perform these steps.
Glue both arrays together.
$arr = [
['id' => 1, 'color' => 'red'],
['id' => 2, 'color' => 'green'],
['id' => 3, 'color' => 'blue'],
['id' => 1, 'size' => 'SM'],
['id' => 2, 'size' => 'XL'],
['id' => 4, 'size' => 'LG'],
['id' => 3, 'size' => 'MD'],
];
Loop the combined array copying the items into a new array.
If the ID has been seen before don't append the row, but merge it with an existing one.
You can achieve all of this with a single foreach loop and array_merge. There is no need for recursive functions or nested loops.
// Loops on merged arrays and copied elements into a new array
foreach(array_merge($color, $size) as $el){
// If the element with this id is already in new array then add the elements together
$merged[$el['id']] = ($merged[$el['id']] ?? []) + $el;
}
The only downside is that you lose the original indexes, but it looks from your question that this was not important to you. If you want, you can reindex the array with
$merged = array_values($merged);
Live Demo

Try:
$out = array();
foreach ($size as $key => $value){
if(!isset($color[$key])) { $color[$key] = array(); }
$out[] = array_merge((array)$value,(array)$color[$key]);
}
Output:
Array
(
[0] => Array
(
[id] => 1
[size] => SM
[color] => red
)
[1] => Array
(
[id] => 2
[size] => XL
[color] => green
)
[2] => Array
(
[id] => 3
[size] => MD
[color] => blue
)
[3] => Array
(
[id] => 4
[size] => LG
)
)

Better way using loop. First calculate the max array the by count this number run a loop. It will work.

Related

How to remove from a multidimensional array all duplicate elements including the original?

I am using php 7.1.
I have seen that to eliminate the duplicate elements it is enough with this
array_unique($array, SORT_REGULAR);
I've also seen this work
array_map("unserialize", array_unique(array_map("serialize", $array)));
But that only deletes the elements that are duplicated from the array, I want to delete those that are duplicated but I don't want it to leave me only 1 without a duplicate, I want it to also delete that original on which it has been based to verify that it is duplicated
How could I do it?
For example i have this
$array = array(
[0] = array(
[id] => 1,
[number] => 12345,
[date] => 2022-05-09
)
[1] = array(
[id] => 2,
[number] => 123456,
[date] => 2022-05-09
)
[2] = array(
[id] => 3,
[number] => 123456,
[date] => 2022-05-09
)
[3] = array(
[id] => 3,
[number] => 123456,
[date] => 2022-05-09
)
)
How can i let it become this:?
$array = array(
[0] = array(
[id] => 1,
[number] => 12345,
[date] => 2022-05-09
)
[1] = array(
[id] => 2,
[number] => 123456,
[date] => 2022-05-09
)
)
This should be straightforward. Pluck all IDs using array_column and use array_count_values to get counts of occurrences of each ID. Then, use array_filter to filter only unique ones.
<?php
$unique_ids = array_count_values(array_column($array,'id'));
$res = array_filter($array, fn($v) => $unique_ids[$v['id']] === 1);
print_r($res);
Online Demo
Implementing the advice from How to remove values from an array if occurring more than one time?, for best time complexity, keep a lookup array of previously encountered values and their index. When a value is encountered more than once, delete the current and original row from the input array. It is perfectly safe to call unset() on an element that has already been unset(), no breakage will occur.
I have extended your input array to demonstrate that unset() will not cause trouble. I am using "array destructuring" in the foreach() to make the code more concise.
Code: (Demo)
$array = [
['id' => 1, 'number' => 12345, 'date' => '2022-05-09'],
['id' => 2, 'number' => 123456, 'date' => '2022-05-09'],
['id' => 3, 'number' => 123456, 'date' => '2022-05-09'],
['id' => 3, 'number' => 123456, 'date' => '2022-05-09'],
['id' => 4, 'number' => 123457, 'date' => '2022-05-10'],
['id' => 4, 'number' => 123458, 'date' => '2022-05-11'],
['id' => 3, 'number' => 123459, 'date' => '2022-05-12']
];
$found = [];
foreach ($array as $index => ['id' => $id]) {
if (isset($found[$id])) {
unset($array[$index], $array[$found[$id]]);
} else {
$found[$id] = $index;
}
}
var_export($array);
Output:
array (
0 =>
array (
'id' => 1,
'number' => 12345,
'date' => '2022-05-09',
),
1 =>
array (
'id' => 2,
'number' => 123456,
'date' => '2022-05-09',
),
)

Find duplicates in multidemnsional array, remove one PHP

i have multidemensional array where are the keys name ​​with and without duplicates and i need to return final array without duplicates and one from duplicate where key amount is smaller.
For ex:
$tmp = [['name' => 'red', 'amount' => 20],
['name' => 'green', 'amount' => 20],
['name' => 'green', 'amount' => 40]];
final array should be:
$finalArray = [['name' => 'red', 'amount' => 20],
['name' => 'green', 'amount' => 20]]
There is a built in function in php to handle just this
https://www.php.net/manual/en/function.array-unique.php
$input = array("a" => "green", "red", "b" => "green", "blue", "red");
$result = array_unique($input);
Output will be
Array
(
[a] => green
[0] => red
[1] => blue
)

Array tree with parent strings

I have an array list with multiple filenames:
Array (
[0] - FILENAME_01.txt,
[1] - FILENAME_02.txt,
[2] - FILENAME_03.txt,
[3] - FILENAME_03_REJ.txt,
[4] - FILENAME_03_REJ_REJ.txt,
[5] - FILENAME_02_REJ.txt
)
At times a file may contain others. Example is the FILENAME_03 which contains FILENAME_03_REJ and another level FILENAME_03_REJ_REJ. I need to display them hierarchically, not looking for "REJ" as it is variable, but if one contains the other. The end result should be:
Array (
[0] - "filename" => FILENAME_01.txt, "parent_id" => 0,
[1] - "filename" => FILENAME_02.txt, "parent_id" => 0,
[2] - "filename" => FILENAME_03.txt, "parent_id" => 0,
[3] - "filename" => FILENAME_03_REJ.txt, "parent_id" => 2,
[4] - "filename" => FILENAME_03_REJ_REJ.txt, "parent_id" => 3,
[5] - "filename" => FILENAME_02_REJ.txt, "parent_id" => 1,
)
The problem should be solved in PHP.
The solution is below:
<?php
$itens = array(
array('filename' => 'FILENAME_01.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_02.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_03.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_03_REJ.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_03_REJ_REJ.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_02_REJ.txt', 'parent_id' => 0),
array('filename' => 'FILENAME_03_REJ_REJ_REJ.txt', 'parent_id' => 0),
);
foreach($itens as $key1 => $value1){
$string_replace = str_replace(".txt", "", $value1['filename']);
foreach($itens as $key => $value){
if((preg_match('/'.$string_replace.'/',$value['filename'])) AND ($value['filename'] != $string_replace . '.txt')){
$itens[$key]['parent_id'] = $key1;
}
}
}
print '<pre>';
print_r($itens);
print '</pre>';
?>

Merge two 2d arrays and remove duplicate rows

I searched a lot of SOF threads and no one seems to stick to my problem. What's kind of wired because this should be a well discussed question :)
Maybe I'm seeking for the wrong thing...
Scenario:
I have 2 arrays
$a = [
['id' => 5, 'name' => 'bruce'],
['id' => 7, 'name' => 'wayne']
];
// 2 elements
and
$b = [
['id' => 6, 'name' => 'chuck'],
['id' => 8, 'name' => 'norris'],
['id' => 7, 'name' => 'wayne'] //also exists in array $a
];
// 3 elements
My goal is
$c = [
['id' => 6, 'name' => 'chuck'],
['id' => 8, 'name' => 'norris'],
['id' => 7, 'name' => 'wayne'],
['id' => 5, 'name' => 'bruce']
];
// 4 elements (no duplicates)
I really don't care about the order inside the array(s) but I want to merge both into one, without having duplicates.
I tried array_merge and array_merge_recursive. No one works. Probably because the functions doesn't know the identifier which identifies each entry. Is there an easy solution or do I really have to create an own method/function for this?
Maybe there is a closure that I could use?
You can do this with very simple inbuilt function of PHP
$c = array_unique(array_merge($a,$b), SORT_REGULAR);
print_r( $c )
The output of the print_r is
Array
(
[0] => Array
(
[id] => 5
[name] => bruce
)
[1] => Array
(
[id] => 7
[name] => wayne
)
[2] => Array
(
[id] => 6
[name] => chuck
)
[3] => Array
(
[id] => 8
[name] => norris
)
)
$temp = array_merge($b, $a);
foreach ($temp as $v) {
$c[$v['id']] = $v;
}
If it finds the same id, the element will be overwritten in $c
Here's an approach at hashing each array with serialize after a key sort:
<?php
$a = [
['id' => 5, 'name' => 'bruce'],
['id' => 7, 'name' => 'wayne']
];
$b = [
['id' => 6, 'name' => 'chuck'],
['name' => 'wayne', 'id' => 7],
['id' => 8, 'name' => 'norris']
];
$merged = array_merge($a, $b);
foreach($merged as $k => $v) {
ksort($v);
$hashes[$k] = serialize($v);
}
$hashes = array_unique($hashes);
var_export(array_intersect_key($merged, $hashes));
Output:
array (
0 =>
array (
'id' => 5,
'name' => 'bruce',
),
1 =>
array (
'id' => 7,
'name' => 'wayne',
),
2 =>
array (
'id' => 6,
'name' => 'chuck',
),
4 =>
array (
'id' => 8,
'name' => 'norris',
),
)
If you index them on unique id then just add them. The result will be indexed on id which is convenient:
$result = array_column($a, null, 'id') + array_column($b, null, 'id');
I don't know how performant this is, but just using phps array-manipulation functions I get:
>>> array_values(array_merge(array_combine(array_column($a, 'name'), $a), array_combine(array_column($b, 'name'), $b)));
=> [
[
"id" => 5,
"name" => "bruce",
],
[
"id" => 7,
"name" => "wayne",
],
[
"id" => 6,
"name" => "chuck",
],
[
"id" => 8,
"name" => "norris",
],
]

Multi-dimensional array, remove array where key and value match another array

I want to remove duplicates where either measurement or altunit matches another array, but ignoring if they're blank.
Array
(
[0] => Array
(
[id] => 2
[altunit] => %
[measurement] =>
)
[1] => Array
(
[id] => 3
[altunit] =>
[measurement] => 6
)
[2] => Array
(
[id] => 4
[altunit] => %
[measurement] =>
)
[3] => Array
(
[id] => 5
[altunit] =>
[measurement] => 6
)
[4] => Array
(
[id] => 6
[altunit] =>
[measurement] => 6
)
)
Becomes
Array
(
[0] => Array
(
[id] => 2
[altunit] => %
[measurement] =>
)
[1] => Array
(
[id] => 3
[altunit] =>
[measurement] => 6
)
)
Best I can come up with is:
$test = array ( 0 => array ( 'id' => '2', 'altunit' => '%', 'measurement' => NULL, ), 1 => array ( 'id' => '3', 'altunit' => NULL, 'measurement' => '6', ), 2 => array ( 'id' => '4', 'altunit' => NULL, 'measurement' => '6', ), 3 => array ( 'id' => '5', 'altunit' => NULL, 'measurement' => '6', ), 4 => array ( 'id' => '6', 'altunit' => NULL, 'measurement' => '6', ), );
$num = [];
foreach($test as $k => $v) $num[] = $v['measurement'];
But this only works for measurement, and removes the id and altunit keys.
Humm,
Make an array of 'knowed value' for measurement and altunit and then check it it exist on the rest of the values.
something like:
$knowed_altunit=array();
$knowed_measurement=array();
foreach($test as $k=>$v){
if(in_array($v['altunit'],$knowed_altunit)
|| in_array($v['mesurement'],$knowed_measurement)){
//if the value of altunit or measurement is already knowed then remove the entry from the array,
unset($test[$k]);
}else{
//if it never been seen, add it so further entry can be checked agaisnt the knowed value
$knowed_altunit[]=$v['altunit'];
$knowed_measurement[]=$v['mesurement'];
}
}
Sorry if any typo but thins might help you wrap your head around the solution to your problem.
You can try this code:
<?php
/* before you need to check that $test variable is declarated and have all items to check */
$values_altunit = array();
$values_measurement = array();
$result = array();
foreach($test as $key => $value) {
/* check if exist altunit and measurement values in blacklist arrays */
if (!in_array($value['altunit'], $values_altunit) && !in_array($value['measurement'], $values_measurement)) {
/* if not exist, add the item to $result array */
$result[$key] = $value;
/* and add altunit and measurement values to blacklist arrays */
$values_altunit[] = $value['altunit'];
$values_measurement[] = $value['measurement'];
}
}
/* print result items */
var_dump($result);
?>
This is a short code that can give the result for unique measurements
<?php
$arr = array(
"0"=> array (
"id" => 2,
"altunit" => "%",
"measurement" => "",
),
"1"=> array (
"id" => 3,
"altunit" => "",
"measurement" => 6,
),
"2"=> array (
"id" => 4,
"altunit" => "%",
"measurement" => "",
),
"3"=> array (
"id" => 5,
"altunit" => "",
"measurement" => 6,
),
"4"=> array (
"id" => 6,
"altunit" => "",
"measurement" => 6,
)
);
$unique_measure = $new_array = array();
foreach($arr as $sup_key => $sup_val){
foreach($sup_val as $sub_key => $sub_val){
if(!in_array($sup_val['measurement'], $unique_measure)){
array_push($unique_measure, $sup_val['measurement']);
array_push($new_array,$sup_val);
}
}
}
print_r($new_array);
?>
Output :
Array
(
[0] => Array
(
[id] => 2
[altunit] => %
[measurement] =>
)
[1] => Array
(
[id] => 3
[altunit] =>
[measurement] => 6
)
)
try this code. it may do the trick.
One More approach to your problems solution can be removing that particular key from the main array using unset(your_array_key) will do the trick in the same code.
Try, this may help you
function remove_dup($array, $keys )
{
$out = array();
foreach($array as $sub)
{
if(empty($out))
{
$out[] = $sub;
continue;
}
foreach($keys as $key)
{
if($flag=in_array( $sub[$key],array_map(function($e) use($key){ return $e[$key];}, $out)) )
break;
}
if(!$flag)
$out[] = $sub;
}
return $out;
}
// Usage
print_r( remove_dup($array, array('altunit','measurement') ) );
Test
[akshay#localhost tmp]$ cat test.php
<?php
function remove_dup($array, $keys )
{
$out = array();
foreach($array as $sub)
{
if(empty($out))
{
$out[] = $sub;
continue;
}
foreach($keys as $key)
{
if($flag=in_array( $sub[$key],array_map(function($e) use($key){ return $e[$key];}, $out)) )
break;
}
if(!$flag)
$out[] = $sub;
}
return $out;
}
$array = array(
"0"=> array (
"id" => 2,
"altunit" => "%",
"measurement" => "",
),
"1"=> array (
"id" => 3,
"altunit" => "",
"measurement" => 6,
),
"2"=> array (
"id" => 4,
"altunit" => "%",
"measurement" => "",
),
"3"=> array (
"id" => 5,
"altunit" => "",
"measurement" => 4,
),
"4"=> array (
"id" => 6,
"altunit" => "",
"measurement" => 6,
)
);
print_r( remove_dup($array, array('altunit','measurement') ) );
?>
Output
[akshay#localhost tmp]$ php test.php
Array
(
[0] => Array
(
[id] => 2
[altunit] => %
[measurement] =>
)
[1] => Array
(
[id] => 3
[altunit] =>
[measurement] => 6
)
)
What you really want is a set, not an array. So if you can't fix the way you're building the array in the first place (my guess is this came from an SQL query, which would be a lot less code to rectify) you have two options for creating mapped sets in PHP.
You could use SplObjectStorage
You could use an Array with the key as a serialized representation of the set
The first approach would look something like this...
$set = new SplObjectStorage();
$arr = [
0 => [
'id' => '2',
'altunit' => '%',
'measurement' => NULL,
],
1 => [
'id' => '3',
'altunit' => NULL,
'measurement' => '6',
],
2 => [
'id' => '4',
'altunit' => NULL,
'measurement' => '6',
],
3 => [
'id' => '5',
'altunit' => NULL,
'measurement' => '6',
],
4 => [
'id' => '6',
'altunit' => NULL,
'measurement' => '6',
],
];
foreach($arr as $part) {
if (isset($part['altunit'])) { // ignore if blank
$key = (object) $part;
$set->attach($key); // attach the set
}
}
// Now you have...
foreach($set as $value) {
var_dump($value);
}
This would give you...
object(stdClass)#2 (3) {
["id"]=>
string(1) "2"
["altunit"]=>
string(1) "%"
["measurement"]=>
NULL
}
The second approach, using an array, where the array key represents a serialized version of the set would look something like this...
$set = [];
$arr = [
0 => [
'id' => '2',
'altunit' => '%',
'measurement' => NULL,
],
1 => [
'id' => '3',
'altunit' => NULL,
'measurement' => '6',
],
2 => [
'id' => '4',
'altunit' => NULL,
'measurement' => '6',
],
3 => [
'id' => '5',
'altunit' => NULL,
'measurement' => '6',
],
4 => [
'id' => '6',
'altunit' => NULL,
'measurement' => '6',
],
];
foreach($arr as $part) {
if (isset($part['altunit'])) { // ignore if blank
$key = "{$part['altunit']}\0{$part['measurement']}";
$set[$key] = $part; // attach the set
}
}
// Now you have...
foreach($set as $value) {
var_dump($value);
}
And that would give you...
array(3) {
["id"]=>
string(1) "2"
["altunit"]=>
string(1) "%"
["measurement"]=>
NULL
}
N.B
But, if this is a result set from an SQL query it's quite possible you could just more effectively eliminate the de-duplication process entirely by modifying the query to use WHERE NOT NULL and GROUP BY clauses instead.
You can try this also
<?php
$keys = array_map(function ($i) { return $i; }, $array);
$dumparr = array_combine($keys, $array);
$result = array_values($deduped);
print_r($result);
?>

Categories