I am wondering how the php spaceship operator compares strings, objects and arrays. For example, the below code.
echo "Its Me at SO" <=> "Its Me at SO";
will return 0, as i know all characters are same, count is same. But if i have a code like below:
echo "Its me at SO" <=> "its Me at so";
It will return 1, means that left side is greater than right side, but how? Is it comparing the ASCII values?
Now lets come to arrays. The below code will return 0, as both arrays are equal by count, values and values at each index.
echo [1,2,3] <=> [1,2,3];
But the below code returns -1
echo [1,2,3] <=> [3,2,1];
And i dont understand why? How this operator compares the arrays and how it calculates that the array on left is smaller than the array on right?
And the same goes for the objects.
Can anybody give a detailed answer that how it works with strings, arrays and objects?
Thank you
"Comparisons are performed according to PHP's usual type comparison rules (http://php.net/manual/en/types.comparisons.php)".
1) Yes, it uses the ASCII values
2) If the arrays are different lengths, the Array with fewer values is smaller.
Otherwise it compares the arrays key by key, giving "earlier" values priority. For example comparing $arr1[0] to $arr2[0] first. If $arr1 has a key that doesn't exist in $arr2, the arrays aren't comparable (eg if we're using non-numeric arrays).
// Arrays are compared like this with standard comparison operators
// $arr1 and $arr2 are arrays
function standard_array_compare($arr1, $arr2)
{
// If either array has more values, that array is considered "larger"
if (count($arr1) < count($arr2)) {
return -1; // $arr1 < $arr2
} elseif (count($arr1) > count($arr2)) {
return 1; // $arr1 > $arr2
}
//Otherwise compare the array values directly
foreach ($arr1 as $key => $val) {
if (!array_key_exists($key, $arr2)) {
return null; // uncomparable, these arrays do not have the same keys
} elseif ($val < $arr2[$key]) {
return -1; // $arr1 < $arr2
} elseif ($val > $arr2[$key]) {
return 1; // $arr1 > $arr2
}
}
return 0; // $arr1 == $arr2
}
Note, the above is not PHP's actual code, just an approximate representation of the logic used.
Essentially, then, it treats an array in a similar way to comparing a big-endian number. It compares $arr1[0] to $arr2[0]. If they are the different it returns -1 or 1 depending which is larger. If they are the same it moves on to $arr1[1] and $arr2[1]. If all values are the same it returns 0 (arrays are equal)
While not exactly the same, it might be simpler to consider [1,2,3] <=> [3,2,1] as basically equivalent to 123 <=> 321...
According to the new features documentation
Comparisons are performed according to PHP's usual type comparison rules.
<?php
// Integers
echo 1 <=> 1; // 0
echo 1 <=> 2; // -1
echo 2 <=> 1; // 1
// Floats
echo 1.5 <=> 1.5; // 0
echo 1.5 <=> 2.5; // -1
echo 2.5 <=> 1.5; // 1
// Strings
echo "a" <=> "a"; // 0
echo "a" <=> "b"; // -1
echo "b" <=> "a"; // 1
Spaceship Arrays php8
<?php
echo [] <=> []; // result is 0
echo [1, 2, 3] <=> [1, 2, 3]; // result is 0
echo [1, 2, 3] <=> []; // 1
echo [1, 2, 3] <=> [1, 2, 1]; // 1
echo [1, 2, 3] <=> [1, 2, 4]; // -1
echo [1,2,3,1] <=> [1,2,4];//1 //thats mean index first piority,1st array index number getter then 2nd array so its positive answer 1
echo [1,2,4] <=> [1,2,3,1]//-1
Related
Q1:
I think the ?? will do nothing when:
$a = [1, 2];
foreach ($a ?? [] as &$v) {
$v++;
}
var_dump($a);
But why?
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
Q2:
This is more strange:
foreach ($a = [1, 2] as &$v) {
$v++;
}
var_dump($a);
// output
array(2) {
[0]=>
int(1)
[1]=>
int(2)
}
My thinking:
I think the expressions are not referencable, but foreach catch the error or somehow and then make a copy.
References that work:
$a = 1;
$c = &$a;
Do not work:
$a = 1;
$c = &($a);
$c = &($a ?? []);
$c = &($a + 1);
Dos ?? make a copy? I just don't want to wrap the foreach with a if (isset($a)) if $a is null and foreach will fail.
TL;DR For your case, you could consider using the null coalesce operator in this manner:
$a = $a ?? [];
foreach ($a as &$v) { ... }
Or, don't use references at all, by either using array_map() or by using the keys to make modifications in the underlying array.
Q1
$a = [1, 2];
foreach ($a ?? [] as &$v) {
$v++;
}
var_dump($a);
The coalesce operator uses a copy of the original array, and then applies the right hand operand if null. Therefore, the iteration happens over a copy of the original array.
You could compare this to the following:
$a = [1, 2];
$x = $a ?? [];
$x[1] = 4;
var_dump($a); // [1, 2]
Code Insight
compiled vars: !0 = $a, !1 = $v
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
8 0 E > ASSIGN !0, <array>
9 1 COALESCE ~3 !0
2 QM_ASSIGN ~3 <array>
3 > FE_RESET_RW $4 ~3, ->8
... rest of looping code
The first operand of FE_RESET_RW is the hash variable that will be iterated over, and you can see that it's ~3 instead of !0 ($a in your code), which is what you expected to happen.
Q2
foreach ($a = [1, 2] as &$v) {
$v++;
}
What happens here is that the return value of the assignment $a = [1, 2] gets used as the array to iterate over.
You can compare this behaviour to something like this:
$x = $a = [1, 2];
$x[0] = 4; // modify in-place
var_dump($a); // [1, 2]
Code Insight
compiled vars: !0 = $a, !1 = $v
line #* E I O op fetch ext return operands
-------------------------------------------------------------------------------------
3 0 E > ASSIGN $2 !0, <array>
1 > FE_RESET_RW $3 $2, ->6
... rest of looping code
Again, $2 is the first operand of FE_RESET_RW, which is the assignment result, and so iteration will not happen against !0 ($a in your code).
You can use the expanded array syntax to get the index, and then use that to dereference the original array value:
$a = [1, 2];
foreach ($a ?? [] as $i => $v) {
++$a[$i];
}
var_dump($a);
But note this is likely useless anyway, because if $a isn't set (so that the ?? qualifies) then the loop will make zero iterations and $a will still be unset for the var_dump(). (Unless that's what you need, I suppose...)
I just dont want to wrap the foreach with a if (isset($a)) if $a is null and foreach will fail.
This is unavoidable if you haven't initialized variables to the correct type, but here's some trickery in a utility function using pass by reference, return by reference and a default value:
function &test(&$array=[]) {
return $array;
}
$a = [1, 2];
foreach (test($a) as &$v) {
$v++;
}
Doesn't generate errors if $a is not set and doesn't loop, however in the above it yields:
array(2) {
[0]=>
int(2)
[1]=>
&int(3)
}
In PHP arrays are assigned by value (assignment copies), so that if $a !== null then $a ?? [] returns the value of $a or [1, 2]. So $a is not modified by the reference to the values of this value using &$v.
Objects are assigned by reference so a reference is returned in this case and the original object is modified, unless $a is not set. Then you will obviously get:
Notice: Undefined variable: a
$a = (object)[1, 2];
foreach ($a ?? [] as &$v) {
$v++;
}
var_dump($a);
This yields:
object(stdClass)#1 (2) {
["0"]=>
int(2)
["1"]=>
&int(3)
}
From Assignment by Reference:
Assignment by reference is also supported, using the "$var = &$othervar;" syntax. Assignment by reference means that both variables end up pointing at the same data, and nothing is copied anywhere.
In these cases, they are all expressions and cannot be referenced:
$c = &($a);
$c = &($a ?? []);
$c = &($a + 1);
If this is just an exercise that's fine, but if you are trying to solve a specific problem then you need to outline that broader problem.
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).
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;
});
Sorry for posting this totally confused n00b question, but I do not get around the arcane usort() explanation in the manual.
<?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";
}
?>
When i echo $a in the cmp function I get a 5156120 as key for the first item, when I echo $b I get 2535630.
That is basically my whole question: What is $a and $b in the callback function?
But if you want to really help, please give a "line-by-line" explanation about what is happening.
HAPPY NYE!!!
(I tried to add the tags "noob" and "confused", need 1400 reputation more though. ; )
$a and $b are the two values being compared in the custom comparison function.
If you have array( 3, 2, 5, 6, 1) that you're sorting, you'll find cmp() compares 3 to 2, 2 to 5, 5 to 6, etc. until the values are properly sorted.
So, for example:
<?php
function cmp($a, $b)
{
echo "$a :compared with: $b <br/>";
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
$arr = array(3, 2, 5, 6, 1);
usort($arr, "cmp");
outputs:
5 :compared with: 2
5 :compared with: 3
5 :compared with: 6
1 :compared with: 5
2 :compared with: 1
3 :compared with: 2
I see usort() usually used to make much more intricate comparisons, where you need to break apart the value and compare just a piece of it, or assign custom priorities (e.g. sort by title President, Vice President, Secretary, etc., by priority and not by alphanumeric value)
Example:
<?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";
}
?>
Result
0: 1
1: 2
2: 3
3: 5
4: 6
in the user defined compare function 'cmd', it has two arguments, $a & $b.
What do the first and second argument represent? Are they array[x] and array[x+1] ?
They are just two of the elements from the array being sorted, not necessarily neighbours. usort performs some sort algorithm (see http://en.wikipedia.org/wiki/Sort_algorithm#Comparison_of_algorithms), which involves comparing various pairs of elements to determine their order. It uses your callback function to determine this.