Sorting array keeping even values on top PHP - php

I have tried sorting the below array to keep the even values on top and in sorted order from asc to desc
$array = array(1,2,3,4,5,6,7,8,9,10);
I tried this to sort the array
usort($array, function($a, $b) {
if ($a % 2 == 0 )
{
return 1 ;
}
else
{
return -1;
}
}
);
I got the output like below
Array
(
[0] => 7
[1] => 9
[2] => 1
[3] => 5
[4] => 3
[5] => 2
[6] => 4
[7] => 6
[8] => 8
[9] => 10
)
And I want the output array to be
Array
(
[0] => 2
[1] => 4
[2] => 6
[3] => 8
[4] => 10
[5] => 1
[6] => 3
[7] => 5
[8] => 7
[9] => 9
)
The even and odd values should be sorted in asc to desc order but keep the even values on top of odd values

usort is not stable. The documentation states:
If two members compare as equal, their relative order in the sorted array is undefined.
So, what you can do is:
usort($array, function($a, $b) {
if ($a % 2 == $b % 2) {
return intval($a - $b);
}
elseif ($a % 2 == 0 )
{
return -1 ;
}
else
{
return 1;
}
}
);
This compares the actual values if both are even or both are odd.

I think you need a somewhat more complex function. Because when you look at it there are different cases to take care of and in every one of them something different must happen:
$a and $b both are even: default numeric comparison
$a and $b both are odd: default numeric comparison
$a is even, $b is odd: $a is always smaller than $b
$a is odd, $b is even: $b is always smaller than $a
And for the implementation see the answer of fab.

A bit lenghty but works fine for me:
$array = array(1,2,3,4,5,6,7,8,9,10);
$even =array();
$odd = array();
foreach ($array as $item) {
if($item%2==0) {
$even[] = $item;
}
else {
$odd[] = $item;
}
}
usort($even);
usort($odd);
$array_sort = array_merge($even,$odd);
print_r($array_sort);

usort($array, function($a, $b) {
if ($a % 2 == $b % 2) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
} elseif ($a % 2 == 0) {
return -1 ;
} else {
return 1;
}
}
);

Short and simple way to write it:
<?php
$array = array(1,2,3,4,5,6,7,8,9,10);
usort($array, function($a, $b){
if ($a % 2 == $b % 2) {
return $a - $b;
}
return $a % 2 != 0;
});
print_r($array);
?>
or with a ternary:
<?php
$array = array(1,2,3,4,5,6,7,8,9,10);
usort($array, function($a, $b){
return ($a % 2 == $b % 2) ? ($a - $b) : ($a % 2 != 0);
});
print_r($array);
?>

For the comparison function, first take the difference of the mod 2 values of the two numbers.
It will be -1, 0, or 1. If it's not zero, then one number is even and the other is odd, so you can return that value. (That will sort odds above evens.)
If it is zero, then either both numbers are even or both numbers are odd, and you need to take the difference of the two numbers to break the tie.
A concise way to write this is:
usort($array, function($a, $b) {
return $a % 2 - $b % 2 ?: $a - $b;
});
To sort odd numbers at the beginning instead, swap $a and $b in the first comparison.
return $b % 2 - $a % 2 ?: $a - $b;

Related

PHP:: Double sort an array by priority

My goal is to sort an array first by the string length and after that sort it again by character value without changing the priority of the length.
Here is my code:
$arr=array("an","am","alien","i");
usort($arr, function ($a, $b) { return (strlen($a) <=> strlen($b)); });
print_r($arr);
I am getting :
Array ( [0] => i [1] => an [2] => am [3] => alien )
...almost but not there)
You're missing the logic to account for sorting by character value. You can add that logic into your custom sort method:
// if lengths are the same, then sort by value
if (strlen($a) == strlen($b)) {
return $a <=> $b;
}
// otherwise, sort by length
return (strlen($a) <=> strlen($b));
You can combine this into a single line by using a ternary:
return strlen($a) == strlen($b) ? $a <=> $b : strlen($a) <=> strlen($b);
Full example (https://3v4l.org/msISD):
<?php
$arr=array("aa", "az", "an","am", "ba", "by", "alien","i");
usort($arr, function ($a, $b) {
return strlen($a) == strlen($b) ? $a <=> $b : strlen($a) <=> strlen($b);
});
print_r($arr);
outputs:
Array
(
[0] => i
[1] => aa
[2] => am
[3] => an
[4] => az
[5] => ba
[6] => by
[7] => alien
)

PHP: How to move array item to the first place without changing its key if value is numeric?

Here is my array:
$arrayA = array(0 => "someString",
1 => "otherString",
2 => "2017",
3 => "anotherString",
4 => "2016");
My goal is to to find the first item that has a numeric value (which would be "2017") and place it first in the array, without changing its original key and keeping the others in the same order.
So I want:
$arrayA = array(2 => "2017",
0 => "someString",
1 => "otherString",
3 => "anotherString",
4 => "2016");
I tried uasort() php function and it seems the way to do it, but I could not figure out how to build the comparison function to go with it.
PHP documentation shows an example:
function cmp($a, $b) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
But, WHO is $a and WHO is $b?
Well, I tried
function my_sort($a,$b) {
if ($a == $b ) {
return 0;
}
if (is_numeric($a) && !is_numeric($b)) {
return -1;
break;
}
}
But, of course, I am very far from my goal. Any help would be much appreciated.
You don't need to sort per se. Once you find the element in question, you can simply push it onto the front of the array with the + operator:
foreach ($arrayA as $k => $v) {
if (is_numeric($v)) {
$arrayA = [$k => $v] + $arrayA;
break;
}
}
print_r($arrayA);
Yields:
Array
(
[2] => 2017
[0] => someString
[1] => otherString
[3] => anotherString
[4] => 2016
)

PHP array diff different types

I have 2 arrays of data from different data sources in different formats, but they represent the same resources. So id in one is the same as guid in the other for example.
Currently I am converting one of the arrays to match the other, and then running them via array_udiff to get the difference.
However, I need to compare 3 properties to check if they are a match, so I can't return -1,0,1 as the 3 fields either match, or do not match.
If I simply return -1 or 0, it works comparing $a to $b, but fails comparing $b to $a
$arr_a = [['id'=>1, 'a'=>1, 'b'=>0],['id'=>2, 'a'=>2, 'b'=>3],['id'=>3, 'a'=>1, 'b'=>0]];
$arr_b = [['id'=>3, 'a'=>1, 'b'=>0],['id'=>4, 'a'=>2, 'b'=>3],['id'=>5, 'a'=>1, 'b'=>0]];
function diff($a, $b) {
if( ($a['id'] == $b['id'])
&& ($a['a'] == $b['a'])
&& ($a['b'] == $b['b'])
) {
return 0;
} else {
return -1;
}
$not_in_b = array_udiff($arr_a, $arr_b,'diff');
$not_in_a = array_udiff($arr_b, $arr_a,'diff');
print_r($not_in_b);
print_r($not_in_a);
The above returns...
Array
(
[0] => Array
(
[id] => 1
[a] => 1
[b] => 0
)
[1] => Array
(
[id] => 2
[a] => 2
[b] => 3
)
)
Array
(
[0] => Array
(
[id] => 3
[a] => 1
[b] => 0
)
[1] => Array
(
[id] => 4
[a] => 2
[b] => 3
)
[2] => Array
(
[id] => 5
[a] => 1
[b] => 0
)
)
As you can see the diff of $a to $b works, but $b to $a does not...
How can I compare multiple vaules like this for equality...
UPDATE
This works, but making two arrays with the three identifying properties values as the keys...
$arr_a = [['id'=>1, 'a'=>1, 'b'=>0],['id'=>2, 'a'=>2, 'b'=>3],['id'=>3, 'a'=>1, 'b'=>0]];
$arr_b = [['id'=>3, 'a'=>1, 'b'=>0],['id'=>4, 'a'=>2, 'b'=>3],['id'=>5, 'a'=>1, 'b'=>0]];
$arra_a_keys=[];
foreach($arr_a as $item) {
$arra_a_keys[$item['id'].'_'.$item['a'].'_'.$item['b']] = $item;
}
$arra_b_keys=[];
foreach($arr_b as $item) {
$arra_b_keys[$item['id'].'_'.$item['a'].'_'.$item['b']] = $item;
}
$not_in_b = array_diff_key($arra_a_keys, $arra_b_keys);
$not_in_a = array_diff_key($arra_b_keys, $arra_a_keys);
print_r($not_in_b);
print_r($not_in_a);
To compare by ids only you can do the following:
$ids = array_column($a, 'id');
$guids = array_column($b, 'guid');
$not_in_b = array_filter($a, function ($item) use ($guids) {
return !in_array($item['id'], $guids);
});
$not_in_a = array_filter($b, function ($item) use ($ids) {
return !in_array($item['guid'], $ids);
});
Here is working demo.
Addition:
Also, you can do it with array_udiff:
$compareFunction = function ($a, $b) {
$id1 = isset($a['id']) ? $a['id'] : $a['guid'];
$id2 = isset($b['id']) ? $b['id'] : $b['guid'];
return strcmp($id1, $id2);
};
$not_in_b = array_udiff($a, $b, $compareFunction);
$not_in_a = array_udiff($b, $a, $compareFunction);
Here is working demo.
But be aware array_udiff is really not the most straight forward function. There is nothing about this in the documentation, but it not only compares but also sorts the arrays you provided with a callback function. That's why
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.
But this sorting also tricks the programmer, because he expected to that in function int callback ( mixed $a, mixed $b ) $a comes from $array1 and $b comes from $array2. That is not the case. You can read this article to find out more details. So I think that array_filter solution is more understandable
You could use a foreach for make array comparable
$ids1 = [];
foreach($a as $v1){
$i1[] = $v1['id'];
}
$ids2 = [];
foreach($b as $v2){
$i2[] = $v2['guid'];
}
$one_notin_two = array_diff($i1,$i2);
$two_notin_one = array_diff($i2,$i1);
You can solve this using array_column() and array_diff().
Here is sample code: See Live Demo
$a = [['id' => 1], ['id' => 2], ['id' => 3], ['id' => 4]];
$b = [['guid' => 3], ['guid' => 4], ['guid' => 6], ['guid' => 7]];
$a = array_column($a, 'id');
$b = array_column($b, 'guid');
$not_in_b = array_diff($a, $b);
$not_in_a = array_diff($b, $a);
Hope it should be solved your problem. Thank you.

How sort date in array form oldest to newest php

I have a problem.
I can't sort date in array from oldest to newest ;/
My array:
$arr = array('2013-02-01','2000-02-01','2016-02-17','0000-00-00','0000-00-00','0000-00-00');
i want output
array(
[0] => '2000-02-01',
[1] => '2013-02-01',
[2] => '2016-02-01',
[3] => '0000-00-00',
[4] => '0000-00-00',
[5] => '0000-00-00',
)
i use own function callback in usort, but this not work ;/
function sortDate($a, $b)
{
if ($a == $b) {
return 0;
} elseif($a == '0000-00-00') {
return 1;
}
return strtotime($a) < strtotime($b) ? 1 : -1;
}
someone has an idea for a solution?
The best sort is:
usort($childs, function ($a, $b) {
if ($a == '0000-00-00')
return 1;
if ($b == '0000-00-00')
return -1;
if ($a == $b)
return 0;
return ($a < $b) ? -1 : 1;
});
This will give the result you want tested in PHP versions 5.3.22 - 5.6.18, but there has been changes in PHP 7 that affect the usort function:
$arr = array('2013-02-01','2000-02-01','2016-02-17','0000-00-00','0000-00-00','0000-00-00');
sort( $arr );
usort( $arr, function( $a, $b )
{
if ( $a === $b ) return 0;
if ( strpos( $b, '0000' ) !== false ) return -1;
return ( $a < $b ) ? -1 : 1;
});
Output:
Array
(
[0] => 2000-02-01
[1] => 2013-02-01
[2] => 2016-02-17
[3] => 0000-00-00
[4] => 0000-00-00
[5] => 0000-00-00
)
Test:
https://3v4l.org/0Tvlm
First, I remove all the zero values from the array, then sort it as needed, and then add zero values back:
$arr = array('2013-02-01','2000-02-01','2016-02-17','0000-00-00','0000-00-00','0000-00-00');
$count = count($arr);
$arr = array_filter($arr, function($v) {
if($v == '0000-00-00') {
return false;
} else {
return true;
}
}, ARRAY_FILTER_USE_BOTH);
$count -= count($arr);
sort($arr);
$arr = array_merge($arr, array_fill(0, $count, '0000-00-00'));
print_r($arr);
This sorts your array as following:
Array
(
[0] => 2000-02-01
[1] => 2013-02-01
[2] => 2016-02-17
[3] => 0000-00-00
[4] => 0000-00-00
[5] => 0000-00-00
)
You have the date comparison backwards. You have:
return strtotime($a) < strtotime($b) ? 1 : -1;
You want:
return strtotime($a) < strtotime($b) ? -1 : 1;
You can use:
return $a < $b ? -1 : 1;

Sorting an array using PHP's usort method

Maybe what I want is 'too' custom and has to be done manually, I thought usort can do it but seems I don't understand it completely. Sorting an array of shows by date in descending order but if date is current year then put those in the beginning of the array:
usort($show, function($a, $b){
$year = (int) date("Y", time());
$a = $a['date'];
$b = $b['date'];
if ($a === $year) return -1;
if ($b === $year) return -1;
if ($a === $b) return 0;
return ($a > $b) ? -1 : 1;
});
If $a is current year and $b is not current year, put $a first.
If $a is not current year and $b is current year, put $b first.
Otherwise just do simple comparison/sorting for $a and $b:
$array = array(
1890,
1725,
2000,
2004,
2015,
2016,
2050,
2156,
2019,
);
usort($array, function ($a, $b) {
$y= date('Y');
if ($a == $y && $b != $y) {
return -1;
} elseif ($a != $y && $b == $y) {
return 1;
}
return $b - $a;
});
var_dump($array);
// output
Array
(
[0] => 2015
[1] => 2156
[2] => 2050
[3] => 2019
[4] => 2016
[5] => 2004
[6] => 2000
[7] => 1890
[8] => 1725
)
LIVE

Categories