Sorting an array by values first, then keys - php

I want to sort this key-value array by values first, then keys.
This is the array:
$a = [
10 => 1,
4 => 2,
3 => 2
];
i want to get:
4 => 2,
3 => 2,
10 => 1
I tried to use arsort, but can't get current answer.

Use uksort to sort by keys, use those keys to look up the values in the array, do a comparison by values first and keys upon equality:
uksort($arr, function ($a, $b) use ($arr) {
if (($res = $arr[$a] - $arr[$b]) != 0) {
return $res;
}
return $a - $b;
});
See https://stackoverflow.com/a/17364128/476 for more in-depth explanations of sort callbacks.

Related

How to sort an associative array by key type and force specific order in PHP? [duplicate]

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
)

Custom sort an associative array by its keys using a lookup array

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);

PHP uksort() for several arrays

I would like to have a unique sort function for several associative arrays.
The best candidate among the various PHP sort functions would be uksort(), (usort() would be ideal, but it changes the array keys to become the numerical index(!)).
For instance (using a simpler array)
function sorting_by_length_desc ($a, $b)
{
return strlen($GLOBALS['arr'][$b]) - strlen($GLOBALS['arr'][$a]);
}
$arr = ('chandler' => 'bing', 'monica' => 'lewinsky');
uksort($arr, 'sorting_by_length_desc');
will make $arr to be
('monica' => 'lewinsky', 'chandler' => 'bing');
without affecting the keys.
So how to use the same sort function for any array, uksort() being called at various places in the code? For instance for $arr1, $arr2, ..., $arrn?
Is it necessary to use another global var with the array name to be assigned to the array to be sorted (before the sort), and used globally in the sort function?
There must be something else, cleaner, right?
You can have a common comparison function like:
function sorting_by_length_desc ($a, $b) {
return strlen($b) - strlen($a);
}
Also uksort sorts the array on keys. Is that what you are looking for?
If you want to sort the arrays on value maintaining the key,value association you can use uasort.
You can achieve this by uksort() function
$arr = array('chandler' => 'bing', 'monica' => 'lewinsky', 'name' => 'smallest one');
uksort($arr, function($a, $b) {
return strlen($a) - strlen($b);
});
$result = $arr;
Result(sorted as smallest string length of array keys):
Array
(
[name] => smallest one
[monica] => lewinsky
[chandler] => bing
)

Sorting Arrays by More the One Value, and Prioritizing the Sort based on Column data

I'm looking for a way to sort an array, based on the information in each row, based on the information in certain cells, that I'll call columns.
Each row has columns that must be sorted based on the priority of: timetime, lapcount & timestamp.
Each column cotains this information: split1, split2, split3, laptime, lapcount, timestamp.
laptime if in hundredths of a second. (1:23.45 or 1 Minute, 23 Seconds & 45 Hundredths is 8345.) Lapcount is a simple unsigned tiny int, or unsigned char. timestamp is unix epoch.
The lowest laptime should be at the get a better standing in this sort. Should two peoples laptimes equal, then timestamp will be used to give the better standing in this sort. Should two peoples timestamp equal, then the person with less of a lapcount get's the better standing in this sort.
By better standing, I mean closer to the top of the array, closer to the index of zero where it a numerical array.
I think the array sorting functions built into php can do this with a callback, I was wondering what the best approch was for a weighted sort like this would be.
<?php
$racers = array('Dygear', 'filur', 'glyphon', 'AndroidXP', 'HorsePower', 'Becky Rose', 'kanutron', 'Victor');
foreach ($racers as $racer)
$query[$racer] = unserialize(file_get_contents('http://lfsworld.net/pubstat/get_stat2.php?version=1.4&ps=1&idk=35cP2S05Cvj3z7564aXKyw0Mqf1Hhx7P&s=2&action=pb&racer='.urlencode($racer)));
foreach ($query as $racer => $data)
{
foreach ($data as $row)
{
if ($row['track'] == '000' && $row['car'] == 'FOX')
$sortArray[$racer] = $row;
}
}
# Sort the Array $sortArray
var_dump($sortArray);
?>
What you want is a Schwartzian transform. Create a new array with the values to sort by as the key and the original values as the values, sort the array by key, then strip away the keys.
EDIT:
Quick-and-dirty implementation:
$times = Array(
Array('val1' => 1, 'val2' => 3, 'val3' => 8),
Array('val1' => 1, 'val2' => 2, 'val3' => 5),
Array('val1' => 3, 'val2' => 8, 'val3' => 6),
Array('val1' => 2, 'val2' => 2, 'val3' => 1),
Array('val1' => 4, 'val2' => 7, 'val3' => 3)
);
$timesdec = Array();
foreach($times as $time)
{
$timesdec[sprintf('%010d%010d', $time['val1'], $time['val3'])] = $time;
}
ksort($timesdec);
$newtimes = array_values($timesdec);
var_dump($newtimes);
This is untested, and I felt like using php 5.3. But, you can do the same without anonymous functions. You don't really need to use separate functions for each of the column comparisons like I did. But, it seems like you're really trying to replicate the way a database sorts multiple columns, so I'm giving you something close to it.
$comparatorSequence = array(
function($a, $b) {
return $a['laptime'] - $b['laptime'];
}
, function($a, $b) {
return $a['timestamp'] - $b['timestamp'];
}
, function($a, $b) {
return $a['lapcount'] - $b['lapcount'];
}
);
usort($theArray, function($a, $b) use ($comparatorSequence) {
foreach ($comparatorSequence as $cmpFn) {
$diff = call_user_func($cmpFn, $a, $b);
if ($diff !== 0) {
return $diff;
}
}
return 0;
});

Comparing Multidimensional arrays in PHP

I am trying to compare to multidimensional arrays, but I can't just use array_diff_assoc(). The arrays I am trying to compare are both associative arrays, and they are both sorted so the keys are in the same order. For the most part the arrays are identical in structure. I can't seem to figure out how to compare the elements that store arrays, I can compare the elements that hold one value just fine does anyone know what I can do?
If your trying to just see if they are different (and not what specifically is different) you could try something like:
return serialize($array1) == seralize($array2);
That would give you a yea or neah on the equality of the two arrays.
It's not clear whether you want to see if they're equal, or actually want an output of what the differences are.
If it's the former then you could do it the proper way, with a recursive function:
$array1 = array('a' => 1, 'b' => 2, 'c' => array('ca' => 1, 'cb' => array('foo')));
$array2 = array('a' => 1, 'b' => 2, 'c' => array('ca' => 1, 'cb' => array('bar')));
var_dump(arrayEqual($array1, $array2));
function arrayEqual($a1, $a2)
{
if (count(array_diff($a1, $a2)))
return false;
foreach ($a1 as $k => $v)
{
if (is_array($v) && !arrayEqual($a1[$k], $a2[$k]))
return false;
}
return true;
}
Or use a complete hack like this:
if (serialize($array1) == serialize($array2))

Categories