Name of natural comparison function in php? - php

I want to do the intersection between two multidimensional arrays in php 5. E.g:
$a = array(array(), array(1), array(2));
$b = array(array(), array(0), array(2));
I naturally try:
array_intersect($a, $b)
But it gives me Notice because of array to string conversion. As a workaround, I want to precise the comparison function using uintersect:
array_uintersect($a, $b, function($x, $y){return $x == $y ? 0 : ( $x < $y ? -1 : 1 );})
And it works. But this is kind of ugly. My question is simple, is there a built-in natural comparison function which works on array and which I could use as callback function like in:
array_uintersect($a, $b, 'natcmp')
Thank you!

Related

Compare two arrays of objects by specific object key value

This is probably really simple, but I cannot seem to get it. I have two arrays of objects $a and $b. In $a I have objects with key email and in $b I have objects with user_email (this cannot be changed as it comes from an API). I want as output a third array $c that has all the objects where email == user_email. I've tried using array_udiff like this:
$c = array_udiff($a, $b,
function ($obj_a, $obj_b) {
return $obj_a->email - $obj_b->user_email;
}
);
For some reason, $obj_b is not always an object from array $b as I would have thought. Is there any clean solution for this? Thank you.
You are probably looking for array_uintersect. Also, you should compare your strings with strcmp or even better with strcasecmp. Remember that the order in which PHP will pass array elements to the callback is not always the same as the order of arrays.
$a = [(object)['email' => 'a'], (object)['email' => 'b'], (object)['email' => 'c']];
$b = [(object)['user_email' => 'c'], (object)['user_email' => 'a'], (object)['user_email' => 'd']];
$comparer = function($obj_a, $obj_b) {
$email_a = property_exists($obj_a, 'email')
? $obj_a->email
: $obj_a->user_email;
$email_b = property_exists($obj_b, 'email')
? $obj_b->email
: $obj_b->user_email;
return strcasecmp($email_a, $email_b);
};
// only objects with email property
$c = array_uintersect($a, $b, $comparer);
// both objects with email and user_email property
$d = array_merge(
array_uintersect($a, $b, $comparer),
array_uintersect($b, $a, $comparer)
);
Testing with property_exists can be changed to testing with instanceof if the arguments are concrete classes.

How to view the steps of the usort() in PHP?

This is the Example #1 from the php.net usort() page:
<?php
function cmp($a, $b) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
$a = array(3, 2, 5, 6, 1);
usort($a, "cmp");
foreach ($a as $key => $value) {
echo "$key: $value\n";
}
?>
The usort function takes the values within the array as pairs ($a-$b; so this is - 3-2, 2-5, 5-6, 6-1) and moves the $b value depending on whether the cmp() function returns -1, 0 or 1. If it is -1 the $b gets moved down (within a current pair), if it is 0 it stays in the same place and if it is 1 it gets moved up. This is how this is suppose to be working based on the top comment from the php.net manual usort() page.
Is there any way to see how this works step by step (the sorting process)? Am I able to see it or is it only possible to see the final result after the sorting is done? I want to understand fully how this process works.
Using some debug output in your comparison function you can only see the comparisons that PHP does, but can't see intermediate states of the array.
But the algorithm used in usort is well known - it's QuickSort ( Which sort algorithms does PHP's usort apply? ).
You can see its vizualisation at http://www.algomation.com/algorithm/quick-sort-visualization (or just google "quicksort algorithm visualization")

PHP array multisort (version 5/7 differences)

I got a array like this:
$json = '{"Categorie1":[{"created":"2017-07-17 08:53:00","catid":"54"},{"created":"2017-05-23 10:15:00","catid":"54"},{"created":"2017-05-09 05:49:23","catid":"54"}],"Categorie2":[{"created":"2017-03-21 08:58:37","catid":"59"},{"created":"2016-12-23 12:48:00","catid":"59"},{"created":"2016-12-08 09:57:10","catid":"59"}],"Categorie3":[],"Categorie4":[{"created":"2017-08-02 07:15:07","catid":"70"},{"created":"2017-08-01 08:03:00","catid":"70"},{"created":"2017-07-31 09:25:00","catid":"70"}],"Categorie5":[{"created":"2017-07-26 14:09:00","catid":"74"},{"created":"2017-06-29 14:03:00","catid":"74"},{"created":"2017-06-28 06:35:35","catid":"74"}]}';
And I wrote a sorting function. Basically it checks which block (categorie) got the newest entry and brings that block to top, sort the blocks):
$array = json_decode($json, true);
function custom($a, $b) {
foreach($a as $k => $v) {
if (isset($b[$k])) {
return (strtotime($a[$k]["created"]) <= strtotime($b[$k]["created"]));
}
}
}
uasort($array, "custom");
When I print this with PHP 5 its perfect: Categorie4 is the first block". But with PHP 7 it doesn't.
print("<pre>");
print_r($array); // PHP 5 is as expected, php 7 is not
I know there where changes, but I can't figure out how to change my code.
Can you guys helping me change the code? The result should show categorie4 as first cat...
The callback function used by the user-defined array sorting functions must return an integer value that is <0 if $a < $b, 0 when $a == $b or >0 when $a > $b. Yours return a boolean that is converted to 1 or 0 and that doesn't reflect the correct order of $a and $b.
It's not clear from the question how should be sorted the empty entries (Categorie3) and I think their place is at the end.
Try this code for PHP 5:
uasort($array, function (array $a, array $b) {
if (empty($a)) { return +1; } // empty arrays go to the end of the list
if (empty($b)) { return -1; }
return strcmp($a[0]['created'], $b[0]['created']);
});
The date&time values of created use a format that can be sorted directly as strings, there is no need to convert them to timestamps.
In PHP 7 you can use the new <=> comparison operator that, in theory, should run faster than the strcmp() function while it produces the same result.
uasort($array, function (array $a, array $b) {
if (empty($a)) { return +1; } // empty arrays go to the end of the list
if (empty($b)) { return -1; }
return $b[0]['created'] <=> $a[0]['created'];
});

Spaceship operator confusion in PHP7 (return -1)

I am pretty new to PHP7 and so far it seems great and powerful. I have been using PHP5.6 so I started understanding the usage of spaceship operator <=>. But somehow I couldn't get the logic that statement returns -1. I know the point of returning to 0 or 1 which are false or true. Can anyone clarify the usage of return -1?
Function normal_sort($a, $b) : int
{
if( $a == $b )
return 0;
if( $a < $b )
return -1;
return 1;
}
function space_sort($a, $b) : int
{
return $a <=> $b;
}
$normalArray = [1,34,56,67,98,45];
//Sort the array in asc
usort($normalArray, 'normal_sort');
foreach($normalArray as $k => $v)
{
echo $k.' => '.$v.'<br>';
}
$spaceArray = [1,34,56,67,98,45];
//Sort it by spaceship operator
usort($spaceArray, 'space_sort');
foreach($spaceArray as $key => $value)
{
echo $key.' => '.$value.'<br>';
}
You have three possibilities when comparing the two values that are passed to a comparison function: $a < $b, $a == $b, or $a > $b. So you need three distinct return values and PHP has chosen the integers: -1, 0, and 1. I guess it could just as easily be strings lesser, equal and greater or integers 5, 7 and 9 or any combination, but it's not.
From the manual usort()
The comparison function must return an integer less than, equal to, or greater than zero if the first argument is considered to be respectively less than, equal to, or greater than the second.
$a < $b return -1
$a == $b return 0
$a > $b return 1
This is NOT how types work in PHP, but you can think of it like this: is $a > $b? where -1 means false, 1 means true and 0 means neither (equal).

How does <=> in php 7 responsed in situations that check bool values?

Looking into php 7 on the way, but <=> confuses me.
Most of the time I use conditional operators, they are used in boolean situations (which <=> almost is, but not quite, being able to return -1 as well). (If X <=> Y). So I'm not sure what will happen in the following cases...
if ($x <=> $y) {
// Do all the 1 things
} else {
// Do all the 2 things
}
What can I expect if it's preceded by...
$x = 0; $y = 1;
or
$x = "Carrot"; $y = "Carrot Juice";
or
$x = "Carrot Juice"; $y = "Carrot";
or
$x = array(carrot, juice); $y = "carrot juice";
There's definitely enough cases about this that it's confusing me as to what it'll do.
The spaceship operator (and other PHP 7 additions) is explained in plain language here:
https://blog.engineyard.com/2015/what-to-expect-php-7
It's mostly useful in the comparison function supplied to functions like usort.
// Pre PHP 7
function order_func($a, $b) {
return ($a < $b) ? -1 : (($a > $b) ? 1 : 0);
}
// Post PHP 7
function order_func($a, $b) {
return $a <=> $b;
}
It's not very useful in if, because if only checks whether the value is truthy or falsey, the different truthy values representing the ordering are not distinguished. If you do use it in a boolean context, it will be considered true when the values are different (because 1 and -1 are bother truthy), false when they're equal (because 0 is falsey). This is similar to trying to use strcmp() and stricmp() in a boolean context, which is why you often see
if (stricmp($x, $y) == 0)
The rules for using arrays with comparison operators is given here (scroll down to the table labeled Comparison with Various Types). When comparing an array with another array, the rule is:
Array with fewer members is smaller, if key from operand 1 is not found in operand 2 then arrays are uncomparable, otherwise - compare value by value
When comparing an array with another type, the array is always greater. So array('carrot', 'juice') <=> 'carrot juice' will be 1.
Why not just try it out for yourself and play around with that new spaceship you got?
Demo
Also if you are wondering how the comparison of the spaceship operator works, see: http://php.net/manual/en/types.comparisons.php
But now if we want to go into a bit more detail about your test data:
First case:
//Test data
$x = 0;
$y = 1;
//operator
0 <=> 1 //0 is smaller than 1, so result: -1
//-1 evaluates to TRUE in the if statement
Second case:
//Test data
$x = "Carrot";
$y = "Carrot Juice";
//operator
"Carrot" <=> "Carrot Juice" //"Carrot" is smaller than "Carrot Juice", so result: -1
//-1 evaluates to TRUE in the if statement
Third case:
//Test data
$x = "Carrot Juice";
$y = "Carrot";
//operator
"Carrot Juice" <=> "Carrot" //"Carrot Juice" is bigger than "Carrot", so result: 1
//1 evaluates to TRUE in the if statement
Fourth case:
//Test data
$x = array("carrot", "juice");
$y = "carrot juice";
//operator
array("carrot", "juice") <=> "carrot juice" //array("carrot", "juice") is bigger than "carrot juice", so result: 1
//1 evaluates to TRUE in the if statement
Introduction
The spaceship operator <=> is a non-associative binary operator with the same precedence as equality operators (==, !=, ===, !==).
The purpost of this operator, is to allow for simpler three-way comparison between left-hand and right-hand operands.
Possible outcomes
The operator can produce any of the following results :
0 : when both operands are equal
-1 : when the left-hand operand is less than the right-hand operand
1 : when the left-hand operand is greater than the right-hand operand.
So, that means :
1 <=> 1; // output : 0
1 <=> 2; // output : -1
2 <=> 1; // output : 1
Practical application
A good practical application of using this operator would be in comparison type callbacks that are expected to return a zero, negative, or positive integer based on a three-way comparison between two values. The comparison function passed to usort is one such example.
Before PHP 7, you would write this :
$arr = [4,2,1,3];
usort($arr, function ($a, $b) {
if ($a < $b) {
return -1;
} elseif ($a > $b) {
return 1;
} else {
return 0;
}
});
Since PHP 7, you can write this :
$arr = [4,2,1,3];
usort($arr, function ($a, $b) {
return $a <=> $b;
});

Categories