Trying to get the last occurrence of a substring in string using recursion in python

Question:

I’m trying to get the length of the longest
substring of message that begins and ends with the string subString

This is the table with what the results should look like:
Table with examples and outputs

This is the code I got so far:

dist = 0
def strDist(message, subString):
    global dist
    if message.find(subString) == -1:
        return dist
    else:
        dist += message.find(subString) + len(subString)
    return strDist(dist*' ' + message[dist:], subString)

and this is the Tester code:

from recursion import *
allPassed = True

def strDistMain():
    global allPassed    
    testCases = [(1, 'cat dog cat dog cat', 'cat', 19),
                 (2, 'cat dog cat dog ca', 'cat', 11),
                 (3, 'at dog cat dog cat', 'cat', 11),
                 (4, 'dog cat dog', 'cat', 3),
                 (5, 'dog dog', 'cat', 0),
                 (6, 'cat', 'cat', 3),
                 (7, 'ca', 'cat', 0),
                 (8, 'c', 'cat', 0),
                 (9, '', 'cat', 0),
                 (10, 'cat', '', 3),
                 (11, '', '', 0)
                 ]
    
    for num, message, mark, expected in testCases:
        result = strDist(message, mark)
        if result != expected:
            print(f'String Distance Test {num} Failed. Expected {expected} got {result}')
            allPassed = False

def main():     
    strDistMain()
    if allPassed:
        print('All tests passed')

    
main()  

The code doesn’t work at all and I’m not sure what’s the problem.
This is the output I’m getting so far:

String Distance Test 1 Failed. Expected 19 got 33
String Distance Test 2 Failed. Expected 11 got 36
String Distance Test 3 Failed. Expected 11 got 46
String Distance Test 4 Failed. Expected 3 got 53
String Distance Test 5 Failed. Expected 0 got 53
String Distance Test 6 Failed. Expected 3 got 56
String Distance Test 7 Failed. Expected 0 got 56
String Distance Test 8 Failed. Expected 0 got 56
String Distance Test 9 Failed. Expected 0 got 56
builtins.RecursionError: maximum recursion depth exceeded while calling a Python object
Asked By: Yusuf Halim

||

Answers:

First of all, you don’t need to use recursion, you can just do:

def strDist(message, mark):
    if message.find(mark) == -1:
        return 0
    return message.rfind(mark) - message.find(mark) + len(mark)

But, if you want to do it with recursion for some reason, you can just do:

def strDist(message, mark):
    index = message.find(mark)
    if index == -1:
        return 0
    return recursive_last(message, mark) - index + len(mark)

def recursive_last(message, mark):
    index = message.find(mark)
    if index == -1:
        return -1
    return max(index, recursive_last(message[index + len(mark):], mark) + index)
    

The first function gets the index of the first occurency, and calls the recursive, that finds the first ocurrency, removes it from the string, and try again.

Answered By: Rodrigo Llanes

It’s not obvious that your strDist function ever converges toward a base case. Using global in a recursive function almost always makes it harder to debug and reason about; odds are good that dist is winding up with a value you don’t expect at some point, and that’s what’s leading to infinite recursion.

Using find (which is an iterative function) in your recursive function sort of defeats the purpose anyway, since as pointed out, if you’re going to use find to find the leftmost occurrence, you could just also use rfind to find the rightmost occurence, and then you’re done without having to do any recursion at all.

A purely recursive (also inefficient) solution would look more like:

def str_dist(message, substr):
    if len(message) < len(substr):
        return -1
    if message.startswith(substr) and message.endswith(substr):
        return len(message)
    return max(str_dist(message[1:], substr), str_dist(message[:-1], substr))

The above function is purely recursive in that it doesn’t do any iteration over message within a given function call (note that startswith and endswith are both iterative over substr; you could write recursive versions of those as well if you really wanted to) and it passes all of your tests.

It has two base cases:

  • There’s no valid answer if message is shorter than substr. (Some form of "too small" base case is needed to prevent infinite recursion on empty strings — you could make the empty string the base case if you wanted to make this check simpler.)
  • The answer is len(message) if message itself meets the condition of starting and ending with substr.

Otherwise, it’s the best answer from either message[1:] or message[:-1].

Answered By: Samwise
def f(message,mark,i=0):
    if message.count(mark) == 1:
        return len(mark)
    elif not message.count(mark):
        return 0
    j = message.find(mark,i)
    if j<0:
        return i+len(mark)-1
    elif not i:
        message = message[j:]
        j = 1

    return f(message,mark,i=j+1)
print(f('cat dog cat dog cat dog','cat'))
print(f('cat dog cat dog ca','cat'))
print(f('at dog cat dog cat','cat'))

print(f('dog cat dog','cat'))

print(f('dog dog','cat'))
print(f('ca','cat'))
print(f('cat',''))
Answered By: islam abdelmoumen
Categories: questions Tags: , ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.