2
\$\begingroup\$

I wrote the following Python program to take 1 word from the user and return certain characters replaced by numbers. Given a dictionary, the program should print all possible combinations of the same word with various characters substituted:

import sys

def main(argv):

    if len(argv) != 2:
        print(f"USAGE: {argv[0]} [word]")
        return

    replacements = {}

    # Populate the known replacements
    replacements['i'] = ['1']
    replacements['e'] = ['3']
    replacements['m'] = ['/v\\']
    replacements['a'] = ['4']
    replacements['r'] = ['2']
    replacements['o'] = ['0']

    print_possibilities(argv[1], replacements, [])


def print_possibilities(s: str, replacements: dict, dupes: list):
    ctr = 0
    tally = 0
    for c in s:
        if c.lower() in replacements:
            tally += 1
    if tally == 0:
        return

    for c in s:
        if c.lower() in replacements:
            for r in replacements[c.lower()]:
                tmp = list(s)
                tmp[ctr] = r
                as_str = ''.join(tmp)
                if as_str in dupes:
                    continue
                print(as_str)
                dupes.append(as_str)
                print_possibilities(as_str, replacements, dupes)
        ctr += 1
    return


if __name__ == '__main__':
    main(sys.argv)

I've tested with python3 replace_word.py mondoman and I get this output:

/v\ondoman
/v\0ndoman
/v\0nd0man
/v\0nd0/v\an
/v\0nd0/v\4n
/v\0nd0m4n
/v\0ndo/v\an
/v\0ndo/v\4n
/v\0ndom4n
/v\ond0man
/v\ond0/v\an
/v\ond0/v\4n
/v\ond0m4n
/v\ondo/v\an
/v\ondo/v\4n
/v\ondom4n
m0ndoman
m0nd0man
m0nd0/v\an
m0nd0/v\4n
m0nd0m4n
m0ndo/v\an
m0ndo/v\4n
m0ndom4n
mond0man
mond0/v\an
mond0/v\4n
mond0m4n
mondo/v\an
mondo/v\4n
mondom4n

I feel that this could be improved to be more performant and more idiomatic and would like a code review.

\$\endgroup\$

1 Answer 1

4
\$\begingroup\$

Think about the problem as a repeated Cartesian product. It allows us to use the very useful product function from the itertools module:

>>> list(product('ab', 'AB', 'xy'))
[('a', 'A', 'x'), ('a', 'A', 'y'), ('a', 'B', 'x'),
('a', 'B', 'y'), ('b', 'A', 'x'), ('b', 'A', 'y'),
('b', 'B', 'x'), ('b', 'B', 'y')]

So your problem can be solved by replacing each character in the string with its possible replacemnets and running product on the result:

import itertools
import sys

def alternatives(ch, repls):
    key = ch.lower()
    if key in repls:
        return [ch, repls[key]]
    return [ch]

def main(argv):
    if len(argv) != 2:
        print(f"USAGE: {argv[0]} [word]")
        return

    # Populate the known replacements
    replacements = {'a' : '4', 'e' : '3', 'i' : '1',
                    'm' : '/v\\', 'o' : '0', 'r' : '2'}

    s = [alternatives(ch, replacements) for ch in argv[1]]
    for it in itertools.product(*s):
        print(''.join(it))

if __name__ == '__main__':
    main(sys.argv)

Note that this version is not exactly identical to your original. If you enter "mondoman" the program will output "mondoman" as one combination.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ I was just looking at this question thinking "oo, this looks like a good place for itertools" but you beat me to the answer. :D \$\endgroup\$
    – Samwise
    Dec 24, 2019 at 15:13

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.