19

If I have string needle and I want to check if it exists contiguously as a substring in haystack, I can use:

if needle in haystack:
    ...

What can I use in the case of a non-continuous subsequence? Example:

>>> haystack = "abcde12345"
>>> needle1 = "ace13"
>>> needle2 = "123abc"
>>> is_subsequence(needle1, haystack)
True
>>> is_subsequence(needle2, haystack)  # order is important!
False
wim
  • 338,267
  • 99
  • 616
  • 750
user4847061
  • 1,223
  • 2
  • 9
  • 8

4 Answers4

19

I don't know if there's builtin function, but it is rather simple to do manually

def exists(a, b):
    """checks if b exists in a as a subsequence"""
    pos = 0
    for ch in a:
        if pos < len(b) and ch == b[pos]:
            pos += 1
    return pos == len(b)
>>> exists("moo", "mo")
True
>>> exists("moo", "oo")
True
>>> exists("moo", "ooo")
False
>>> exists("haystack", "hack")
True
>>> exists("haystack", "hach")
False
>>>
Ishamael
  • 12,583
  • 4
  • 34
  • 52
19

Using an iterator trick:

it = iter(haystack)
all(x in it for x in needle)

This is only a concise version of the same idea presented in tobias_k's answer.

wim
  • 338,267
  • 99
  • 616
  • 750
  • 3
    For anyone else who tries to inline `it`, that is, tries to do it one line like: `all(x in iter(haystack) for x in needle)`, it doesn't work because `iter(haystack)` is re-instantiated each time. – Garrett May 06 '20 at 01:15
  • 1
    This implementation is **in average 1000 times faster** than @Ishamael implementation – pouya Oct 02 '21 at 18:55
  • This should be the best answer - concise and elegant! – Daniel Hao Dec 11 '22 at 04:33
5

Another possibility: You can create iterators for both, needle and haystack, and then pop elements from the haystack-iterator until either all the characters in the needle are found, or the iterator is exhausted.

def is_in(needle, haystack):
    try:
        iterator = iter(haystack)
        for char in needle:
            while next(iterator) != char:
                pass
        return True
    except StopIteration:
        return False
tobias_k
  • 81,265
  • 12
  • 120
  • 179
-1

We can try simple for loop and break method and pass on substring once the match is found

def substr(lstr,sstr):
lenl = len(lstr)
for i in sstr:
    for j in range(lenl):
        if i not in lstr:
            return False
        elif i == lstr[j]:
            lstr = lstr[j+1:]
            break
        else:
            pass
return True   
Shri
  • 177
  • 1
  • 1
  • 9