Compare two arrays and create an associative array of true/false values - php

I want to compare the values of two flat, indexed arrays and generate a new array where the keys are the original values from the first array and the values are boolean values indicating whether the same value occurred in both origibal arrays.
$array1 = [
"car1",
"car2",
"car3",
"car4",
"car5"
];
$array2 = [
"car1",
"car4",
"car5"
}
I tried to compare the arrays with the array_diff() function but it gave me elements values instead of boolean values.
I want to compare each value from the two arrays and generate an "array map" or maybe using the array_combine() function to get array like this:
[
"car1" => true,
"car2" => false,
"car3" => false
"car4" => true,
"car5" => true,
]

Arrays are fun!
PHP has a TON of array functions, and so there's lots of potential solutions.
I came up with this one as a personal challenge, which uses no loops, filters, or maps.
This solution uses array_intersect to find values that exist in both arrays, then array_values along with array_fill_keys to turn them into associative arrays populated with TRUE or FALSE, and finally array_merge to put them together into a single array:
$array1 = array( 0 => "car1", 1 => "car2", 2 => "car3", 3 => "car4", 4 => "car5");
$array2 = array( 0 => "car1", 1 => "car4", 2 => "car5" );
// Find all values that exist in both arrays
$intersect = array_intersect( $array1, $array2 );
// Turn it into an associative array with TRUE values
$intersect = array_fill_keys( array_values($intersect), TRUE );
// Turn the original array into an associative array with FALSE values
$array1 = array_fill_keys( array_values( $array1 ), FALSE );
// Merge / combine the arrays - $intersect MUST be second so that TRUE values override FALSE values
$results = array_merge( $array1, $intersect );
var_dump( $results ); results in:
array (size=5)
'car1' => boolean true
'car2' => boolean false
'car3' => boolean false
'car4' => boolean true
'car5' => boolean true

array_map or array_combine does not actually return what you want. If you wanted to use array_map to the desired effect without writing a foreach loop, below gives you the desired result. Note you have to pass array3 as a reference.
<?php
$array1 = array( 0 => "car1", 1 => "car2", 2 => "car3", 3 => "car4", 4 => "car5");
$array2 = array( 0 => "car1", 1 => "car4", 2 => "car5" );
$array3 = [];
array_map(function($a) use ($array2, &$array3) {
$array3[$a] = in_array($a, array_values($array2));
}, $array1);
var_dump($array3);
Output:
array(5) {
["car1"]=>
bool(true)
["car2"]=>
bool(false)
["car3"]=>
bool(false)
["car4"]=>
bool(true)
["car5"]=>
bool(true)
}

This is slow, but it should do what you want, and be easy to understand.
// Loop over the outer array which we're told has all the categories
$result = array();
foreach($array1 as $sCar1) {
$found = false;
// Loop over the categories of the post
foreach($array2 as $sCar2) {
// If the current post category matches
// the current category we're searching for
// note it and move on
if($sCar2 == $sCar1) {
$found = true;
break;
}
}
// Now indicate in the result if the post has the current category
$result[$sCar1] = $found;
}

<?php
//EXAMPLE 1
$array1 = [0 => "car1", 1 => "car2", 2 => "car3", 3 => "car4", 4 => "car5"];
$array2 = [0 => "car1", 1 => "car4", 2 => "car5"];
$resultArray = [];
foreach ($array1 as $key => $val) {
$resultArray[$val] = in_array($val, $array2);
}
var_dump($resultArray);
?>
<?php
//EXAMPLE 2
$array1 = [0 => "car1", 1 => "car2", 2 => "car3", 3 => "car4", 4 => "car5"];
$array2 = [0 => "car1", 1 => "car4", 2 => "car5"];
$resultArray = [];
$flipped = array_flip($array2);
foreach ($array1 as $key => $val) {
$resultArray[$val] = isset($flipped[$val]);
}
var_dump($resultArray);
?>
RESULT:
array (size=5)
'car1' => boolean true
'car2' => boolean false
'car3' => boolean false
'car4' => boolean true
'car5' => boolean true

With array_count_values it is as simple as this:
$results = array_map(function ($count) {
return $count !== 1;
}, array_count_values(array_merge($array1, $array2)));
So basically, we merge two arrays together, then count occurrences of values. Then if a count is not equal to 1 we map value to true, and otherwise - false. Interesting, that this will work regardless of which array is shorter.
Here is working demo.
Be aware, that array_count_values function throws E_WARNING for every element which is not string or integer. So this approach will work only on arrays of strings and integers.
Also, this approach might fail if your array elements not unique, for this situation you have to apply array_unique to each array beforehand.

Making iterated in_array() calls or nested loops to make value comparisons is not likely to be the most performant approach because value comparisons are slower than key comparisons in PHP.
Making key comparisons is fast, but making no iterated comparisons will be faster again. By relying on the fact that arrays cannot have duplicate keys on a given level, just alter the elements of each input array to be the desired boolean value and then overwrite the first with the second.
Code: (Demo)
var_export(
array_replace(
array_fill_keys($array1, false),
array_fill_keys($array2, true),
)
);

Related

how to fix array_udiff while having strings inside array [duplicate]

I have an array containing rows of associative data.
$array1 = array(
array('ITEM' => 1),
array('ITEM' => 2),
array('ITEM' => 3),
);
I have a second array, also containing rows of associative data, that I would like to filter using the first array.
$array2 = array(
array('ITEM' => 2),
array('ITEM' => 3),
array('ITEM' => 1),
array('ITEM' => 4),
);
This feels like a job for array_diff(), but how can I compare the rows exclusively on the deeper ITEM values?
How can I filter the second array and get the following result?
array(3 => array('ITEM' => 4))
You can define a custom comparison function using array_udiff().
function udiffCompare($a, $b)
{
return $a['ITEM'] - $b['ITEM'];
}
$arrdiff = array_udiff($arr2, $arr1, 'udiffCompare');
print_r($arrdiff);
Output:
Array
(
[3] => Array
(
[ITEM] => 4
)
)
This uses and preserves the arrays' existing structure, which I assume you want.
I would probably iterate through the original arrays and make them 1-dimensional... something like
foreach($array1 as $aV){
$aTmp1[] = $aV['ITEM'];
}
foreach($array2 as $aV){
$aTmp2[] = $aV['ITEM'];
}
$new_array = array_diff($aTmp1,$aTmp2);
Another fun approach with a json_encode trick (can be usefull if you need to "raw" compare some complex values in the first level array) :
// Compare all values by a json_encode
$diff = array_diff(array_map('json_encode', $array1), array_map('json_encode', $array2));
// Json decode the result
$diff = array_map('json_decode', $diff);
A couple of solutions using array_filter that are less performant than the array_udiff solution for large arrays, but which are a little more straightforward and more flexible:
$array1 = [
['ITEM' => 1],
['ITEM' => 2],
['ITEM' => 3],
];
$array2 = [
['ITEM' => 2],
['ITEM' => 3],
['ITEM' => 1],
['ITEM' => 4],
];
$arrayDiff = array_filter($array2, function ($element) use ($array1) {
return !in_array($element, $array1);
});
// OR
$arrayDiff = array_filter($array2, function ($array2Element) use ($array1) {
foreach ($array1 as $array1Element) {
if ($array1Element['ITEM'] == $array2Element['ITEM']) {
return false;
}
}
return true;
});
As always with array_filter, note that array_filter preserves the keys of the original array, so if you want $arrayDiff to be zero-indexed, do $arrayDiff = array_values($arrayDiff); after the array_filter call.
you can use below code to get difference
$a1 = Array(
[0] => Array(
[ITEM] => 1
)
[1] => Array(
[ITEM] => 2
)
[2] => Array(
[ITEM] => 3
)
);
$a2 = Array(
[0] => Array(
[ITEM] => 2
)
[1] => Array(
[ITEM] => 3
)
[2] => Array(
[ITEM] => 1
)
[3] => Array(
[ITEM] => 4
));
array_diff(array_column($a1, 'ITEM'), array_column($a2, 'ITEM'));
Having the same problem but my multidimensional array has various keys unlike your "ITEM" consistently in every array.
Solved it with: $result = array_diff_assoc($array2, $array1);
Reference: PHP: array_diff_assoc
Another solution
if( json_encode($array1) == json_encode($array2) ){
...
}
Trust that the maintainers of PHP have optimized array_udiff() to outperform all other techniques which could do the same.
With respect to your scenario, you are seeking a filtering array_diff() that evaluates data within the first level's "value" (the row of data). Within the custom function, the specific column must be isolated for comparison. For a list of all native array_diff() function variations, see this answer.
To use the first array to filter the second array (and output the retained data from the second array), you must write $array2 as the first parameter and $array1 as the second parameter.
array_diff() and array_intersect() functions that leverage (contain u in their function name) expect an integer as their return value. That value is used to preliminary sort the data before actually performing the evaluations -- this is a performance optimization. There may be scenarios where if you only return 0 or 1 (not a three-way comparison), then the results may be unexpected. To ensure a stable result, always provide a comparison function that can return a negative, a positive, and a zero integer.
When comparing integer values, subtraction ($a - $b) will give reliable return values. For greater utility when comparing float values or non-numeric data, you can use the spaceship operator when your PHP version makes it available.
Codes: (Demo)
PHP7.4+ (arrow functions)
var_export(
array_udiff($array2, $array1, fn($a, $b) => $a['ITEM'] <=> $b['ITEM'])
);
PHP7+ (spaceship operator)
var_export(
array_udiff(
$array2,
$array1,
function($a, $b) {
return $a['ITEM'] <=> $b['ITEM'];
}
)
);
PHP5.3+ (anonymous functions)
var_export(
array_udiff(
$array2,
$array1,
function($a, $b) {
return $a['ITEM'] === $b['ITEM']
? 0
: ($a['ITEM'] > $b['ITEM'] ? 1 : -1);
}
)
);
Output for all version above:
array (
3 =>
array (
'ITEM' => 4,
),
)
When working with object arrays, the technique is the same; only the syntax to access a property is different from accessing an array element ($a['ITEM'] would be $a->ITEM).
For scenarios where the element being isolated from one array does not exist in the other array, you will need to coalesce both $a and $b data to the opposite fallback column because the data from the first array and the second arrays will be represented in both arguments of the callback.
Code: (Demo)
$array1 = array(
array('ITEM' => 1),
array('ITEM' => 2),
array('ITEM' => 3),
);
$array2 = array(
array('ITEMID' => 2),
array('ITEMID' => 3),
array('ITEMID' => 1),
array('ITEMID' => 4),
);
// PHP7.4+ (arrow functions)
var_export(
array_udiff(
$array2,
$array1,
fn($a, $b) => ($a['ITEM'] ?? $a['ITEMID']) <=> ($b['ITEM'] ?? $b['ITEMID'])
)
);
Compares array1 against one or more other arrays and returns the values in array1 that are not present in any of the other arrays.
//Enter your code here, enjoy!
$array1 = array("a" => "green", "red", "blue");
$array2 = array("b" => "green", "yellow", "red");
$result = array_diff($array1, $array2);
print_r($result);

Calculate the average of each column in a 2d array

I need to calculate the average value for each column of data in an array of associative arrays. The result should be a flat, associative array of averages.
Sample array:
$array = [
[
"a" => 0.333,
"b" => 0.730,
"c" => 0.393
],
[
"a" => 0.323,
"b" => 0.454,
"c" => 0.987
],
[
"a" => 0.753,
"b" => 0.983,
"c" => 0.123
]
];
I am looking for a simpler way of processing all the array elements and producing a single array which has a mean value (average) of all the corresponding values.
My current code works, but I'm hoping for a more elegant approach.
$a = []; // Store all a values
$b = []; // Store all b values
$c = []; // Store all c values
for ( $i = 0; $i < count( $array ); $i ++ ) {
// For each array, store each value in it's corresponsing array
// Using variable variables to make it easy
foreach ( $array[ $i ] AS $key => $val ) {
$k = $key;
$$k[] = $val;
};
}
// Create single array with average of all
$fa = array(
'a' => array_sum($a) / count($a),
'b' => array_sum($b) / count($b),
'c' => array_sum($c) / count($c)
);
The desired result:
[
'a' => 0.4696666666666667,
'b' => 0.7223333333333333,
'c' => 0.501,
]
Assuming each sub-array has the same keys:
foreach(array_keys($array[0]) as $key) {
$result[$key] = array_sum($tmp = array_column($array, $key))/count($tmp);
}
Get the keys from the first sub-array
Loop, extract those values from the main array and calculate
Here is a functional-style approach which will work as #AbraCadaver's answer does.
Code: (Demo)
var_export(
array_reduce(
array_keys($array[0] ?? []),
fn($result, $k) => $result + [$k => array_sum($c = array_column($array, $k)) / count($c)],
[]
)
);
I used ?? [] as a safeguard for live applications where it is possible that the input array is empty (and does not contain an element at [0]).
Using PHP7.4's arrow function syntax allows the accessibility of the input array without a use() statement.
The + in the custom function is not performing addition; it is uniting the result array with the new associative array -- this serves to push the new element into the returned result array.

Get all the keys presented in all arrays

I have some arrays, for example
$arr[0]=array(k1=>1,k2=>1,k3=>1);
$arr[1]=array(k2=>1,k3=>1,k4=>1);
$arr[2]=array(k3=>1,k4=>1,k5=>1);
So, I need to get all the keys (dynamically, the number of arrays can differ), presented in all arrays. In this case it is k3 key. So the result should be array('k3'=>1)
I suggest it could be achieved by multiple loops, but probably there's some easier way.
You need the function array_intersect_key():
<?php
$arr1 = array('k1' => 1, 'k2' => 1, 'k3' => 1);
$arr2 = array('k2' => 1, 'k3' => 1, 'k4' => 1);
$arr3 = array('k3' => 1, 'k4' => 1, 'k5' => 1);
print_r(
array_intersect_key($arr1, $arr2, $arr3)
);
Output:
Array
(
[k3] => 1
)
To get the common elements in three arrays, you can use array_intersect()
Note: This function works on common array values and not common array keys
Try this:
$key1 = array_flip($arr1);
$key2 = array_flip($arr1);
$key3 = array_flip($arr1);
$intersect = array_flip(array_intersect($key1, $key2, $key3));

How to determine max length of a certain array column?

The array looks like this:
array(
array(5, true, 'Foo'),
array(8, true, 'Bar'),
array(8, true, 'FooBar'),
)
Can I determine the longest string length of the 3rd column, without having to iterate over the array?
In my example the longest string would be "FooBar" - 6 chars.
If the inner array had only the string element, I could do max(array_map('strlen', $arr)), but it has 3 items...
Add array_map('array_pop', $arr) to the mix:
<?php
$arr = array(
array(5, true, 'Foo'),
array(8, true, 'Bar'),
array(8, true, 'FooBarss')
);
print_r(max(array_map('strlen', array_map('array_pop', $arr))));
?>
http://codepad.org/tRzHoy7Z
Gives 8 (after I added the two ss to check). array_pop() takes the last array element off and returns it, use array_shift() to get the first.
First I'm pretty sure that the max function iterates over the whole array. But if you're fine with using it then you can define your own comparison function and pass it.
function cmp($a, $b) {
if (strlen($a[2]) == strlen($b[2])))
return 0;
return (strlen($a[2]) < strlen($b[2])) ? -1 : 1;
}
max(array_map('cmp', $arr))
Simply sorting the array of arrays then picking the first positioned row involves extra/unnecessary algorithmic sorting (specifically for non-longest rows) and also may give misleading results if there is a tie for longest.
I recommend accommodating the possibility of multiple longest strings and only traversing the input array one time.
Code: (Demo)
$array = [
[5, true, 'FooBar'],
[8, true, 'Bar'],
[18, true, 'IsTied'],
];
$result = [];
$longest = 0;
foreach ($array as $row) {
$len = strlen($row[2]);
if ($len >= $longest) {
$longest = $len;
$result[$len][] = $row;
}
}
var_export($result[$longest] ?? []);
Output:
array (
0 =>
array (
0 => 5,
1 => true,
2 => 'FooBar',
),
1 =>
array (
0 => 18,
1 => true,
2 => 'IsTied',
),
)
That said, if you just want to see the longest length and don't care where it came from, then you can use this:
var_export(max(array_map('strlen', array_column($array, 2))));

array_splice() - Numerical Offsets of Associative Arrays

I'm trying to do something but I can't find any solution, I'm also having some trouble putting it into works so here is a sample code, maybe it'll be enough to demonstrate what I'm aiming for:
$input = array
(
'who' => 'me',
'what' => 'car',
'more' => 'car',
'when' => 'today',
);
Now, I want to use array_splice() to remove (and return) one element from the array:
$spliced = key(array_splice($input, 2, 1)); // I'm only interested in the key...
The above will remove and return 1 element (third argument) from $input (first argument), at offset 2 (second argument), so $spliced will hold the value more.
I'll be iterating over $input with a foreach loop, I know the key to be spliced but the problem is I don't know its numerical offset and since array_splice only accepts integers I don't know what to do.
A very dull example:
$result = array();
foreach ($input as $key => $value)
{
if ($key == 'more')
{
// Remove the index "more" from $input and add it to $result.
$result[] = key(array_splice($input, 2 /* How do I know its 2? */, 1));
}
}
I first though of using array_search() but it's pointless since it'll return the associative index....
How do I determine the numerical offset of a associative index?
Just grabbing and unsetting the value is a much better approach (and likely faster too), but anyway, you can just count along
$result = array();
$idx = 0; // init offset
foreach ($input as $key => $value)
{
if ($key == 'more')
{
// Remove the index "more" from $input and add it to $result.
$result[] = key(array_splice($input, $idx, 1));
}
$idx++; // count offset
}
print_R($result);
print_R($input);
gives
Array
(
[0] => more
)
Array
(
[who] => me
[what] => car
[when] => today
)
BUT Technically speaking an associative key has no numerical index. If the input array was
$input = array
(
'who' => 'me',
'what' => 'car',
'more' => 'car',
'when' => 'today',
'foo', 'bar', 'baz'
);
then index 2 is "baz". But since array_slice accepts an offset, which is not the same as a numeric key, it uses the element found at that position in the array (in order the elements appear), which is why counting along works.
On a sidenote, with numeric keys in the array, you'd get funny results, because you are testing for equality instead of identity. Make it $key === 'more' instead to prevent 'more' getting typecasted. Since associative keys are unique you could also return after 'more' was found, because checking subsequent keys is pointless. But really:
if(array_key_exists('more', $input)) unset($input['more']);
I found the solution:
$offset = array_search('more', array_keys($input)); // 2
Even with "funny" arrays, such as:
$input = array
(
'who' => 'me',
'what' => 'car',
'more' => 'car',
'when' => 'today',
'foo', 'bar', 'baz'
);
This:
echo '<pre>';
print_r(array_keys($input));
echo '</pre>';
Correctly outputs this:
Array
(
[0] => who
[1] => what
[2] => more
[3] => when
[4] => 0
[5] => 1
[6] => 2
)
It's a trivial solution but somewhat obscure to get there.
I appreciate all the help from everyone. =)
$i = 0;
foreach ($input as $key => $value)
{
if ($key == 'more')
{
// Remove the index "more" from $input and add it to $result.
$result[] = key(array_splice($input, $i , 1));
}
$i++;
}

Categories