0

I would like to merge two arrays to compare old vs new values. For example, $arr1 is old values $arr2 is new values.

In case when the data is deleted $arr2 is an empty array. Example:

$arr1 =  [
  "databases" => [
    0 => [
      "id" => 1
      "name" => "DB1"
      "slug" => "db1"
      "url" => "https://www.db1.org"
    ]
  ]
];

$arr2 = [];

For this my expected output after merge is

$merged = [
    "databases" => [
        0 => [
            "id" => [
                'old' => 1,
                'new' => null
            ],
            "name" => [
                'old' => "DB1",
                'new' => null
            ],
            "slug" => [
                'old' => "db1",
                'new' => null
            ],
            "url" => [
                'old' => "https://www.db1.org",
                'new' => null
            ],
        ]
    ]
];

if arr2 is different then the values should be present in the new field instead of null.

For example:

$arr1 =  [
  "databases" => [
    0 => [
      "id" => 1
      "name" => "DB1"
      "slug" => "db1"
      "url" => "https://www.db1.org"
    ]
  ]
];

$arr2 =  [
  "databases" => [
    0 => [
      "id" => 5
      "name" => "DB2"
      "slug" => "db2"
      "url" => "https://www.db2.com"
    ]
  ]
];

expected output:

$merged = [
    "databases" => [
        0 => [
            "id" => [
                'old' => 1,
                'new' => 5
            ],
            "name" => [
                'old' => "DB1",
                'new' => "DB2"
            ],
            "slug" => [
                'old' => "db1",
                'new' => "db2"
            ],
            "url" => [
                'old' => "https://www.db1.org",
                'new' => "https://www.db2.com"
            ],
        ]
    ]
];

Case 3 is when $arr1 is empty but $arr2 is populated:

$arr1 = [];

$arr2 =  [
  "databases" => [
    0 => [
      "id" => 1
      "name" => "DB1"
      "slug" => "db1"
      "url" => "https://www.db1.org"
    ]
  ]
];

and the expected output is:

$merged = [
    "databases" => [
        0 => [
            "id" => [
                'old' => null,
                'new' => 1
            ],
            "name" => [
                'old' => null,
                'new' => "DB1"
            ],
            "slug" => [
                'old' => null,
                'new' => "db1"
            ],
            "url" => [
                'old' => null,
                'new' => "https://www.db1.org"
            ],
        ]
    ]
];

The inbuilt php functions cannot format the data in old vs new format so was wondering how to go about this? Any solutions/suggestions would be appreciated.

Update

Here is what I had tried before:

  1. I had tried simple array_merge_recursive but it does not store the source array. So if you have $arr1 key not there, the final merged array will only have one value.
  2. I tried some more recursive functions late in the night but failed so in essence didn't have anything to show for what I had tried. However, this morning, I came up with the solution and have posted it as an answer in case anyone needs to use it.
Lutz Prechelt
  • 36,608
  • 11
  • 63
  • 88
Coola
  • 2,934
  • 2
  • 19
  • 43
  • 1
    Hi! At the moment, this question reads a bit too much like wanting someone to do your work for you. You'll probably get better help if you make an attempt yourself, and show the code you come up with, and where you think it goes wrong. – IMSoP Aug 21 '21 at 11:59
  • @IMSoP sorry, it was late in the night when I gave up and posted this. Now that I read it again yes it does sound like it. I already came up with a solution and will post it as an answer in case anyone else needs this. I just need to test it out a bit more. – Coola Aug 21 '21 at 14:39
  • reg. _"how to go about this?"_: why not have two arrays, one for _old_, the other for _new_. Then obtain the keys and do the various operations. The `+` array operator might come in handy. Just some ideas. – hakre Aug 21 '21 at 14:50

2 Answers2

0

After reviewing my own code here is the solution I came up with. I am posting it here in case someone else needs a solution for this:

  /**
   * Function to merge old and new values to create one array with all entries
   *
   * @param array $old
   * @param array $new
   * @return void
   */
  function recursiveMergeOldNew($old, $new) {
    $merged = array();

    $array_keys = array_keys($old) + array_keys($new);
    
    if($array_keys) {
      foreach($array_keys as $key) {
        $oldChildArray = [];
        $newChildArray = [];
        if(isset($old[$key])) {
          if(!is_array($old[$key])) {
            $merged[$key]['old'] = $old[$key];
          } else {
            $oldChildArray = $old[$key];
          }
        } else {
          $merged[$key]['old'] = null;
        }
        
        if(isset($new[$key])) {
          if( !is_array($new[$key])) {
            $merged[$key]['new'] = $new[$key];
          } else {
            $newChildArray = $new[$key];
          }
        } else {
          $merged[$key]['new'] = null;
        }

        if($oldChildArray || $newChildArray) {
          $merged[$key] = recursiveMergeOldNew($oldChildArray, $newChildArray);
        }
      }
    }

    return $merged;
  }

Note - this solution needs testing.

Coola
  • 2,934
  • 2
  • 19
  • 43
0

Interesting question, as long as a (non-empty) array on one side means to traverse into it and any skalar or null is a terminating node (while if any of old or new being an array would enforce traversing deeper so dropping the other non-array value):

It works by mapping both old and new on one array recursively and when the decision is to make to traverse to offer null values in case a keyed member is not available while iterating over the super set of the keys of both while null would represent no keys:

$keys = array_unique(array_merge(array_keys($old ?? []), array_keys($new ?? [])));

$merged = [];
foreach ($keys as $key) {
    $merged['old'] = $old[$key] ?? null;
    $merged['new'] = $new[$key] ?? null;
}

This then can be applied recursively, for which I found it is easier to handle both $old and $new as ['old' => $old, 'new' => $new] for that as then the same structure can be recursively merged:

function old_and_new(array $old = null, array $new = null): array
{
    $pair = get_defined_vars();
    $map =
        static fn(callable $map, array $arrays): array => in_array(true, array_map('is_array', $arrays), true)
        && ($parameter = array_combine($k = array_keys($arrays), $k))
        && ($keys = array_keys(array_flip(array_merge(...array_values(array_map('array_keys', array_filter($arrays, 'is_array'))))))
        )
            ? array_map(
                static fn($key) => $map($map, array_map(static fn($p) => $arrays[$p][$key] ?? null, $parameter)),
                array_combine($keys, $keys)
            )
            : $arrays;

    return $map($map, $pair);
}

print_r(old_and_new(new: $arr2));

Online demo: https://3v4l.org/4KdLs#v8.0.9


The inner technically works with more than two arrays, e.g. three. And it "moves" the array keys upwards, similar to a transpose operation. Which btw. there is a similar question (but only similar, for the interesting part in context of your question it is not answered and my answer here doesn't apply there directly):

hakre
  • 193,403
  • 52
  • 435
  • 836
  • Thanks for the solution. But this fails if the depth of the array is different. So if the level of "databases" is nested one level deeper it would fail. – Coola Aug 27 '21 at 16:06
  • @Coola: Yes, that was a limitation with that suggestion, as the traversal (by depth) had to be mapped to get it mapped. I now made it a recursive function, it is actually easier, however I had not much time to write the description but you can already find the answer updated. – hakre Aug 27 '21 at 17:32
  • Thanks Hakre. Also how does it compare to my answer below? Are there any advantages to either? – Coola Aug 27 '21 at 18:25
  • Things it does differently: turning old+new into an array *first* and then traverse on it. Builds groups of unique keys (your `array_keys($old) + array_keys($new)` may see side-effects you don't want for keys), allows intermediates (old, new1, new2, new3, latest) strictly not needed, but it generalizes the code which can give stability. Directly compared, recursion is simplified by using one instead of two (or three etc.) parameters. Otherwise: This example code-style is really not very readable. It should have a version that shows it with foreach etc. as well and for comparison. – hakre Aug 27 '21 at 20:58