0

I've been trying to find the most optimized way to compute the number of occurrences of each digit from 0 to 9 in a random range of numbers typed in by the user for a random personal project. Say, the user enters 1 as the lower bound (inclusive) and 20 as the upper bound (inclusive). Output should be like this:

2 12 3 2 2 2 2 2 2 2

User can only enter positive integers. Now, the below code runs fine for small range of numbers/ small bounds, however, as expected it takes 4 seconds+ on my laptop for large numbers/range. I've been trying to find a way to make things quicker, I used modulus to get the digits thinking maybe string conversion is to blame, but it didn't increase speed that much. I want to reduce runtime to less than 2 seconds. There must be a way, but what? Here is my original code:

import java.util.Scanner;

public class CountDigitsRandomRange {
    public static void main(String[] args) {
        Scanner s = new Scanner(System.in);
        String g = s.nextLine();

        while (!g.equals("0 0")) {
            String[] n = g.split(" ");
            long x = Long.parseLong(n[0]);
            long y = Long.parseLong(n[1]);

            long zero = 0;
            long one = 0;
            long two = 0;
            long three = 0;
            long four = 0;
            long five = 0;
            long six = 0;
            long seven = 0;
            long eight = 0;
            long nine = 0;

            for (long i = x; i <= y; i++) {
                String temp = String.valueOf(i);

                for (int j = 0; j < temp.length(); j++) {
                    if (temp.charAt(j) == '0') {
                        zero++;
                    }
                    if (temp.charAt(j) == '1') {
                        one++;
                    }
                    if (temp.charAt(j) == '2') {
                        two++;
                    }
                    if (temp.charAt(j) == '3') {
                        three++;
                    }
                    if (temp.charAt(j) == '4') {
                        four++;
                    }
                    if (temp.charAt(j) == '5') {
                        five++;
                    }
                    if (temp.charAt(j) == '6') {
                        six++;
                    }
                    if (temp.charAt(j) == '7') {
                        seven++;
                    }
                    if (temp.charAt(j) == '8') {
                        eight++;
                    }
                    if (temp.charAt(j) == '9') {
                        nine++;
                    }
                }
            }
            System.out.println(zero + " " + one + " " + two + " "+three + " " + four
             + " " + five + " " + six + " " + seven + " " + eight + " " + nine);
            g=s.nextLine();
        }
    }
}

I've seen some solutions online similar to my issue but they're mostly in C/C++, I don't get the syntax.

Olivier
  • 13,283
  • 1
  • 8
  • 24
Sammie
  • 17
  • 5
  • Why go through the expensive operation of splitting the string? You can just loop through each char in the entire string and use switch-case to increment the counter for the number it matches. That should be the fastest solution possible. – magicmn Jan 22 '22 at 08:42
  • @magicmn nah, it still takes some time. Maybe there's a better way to do this with dynamic programming but can't figure out how. – Sammie Jan 22 '22 at 12:54

2 Answers2

1

Here is a simple implementation that uses modulus. If you want a faster code, you will need to find some smart formula that gives you the result without performing the actual computation.

import java.util.Arrays;

public class Counter
{
    private static long[] counts = new long[10];

    public static void count(long x, long y)
    {
        Arrays.fill(counts, 0);
        for(long val=x; val<=y; val++)
            count(val);
    }

    public static void count(long val)
    {
        while(val>0)
        {
            int digit = (int)(val % 10);
            counts[digit]++;
            val /= 10;
        }
    }

    public static void main(String[] args)
    {
        count(1, 20);
        System.out.println(Arrays.toString(counts));
    }
}

Output:

[2, 12, 3, 2, 2, 2, 2, 2, 2, 2]
Olivier
  • 13,283
  • 1
  • 8
  • 24
  • Thank you for your solution. Your code is definitely much better than mine, but it still takes a long time for large numbers like 100000000 and 100000000. You're right, maybe it needs some smart solution where I don't actually compute anything – Sammie Jan 22 '22 at 12:51
  • @Sammie: The larger the range, the longer it will take to calculate the result. You can manage user expectations by printing a message if the range is large, say 10,000 or greater. By the time they read and understand the message, your code will complete its calculations. – Gilbert Le Blanc Jan 22 '22 at 13:01
0

So here is an issue you might not be aware of @Sammie. In my opinion, you should NOT use the seconds provided by your runner in Java to count time when it comes to making operations more efficient. As far as I am informed, a more objective calculation is to use internal methods of Java which depend on the CPU clock to count time. This way there is less variation between different PC's (although this I don't believe is fully eliminated). Please check my references below:

  1. Clock milis (only use this if you cannot use the solution below)
  2. Nanoseconds

Edit: Here is another stack overflow post discussing this matter. Nanoseconds seem to be preferable.

All you need to do after that is convert into minutes, and you should now be calculating more precisely.

  • 1
    You shouldn't use either to measure and compare performance of operations.The JVM needs some time to "warm-up" and repeating the process a few thousand times will reduce most of the randomness that a single test can give. So better to use a benchmarking library instead. – magicmn Jan 22 '22 at 08:58
  • @magicmn I was not aware of benchmarking libraries for Java. I will take a look and potentially update my answer. Thanks for the info. – Nick the Community Scientist Jan 22 '22 at 09:01
  • 1
    @NicktheCommunityScientist um, I just stated the time to give an idea of how long my users had to wait before they saw the results. Minutes/seconds/nanoseconds etc. internal calculation time won't matter to them. They'd like instantaneous results like my code gives with small values/range. That doesn't happen with big numbers like 1000000 and 100000000 which is why I posted this, so someone could guide me with a better version of it or tell me where I went wrong. – Sammie Jan 22 '22 at 12:20
  • @Sammie Ok, that's fine. I guess I misinterpreted the question. I will see if I can help with that. – Nick the Community Scientist Jan 22 '22 at 12:53