Task description
Your task is to make a function that can take any non-negative integer as a argument and return it with its digits in descending order. Essentially, rearrange the digits to create the highest possible number.
Examples:
Input:21445
Output:54421
Input:145263
Output:654321
Input:1254859723
Output:9875543221
Task solution
Tests
We want to test a couple of things:
- Does the function provide an error for invalid input types
- Does the function provide an error for a non-positive integer input
- Does the function work as expected with valid inputs
For the tests, I have used PHPUnit. I also check the largest possible number on 32bit
and 64bit
systems still works, if you go higher than that, a ParseError
would be thrown in the compile step since ParseError
extends CompileError
anyway.
This all taken into account, I wrote the test cases below:
class DescendingOrderTests extends TestCase {
public function testNegativeNumbers() {
$this->expectException(InvalidArgumentException::class);
descendingOrder(-1);
}
public function testInvalidInput() {
$this->expectException(TypeError::class);
descendingOrder("test");
}
public function testSmallNumbers() {
$this->assertSame(0, descendingOrder(0));
$this->assertSame(1, descendingOrder(1));
}
public function testMediumNumbers() {
$this->assertSame(51, descendingOrder(15));
$this->assertSame(2110, descendingOrder(1021));
}
public function testLargeNumbers() {
$this->assertSame(987654321, descendingOrder(123456789));
if(PHP_INT_SIZE === 4) { // 32bit system
$this->assertSame(7463847412, descendingOrder(PHP_INT_MAX));
} else if(PHP_INT_SIZE === 8) { // 64bit system
$this->assertSame(9223372036854775807, descendingOrder(PHP_INT_MAX));
}
}
}
Implementation
function descendingOrder(int $number): int {
if($number < 0) {
throw new InvalidArgumentException("The param \$number must be a positive integer");
}
$numarray = str_split($number);
rsort($numarray);
return (int) join($numarray);
}
First we create the function descendingOrder
, it takes in the input parameter which we expect to be an integer and returns an integer.
Note: Since PHP is a dynamically typed language, even with type declarations, if a
float
is passed, everything past the.
will be stripped so2.6
will become2
, this can be annoying if you expected it to be3
but in fairness, it works as I want it to for this implementation and I actually like this feature to some degree but it would be nice if rounding was properly done, although for this use case, it doesn't really matter 🤷♂️.
In the function body we make sure it is a positive number as defined in the task description by checking if the number passed is less than 0
and if it is, throw an error. If it is 0
or greater, we know it is an integer and is positive.
Since PHP uses Type Juggling, the function str_split
will convert the number to a string and then split it into individual characters.
From here, we use the rsort
function which mutates the given array, in this case $numarray
, to reverse the current order of elements.
Finally we join the array elements back to being a single string and then return it as an integer value using the (int)
decorator.
Sidenote
The strange thing here is that if I remove the (int)
decorator, all tests pass, except the testLargeNumbers
test which uses the PHP_INT_MAX
constant.
For some reason when a constant is passed into the function it doesn't provide the right return type, a TypeError: Return value of descendingOrder() must be of the type integer, string returned
is thrown in the case of PHP_INT_MAX
for example. Thus, we need to use the (int)
decorator to be 100% sure we are returning an int
type and not implicitely relying on PHP itself to do the work for us.
This is strange since we say in the function signature that we return an int
and PHP knows how to convert a string
representation of an int
by itself via the Type Juggling system. That is how all the other cases implicitely pass without the(int)
decorator but constants seem to be treated differently somehow. It's not such a big deal to be explicit I suppose though, just strange but it happens from time to time that we, as developers, find such interesting things crop up from time to time 😅.
Conclusions
This is a pretty simple task but even the simplest of tasks provide the opportunity to learn and in this case, I learned a bit more about how php type juggles comparitively to javascript or python for example and I think the only thing I dislike in this implementation is the usage of the rsort
function since this causes a mutation of the original array.
If I were to reimplement the solution to account for replacing rsort
with a non-mutating alternative, I would probably implement something like this:
function reverse_quick_sort(array $arr): array {
if(count($arr) <= 1) return $arr;
$pivot = $arr[0];
$left = [];
$right = [];
for($i = 1; $i < count($arr); $i++) {
$arr[$i] > $pivot ? $left[] = $arr[$i] : $right[] = $arr[$i];
}
return array_merge(
reverse_quick_sort($left),
[$pivot],
reverse_quick_sort($right)
);
}
function descendingOrder(int $number): int {
if($number < 0) {
throw new InvalidArgumentException("The param \$number must be a positive integer");
}
$numarray = str_split($number);
$reversed = reverse_quick_sort($numarray);
return (int) join($reversed);
}
Top comments (2)
Worth noting that if you add the
declare(strict_types=1)
(since PHP 7.0.0) declaration at the top of your PHP script, integers wont coerce with floats and instead will throw an error as expected.Good to know, thanks! Interesting it isn't the default behaviour though but I assume this can be set in the
.ini
or elsewhere to be defaulted. I will look into that but either way, thanks for sharing!