# How can I find the missing value more concisely?

## Question:

The following code checks if `x` and `y` are distinct values (the variables `x`, `y`, `z` can only have values `a`, `b`, or `c`) and if so, sets `z` to the third character:

``````if x == 'a' and y == 'b' or x == 'b' and y == 'a':
z = 'c'
elif x == 'b' and y == 'c' or x == 'c' and y == 'b':
z = 'a'
elif x == 'a' and y == 'c' or x == 'c' and y == 'a':
z = 'b'
``````

Is is possible to do this in a more, concise, readable and efficient way?

``````z = (set(("a", "b", "c")) - set((x, y))).pop()
``````

I am assuming that one of the three cases in your code holds. If this is the case, the set `set(("a", "b", "c")) - set((x, y))` will consist of a single element, which is returned by `pop()`.

Edit: As suggested by Raymond Hettinger in the comments, you could also use tuple unpacking to extract the single element from the set:

``````z, = set(("a", "b", "c")) - set((x, y))
``````
``````z = (set('abc') - set(x + y)).pop()
``````

Here are all of the scenarios to show that it works:

``````>>> (set('abc') - set('ab')).pop()   # x is a/b and y is b/a
'c'
>>> (set('abc') - set('bc')).pop()   # x is b/c and y is c/b
'a'
>>> (set('abc') - set('ac')).pop()   # x is a/c and y is c/a
'b'
``````

I think it should looks like that:

``````z = (set(("a", "b", "c")) - set((x, y))).pop() if x != y else None
``````

I think the solution by Sven Marnach and F.J is beautiful, but it’s not faster in my little test. This is Raymond’s optimized version using a pre-computed `set`:

``````\$ python -m timeit -s "choices = set('abc')"
-s "x = 'c'"
-s "y = 'a'"
"z, = choices - set(x + y)"
1000000 loops, best of 3: 0.689 usec per loop
``````

This is the original solution:

``````\$ python -m timeit -s "x = 'c'"
-s "y = 'a'"
"if x == 'a' and y == 'b' or x == 'b' and y == 'a':"
"    z = 'c'"
"elif x == 'b' and y == 'c' or x == 'c' and y == 'b':"
"    z = 'a'"
"elif x == 'a' and y == 'c' or x == 'c' and y == 'a':"
"    z = 'b'"
10000000 loops, best of 3: 0.310 usec per loop
``````

Note that this is the worst possible input for the `if`-statements since all six comparisons will have to be tried out. Testing with all values for `x` and `y` gives:

``````x = 'a', y = 'b': 0.084 usec per loop
x = 'a', y = 'c': 0.254 usec per loop
x = 'b', y = 'a': 0.133 usec per loop
x = 'b', y = 'c': 0.186 usec per loop
x = 'c', y = 'a': 0.310 usec per loop
x = 'c', y = 'b': 0.204 usec per loop
``````

The `set`-based variant shows the same performance for different inputs, but it is consistently between 2 and 8 times slower. The reason is that the `if`-based variant runs much simpler code: equality tests compared to hashing.

I think both types of solutions are valuable: it’s important to know that creating “complicated” data structures like sets cost you something in performance — while they give you a lot in readability and development speed. The complex data types are also much better when the code change: it’s easy to extend the set-based solution to four, five, … variables whereas the if-statements quickly turn into a maintenance nightmare.

``````z = 'a'*('a' not in x+y) or 'b'*('b' not in x+y) or 'c'
``````

or less hackish and using Conditional Assignment

``````z = 'a' if ('a' not in x+y) else 'b' if ('b' not in x+y) else 'c'
``````

but probably the dict solution is faster… you’d have to time it.

Try this option, using dictionaries:

``````z = {'ab':'c', 'ba':'c', 'bc':'a', 'cb':'a', 'ac':'b', 'ca':'b'}[x+y]
``````

Of course, if the `x+y` key is not present in the map, it’ll produce a `KeyError` which you’ll have to handle.

If the dictionary is precomputed a single time and stored for future use, the access will be much faster, since no new data structures will have to be created for each evaluation, only a string concatenation and a dictionary lookup are needed:

``````lookup_table = {'ab':'c', 'ba':'c', 'bc':'a', 'cb':'a', 'ac':'b', 'ca':'b'}
z = lookup_table[x+y]
``````

If the three items in question weren’t `"a"`, `"b"` and `"c"`, but rather `1`, `2` and `3`, you could also use a binary XOR:

``````z = x ^ y
``````

More generally, if you want to set `z` to the remaining one of three numbers `a`, `b` and `c` given two numbers `x` and `y` from this set, you can use

``````z = x ^ y ^ a ^ b ^ c
``````

Of course you can precompute `a ^ b ^ c` if the numbers are fixed.

This approach can also be used with the original letters:

``````z = chr(ord(x) ^ ord(y) ^ 96)
``````

Example:

``````>>> chr(ord("a") ^ ord("c") ^ 96)
'b'
``````

Don’t expect anyone reading this code to immediately figure out what it means 🙂

Using list comprehension, assuming like others that one of the three cases in your code holds:

``````l = ['a', 'b', 'c']
z = [n for n in l if n not in [x,y]].pop()
``````

Or, like in the accepted answer, taking advantage of the tuple to unpack it,

``````z, = [n for n in l if n not in [x,y]]
``````

Sven’s excellent code did just a little too much work and chould have used tuple unpacking instead of pop(). Also, it could have added a guard `if x != y` to check for x and y being distinct. Here is what the improved answer looks like:

``````# create the set just once
choices = {'a', 'b', 'c'}

x = 'a'
y = 'b'

# the main code can be used in a loop
if x != y:
z, = choices - {x, y}
``````

Here are the comparative timings with a timing suite to show the relative performance:

``````import timeit, itertools

setup_template = '''
x = %r
y = %r
choices = {'a', 'b', 'c'}
'''

new_version = '''
if x != y:
z, = choices - {x, y}
'''

original_version = '''
if x == 'a' and y == 'b' or x == 'b' and y == 'a':
z = 'c'
elif x == 'b' and y == 'c' or x == 'c' and y == 'b':
z = 'a'
elif x == 'a' and y == 'c' or x == 'c' and y == 'a':
z = 'b'
'''

for x, y in itertools.product('abc', repeat=2):
print 'nTesting with x=%r and y=%r' % (x, y)
setup = setup_template % (x, y)
for stmt, name in zip([original_version, new_version], ['if', 'set']):
print min(timeit.Timer(stmt, setup).repeat(7, 100000)),
print 't%s_version' % name
``````

Here are the results of the timings:

``````Testing with x='a' and y='a'
0.0410830974579     original_version
0.00535297393799    new_version

Testing with x='a' and y='b'
0.0112571716309     original_version
0.0524711608887     new_version

Testing with x='a' and y='c'
0.0383319854736     original_version
0.048309803009      new_version

Testing with x='b' and y='a'
0.0175108909607     original_version
0.0508949756622     new_version

Testing with x='b' and y='b'
0.0386209487915     original_version
0.00529098510742    new_version

Testing with x='b' and y='c'
0.0259420871735     original_version
0.0472128391266     new_version

Testing with x='c' and y='a'
0.0423510074615     original_version
0.0481910705566     new_version

Testing with x='c' and y='b'
0.0295209884644     original_version
0.0478219985962     new_version

Testing with x='c' and y='c'
0.0383579730988     original_version
0.00530385971069    new_version
``````

These timings show that the original-version performance varies quite a bit depending on which if-statements are triggered by the various the input values.

The `strip` method is another option that runs quickly for me:

``````z = 'abc'.strip(x+y) if x!=y else None
``````

See if this works

``````if a not in xy
z= 'a'
if b not in xy
z='b'
if c not in xy
z='c'
``````
Categories: questions
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.