1

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

Taku
  • 31,927
  • 11
  • 74
  • 85
Mr_and_Mrs_D
  • 32,208
  • 39
  • 178
  • 361
  • 2
    Do you have a particular reason to be using the bitwise OR operator on `bool`s? – jscs Nov 16 '15 at 19:54
  • @Josh: That's what was used in the code - should I change it to or ? – Mr_and_Mrs_D Nov 16 '15 at 19:56
  • 2
    I would think so. See [Python boolean operators vs bitwise operators](http://stackoverflow.com/q/3845018) if you'd like more info. – jscs Nov 16 '15 at 20:05
  • @JoshCaswell: don't you still find `result |= method(j)` more elegant (read readable) than result = result or method(j)` – Mr_and_Mrs_D Nov 16 '15 at 20:09
  • 3
    Personally, no, because on reading `|` I would expect the types to be used for their bit values, not their boolean values. – jscs Nov 16 '15 at 20:11
  • You have a point - but I think here we reach opinion. To me `|` here is clearer – Mr_and_Mrs_D Nov 16 '15 at 20:14

2 Answers2

6

You can do this in a list comprehension with zip.

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

Or in the case of your example:

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

If it HAS to be a tuple, you can change the list comp to a generator and wrap it in tuple().

You can also move the call to method(j) inside the call to zip instead of assigning it to an intermediate variable. I think it makes it a little less readable, but it's a matter of personal preference.

Morgan Thrapp
  • 9,748
  • 3
  • 46
  • 67
  • @Mr_and_Mrs_D It's much more efficient than doing it manually, plus this will work no matter how many values you have. I don't understand what solution you think would be more "elegant". – Morgan Thrapp Nov 16 '15 at 20:02
  • 1
    You mean that `result = res1 | result[0], res2 | result[1], res3 | result[2]` is less efficient than `result = [a|b for a,b in zip(result,xor)]` ? Can you time it ? – Mr_and_Mrs_D Nov 16 '15 at 20:05
  • The most elegant would be `tuple1 | tuple2` - unfortunately it's a Syntax error – Mr_and_Mrs_D Nov 16 '15 at 20:07
  • 2
    @Mr_and_Mrs_D The difference in time is going to be negligible for only 3 values. Again, I'm not sure what you're looking for if this isn't it. – Morgan Thrapp Nov 16 '15 at 20:07
  • You referred to the timing - and you are wrong, the way in the question is hands down faster. – Mr_and_Mrs_D Nov 16 '15 at 20:17
  • @Mr_and_Mrs_D: The way you're doing it — explicitly processing each of the three element tuple's components — would be faster. Apparently that's what you think "pythonic" means. Morgan's code will handle tuples of any size unchanged and is slightly shorter, which is another equally valid meaning. Unfortunately the generality comes at a price (which is often the case unless it's handled by a Python built-in). – martineau Nov 16 '15 at 20:32
  • @martineau: thanks - that's a possible answer - by pythonic I did not mean speed only - I meant the straightforwardness of `result |= method(j)`. Speed is always a concern in a loop body of course. The `any` answer by @PeterDeGlopper comes closer to the point - but next developer reading the code would be happier to read the code in my question than `any(grouped[0]) for grouped in zip(somelist)`. So there is no `tuple1 | tuple2` finally ? – Mr_and_Mrs_D Nov 16 '15 at 20:39
  • There is no built in element-wise `or` on tuples, no. – Peter DeGlopper Nov 16 '15 at 20:53
  • @Mr_and_Mrs_D: There's nothing built-in that will perform `tuple | tuple` the way you want. If you changed your data representation to bit values, you could effectively `|` as many elements together at once as you wished since Python supports arbitrarily long integers. Lastly, speed is _not_ always a concern, some even think [premature optimization is the root of all evil](https://en.wikipedia.org/wiki/Program_optimization#When_to_optimize). – martineau Nov 16 '15 at 20:54
2

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 ors 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)))
Peter DeGlopper
  • 36,326
  • 7
  • 90
  • 83
  • Hmm - will ponder over this a bit :) Still how would this adapt to the loop ? I mean would it not need even more lines ? – Mr_and_Mrs_D Nov 16 '15 at 19:58
  • This version doesn't need an explicit for loop - the generator expression (`for grouped in zip(some_list)`) takes care of the iteration. – Peter DeGlopper Nov 16 '15 at 20:01
  • See the loop in my question - it's a big loop – Mr_and_Mrs_D Nov 16 '15 at 20:02
  • This is the wrong zipping, not what the OP is asking for. See the last line of the second snippet in the question. – jscs Nov 16 '15 at 20:09
  • @JoshCaswell - how so? The snippet shows an ongoing or of each element of the result tuple, so zipping the list of results tuples should group all the first elements, then all the second, etc. – Peter DeGlopper Nov 16 '15 at 20:14
  • This doesn't use OP's `method` at all. – Morgan Thrapp Nov 16 '15 at 20:16
  • 1
    The question is asking for the pairwise comparison of the initial `result` tuple and the tuple returned from the method: `zip(result, ret_val)`. – jscs Nov 16 '15 at 20:17
  • The initial result tuple is just all `False`. The OP asks for "a tuple containing the `or` result of the individual tuples", which is exactly what this does. Only question is whether or not getting the list of result tuples and then reducing is appropriate compared to maintaining a running sum in the loop. Rework it with ints and it's just the difference between and initial zero Val and iteratively adding or accumulating a list of values and using `sum`. – Peter DeGlopper Nov 16 '15 at 20:25
  • I see what you're saying now. It was me misinterpreting the question. (I was also assuming the initial `False, False, False` was a dummy value for the question, not the real code.) – jscs Nov 16 '15 at 21:08
  • @JoshCaswell - actually, you were right that it was the wrong zipping, I forgot the all-important `*` expansion to make it convolve the tuples. Fixed and expanded. – Peter DeGlopper Nov 16 '15 at 22:35
  • The `result_list.append(method(j))` is what I thought of using after reading the helpful and not so helpful answers and comments here - thanks – Mr_and_Mrs_D Nov 17 '15 at 02:21