6.1. Constructing Arrays#
6.1.1. Array Comprehensions: A Mathematical Approach#
Beyond creating arrays of zeros or random numbers, we often need to construct arrays based on a specific mathematical rule. Julia’s comprehension syntax is a powerful and concise way to do this, closely mirroring mathematical set-builder notation. It’s an elegant alternative to writing a for
loop to populate an array.
The general form is:
A = [ expression for variable1 in range1, variable2 in range2, ... ]
Here, the expression
is evaluated for each combination of values from the given ranges (or any iterable). The results are then collected into a new array whose dimensions are determined by the lengths of the ranges.
# Create a vector containing the first 5 perfect squares.
# The expression `x^2` is evaluated for each `x` in the range `1:5`.
squares = [ x^2 for x in 1:5 ]
5-element Vector{Int64}:
1
4
9
16
25
# Using two variables creates a 2D array (a Matrix).
# This builds a 3x5 matrix where the entry A[i,j] = i + j.
A = [ i+j for i in 1:3, j in 1:5 ]
3×5 Matrix{Int64}:
2 3 4 5 6
3 4 5 6 7
4 5 6 7 8
# Another example: a 3x5 matrix where B[i,j] = i^j.
B = [ i^j for i in 1:3, j in 1:5 ]
3×5 Matrix{Int64}:
1 1 1 1 1
2 4 8 16 32
3 9 27 81 243
6.1.1.1. Filtering with if
#
You can add an if
statement at the end of a comprehension to filter the values. The expression is only evaluated and included in the final array if the condition is true
.
# Create a vector of squares for numbers from 1 to 10,
# but only include the square if the number `x` is a multiple of 3.
multiples_of_3_squared = [ x^2 for x in 1:10 if x % 3 == 0 ]
3-element Vector{Int64}:
9
36
81
6.1.1.2. Specifying the Element Type#
Julia automatically infers the element type of the array from the expression. However, you can explicitly set the type by prefixing the comprehension with the desired type. This is useful for ensuring type stability or controlling memory usage.
# Create a 2x10 matrix of random integers.
# Even though `rand` produces integers here, we explicitly specify
# that the matrix should store them as `Float64` values.
y = Float64[ rand(-5:5) for i in 1:2, j in 1:10 ]
2×10 Matrix{Float64}:
-5.0 1.0 1.0 -1.0 -1.0 3.0 2.0 4.0 1.0 3.0
-3.0 -5.0 2.0 1.0 -4.0 -2.0 4.0 2.0 -2.0 4.0
6.1.2. Generator Expressions for Efficiency#
What if you need to perform a calculation on a sequence of values, but you don’t actually need to store all the values in memory at once? This is common when you want to sum a series or find a maximum value.
By simply removing the square brackets []
from a comprehension, you create a generator expression. Instead of building an array, this creates a special generator object that produces the values on demand. This is often called “lazy” evaluation and can be incredibly memory-efficient.
# This doesn't compute all 10 values and store them.
# Instead, `gen` is an object that knows how to produce the values when asked.
gen = (i*(i-1) for i in 1:10)
Base.Generator{UnitRange{Int64}, var"#11#12"}(var"#11#12"(), 1:10)
# We can iterate over the generator just like an array.
# The values are computed one by one as the loop progresses.
for x in gen
print(x, " ")
end
0 2 6 12 20 30 42 56 72 90
6.1.3. Other Powerful Array Creation Techniques#
6.1.3.1. reshape
#
The reshape
function is a versatile tool that takes an existing collection of elements and pours them into an array of new dimensions without changing the underlying data.
# Take the numbers 1 through 15 and arrange them into a 3x5 matrix.
# Note that Julia is "column-major," meaning it fills the first column completely,
# then the second column, and so on.
reshape(1:15, 3, 5)
3×5 reshape(::UnitRange{Int64}, 3, 5) with eltype Int64:
1 4 7 10 13
2 5 8 11 14
3 6 9 12 15
6.1.3.2. permutedims
#
Since reshape
fills arrays column-by-column, how can we achieve row-by-row filling? A common trick is to reshape
into the transposed dimensions and then use permutedims
to swap the dimensions.
For a 2D matrix, permutedims(A)
is equivalent to the transpose A'
.
# To get a 3x5 matrix filled row-by-row, we first create a 5x3 matrix.
# Then, permuting its dimensions gives us the desired 3x5 layout.
permutedims(reshape(1:15, 5, 3))
3×5 Matrix{Int64}:
1 2 3 4 5
6 7 8 9 10
11 12 13 14 15
6.1.3.3. repeat
#
The repeat
function constructs a larger array by tiling it with copies of a smaller one. You can specify how many times to repeat along each dimension.
# Repeat the elements of the vector `1:2` three times.
repeat(1:2, 3)
6-element Vector{Int64}:
1
2
1
2
1
2
# For multiple dimensions, you can specify inner and outer repetitions.
# Here, we repeat `1:2` 3 times down (inner) and 4 times across (outer).
repeat(1:2, 3, 4)
6×4 Matrix{Int64}:
1 1 1 1
2 2 2 2
1 1 1 1
2 2 2 2
1 1 1 1
2 2 2 2
# You can also repeat an entire matrix.
# `repeat(A, m, n)` will create a larger matrix by tiling A `m` times
# vertically and `n` times horizontally.
A = [1 2; 3 4]
repeat(A, 1, 2) # Repeat 1 time along dimension 1, 2 times along dimension 2.
2×4 Matrix{Int64}:
1 2 1 2
3 4 3 4