check if letters of a string are in sequential order in another string

Viewed 560

If it were just checking whether letters in a test_string are also in a control_string,

I would not have had this problem.

I will simply use the code below.

if set(test_string.lower()) <= set(control_string.lower()):
    return True

But I also face a rather convoluted task of discerning whether the overlapping letters in the

control_string are in the same sequential order as those in test_string.

For example,

test_string = 'Dih'
control_string = 'Danish'
True

test_string = 'Tbl'
control_string = 'Bottle'
False

I thought of using the for iterator to compare the indices of the alphabets, but it is quite hard to think of the appropriate algorithm.

for i in test_string.lower():
    for j in control_string.lower():
        if i==j:
            index_factor = control_string.index(j)

My plan is to compare the primary index factor to the next factor, and if primary index factor turns out to be larger than the other, the function returns False.

I am stuck on how to compare those index_factors in a for loop.

How should I approach this problem?

6 Answers

You could just join the characters in your test string to a regular expression, allowing for any other characters .* in between, and then re.search that pattern in the control string.

>>> test, control = "Dih", "Danish"
>>> re.search('.*'.join(test), control) is not None
True
>>> test, control = "Tbl", "Bottle"
>>> re.search('.*'.join(test), control) is not None
False

Without using regular expressions, you can create an iter from the control string and use two nested loops,1) breaking from the inner loop and else returning False until all the characters in test are found in control. It is important to create the iter, even though control is already iterable, so that the inner loop will continue where it last stopped.

def check(test, control):
    it = iter(control)
    for a in test:
        for b in it:
            if a == b:
                break
        else:
            return False
    return True

You could even do this in one (well, two) lines using all and any:

def check(test, control):
    it = iter(control)
    return all(any(a == b for b in it) for a in test)

Complexity for both approaches should be O(n), with n being the max number of characters.

1) This is conceptually similar to what @jpp does, but IMHO a bit clearer.

Here's one solution. The idea is to iterate through the control string first and yield a value if it matches the next test character. If the total number of matches equals the length of test, then your condition is satisfied.

def yield_in_order(x, y):
    iterstr = iter(x)
    current = next(iterstr)
    for i in y:
        if i == current:
            yield i
            current = next(iterstr)

def checker(test, control):
    x = test.lower()
    return sum(1 for _ in zip(x, yield_in_order(x, control.lower()))) == len(x)

test1, control1 = 'Tbl', 'Bottle'
test2, control2 = 'Dih', 'Danish'

print(checker(test1, control1))  # False
print(checker(test2, control2))  # True

@tobias_k's answer has cleaner version of this. If you want some additional information, e.g. how many letters align before there's a break found, you can trivially adjust the checker function to return sum(1 for _ in zip(x, yield_in_order(...))).

Recursion is the best way to solve such problems. Here's one that checks for sequential ordering.

def sequentialOrder(test_string, control_string, len1, len2): 

    if len1 == 0:     # base case 1
        return True

    if len2 == 0:     # base case 2
        return False

    if test_string[len1 - 1] == control_string[len2 - 1]: 
        return sequentialOrder(test_string, control_string, len1 - 1, len2 - 1)  # Recursion 

    return sequentialOrder(test_string, control_string, len1, len2-1)

test_string = 'Dih'
control_string = 'Danish'

print(isSubSequence(test_string, control_string, len(test_string), len(control_string)))

Outputs:

True

and False for

test_string = 'Tbl'
control_string = 'Bottle'

Here's an Iterative approach that does the same thing,

def sequentialOrder(test_string,control_string,len1,len2): 

    i = 0
    j = 0

    while j < len1 and i < len2: 
        if test_string[j] == control_string[i]:     
            j = j + 1    
        i = i + 1

    return j==len1 

test_string = 'Dih'
control_string = 'Danish'

print(sequentialOrder(test_string,control_string,len(test_string) ,len(control_string)))

A simple way is making use of the key argument in sorted, which serves as a key for the sort comparison:

def seq_order(l1, l2):
    intersection = ''.join(sorted(set(l1) & set(l2), key = l2.index))
    return True if intersection == l1 else False

Thus this is computing the intersection of the two sets and sorting it according to the longer string. Having done so you only need to compare the result with the shorter string to see if they are the same.

The function returns True or False accordingly. Using your examples:

seq_order('Dih', 'Danish')
#True

seq_order('Tbl', 'Bottle')
#False

seq_order('alp','apple')
#False

An elegant solution using a generator:

def foo(test_string, control_string):
    if all(c in control_string for c in test_string):
        gen = (char for char in control_string if char in test_string)
        if all(x == test_string[i] for i, x in enumerate(gen)):
            return True
    return False

print(foo('Dzn','Dahis')) # False
print(foo('Dsi','Dahis')) # False
print(foo('Dis','Dahis')) # True

First check if all the letters in the test_string are contained in the control_string. Then check if the order is similar to the test_string order.

You can use find(letter, last_index) to find occurence of desired letter after processed letters.

def same_order_in(test, control):
    index = 0
    control = control.lower()
    for i in test.lower():
        index = control.find(i, index)
        if index == -1:
            return False
        # index += 1 # uncomment to check multiple occurrences of same letter in test string  
    return True

If test string have duplicate letters like:

test_string = 'Diih'
control_string = 'Danish'

With commented line same_order_in(test_string, control_string) == True

and with uncommented line same_order_in(test_string, control_string) == False

Related