How can I find the missing value more concisely?
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))
The strip
method is another option that runs quickly for me:
z = 'abc'.strip(x+y) if x!=y else None
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.
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'
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 :)