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

Bun*_*bit 76 python benchmarking microbenchmark

下面的代码检查xy是不同的值(变量x,y,z只能有值a,bc),并且如果是这样,设置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'
Run Code Online (Sandbox Code Playgroud)

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

Sve*_*ach 62

z = (set(("a", "b", "c")) - set((x, y))).pop()
Run Code Online (Sandbox Code Playgroud)

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

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

z, = set(("a", "b", "c")) - set((x, y))
Run Code Online (Sandbox Code Playgroud)

  • 如果您使用的是Python 2.7/3.1或更高版本,则可以使用set literals更简洁地编写它,如下所示:`z =({'a','b','c'} - {x,y}) .pop()` (26认同)
  • *pop()*是不必要的,也很慢.请改用元组解压缩.另外,``set(("a","b","c"))``是不变的,因此它可以预先计算一次,只留下在循环中使用的设置差分(如果不是在循环中使用,那么我们不关心速度). (7认同)
  • 就个人而言,我会选择第一个,因为它比随机逗号更明显. (4认同)
  • @Ed:我知道,但OP没有指定`x == y`时要做什么,所以我省略了测试.如果需要的话,添加`if x!= y:`很容易. (3认同)

che*_*ner 47

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

z = 'abc'.strip(x+y) if x!=y else None
Run Code Online (Sandbox Code Playgroud)

  • +1它也非常透明,与大多数答案不同,它处理的是x == y. (2认同)

Ray*_*ger 27

Sven的优秀代码只做了太多的工作,并且使用了tuple解包而不是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}
Run Code Online (Sandbox Code Playgroud)

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

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
Run Code Online (Sandbox Code Playgroud)

以下是时间的结果:

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
Run Code Online (Sandbox Code Playgroud)

这些时序表明,原始版本的性能会有很大差异,具体取决于各种输入值触发的if语句.

  • 你的测试似乎有偏见.所谓的"set_version"有时只是更快,因为它受到额外的`if`语句的保护. (2认同)
  • @ekhumoro这就是问题规范所要求的:"检查*x*和*y*是否为不同的值,如果是,则将*z*设置为第三个字符".检查值是否不同的最快(也是最直接)的方法是``x!= y``.只有当它们不同时我们才能确定第三个字符的集合差异:-) (2认同)
  • 我的观点是你的测试没有表明`set_version`表现更好_因为它基于sets_; 它只会因为保护`if`语句而表现得更好. (2认同)
  • Sven的"set-version"增加了几个优化,但是对于"if-version"却没有这样做.将"if x!= y"保护添加到"if-version"可能会使其比迄今为止提供的所有其他解决方案更加一致且性能更好(尽管显然不那么可读和简洁).你的"set_version"是一个非常好的解决方案 - 它不是_quite_和测试看起来一样好;-) (2认同)

And*_*ark 18

z = (set('abc') - set(x + y)).pop()
Run Code Online (Sandbox Code Playgroud)

以下是显示其有效的所有方案:

>>> (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'
Run Code Online (Sandbox Code Playgroud)


Sve*_*ach 15

如果有问题的三个项目都没有"a","b"而且"c",而是1,2并且3,你也可以使用二进制XOR:

z = x ^ y
Run Code Online (Sandbox Code Playgroud)

更一般地,如果要设置z到剩下的三个号码中的一个a,bc给出了两个数字x,并y从该集合,你可以使用

z = x ^ y ^ a ^ b ^ c
Run Code Online (Sandbox Code Playgroud)

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

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

z = chr(ord(x) ^ ord(y) ^ 96)
Run Code Online (Sandbox Code Playgroud)

例:

>>> chr(ord("a") ^ ord("c") ^ 96)
'b'
Run Code Online (Sandbox Code Playgroud)

不要指望任何人阅读此代码立即弄明白这意味着什么:)


Mar*_*ler 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
Run Code Online (Sandbox Code Playgroud)

这是原始解决方案:

$ 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
Run Code Online (Sandbox Code Playgroud)

请注意,这是-statements 最糟糕的输入,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
Run Code Online (Sandbox Code Playgroud)

set基变体示出了用于不同的输入相同的性能,但它是一贯之间较慢2和8倍.原因是if基于变量的变体运行更简单的代码:与散列相比的相等测试.

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


Che*_*ter 8

z = 'a'*('a' not in x+y) or 'b'*('b' not in x+y) or 'c'
Run Code Online (Sandbox Code Playgroud)

或更少的hackish和使用条件分配

z = 'a' if ('a' not in x+y) else 'b' if ('b' not in x+y) else 'c'
Run Code Online (Sandbox Code Playgroud)

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


Ósc*_*pez 8

使用词典尝试此选项:

z = {'ab':'c', 'ba':'c', 'bc':'a', 'cb':'a', 'ac':'b', 'ca':'b'}[x+y]
Run Code Online (Sandbox Code Playgroud)

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

如果字典预先计算并存储以供将来使用,则访问速度会快得多,因为不需要为每个评估创建新的数据结构,只需要字符串连接和字典查找:

lookup_table = {'ab':'c', 'ba':'c', 'bc':'a', 'cb':'a', 'ac':'b', 'ca':'b'}
z = lookup_table[x+y]
Run Code Online (Sandbox Code Playgroud)

  • 只是为了好玩,这是另一个字典选项:`{1:'c',2:'b',3:'a'} [ord(x)+ ord(y)-ord('a')*2] `,额外的复杂性可能不值得节省的空间. (2认同)
  • @FJ:`z = {1:'a',2:'b',3:'c'} [2*(x + y中的'a')+(x + y中的'b')]这个有趣... (2认同)