简体   繁体   中英

Generate all possible string from a given length

I would like to be able to generate all possible strings from a given length, and I frankly don't know how to code that. So for further explanation, I and a friend would like to demonstrate some basic hacking techniques, so bruteforcing comes up. Of course, he will be my victim, no illegal thing there.

However, the only thing he told me is that his PW will be 4-char-long, but I'm pretty sure his PW won't be in any dictionnary, that would be toi easy.

So I came up with the idea of generating EVERY 4-char-long-string possible, containing az characters (no caps).

Would someone have a lead to follow to code such an algorithm ? I don't really bother with performances, if it takes 1 night to generate all PW, that's no problem.

Don't forget, that's only on demonstration purposes.

You can do it just how you'd do it with numbers. Start with aaaa. Then increment the 'least significant' part, so aaab. Keep going until you get to aaaz. Then increment to aaba. Repeat until you get to zzzz.

So all you need to do is implement is

String getNext(String current)

To expand on this; It possibly isnt the quickest way of doing things, but it is the simplest to get right.

As the old adage goes - 'first make it right, then make it fast'. Getting a working implementation that passes all your tests (you do have tests, right?) is what you do first. You then rewrite it to make it fast, using your tests as reassurance you're not breaking the core functionality.

The absolutely simplest way is to use four nested loops:

char[] pw = new char[4];
for (pw[0] = 'a' ; pw[0] <= 'z' ; pw[0]++)
    for (pw[1] = 'a' ; pw[1] <= 'z' ; pw[1]++)
        for (pw[2] = 'a' ; pw[2] <= 'z' ; pw[2]++)
            for (pw[3] = 'a' ; pw[3] <= 'z' ; pw[3]++)
                System.out.println(new String(pw));

This does not scale well, because adding extra characters requires adding a level of nesting. Recursive approach is more flexible, but it is harder to understand:

void findPwd(char[] pw, int pos) {
    if (pos < 0) {
        System.out.println(new String(pwd));
        return;
    }
    for (pw[pos] = 'a' ; pw[pos] <= 'z' ; pw[pos]++)
        findPwd(pw, pos-1);
}

Call recursive method like this:

char[] pw = new char[4];
findPwd(pw, 3);
private static void printAllStringsOfLength(int len) {
    char[] guess = new char[len];
    Arrays.fill(guess, 'a');

    do {
        System.out.println("Current guess:  " + new String(guess));
        int incrementIndex = guess.length - 1;
        while (incrementIndex >= 0) {
            guess[incrementIndex]++;
            if (guess[incrementIndex] > 'z') {
                if (incrementIndex > 0) {
                    guess[incrementIndex] = 'a';
                }
                incrementIndex--;
            }
            else {
                break;
            }
        }

    } while (guess[0] <= 'z');
}
public class GenerateCombinations {

    public static void main(String[] args) {
        List<Character> characters = new ArrayList<Character>();
        for (char c = 'a'; c <= 'z'; c++) {
            characters.add(c);
        }
        List<String> allStrings = new ArrayList<String>();
        for (Character c : characters) {
            for (Character d : characters) {
                for (Character e : characters) {
                    for (Character f : characters) {
                        String s = "" + c + d + e + f;
                        allStrings.add(s);
                    }
                }
            }
        }
        System.out.println(allStrings.size()); // 456 976 combinations
    }
}

This is something you can do recursively.

Lets define every (n) -character password the set of all (n-1) -character passwords, prefixed with each of the letters a thru z . So there are 26 times as many (n) -character passwords as there are (n-1) -character passwords. Keep in mind that this is for passwords consisting of lower-case letters. Obviously, you can increase the range of each letter quite easily.

Now that you've defined the recursive relationship, you just need the terminating condition.

That would be the fact that there is only one (0) -character password, that being the empty string.

So here's the recursive function:

def printNCharacterPasswords (prefix, num):
    if num == 0:
        print prefix
        return
    foreach letter in 'a'..'z':
        printNCharacterPasswords (prefix + letter, num - 1)

to be called with:

printNCharacterPasswords ("", 4)

And, since Python is such a wonderful pseudo-code language, you can see it in action with only the first five letters:

def printNCharacterPasswords (prefix, num):
    if num == 0:
        print prefix
        return
    for letter in ('a', 'b', 'c', 'd', 'e'):
        printNCharacterPasswords (prefix + letter, num - 1)

printNCharacterPasswords ("", 2)

which outputs:

aa
ab
ac
ad
ae
ba
bb
bc
bd
be
ca
cb
cc
cd
ce
da
db
dc
dd
de
ea
eb
ec
ed
ee

A aroth points out, using a digit counter approach is faster. To make this even faster, you can use a combination of an inner loop for the last digit and a counter for the rest (so the number of digits can be variable)

public static void main(String... args) {
    long start = System.nanoTime();
    int letters = 26;
    int count = 6;
    final int combinations = (int) Math.pow(letters, count);
    char[] chars = new char[count];
    Arrays.fill(chars, 'a');
    final int last = count - 1;

    OUTER:
    while (true) {
        for (chars[last] = 'a'; chars[last] <= 'z'; chars[last]+=2) {
            newComination(chars);
            chars[last]++;
            newComination(chars);
        }

        UPDATED:
        {
            for (int i = last - 1; i >= 0; i--) {
                if (chars[i]++ >= 'z')
                    chars[i] = 'a';
                else
                    break UPDATED;
            }
            // overflow;
            break OUTER;
        }
    }
    long time = System.nanoTime() - start;
    System.out.printf("Took %.3f seconds to generate %,d combinations%n", time / 1e9, combinations);
}

private static void newComination(char[] chars) {

}

prints

Took 0.115 seconds to generate 308,915,776 combinations

Note: the loop is so simple, its highly likely that the JIT can eliminate key pieces of code (after in-lining newCombination) and the reason its so fast is its not really calculating every combination.


A simpler way to generate combinations.

long start = System.nanoTime();
int letters = 26;
int count = 6;
final int combinations = (int) Math.pow(letters, count);
StringBuilder sb = new StringBuilder(count);
for (int i = 0; i < combinations; i++) {
    sb.setLength(0);
    for (int j = 0, i2 = i; j < count; j++, i2 /= letters)
        sb.insert(0, (char) ('a' + i2 % letters));
//  System.out.println(sb);
}
long time = System.nanoTime() - start;
System.out.printf("Took %.3f seconds to generate %,d combinations%n", time / 1e9, combinations);

prints

aaaa
aaab
aaac
....
zzzx
zzzy
zzzz
Took 0.785 seconds to generate 456,976 combinations

It spends most of its time waiting for the screen to update. ;)

If you comment out the line which prints the combinations, and increase the count to 5 and 6

Took 0.671 seconds to generate 11,881,376 combinations
Took 15.653 seconds to generate 308,915,776 combinations
public class AnagramEngine {

    private static int[] anagramIndex;

    public AnagramEngine(String str) {
        AnagramEngine.generate(str);
    }

    private static void generate(String str) {
        java.util.Map<Integer, Character> anagram = new java.util.HashMap<Integer, Character>();
        for(int i = 0; i < str.length(); i++) {
            anagram.put((i+1), str.charAt(i));
        }        
        anagramIndex = new int[size(str.length())];        
        StringBuffer rev = new StringBuffer(AnagramEngine.start(str)+"").reverse();
        int end = Integer.parseInt(rev.toString());
        for(int i = AnagramEngine.start(str), index = 0; i <= end; i++){
            if(AnagramEngine.isOrder(i)) 
                anagramIndex[index++] = i;   
        }
        for(int i = 0; i < anagramIndex.length; i++) {
            StringBuffer toGet = new StringBuffer(anagramIndex[i] + "");
            for(int j = 0; j < str.length(); j++) {
               System.out.print(anagram.get(Integer.parseInt(Character.toString(toGet.charAt(j)))));
            }
            System.out.print("\n");
        }
        System.out.print(size(str.length()) + " iterations");
    }

    private static boolean isOrder(int num) {
        java.util.Vector<Integer> list = new java.util.Vector<Integer>();
        String str = Integer.toString(num);              
        char[] digits = str.toCharArray();
        for(char vecDigits : digits) 
            list.add(Integer.parseInt(Character.toString(vecDigits)));     
        int[] nums = new int[str.length()];
        for(int i = 0; i < nums.length; i++) 
            nums[i] = i+1;
        for(int i = 0; i < nums.length; i++) {
            if(!list.contains(nums[i])) 
                return false;    
        }
        return true;           
    }    

    private static int start(String str) {
        StringBuffer num = new StringBuffer("");
        for(int i = 1; i <= str.length(); i++) 
            num.append(Integer.toString(i));
        return Integer.parseInt(num.toString());
    }    

    private static int size(int num) {
        int size;
        if(num == 1) { 
            return 1;
        }
        else {
            size = num * size(num - 1);   
        }            
        return size;
    }

    public static void main(final String[] args) {
        final java.util.Scanner sc = new java.util.Scanner(System.in);
        System.out.printf("\n%s\t", "Entered word:");
        String word = sc.nextLine();
        System.out.printf("\n");
        new AnagramEngine(word);
    }
}

Put all the characters you expect the password to contain into an array. Write a stub function to test if your algorithm finds the correct password. Start with passwords of length 1, work your way up to 4 and see if your fake password is found on each iteration.

you can use the following code for getting random string. It will return you a string of 32 chars. you can get string of desired length by using substring(). Like if you want a string with 10 chars then:

import java.security.SecureRandom;
import java.math.BigInteger;

SecureRandom srandom = new SecureRandom();
String rand = new BigInteger(176, srandom).toString(32);
rand.substring(0,7);

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