function call the convert a list is alpha characters to numeric

Viewed 58

I am trying a manual implementation of the Soundex Algorithm and this requires converting alpha text characters to numeric text characters. I have defined the following function:

import re

def sub_pattern(text):
    sub = [str(i) for i in range(1,4)]
    string = text

    abc = re.compile('[abc]')
    xyz = re.compile('[xyz]')

    encode = [abc, xyz]
    encode_iter = iter(encode)

    alpha_search = re.compile('[a-zA-Z]')

    for i in sub:
        if alpha_search.search(string):
            pattern = next(encode_iter)
            string = pattern.sub(i, string)
        else:
            return(string)

This function will encode abc characters to 1 and xyz characters to 2. However, it only works for a single string and I need to pass a list of strings to the function. I've gotten the results I want using:

list(map(sub_pattern, ['aab', 'axy', 'bzz']

But I want to be able to pass the list to the function directly. I've tried this with no success as it ends only returning the first string from the list.

def sub_pattern(text_list):
    all_encoded = []
    sub = [str(i) for i in range(1,4)]

    abc = re.compile('[abc]')
    xyz = re.compile('[xyz]')

    encode = [abc, xyz]
    encode_iter = iter(encode)

    alpha_search = re.compile('[a-zA-Z]')

    for string in text_list:
        for i in sub:
            if alpha_search.search(string):
                pattern = next(encode_iter)
                string = pattern.sub(i, string)
            else:
                all_encoded.append(string)

A couple things to note:

  1. Because I am implementing the Soundex Algorithm, the order of the text when I encode it matters. I would prefer to update the string character at its orginal index to avoid having to reorganize it afterwards. In other words, you can't do any sorting to the string...I've created the iterator to incrementally update the string and it only grabs the next regex pattern if all the characters have not already been converted.
  2. This function will be a part of two custom classes that I am creating. Both will call the __iter__ method so that I can created the iterable. That's why I use the iter() function to create an iterable because it will create a new instance if the iterator automatically.

I know this may seem like a trivial issue relative to what I'm doing, but I'm stuck.

Thank you in advance.

2 Answers

How about using your own function recursively? You get to keep the original exactly as it is, in case you needed it:

import re

def sub_pattern(text):
    if isinstance(text, str):
        sub = [str(i) for i in range(1,4)]
        string = text

        abc = re.compile('[abc]')
        xyz = re.compile('[xyz]')

        encode = [abc, xyz]
        encode_iter = iter(encode)

        alpha_search = re.compile('[a-zA-Z]')

        for i in sub:
            if alpha_search.search(string):
                pattern = next(encode_iter)
                string = pattern.sub(i, string)
            else:
                return(string)
    else:
        return([sub_pattern(t) for t in text])


print(list(map(sub_pattern, ['aab', 'axy', 'bzz']))) # old version still works
print(sub_pattern(['aab', 'axy', 'bzz'])) # new version yields the same result

Should a reader don't know what recursively means: calling a function from within itself.

  • It is allowed because each function call creates its own scope,
  • it can be useful when you can solve a problem by performing a simple operation multiple times, or can't predict in advance how many times you need to perform it to reach your solution, e.g. when you need to unpack nested structures
  • it is defined by choosing a base case (the solution), and call the function in all other cases until you reach your base case.

I assume the issue with your example was, that once you traversed the iterator, you ran into StopIteration for the next string.

I'm not sure this is what you want, but I would create a new iterator for each string, since you have to be able to traverse over all of it for every new item. I tweaked some variable names that may cause confusion, too (string and sub). See comments for changes:

def sub_pattern(text_list):
    all_encoded = []
    digits = [str(i) for i in range(1,4)]

    abc = re.compile('[abc]')
    xyz = re.compile('[xyz]')

    encode = [abc, xyz]
    alpha_search = re.compile('[a-zA-Z]')

    for item in text_list:
        # Create new iterator for each string.
        encode_iter = iter(encode)
        for i in digits:
            if alpha_search.search(item):
                pattern = next(encode_iter)
                item = pattern.sub(i, item)
            else:
                all_encoded.append(item)
                # You likely want appending to end once no more letters can be found.
                break
    # Return encoded texts.
    return all_encoded

Test:

print(sub_pattern(['aab', 'axy', 'bzz'])) # Output: ['111', '122', '122']
Related