Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

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.

like image 831
Mark Kittisopikul Avatar asked Nov 03 '21 17:11

Mark Kittisopikul


1 Answers

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
like image 155
Mark Kittisopikul Avatar answered Sep 20 '22 00:09

Mark Kittisopikul