【问题标题】:Why does first character of a string repeat itself when finding all possible combinations of given string?为什么在查找给定字符串的所有可能组合时,字符串的第一个字符会重复?
【发布时间】:2019-11-26 19:28:27
【问题描述】:

我的问题可能不清楚,但我正在尝试获取所有可能的 10 个字符长度的字符串组合。这是我的代码:

def printPwd(set, k):
    n = len(set)
    pwdRec(set,"", n, k)

def pwdRec(set, prefix, n, k):
    if k==0:
        print(prefix)
        return

    for i in range(n):
        nPrefix = prefix + set[i]
        nk = k-1
        pwdRec(set, nPrefix, n, nk)

chars = list("abcdefghijklmno.@-_&")
length = 10

printPwd(chars, length)

它输出如下内容:

aaaaabcde@
aaaaabcde-
aaaaabcde_
aaaaabcdfg
...

我得到了五个“a”,但我不知道如何解决它

【问题讨论】:

  • 不是您问题的答案,而是另一种选择:import itertools as it; print(list(map("".join, it.permutations("abcdefghijklmno.@-_&", 10))))。注意:可能是一个很长的输出,所以从一个小的输入字符串和一个小的长度开始。
  • 这可能是完全错误的,但我相信问题是当你调用你的递归函数时,你并没有删除你刚刚看到的集合的字符。你应该调用 pwdRec(set[i:], nPrefix, n, nk)
  • 查看这个可爱的debug 博客寻求帮助。在每个函数的顶部放置一个print 语句来回显输入参数;这通常会显示递归错误。
  • @0 0,您的答案给出了组合列表。在我检查它是否是正确的密码之前,它必须搜索所有组合。因此,如果密码在中间,那将花费不必要的时间。 @alex067,它给出了相同的结果。谢谢大家
  • 您可以在不计算列表的情况下迭代排列,然后中途退出(请注意,您的问题是“我正在尝试使 all 成为可能”;您的评论已更改问题)。不同之处在于我评论中的排列不重用元素;如果你愿意,你可以使用itertools.combinations_with_replacement;请参阅 itertools 文档。

标签: python loops combinations brute-force


【解决方案1】:

注意:这个答案给出了正确的输出,但我无法弄清楚它为什么起作用

你应该问的问题不是

“为什么我得了五个'a'?”

但是

“为什么我得不到十个'a'?”

。该算法的预期输出为:

aaaaaaaaaa
aaaaaaaaab
aaaaaaaaac
aaaaaaaaad
aaaaaaaaae
...

但你的开始

aaaaj@bc
aaaaaaj@bd
aaaaaaj@be
aaaaaaj@bf

事实上,正在发生一些非常令人担忧的事情。输出甚至不是确定性的,每次运行它都会得到不同的结果!我实际上还没有弄清楚这一点,所以请评论者提供帮助。如果我在调试中运行它,慢慢点击我会得到正确的输出。我还可以通过在函数中添加一个小睡眠来纠正输出,如下所示:

import time

def printPwd(set, k):
    n = len(set)
    pwdRec(set,"", n, k)

def pwdRec(set, prefix, n, k):
    time.sleep(0.000001)
    if k==0:
        print(prefix)
        return

    for i in range(n):
        nPrefix = prefix + set[i]
        nk = k-1
        pwdRec(set, nPrefix, n, nk) #alex067's change here plus my change


chars = list("abcdefghijklmno.@-_&")
length = 10

printPwd(chars, length)

看起来我们遇到了某种可变类型或非原子操作,但我还没有找到它。

alex067 的评论 进行@alex067 建议的更改似乎首先修复了输出,但在前两个循环之后您会出现超出范围的错误,因为您的集合不再是n 长。您实际上还错过了一些排列,其中第一个是aaaaaaaaba,所以它根本不是固定的,但至少它是确定性的。

【讨论】:

  • @alex067 的建议意味着对于字符串中的任何给定字符,其右侧的字符要么相同,要么出现在有效字符列表中“较晚”的字符。因此,为什么它不产生 aaaaaaaaba 等等。
  • 是的,我没有时间写出来。奇怪的是,仅仅添加切片就可以使输出正常运行。我想它只是减慢速度足以避免问题
  • 是啊,调试好奇怪啊。
【解决方案2】:

我能想到的最佳答案是“为什么不呢?”密码不能包含重复项,但您的代码中没有任何内容强制执行此操作。相反,您的程序只是生成由字符集组成的所有一定长度的字符串。

@SimonN 说你的输出应该以一个 10 'a's 的字符串开头是正确的,它确实如此。当写入文件时,第一个字符串确实是 'aaaaaaaaaa',这让我相信观察到的输出差异纯粹是图形化的,这是由快速连续打印的大量字符串引起的。

@alex067 的建议也没有解决问题,因为它不仅会导致重复的字符,而且它不会在使用后只删除一个字符,而是会删除它之前的所有字符名单。最后一点意味着对于长度为 3 的密码,例如,完全有效的字符串 acbbac 等将被省略。当然,我们可以使用prefix[i+1:] 来代替prefix[i:],但这只会让我们摆脱前者,而不是后者。


如您所知,itertools 提供了可能是最好的解决方案,我将在下面介绍。然而,在此之前,这里有一个我觉得很不错的递归方法:

def pwd_rec(curr_charset: str, curr_pwd: str, pwd_len: int) -> List[str]:
    result_strs = []
    for idx, curr_char in enumerate(curr_charset):
        new_pwd = curr_pwd + curr_char
        if len(new_pwd) == pwd_len:
            result_strs.append(new_pwd)
        else:
            result_strs.extend(pwd_rec(curr_charset[:idx] + curr_charset[idx+1:], new_pwd, pwd_len))
    return result_strs

这是该方法的一个更懒的版本,使用生成器而不是列表:

def pwd_rec(curr_charset: str, curr_pwd: str, pwd_len: int) -> Iterator[str]:
    for idx, curr_char in enumerate(curr_charset):
        new_pwd = curr_pwd + curr_char
        if len(new_pwd) == pwd_len:
            yield new_pwd
        else:
            yield from pwd_rec(curr_charset[:idx] + curr_charset[idx+1:], new_pwd, pwd_len)

正如所承诺的,这里有一个使用itertools的方法:

import itertools as itt

def pwd_perms(charset: str, pwd_len: int) -> Iterator[str]:
    for curr in itt.permutations(charset, pwd_len):
        yield ''.join(curr)

如果您有任何问题,请告诉我:)

【讨论】:

    猜你喜欢
    • 2012-04-20
    • 2015-07-22
    • 2018-06-29
    • 1970-01-01
    • 1970-01-01
    • 2015-03-09
    • 2017-05-26
    • 1970-01-01
    • 2021-04-30
    相关资源
    最近更新 更多