Append column values from a 2d array to the rows of another 2d array based on shared column values
Asked Answered
J

7

9

Let's say I have following arrays:

$first = [
    ['id' => 5, 'name' => 'Education'],
    ['id' => 4, 'name' => 'Computers'],
    ['id' => 7, 'name' => 'Science'],
    ['id' => 1, 'name' => 'Sports'],
];

$second = [
    ['id' => 1, 'title' => 'Sport'],
    ['id' => 7, 'title' => 'Sci'],
    ['id' => 4, 'title' => 'Comp'],
    ['id' => 5, 'title' => 'Edu'],
];

And desired output is:

[
    ['id' => 5, 'name' => 'Education', 'title' => 'Edu'],
    ['id' => 4, 'name' => 'Computers', 'title' => 'Comp'],
    ['id' => 7, 'name' => 'Science', 'title' => 'Sci'],
    ['id' => 1, 'name' => 'Sports', 'title' => 'Sport'],
]

I have managed to merge these arrays with simply:

foreach ($first as $key => $value) {
    $result[$key] = array_merge($first[$key], $second[$key]);
}

But the output is combined by first level index instead of their id value:

Array
    (
        [0] => Array
            (
                [id] => 5
                [name] => Education
                [title] => Sport
            )

        [1] => Array
            (
                [id] => 4
                [name] => Computers
                [title] => Sci
            )

        [3] => Array
            (
                [id] => 7
                [name] => Science
                [title] => Comp

        [4] => Array
            (
                [id] => 1
                [name] => Sports
                [title] => Edu
            )
    )

I would like to append the title values from the second array to the first array where there is a matching id value and maintain the sorting of the first array.

How can I achieve this?

Juggle answered 2/12, 2012 at 13:7 Comment(1)
Related page where rows are indexed instead of associative and the two arrays do not have the same length/ids: Merge two indexed arrays of indexed arrays based on first column valueRamose
K
11

You can just do a nested loop and check if the id values match, then add title to $first (or name to $second)

foreach($first as $key => $value){
    foreach($second as $value2){
        if($value['id'] === $value2['id']){
            $first[$key]['title'] = $value2['title'];
        }               
    }
}
Kierkegaard answered 2/12, 2012 at 13:28 Comment(2)
What is the time complexity of this? O(n^2)? It can be definitely done better.Deviate
The time complexity won't be awesome because there is no conditional break in the inner loop.Ramose
S
2

You code works fine. Your expectations are simply incorrect. For example in one array 4th element id holds 1 but in another array, 4th element id is 5, so your "merge these arrays on the same id" makes no sense as by merging 4th elements into one you also merge their children, and since id is used in both arrays, once value HAVE TO be gone as there cannot be two equal keys in array.

EDIT

you have to merge manually as PHP functions merge based on keys while you want to merge based on content:

$result = array();
foreach( $arrayA as $keyA => $valA ) {
  foreach( $arrayB as $keyB => $valB ) {
     if( $valA['id'] == $valB['id'] ) {
       $result[$keyA] = $valA + $valB;

       // or if you do not care output keys, just
       // $result[] = $valA + $valB;
     }
  }
}
Semeiology answered 2/12, 2012 at 13:14 Comment(3)
I have corrected some mistakes in outputs, sorry for that. What I want is to take e.g $first[0]['id'] (equals 5) find same id in the second array, here $second[4]['id'] and merge them into new array like so: array('id'=>5, 'name'=>'Education', 'title'=>'Edu'). Isn't that possible?Juggle
Works like a charm, thanks :) And sorry for misleading title.Juggle
No early breaking when a match is found?Ramose
J
2

To provide an alternative approach available in PHP 5.5+.

Since the ordering of the two arrays is not identical, first use array_column to index the arrays by the id.

This will allow you to use array_replace_recusrive on the id indexed arrays.

array_replace_recursive will merge the values in the arrays matching the index association of both array sets.

Optionally use array_values to reindex the array, removing the id index association.

Example https://3v4l.org/ndv2j

$first = array_column($first, null, 'id');
$second = array_column($second, null, 'id');
$result = array_values(array_replace_recursive($first, $second));

Result

Array
(
    [0] => Array
        (
            [id] => 5
            [name] => Education
            [title] => Edu
        )

    [1] => Array
        (
            [id] => 4
            [name] => Computers
            [title] => Comp
        )

    [2] => Array
        (
            [id] => 7
            [name] => Science
            [title] => Sci
        )

    [3] => Array
        (
            [id] => 1
            [name] => Sports
            [title] => Sport
        )

)
Jasun answered 3/11, 2018 at 18:36 Comment(1)
This snippet makes 4 functional loops.Ramose
R
1

Do not use foreach in foreach,that might be too slow when the array so big.

$idArray = array_column($secondArray,'title','id');
foreach($firstArray as $key => $val){
  $firstArray[$key]['title'] = (isset($idArray[$val['id']])) ? $idArray[$val['id']] : 'some title';
}
Rimmer answered 24/11, 2017 at 8:45 Comment(0)
S
1

This can be easily done using the 3-parameter form of array_column to re-index your second array by the id value and then looping over the first array, merging the contents of the matching second value (where it exists):

$second = array_column($second, null, 'id');
foreach ($first as &$subject) {
    $subject = array_merge($subject, $second[$subject['id']] ?? []);
}

Output:

Array
(
    [0] => Array
        (
            [id] => 5
            [name] => Education
            [title] => Edu
        )
    [1] => Array
        (
            [id] => 4
            [name] => Computers
            [title] => Comp
        )
    [3] => Array
        (
            [id] => 7
            [name] => Science
            [title] => Sci
        )
    [4] => Array
        (
            [id] => 1
            [name] => Sports
            [title] => Sport
        )
)

Demo on 3v4l.org

This has the advantage over Will B.'s answer of not re-indexing the first array. It's also slightly faster (my tests show ~10%) due to only having one call to array_column and none to array_values.

Update

This code can actually be sped up even more by using the array union operator (+) (thanks @mickmackusa):

$second = array_column($second, null, 'id');
foreach ($first as &$subject) {
    $subject += $second[$subject['id']] ?? [];
}

The output of this code is the same as above, but it runs about 10% faster again.

Demo on 3v4l.org

Sharolynsharon answered 12/10, 2020 at 6:16 Comment(2)
This answer is probably as optimized for speed as it can be, but bear in mind that the call of array_column() will lose data IF there are duplicate id column values in the $second array. (This is just a fringe consideration for readers, because the sample data in the question does not have duplicated id column values in the $second array.)Ramose
Another fringe scenario would be if the $first array did not contain an id value that corresponds to the $second array, the data from the $second would be omitted from the result. This may or may not be a desired behavior. In the posted question all available id values are represented in both arrays.Ramose
A
0

Sort both arrays by your 'id' field, then let php do the merge with array_replace_recursive.

function cmp($a, $b) {
  return ((int) $a['id'] < (int) $b['id']) ? -1 : 1;
}

usort($array1, 'cmp');
usort($array2, 'cmp');

$result = array_replace_recursive($array1, $array2);
Ardyth answered 2/12, 2012 at 14:7 Comment(3)
This untested snippet is clearly incorrect in its output.Ramose
Hey @mickmackusa, well spotted. This code works fine with array_replace_recursive but not with array_merge, obviously!Ardyth
I still don't recommend this technique because usort() will require greater computation complexity versus other answers on this page. I'm biased because I cooperated with Nick already, but -- I'd use Nick's answer. This assumes that both arrays contain the same volume and data structure. It also requires both arrays to share the same ids. This answer is not reliable for general use.Ramose
F
0

Make sure that the items are in the same order then:

$items = array_map(function($itemFirstArray, $itemSecondArray) {
  return array_merge($itemFirstArray, $itemSecondArray);
}, $firstArray, $secondArray);
Feininger answered 15/1, 2018 at 13:10 Comment(2)
It probably makes sense to not yatta-yatta over the bit of code that ensures that the two arrays are sorted correctly. To be clear, this technique requires that both arrays contain exactly and only the same id-related data.Ramose
This answer requires that both arrays are the same length and have exactly the same mapping column values.Ramose

© 2022 - 2024 — McMap. All rights reserved.