76

以下代码检查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'

是否有可能以更简洁、更易读和更有效的方式做到这一点?

4

11 回答 11

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

我假设您的代码中的三种情况之一成立。如果是这种情况,该集合set(("a", "b", "c")) - set((x, y))将由一个元素组成,该元素由pop().

编辑:正如 Raymond Hettinger 在评论中所建议的,您还可以使用元组解包从集合中提取单个元素:

z, = set(("a", "b", "c")) - set((x, y))
于 2012-01-09T17:23:39.130 回答
47

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

z = 'abc'.strip(x+y) if x!=y else None
于 2012-01-09T19:15:29.473 回答
27

Sven 出色的代码做了太多的工作,应该使用元组解包而不是pop()。此外,它可以添加一个保护if x != y来检查xy是否不同。以下是改进后的答案:

# 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 语句。

于 2012-01-09T19:06:26.553 回答
18
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'
于 2012-01-09T17:24:21.510 回答
15

如果有问题的三个项目不是"a","b""c", 而是1,23, 您还可以使用二进制 XOR:

z = x ^ y

更一般地说,如果你想设置z三个数字中的剩余一个abc给定两个数字x,并且y从这个集合中,你可以使用

z = x ^ y ^ a ^ b ^ c

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

这种方法也可以与原始字母一起使用:

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

例子:

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

不要指望任何阅读此代码的人会立即弄清楚它的含义:)

于 2012-01-09T18:02:04.027 回答
13

我认为 Sven Marnach 和 FJ 的解决方案很漂亮,但在我的小测试中并没有更快。这是 Raymond 使用预计算的优化版本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 语句很快变成了维护的噩梦。

于 2012-01-09T17:39:58.777 回答
8
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'

但可能 dict 解决方案更快......你必须计时。

于 2012-01-09T17:44:02.893 回答
8

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

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]
于 2012-01-09T17:45:01.803 回答
2

我认为它应该是这样的:

z = (set(("a", "b", "c")) - set((x, y))).pop() if x != y else None
于 2012-01-09T17:38:34.033 回答
1

使用列表推导,像其他人一样假设您的代码中的三种情况之一成立:

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]]
于 2012-01-09T18:57:03.513 回答
0

看看这是否有效

if a not in xy
    z= 'a'
if b not in xy
    z='b'
if c not in xy
    z='c'
于 2012-01-10T00:19:29.513 回答