附加到 Python 列出每个列表项的所有可能转换

Append to Python list all possible transformations for each list item

我有一个可能的密码列表,我需要在此列表中附加每个密码的简单转换。说我的名单是

['sauce', 'banana']

这里显示了一系列转换。

'a'-->'@'

's'-->'$'

然后我想将所有可能的转换添加到列表中。所以现在列表应该看起来像

['$auce', 's@uce', '$@uce', 'b@nana', 'ban@na',
 'banan@', 'b@n@na', 'b@nan@,' 'ban@n@', 'b@n@n@']

在 Python 中我该怎么做?

我首先尝试创建一个进行所有转换的函数。然后我把那个转换后的字符串和原始字符串做了一个叉积。但是,这样会造成很多重复,而且看起来有点hacky。

函数:

def symbolize(s):
    options = {
        'a': '@',
        'S': '$'
    }
    copy = ''
    for i in range(len(s)):
        if s[i] in options:
            copy += options[s[i]]
        else:
            copy += s[i]
    return copy

然后是叉积:

for x in range(len(candidates)):
    candidates += list(''.join(t) for t in itertools.product(
        *zip(candidates[x], symbolize(candidates[x]))))

如果您愿意,可以使用递归,尽管 python 将其限制为 2000 的深度:

创建映射和列表:

lst = ['sauce', 'banana']
mapping = {'a':'@', 's':'$'}

现在递归生成所有可能性(包括完全不替换):

def opts(_mapping, _str):
    if not _str:
       yield ""
    else:
        for opt in opts(_mapping, _str[1:]):
            if _str[0] in _mapping:
                yield _mapping[_str[0]] + opt
            yield _str[0] + opt

输出:

[list(opts(mapping, st)) for st in lst]

=> [['$@uce', 's@uce', '$auce', 'sauce'], ['b@n@n@', 'ban@n@', 'b@nan@', 'banan@', 'b@n@na', 'ban@na', 'b@nana', 'banana']]

我真的很高兴深入研究这个答案!这里有很多对字符串的迭代,但我喜欢我的回答!

import functools

def transform(pwd, subs):
    result = {pwd}
    stack = [pwd]
    # contains all resolved strings left to permute on
    while True:
        pwd = stack.pop()
        # grab a password

        if not stack and not any(i in subs for i in pwd):
            return result
            # if the stack is empty and is no way to permute further,
            #  then return our result.

        for idx,ch in enumerate(pwd):
            if ch in subs:
                repl = subs[ch]
                transformation = pwd[:idx]+repl+pwd[idx+1:]
                # transformation is our transformed word
                result.add(transformation)
                # add our transformation to our result set
                stack.append(transformation)
                # and toss it on our stack to transform further

def transform_multiple(pwds, subs):
    return functools.reduce(set.union,
                            (transform(pwd, subs) for pwd in pwds))

演示:

In [55]: transform_multiple(['banana', 'sauce','ananas'], {'a':'@', 's':'$'})
Out[55]:
{'$@uce',
 '$auce',
 '@n@n@$',
 '@n@n@s',
 '@n@na$',
 '@n@nas',
 '@nan@$',
 '@nan@s',
 '@nana$',
 '@nanas',
 'an@n@$',
 'an@n@s',
 'an@na$',
 'an@nas',
 'anan@$',
 'anan@s',
 'anana$',
 'ananas',
 'b@n@n@',
 'b@n@na',
 'b@nan@',
 'b@nana',
 'ban@n@',
 'ban@na',
 'banan@',
 'banana',
 's@uce',
 'sauce'}

如果我要在这上面多花一点时间,我可能会删除 if not stack and not any(...) 调用并在 for idx,ch in enumerate 循环中放置一个标志,如果对其进行了更改则进行标志字符串,然后在循环结束后测试 if not flag and not stack: return result。这将为我们节省每次循环中 pwdlen(pwd) 成员资格测试的整个迭代。

from itertools import product

def all_versions_of_word(word, alt_chars, skip_orig=True):
    chars = [ch + alt_chars.get(ch, "") for ch in word]
    combos = product(*chars)
    if skip_orig and word: next(combos)  # drop the first item
    return ("".join(c) for c in combos)

def transform_passwords(passwords, alt_chars={"a":"@", "s":"$"}):
    for word in passwords:
        yield from all_versions_of_word(word, alt_chars)

运行起来像

>>> list(transform_passwords(['sauce', 'banana']))
['s@uce',
 '$auce',
 '$@uce',
 'banan@',
 'ban@na',
 'ban@n@',
 'b@nana',
 'b@nan@',
 'b@n@na',
 'b@n@n@']