Search code examples
phpalgorithmfilteringbig-oarray-difference

Most efficient way to find bidirectional differences between associative arrays


I have a contact array with a set of values which I need to update. I also have a contactChangeLog array that will save the diff between an existing and updated contact. Only the keys which get updated need to be saved to the log. So with 2 contact arrays:

$oldContact = array(
        'name' => 'Joeahkd',
        'address' => '123 Masjdhfin',
        'city' => 'Spring',
        'state' => 'CA',
        'zip' => '90101',
 );

$newContact = array(
        'name' => 'Joe',
        'address' => '123 Main St',
        'city' => 'Springville',
        'state' => 'CA',
        'zip' => '90101',
);

I can use array_diff_assoc()...

$existing = array_diff_assoc($oldContact, $newContact);
$update = array_diff_assoc($newContact, $oldContact);

$diff = array('previous' => $existing, 'new' => $update);
print_r($diff);

However, I am iterating twice through each array and something I learned about big O notation tells me that isn't right. Is there a more algorithmically efficient way of returning the updated keys and both existing and updated values? The result should be:

$previous = array(
        'name' => 'Joeahkd',
        'address' => '123 Masjdhfin',
        'city' => 'Spring'
 );

$updated = array(
        'name' => 'Joe',
        'address' => '123 Main St',
        'city' => 'Springville'
);

Solution

  • You can try array_filter:

    $updated = array();    
    $previous = array_filter($oldContact, function($v, $k) {
        if ($v != $newContact[$k]) {
            $updated[$k] = $newContact[$k];
            return true;
        }
        return false;
    }, ARRAY_FILTER_USE_BOTH);
    

    array_filter will return the keys/values from the input array if the callback function we specify returns true during each key/value iteration and those will be our $previous. While we do the checking in the callback function we construct $updated.

    UPDATE:

    Using array_filter with array_intersect_key even cut the code further:

    $previous = array_filter($oldContact, function($v, $k) {
        return ($v != $newContact[$k]);
    }, ARRAY_FILTER_USE_BOTH);
    
    $updated = array_intersect_key($newContact, $previous);