简体   繁体   中英

Append to Python list all possible transformations for each list item

I have a list of possible passwords, and I need to append simple transformations of each password in this list. Say my list is

['sauce', 'banana']

and I have a series of transformations shown here.

'a'-->'@'

's'-->'$'

I then want to add to the list every possible transformation. So now the list should look something like

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

How would I do that in Python?

I tried first creating a function that made all transformations. Then I took that transformed string and essentially did a cross product with the original string. However, this causes a lot of repeats, and it seems a bit hacky.

The function:

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

And then the cross product:

for x in range(len(candidates)):
    candidates += list(''.join(t) for t in itertools.product(
        *zip(candidates[x], symbolize(candidates[x]))))
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)

which runs like

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

If you want you can use recursion, although pytohn limits it to a depth of 2000:

Create a mapping and the list:

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

Now recursively generate all possibilities (including no replacement at all):

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

Output:

[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']]

I really had fun digging into this answer! There's a lot of iterating over the string here, but I like my answer!

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))

DEMO:

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 I were to spend a little more time on this, I'd probably remove the if not stack and not any(...) call and put a flag inside the for idx,ch in enumerate loop that flags if there's a change made to that string, then test if not flag and not stack: return result after the loop ends. This would save us a whole iteration on pwd and len(pwd) membership tests every time through the loop.

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