Best way in comparing multiple values in arrays - php

if ($item['id_piatto'] === $riga['id_piatto'] && $item['id_portata'] === $riga['id_portata'] && $item['id_dieta'] === $riga['id_dieta']) {
$item['quantita'] += $riga['quantita'];
}
Is there a more compact way to compare multiple values of two arrays instead of the one in the code above?

In order to compare several values ​​of two arrays (Not the whole array!) with given keys, such a function is useful:
function array_cmp_keys(array $arr1, array $arr2, array $keys){
foreach($keys as $key){
if($arr1[$key] !== $arr2[$key]) return false;
}
return true;
}
Testdata:
$item = ['id_piatto' => 1, 'id_portata' => 2, 'id_dieta' => 3, 'xx' => 4];
$riga = ['id_piatto' => 1, 'id_portata' => 2, 'id_dieta' => 3, 'xx' => 5];
How to use:
$keys = ['id_piatto', 'id_portata', 'id_dieta'];
if(array_cmp_keys($item,$riga,$keys)) {
echo "Equal";
}
else {
echo "not Equal";
}
"Equal" is output for this test data

As the top comment points out, your original solution is just fine especially if you are only dealing with a small number of array elements. You only have 3 lines of code and the memory footprint is tiny. Go with your own solution unless you have a good reason not to.
If you have a large number of array elements to compare and a limited number of elements to exclude, you may wish to automate things a bit. Note that this has a memory hit because you're duplicating the array.
<?php
// Original arrays
$first = ['name' => 'tony', 'address' => '123 Main St.', 'position' => 'clerk', 'amount' => 15];
$second = ['name' => 'tony', 'address' => '123 Main St.', 'position' => 'clerk', 'amount' => 23];
// Keys that you want to exclude from the comparison.
$excludeInComparison = ['amount'];
// Duplicate the arrays but exclude these keys for the comparison.
$firstFiltered = array_diff_key($first, array_flip($excludeInComparison));
$secondFiltered = array_diff_key($second, array_flip($excludeInComparison));
// Compare the filtered arrays
if ($firstFiltered == $secondFiltered) {
$first['amount'] += $second['amount'];
}

You can use PHP's advanced array destructuring syntax, available from PHP 7.1:
['id_piatto' => $a[], 'id_portata' => $a[], 'id_dieta' => $a[]] = $item;
['id_piatto' => $b[], 'id_portata' => $b[], 'id_dieta' => $b[]] = $riga;
$item['quantita'] += ($a === $b) ? $riga['quantita'] : 0;
I think this looks nice and readable, but it really shines when the input arrays have different keys and/or nested structure, I'll give you an example:
$one = [
'foo' => 1,
'bar' => [1,2,3],
'baz' => ['baz_id' => 4]
];
$two = [
'foo' => 1,
'bar' => [5,5,3],
'boo_id' => 4
];
['foo' => $a[], 'bar' => [2 => $a[]], 'baz' => ['baz_id' => $a[]]] = $one;
['foo' => $b[], 'bar' => [2 => $b[]], 'boo_id' => $b[]] = $two;
var_dump($a === $b); // true
Note how I used a numeric index to access the third value in the 'bar' sub-array. Finally, this is much more flexible than the custom function approach in some of the other answers. For detailed info, Check out this blog post about array destructuring in PHP.
Addendum
If it's all about nice looking code and readability, is this really better than the standard approach (maybe structured with some linebreaks)?
if(
$one['foo'] === $two['foo']
&& $one['bar'][2] === $two['bar'][2]
&& $one['baz']['baz_id'] === $two['boo_id']
) {
// Do something...
}
Apart from the stylistic viewpoint, the standard approach has still two big advantages:
One could easily use an individual comparison operator for each expression
One could implement more advanced boolean logic
One the other hand, the destructuring approach will generate 2 "normalized" subsets of your input arrays on the fly, which could be useful later...
if($a === $b) {
// Do something
} else {
return [
'error_msg' => "Inputs don't match",
'context' => [
'expected' => $a,
'actual' => $b
]
];
}

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);

Average each associative pair found in a 2d array

Consider this collection below:
$collection = [
[1 => 10.0, 2 => 20.0, 3 => 50.0, 4 => 80.0, 5 => 100.0],
[3 => 20.0, 5 => 20.0, 6 => 100.0, 7 => 10.0],
[1 => 30.0, 3 => 30.0, 5 => 10.0, 8 => 10.0]
];
Consider this theorical output based on the intersection of the Arrays contained into $collection, considering their array keys with respective values based on the average of the single values:
$output = Array ( 3 => 33.3333, 5 => 43.3333 );
Can this problem be resolved with a native PHP function like array_intersect_* in an elegant way?
If not, can you suggest me an elegant solution that doesn't necessarily need an outer ugly foreach?
Keep in mind that the number of arrays that need to be intersected is not fixed. It can be 2 input arrays as it can be 1000 input arrays.
Keys will be integers at all times, and Values will be floats or integers at all times.
In other words:
$collection = [
$arr1 = [ ... ];
$arr2 = [ ... ];
$arr3 = [ ... ];
...
$arrn = [ ... ];
];
$output = [ intersected and weighted array based (on comparison) on keys from $arr1 to $arrn, and (on values) from the value averages ];
Count the input array once.
$n = count($collection);
Compute the intersection of all the sub-arrays by key.
$intersection = array_intersect_key(...$collection);
// PHP5: $intersection = call_user_func_array('array_intersect_key', $input);
Build your result by averaging the column from the input array for each key from the intersection.
$output = [];
foreach ($intersection as $key => $value) {
$output[$key] = array_sum(array_column($collection, $key)) / $n;
}
If you really want to completely avoid foreach you can use array_map instead.
$output = array_map(function($key) use ($collection, $n) {
return array_sum(array_column($collection, $key)) / $n;
}, array_keys($intersection));
But in my opinion, this just adds unnecessary complexity.
Note: The values in $intersection will be single values from the first sub-array, but they don't really matter; they're disregarded when generating the output. If it bothers you to have a useless $value variable in the foreach, then you can do foreach (array_keys($intersection) as $key) instead, but I opted for avoiding an unnecessary function call.
Can this problem be resolved with a native PHP function like array_intersect_* in an elegant way?
Well, elegance is in the eye of the developer. If functional-style programming with no new globally-scoped variables equals elegance, then I have something tasty for you. Can a native array_intersect_*() call be leveraged in this task? You bet!
There's a big lack in PHP native functions on intersects - #Maurizio
I disagree. PHP has a broad suite of powerful, optimized, native array_intersect*() and array_diff*() functions. I believe that too few developers are well-acquainted with them all. I've even build a comprehensive demonstration of the different array_diff*() functions (which can be easily inverted to array_intersect*() for educational purposes).
Now, onto your task. First, the code, then the explanation.
Code: (Demo)
var_export(
array_reduce(
array_keys(
array_intersect_ukey(
...array_merge($collection, [fn($a, $b) => $a <=> $b])
)
),
fn($result, $k) => $result + [$k => array_sum(array_column($collection, $k)) / count($collection)],
[]
)
);
The first subtask is to isolate the keys which are present in every row. array_intersect_ukey() is very likely the best qualified tool. The easy part is the custom function -- just write the two parameters with the spaceship in between. The hard part is setting up the variable number of leading input parameters followed by the closure. For this, temporarily merge the closure as an array element onto the collection variable, then spread the parameters into the the native function.
The payload produced by #1 is an array consisting of the associative elements from the first row where the keys were represented in all rows ([3 => 50.0, 5 => 100.0]). To prepare the data for the next step, the keys must be converted to values -- array_keys() is ideal because the float value are of no further use.
Although there is an equal number of elements going into and returning in the final "averaging step", the final result must be a flat associative array -- so array_map() will not suffice. Instead, array_reduce() is better suited. With the collection variable accessible thanks to PHP7.4's arrow function syntax, array_column() can isolate the full column of data then the averaging result pushed as an associative element into the result array.
I guess it could be done like this:
<?php
$intersecting_arrays = Array (
0 => Array ( 'one' => 10, 'two' => 20, 'three' => 50, 'four' => 80, 'five' => 100 ),
1 => Array ( 'three' => 20, 'five' => 20, 'six' => 100, 'seven' => 10 ),
2 => Array ( 'one' => 30, 'three' => 30, 'five' => 10, 'eight' => 10 )
);
$temp = $intersecting_arrays[0];
for($i = 1; $i < count($intersecting_arrays); $i++) {
$temp = array_intersect_key($temp, $intersecting_arrays[$i]);
}
$result = Array();
foreach(array_keys($temp) as $key => $val) {
$value = 0;
foreach($intersecting_arrays as $val1) {
$value+= $val1[$val];
}
$result[$key] = $value / count($intersecting_arrays);
}
print_r($temp);
print_r($result);
https://3v4l.org/j8o75
In this manner it doesn't depend on how much arrays you have.
Here you get the intersection of keys in all arrays and then count an average using collected keys.
Ok, with an unknown number of input arrays, I would definitively go with two nested foreach loops to combine them first - getting an unknown number into array_merge_recursive or similar is going to be difficult.
$input = [
0 => [ 'one' => 10, 'two' => 20, 'three' => 50, 'four' => 80, 'five' => 100],
1 => [ 'three' => 20, 'five' => 20, 'six' => 100, 'seven' => 10],
2 => [ 'one' => 30, 'three' => 30, 'five' => 10, 'eight' => 10]
];
$combined = [];
foreach($input as $array) {
foreach($array as $key => $value) {
$combined[$key][] = $value;
}
}
$averages = array_map(function($item) {
return array_sum($item)/count($item);
}, $combined);
var_dump($averages);
https://3v4l.org/hmtj5
Note that this solution doesn't need to check for array vs single integer in the array_map callback, because unlike array_merge_recursive, $combined[$key][] inside the loops sees to it that even the keys with just one value will have that value in an array.
EDIT:
but keep in mind that not all the keys are going to be taken into account
Ah, ok, so you want averages only for those keys that occurred more than once. That can easily be fixed by filtering the combined array before using array_map on it:
$combined = array_filter($combined, function($v, $k) {
return count($v) != 1;
}, ARRAY_FILTER_USE_BOTH );
Integrated into above solution: https://3v4l.org/dn5ro
EDIT #2
[Andreas' comment] I think "one" should not be in output since it is not in all three arrays.
Ah, I see ... couldn't tell that was the actually desired result even from the example :-) Then my filtering has to be modified a little bit again, and take the number of input arrays into account:
$combined = array_filter($combined, function($v, $k) use($input) {
return count($v) == count($input);
}, ARRAY_FILTER_USE_BOTH );
https://3v4l.org/9H086
You can merge the arrays to one and use array_sum and count() to get the average.
$arr1 = Array ( 'one' => 10, 'two' => 20, 'three' => 50, 'four' => 80, 'five' => 100 );
$arr2 = Array ( 'three' => 20, 'five' => 20, 'six' => 100, 'seven' => 10 );
$arr3 = Array ( 'one' => 30, 'three' => 30, 'five' => 10, 'eight' => 10 );
$array = array_merge_recursive($arr1,$arr2,$arr3);
$key= "two";
If(is_array($array[$key])){
$avg = array_sum($array[$key])/count($array[$key]);
}Else{
$avg = $array[$key];
}
Echo $avg;
https://3v4l.org/pa3PH
Edit to follow $collection array.
Try this then. Use array column to grab the correct key and use array_sum and count to get the average.
$collection = array(
Array ( 'one' => 10, 'two' => 20, 'three' => 50, 'four' => 80, 'five' => 100 ),
Array ( 'three' => 20, 'five' => 20, 'six' => 100, 'seven' => 10 ),
Array ( 'one' => 30, 'three' => 30, 'five' => 10, 'eight' => 10 ));
$key= "three";
$array = array_column($collection, $key);
If(count($array) != 1){
$avg = array_sum($array)/count($array);
}Else{
$avg = $array[0];
}
Echo $avg;
https://3v4l.org/QPsiS
Final edit.
Here I loop through the first subarray and use array column to find all the matching keys.
If the count of keys is the same as the count of collection the key exsists in all subarrays and should be "saved".
$collection = array(
Array ( 'one' => 10, 'two' => 20, 'three' => 50, 'four' => 80, 'five' => 100 ),
Array ( 'three' => 20, 'five' => 20, 'six' => 100, 'seven' => 10 ),
Array ( 'one' => 30, 'three' => 30, 'five' => 10, 'eight' => 10 ));
Foreach($collection[0] as $key => $val){
$array = array_column($collection, $key);
If(count($array) == count($collection)){
$avg[$key] = array_sum($array)/count($array);
}
}
Var_dump($avg);
https://3v4l.org/LfktH

How to check if a single multi dimensional array contails same array as value in php? [duplicate]

I'd like to check if two arrays are equal. I mean: same size, same index, same values. How can I do that?
Using !== as suggested by a user, I expect that the following would print enter if at least one element in the array(s) are different, but in fact it does not.
if (($_POST['atlOriginal'] !== $oldAtlPosition)
or ($_POST['atl'] !== $aext)
or ($_POST['sidesOriginal'] !== $oldSidePosition)
or ($_POST['sidesOriginal'] !== $sideext)) {
echo "enter";
}
$arraysAreEqual = ($a == $b); // TRUE if $a and $b have the same key/value pairs.
$arraysAreEqual = ($a === $b); // TRUE if $a and $b have the same key/value pairs in the same order and of the same types.
See Array Operators.
EDIT
The inequality operator is != while the non-identity operator is !== to match the equality
operator == and the identity operator ===.
According to this page.
NOTE: The accepted answer works for associative arrays, but it will not work as expected with indexed arrays (explained below). If you want to compare either of them, then use this solution. Also, this function may not works with multidimensional arrays (due to the nature of array_diff function).
Testing two indexed arrays, which elements are in different order, using $a == $b or $a === $b fails, for example:
<?php
(array("x","y") == array("y","x")) === false;
?>
That is because the above means:
array(0 => "x", 1 => "y") vs. array(0 => "y", 1 => "x").
To solve that issue, use:
<?php
function array_equal($a, $b) {
return (
is_array($a)
&& is_array($b)
&& count($a) == count($b)
&& array_diff($a, $b) === array_diff($b, $a)
);
}
?>
Comparing array sizes was added (suggested by super_ton) as it may improve speed.
Try serialize. This will check nested subarrays as well.
$foo =serialize($array_foo);
$bar =serialize($array_bar);
if ($foo == $bar) echo "Foo and bar are equal";
Short solution that works even with arrays which keys are given in different order:
public static function arrays_are_equal($array1, $array2)
{
array_multisort($array1);
array_multisort($array2);
return ( serialize($array1) === serialize($array2) );
}
function compareIsEqualArray(array $array1,array $array2):bool
{
return (array_diff($array1,$array2)==[] && array_diff($array2,$array1)==[]);
}
Compare them as other values:
if($array_a == $array_b) {
//they are the same
}
You can read about all array operators here:
http://php.net/manual/en/language.operators.array.php
Note for example that === also checks that the types and order of the elements in the arrays are the same.
if (array_diff($a,$b) == array_diff($b,$a)) {
// Equals
}
if (array_diff($a,$b) != array_diff($b,$a)) {
// Not Equals
}
From my pov it's better to use array_diff than array_intersect because with checks of this nature the differences returned commonly are less than the similarities, this way the bool conversion is less memory hungry.
Edit Note that this solution is for plain arrays and complements the == and === one posted above that is only valid for dictionaries.
Another method for checking equality regardless of value order works by using http://php.net/manual/en/function.array-intersect.php, like so:
$array1 = array(2,5,3);
$array2 = array(5,2,3);
if($array1 === array_intersect($array1, $array2) && $array2 === array_intersect($array2, $array1)) {
echo 'Equal';
} else {
echo 'Not equal';
}
Here's a version that works also with multidimensional arrays using http://php.net/manual/en/function.array-uintersect.php:
$array1 = array(
array(5, 2),
array(3, 6),
array(2, 9, 4)
);
$array2 = array(
array(3, 6),
array(2, 9, 4),
array(5, 2)
);
if($array1 === array_uintersect($array1, $array2, 'compare') && $array2 === array_uintersect($array2, $array1, 'compare')) {
echo 'Equal';
} else {
echo 'Not equal';
}
function compare($v1, $v2) {
if ($v1===$v2) {
return 0;
}
if ($v1 > $v2) return 1;
return -1;
}
One way: (implementing 'considered equal' for https://www.rfc-editor.org/rfc/rfc6902#section-4.6)
This way allows associative arrays whose members are ordered differently - e.g. they'd be considered equal in every language but php :)
// recursive ksort
function rksort($a) {
if (!is_array($a)) {
return $a;
}
foreach (array_keys($a) as $key) {
$a[$key] = ksort($a[$key]);
}
// SORT_STRING seems required, as otherwise
// numeric indices (e.g. "0") aren't sorted.
ksort($a, SORT_STRING);
return $a;
}
// Per https://www.rfc-editor.org/rfc/rfc6902#section-4.6
function considered_equal($a1, $a2) {
return json_encode(rksort($a1)) === json_encode(rksort($a2));
}
Syntax problem on your arrays
$array1 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value3',
);
$array2 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value3',
);
$diff = array_diff($array1, $array2);
var_dump($diff);
Here is the example how to compare to arrays and get what is different between them.
$array1 = ['1' => 'XXX', 'second' => [
'a' => ['test' => '2'],
'b' => 'test'
], 'b' => ['no test']];
$array2 = [
'1' => 'XX',
'second' => [
'a' => ['test' => '5', 'z' => 5],
'b' => 'test'
],
'test'
];
function compareArrayValues($arrayOne, $arrayTwo, &$diff = [], $reversed = false)
{
foreach ($arrayOne as $key => $val) {
if (!isset($arrayTwo[$key])) {
$diff[$key] = 'MISSING IN ' . ($reversed ? 'FIRST' : 'SECOND');
} else if (is_array($val) && (json_encode($arrayOne[$key]) !== json_encode($arrayTwo[$key]))) {
compareArrayValues($arrayOne[$key], $arrayTwo[$key], $diff[$key], $reversed);
} else if ($arrayOne[$key] !== $arrayTwo[$key]) {
$diff[$key] = 'DIFFERENT';
}
}
}
$diff = [];
$diffSecond = [];
compareArrayValues($array1, $array2, $diff);
compareArrayValues($array2, $array1, $diffSecond, true);
print_r($diff);
print_r($diffSecond);
print_r(array_merge($diff, $diffSecond));
Result:
Array
(
[0] => DIFFERENT
[second] => Array
(
[a] => Array
(
[test] => DIFFERENT
[z] => MISSING IN FIRST
)
)
[b] => MISSING IN SECOND
[1] => DIFFERENT
[2] => MISSING IN FIRST
)
array_diff — Computes the difference of arrays
http://php.net/manual/en/function.array-diff.php
array array_diff ( array $array1 , array $array2 [, array $... ] )
Compares array1 against one or more other arrays and returns the values in array1 that are not present in any of the other arrays.
If you want to check non associative arrays, here is the solution:
$a = ['blog', 'company'];
$b = ['company', 'blog'];
(count(array_unique(array_merge($a, $b))) === count($a)) ? 'Equals' : 'Not Equals';
// Equals
The following solution works with custom equality functions that you can pass as a callback. Note that it doesn't check arrays order.
trait AssertTrait
{
/**
* Determine if two arrays have the same elements, possibly in different orders. Elements comparison function must be passed as argument.
*
* #param array<mixed> $expected
* #param array<mixed> $actual
*
* #throws InvalidArgumentException
*/
public static function assertArraysContainSameElements(array $expected, array $actual, callable $comparisonFunction): void
{
Assert::assertEquals(\count($expected), \count($actual));
self::assertEveryElementOfArrayIsInAnotherArrayTheSameAmountOfTimes($expected, $actual, $comparisonFunction);
self::assertEveryElementOfArrayIsInAnotherArrayTheSameAmountOfTimes($actual, $expected, $comparisonFunction);
}
/**
* #param array<mixed> $needles
* #param array<mixed> $haystack
*
* #throws InvalidArgumentException
*/
private static function assertEveryElementOfArrayIsInAnotherArrayTheSameAmountOfTimes(
array $needles,
array $haystack,
callable $comparisonFunction
): void {
Assert::assertLessThanOrEqual(\count($needles), \count($haystack));
foreach ($needles as $expectedElement) {
$matchesOfExpectedElementInExpected = \array_filter(
$needles,
static fn($element): bool => $comparisonFunction($expectedElement, $element),
);
$matchesOfExpectedElementInActual = \array_filter(
$haystack,
static fn($element): bool => $comparisonFunction($expectedElement, $element),
);
Assert::assertEquals(\count($matchesOfExpectedElementInExpected), \count($matchesOfExpectedElementInActual));
}
}
}
I usually use it in database integrations tests when I want to ensure that the expected elements are returned but I don't care about the sorting.
The proper way to compare whether two arrays are equal is to use strict equality (===), which compares recursively. Existing answers are unable to recursively sort an arbitrary array (array of arbitrary depth and order, containing a mixture of sequential and associative arrays) and hence cannot handle comparisons of arbitrary arrays. Sequential arrays are associative arrays with a sequential key (0,1,2,3...) whereas associative arrays do not have a sequential key.
To sort these arbitrary arrays, we have to:
Traverse downwards towards leaf nodes with no more sub-arrays
Sort sequential arrays by serializing then sorting them (to remove the need of having to use custom comparators)
Sort associative arrays by key
The following code implements the solution described above. Improvements to the code are welcome.
function recur_sort( &$array ) {
foreach ( $array as &$value ) {
if ( is_array( $value ) ) recur_sort( $value );
}
if ( is_sequential_array( $array ) ) {
$array = array_map( function( $el ) { return json_encode( $el ); }, $array );
sort( $array, SORT_STRING );
$array = array_map( function( $el ) { return json_decode( $el, true ); }, $array );
return;
} else {
return ksort( $array );
}
}
function is_sequential_array(Array &$a) {
$n = count($a);
for($i=0; $i<$n; $i++) {
if(!array_key_exists($i, $a)) {
return false;
}
}
return true;
}
Example (in PHPUnit):
//A stricter and recursive assertEqualsCanonicalizing
public function assertSameCanonicalizing( $expected, $actual ) {
recur_sort( $expected );
recur_sort( $actual );
$this->assertSame( $expected, $actual );
}
If you want to check that your arrays have the strictly equal (===) associations of keys and values, you can use the following function:
function array_eq($a, $b) {
// If the objects are not arrays or differ in their size, they cannot be equal
if (!is_array($a) || !is_array($b) || count($a) !== count($b)) {
return false;
}
// If the arrays of keys are not strictly equal (after sorting),
// the original arrays are not strictly equal either
$a_keys = array_keys($a);
$b_keys = array_keys($b);
array_multisort($a_keys);
array_multisort($b_keys);
if ($a_keys !== $b_keys) {
return false;
}
// Comparing values
foreach ($a_keys as $key) {
$a_value = $a[$key];
$b_value = $b[$key];
// Either the objects are strictly equal or they are arrays
// which are equal according to our definition. Otherwise they
// are different.
if ($a_value !== $b_value && !array_eq($a_value, $b_value)) {
return false;
}
}
return true;
}
To compare the values of your arrays, also multidimensional, associative and in any combination:
/**
* #see PHPUnit Assert::assertEqualsCanonicalizing()
* #return true if all keys and values are equal and of the same type,
* irregardless of items or keys order
*/
function array_vals_equal(array $a, array $b): bool {
// sort multi-dimensional recursive
$_deep_sort = function (array $a) use (&$_deep_sort): array{
// sort discarding index association or sort keys, depending on array type
array_is_list($a) ? sort($a) : ksort($a);
return array_map(fn($v) => is_array($v) ? $_deep_sort($v) : $v, $a);
};
// operator === checks that the count, types and order of the elements are the same
return $_deep_sort($a) === $_deep_sort($b);
}
// Test cases
assertEquals(array_vals_equal([1], [1]), true, 'simple eq');
assertEquals(array_vals_equal([0], [false]), false, 'simple eq');
assertEquals(array_vals_equal([0], [null]), false, 'simple eq');
assertEquals(array_vals_equal([0, 1], [1, 0]), true, 'simple eq, diff order');
assertEquals(array_vals_equal([0, 1, 2], [1, 0]), false, 'diff count');
assertEquals(array_vals_equal([0, 1], [0, 1, 2]), false, 'diff count 2');
assertEquals(array_vals_equal([1, 2], [1, 2, 'hello']), false, 'diff count 3');
//
assertEquals(array_vals_equal([1, 2, 2], [2, 1, 1]), false, 'same vals repeated');
assertEquals(array_vals_equal([1, 2, 2], [2, 2, 1]), true, 'same vals, different order');
//
assertEquals(array_vals_equal([1, 2, 3], ['1', '2', '3']), false, 'int should not be eq string');
assertEquals(array_vals_equal([0 => 'a', 1 => 'b'], [0 => 'b', 1 => 'a']), true, 'same vals, diff order');
assertEquals(array_vals_equal(['a', 'b'], [3 => 'b', 5 => 'a']), true, 'same vals, diff indexes');
// associative arrays whose members are ordered differently
assertEquals(array_vals_equal(['aa' => 'a', 'bb' => 'b'], ['bb' => 'b', 'aa' => 'a']), true, 'dict with different order');
assertEquals(array_vals_equal(['aa' => 'a', 'bb' => 'b'], ['aa' => 'a']), false, 'a key is missing');
assertEquals(array_vals_equal(['aa' => 'a', 'bb' => 'b'], ['aa' => 'a', 'zz' => 'b']), false, 'dict same vals diff key');
// nested arrays with keys in different order
assertEquals(array_vals_equal(
['aa' => 'a', 'bb' => ['bb' => 'b', 'aa' => 'a']],
['aa' => 'a', 'bb' => ['aa' => 'a', 'bb' => 'b']]
), true, 'dict multi 2 level, keys in different order');
assertEquals(array_vals_equal(
['aa' => 'a', 'bb' => ['aa2' => 'a', 'bb2' => ['aa3' => 'a', 'bb3' => 'b']]],
['aa' => 'a', 'bb' => ['aa2' => 'a', 'bb2' => ['aa3' => 'a', 'bb3' => 'b']]]
), true, 'dict multi 3 level');
assertEquals(array_vals_equal(
['aa' => 'a', 'bb' => [0, 1]],
['aa' => 'a', 'bb' => [1, 0]]
), true, 'dict multi level, 2^ level sequential in different order');
assertEquals(array_vals_equal([[0, 1], ['a', 'b']], [['b', 'a'], [1, 0]]), true, 'multi level sequential');
If you'd like to generate a detailed report, you could use something like this:
function deepCompare(Array $a, Array $b, string $parentAKey, string $parentBKey, bool $compareInverted = true, bool $compareValues = true, string $log = '')
{
foreach ($a as $aKey => $aValue) {
$fullAKey = implode('.', [$parentAKey, $aKey]);
$fullBKey = implode('.', [$parentBKey, $aKey]);
if (! isset($b[$aKey])) {
$log .= "⍰ {$fullAKey} has no equivalent {$fullBKey}\n";
} else {
$bValue = $b[$aKey];
if (is_array($aValue)) {
$log = deepCompare($aValue, $bValue, $fullAKey, $fullBKey, false, $compareValues, $log);
} else {
if ($compareValues) {
if ($aValue != $bValue) {
$log .= "≠ {$fullAKey} value differs from {$fullBKey}\n";
}
}
}
}
}
if ($compareInverted) {
$log = deepCompare($b, $a, $parentBKey, $parentAKey, false, false, $log);
}
return $log;
}
Here is an example for it:
$november = [
'site1' => [
'id' => 15,
'name' => 'Brazil',
'extendedHours' => 454,
],
'site2' => [
'id' => 43,
'name' => 'Portugal',
'extendedHours' => 448,
],
'site3' => [
'id' => 49,
'name' => 'Spain',
'extendedHours' => 0,
],
'totalExtendedHours' => 902,
];
$december = [
'site1' => [
'id' => 15,
'name' => 'Brazil',
'extendedHours' => 498,
],
'site2' => [
'id' => 43,
'name' => 'Portugal',
'extendedHours' => 409,
'extraRequests' => 6,
],
'totalExtendedHours' => 907,
'totalExtraRequests' => 6,
];
echo deepCompare(
$november, -- origin array
$december, -- target array
'Nov2022', -- descriptive name of origin array
'Dec2022', -- descriptive name of target array
true, -- should also compare arrays in reverse order?
true -- should care about array values? (false = names only)
);
This example will output:
≠ Nov2022.site1.extendedHours value differs from Dec2022.site1.extendedHours
≠ Nov2022.site2.extendedHours value differs from Dec2022.site2.extendedHours
⍰ Nov2022.site3 has no equivalent Dec2022.site3
≠ Nov2022.totalExtendedHours value differs from Dec2022.totalExtendedHours
⍰ Dec2022.site2.extraRequests has no equivalent Nov2022.site2.extraRequests
⍰ Dec2022.totalExtraRequests has no equivalent Nov2022.totalExtraRequests
I hope that helps someone.
Use php function array_diff(array1, array2);
It will return a the difference between arrays. If its empty then they're equal.
example:
$array1 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value3'
);
$array2 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value4'
);
$diff = array_diff(array1, array2);
var_dump($diff);
//it will print array = (0 => ['c'] => 'value4' )
Example 2:
$array1 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value3',
);
$array2 = array(
'a' => 'value1',
'b' => 'value2',
'c' => 'value3',
);
$diff = array_diff(array1, array2);
var_dump($diff);
//it will print empty;

Move items of an array into the start of a multidimensional array

I have an array that contains some other arrays, I will make a sample of the original structure below in order to understand my issue.
$foo = array(
[0] => array('name' => foo, 'offered' => 1)
[1] => array('name' => foo)
[2] => array('name' => foo, 'offered' => 1)
[3] => array('name' => foo, 'offered' => 1)
);
What i want to do is, to sort my array in order to get first every array that contains the key 'offered'. In the example above i need do get this type of order. [0],[2],[3],[1].
You could use uasort() with a custom comparison function.
$foo = array(
array('name' => 'mark', 'offered' => 1),
array('name' => 'joe'),
array('name' => 'bill', 'offered' => 1),
array('name' => 'hugo', 'offered' => 1)
);
uasort($foo, 'customSort');
function customSort($a, $b) {
$a = isset($a['offered']);
$b = isset($b['offered']);
if (($a && $b) || (!$a && !$b)) return 0;
else if ($a && !$b) return -1;
else return 1;
}
print_r($foo);
Please check:
http://php.net/manual/it/function.usort.php
http://php.net/manual/it/function.uasort.php
http://php.net/manual/it/function.uksort.php
Explanation: (a bit simplified for newcomers) When you sort an array with these special sorting functions, you can tell PHP to use a custom function to let it decide whether a value is "less than another" (and return 1) or "greater than" another (and return -1) or "equal to another" (and return 0).
This functions has as parameters ($a, $b) the two array items to compare.
In this case we decide that the ones where "offered" exists are "less than" the others, so they will be sorted first.
I would iterate and check if the key exists, depending on that, rebuild the array.
$final = [];
foreach ($foo as $r) {
if (isset($r['offered']))
array_unshift($final, $r);
else
array_push($final, $r);
}
var_dump($final);
I find the spaceship operator (php7+) to provide a very clean syntax.
Evaluate the offered column from both $a and $b to check if the column is set. If not the isset() false will be treated as 0, otherwise true will be treated as 1.
By writing $b on the left of the operator and $a on the right, DESC sorting is performed -- this positions all 1 (true) evaluations before 0 (false) evaluations.
Code: (Demo)
$array = [
['name' => 'a', 'offered' => 1],
['name' => 'b'],
['name' => 'c', 'offered' => 1],
['name' => 'd', 'offered' => 1],
['name' => 'e']
];
usort($array, function($a, $b) {
return isset($b['offered']) <=> isset($a['offered']);
});
var_export($array);
Output:
array (
0 =>
array (
'name' => 'a',
'offered' => 1,
),
1 =>
array (
'name' => 'c',
'offered' => 1,
),
2 =>
array (
'name' => 'd',
'offered' => 1,
),
3 =>
array (
'name' => 'b',
),
4 =>
array (
'name' => 'e',
),
)

Hard PHP nut : how to insert items into one associate array?

Yes, this is a bit of a trick question; one array (without copies), as opposed to any odd array. Let me explain, so let's start here ;
$a = array ( 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'five' => 5, 'six' => 6 ) ;
Pretend that this array is long, over a hundred long. I loop through it, step by step, but at some point (let's make up that this happens at the second item) something happens. Maybe the data is funky. Nevertheless, we need to add some items to it for later processing, and then keep looping through it, without losing the current position. Basically, I would like to do something like this ;
echo current ( $a ) ; // 'two'
array_insert ( $a, 'four', 'new_item', 100 ) ;
echo current ( $a ) ; // 'two'
Definition for array_insert is ( $array, $key_where_insert_happens, $new_key, $new_value ) ; Of course $new_key and $new_value should be wrapped in an array wrapper, but that's besides the point right now. Here's what I want to see happening after the above code having ran ;
print_r ( $a ) ; // array ( 'one' => 1, 'two' => 2, 'three' => 3, 'four' => 4, 'new_item' => 100, 'five' => 5, 'six' => 6 ) ;
echo current ( $a ) ; // 'two'
Whenever you use array_splice, array_slice, array_push or most of the other array fiddling functions, you basically create a copy of the array, and then you can copy it back, but this breaks the reference to the original array and the position as well, and my loop above breaks. I could use direct reference (ie. $a['new_item'] = 'whatever;) or put it at the end, but none of these will insert items into a given position.
Any takers? How can I do a true insert into an associative array directly (that's being processed elsewhere)? My only solution so far is to ;
record the position (current())
Do the splice/insert (array_slice)
overwrite old array with new ($old = $new)
search the new position (first reset() then looping through to find it [!!!!!!])
Surely there's a better, simpler and more elegant way for doing something that's currently kludgy, heavy and poorly hobbled together? Why isn't there a array_set_position ( $key ) function that quickly can help this out, or an array_insert that works directly on the same array (or both)?
Maybe I'm not understanding you correctly but have you looked into array_splice()?
This answer might also interest you.
Would something like this work?
function array_insert($input, $key, $value)
{
if (($key = array_search($key, array_keys($input))) !== false)
{
return array_splice($input, $key, 1, $value);
}
return $input;
}
This was the best I could come up with:
$a = array
(
'one' => 1,
'two' => 2,
'three' => 3,
'four' => 4,
'five' => 5,
'six' => 6,
);
ph()->Dump(next($a)); // 2
array_insert($a, 'four', array('new_item' => 100));
ph()->Dump(current($a)); // 2
function array_insert(&$array, $key, $data)
{
$k = key($array);
if (array_key_exists($key, $array) === true)
{
$key = array_search($key, array_keys($array)) + 1;
$array = array_slice($array, null, $key, true) + $data + array_slice($array, $key, null, true);
while ($k != key($array))
{
next($array);
}
}
}
ph()->Dump($a);
/*
Array
(
[one] => 1
[two] => 2
[three] => 3
[four] => 4
[new_item] => 100
[five] => 5
[six] => 6
)
*/
I don't think it's possible to set an array internal pointer without looping.
Rather than using the associative array as the event queue, keep a separate integer-indexed array. Loop over an explicit index variable rather than using the internal array position.
$events = array_keys($a);
for ($i=0; $i < count($events); ++$i) {
...
/* if there's an event to add after $j */
array_splice($events, $j+1, 0, array($new_event_key));
$a[$new_event_key] = $new_event_data;
...
}
To keep things more cohesive, you can package the two arrays into an event queue class.

Categories