This sort of thing can be implemented in very "clever" ways, so I couldn't resist:
$c64 = str_split(
'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'
);
$sortValue = function($char) {
return !ctype_digit($char) << 9 | !ctype_punct($char) << 8 | ord($char);
}
$sortFunction = function($x, $y) use ($sortValue) {
return $sortValue($x) - $sortValue($y);
};
usort($c64, $sortFunction);
See it in action.
How it works
I 'm taking advantage of the fact that ord
returns the ordinal value of a character, which is both what plain sort
uses to sort the input and also constrained to the range [0, 255] -- i.e. it uses no more than 8 bits for its value.
What this code is doing is taking the return value of ord
(remember: that's how the default sort
works) and augmenting it with two extra bits of information: the MSB represents "is this character not a digit?" and the LSB represents "is this character not punctuation?".
These bits are ORed together with the ordinal value, producing 10-bit quantities that look like this:
Bit# 10 9 8 7 6 5 4 3 2 1 0
^ ^ ^ ^
| | \-----------+-----------/
| | \------------- ord
| \---------------------------- "not punctuation" bit
\------------------------------- "not digit" bit
What happens when you treat these values as integers? Clearly values that correspond to non-digits are going to be larger than any others, and similarly values that correspond to non-punctuation are going to be larger than letters and such.
So by using the result of $x - $y
to determine which item is larger we are in effect making digits be considered smaller than everything else and punctuation larger than digits but smaller than non-digits. This makes sort
put digits first, then punctuation, then everything else when it makes an ascending sort.
Finally, it is very significant that the value of ord
takes part in the comparison: for elements in the same class (e.g. digits) we want their sort order to be the same as the order a plain sort
would produce.