Descending Order
Tags: kata, challenge, codewars, php
Published: 2019-11-24 20:31:17

## 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:

1. Does the function provide an error for invalid input types
2. Does the function provide an error for a non-positive integer input
3. Does the function work as expected with valid inputs

For the tests, I have used [PHPUnit](https://phpunit.de/). 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](https://www.php.net/manual/en/class.parseerror.php) since `ParseError` extends `CompileError` anyway.

This all taken into account, I wrote the test cases below:

```php
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

```php
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 so `2.6` will become `2`, this can be annoying if you expected it to be `3` 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](https://www.php.net/manual/en/language.types.type-juggling.php), 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](https://www.php.net/manual/en/language.types.type-juggling.php) 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:

```php
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);
}
```