The documentation for min() shows the following example:
// Multiple arrays of the same length are compared from left to right
// so in our example: 2 == 2, but 4 < 5
$val = min(array(2, 4, 8), array(2, 5, 1)); // array(2, 4, 8)
Given the following code:
$input = [
[3, 6],
[2, 9],
];
var_dump(min(...$input)); // returns [2, 9] as expected
If you make the same array associative, it fails and always seems to just return the first array:
$input = [
["three" => 3, "six" => 6],
["two" => 2, "nine" => 9],
];
var_dump(min(...$input)); // returns ["three" => 3, "six" => 6]
Why?
Here's an example
According to the documentation values are compared using the standard comparison rules.
In the table of "comparison with various types" there, it states that if both operands are arrays and a key in operand 1 is not present in operand 2, then the arrays are not comparable. This is why min simply returns whatever is the first value in your array.
Specifically, arrays are compared as follows:
If one array has fewer values than the other, it is smaller.
Otherwise if any key in operand 1 is not in operand 2, the arrays are uncomparable.
For each value in operand 1 in turn, compare with the value with the same key in operand 2.
If the same, continue comparing values.
Otherwise the smaller array is the one with the smaller value.
Because they are not comparable, min is simply returning the first array in the list. If you swap the order, the other array will be returned. You can see this if you sort the array with sort($input). Every time you sort it the array is reversed.
To get the behaviour you desire, sort the arrays based on their values and then fetch the first element. But be aware that this will depend which key you defined first, so ["three" => 3, "six" => 6] is not the same as ["six" => 6, "three" => 3].
usort($input, function($a, $b) { return array_values($a) <=> array_values($b); });
var_dump($input[0]);
Just convert them to simple arrays and then return the associative array associated to the result of min.
<?php
function array_min_assoc(){
$args = func_get_args();
$not_assoc = array_map('array_values',$args);
$min = min(...$not_assoc);
$key = array_search($min, $not_assoc);
if ($key !== false){
return $args[$key];
}
}
$input = [
["three" => 3, "six" => 6],
["two" => 2, "nine" => 9],
];
var_dump(array_min_assoc(...$input));
/* returns
array(2) {
["two"] => int(2)
["nine"]=> int(9)
}
*/
Related
This question already has answers here:
Custom key-sort a flat associative based on another array
(16 answers)
Closed 2 years ago.
I have an associative array filled with more or less randomly mixed numeric and string keys, let's go with such example:
$arr = array('third'=>321, 4=>1, 65=>6, 'first'=>63, 5=>88, 'second'=>0);
Now I'd like to sort it in such way:
Sort the array so the numeric keys would be first, and string keys after.
The numeric values sholud be in specific order: 5, 4, 65
The string keys should be ordered by specific order as well: 'first', 'second', 'third'
Output in this case should look like: array(5=>88, 4=>1, 65=>6, 'first'=>63, 'second'=>0, 'third'=>321)
Each element might not be present at all in the original $arr, which might be additional problem...
My guess would be to split the array and make separate one with string keys, and one with numerc keys. Than sort each one and glue them together... But I do't know how to do it?
Edit: Turned out to be a very poor idea, much better approach is in the answer below.
The spaceship operator inside of a uksort() call is the only way that I would do this one.
Set up a two arrays containing your prioritized numeric and word values, then flip them so that they can be used a lookups whereby the respective values dictate the sorting order.
By writing two arrays of sorting criteria separated by <=> the rules will be respected from left to right.
The is_int() check is somewhat counterintuitive. Because we want true outcomes to come before false outcomes, I could have swapped $a and $b in the first element of the criteria arrays, but I decided to keep all of the variables in the same criteria array and just negate the boolean outcome. When sorting ASC, false comes before true because it is like 0 versus 1.
Code: (Demo)
$numericPriorities = array_flip([5, 4, 65]);
$numericOutlier = count($numericPriorities);
$wordPriorities = array_flip(['first', 'second', 'third']);
$wordOutlier = count($wordPriorities);
$arr = ['third' => 321, 4 => 1, 'zero' => 'last of words', 7 => 'last of nums', 65 => 6, 'first' => 63, 5 => 88, 'second' => 0];
uksort(
$arr,
function($a, $b) use ($numericPriorities, $numericOutlier, $wordPriorities, $wordOutlier) {
return [!is_int($a), $numericPriorities[$a] ?? $numericOutlier, $wordPriorities[$a] ?? $wordOutlier]
<=>
[!is_int($b), $numericPriorities[$b] ?? $numericOutlier, $wordPriorities[$b] ?? $wordOutlier];
}
);
var_export($arr);
or (demo)
uksort(
$arr,
function($a, $b) use ($numericPriorities, $numericOutlier, $wordPriorities, $wordOutlier) {
return !is_int($a) <=> !is_int($b)
?: (($numericPriorities[$a] ?? $numericOutlier) <=> ($numericPriorities[$b] ?? $numericOutlier))
?: (($wordPriorities[$a] ?? $wordOutlier) <=> ($wordPriorities[$b] ?? $wordOutlier));
}
);
Output:
array (
5 => 88,
4 => 1,
65 => 6,
7 => 'last of nums',
'first' => 63,
'second' => 0,
'third' => 321,
'zero' => 'last of words',
)
I was extreamly overthinking the case. I came out with a solution that I was unalbe to implement, and unnecessary I described it and ask for help on how to do this.
Turned out that what I really wanted was extreamly simple:
$arr = array('third'=>321, 4=>1, 65=>6, 'first'=>63, 5=>88, 'second'=>0);
$proper_order = array(5, 4, 65, 'first', 'second', 'third');
$newtable = array();
foreach ($proper_order as $order)
{
if (isset($arr[$order]))
$newtable[$order] = $arr[$order];
}
unset($order, $arr, $proper_order);
print_r($newtable);
The result is as expected, and the code should resist the case when the original $arr is incomplete:
Array
(
[5] => 88
[4] => 1
[65] => 6
[first] => 63
[second] => 0
[third] => 321
)
I need to sort the following flat, associative array by its keys, but not naturally. I need to sort the keys by a predefined array of values.
$aShips = [
'0_204' => 1,
'0_205' => 2,
'0_206' => 3,
'0_207' => 4
];
My order array looks like this:
$order = ["0_206", "0_205", "0_204", "0_207"];
The desired result:
[
'0_206' => 3,
'0_205' => 2,
'0_204' => 1,
'0_207' => 4
]
I know how to write a custom sorting function, but I don't know how to integrate the order array.
function cmp($a, $b){
if ($a==$b) return 0;
return ($a<$b)?-1:1;
}
uasort($aShips, "cmp");
If you want to sort by keys. Use uksort. Try the code below.
<?php
$aShips = array('0_204' => 1, '0_205' => 2, '0_206' => 3, '0_207' => 4);
uksort($aShips, function($a, $b) {
return $b > $a;
});
According to the Official PHP Docs, you can use uasort() like this (just for demo):
<?php
// Comparison function
function cmp($a, $b) {
if ($a == $b) {
return 0;
}
return ($a < $b) ? -1 : 1;
}
// Array to be sorted
$array = array('a' => 4, 'b' => 8, 'c' => -1, 'd' => -9, 'e' => 2, 'f' => 5, 'g' => 3, 'h' => -4);
print_r($array);
// Sort and print the resulting array
uasort($array, 'cmp');
print_r($array);
?>
You don't need to leverage a sorting algorithm.
Simply flip $order to use its values as keys (maintaining their order), then merge the two arrays to replace the unwanted values of $order with the values from $aShip.
This assumes that $aShip includes all key values represented in $order, of course. If not, array_intersect_key() can be used to filter $order, but this is dwelling on a fringe case not included in the posted question.
Code: (Demo)
var_export(array_replace(array_flip($order), $aShips));
Output:
array (
'0_206' => 3,
'0_205' => 2,
'0_204' => 1,
'0_207' => 4,
)
This also works when you haven't listed every occurring key in $order -- the unlisted keys are "moved to the back". Proven by this Demo.
Using a custom sorting algorithm can be done and there will be a number of ways, but I'll only show one for comparison's sake.
Reverse your order array and use it as a lookup array while asking uksort() to sort in a descending fashion. If an encountered key is not found in the lookup assign it a value of -1 to ensure it moves to the back of the array.
Code: (Demo)
$lookup = array_flip(array_reverse($order));
uksort($aShips, function($a, $b) use ($lookup) {
return ($lookup[$b] ?? -1) <=> ($lookup[$a] ?? -1);
});
var_export($aShips);
If you don't like reversing the lookup and sorting DESC, you can count the order array to determine a fallback value. This alternative script uses arrow function syntax for brevity and to gain direct access to the variables declared outside of the closure. (Demo)
$lookup = array_flip($order);
$default = count($order);
uksort($aShips, fn($a, $b) => ($lookup[$a] ?? $default) <=> ($lookup[$b] ?? $default));
var_export($aShips);
Can someone please explain to me whats the difference between these 2 functions:
array_diff_ukey
array_diff_uassoc
They both take keys into the compare function, and based on those keys decide if the array element should be returned or not. I've checked the php manual, but to me they both seem to be doing the same thing...
array_diff_ukey returns those elements of the first array whose keys compare different to all keys in the second array (the semantics of the comparison being user-defined). The values associated with those keys do not play a part.
array_diff_uassoc is a "more inclusive" version of the above that also checks values: if a key in the first array compares equal to a key in the second array but the values are different, that element is also included in the result. In this case, comparison of values is not user-defined, but works as in array_diff: for two values to compare equal, their string representation must be identical.
Example, adapted from the PHP docs:
function key_compare_func($key1, $key2)
{
if ($key1 == $key2)
return 0;
else if ($key1 > $key2)
return 1;
else
return -1;
}
$array1 = array('blue' => 1, 'red' => 2, 'green' => "3", 'purple' => 4);
$array2 = array('green' => 3, 'blue' => 6, 'yellow' => 7, 'cyan' => 8);
var_dump(array_diff_ukey($array1, $array2, 'key_compare_func'));
var_dump(array_diff_uassoc($array1, $array2, 'key_compare_func'));
See it in action.
Here, array_diff_ukey will return the "red" and "purple" elements from $array1 because these keys do not compare equal to any of the keys in $array2. However array_diff_uassoc will also return the "blue" element, because even though that key exists in both arrays the associated values are different.
Note that the "green" element is not included in either result, despite the fact that the associated value is a string in $array1 and an integer in $array2.
From the manual:
array_diff — Computes the difference of arrays
array_diff_key — array_diff_key — Computes the difference of arrays using keys for comparison
array_diff_assoc — Computes the difference of arrays with additional index check
This additional index check means that not only the value must be the same, but also the key must be the same. So the difference between array_diff_ukey and array_diff_uassoc is that the latter checks both keys and values, while the first only checks the keys.
The addition of the u after diff_ means that you must supply a custom callback function instead of the default built-in function.
Example based on the manual (Fiddle)
<?php
header("Content-Type: text/plain");
$array1 = array('blue' => 1, 'red' => 2, 'green' => 3, 'black' => 0, 'purple' => 4);
$array2 = array('green' => 5, 'blue' => 6, 'yellow' => 7, 'cyan' => 8, 'black' => 0);
var_dump(array_diff($array1, $array2));
var_dump(array_diff_key($array1, $array2));
var_dump(array_diff_assoc($array1, $array2));
?>
Output
array(4) {
["blue"]=>
int(1)
["red"]=>
int(2)
["green"]=>
int(3)
["purple"]=>
int(4)
}
array(2) {
["red"]=>
int(2)
["purple"]=>
int(4)
}
array(4) {
["blue"]=>
int(1)
["red"]=>
int(2)
["green"]=>
int(3)
["purple"]=>
int(4)
}
In the PHP manual for usort(), it states:
If two members compare as equal, their relative order in the sorted array is undefined.
Also,
A new sort algorithm was introduced. The cmp_function doesn't keep the original order for elements comparing as equal.
Then my question is: what happens if two elements are equal (e.g. the user defined function returns 0)?
I am using this function and apparently equal items are arranged randomly in the sorted array.
Be careful not to confuse "undefined" and "random".
A random implementation would indeed be expected to give a different ordering every time. This would imply there was specific code to shuffle the results when they're found to be equal. This would make the algorithm more complex and slower, and rarely be a desirable outcome.
What undefined means is the opposite: absolutely no care has been taken while designing the algorithm to have predictable or stable order. This means the result may be different every time you run it, if that happens to be the side effect of the algorithm on that data.
You can see the core sort implementation in the PHP source code. It consists of a mixture of "quick sort" (divide and conquer) and insertion sort (a simpler algorithm effective on short lists) with hand-optimised routines for lists of 2, 3, 4, and 5 elements.
So the exact behaviour of equal members will depend on factors like the size of the list, where in the list those equal members come, how many equal members there are in one batch, and so on. In some situations, the algorithm will see that they're identical, and not swap them (the ideal, because swapping takes time); in others, it won't directly compare them until they've already been moved relative to other things, so they'll end up in a different order.
I also find the same while sorting my array, then I found some custom made function because php have some limitation to use defined sorting function .
http://php.net/manual/en/function.uasort.php#Vd114535
PHP 7 uses a stable sort algorithm for small arrays (< 16),
but for larger arrays the algorithm is still not stable.
Furthermore PHP makes no guarantee whether sorting with *sort() is
stable or not https://bugs.php.net/bug.php?id=53341.
If I have an array of: ['b', 'a', 'c', 'b'] and I were to sort this I would get: ['a','b','b','c']. Since 'b' == 'b' php can not guarantee that one comes before the other so the sort order is 'undefined', however since they are equal, does this matter?
If you are using a sort function that returns 0 for unequal objects you're facing a different problem altogether.
understand that php does not care about the order if all the compared values are same.
Example:
$temp=array("b"=>"10","c"=>"10","d"=>"10","e"=>"4");
as above array has 4 array length in which 3 are having the same values as shown b,c,d = 10;
arsort() //The arsort() function sorts an associative array in descending order, according to the value
if print_r(arsort($temp)) o/p: => Array ( [b] => 10 [c] => 10 [d] => 10 [e] => 4 )
this means it return array after sorting equal value but keeps the position(order) same for equal values
but
if $temp=array("a"=>"4",b"=>"10","c"=>"10","d"=>"10","e"=>"4");
here in above array b,c,d = 10 are bounded under two extreme left and right arrays having value less then center (b,c,d = 10) values
the arsort of above temp is o/p:
Array ( [c] => 10 [b] => 10 [d] => 10 [a] => 4 [e] => 4 )
it gives the middle part i.e [c] array in center.
this means if similar values or equal values array is bounded by from both side by lower values array or the first value is lower then the order of equal gives middle one from three array values as first in that three .
I was also facing the same problem, where 2 rows have same values and when apply sort function its order gets changed which I didn't want. I want to sort keys based on their values, if they are equal don't change order. So here is my solution-
// sample array
$arr = Array("a" => 0.57,"b" => 1.19,"c" => 0.57,"d" => 0.57,"e" => 0.57,"f" => 0.57,"g" => 0.57,"h" => 0.57,"i" => 0.99,"j" => 1.19,"k" => 1.19);
$multi_arr = [];
foreach ($arr as $k=>$val){
$multi_arr["$val"][] = array($k=>$val);
}
uksort($multi_arr, function ($a, $b) {
return $b > $a ? 1 : -1;
});
$s_arr = [];
foreach ($multi_arr as $k=>$val){
foreach($val as $p_id){
$p_arr = array_keys($p_id);
$s_arr[] = $p_arr[0];
}
}
print_r($s_arr);
output-
Array([0] => b,[1] => j,[2] => k,[3] => i,[4] => a,[5] => c,[6] => d,[7] => e,[8] => f,[9] => g,[10] => h)
This function preserves order for equal values (example from my CMS EFFCORE):
function array_sort_by_number(&$array, $key = 'weight', $order = 'a') {
$increments = [];
foreach ($array as &$c_item) {
$c_value = is_object($c_item) ? $c_item->{$key} : $c_item[$key];
if ($order === 'a') $increments[$c_value] = array_key_exists($c_value, $increments) ? $increments[$c_value] - .0001 : 0;
if ($order === 'd') $increments[$c_value] = array_key_exists($c_value, $increments) ? $increments[$c_value] + .0001 : 0;
if (is_object($c_item)) $c_item->_synthetic_weight = $c_value + $increments[$c_value];
else $c_item['_synthetic_weight'] = $c_value + $increments[$c_value];
}
uasort($array, function ($a, $b) use ($order) {
if ($order === 'a') return (is_object($b) ? $b->_synthetic_weight : $b['_synthetic_weight']) <=> (is_object($a) ? $a->_synthetic_weight : $a['_synthetic_weight']);
if ($order === 'd') return (is_object($a) ? $a->_synthetic_weight : $a['_synthetic_weight']) <=> (is_object($b) ? $b->_synthetic_weight : $b['_synthetic_weight']);
});
foreach ($array as &$c_item) {
if (is_object($c_item)) unset($c_item->_synthetic_weight);
else unset($c_item['_synthetic_weight']);
}
return $array;
}
It works with both arrays and objects.
It adds a synthetic key, sorts by it, and then removes it.
Test
$test = [
'a' => ['weight' => 4],
'b' => ['weight' => 10],
'c' => ['weight' => 10],
'd' => ['weight' => 10],
'e' => ['weight' => 4],
];
$test_result = [
'b' => ['weight' => 10],
'c' => ['weight' => 10],
'd' => ['weight' => 10],
'a' => ['weight' => 4],
'e' => ['weight' => 4],
];
array_sort_by_number($test, 'weight', 'a');
print_R($test);
var_dump($test === $test_result);
$test = [
'a' => ['weight' => 4],
'b' => ['weight' => 10],
'c' => ['weight' => 10],
'd' => ['weight' => 10],
'e' => ['weight' => 4],
];
$test_result = [
'a' => ['weight' => 4],
'e' => ['weight' => 4],
'b' => ['weight' => 10],
'c' => ['weight' => 10],
'd' => ['weight' => 10],
];
array_sort_by_number($test, 'weight', 'd');
print_R($test);
var_dump($test === $test_result);
In PHP, say that you have an associative array like this:
$pets = array(
"cats" => 1,
"dogs" => 2,
"fish" => 3
);
How would I find the key with the lowest value? Here, I'd be looking for cats.
Is there some built in PHP function that I've missed which does this? It would also be great if there was a solution that accounted for several values being identical, as below:
$pets = array(
"cats" => 1,
"dogs" => 1,
"fish" => 2
);
Above, I wouldn't mind if it just output either; cats or dogs.
Thanks in advance.
array_keys is your friend:
$pets = array(
"cats" => 1,
"dogs" => 2,
"fish" => 3
);
array_keys($pets, min($pets)); # array('cats')
P.S.: there is a dup here somewhere on SO (it had max instead of min, but I can distinctly remember it).
Thats how i did it.
$pets = array(
"cats" => 1,
"dogs" => 2,
"fish" => 3
);
array_search(min($pets), $pets);
I hope that helps
Might try looking into these:
natcasesort(array)
natsort(array)
$min_val = null;
$min_key = null;
foreach($pets as $pet => $val) {
if ($val < $min_val) {
$min_val = $min;
$min_key = $key;
}
}
You can also flip the array and sort it by key:
$flipped = array_flip($pets);
ksort($flipped);
Then the first key is the minimum, and its value is the key in the original array.
Another approach for retrieving a single string is by using a desirable sorting method and retrieving the first key directly by using key() on the sorted array. In this instance the key with the lowest value is desired, asort will sort from lowest to highest values and reset the internal pointer. To retrieve the reverse (highest to lowest) use arsort.
Example: https://3v4l.org/5ijPh
$pets = array(
"dogs" => 2,
"cats" => 1,
"fish" => 3
);
asort($pets);
var_dump(key($pets));
//string(4) "cats"
$pets = array(
"dogs" => 1,
"cats" => 1,
"fish" => 3
);
asort($pets);
var_dump(key($pets));
//string(4) "dogs"
Take note that all of the PHP array sorting methods will alter the array by-reference.
To prevent altering the original array, create a copy of the array or use an Iterator.
$petsSorted = $pets;
asort($petsSorted);
key($petsSorted);
find the highest value
print max(120, 7, 8, 50);
returns --> 120
$array = array(100, 7, 8, 50, 155, 78);
print max($array);
returns --> 155
find the lowest value
print min(120, 7, 8, 50);
returns --> 7
$array = array(50, 7, 8, 101, 5, 78);
print min($array);
returns --> 5