I have a method that returns a three element tuple of boolean values and I call it on a loop. I want to finally get a three element tuple containing the or
result of the individual tuples. If the method only returned a boolean it would just be:
result = False
for j in some_list: # there is more processing in the loop, omitted
result |= method(j)
return result
Can I generalize this in some elegant way to or the tuples that method()
now returns? Of course I could do:
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
but seems a bit inelegant.
EDIT: clarified I want to return the result in both cases - first a boolean then a tuple of booleans
Let's unpack this a little.
There is no built in way to do an element-wise or
(logical or bitwise) on two tuples. Morgan Thrapp's answer shows a good way to write your own, so if you want to maintain a running state in your for loop, that's the way I'd go. The generator expression is going to be easily understood by people familiar with Python - although I'd use tuple(a or b for a, b in zip(result, new_result))
rather than a | b
if I don't actually want the bitwise version.
Numpy arrays have a logical_or
function that works element-wise, but that'd be serious overkill if you just have a moderate number of tuples of booleans.
An alternative to keeping a running state is to collect all the result tuples and compute the final boolean value tuple from the list of result tuples. This wouldn't be appropriate if you're going to terminate the loop early (say if all elements of your accumulated result tuple are True) or if your loop has enough iterations that the memory use matters. Depending on your tastes, though, it might be clearer. This is essentially the same decision as keeping a running total of numeric values versus just collecting the values as the loop runs and summing afterwards, and would be my preferred approach if for instances you rework your for loop as an iterator of result tuples.
That would look something like:
result_list = []
for j in some_list:
result_list.append(method(j))
result = tuple(any(grouped) for grouped in zip(*result_list))
zip
on the star-expanded result list will group all the first/second/third values of your list of tuples as n-length tuples where n is the number of results, and any
effectively or
s them together. EG:
>>> 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)
Since or
over booleans is equivalent to addition over numbers, and any
is equivalent to sum
, you can consider similar models with ints. The for loop/multiple assignment version:
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 the generator expression running sum version:
sums = (0, 0, 0)
for j in some_list:
result = method(j)
sums = (a + b for a, b in zip(sums, result))
vs accumulating and summing the list of results:
result_list = []
for j in some_list:
result_list.append(method(j))
sums = tuple(sum(grouped) for grouped in zip(*result_list))
This version is particularly nice if your for
loop doesn't have any other body, since you can collapse the whole thing into whatever level of generator expressions/list comprehensions you like:
result_list = [method(j) for j in some_list]
sums = tuple(sum(grouped) for grouped in zip(*result_list))
or:
sums = tuple(sum(grouped) for grouped in zip(*(method(j) for j in some_list)))