Higeath
Higeath

Reputation: 561

Merging values of nested array

As the title says I get array looking something like this:

array (size=376)
  0 => 
    array (size=3)
      'source' => int 0
      'target' => int 47
      'officers' => 
        array (size=1)
          0 => string 'PARKER, Thomas, Sir' (length=19)
  1 => 
    array (size=3)
      'source' => int 0
      'target' => int 104
      'officers' => 
        array (size=1)
          0 => string 'STEVENS, Anne' (length=13)
  2 => 
    array (size=3)
      'source' => int 0
      'target' => int 187
      'officers' => 
        array (size=1)
          0 => string 'PARKER, Thomas, Sir' (length=19)
  3 => 
    array (size=3)
      'source' => int 0
      'target' => int 229
      'officers' => 
        array (size=1)
          0 => string 'GROTE, Byron' (length=12)
  4 => 
    array (size=3)
      'source' => int 0
      'target' => int 244
      'officers' => 
        array (size=1)
          0 => string 'GROTE, Byron' (length=12)
  5 => 
    array (size=3)
      'source' => int 1
      'target' => int 60
      'officers' => 
        array (size=1)
          0 => string 'BASON, John' (length=11)
  6 => 
    array (size=3)
      'source' => int 1
      'target' => int 92
      'officers' => 
        array (size=1)
          0 => string 'HAUSER, Wolfhart, Dr' (length=20)

If source and target are the same I want to create 1 value in this array for example

Insteed of this:

    5 => 
        array (size=3)
          'source' => int 1
          'target' => int 92
          'officers' => 
            array (size=1)
              0 => string 'BASON, John' (length=11)
      6 => 
        array (size=3)
          'source' => int 1
          'target' => int 92
          'officers' => 
            array (size=1)
              0 => string 'HAUSER, Wolfhart, Dr' (length=20)

I would want to get

5 => 
            array (size=3)
              'source' => int 1
              'target' => int 92
              'officers' => 
                array (size=1)
                  0 => string 'BASON, John' (length=11)
                  1 => string 'HAUSER, Wolfhart, Dr' (length=20)

My idea to achieve this:

for ($i = 0; $i < count($edges); $i++) {
    for ($j = $i + 1; $j < count($edges); $j++) {
        if($edges[$i]['source']==$edges[$j]['source']&&$edges[$i]['target']==$edges[$j]['target']){
            foreach( $edges[$j]['officers'] as $officer){
                array_push($edges[$i]['officers'], $officer);
            }
            array_splice($edges, $j,1);
        }

    }
}

It seems to be merging but only 2 values and it isn't deleting old values so:

I got something like :

 66 => 
    array (size=3)
      'source' => int 24
      'target' => int 103
      'officers' => 
        array (size=2)
          0 => string 'GILAURI, Irakli' (length=15)
          1 => string 'JANIN, Neil' (length=11)
  67 => 
    array (size=3)
      'source' => int 24
      'target' => int 103
      'officers' => 
        array (size=1)
          0 => string 'MORRISON, David' (length=15)

So it merget Gilauri and Janin but didn't add Morrison, David to officers array and also Gilauri, Irakli and Janin, Neil have separate row in this array which shouldn't be added.

Upvotes: 0

Views: 51

Answers (2)

Don&#39;t Panic
Don&#39;t Panic

Reputation: 41820

This is similar to other problems where you can solve this by using a key from the sub-arrays as a key in your merged result. It's just a little different because you need to use more than one key to determine which records should be merged.

I would suggest doing it by just concatenating the two keys.

foreach ($your_array as $item) {
    $k = "$item[source].$item[target]";
    if (isset($merged[$k])) {
        $merged[$k]['officers'] = array_merge($merged[$key]['officers'], $item['officers']);
    } else {
        $merged[$k] = $item;
    }
}

And if you don't like the composite keys, you can do array_values($merged) afterward to get back to plain numeric keys.

Upvotes: 2

bwoebi
bwoebi

Reputation: 23787

Note that array_splice rekeys the array each time it is executed. From the documentation:

Note that numeric keys in input are not preserved.

I.e. array_splice($array, 1, 1) on $array = [0 => 1, 1 => 2, 2 => 3] will result in [0 => 1, 1 => 3].

Thus when you are iterating with a for loop, your index will point one entry further than before for each removed array element.

To remove by index without rekeying, use unset($edges[$j]) instead of the array_splice().

Alternatively use a foreach loop (by reference to operate on the actual loop and not on a copy):

foreach ($edges as $i => &$edge) {
    for ($j = $i + 1; $j < count($edges); $j++) {
        if ($edge['source'] == $edges[$j]['source'] && $edge['target'] == $edges[$j]['target']) {
            foreach ($edges[$j]['officers'] as $officer) {
                array_push($edge['officers'], $officer);
            }
            array_splice($edges, $j, 1);
        }
    }
}

But I recommend the unset() alternative as it is much faster (array_splice always recreates the array completely).

Upvotes: 2

Related Questions