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])