3
\$\begingroup\$

I am not sure what to call this type of code (16-digit alphanumeric code).

Conditions are like this:

  1. Digits (or characters) are limited 16
  2. Numeric, alphabet mixed
  3. No duplication allowed (not yet implemented)

One class with the main method:

public class QuadraDigitCipherGenerator {
    // Step 1. Generate 16 digit wich is not duplicated
    // Step 2. divide by 4 digit

    private static List<Integer> pickRandomIndex (String original, int count) {
        List<Integer> randIndices = new ArrayList<Integer>();
        Set<Integer> noDuplicateIndices = new HashSet<Integer>();
        System.out.println(noDuplicateIndices.size() + "    " + count);
        while (noDuplicateIndices.size() < count){
            System.out.println(noDuplicateIndices.size() + "    " + count);
            int randNumber = (int)(Math.random() * original.length());
            noDuplicateIndices.add(randNumber);
        }
        randIndices.addAll(noDuplicateIndices);
        return randIndices;
    }

    // FROM 97 - 122
    private static char convertDigit2Char(char digit) {
        return (char)(17 + digit);
    }

    private static String generateCode(int digit, int charCnt) {
        long nano = System.nanoTime();
        StringBuilder util = new StringBuilder(String.valueOf(nano));
        util = util.reverse();
        List<Integer> convertIndices = pickRandomIndex(util.toString(), charCnt);
        System.out.println("Below indices will be casting to Alphabet " + convertIndices);

        for(Integer i : convertIndices){
            char target = util.charAt(i);
            util.setCharAt(i, convertDigit2Char(target));
        }
        return util.toString();
    }

    private static String beautifyDigits(String original, int term){
        System.out.println("original " + original);
        return original.substring(0, term) + "-" + original.substring(term, 2*term)
                + "-" + original.substring(2*term, 3*term) + "-" + original.substring(3*term, 4*term);
    }


    public static void main(String argv[]) throws Exception {
        int digit = 16;
        System.out.println(digit + " digits random code > " + beautifyDigits(generateCode(16, 4), 4));
    }
}

The output looks like D33J-H872-3545-71A1, a product serial key.

Anyway, I think this code is not satisfied with the third condition (no duplication). Do you have any advice with regards to that aspect of the requirements?

\$\endgroup\$
  • \$\begingroup\$ popular java library suggestion also would be helpful for me :D \$\endgroup\$ – Juneyoung Oh Mar 31 '17 at 7:28
  • 2
    \$\begingroup\$ We can't really tell you how to implement "no duplication", that you will have to decide for yourself. The most straightforward way to strictly avoid duplication is to store all the keys you have already generated in some file or database, and check each new key against it to make sure it doesn't already exist. \$\endgroup\$ – Phrancis Mar 31 '17 at 7:42
  • \$\begingroup\$ @Phrancis The generated code will be primary key in database. However, user want to push data as bulk(like thousands) I need to be sure unique of this 16 digit code. I think java UUID is quite unique but it seems to long for the clients. \$\endgroup\$ – Juneyoung Oh Mar 31 '17 at 8:10
  • \$\begingroup\$ I thought that UUID would serve this purpose very nicely as well, although you are correct that if you are stuck with this 16-digit format because that's what the client wants then you have to come up with something like what you did, and may just have to live with the overhead of checking the database on each new key. \$\endgroup\$ – Phrancis Mar 31 '17 at 9:04
  • \$\begingroup\$ @Phrancis matching each key with database would be my final choice, since in this scenario, user can do bulk creation(and the code what I am trying to make is primary key). It seems hopeless, but if you do not mind I would like to wait for other alternatives. Thanks! \$\endgroup\$ – Juneyoung Oh Mar 31 '17 at 9:21
10
\$\begingroup\$

Okay first off, let's make your random character choosing better.

The simplest way to get random symbols is to simply define the alphabet and then index into it.

So here's how you'd typically do it:

static final private String ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789";
final private Random rng = new SecureRandom();    

char randomChar(){
    return ALPHABET.charAt(rng.nextInt(ALPHABET.length()));
}

String randomUUID(int length, int spacing, char spacerChar){
    StringBuilder sb = new StringBuilder();
    int spacer = 0;
    while(length > 0){
        if(spacer == spacing){
            sb.append(spacerChar);
            spacer = 0;
        }
        length--;
        spacer++;
        sb.append(randomChar());
    }
    return sb.toString();
}

The likelihood of generating two keys that are duplicates is \$\frac{1}{36^{16}} \approx 8\cdot 10^{-24}\$. So if you generate 10000 (ten thousand) bulk keys, the likelyhood of not having any duplicates is: $$(1-\frac{1}{36^{16}})^{10000}=99.99999999999999999987\%$$.

In other words, you are not very likely to get duplicates as long as your random function is good (hence the use of SecureRandom instead of Math.random(). To be certain you have to always check against your database anyway as a last step so I would just not bother with duplicate checking on the client side as the likelihood of finding duplicates is so very low.

\$\endgroup\$
  • \$\begingroup\$ Awesome solution that I looking for! I should add small case too. Thanks! \$\endgroup\$ – Juneyoung Oh Apr 3 '17 at 3:08
  • \$\begingroup\$ It is quite impressive code. I just read UUID.java. It calls SecureRandom internally when I call randomUUID(). \$\endgroup\$ – Juneyoung Oh Apr 3 '17 at 4:43
  • \$\begingroup\$ @emily-l Where did you get number 16? Is that length? \$\endgroup\$ – insan-e May 22 '18 at 9:09
  • \$\begingroup\$ @emily-l what about birthday problem everyone mentions? \$\endgroup\$ – insan-e May 22 '18 at 9:16
  • \$\begingroup\$ I think the probability of having a duplicate is much higher. Let us take the alphabet of 32 characters and only 4 characters long output string. This has 32^4=1048576 of possible options. Using the formula generating 100.000 unique entries has the likelyhood of not having any duplicates 0.909 which seems a lot. But let say you wish to have have just one new unique value, you have 948576 of not yet taken options out of all 1048576. 948576/1048576 = 0.905. I am pretty sure this is the probability that your newly selected string is unique. A single string only. \$\endgroup\$ – Michał Cegielski Aug 24 '18 at 9:30
1
\$\begingroup\$

Ok, recognizing that what you are trying to do is generate a series of base 36 numbers, we can do this simpler and functionally.

 public static void main(String[] args) {
        String result = new SecureRandom().ints(0,36)
            .mapToObj(i -> Integer.toString(i, 36))
            .map(String::toUpperCase).distinct().limit(16).collect(Collectors.joining())
            .replaceAll("([A-Z0-9]{4})", "$1-").substring(0,19);

        System.out.println(result);
    }

produces: 51QZ-RJ30-JZW3-97L7

16 alphanums, all unique.

There are several improvements with this implementation.

  1. It's shorter. Less code is generally a win.
  2. It doesn't define its own alphabet and takes advantage of a detail of the problem by using the built in Integer libraries to output base 36 numbers
  3. It's purely functional and has no external variables to manage. The previous implementation has 4 variables - 2 in function. Those variables are incremented and decremented leaving room for bugs.
  4. This code makes a clear delineation between the generation of the number set and its format. Separation of concerns is generally considered best practice.
  5. Uses native libraries to perform the format and eliminates any logic which can also introduce bugs.

In general, it's a library driven approach which will naturally reduce the chance for bugs as it relies on well vetted existing code.

\$\endgroup\$
1
\$\begingroup\$

In manufacturing a lot of times the serial number encodes information about the product, such as the date of manufacture and part or model numbers and other options. They also include serial numbers for batches and for individual pieces. This leads to them being unique, as they are literally serial numbers. You can use some other type of encoding like base 16 or 32 to obfuscate the serial 001 type of numbers or the dates.

For the type of serial number you are using, you might also consider a checksum for the last digit. See the luhn algorithm for example, which is especially useful if people are transcribing your serial number.

\$\endgroup\$
  • \$\begingroup\$ This does not provide an answer to the question. Once you have sufficient reputation you will be able to comment on any post; instead, provide answers that don't require clarification from the asker. - From Review \$\endgroup\$ – Toby Speight Jan 22 at 16:12
  • \$\begingroup\$ I know it's not based on the code, but it is in response to their question: "Anyway, I think this code is not satisfied with the third condition (no duplication). Do you have any advice with regards to that aspect of the requirements?" \$\endgroup\$ – jreese Jan 22 at 16:15
  • \$\begingroup\$ It doesn't appear to be a review of the code itself, making it Not An Answer. My understanding is that the format and randomness are a requirement. \$\endgroup\$ – Toby Speight Jan 22 at 16:20
  • \$\begingroup\$ Fair enough, I'd have made it a comment if I could have. They asked a question along with posting their code so I tried to answer it with some practical application examples. \$\endgroup\$ – jreese Jan 22 at 16:27
  • 1
    \$\begingroup\$ In my opinion, this is a review. The code has clearly been read, understood, and commented on - This answer suggests possible improvements of the existing code and features. Welcome to Code Review, jreese! \$\endgroup\$ – Simon Forsberg Jan 22 at 16:32

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