-4

I wrote a function searching for a pattern in a string, but I want it to be able to read "?" as any letter (a-z) so that, for example, the pattern "?ab?c" would be found in "xabyc". I haven't been able to find an answer, but it's possible that I'm just searching the wrong thing. I'd like to be able to do this just by defining ? as any letter a-z, without using regular expressions or anything like that. Here's my original code:

def main():
    string=input("String")
    pattern=input("Pattern")
    m = match(string, pattern)
    if m==True:
        print ("A perfect match!")
    elif m==False:
        print ("Better try again...")
def match(string, pattern):
    index=0
    while index<=string:
        if string[index:len(pattern)]==pattern:
            return True
        index = index +1
    return False

edit: realized that the whole code is wrong, changed the last part of the code, it still doesn't work and i still have no idea how to make the "?" work.

def match(string, pattern):
    index= string[0]
    if len(pattern)<=len(string):
        while index<=len(string):   
            if string[index:len(pattern)]==pattern:
                return True
            else:
                index = index +1
        return False
  • You don't even have a `?` in your code... how do you think you're able to match `?` against something else? Are you asking us how to adapt your code so that it can treat `?` as a wildcard? – univerio Oct 04 '14 at 23:02
  • 2
    why not use regulars expression (`re` module)? – m.wasowski Oct 04 '14 at 23:02
  • I want to know if there's any way in python to say "?" is just any letter, or if there's a way to adapt my code to say "if one of the characters in the pattern is a ?, just treat it like it matches" without using regular expression. – galacticcannibalism Oct 04 '14 at 23:09
  • `if "a" in my_string` will check *if one of the characters in the pattern is a* – Padraic Cunningham Oct 04 '14 at 23:12
  • can i just put in a line like `?=['a','b','c','d','e','f','g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v','w','x','y','z']` – galacticcannibalism Oct 04 '14 at 23:14
  • No, you can't. Why again do you want to avoid regular expressions? And what's wrong with @Yoel's answer? – Lukas Graf Oct 04 '14 at 23:23

1 Answers1

1

If you want to avoid regular expressions altogether and need to support only that single case illustrated in your post, then this should work:

def match(string, pattern):
    if len(string) != len(pattern):
        return False
    for s, p in zip(string, pattern):
        if s != p and p != '?':
            return False
    return True

Execution example:

>>> match('ABC', 'ABC')
True
>>> match('ABC', 'AbC')
False
>>> match('ABC', 'A?C')
True

Explanation:

  1. You may iterate over a string, with each iteration yielding another character:

    >>> [i for i in 'ABC']
    ['A', 'B', 'C']
    
  2. zip allows you to iterate over both lists together:

    >>> [(s, p) for (s, p) in zip('STRING1', 'string2')]
    [('S', 's'), ('T', 't'), ('R', 'r'), ('I', 'i'), ('N', 'n'), ('G', 'g'), ('1', '2')]
    

A few remarks regarding your code snippet:

  1. There is no need to convert the input strings to str since they are guaranteed to be of that type.
  2. Check for True and False values using the is operator. As @PadraicCunningham commented, just checking if m (without == True or is True) would probably suffice in this case, since the value returned is certainly either True or False, but I prefer being explicit nonetheless. See this for more details.
  3. Your while loop either runs forever or never. Your test of pattern <= string does not change throughout the loop and compares them lexicographically.
  4. When using slicing to access just part of an iterable, there is no need to explicitly mention it's beginning if it starts from 0, e.g. string[0:len(pattern)] is equivalent to string[:len(pattern)].
  5. You have incremented the index variable without initializing it first, and you don't use it anywhere else.
  6. The previous comment is no longer relevant, since you've updated your code, but your new statement doesn't make any sense either. Python doesn't have built-in support for adding an int to a str, since it's not well-defined. Executing that line of code will throw the following exception:

    >>> 'ABC'+1
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: cannot concatenate 'str' and 'int' objects
    

    Furthermore, even if it would have returned some value, you can't just set it into an str object since str objects are immutable (in the following example -1 denotes the length of the iterable minus 1):

    >>> x = 'ABCDE'
    >>> x[1:-1] = '123'
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: 'str' object does not support item assignment
    

    With lists, it would work:

    >>> x = ['A', 'B', 'C', 'D', 'E']
    >>> x[1:-1] = [1, 2, 3, 4, 5]
    >>> x
    ['A', 1, 2, 3, 4, 5, 'E']
    
  7. The previous comment is no longer relevant since you've updated your code again, but that update is flawed as well. Now you're comparing an int against a str in the test clause of your while loop. In Python 3, this will always raise an exception (though, not in Python 2):

    >>> 'ABC' > 0
    Traceback (most recent call last):
      File "<stdin>", line 1, in <module>
    TypeError: unorderable types: str() > int()
    

EDIT:

Following your comment on the actual desired behavior, here is an updated solution. In a nutshell, it's similar to the previous one, but it uses a modified version of the match function that expects both inputs to be of the exact same length. match_all extracts such substrings from the original string variable and invokes match with each of these substrings along with the original pattern variable:

def match_all(string, pattern):
    if len(string) < len(pattern):
        return False
    for index in range(0, len(string) - len(pattern) + 1):
        if match(string[index:index + len(pattern)], pattern) is True:
            return True
    return False

def match(string, pattern):
    for s, p in zip(string, pattern):
        if s != p and p != '?':
            return False
    return True

Execution example:

>>> match_all('ABC', '????')
False
>>> match_all('ABC', '???')
True
>>> match_all('ABC', 'AB')
True
>>> match_all('ABC', 'BC')
True
>>> match_all('ABC', 'Ab')
False
>>> match_all('ABC', 'A?')
True
Community
  • 1
  • 1
Yoel
  • 9,144
  • 7
  • 42
  • 57
  • you can just use `if m`, no need for `is` – Padraic Cunningham Oct 04 '14 at 23:44
  • thank you so much for trying to help with my very poor python skills, i've changed the code again but it still doesn't work – galacticcannibalism Oct 04 '14 at 23:46
  • 1
    @PadraicCunningham, Thanks for your comment but I prefer using `is` since I find it more explicit and due to errors I've encountered in the past (i.e. I don't want it to succeed if anything that is evaluated to `False` is returned), though it might be unnecessary in this example. – Yoel Oct 04 '14 at 23:56
  • I imagine the function returns `True` or `False` based on the `if m == True elif m == False` so it is safe enough and pythonic to use `if m` – Padraic Cunningham Oct 04 '14 at 23:59
  • what i was trying to do with index=index+1 is make it move on to the next letter in the string (ex. from "**c**at" to "c**a**t"). what would be the right syntax for that? – galacticcannibalism Oct 05 '14 at 00:10
  • 1
    @AvaTess, I suggest you go over my solution and see whether you understand it. Also, try looking for some good Python tutorials and invest some time in them. It would be much more efficient. – Yoel Oct 05 '14 at 00:16
  • i've checked three textbooks, several websites, and multiple forums for help. i'm afraid i just don't understand. your answer solution seems to only return true if the pattern exactly matches the input, while i need it to return true if the pattern can be found anywhere in the input. – galacticcannibalism Oct 05 '14 at 00:19
  • i'm sorry i'm just frustrated and confused, and every source i consult just seems to be confusing me more – galacticcannibalism Oct 05 '14 at 00:20
  • @PadraicCunningham, I've updated my answer to reflect your comment, though I still believe in being explicit, lol :-) Thanks! – Yoel Oct 05 '14 at 00:20
  • @AvaTess, that's not what I've understood when I read your question. I've updated my answer. Sorry for the confusion. – Yoel Oct 05 '14 at 00:40
  • You're welcome, but I strongly suggest you improve your Python skills by looking for some basic tutorials online, with a genuine intent to learn Python and not just solve a particular question. You can start [here](http://stackoverflow.com/q/70577/3903832). – Yoel Oct 05 '14 at 00:46
  • @galacticcannibalism, if this answer proved beneficial, please consider [accepting](http://meta.stackexchange.com/q/5234) it by clicking the check-mark that is to the left of the answer. Note that there is absolutely no obligation to do this. – Yoel Oct 06 '14 at 21:18