简体   繁体   English

Java中的排列而不使用数组

[英]Permutations in Java without using an array

The code below loops through all of the characters submitted, puts them into a string array and then after hashing them, checks against the hashed password originally entered. 下面的代码循环遍历所有提交的字符,将它们放入字符串数组,然后对它们进行散列,然后对照最初输入的散列密码进行检查。 This logic works fine for up to 5 character passwords however when I get to 6 characters the number of permutations (36^6) is so large that a memory exception is thrown due to the size of the array being too large. 此逻辑适用于最多5个字符的密码,但是当我输入6个字符时,排列的数目(36 ^ 6)太大,以至于由于数组的大小太大而引发了内存异常。

I am therefore trying to modify it too use just string rather than an array to stop this memory issue but cant quite get it to work, any obvious suggestions? 因此,我试图仅使用字符串而不是数组来修改它来停止此内存问题,但无法完全使它工作,有什么明显的建议吗?

You can just enumerate all the possible passwords containing letters and digits by counting from 0 to infinity and converting the number toString with base 36. 您可以通过从0计数到无穷大和转换的数量只是列举含有字母和数字的所有可能的密码toString与基地36。

long k = 0;
while (true) {
    String pwd = Long.toString(k++, 36);
    if (SHA1(pwd).equals(hashedPassword)) {
        System.out.println(pwd);
    }
}

Here, toString works for a base up to 36, using [0-9a-z] , ie it works in your case, but if you want to include special characters you will have to create your own number-to-password function (think division and modulo), but the rest remains the same. 在这里, toString使用[0-9a-z]最多可使用36个基数,也就是说,它适用于您的情况,但是,如果要包括特殊字符,则必须创建自己的数字转密码功能(请考虑除法和取模),但其余部分保持不变。

This way, the memory requirement is O(1), but of course the complexity is still O(36 n ) for passwords with up to n characters. 这样,内存要求为O(1),但是对于最多n个字符的密码,复杂度当然仍为O(36 n )。


One problem with this approach is that -- as with all representations of numbers -- leading zeros will be omitted, thus Long.toString will never produce a password starting with 0 (except 0 itself). 这种方法的一个问题是-与所有数字表示一样,前导零将被忽略,因此Long.toString将永远不会生成以0开头的密码(除了0本身)。 To counter this, you could use two nested loops -- the outer loop iterating the number of digits in the password and the inner loop iterating the numbers up to 36 d and padding the string with leading zeros, or loop from 36 d to 2*36 d and cut away the first (non-zero) digit. 为了解决这个问题,您可以使用两个嵌套循环-外循环迭代密码中的位数,而内循环迭代数字直至36 d,并用前导零填充字符串,或者从36 d循环成2 * 36 d并切掉第一个(非零)数字。 This may seem like a lot more work than before, but in fact it just produces twice as many passwords. 这似乎比以前需要做更多的工作,但实际上它只产生两倍的密码。

for (int d = 1; d < 3; d++) {
    long p = pow(36, d);
    for (long n = p; n < 2 * p; n++) {
        String pwd = Long.toString(n, 36).substring(1);
        System.out.println(n + " " + pwd);
    }
}

Output: 输出:

0
1
...
z
00
01
...
zz

In addition to tobias' answer. 除了托比亚斯的答案。

Here's a way you can use your specified alphabet to recurse through all possible combinations of it, up to a certain length, and operate on each of them without storing them unless you detect that it is a match. 您可以通过这种方式使用指定的字母递归遍历所有可能的组合(直到一定长度),然后对每个字母进行操作而不存储它们,除非您检测到匹配。

// Depth first combinations
private static void combineRecurse(String base, char[] alphabet, int maxLen,
        Consumer<String> consumer) {
    if (base.length() < maxLen - 1) {
        for (int i = 0; i < alphabet.length; i++) {
            String newPermutation = base + alphabet[i];
            consumer.accept(newPermutation);
            combineRecurse(newPermutation, alphabet, maxLen, consumer);
        }
    } else {
        // the new permutiation will be the maxlength
        for (int i = 0; i < alphabet.length; i++) {
            consumer.accept(base + alphabet[i]);
        }
    }
}

public static void forEachCombination(char[] alphabet, int maxLen, Consumer<String> consumer) {
    if(alphabet == null || consumer == null || maxLen < 1) return;
    combineRecurse("", alphabet, maxLen, consumer);
}

public static void main(String[] args) {

    char[] alphabet = { ... };
    final String hashedPassword = SHA1(password);
    final int maxlen = 16;

    List<String> possiblePasswords = new ArrayList<>();
    forEachCombination(alphabet, maxlen, s -> {
        // System.out.println(s);
        if (SHA1(s).equals(hashedPassword)) {
            possiblePasswords.add(s);
        }
    });
}

Making this iterative rather than recursive might be better, but for now the recursive is easy to implement and is probably more easily understandable for a beginner. 进行这种迭代而不是递归可能会更好,但是目前,递归易于实现,对于初学者来说更容易理解。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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