12

I'm working in Python, using any() like so to look for a match between a String[] array and a comment pulled from Reddit's API.

Currently, I'm doing it like this:

isMatch = any(string in comment.body for string in myStringArray)  

But it would also be useful to not just know if isMatch is true, but which element of myStringArray it was that had a match. Is there a way to do this with my current approach, or do I have to find a different way to search for a match?

ayhan
  • 70,170
  • 20
  • 182
  • 203
Danny B
  • 121
  • 1
  • 4

5 Answers5

14

You could use next with a default of False on a conditional generator expression:

next((string for string in myStringArray if string in comment.body), False)

The default is returned when there is no item that matched (so it's like any returning False), otherwise the first matching item is returned.

This is roughly equivalent to:

isMatch = False  # variable to store the result
for string in myStringArray:
    if string in comment.body:
        isMatch = string
        break  # after the first occurrence stop the for-loop.

or if you want to have isMatch and whatMatched in different variables:

isMatch = False  # variable to store the any result
whatMatched = '' # variable to store the first match
for string in myStringArray:
    if string in comment.body:
        isMatch = True
        whatMatched = string
        break  # after the first occurrence stop the for-loop.
Asclepius
  • 57,944
  • 17
  • 167
  • 143
MSeifert
  • 145,886
  • 38
  • 333
  • 352
  • Is it really a good idea to store *either* a bool or the matched string in the same variable? This seems to be taking dynamic types way too far. – brianpck Jan 18 '17 at 20:04
  • 1
    `any` can be understood immediately just by looking at it. A `for` loop wouldn't be much worse. I've been looking at this for a minute and still can't convince myself that it works; that makes it a bad solution. – Mark Ransom Jan 18 '17 at 20:07
  • @MarkRansom I've included a version without `next` that should be equivalent. Just in case it helps to understand what's happening. :) – MSeifert Jan 18 '17 at 23:19
  • 2
    in Python 3.8, next takes no keyword arguments, but default is a positional argument instead. So `next((shizzle for shizzle in english if shizzle in websters), False)` – Kardo Paska Aug 26 '20 at 22:32
  • 1
    @brianpck i think this first one-line solution is fine, as long as you replace the `False` with `None`. `None` is a sensible 'no value found' response, which can then be used via `if match is None: ...` – starwarswii Sep 08 '21 at 20:46
9

For python 3.8 or newer use Assignment Expressions

if any((match := candidate) in comment.body for candidate in candidates):
    print(match)
Asclepius
  • 57,944
  • 17
  • 167
  • 143
airborne
  • 3,664
  • 4
  • 15
  • 27
2

I agree with the comment that an explicit loop would be clearest. You could fudge your original like so:

isMatch = any(string in comment.body and remember(string) for string in myStringArray)
                                    ^^^^^^^^^^^^^^^^^^^^^

where:

def remember(x):
    global memory
    memory = x
    return True

Then the global memory will contain the matched string if isMatch is True, or retain whatever value (if any) it originally had if isMatch is False.

Tim Peters
  • 67,464
  • 13
  • 126
  • 132
2

It's not a good idea to use one variable to store two different kinds of information: whether a string matches (a bool) and what that string is (a string).

You really only need the second piece of information: while there are creative ways to do this in one statement, as in the above answer, it really makes sense to use a for loop:

match = ''
for string in myStringArray:
    if string in comment.body:
        match = string
        break

if match:
    pass # do stuff
brianpck
  • 8,084
  • 1
  • 22
  • 33
2

Say you have a = ['a','b','c','d'] and b = ['x','y','d','z'],

so that by doing any(i in b for i in a) you get True.

You can get:

  • The array of matches : matches = list( (i in b for i in a) )
  • Where in a it first matches : posInA = matches.index(True)
  • The value : value = a[posInA]
  • Where in b it first matches : posInB = b.index(value)

To get all the values and their indexes, the problem is that matches == [False, False, True, True] whether the multiple values are in a or b, so you need to use enumerate in loops (or in a list comprehension).

for m,i in enumerate(a):
    print('considering '+i+' at pos '+str(m)+' in a')
    for n,j in enumerate(b):
        print('against '+j+' at pos '+str(n)+' in b')
        if i == j:
            print('in a: '+i+' at pos '+str(m)+', in b: '+j+' at pos '+str(n))
n49o7
  • 476
  • 7
  • 8