I'm trying to make a function that will compare multiple variables to an integer and output a string of three letters. I was wondering if there was a way to translate this into Python. So say:

x = 0
y = 1
z = 3
mylist = []

if x or y or z == 0 :
    mylist.append("c")
if x or y or z == 1 :
    mylist.append("d")
if x or y or z == 2 :
    mylist.append("e")
if x or y or z == 3 : 
    mylist.append("f")

which would return a list of

["c", "d", "f"]

Is something like this possible?

16 upvote
  flag
shouldn't the question be "test 'multiple' variables against multiple values"? – Devesh Khandelwal
upvote
  flag
use 1 in (tuple) – DNinja21

14 Answers 11

up vote 439 down vote accepted

You misunderstand how boolean expressions work; they don't work like an English sentence and guess that you are talking about the same comparison for all names here. You are looking for:

if x == 1 or y == 1 or z == 1:

x and y are otherwise evaluated on their own (False if 0, True otherwise).

You can shorten that to:

if 1 in (x, y, z):

or better still:

if 1 in {x, y, z}:

using a set to take advantage of the constant-cost membership test (in takes a fixed amount of time whatever the left-hand operand is).

When you use or, python sees each side of the operator as separate expressions. The expression x or y == 1 is treated as first a boolean test for x, then if that is False, the expression y == 1 is tested.

This is due to operator precedence. The or operator has a lower precedence than the == test, so the latter is evaluated first.

However, even if this were not the case, and the expression x or y or z == 1 was actually interpreted as (x or y or z) == 1 instead, this would still not do what you expect it to do.

x or y or z would evaluate to the first argument that is 'truthy', e.g. not False, numeric 0 or empty (see boolean expressions for details on what Python considers false in a boolean context).

So for the values x = 2; y = 1; z = 0, x or y or z would resolve to 2, because that is the first true-like value in the arguments. Then 2 == 1 would be False, even though y == 1 would be True.

The same would apply to the inverse; testing multiple values against a single variable; x == 1 or 2 or 3 would fail for the same reasons. Use x == 1 or x == 2 or x == 3 or x in {1, 2, 3}.

upvote
  flag
can you explain a bit more why the OP misunderstands booleans? – Private
53 upvote
  flag
I wouldn't be so quick to go for the set version. Tuple's are very cheap to create and iterate over. On my machine at least, tuples are faster than sets so long as the size of the tuple is around 4-8 elements. If you have to scan more than that, use a set, but if you are looking for an item out of 2-4 possibilities, a tuple is still faster! If you can arrange for the most likely case to be first in the tuple, the win is even bigger: (my test: timeit.timeit('0 in {seq}'.format(seq=tuple(range(9, -1, -1))))) – SingleNegationElimination
31 upvote
  flag
@dequestarmappartialsetattr: In Python 3.3 and up, the set is stored as a constant, bypassing the creation time altogether, eliminating the creation time. Tuples can be cheap to create as Python caches a bundle of them to avoid memory churn, making that the biggest difference with sets here. – Martijn Pieters
5 upvote
  flag
@dequestarmappartialsetattr: If you time just the membership test, for integers sets and tuples are equally fast for the ideal scenario; matching the first element. After that tuples lose out to sets. – Martijn Pieters
6 upvote
  flag
Woah. I never realized there was a literal set notation. How long has that been there? All this time I've been writing set(x, y, z), I could have been saving myself 3 characters. I find it weird that there's something so basic that I've never seen in the years I've been using Python. – ArtOfWarfare
upvote
  flag
@ArtOfWarfare: it was added to Python 3.1 and 2.7. – Martijn Pieters
1 upvote
  flag
@MartijnPieters: Oh. Then perhaps I should avoid it. Although many of the machines I work with have 2.7, some are only running 2.6. – ArtOfWarfare
upvote
  flag
wow, the literal set notation is a new shiny thing for me but it's available since py2.7, on July 2010, 5 years ago! docs.python.org/3/whatsnew/2.7.html – franciscod
2 upvote
  flag
@MartijnPieters: Using the set literal notation for this test isn't a savings unless the contents of the set literal are also literals, right? if 1 in {x, y, z}: can't cache the set, because x, y and z could change, so either solution needs to build a tuple or set from scratch, and I suspect whatever lookup savings you might get when checking for membership would be swamped by greater set creation time. – ShadowRanger
upvote
  flag
@ShadowRanger: yes, peephole optimisation (be it for in [...] or in {...}) only works if the contents of the list or set are immutable literals too. – Martijn Pieters

The direct way to write x or y or z == 0 is

if any(map((lambda value: value == 0), (x,y,z))):
    pass # write your logic.

But I dont think, you like it. :) And this way is ugly.

The other way (a better) is:

0 in (x, y, z)

BTW lots of ifs could be written as something like this

my_cases = {
    0: Mylist.append("c"),
    1: Mylist.append("d")
    # ..
}

for key in my_cases:
    if key in (x,y,z):
        my_cases[key]()
        break
2 upvote
  flag
In your example of the dict instead of a key, you will get errors because the return value of .append is None, and calling None gives an AttributeError. In general I agree with this method, though. – SethMMorton

Your problem is more easily addressed with a dictionary structure like:

x = 0
y = 1
z = 3
d = {0: 'c', 1:'d', 2:'e', 3:'f'}
mylist = [d[k] for k in [x, y, z]]
12 upvote
  flag
Or even d = "cdef" which leads to MyList = ["cdef"[k] for k in [x, y, z]] – aragaer
6 upvote
  flag
or map(lambda i: 'cdef'[i], [x, y, z]) – dansalmo

To check if a value is contained within a set of variables you can use the inbuilt modules itertools and operator.

For example:

Imports:

from itertools import repeat
from operator import contains

Declare variables:

x = 0
y = 1
z = 3

Create mapping of values (in the order you want to check):

check_values = (0, 1, 3)

Use itertools to allow repetition of the variables:

check_vars = repeat((x, y, z))

Finally, use the map function to create an iterator:

checker = map(contains, check_vars, check_values)

Then, when checking for the values (in the original order), use next():

if next(checker)  # Checks for 0
    # Do something
    pass
elif next(checker)  # Checks for 1
    # Do something
    pass

etc...

This has an advantage over the lambda x: x in (variables) because operator is an inbuilt module and is faster and more efficient than using lambda which has to create a custom in-place function.

Another option for checking if there is a non-zero (or False) value in a list:

not (x and y and z)

Equivalent:

not all((x, y, z))
upvote
  flag
This doesn't answer the OP's question. It only covers the first case in the provided example. – wallacer

I think this will handle it better:

my_dict = {0: "c", 1: "d", 2: "e", 3: "f"}

def validate(x, y, z):
    for ele in [x, y, z]:
        if ele in my_dict.keys():
            return my_dict[ele]

Output:

print validate(0, 8, 9)
c
print validate(9, 8, 9)
None
print validate(9, 8, 2)
e
d = {0:'c', 1:'d', 2:'e', 3: 'f'}
x, y, z = (0, 1, 3)
print [v for (k,v) in d.items() if x==k or y==k or z==k]

If you want to use if, else statements following is another solution:

myList = []
aList = [0,1,3]

for l in aList:
    if l==0:myList.append('c')
    elif l==1:myList.append('d')
    elif l==2:myList.append('e')
    elif l==3:myList.append('f')

print(myList)

If you ARE very very lazy, you can put the values inside an array. Such as

list = []
list.append(x)
list.append(y)
list.append(z)
nums = [add numbers here]
letters = [add corresponding letters here]
for index in range(len(nums)):
    for obj in list:
        if obj == num[index]:
            MyList.append(letters[index])
            break

You can also put the numbers and letters in a dictionary and do it, but this is probably a LOT more complicated than simply if statements. That's what you get for trying to be extra lazy :)

One more thing, your

if x or y or z == 0:

will compile, but not in the way you want it to. When you simply put a variable in an if statement (example)

if b

the program will check if the variable is not null. Another way to write the above statement (which makes more sense) is

if bool(b)

Bool is an inbuilt function in python which basically does the command of verifying a boolean statement (If you don't know what that is, it is what you are trying to make in your if statement right now :))

Another lazy way I found is :

if any([x==0, y==0, z==0])

Set is the good approach here, because it orders the variables, what seems to be your goal here. {z,y,x} is {0,1,3} whatever the order of the parameters.

>>> [chr(ord('c')+i) for i in {z,x,y}]
['c', 'd', 'f']

the letter corresponding to a number is given here by chr(ord('c')+i) , where chr and ord are bridges between characters and numeric codes.

So the whole solution is O(n).

1 upvote
  flag
You should add a description of what your code accomplishes and how it does it. Short answers using only code is discouraged – Raniz

This code may be helpful

L ={x, y, z}
T= ((0,"c"),(1,"d"),(2,"e"),(3,"f"),)
List2=[]
for t in T :
if t[0] in L :
    List2.append(t[1])
    break;

Previous Solution: As stated by Martijn Pieters, the correct, and fastest, format is:

if 1 in {x, y, z}:

The one major issue that does not seem to be addressed is that you want your output list to include each letter after a true if statement.

Using only Martijn Pieters' advice you would now have:

if 0 in {x, y, z}:
    Mylist.append("c")
elif 1 in {x, y, z}:
    Mylist.append("d")
...

Problem: The first if statement would return true, and you would never get to the following elif statement. So your list would simply return:

["c"]

What you want is to have separate if statements so that python will read each statement whether the former were true or false. Such as:

if 0 in {x, y, z}:
    Mylist.append("c")
if 1 in {x, y, z}:
    Mylist.append("d")
if 2 in {x, y, z}:
    Mylist.append("e")
...

This will work, but 'if' you are comfortable using dictionaries (see what I did there), you can clean this up by making an initial dictionary mapping the numbers to the letters you want, then just using a 'for' loop:

numToLetters = {0:"c", 1:"d", 2:"e", 3:"f"}
for number in numToLetters:
    if number in {x, y, z}:
        Mylist.append(numToLetters[number])

Don't get too much complicated just follow the simplest way.

 x = 0
 y = 1
 z = 3

 Mylist = []

 if x == 0 or y==0 or z==0:
     Mylist.append('c')
 elif x == 1 or y == 1 or z==1:
     Mylist.append('d')
 elif x==2 or y==2 or z==2:
     Mylist.append('e')
 elif x==3 or y==3 or z==3:
     Mylist.append('f')

Cheers!

4 upvote
  flag
The problem with this approach is the high likelihood of making a typo. Which is exactly what happened. elif x==1 or y==2 or z==2: => elif x==2 or y==2 or z==2: oops. Fixed now though. – Paul Rooney

All of the excellent answers provided here concentrate on the specific requirement of the original poster and concentrate on the if 1 in {x,y,z} solution put forward by Martijn Pieters.
What they ignore is the broader implication of the question:
How do I test one variable against multiple values?
The solution provided will not work for partial hits if using strings for example:
Test if the string "Wild" is in multiple values

>>> x="Wild things"
>>> y="throttle it back"
>>> z="in the beginning"
>>> if "Wild" in {x,y,z}: print (True)
... 

or

>>> x="Wild things"
>>> y="throttle it back"
>>> z="in the beginning"
>>> if "Wild" in [x,y,z]: print (True)
... 

for this scenario it's easiest to convert to a string

>>> [x,y,z]
['Wild things', 'throttle it back', 'in the beginning']
>>> {x,y,z}
{'in the beginning', 'throttle it back', 'Wild things'}
>>> 

>>> if "Wild" in str([x,y,z]): print (True)
... 
True
>>> if "Wild" in str({x,y,z}): print (True)
... 
True

One line solution:

mylist = [{0: 'c', 1: 'd', 2: 'e', 3: 'f'}[i] for i in [0, 1, 2, 3] if i in (x, y, z)]

Or:

mylist = ['cdef'[i] for i in range(4) if i in (x, y, z)]

Not the answer you're looking for? Browse other questions tagged or ask your own question.