如何更简洁地找到缺失值?


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'

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


6
简短的回答是“是!” Python的集合非常适合检查差异性和计算未使用的元素。
Raymond Hettinger'1

1
感谢所有答案,我想我将使用set的解决方案,因为它的速度和可读性都很高。ÓscarLópez基于查找表的答案也很吸引人。
兔子兔子2012年

Answers:


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))

26
如果您使用的是Python 2.7 / 3.1或更高版本,则可以使用set字面量更简洁地编写它,如下所示:z = ({'a', 'b', 'c'} - {x, y}).pop()
Taymon 2012年

7
弹出()是不必要的和缓慢的。请改用元组拆包。另外,theset(("a", "b", "c"))是不变的,因此它可以被预先计算一次,仅将差集用于循环(如果不在循环中使用,那么我们就不会在乎速度)。
雷蒙德·海廷格

3
@Ed:我知道,但是OP没有指定when做什么x == y,所以我省略了测试。if x != y:如果需要,添加起来很容易。
Sven Marnach 2012年

4
就个人而言,我会选择第一个,因为它比随机逗号更明显。
约翰,

1
您可能需要替换set(("a", "b", "c"))set("abc")
kasyc 2012年

47

strip方法是为我快速运行的另一个选项:

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

2
+1它也是非常透明的,并且与大多数答案不同,它处理x == y。
Ed Staub,2012年

1
好主意,+ 1;尽管我实际上认为"a""b"并且"c"在原始帖子中只是真正价值的占位符。这个解决方案没有推广到任何其他类型的值比长度为1的串
斯文Marnach

@chepner多数民众赞成在创意!感谢您回答chepner。
兔子兔子2012年

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


2
您的测试似乎有偏差。所谓的“ set_version”有时只是更快一些,因为它受到附加if语句的保护。
ekhumoro 2012年

2
@ekhumoro这就是问题规范所要求的:“检查xy是否为不同的值,如果是,则将z设置为第三个字符”。检查值是否不同的最快(也是最直接的方法)是x != y。只有当它们不同时,我们才能通过集合差确定第三个字符:-)
Raymond Hettinger 2012年

2
我要说的是,您的测试没有显示出set_version更好的性能,因为它基于集合;由于保护if声明,它只能表现更好。
ekhumoro 2012年

1
@ekhumoro这是测试结果的怪异阅读。该代码完成了OP的要求。时序显示了所有可能的输入组的比较性能。由您决定如何解释这些内容。if x != y: z, = choices - {x, y}与OP的原始代码相比,使用票价的版本计时相当合理。我不知道您的偏见概念从何而来-时机已定,而AFAICT仍然是已发布的最佳答案。它既干净又快速。
Raymond Hettinger 2012年

2
Sven的“ set-version”中添加了一些优化,但“ if-version”未进行相同的优化。if x != y在“ if-version”中增加一个保护措施可能会使其比迄今为止提供的所有其他解决方案更加一致和更好的性能(尽管显然不那么可读和简洁)。您的“set_version”是一个很好的解决方案-它只是不相当好,因为测试使它看起来;-)
ekhumoro

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'

15

如果有问题的三个项目都没有"a""b"而且"c",而是12并且3,你也可以使用二进制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'

不要指望阅读此代码的人立即发现它的含义:)


+1这个解决方案看起来不错而优雅;如果您使用它自己的功能并给幻数96命名,则逻辑很容易遵循/维护(xor_of_a_b_c = 96 # ord('a') ^ ord('b') ^ ord('c') == 96)。但是,就原始速度而言,这比if / elifs的长链慢约33%;但比该set方法快500%。
jimbob博士2012年

@sven感谢您向我介绍XOR运算符,您的解决方案简洁明了,我认为这个示例将使它牢牢记住我,再次感谢:)
Bunny Rabbit

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

请注意,这是-statement的最坏输入if因为必须尝试所有六个比较。与所有值进行测试x,并y给出了:

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基变体示出了用于不同的输入相同的性能,但它是一贯之间较慢倍和8 2。原因是if基于-based的变体运行的代码简单得多:与散列相比,相等测试。

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


1
@martinGeisler非常感谢您的回复,我什至不知道我们可以在python中计时这样的事情。其他答案,让您知道。
兔子兔子2012年

1
基于集合的解决方案优化了简洁性可读性(和优雅性)。但是还提到了效率,因此我去研究了建议的解决方案的性能。
马丁·盖斯勒

1
@MartinGeisler:是的,当我注意到这一点时,我删除了我的评论。而且我通常确实会发现有趣,至少知道什么更快。
Sven Marnach 2012年

1
@BunnyRabbit:timeit模块非常适合像这样的微基准测试。当然,您应该首先整个程序进行概要分析,以确定瓶颈在哪里,但是一旦发现瓶颈,那么及时就可以是一种快速尝试彼此不同的实现的好方法。
马丁·盖斯勒 Martin Geisler)2012年

1
+1-证明简单的一系列比较是合理且快速的基准测试。
杰夫·费兰德

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]

2
只是为了好玩,这是另一个dict选项:{1: 'c', 2: 'b', 3: 'a'}[ord(x)+ord(y)-ord('a')*2],但是额外的复杂性可能不值得节省空间。
安德鲁·克拉克

2
@FJ:z = {1: 'a', 2: 'b', 3: 'c'}[2*('a' in x+y)+('b' in x+y)] 这很有趣...
ChessMaster

它比OP原始代码快吗?如果是这样,为什么?哈希值的计算如何比简单比较更快?
最高

@Max它的单个散列计算,而不是一整串比较和条件式
奥斯卡洛佩兹

太酷了。没有意识到哈希函数有多快!
最大

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的解决方案可能更快...您必须对其进行计时。


2

我认为应该像这样:

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

12
len(set((x, y))) == 2x != y我见过的最不可读的书写方式:)
Sven Marnach'1

是的,Sven)))感谢您的评论。当我开始编写该脚本时,它还有另一个基本概念))最后我忘了对其进行编辑。
自称2012年

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]]

0

看看是否可行

if a not in xy
    z= 'a'
if b not in xy
    z='b'
if c not in xy
    z='c'
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.