【问题标题】:Most pythonic way to 'or' tuples?'或'元组的最pythonic方式?
【发布时间】:2015-11-16 19:45:35
【问题描述】:

我有一个返回布尔值的三元素元组的方法,我在循环中调用它。我想最终得到一个包含or 各个元组结果的三元素元组。如果该方法只返回一个布尔值,那就是:

result = False
for j in some_list: # there is more processing in the loop, omitted
    result |= method(j)
return result

我可以以某种优雅的方式将其概括为method() 现在返回的元组吗?当然可以:

result = False, False, False
for j in some_list:
    res1, res2, res3 = method(j)
    result = res1 | result[0], res2 | result[1], res3 | result[2]
return result

但似乎有点不雅。

编辑:澄清我想在两种情况下都返回结果 - 首先是一个布尔值,然后是一个布尔值元组

【问题讨论】:

  • 您是否有特殊的理由在bools 上使用按位或运算符?
  • @Josh:这就是代码中使用的内容-我应该将其更改为还是?
  • 我会这么认为。如果您想了解更多信息,请参阅Python boolean operators vs bitwise operators
  • @JoshCaswell:你难道还没有发现result |= method(j) 比 result = result 或 method(j) 更优雅(可读)`
  • 就个人而言,不,因为在阅读 | 时,我希望这些类型用于它们的位值,而不是它们的布尔值。

标签: python python-2.7 tuples iterable-unpacking


【解决方案1】:

您可以使用 zip 在列表推导中执行此操作。

result = False, True, False
xor = True, True, False
result = [a|b for a,b in zip(result,xor)]
print(result)

或者在你的例子中:

result = False, False, False
for j in some_list:
    xor = method(j)
    result = [a|b for a,b in zip(result,xor)]

如果它必须是一个元组,您可以将列表 comp 更改为生成器并将其包装在 tuple() 中。

您还可以在对 zip 的调用中移动对 method(j) 的调用,而不是将其分配给中间变量。我认为它使它的可读性降低了一点,但这是个人喜好的问题。

【讨论】:

  • @Mr_and_Mrs_D 这比手动执行要高效得多,而且无论您有多少值,这都会起作用。我不明白您认为哪种解决方案会更“优雅”。
  • 您的意思是result = res1 | result[0], res2 | result[1], res3 | result[2] 的效率低于result = [a|b for a,b in zip(result,xor)] ?你能计时吗?
  • 最优雅的是tuple1 | tuple2 - 不幸的是这是一个语法错误
  • @Mr_and_Mrs_D 只有 3 个值的时间差异可以忽略不计。同样,如果不是这样,我不确定您在寻找什么。
  • 你提到了时间 - 你错了,问题的方式是更快。
【解决方案2】:

让我们把它拆开一点。

没有内置的方法可以在两个元组上执行逐元素or(逻辑或按位)。 Morgan Thrapp 的回答显示了一种编写自己的好方法,因此,如果您想在 for 循环中保持运行状态,那就是我要走的路。熟悉 Python 的人会很容易理解生成器表达式 - 尽管如果我实际上不想要按位版本,我会使用 tuple(a or b for a, b in zip(result, new_result)) 而不是 a | b

Numpy 数组有一个 logical_or 函数,可以按元素工作,但如果你只有中等数量的布尔元组,那将是严重的矫枉过正。

保持运行状态的另一种方法是收集所有结果元组并从结果元组列表中计算最终的布尔值元组。如果您要提前终止循环(例如,如果您的累积结果元组的所有元素都是 True),或者如果您的循环有足够的迭代次数,内存使用很重要,这将是不合适的。但是,根据您的口味,它可能会更清晰。这基本上与保持数值的总和与仅在循环运行时收集值并在之后求和的决定相同,如果例如您将 for 循环重新设计为结果元组的迭代器,这将是我的首选方法。

看起来像:

result_list = []
for j in some_list:
    result_list.append(method(j))
result = tuple(any(grouped) for grouped in zip(*result_list))

星形扩展结果列表中的zip 会将元组列表中的所有第一/第二/第三个值分组为 n 长度的元组,其中 n 是结果数,any 实际上是 ors他们在一起。例如:

>>> result_list = [(False, True, False), (True, False, False), (False, False, False), (True, True, False)]
>>> zip(*result_list)
[(False, True, False, True), (True, False, False, True), (False, False, False, False)]
>>> tuple(any(grouped) for grouped in zip(*result_list))
(True, True, False)

由于or 对布尔值等价于对数字的加法,而any 等价于sum,您可以考虑使用整数的类似模型。 for循环/多重赋值版本:

sums = (0, 0, 0)
for j in some_list:
    result = method(j)
    sums[0] += result[0]
    sums[1] += result[1]
    sums[2] += result[2] 

vs 生成器表达式运行求和版本:

sums = (0, 0, 0)
for j in some_list:
    result = method(j)
    sums = (a + b for a, b in zip(sums, result))

vs 对结果列表进行累加求和:

result_list = []
for j in some_list:
    result_list.append(method(j))
sums = tuple(sum(grouped) for grouped in zip(*result_list))

如果您的 for 循环没有任何其他主体,则此版本特别好,因为您可以将整个内容折叠成您喜欢的任何级别的生成器表达式/列表推导:

result_list = [method(j) for j in some_list]
sums = tuple(sum(grouped) for grouped in zip(*result_list))

或:

sums = tuple(sum(grouped) for grouped in zip(*(method(j) for j in some_list)))

【讨论】:

  • 嗯 - 会考虑一下 :) 这将如何适应循环?我的意思是它不需要更多的行吗?
  • 此版本不需要显式 for 循环 - 生成器表达式 (for grouped in zip(some_list)) 负责迭代。
  • 查看我的问题中的循环 - 这是一个大循环
  • 这是错误的压缩,而不是 OP 要求的。请参阅问题中第二个 sn-p 的最后一行。
  • 问题是要求对初始result元组和方法返回的元组进行成对比较:zip(result, ret_val)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-10-15
  • 2020-04-21
  • 2013-04-25
  • 2011-09-26
相关资源
最近更新 更多