简体   繁体   中英

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.

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.

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

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;
}

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.

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;
}

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM