lp1051
lp1051

Reputation: 501

Get number of identical key-value pairs present in two arrays

I have 2 arrays:

$haystack = array(
    array(
        'name' => 'Bill',
        'city' => 'Rome',
        'color' => 'blue'
    ),
    array(
        'name' => 'Jane',
        'city' => 'Wien',
        'color' => 'red'
    )
);

$needles = array(
    array(
        'name' => 'Bill',
        'city' => 'Rome',
        'color' => 'red'
    ),
    array(
        'name' => 'Jane',
        'city' => 'Wien',
        'color' => 'red'
    )
);

Now I would like to get number of key-value pairs that are present (identical) in both $needles and $haystack arrays. The result should be:

I know, I could run ugly nested foreach:

foreach($haystack as $hi => $arr) {
    foreach($arr as $key => $val) {
        foreach($needles as $ni => $needle) {
            if (!isset($matches[$hi][$ni])) {
                $matches[$hi][$ni] = 0;
            }
            foreach($needle as $k => $v) {
                $key == $k && $val == $v && $matches[$hi][$ni]++;
            }
        }
    }
}
print_r($matches);

but is there any better way? Perhaps some built-in array function that I missed, or should I look more into Iterators?

UPDATE: I'd like to add, that I have also multiple needles array. I made a simple example with just one-dimensional array, but please consider that as well.

UPDATE2: I changed $needles into multi-dimensional array in example. I'm sorry that I didn't do it from the start, but I hoped it will be easier to understand with simple needle and the answers may give me idea how to fix it for multi-dimensional needle as well...

Thanks for help!

Upvotes: 2

Views: 167

Answers (2)

LSerni
LSerni

Reputation: 57388

Now I would like to get number of key-value pairs that are present (identical) in both $needle and $haystack arrays.

This should fit the bill:

return array_map(
    function ($straw) use ($needle) {
        return count(array_intersect_assoc($straw, $needle));
    },
    $haystack
);

It will reproduce a haystack made up of "straws", by keeping the same keys but having for values the count of common keypairs between each straw and the reference needle.

The result should be 2 for $haystack[0] and 1 for $haystack[1].

Indeed, with your input data it returns as expected:

Array
(
    [0] => 2
    [1] => 1
)

Recursive version

In this case we have a haystack (and a needle) with keys, and every value can be an array. We consider two values to be "equal" if they are identical, except for key order. This identicality can be checked by comparing the serializations of the two arrays; to implement the key-order invariance we serialize not the original arrays but copies with the keys (and keys of values, recursively) sorted in a deterministic way.

function deepmatches($haystack, $needle) {
    // Function to "comb" an array so that all keys are in ascending order.
    $keySort    = function($arr, $ks) {
        if (!is_array($arr)) {
            return $arr;
        }
        ksort($arr, SORT_STRING);
        return array_map(
            function ($values) use ($ks) {
                return $ks($values, $ks);
            },
            $arr
        );
    };

    return array_map(
        function ($straw) use ($needle, $keySort) {
            return count(
                array_uintersect_assoc($straw, $needle,
                    function($val1, $val2) use ($keySort) {
                        if (is_array($val1)) {
                            if (!is_array($val2)) { return -1; }
                            // Both arrays.
                            $val1 = serialize($keySort($val1, $keySort));
                            $val2 = serialize($keySort($val2, $keySort));
                        } else {
                            if (is_array($val2)) { return 1; }
                        }
                        if ($val1 < $val2) { return -1; }
                        if ($val1 > $val2) { return 1; }
                        return 0;
                    }
                )
            );
        },
        $haystack
    );
}

The behaviour is the same when the values are strings. Otherwise, values are considered recursively.

$needle = array(
    'name' => 'Bill',
    'city' => array( 
        'country' => 'Italy', 
        'name' => 'Rome'
    ),
    'color' => 'red'
);

The actual case

Here we don't have recursive arrays, but arrays of arrays; we map haystack using needles:

return array_map(
    function ($straw) use ($needles) {
        // $straw is one Element, $needles is an array of.
        return array_map(
            function ($needle) use ($straw) {
                // How many elements are common between $needle and $straw
                return count(array_intersect_assoc($straw, $needle));
            },
            $needles
        );
    },
    $haystack
);

This returns an array with the same cardinality of $haystack, containing arrays that have the same cardinality of $needles. So

$ret[5][2]

contains how many common elements are there between the sixth element of haystack and the third needle.

Given that the main time expenditure is in intersect_assoc, it might well be that the foreach() construct will turn out to be faster. Closures need to twiddle the scope and exercise the stack, while foreachs don't:

$ret = [ ];
foreach ($haystack as $kh => $straw) {
    $row = [ ];
    foreach ($needles as $kn => $needle) {
        $row[$kn] = count(array_intersect_assoc($straw, $needle));
    }
    $ret[$kh] = $row;
}
unset($row);

Upvotes: 2

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

Reputation: 41810

Counting the result of array_intersect_assoc should simplify the code a bit.

foreach ($haystack as $i => $arr) {
    $matches = count(array_intersect_assoc($needle, $arr));
    echo "Needle found {$matches} times in haystack at index {$i} \n";
}

Upvotes: 1

Related Questions