How do I take an elementwise OR of several matrices in Julia?
Asked Answered
H

1

6

I have a several boolean matrices, and I want a resulting matrix that indicates if any of the elements in that position of those matrices are true. Is there a single function in the Julia language that would allow me to obtain an elementwise OR over an arbitrary number of matrices?

# My data
a = Bool[1 0; 1 1]
b = Bool[0 0; 1 1]
c = Bool[0 0; 0 0]
d = Bool[0 0; 1 1]

# Arrays of Bool Arrays
z1 = [a]
z2 = [a, b]
z3 = [b, c, d]
z4 = [a, b, c, d]
z100 = [rand(Bool, 2, 2) for i in 1:100]

# Expected
julia> some_function(z1)
2×2 BitMatrix:
 1  0
 1  1

julia> some_function(z2)
2×2 BitMatrix:
 1  0
 1  1

julia> some_function(z3)
2×2 BitMatrix:
 0  0
 1  1

julia> some_function(z4)
2×2 BitMatrix:
 1  0
 1  1

julia> some_function(z100)
2×2 BitMatrix:
 1  1
 1  1

This question was originally asked on Julia Slack.

Holdall answered 3/11, 2021 at 17:58 Comment(0)
H
11

The straightforward approach to this in Julia uses broadcasting. The OR operator in Julia is |. To broadcast an operator like this, we can prefix it with a dot as follows.

julia> .|(a)
2×2 BitMatrix:
 1  0
 1  1

julia> .|(a,b)
2×2 BitMatrix:
 1  0
 1  1

julia> .|(a,b,c)
2×2 BitMatrix:
 1  0
 1  1

julia> .|(a,b,c,d)
2×2 BitMatrix:
 1  0
 1  1

Indicating each matrix manually is tedious. To avoid this, we can use the splat operator which will take elements in an iterator and turn them each into a distinct argument for the function being called.

julia> .|(z1...)
2×2 BitMatrix:
 1  0
 1  1

julia> .|(z2...)
2×2 BitMatrix:
 1  0
 1  1

julia> .|(z3...)
2×2 BitMatrix:
 0  0
 1  1

julia> .|(z4...)
2×2 BitMatrix:
 1  0
 1  1

julia> .|(z100...)
2×2 BitMatrix:
 1  1
 1  1

Note that broadcasting allows for expansion of some of the arguments so all the arguments do not need to be the same shape.

julia> .|(z4..., [1 0])
2×2 Matrix{Int64}:
 1  0
 1  1

julia> .|(z4..., [0 1])
2×2 Matrix{Int64}:
 1  1
 1  1

julia> .|(z4..., [0, 1])
2×2 Matrix{Int64}:
 1  0
 1  1

julia> .|(z4..., [1, 0])
2×2 Matrix{Int64}:
 1  1
 1  1

julia> .|(z4..., 0)
2×2 Matrix{Int64}:
 1  0
 1  1

julia> .|(z4..., 1)
2×2 Matrix{Int64}:
 1  1
 1  1

Since the above solutions use broadcasting, they are quite general. If we constrain the problem such that all the boolean matrices must be the same size, then we can take advantage of short circuit evaluation. Once we find a 1 or true value in any position, we do not need to check the elements in the same position of subsequent matrices. To implement this we will use the any function along with an array comprehension.

julia> short_circuit_or(z...) = short_circuit_or(z)
short_circuit_or (generic function with 1 method)

julia> short_circuit_or(z::Tuple) = [
           any(x->x[ind], z) for ind in CartesianIndices(first(z))
       ]
short_circuit_or (generic function with 2 methods)

julia> short_circuit_or(a,b,c)
2×2 Matrix{Bool}:
 1  0
 1  1

julia> short_circuit_or(z4...)
2×2 Matrix{Bool}:
 1  0
 1  1

julia> short_circuit_or(z1)
2×2 Matrix{Bool}:
 1  0
 1  1

julia> short_circuit_or(z2)
2×2 Matrix{Bool}:
 1  0
 1  1

julia> short_circuit_or(z3)
2×2 Matrix{Bool}:
 0  0
 1  1

julia> short_circuit_or(z4)
2×2 Matrix{Bool}:
 1  0
 1  1

julia> short_circuit_or(z100)
2×2 Matrix{Bool}:
 1  1
 1  1

As demonstrated by these benchmarks, short circuit evaluation can save time.

julia> using BenchmarkTools

julia> @btime .|($z100...)
  3.032 ms (24099 allocations: 1.91 MiB)
2×2 BitMatrix:
 1  1
 1  1

julia> @btime short_circuit_or($z100)
  76.413 ns (1 allocation: 96 bytes)
2×2 Matrix{Bool}:
 1  1
 1  1
Holdall answered 3/11, 2021 at 17:58 Comment(4)
this is very nice answer! However it misses the code for short_circuit_or - could you add it so other readers can use it?Piceous
Splatting can be quite expensive. reduce(.|, z100) is much faster than .|(z100...).Melise
@PrzemyslawSzufel The code for short_circuit_or is there. There are two methods, one to make calling easier, the other to do the work. These don't have the word "function" in them which might make them harder to spot, but they are right at the top of the last code segment.Inigo
This is a stunningly good answer, not only because it actually answers the question but also because it illustrates how effective splatting and broadcast are for making simple code. Since these are really central features, it is good to have a clear exposition of their use.Inigo

© 2022 - 2024 — McMap. All rights reserved.