Sort multidimensional array with floats - php

I have a multidimensional array with locations data (e.g. address, phone, name,..) and their relative distance from a certain point as floats (e.g. 0.49012608405149 or 0.72952439473047 or 1.4652101344361 or 13.476735354172).
Now I need to sort this array so that it starts with the data set of closest distance (0.49012608405149) and ends with the farthest (13.476735354172).
The function I use so far does a good job, but messes up some times, which is of course as it uses strcmp
function cmp($a, $b) {
return strcmp($a["distance"], $b["distance"]);
}
usort($resultPartner, "cmp");
I googled a lot but couldn't find anything for my case. If possible I would like to avoid a foreach statement, as I read it can have a poor performance with big arrays.
Do you have any idea/experience with that and can give me a working function for this? Thank you!

strcmp() is binary safe string comparison why you don't just compare floats?
When comparing floats php manual says
Returning non-integer values from the comparison function, such as
float, will result in an internal cast to integer of the callback's
return value. So values such as 0.99 and 0.1 will both be cast to an
integer value of 0, which will compare such values as equal.
So you must be careful.
Look at this: http://www.cygnus-software.com/papers/comparingfloats/comparingfloats.htm
Since floating point calculations involve a bit of uncertainty we can
try to allow for this by seeing if two numbers are ‘close’ to each
other.
Try something like this:
function cmpfloat($a, $b) {
if (abs($a["distance"]-$b["distance"]) < 0.00000001) {
return 0; // almost equal
} else if (($a["distance"]-$b["distance"]) < 0) {
return -1;
} else {
return 1;
}
}
Following function is good if comparing integer values:
function cmp($a, $b) {
return $a["distance"] < $b["distance"] ? -1 : ($a["distance"] === $b["distance"] ? 0 : 1);
}
if a distance is smaller than b distance return -1
if a distance equals b distance return 0
if a distance is greater than b distance return 1
Reason:
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.

Maybe in such way:
$data = array(
array('dist' => 0.72952439473047),
array('dist' => 0.49012608405149),
array('dist' => 0.95452439473047),
array('dist' => 0.12952439473047),
);
foreach ($data as $k => $v) {
$dist[$k] = $v['dist'];
}
array_multisort($dist, SORT_ASC, $data);

Related

What is the reason of returning -1 instead of lets say 0 at the end of this function's code?

I am talking about the second "return -1;" on the 12th line of the code. This gets reached only if two sets of numbers are exactly the same, like when comparing '192.167.11' to '192.167.11'. I will also add that using range(0,2) would be a better option for this piece of code (range(0,3) produces errors if two elements happen to be the same; I did not change that as this is the original code example from PHP Array Exercise #21 from w3resource.com).
<?php
function sort_subnets($x, $y){
$x_arr = explode('.', $x);
$y_arr = explode('.', $y);
foreach (range(0, 3) as $i) {
if ($x_arr[$i] < $y_arr[$i]) {
return -1;
} elseif ($x_arr[$i] > $y_arr[$i]) {
return 1;
}
}
return -1;
}
$subnet_list =
array('192.169.12',
'192.167.11',
'192.169.14',
'192.168.13',
'192.167.12',
'122.169.15',
'192.167.16'
);
usort($subnet_list, 'sort_subnets');
print_r($subnet_list);
?>
Returning "-1" would move the second element (the same as the first in the current $x and $y pair) towards the higher index of the array (down the array). Why not return "0" and keep everything as is if the two elements are exactly the same? Is there any reason for returning the "-1" maybe based on how the usort() works (or any other factor of this)?
Thanks.
EDIT:
I think that this is Insertion Sort (array size 6-15 elements; normally it would be Quicksort).
If the two elements are the same, there's no difference between swapping the order and keeping the order the same. So it doesn't make a difference what it returns in that case.
You're right that 0 is more appropriate. This would be more important if usort were "stable". But the documentation says
Note:
If two members compare as equal, their relative order in the sorted array is undefined.
To illustrate the excellent point of #Don'tPanic:
<?php
function sort_subnets($x, $y){
$x_arr = explode('.', $x);
$y_arr = explode('.', $y);
return $x_arr <=> $y_arr;
}
$subnet_list =
array('192.169.12',
'192.167.11',
'192.169.14',
'192.168.13',
'192.167.12',
'122.169.15',
'192.167.16'
);
usort($subnet_list, 'sort_subnets');
print_r($subnet_list);
See live code
Note the use of the "spaceship" operator, namely <=> which offers a conciseness that spares one from having to write code like the following in a function:
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
Lastly, note that the user-defined callback for usort() makes use of ternary logic because sometimes as in the case of sorting bivalent logic is insufficient. Yet, usort() itself utilizes two-part logic, returning TRUE on success and FALSE on failure.

sort array by length and then alphabetically

I'm trying to make a way to sort words first by length, then alphabetically.
// from
$array = ["dog", "cat", "mouse", "elephant", "apple"];
// to
$array = ["cat", "dog", "apple", "mouse", "elephant"];
I've seen this answer, but it's in Java, and this answer, but it only deals with the sorting by length. I've tried sorting by length, using the code provided in the answer, and then sorting alphabetically, but then it sorts only alphabetically.
How can I sort it first by length, and then alphabetically?
You can put both of the conditions into a usort comparison function.
usort($array, function($a, $b) {
return strlen($a) - strlen($b) ?: strcmp($a, $b);
});
The general strategy for sorting by multiple conditions is to write comparison expressions for each of the conditions that returns the appropriate return type of the comparison function (an integer, positive, negative, or zero depending on the result of the comparison), and evaluate them in order of your desired sort order, e.g. first length, then alphabetical.
If an expression evaluates to zero, then the two items are equal in terms of that comparison, and the next expression should be evaluated. If not, then the value of that expression can be returned as the value of the comparison function.
The other answer here appears to be implying that this comparison function does not return an integer greater than, less than, or equal to zero. It does.
Note: I didn`t post my answer early,because #Don't Panic faster then me. However,I want to add some explanation to his answer ( hope, it will be useful for more understanding).
usort($array, function($a, $b) {
return strlen($a) - strlen($b) ?: strcmp($a, $b);
});
Ok. Function usort waits from custom comparison function next (from docs):
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.
Ok, rewrite #Don't Panic code to this view (accoding the condition above):
usort($array, function($a, $b) {
// SORT_ORDER_CONDITION_#1
// equals -> going to next by order sort-condition
// in our case "sorting alphabetically"
if (strlen($a) == strlen($b)){
// SORT_ORDER_CONDITION_#2
if (strcmp($a,$b)==0) // equals - last sort-condition? Return 0 ( in our case - yes)
return 0; //
return (strcmp($a,$b)) ? -1 : 1;
}else{
return (strlen($a) < strlen ($b) ) ? - 1 : 1;
}
});
"Common sort strategy" (abstract) with multi sort-conditions in order like (CON_1,CON_2... CON_N) :
usort($array, function(ITEM_1, ITEM_2) {
// SORT_ORDER_CONDITION_#1
if (COMPARING_1_EQUALS){
// SORT_ORDER_CONDITION_#2
if (COMPARING_2_EQUALS){ // If last con - return 0, else - going "deeper" ( to next in order)
//...
// SORT_ORDER_CONDITION_#N
if (COMPARING_N_EQUALS) // last -> equals -> return 0;
return 0;
return ( COMPARING_N_NOT_EQUALS) ? -1 : 1;
//...
}
return ( COMPARING_2_NOT_EQUALS) ? -1 : 1;
}else{
return ( COMPARING_1_NOT_EQUALS ) ? - 1 : 1;
}
});
In practise (from my exp), it's sorting unordered multidimensional-array by several conditions. You can use usort like above.
This is not as short as other methods, but I would argue that it's clearer, and can be easily extended to cover other use cases:
$f = function ($s1, $s2) {
$n = strlen($s1) <=> strlen($s2);
if ($n != 0) {
return $n;
}
return $s1 <=> $s2;
};
usort($array, $f);

Combining levenshtein with in_array in PHP?

I want to verify if a levenshtein of factor <= 2 is present in an array. So:
in_array("test", $some_array);
to something like "check if in array, can have errors if levenshtein factor <= 2, by comparison"
levenshtein("test", $element_of_array_by_'in_array'_function);
Is this possible or do I have to iterate the array?
This should work for you:
You are looking for array_reduce(). With this you can reduce your array to one single return value.
You start with FALSE as return value. Then you loop through each array element and check if the return value of levenshtein() is smaller or equals 2.
If not then your return value of array_reduce() won't change and is still FALSE. If it is smaller or equals 2 you change the value to TRUE and array_reduce() will return TRUE.
array_reduce($some_array, function($keep, $v){
if(levenshtein($v, "test") <= 2)
return $keep = TRUE;
return $keep;
}, FALSE);

php array_multisort not working

I'm having this issue where I want to sorty a multidimensional array based on 2 parameters
I build my array like this:
$teamList[$t['id']] = array(
'id' => $t['id'],
'name' => $t['name'],
'score' => $score,
'points' => $array
);
I then sort like this:
foreach ($teamList as $key => $row) {
$score[$key] = $row['score'];
$points[$key] = $row['points'];
}
array_multisort($score, SORT_DESC, $points, SORT_DESC, $teamList);
But the $teamList remains unsorted?
You can easily use a user defined compare function instead of doing all the copying of values and abusing array_multisort().
function sortByScoreAndPoints($a, $b) {
if ($a['score'] == $b['score']) {
if ($a['points'] == $b['points']) {
return 0;
}
return ($a['points'] > $b['points']) ? -1 : 1;
}
return ($a['score'] > $b['score']) ? -1 : 1;
}
uasort($teamlist, 'sortByScoreAndPoints');
The sort function has to accept two parameters which can have arbitrary names, but $a and $b is used in the docs. During sorting, PHP passes any two values of the array as $a and $b and expects an answer in which order they should appear. Your sort function has to return -1 if $a should appear first, 0 if they are equal, or 1 if $a should appear last, compared to $b.
My code first tests if the scores are equal. If not, the last return will compare which score is higher ( $a > $b ), and the highes score goes into the list first (if a is bigger than b, return -1 to say a goes first).
If the scores are equal, points will be tested. If they are not equal, the comparison takes place again. Otherwise 0 is returned.
Any entry in the team list with equal score and points might appear in arbitrary location in the result (but not random - the same input array will always be sorted the same), because there is no further ordering specified. You might easily extend your sorting by adding another comparison for the name or the id, if you like.
If you want your sorted array to be renumbered starting at 0, use usort() instead of uasort().

A function to get the smaller number

I have 2 variables each containing a number (integer). I would like to sort them to have the lowest number first and the second largest. For example:
$sortedVar = getSmaller(45, 62); // Will return 45
$sortedVar = getSmaller(87, 23); // Will return 23
Do you see what I want to do? Can you help me please?
Thanks :)
http://php.net/manual/en/function.min.php
min — Find lowest value..
If the first and only parameter is an array, min() returns the lowest value in that array. If at least two parameters are provided, min() returns the smallest of these values.
Note:
Values of different types will be compared using the standard comparison rules. For instance, a non-numeric string will be compared to an integer as though it were 0, but multiple non-numeric string values will be compared alphanumerically. The actual value returned will be of the original type with no conversion applied.
Caution
Be careful when passing arguments with mixed types values because min() can produce unpredictable results...
Use min() which supports any number of arguments as well as arrays.
$smallest = min(1,2); //returns 1
$smallest = min(4,3,2); //returns 2
$smallest = min(array(5,4)) //returns 4
function getSmaller($a, $b) {
return $a < $b ? $a : $b;
}
In plain english, if $a is smaller than $b, then return $a, else return $b.
Or as others pointed out, there's also a function for that, called min().
$sortedVar = $a < $b ? $a : $b;

Categories