Bez Obid
Bez Obid

Reputation: 21

Java: fast way to check if digits in int are in ascending order

I'm writing a program that finds all of the Armstrong numbers in a range between zero and Integer.MAX_VALUE. Time limit is 10 seconds. I've found that the most time-consuming method is the one that narrows the range of numbers to process by picking only those having their digits in ascending order (with trailing zeros if any). It takes about 57 seconds to run on my machine. Is there any way to make it faster?

static boolean isOK(int x)
{
    int prev = 0;

    while(x > 0)
    {
        int digit = x % 10;
        if((digit > prev || digit == 0) && prev != 0) return false;
        x /= 10;
        prev = digit;
    }
    return true;
}

This method reduces the number of numbers to process from 2.147.483.647 to 140.990.

Upvotes: 1

Views: 5164

Answers (7)

Sam Alam
Sam Alam

Reputation: 1

It has been asked on the MIU test, unfortunately, I was not able to solve it, I later worked on it and it worked fine.

static int isAscending(int n){
    int rem, rem1, pval = 0; boolean isAsc = true;

    while(n != 0){
        rem = n % 10;
        n /= 10;
        rem1 = n % 10;
        n /= 10;
        if(rem > rem1){
            continue;
        } else {
            isAsc = false;
            break;
        }
    }

    if(isAsc == true){
        return 1;
    } else {
        return 0;
    }
}

Upvotes: 0

David Soroko
David Soroko

Reputation: 9086

Perhaps instead of sifting through all the ints, just build up the set of numbers with digits in ascending order. I would argue that you probably want a set of strings (and not ints) because it it is easier to build (recursively by appending/ prepending characters) and then later on you need only the individual "digits" for the power test.

My take on the problem, goes to Long.MAX_VALUE (19 digits) in about 6 seconds and all the way to 39 digits in about an hour

Upvotes: 1

David Soroko
David Soroko

Reputation: 9086

The following code runs x4 as fast as the original (3 sec. on my laptop) and prints 140990 in 3 sec. The method isOK is unchanged

public class Main {

    public static boolean isOK(int x) {
        int prev = 0;
        while (x > 0) {
            int digit = x % 10;
            if (prev != 0 && (digit > prev || digit == 0)) return false;
            x /= 10;
            prev = digit;
        }
        return true;
    }

    public static void main(String[] args) throws Exception {
        long start = System.currentTimeMillis();

        Set<Integer> candidates = IntStream.range(0, Integer.MAX_VALUE)
                .parallel()
                .filter(n -> isOK(n))
                .boxed()
                .collect(Collectors.toSet());
        long stop = System.currentTimeMillis() - start;

        System.err.printf("%d in %d sec.", candidates.size(), stop / 1000);
    }
}

Upvotes: 0

David Soroko
David Soroko

Reputation: 9086

The following code doesn't deal with trailing zeroes but is worth checking if it looks promising in terms of performance.

static boolean isOK(int x) {
    if (x < 10) {
        return true;
    }

    String xs = Integer.toString(x);
    for (int i = 1; i < xs.length(); i++) {
        if (xs.charAt(i) < xs.charAt(i - 1)) {
            return false;
        }
    }
    return true;
}

Upvotes: 0

Olivier Gr&#233;goire
Olivier Gr&#233;goire

Reputation: 35417

You perform two divisions. Divisions are slower than multiplications. So is there a way to change a division into a multiplication? Well... yes, there is.

public static boolean isArmstrongNumber(int n) {
  int prev = 0;
  while (n > 0) {
    int quotient = n / 10;
    int digit = n - (quotient * 10);
    if ((digit > prev || digit == 0) && prev != 0) {
        return false;
    }
    n = quotient;
    prev = digit;
  }
  return true;
}

Upvotes: 0

OldCurmudgeon
OldCurmudgeon

Reputation: 65803

There is very little optimisable code here. It is likely that your time issue is elsewhere. However, one technique comes to mind and that is Memoization.

static Set<Integer> oks = new HashSet<>();

static boolean isOK(int x) {
    if (!oks.contains(x)) {
        int prev = 0;

        while (x > 0) {
            int digit = x % 10;
            if ((digit > prev || digit == 0) && prev != 0) {
                return false;
            }
            x /= 10;
            prev = digit;
        }
        // This one is OK.
        oks.add(x);
    }
    return true;
}

This trick uses a Set to remember all of the ok numbers so you don't need to check them. You could also keep a Set of those that failed too to avoid checking them again but keeping all integers in a collection is likely to break something.

Upvotes: 0

Salem Derisavi
Salem Derisavi

Reputation: 137

One alternative is to construct the set of Armstrong numbers one by one and count them instead of checking every number to see whether it's an Armstrong number or not.

In constructing the whole set, note that when you choose each digit, the set of digits you can choose for the next position is determined, and so on. Two alternatives to implement such a method are recursion and backtracking (which is basically a cheaper way to implement recursion). This method will not need the use of time-consuming division and remainder operations.

Upvotes: 1

Related Questions