DEV Community

James Robb
James Robb

Posted on • Updated on

Descending Order

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. 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));
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

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);
}
Enter fullscreen mode Exit fullscreen mode

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, 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);
}
Enter fullscreen mode Exit fullscreen mode

Top comments (2)

Collapse
 
aminnairi profile image
Amin

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.

<?php

// https://repl.it/repls/OpenRowdyTrials

declare(strict_types=1);

function a(int $b) {
    return $b;
}

var_dump(a(1.1));
// PHP Fatal error:  Uncaught TypeError: Argument 1 passed to a() must be of the type integer, float given
Collapse
 
jamesrweb profile image
James Robb

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!