29

I'm trying to compare a string called facility to multiple possible strings to test if it is valid. The valid strings are:

auth, authpriv, daemon, cron, ftp, lpr, kern, mail, news, syslog, user, uucp, local0, ... , local7

Is there an efficient way of doing this other than:

if facility == "auth" or facility == "authpriv" ...
  • For looking for a substring, try https://stackoverflow.com/questions/3389574/check-if-multiple-strings-exist-in-another-string – tripleee Jan 08 '20 at 10:35
  • Many newcomers are tripped by the fact that `if facility == "auth" or "authpriv"` doesn't do what they want (it checks if `facility == "auth"` is true `or` if `"authpriv"` is not an empty string). – tripleee Feb 04 '20 at 12:32
  • 1
    For dupe searchers, if you find this and someone is doing a `if variable == literal or literal or literal:` sort of test, [Why does "a == x or y or z" always evaluate to True?](https://stackoverflow.com/q/20002503/364696) is a better dupe target. – ShadowRanger Mar 15 '22 at 23:10

3 Answers3

61

If, OTOH, your list of strings is indeed hideously long, use a set:

accepted_strings = {'auth', 'authpriv', 'daemon'}

if facility in accepted_strings:
    do_stuff()

Testing for containment in a set is O(1) on average.

Samuel Katz
  • 24,066
  • 8
  • 71
  • 57
pillmuncher
  • 10,094
  • 2
  • 35
  • 33
  • Yes, that would be the way to go. http://wiki.python.org/moin/PythonSpeed is pretty good reading for anyone interested in a general overview of efficiency in python. Though you wouldn't happen to know the average time for `set()` would you? – waffle paradox Jul 27 '11 at 00:59
  • One potential downside with this is that the order of iteration over them becomes unpredictable, but that's only a problem if you're using them for anything else (such as to print the list of accepted strings in a help message). – Ben Jul 27 '11 at 03:37
  • 6
    In python2.7/3 you can write `accepted_strings = {'auth', 'authpriv', 'daemon'}` so that no list is created prior to building a set. – Michał Bentkowski Jul 27 '11 at 08:19
  • 2
    In more modern Python, you'll benefit a bit by inlining the test to `if facility in {'auth', 'authpriv', 'daemon'}:` (as long as all options are constant literals as in this case); it'll be converted to a `frozenset` when the function doing it is compiled and stored in the function's constants, where this rebuilds the `set` each time you reach the `accepted_strings = {'auth', 'authpriv', 'daemon'}` line. – ShadowRanger Mar 15 '22 at 22:59
11

Unless your list of strings gets hideously long, something like this is probably best:

accepted_strings = ['auth', 'authpriv', 'daemon'] # etc etc 

if facility in accepted_strings:
    do_stuff()
waffle paradox
  • 2,755
  • 18
  • 19
  • oh awesome thank you. what happens if my list does actually get really long? –  Jul 27 '11 at 00:40
  • That was just a small joke, since you wouldn't want to type in a list of 10,000 strings by hand. – waffle paradox Jul 27 '11 at 00:56
  • This is the option I originally used, but as my application might grow I'm going to accept @pillmucher's answer. Thanks +1 –  Jul 27 '11 at 01:51
  • No probs. His is probably the safer one, though as a note you're going to have to have lists orders of magnitude bigger than what you have now before the difference between set and list containment will really start to be noticeable. Just remember premature optimization is the root of all evil. ;) – waffle paradox Jul 27 '11 at 01:57
3

To efficiently check if a string matches one of many, use this:

allowed = set(('a', 'b', 'c'))
if foo in allowed:
    bar()

set()s are hashed, unordered collections of items optimized for determining whether a given item is in them.

Colin Valliant
  • 1,899
  • 1
  • 13
  • 20