如何更精确地找到缺少的值?

下面的代码检查 xy是否是不同的值(变量 xyz只能有值 abc) ,如果是,则将 z设置为第三个字符:

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'

是否有可能做到这一点,在一个更简洁,可读性和有效的方式?

3281 次浏览
z = (set(("a", "b", "c")) - set((x, y))).pop()

我假设你代码里的三个案子中有一个是成立的。如果是这种情况,集合 set(("a", "b", "c")) - set((x, y))将由单个元素组成,该元素由 pop()返回。

编辑: 正如 Raymond Hettinger 在评论中建议的那样,您也可以使用 tuple 解包来从集合中提取单个元素:

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

下面是所有可以证明这种方法有效的场景:

>>> (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'

我认为应该是这样的:

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

我认为斯文 · 马尔纳和 F.J 的解决方案很漂亮,但在我的小测试中并没有更快。这是雷蒙德的优化版本使用预先计算的 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

这是最初的解决方案:

$ 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

注意,这是 if语句的 最坏的可能输入,因为所有六个比较都必须进行尝试。对 xy的所有值进行测试得出:

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

基于 set的变体对于不同的输入显示出相同的性能,但是它在 慢2到8倍之间是一致的。原因是基于 if的变体运行更简单的代码: 与哈希相比,相等性测试。

我认为这两种类型的解决方案都是有价值的: 重要的是要知道,创建“复杂”的数据结构(如集合)会在性能方面付出一些代价ーー而它们在可读性和 发展速度方面会给你带来很多好处。当代码发生变化时,复杂的数据类型也会变得更好: 基于集合的解决方案很容易扩展到四个、五个、 ... 变量,而 if 语句很快就会变成维护的噩梦。

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

使用条件赋值

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

但是可能解决这个问题的方法更快... 你得计时。

试试这个选项,使用字典:

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

当然,如果地图中没有 x+y键,它将产生一个 KeyError,您必须处理它。

如果字典预先计算一次并存储以备将来使用,那么访问速度会快得多,因为不需要为每次计算创建新的数据结构,只需要字符串串联和字典查找:

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

如果所讨论的三个项目不是 "a""b""c",而是 123,你也可以使用二进制异或:

z = x ^ y

更一般地说,如果您希望将 z设置为三个数字 abc中的其余一个,并从该集中给出两个数字 xy,则可以使用

z = x ^ y ^ a ^ b ^ c

当然,如果数字是固定的,您可以预先计算 a ^ b ^ c

这种方法也可用于原始信件:

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

例如:

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

不要指望任何人阅读这段代码后会立即明白它的意思:)

使用列表内涵,假设和其他人一样,你的代码中有三种情况:

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

或者,就像公认的答案一样,利用元组来解压缩它,

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

Sven 的优秀代码只是做了太多的工作,本可以使用元组解包而不是 。此外,它可以增加一个保护 if x != y,以检查 X是不同的。下面是改进后的答案:

# 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}

下面是一个时间套件的比较时间,以显示相对表现:

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

以下是计时的结果:

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

这些计时显示,原版的的性能差异很大,这取决于不同的输入值触发哪些 if 语句。

strip方法是另一个对我来说运行速度很快的选项:

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

看看能不能成功

if a not in xy
z= 'a'
if b not in xy
z='b'
if c not in xy
z='c'