【问题标题】:How can I find the missing value more concisely?如何更简洁地找到缺失值?
【发布时间】:2012-01-09 17:21:31
【问题描述】:

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

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

【问题讨论】:

  • 简短的回答是“是的!” Python 的集合非常适合检查独特性和计算未使用的元素。
  • 感谢大家的回答,我想我会使用 set 的解决方案,因为它相当快速且可读,Óscar López 基于查找表的答案也很有趣。

标签: python benchmarking microbenchmark


【解决方案1】:
z = (set(("a", "b", "c")) - set((x, y))).pop()

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

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

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

【讨论】:

  • 如果您使用的是 Python 2.7/3.1 或更高版本,您可以使用 set literals 更简洁地编写它,如下所示:z = ({'a', 'b', 'c'} - {x, y}).pop()
  • pop() 是不必要且缓慢的。改为使用元组解包。此外,set(("a", "b", "c")) 是不变的,因此可以预先计算一次,只留下要在循环中使用的集合差分(如果它没有在循环中使用,那么我们不太关心速度)。
  • @Ed:我知道,但是OP没有指定x == y时要做什么,所以我省略了测试。如果需要,添加 if x != y: 很容易。
  • 就个人而言,我会选择第一个,因为它比随机逗号更明显。
  • 您可能希望将set(("a", "b", "c")) 替换为set("abc")
【解决方案2】:

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

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

【讨论】:

  • +1 也很透明,和大多数答案不同,它处理的是x==y。
  • 好主意,+1;虽然我实际上认为原始帖子中的"a""b""c" 只是真实值的占位符。除了长度为 1 的字符串之外,此解决方案不会推广到任何其他值类型。
  • @chepner 很有创意!感谢您回答 chepner。
【解决方案3】:

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

【讨论】:

  • 您的测试似乎有偏见。所谓的“set_version”只是有时更快,因为它由一个额外的if 语句保护。
  • @ekhumoro 这就是问题规范所要求的:“检查 xy 是否是不同的值,如果是,则设置 z 到第三个字符”。检查值是否不同的最快(也是最直接)的方法是x != y。只有当它们不同时,我们才能确定第三个字符的集合差异:-)
  • 我的意思是,您的测试并没有表明set_version 的性能更好因为它是基于集合的;由于 if 声明的保护,它只会表现得更好。
  • @ekhumoro 这是对测试结果的奇怪解读。代码执行 OP 所要求的。时序显示了所有可能的输入组的比较性能。如何解释这些取决于您。与 OP 的原始代码相比,使用 if x != y: z, = choices - {x, y} 的版本的时间安排相当不错。我不知道您的偏见概念从何而来——时间就是它们,而 AFAICT,这仍然是已发布的最佳答案。它既干净又快速。
  • Sven 的“set-version”中添加了几个优化,但“if-version”没有做同样的优化。在“if-version”中添加if x != y 保护可能会使它比迄今为止提供的所有其他解决方案更加一致和性能更好(尽管显然不那么可读和简洁)。您的“set_version”是一个非常好的解决方案 - 它只是不像测试看起来那样相当 ;-)
【解决方案4】:
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'

【讨论】:

    【解决方案5】:

    如果有问题的三个项目不是"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'
    

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

    【讨论】:

    • +1 这个解决方案看起来不错且优雅;如果您将其设为自己的功能并为幻数 96 命名,则逻辑很容易遵循/维护(xor_of_a_b_c = 96 # ord('a') ^ ord('b') ^ ord('c') == 96)。然而,就原始速度而言,这比if / elifs 的长链慢了大约 33%;但比 set 方法快 500%。
    • @sven 感谢您向我介绍 XOR 运算符,您的解决方案简洁优雅,我认为这个示例会让我印象深刻,再次感谢 :)
    【解决方案6】:

    我认为 Sven Marnach 和 F.J 的解决方案很漂亮,但在我的小测试中并没有更快。这是 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-statements,这是最糟糕的输入,因为必须尝试所有六个比较。使用 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 语句很快就会变成维护的噩梦。

    【讨论】:

    • @martinGeisler 非常感谢您的回复,我不知道我们可以在 python 中对这样的事情进行计时。我有一种直觉认为 Chessmasters 解决方案可以正常工作且高效,我会尝试像使用其他答案一样对其进行测试并告知您。
    • 基于集合的解决方案优化了简洁可读性(以及优雅)。但是也提到了效率,所以我去研究了所提出的解决方案的性能。
    • @MartinGeisler:是的,当我注意到这一点时,我删除了我的评论。而且我通常确实觉得至少知道什么更快是很有趣的。
    • @BunnyRabbit:timeit 模块非常适合这样的微基准测试。您当然应该首先分析您的整个程序以确定瓶颈在哪里,但是当它们被识别时,timeit 可能是快速尝试不同实现的好方法。
    • +1 -- 证明简单系列比较的基准测试是合乎逻辑且快速的。
    【解决方案7】:

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

    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]
    

    【讨论】:

    • 只是为了好玩,这里是另一个 dict 选项:{1: 'c', 2: 'b', 3: 'a'}[ord(x)+ord(y)-ord('a')*2],虽然额外的复杂性可能不值得节省空间。
    • @F.J: z = {1: 'a', 2: 'b', 3: 'c'}[2*('a' in x+y)+('b' in x+y)] 这很有趣...
    • 比OP原码快吗?如果是这样,为什么?哈希值的计算怎么能比简单的比较快?
    • @max 它是一个单一的哈希计算,而不是一堆比较和条件表达式
    • 酷.. 没有意识到哈希函数有多快!
    【解决方案8】:
    z = 'a'*('a' not in x+y) or 'b'*('b' not in x+y) or 'c'
    

    或更少的hackish并使用条件赋值

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

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

    【讨论】:

      【解决方案9】:

      我觉得应该是这样的:

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

      【讨论】:

      • len(set((x, y))) == 2 是我见过的写x != y 最难读的方式:)
      • 是的,斯文)))感谢您的评论。当我开始编写这个脚本时,它还有一个基本想法))最后我忘了编辑它。
      【解决方案10】:

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

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

      【讨论】:

        【解决方案11】:

        看看这是否有效

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

        【讨论】:

          猜你喜欢
          • 2017-08-29
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-08-16
          • 2023-03-14
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多