6.3. Logical Indexing#

6.3.1. Boolean Masks#

We’ve seen how to index an array with integers or ranges, like x[3] or x[2:5]. Logical indexing (also called boolean indexing) offers a different, often more intuitive, way to select elements. Instead of specifying which indices you want, you specify a condition that elements must satisfy.

The syntax is x[mask], where mask is an array of booleans (true or false) with the same length as x. The operation returns a new array containing only the elements of x where the corresponding value in mask is true.

This is equivalent to x[findall(mask)], but it’s more direct, readable, and generally more performant.

# Let's find the integers x where sin(x) is positive.
x = -11:11
y = sin.(x)

# 1. Create a boolean mask: `y .> 0` will be an array of true/false values.
is_positive = y .> 0

# 2. Use the mask to select elements from the original array `x`.
x_where_sin_is_positive = x[is_positive]
11-element Vector{Int64}:
 -11
 -10
  -6
  -5
  -4
   1
   2
   3
   7
   8
   9

This technique is especially powerful for multi-dimensional arrays, allowing you to select entire rows or columns based on a condition.

# Example: Select columns of a matrix whose sum is non-negative.
A = rand(-10:10, 3, 10)
3×10 Matrix{Int64}:
 -4  -6   7   8  -1  -4   5  -4   9   3
 10   0  -6  -8   2  -6  -8   7  -5  -9
 -5   7   1   5  -5   5  -3  10  -6  -9
# 1. Calculate the sum of each column and check if it's >= 0.
# This creates a 1x10 boolean matrix (a "row mask").
column_mask = sum(A, dims=1) .≥ 0
1×10 BitMatrix:
 1  1  1  1  0  0  0  1  0  0
# 2. Use the mask to select columns.
# We use `:` to select all rows.
# We use `column_mask[:]` to flatten the 1x10 mask into a 1D vector,
# which is what Julia expects for indexing a single dimension.
B = A[:, column_mask[:]]
3×5 Matrix{Int64}:
 -4  -6   7   8  -4
 10   0  -6  -8   7
 -5   7   1   5  10

6.3.2. Refactoring Previous Code: The Sieve of Eratosthenes#

Vectorized tools like logical indexing allow us to simplify code we’ve written previously. In our Sieve function, we used a for loop to collect the prime numbers from our boolean array:

Before (with a for loop):

primes = Int64[]
for i = 2:n
    if is_prime[i]
        push!(primes, i)
    end
end

This is perfectly fine, but we can now express the same idea more concisely.

After (with array comprehensions): Using array comprehensions, we can replace the entire part of the code by a single line:

primes = [ i for i = 2:n if prime[i] ]

After (with findall): Alternatively, since the prime numbers are just the indices of the true values in our is_prime array, we can use findall for another direct, one-line solution:

primes = findall(is_prime)

6.3.3. Refactoring Previous Code: Monte Carlo PI Calculation#

Similarly, our dart-throwing simulation for \(\pi\) used a loop to count the number of “hits” inside the unit circle.

Before (with a for loop):

hits = 0
for i = 1:n
    if x[i]^2 + y[i]^2  1
        hits += 1
    end
end

After (with count): The count function is tailor-made for this. We can generate a boolean mask on the fly and count how many true values it contains.

hits = count(@. x^2 + y^2  1) 

6.3.4. Refactoring Previous Code: Checking for a Flush#

Our poker simulation used a loop with a flag variable and a break statement to check if all cards in a hand had the same suit.

Before (with a for loop and a flag):

same_suit = true
for i = 2:5
    if suits[i]  suits[1]
        same_suit = false
        break
    end
end

After (with all): This entire pattern can be replaced with the all function, which checks if every element in a boolean array is true. This version is not just shorter—it more clearly states the code’s intent.

same_suit = all(suits .== suits[1])