3
\$\begingroup\$

I wrote a code to convert an Integer between 1 and 238328 to a unique String, formed from base62 characters (A-Za-z0-9) and of length 3. The String generated can be reconverted to get back the initial Integer.

For example, if the number was 238328 I can convert it to "zzz". I want to be able to use that String "zzz" and get back the number 238328. I have written 2 methods to perform these operations and based on my testing it is working fine.

I am looking for some inputs on making it more readable and possibly more efficient. The conversion consists of 3 nested loops which really does not look right, though I can't get my head around a better way to do this.

class Permutation {

    private final String[] DICTIONARY = new String[]{
            "0","1","2","3","4","5","6","7","8","9",
            "A","B","C","D","E","F","G","H","I","J","K","L","M","N","O","P","Q","R","S","T","U","V","W","X","Y","Z",
            "a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y","z"};

    private Map<String, Integer> codes = new HashMap<>();
    private Map<Integer, String> numbers = new HashMap<>();

    String generateCode(int num){

        int maxNumber = 238328;
        if(num > maxNumber || num < 1) return null;

        if(numbers.containsKey(num)){
            return numbers.get(num);
        }

        int counter = 0;

        for (int i = 0; i < DICTIONARY.length; i++) {
            for (int j = 0; j < DICTIONARY.length; j++) {
                for (int k = 0; k < DICTIONARY.length; k++) {
                    String code = DICTIONARY[i] + DICTIONARY[j] + DICTIONARY[k];
                    counter++;
                    numbers.put(counter, code);
                    codes.put(code, counter);
                    if(counter == num) return code;
                }
            }
        }

        return null;
    }

    int decode(String code){
        if(codes.containsKey(code)){
            return codes.get(code);
        }

        return 0;
    }
}
\$\endgroup\$
1
  • \$\begingroup\$ As you noticed, it is base 62. Conversion is a simple matter of 3 multiplications/divisions. \$\endgroup\$
    – vnp
    Commented Mar 20, 2018 at 19:56

1 Answer 1

2
\$\begingroup\$

Your problem definition is .... odd. your code, will, for the input value 1 generate the output 000. Is that what you really want? Why do you need to 1-index the value set instead of 0-index it?

Further, your code caches all values up-to-and-including the input value in a HashMap. This can become quite large.... for the full dataset you're looking at about a quarter-million values where each is about 128 bytes of memory (a String, an Integer, and a Map.Entry plus some other overheads), or about 70 megabytes of data.

Your DICTIONARY should be an array of chars, not an array of String.

The maxNumber should be a static constant too.

Finally, you return a null value for an invalid input. That's very unconventional, you should throw an IllegalArgumentException instead.

As mentioned in a comment, you can "easily" solve your problem with a few "simple" operations.

private static final char[] DIGITS = ("0123456789"
       + "ABCDEFGHIJKLMNOPQRSTUVWXYZ"
       + "abcdefghijklmnopqrstuvwxyz").toCharArray();
private static final int BASE = DIGITS.length;
private static final int MAX_NUMBER = BASE * BASE * BASE;

public static String generateCode(int num) {
    if (num < 1 || num > MAX_NUMBER) {
        throw new IllegalArgumentException("Illegal input value: " + num);
    }

    int value = num - 1;

    char ac = DIGITS[((value / BASE) / BASE) % BASE];
    char bc = DIGITS[(value / BASE) % BASE];
    char cc = DIGITS[value % BASE];
    return new String(new char[]{ac, bc, cc});
}

Note that I compute each digit separately (in to ac, bc, and cc) and then return the combination as a string.

Further, when running the code, I discovered that you are off on your zzz assertion .... the max value you propose for zzz is wrong, what you have is yyy. - I discovered I had a text transpose of zy instead of yz in my constant.

See the code running on ideone: https://ideone.com/EZqXlJ

For the inputs supplied in the tests I get the values:

Value 0 Error Illegal input value: 0
Value 1: 000
Value 2: 001
Value 62: 00z
Value 63: 010
Value 1000: 0G7
Value 238328: zzz
Value 238329 Error Illegal input value: 238329
\$\endgroup\$
3
  • \$\begingroup\$ Maybe specifying DICTIONARY with a long literal isn't the best of ideas - I doubt doing same for MaxNumber without sanity check against DICTIONARY is. Without specification, I'd use "little-endian" "digit" strings. \$\endgroup\$
    – greybeard
    Commented Mar 20, 2018 at 22:12
  • \$\begingroup\$ @greybeard - not sure I follow all you are suggesting, but you've made me realize that the naming conventions are off, and using an array of "digits" is better than a Dictionary.charAt() concept. Editing my answer. \$\endgroup\$
    – rolfl
    Commented Mar 20, 2018 at 23:27
  • \$\begingroup\$ @karvai - I subtract the number by 1 because your specification demands it. What does your code give you for generateCode(1)... it gives 000 ... right? That's why I subtract 1, and that was the first issue I noted in my answer. \$\endgroup\$
    – rolfl
    Commented Mar 20, 2018 at 23:30

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.