2

I need to determine all possible letter combinations of a string that has numbers when converting numbers into possible visually similar letters.

Using the dictionary:

number_appearance = {
       '1': ['l', 'i'],
       '2': ['r', 'z'],
       '3': ['e', 'b'],
       '4': ['a'],
       '5': ['s'],
       '6': ['b', 'g'] ,
       '7': ['t'],
       '8': ['b'], 
       '9': ['g', 'p'],
       '0': ['o', 'q']} 

I want to write a function that takes an input and creates all possible letter combinations. For example:

text = 'l4t32'

def convert_numbers(text):
      return re.sub('[0-9]', lambda x: number_appearance[x[0]][0], text)

I want the output to be a list with all possible permutations:

['later', 'latbr', 'latbz', 'latez]

The function above works if you are just grabbing the first letter in each list from number_appearance, but I'm trying to figure out the best way to iterate through all possible combinations. Any help would be much appreciated!

Alison LT
  • 395
  • 1
  • 3
  • 14
  • 1
    Does this answer your question? [Get the cartesian product of a series of lists?](https://stackoverflow.com/questions/533905/get-the-cartesian-product-of-a-series-of-lists) – Karl Knechtel Aug 10 '21 at 21:47
  • I notice that in the list of all possible permutations you replace the `r` with `z`, but you do not replace the `l` with `i`. Why is that? – MikeM Aug 10 '21 at 21:57
  • I am only replacing numbers with letters that appear like them. The `l` isn't replaced because it is already a letter. If I had used the number `1` instead of the letter `l` than I would want a version that used both `l` and `i`. – Alison LT Aug 10 '21 at 22:03
  • But the `r` is already a letter also! I think you need to remove `'latbz'` and `'latez'` from the list of permutations. – MikeM Aug 10 '21 at 22:04
  • Oops - that is a mistake on my part - the `r` should have been a `2` in the example :) – Alison LT Aug 10 '21 at 22:16
  • @KarlKnechtel - I was able to put together a solution based on your reference above. Thanks! – Alison LT Aug 10 '21 at 22:17
  • As an aside, you will probably find it simpler and more efficient to use [the `translate` method of strings](https://docs.python.org/3.8/library/stdtypes.html#str.translate) to do the multiple replacement logic, rather than that regex-based approach. I will add an answer in order to show this technique. – Karl Knechtel Aug 10 '21 at 22:21
  • @KarlKnechtel thanks - it seems i didn't `re.sub` when using the Cartesian product way... if you have other suggestions to make what I posed below more efficient or elegant, lmk! – Alison LT Aug 10 '21 at 22:36

3 Answers3

2

As an upgrade from your own answer, I suggest the following:

    def convert_numbers(text):
        all_items = [number_appearance.get(char, [char]) for char in text]
        return [''.join(elem) for elem in itertools.product(*all_items)]

The improvements are that:

  • it doesn't convert text to a list (there is no need for that)
  • you don't need regex
  • it will still work if you decide instead that you also want to add other characters on top of numbers
Daniel Hao
  • 4,922
  • 3
  • 10
  • 23
Anne Aunyme
  • 506
  • 4
  • 14
0
def convert_num_appearance(text):
    string_characters = [character for character in text]
    
    all_items = []

    for item in string_characters:
        if re.search('[a-zA-Z]', item):
           all_items.append([item])
        elif re.search('\d', item):
           all_items.append(number_appearance[item])

    return [''.join(elem) for elem in itertools.product(*all_items)]
Alison LT
  • 395
  • 1
  • 3
  • 14
0

I would break down the problem like so:

  1. First, create a function that can do the replacement for a given set of replacement letters. My input specification is a sequence of letters, where the first letter is the replacement for the '0' character, next for 1 etc. This allows me to use the index in that sequence to determine the character being replaced, while generating a plain sequence rather than a dict or other complex structure. To do the replacement, I will use the built-in translate method of the original string. That requires a dictionary as described in the documentation, which I can easily build with a dict comprehension, or with the provided helper method str.maketrans (a static method of the str type).

  2. Use itertools.product to generate those sequences.

  3. Use a list comprehension to apply the replacement for each sequence.

Thus:

from itertools import product

def replace_digits(original, replacement):
    # translation = {ord(str(i)): c for i, c in enumerate(replacement)}
    translation = str.maketrans('0123456789', ''.join(replacement))
    print(translation)
    return original.translate(translation)

replacements = product(
   ['o', 'q'], ['l', 'i'], ['r', 'z'], ['e', 'b'], ['a'],
   ['s'], ['b', 'g'] , ['t'], ['b'], ['g', 'p']
)

[replace_digits('14732', r) for r in replacements]

(You will notice there are duplicates in the result; this is because of variant replacements for symbols that don't appear in the input.)

Karl Knechtel
  • 62,466
  • 11
  • 102
  • 153