1

I know how to test multiple boolean conditions (as below), but am wondering if I can combine testing multiple boolean conditions by grouping variables together, as below? Is there a way to shorten the working, but complex, compound boolean condition (version 1) in a comprehensible manner?

(In my real code, the actual test strings would vary at runtime, and would be variables).

Example:

AnimalString = 'We were looking for a mouse in our house.'
s = AnimalString

# 1) WORKS if-statement, but very long.
if 'dolphin' in s or 'mouse' in s or 'cow' in s:
    print('1: At least one listed animal is in the AnimalString.')

# 2) DOESN'T WORK Desired complex compound boolean expression
if ('dolphin' or 'mouse' or 'cow') in s:
    print('2: At least one listed animal is in the AnimalString.')

# 3) DOESN'T WORK Desired complex compound boolean expression
if 'dolphin' or 'mouse' or 'cow' in s:
    print('3: At least one listed animal is in the AnimalString.')

I know why versions 2),3) don't work based on the SO posts - here and here:

  • version 2) returns a boolean expression result as True or True or False, which falsely prints the message.
  • version 3) tests only for the string 'dolphin' in the AnimalString and gives no output.

Additional question: Any idea, maybe, if this can be solved in an even cleaner manner? Without any(), just tuples/lists and operators...

Accepted solution: Going through comments, answers and documentation this seems to be the best solution:
any(item in AnimalString for item in ['dolphin', 'mouse', 'cow'])

Mike
  • 191
  • 7
  • Did you try with the negative expression : `('dolphin' or 'mouse' or 'cow') in AnimalString` becomes `not('dolphin' and 'mouse' and 'cow') in AnimalString` – Clément Jan 08 '20 at 09:00
  • Did you try them all, because the only one that seemed not to work was the second one, the other two worked – maestro.inc Jan 08 '20 at 09:02
  • @maestro.inc the third one does not work properly either. Only the first one is working as it should. – Mike Jan 08 '20 at 09:11
  • For fun, examine what `'dolphin' or 'mouse' or 'cow'` evaluates to. – tripleee Jan 08 '20 at 10:02
  • @tripleee can you, please, re-open this question, because [Comparing a string to multiple items in Python](https://stackoverflow.com/q/6838238/9606141) does not answer my question. I am trying to shorten a boolean expression (second if-statement in my code example) that is checking if any of several sub-strings is present in the main string. Answer to your question: `'dolphin'` as stated in the question. – Mike Jan 08 '20 at 10:17
  • 2
    I don't think this should be reopened. We have on the order of a cubic parsec of similar questions. I added a second duplicate which addresses that part of your question. I guess you are looking for `any(x in AnimalString for x in ('dolphin', 'mouse', 'cow'))` – tripleee Jan 08 '20 at 10:23
  • @tripleee Indeed, your last comment is a good answer to my question. Thank you. I was searching through the SO site, but could not find the right thing. Any idea, maybe, if this can be solved in an even cleaner manner? Without `'any'`, just tuples/lists and operators... – Mike Jan 08 '20 at 10:28
  • 1
    I don't think there is a way to do that. I added a third duplicate which contains that particular answer as its accepted answer. – tripleee Jan 08 '20 at 10:31
  • @Mike: you can't shorten the boolean expression, the expression `('dolphin' or 'mouse' or 'cow') in ...` doesn't make any sense, you can see that if you simply look at what `('dolphin' or 'mouse' or 'cow')` evaluates to, it's just the leftmost non-falsey value, i.e. `'dolphin'`. Please see the `help('or')` manpage. `or` is a Boolean `or` operator, nothing more than that. Chaining multiple `in`-operands together with `or` is not going to somehow generate an n-way combination expression of their values such that it can be used with `in`. No more than trying `3 or 5 == 4` with the `==` operator. – smci Jan 08 '20 at 13:28
  • Mike, the short answer is there is no operator that can convert a list/tuple/set/whatever of operands to the `in`-operator into one compound LHS operand that be used in a single expression on the LHS of `in`. That's why the idiomatic Pythonic thing is to use `any( ...)`. Can you explain why you don't want to use it? (How else would you deal with an arbitrary-length list of inputs, other than iterator/generator, list comprehension, loop constructs like for/while, or recursive functions? This is the idiomatic Python solution. – smci Jan 08 '20 at 13:42

1 Answers1

3

I'm not sure if I understood the question correctly, but I'll give you some patterns I used in the past for complex conditions.

One approach you can use, that allows you to define a complex logic over a number of possibilities, and have dynamic inputs for that condition, is the function any. This function will return True if any element in the iterator is True. This allows you to compare values against collections of values or even collections against other collections of values. Here is an example:

AnimalString = 'We were looking for a mouse in our house.'
AnimalList = [ 'dolphin', 'mouse', 'cow' ]

if any( p in AnimalString for p in AnimalList ):
    print('At least one listed animal is in the AnimalString.')

If you need the value that matched, you can use next, and if you want your condition to be True in all of the elements, you can use all, both of them work in a similar fashion to any. Example of these function:

AnimalMatch = next(( p for p in AnimalList if p in AnimalString ), None )
if AnimalMatch is not None:
    print('"%s" is in AnimalString.' % AnimalMatch)

if not all( p in AnimalString for p in AnimalList ):
    print('Not all animals are contained in AnimalString.')

Another approach to big logical conditions, and sometimes complex, is the usage of set or dict. This allows you to check a collection of values in a performant manner (since you are using hashing), allowing for a huge amount of values to be matched, or even allowing for different outputs (in the case of the dict). Here is the example:

# Example with set  
AnimalStringSet = set( AnimalString[:-1].lower().split() ) # preprocessing
if 'mouse' in AnimalStringSet: 
    print('At least one listed animal is in the AnimalString.')

# Example with dict
AnimalSoundsDict = { 'dolphin': 'click', 'mouse': 'squeak', 'cow': 'moo' }
if 'mouse' in AnimalSoundsDict: 
    print('The "%s" sounds like "%s"' % (Animal, AnimalSoundsDict[Animal]))
else:
    print('Animal "%s" not found' % Animal)

This approach might require some preprocessing, but in scenarios where you have to deal with a lot of values, it might be worth it.

At last, for strings you can always use regular expression or regex from the library re, but it's easier to make your code difficult to read. Example:

AnimalRegEx = '|'.join(AnimalList) # 'dolphin|mouse|cow'
if re.search(AnimalRegEx, AnimalString) is not None:
    print('At least one listed animal is in the AnimalString.')

You can also have a combination of these two last approaches with the first one.

MkWTF
  • 1,372
  • 7
  • 11
  • your first code snippet indeed does the job and it allows for the update of the AnimalList. Do you have any idea, if this can be solved in an even cleaner manner? Without `'any'`, just tuples/lists and operators... – Mike Jan 08 '20 at 10:26