Single- and multidimensional arrays
Julia, as in most languages for technical computing, provides an excellent implementation of arrays. Often, technical computing languages place more emphasis on arrays than on other containers. Arrays are not something special in Julia. The library for working with arrays is almost completely implemented in Julia, so its performance depends on the compiler, as well as the performance of other code written in Julia. For this reason, you can also define custom array types by inheriting them from AbstractArray
. For more information about implementing a custom array type, see the section of the manual devoted to the AbstractArray interface.
An array is a collection of objects stored in a multidimensional table. Zero-dimensional arrays are allowed; see this FAQ section. In the most general case, an array can contain objects of the type Any
. For most computing purposes, arrays should contain objects of a more specific type, such as Float64
or Int32
.
Unlike many other languages for technical computing, in Julia, as a rule, programs do not necessarily have to be written in a vectorized style to ensure performance. The Julia compiler uses type inference and generates optimized code for scalar indexing of arrays, which allows you to write programs in a more convenient and readable style without sacrificing performance, and sometimes saving memory.
In Julia, all function arguments are https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_sharing [passed by co-use] (i.e. by pointers). In some languages, arrays are passed by value for technical calculations. This prevents the called object from accidentally changing the value in the caller, but it makes it difficult to avoid unnecessary copying of arrays. According to the convention, if the name of a function ends with the character !
, it means that it will change or delete the value of one or more of its arguments (compare, for example, the functions sort
and sort!
). The called functions must explicitly create copies of the input data so that they do not change. Many functions that do not require changing arguments are implemented by calling a function with the same name, but with the symbol !
at the end, which returns an explicit copy of the created input data.
Main functions
Function | Description |
---|---|
The type of elements in |
|
Number of elements in |
|
Number of dimensions of array |
|
A tuple containing the dimensions of array |
|
The size of the array |
|
A tuple containing valid indexes of the array |
|
The range of acceptable indexes for the dimension |
|
An efficient iterator for iterating through each position in |
|
The step of the array (the distance between the linear indexes of neighboring elements) in the dimension |
|
Tuple of array steps in each dimension |
Creation and initialization
There are many functions available for creating and initializing arrays. In the list of such functions below, there are calls with the argument dims...
can take either a single tuple with dimension sizes, or a set of dimension sizes passed as a variable number of arguments. Most of these functions also take the first argument T
, which defines the type of array elements. If the T
argument is not specified, the default type is Float64
.
Function | Description |
---|---|
Uninitialized dense array |
|
An array of only zeros |
|
An array |
|
Array |
|
The |
|
An array with the same data as in |
|
Copies the array |
|
Copies array |
|
An uninitialized array of the same type as |
|
An array with the same binary data as in |
|
An array with random numbers independently, identically (iid [1]) and evenly distributed values. For floating point types |
|
An array of |
|
The identity matrix of |
|
A range of |
|
Fills the array |
|
An array filled with the value |
Here are some examples illustrating the different ways measurements can be passed to these functions.
julia> zeros(Int8, 2, 3)
2×3 Matrix{Int8}:
0 0 0
0 0 0
julia> zeros(Int8, (2, 3))
2×3 Matrix{Int8}:
0 0 0
0 0 0
julia> zeros((2, 3))
2×3 Matrix{Float64}:
0.0 0.0 0.0
0.0 0.0 0.0
Here (2, 3)
is Tuple
, and the first argument — the element type — is optional (by default, Float64
).
Array literals
Arrays can also be created directly using square brackets. The syntax [A, B, C, ...]
creates a one-dimensional array (that is, a vector), the elements of which are arguments separated by commas. Type of elements (eltype
) of the resulting array is determined automatically based on the types of arguments inside the brackets. If all arguments are of the same type, this will be the eltype' type. If they all have a common promotion type, they are converted to this type using the function `convert
, and this type is the element type (eltype
) of the array. Otherwise, a heterogeneous array is created that can contain anything — Vector{Any}
. It can also be a literal []
without arguments. An array literal can be typed using the syntax T[A, B, C, ...]
, where 'T` is the type.
julia> [1, 2, 3] # Массив значений типа `Int`
3-element Vector{Int64}:
1
2
3
julia> promote(1, 2.3, 4//5) # This combination of Int, Float64, and Rational values is promoted to Float64
(1.0, 2.3, 0.8)
julia> [1, 2.3, 4//5] # This will be the type of elements in this array.
3-element Vector{Float64}:
1.0
2.3
0.8
julia> Float32[1, 2.3, 4//5] # Specifying the item type manually
3-element Vector{Float32}:
1.0
2.3
0.8
julia> []
Any[]
Concatenation
If arguments in square brackets are separated by single semicolons (;
) or line breaks, rather than commas, then their contents are joined vertically (the arguments themselves are not used as elements).
julia> [1:2, 4:5] # Имеет запятую, поэтому конкатенация не производится. Элементами являются сами диапазоны
2-element Vector{UnitRange{Int64}}:
1:2
4:5
julia> [1:2; 4:5]
4-element Vector{Int64}:
1
2
4
5
julia> [1:2
4:5
6]
5-element Vector{Int64}:
1
2
4
5
6
If the arguments are separated by tabs, spaces, or pairs of colons, then their contents are joined horizontally.
julia> [1:2 4:5 7:8]
2×3 Matrix{Int64}:
1 4 7
2 5 8
julia> [[1,2] [4,5] [7,8]]
2×3 Matrix{Int64}:
1 4 7
2 5 8
julia> [1 2 3] # Числа также могут объединяться по горизонтали
1×3 Matrix{Int64}:
1 2 3
julia> [1;; 2;; 3;; 4]
1×4 Matrix{Int64}:
1 2 3 4
Single semicolons (or line breaks) can be combined with spaces (or tabs) to concatenate both horizontally and vertically.
julia> [1 2
3 4]
2×2 Matrix{Int64}:
1 2
3 4
julia> [zeros(Int, 2, 2) [1; 2]
[3 4] 5]
3×3 Matrix{Int64}:
0 0 1
0 0 2
3 4 5
julia> [[1 1]; 2 3; [4 4]]
3×2 Matrix{Int64}:
1 1
2 3
4 4
Spaces (and tab characters) take precedence over semicolons, so horizontal concatenation is performed first, and then vertically. However, if pairs of semicolons are used for horizontal concatenation, vertical concatenations are performed first, and then the result is combined horizontally.
julia> [zeros(Int, 2, 2) ; [3 4] ;; [1; 2] ; 5]
3×3 Matrix{Int64}:
0 0 1
0 0 2
3 4 5
julia> [1:2; 4;; 1; 3:4]
3×2 Matrix{Int64}:
1 1
2 3
4 4
Just as ;
and ;;
serve for concatenation in the first and second dimensions, you can use more semicolons: the general principle of operation will be similar. The number of semicolons in the separator determines the dimension: ;;;
it is used for concatenation in the third dimension, ;;;;
— in the fourth and so on. The fewer semicolons there are, the higher the priority, so the lower dimensions are usually combined first.
julia> [1; 2;; 3; 4;; 5; 6;;;
7; 8;; 9; 10;; 11; 12]
2×3×2 Array{Int64, 3}:
[:, :, 1] =
1 3 5
2 4 6
[:, :, 2] =
7 9 11
8 10 12
As stated earlier, spaces (and tab characters) used for horizontal concatenation take precedence over any number of semicolons. Therefore, when defining high-dimensional arrays, you can specify the strings first, and then the elements, so that the text representation corresponds to the structure of the array.:
julia> [1 3 5
2 4 6;;;
7 9 11
8 10 12]
2×3×2 Array{Int64, 3}:
[:, :, 1] =
1 3 5
2 4 6
[:, :, 2] =
7 9 11
8 10 12
julia> [1 2;;; 3 4;;;; 5 6;;; 7 8]
1×2×2×2 Array{Int64, 4}:
[:, :, 1, 1] =
1 2
[:, :, 2, 1] =
3 4
[:, :, 1, 2] =
5 6
[:, :, 2, 2] =
7 8
julia> [[1 2;;; 3 4];;;; [5 6];;; [7 8]]
1×2×2×2 Array{Int64, 4}:
[:, :, 1, 1] =
1 2
[:, :, 2, 1] =
3 4
[:, :, 1, 2] =
5 6
[:, :, 2, 2] =
7 8
Although both spaces (or tab characters) and the characters ;;
denote concatenation in the second dimension. They cannot be used together in a single array expression unless double semicolons serve as a continuation character. In this case, they allow concatenation on multiple lines (the newline character is not interpreted as vertical concatenation).
julia> [1 2 ;;
3 4]
1×4 Matrix{Int64}:
1 2 3 4
Using semicolons at the end, you can add another dimension of length 1.
julia> [1;;]
1×1 Matrix{Int64}:
1
julia> [2; 3;;;]
2×1×1 Array{Int64, 3}:
[:, :, 1] =
2
3
A more universal means for concatenation is the function cat
. Below are a number of auxiliary functions and their corresponding short notation forms.
Syntax | Function | Description |
---|---|---|
Combines input arrays by |
||
|
The short form of the call entry is |
|
|
The short form of the call entry is |
|
|
Concatenation both vertically and horizontally |
|
|
Concatenation over n dimensions simultaneously, where the number of semicolons indicates the dimension to be concatenated |
Typed array literals
An array with elements of a certain type can be created using the syntax T[A, B, C, ...]
. As a result, a one-dimensional array with elements of type T
will be created: A
, B
, C
, etc. For example, the expression Any[x, y, z]
creates a heterogeneous array that can contain any values.
Before the concatenation expression, you can also specify the type that the elements will have.
julia> [[1 2] [3 4]]
1×4 Matrix{Int64}:
1 2 3 4
julia> Int8[[1 2] [3 4]]
1×4 Matrix{Int8}:
1 2 3 4
Inclusions
Inclusions are a universal and efficient way to create arrays. The inclusion syntax is similar to how the creation of a set is written in mathematics.:
A = [ F(x, y, ...) for x=rx, y=ry, ... ]
The meaning of this notation is that the expression F(x,y,...)
is calculated for variables x
, y
, etc., which sequentially take each of the values in the specified list. Values can be specified as any iterable object, but these are usually ranges, such as 1:n
or 2:(n-1)
, or explicitly specified arrays of values, such as [1.2, 3.4, 5.7]
. The result is an N-dimensional dense array with measurements that are the result of concatenation of measurements of ranges of variables rx
, ry
, etc., and with each calculation F(x,y,...)
returns a scalar value.
The following example calculates the weighted average of the current element and its neighboring elements on the left and right according to a one-dimensional table. :
julia> x = rand(8)
8-element Array{Float64,1}:
0.843025
0.869052
0.365105
0.699456
0.977653
0.994953
0.41084
0.809411
julia> [ 0.25*x[i-1] + 0.5*x[i] + 0.25*x[i+1] for i=2:length(x)-1 ]
6-element Array{Float64,1}:
0.736559
0.57468
0.685417
0.912429
0.8446
0.656511
As in the case of array literals, the type of the resulting array depends on the types of calculated elements. The type can also be explicitly specified before inclusion. For example, in order for the result to have single precision, you can write the following expression:
Float32[ 0.25*x[i-1] + 0.5*x[i] + 0.25*x[i+1] for i=2:length(x)-1 ]
Generative expressions
The inclusion can also be written without enclosing it in parentheses. The result is an object called a generator. By iterating over such an object, you can get values on request without storing the array in memory in advance (see the section Iteration). For example, the following expression summarizes a sequence without allocating memory:
julia> sum(1/n^2 for n=1:1000)
1.6439345666815615
When writing a generator expression with multiple dimensions in the argument list, the generator must be separated from subsequent arguments by parentheses.:
julia> map(tuple, 1/(i+j) for i=1:2, j=1:2, [1:4;])
ERROR: syntax: invalid iteration specification
All comma-separated expressions after for
are considered ranges. By adding parentheses, you can specify a third argument for the function. map
:
julia> map(tuple, (1/(i+j) for i=1:2, j=1:2), [1 3; 2 4])
2×2 Matrix{Tuple{Float64, Int64}}:
(0.5, 1) (0.333333, 3)
(0.333333, 2) (0.25, 4)
Generators are implemented through internal functions. As in other cases of using internal functions in a language, variables from the external scope can be captured in such functions. For example, sum(p[i] - q[i] for i=1:n)
captures three variables from the external area: p
, q
and n
. Captured variables can affect performance; see performance tips.
Ranges in generators and inclusions may depend on previous ranges; several keywords are used for this purpose.:
julia> [(i, j) for i=1:3 for j=1:i]
6-element Vector{Tuple{Int64, Int64}}:
(1, 1)
(2, 1)
(2, 2)
(3, 1)
(3, 2)
(3, 3)
In such cases, the result is always one-dimensional.
The generated values can be filtered using the if
keyword:
julia> [(i, j) for i=1:3 for j=1:i if i+j == 4]
2-element Vector{Tuple{Int64, Int64}}:
(2, 2)
(3, 1)
Indexing
The syntax for indexing an n-dimensional array A
has the following general form.
X = A[I_1, I_2, ..., I_n]
Here, I_k
can be a scalar integer, an array of integers, or any other supported index. These include Colon
(:
) to select all indexes in the entire dimension, ranges of the form a:c
or a:b:c
for selecting a continuous segment or a segment with a specified step and arrays of boolean values for selecting elements by indexes `true'.
If all indexes are scalar, then X
is a separate element from the array `A'. Otherwise, `X' is an array whose number of dimensions is equal to the sum of the dimensions of all indexes.
For example, if all the indexes of I_k
are vectors, then X
will have the form (length(I_1), length(I_2), ..., length(I_n))
, and at the position i_1, i_2, ..., i_n
the variable X
will be contains the value A[I_1[i_1], I_2[i_2], ..., I_n[i_n]]
.
Example:
julia> A = reshape(collect(1:16), (2, 2, 2, 2))
2×2×2×2 Array{Int64, 4}:
[:, :, 1, 1] =
1 3
2 4
[:, :, 2, 1] =
5 7
6 8
[:, :, 1, 2] =
9 11
10 12
[:, :, 2, 2] =
13 15
14 16
julia> A[1, 2, 1, 1] # только скалярные индексы
3
julia> A[[1, 2], [1], [1, 2], [1]] # все индексы являются векторами
2×1×2×1 Array{Int64, 4}:
[:, :, 1, 1] =
1
2
[:, :, 2, 1] =
5
6
julia> A[[1, 2], [1], [1, 2], 1] # индексы разных типов
2×1×2 Array{Int64, 3}:
[:, :, 1] =
1
2
[:, :, 2] =
5
6
Please note that in the last two cases the sizes of the resulting arrays are different.
If I_1
is changed to a two-dimensional matrix, X
becomes an n+1
-dimensional array of the form (size(I_1, 1), size(I_1, 2), length(I_2), ..., length(I_n))
. The introduction of the matrix adds one dimension.
Example:
julia> A = reshape(collect(1:16), (2, 2, 2, 2));
julia> A[[1 2; 1 2]]
2×2 Matrix{Int64}:
1 2
1 2
julia> A[[1 2; 1 2], 1, 2, 1]
2×2 Matrix{Int64}:
5 6
5 6
At the position i_1, i_2, i_3, ..., i_{n+1}
contains the index value A[I_1[i_1, i_2], I_2[i_3], ..., I_n[i_{n+1}]]
. All measurements with scalar indexes are discarded. For example, if J
is an array of indexes, then the result of A[2, J, 3]
will be an array of size size(J)'. Its `j’th element is filled with the value `A[2, J[j], 3]
.
A special syntax element is the keyword end', which represents the last index of each dimension in parentheses, determined by the size of the most deeply nested indexed array. Indexing syntax without the 'end
keyword is equivalent to calling getindex
:
X = getindex(A, I_1, I_2, ..., I_n)
Example:
julia> x = reshape(1:16, 4, 4)
4×4 reshape(::UnitRange{Int64}, 4, 4) with eltype Int64:
1 5 9 13
2 6 10 14
3 7 11 15
4 8 12 16
julia> x[2:3, 2:end-1]
2×2 Matrix{Int64}:
6 10
7 11
julia> x[1, [2 3; 4 1]]
2×2 Matrix{Int64}:
5 9
13 1
Assignment by index
The syntax for assigning values to elements of an n-dimensional array A
has the following general form.
A[I_1, I_2, ..., I_n] = X
Here, I_k
can be a scalar integer, an array of integers, or any other supported index. These include Colon
(:
) to select all indexes in the entire dimension, ranges of the form a:c
or a:b:c
to select a continuous segment or a segment with a specified step and arrays of boolean values to select elements by indexes `true'.
If all indexes I_k
are integers, the value at the position I_1, I_2, ..., I_n
of the array A
is overwritten by the value X
, the type of which is converted using the function if necessary. convert
to eltype
of the array `A'.
If at least one index I_k
is itself an array, the expression X
in the right part must also be an array of the same shape as the result of indexing A[I_1, I_2, ..., I_n]
, or a vector with the same number of elements. The value at the position I_1[i_1], I_2[i_2], ..., I_n[i_n]
of the array A
is overwritten by the value X[i_1, i_2, ..., i_n]
, the type of which is converted if necessary. The element-wise assignment operator .=
can be used for translates X
to selected positions:
A[I_1, I_2, ..., I_n] .= X
As in the case of indexing, the keyword end
can represent the last index of each dimension in parentheses, determined by the size of the array whose elements are assigned values. The syntax of an index assignment without the end
keyword is equivalent to calling a function setindex!
:
setindex!(A, X, I_1, I_2, ..., I_n)
Example:
julia> x = collect(reshape(1:9, 3, 3))
3×3 Matrix{Int64}:
1 4 7
2 5 8
3 6 9
julia> x[3, 3] = -9;
julia> x[1:2, 1:2] = [-1 -4; -2 -5];
julia> x
3×3 Matrix{Int64}:
-1 -4 7
-2 -5 8
3 6 -9
Supported index types
In the expression A[I_1, I_2, ..., I_n]
, each element of I_k
can be a scalar index, an array of scalar indexes, or an object that represents an array of scalar indexes and can be transformed into it by a function to_indices
:
-
Scalar indexes by default include:
-
Integers that are not logical
-
CartesianIndex{N}
s, which behave like anN
-tuple of integers spanning multiple dimensions (see below for more details)
-
-
Arrays of scalar indexes include:
-
Vectors and multidimensional arrays of integers
-
Empty arrays of the type
[]
that are not used to select elements, for exampleA[[]]
(not to be confused withA[]
) -
Ranges of the type
a:c
ora:b:c
to select a continuous segment or a segment with a specified step froma
toc
(inclusive) -
Any custom array of scalar indexes that is a subtype of
AbstractArray
-
Arrays
CartesianIndex{N}
(see details below)
-
-
To objects that represent an array of scalar indexes and can be transformed into it by a function
to_indices
, by default refer to:-
Colon()
(:
), which represents all indices within an entire dimension or across the entire array -
Arrays of boolean values for selecting elements by
true
indexes (see below for details).
-
Some examples:
julia> A = reshape(collect(1:2:18), (3, 3))
3×3 Matrix{Int64}:
1 7 13
3 9 15
5 11 17
julia> A[4]
7
julia> A[[2, 5, 8]]
3-element Vector{Int64}:
3
9
15
julia> A[[1 4; 3 8]]
2×2 Matrix{Int64}:
1 7
5 15
julia> A[[]]
Int64[]
julia> A[1:2:5]
3-element Vector{Int64}:
1
5
9
julia> A[2, :]
3-element Vector{Int64}:
3
9
15
julia> A[:, 3]
3-element Vector{Int64}:
13
15
17
julia> A[:, 3:3]
3×1 Matrix{Int64}:
13
15
17
Cartesian indexes
Special object CartesianIndex{N}
represents a scalar index that acts as a tuple of N
integer values spanning multiple dimensions. For example:
julia> A = reshape(1:32, 4, 4, 2);
julia> A[3, 2, 1]
7
julia> A[CartesianIndex(3, 2, 1)] == A[3, 2, 1] == 7
true
This in itself may seem rather trivial: CartesianIndex simply combines several integer values into a single object representing a multidimensional index. However, in combination with other forms of indexing and iterators that produce CartesianIndex objects, very elegant and efficient code can be obtained. See the section Iteration below. A number of more complex examples can be found in https://julialang.org/blog/2016/02/iteration [this blog entry is about multidimensional algorithms and iteration].
Arrays of CartesianIndex' objects are also supported.{N}
. Such an array represents a collection of scalar indexes, each of which covers N
dimensions. This makes possible a form of indexing, sometimes called point indexing. For example, it provides top-down access to diagonally positioned elements from the first "page" of array A
:
julia> page = A[:, :, 1]
4×4 Matrix{Int64}:
1 5 9 13
2 6 10 14
3 7 11 15
4 8 12 16
julia> page[[CartesianIndex(1, 1),
CartesianIndex(2, 2),
CartesianIndex(3, 3),
CartesianIndex(4, 4)]]
4-element Vector{Int64}:
1
6
11
16
This can be expressed much more simply with point translation in combination with the usual integer index (instead of extracting the first page (page
) from the array A
in a separate action). This approach can also be used in combination with :
to simultaneously extract diagonals from two pages.:
julia> A[CartesianIndex.(axes(A, 1), axes(A, 2)), 1]
4-element Vector{Int64}:
1
6
11
16
julia> A[CartesianIndex.(axes(A, 1), axes(A, 2)), :]
4×2 Matrix{Int64}:
1 17
6 22
11 27
16 32
The |
Logical indexing
Indexing based on an array of boolean values, often called logical indexing or logical mask indexing, allows you to select items by indexes with the value true'. Indexing by the vector of logical values `B
is essentially the same as indexing by the vector of integer values returned by the method findall(B)
. Similarly, indexing by an N
-dimensional array of boolean values is essentially the same as indexing by a vector of objects CartesianIndex{N}
with values of true'. The logical index must be an array of the same shape as the indexed dimension, or it must be the only index and correspond to the shape of a one-dimensional representation with a modified shape for the indexed array. As a rule, it is more efficient to use arrays of boolean values as indexes directly than to call the method first. `findall
.
julia> x = reshape(1:12, 2, 3, 2)
2×3×2 reshape(::UnitRange{Int64}, 2, 3, 2) with eltype Int64:
[:, :, 1] =
1 3 5
2 4 6
[:, :, 2] =
7 9 11
8 10 12
julia> x[:, [true false; false true; true false]]
2×3 Matrix{Int64}:
1 5 9
2 6 10
julia> mask = map(ispow2, x)
2×3×2 Array{Bool, 3}:
[:, :, 1] =
1 0 0
1 1 0
[:, :, 2] =
0 0 0
1 0 0
julia> x[mask]
4-element Vector{Int64}:
1
2
4
8
julia> x[vec(mask)] == x[mask] # Indexing can also be performed using a single logical vector.
true
Number of indexes
Cartesian indexing
To index an N
-dimensional array, exactly N
indexes are usually used: each index is used to select positions in its dimension. For example, in a three-dimensional array A = rand(4, 3, 2)
, the index A[2, 3, 1]
selects a number in the second row of the third column on the first "page" of the array. This approach is often referred to as cartesian indexing.
Linear indexing
If only one index i
is specified, it does not represent a position in a specific dimension of the array. Instead, it serves to select the i’th element in the entire array, expanded linearly by columns. This approach is called linear indexing. The array is represented as if it were transformed into a one-dimensional vector using the function `vec
.
julia> A = [2 6; 4 7; 3 1]
3×2 Matrix{Int64}:
2 6
4 7
3 1
julia> A[5]
7
julia> vec(A)[5]
7
The linear index in array A
can be converted to CartesianIndex
for Cartesian indexing using CartesianIndices(A)[i]
(see type description CartesianIndices
), and a set of N
Cartesian indexes can be converted to a linear index using LinearIndices(A)[i_1, i_2, ..., i_N]
(see type description LinearIndices
).
julia> CartesianIndices(A)[5]
CartesianIndex(2, 2)
julia> LinearIndices(A)[2, 2]
5
It is important to note that these conversions vary significantly in terms of performance. To convert a linear index into a set of Cartesian indexes, division and remainder are required, while the reverse requires only multiplication and addition. In modern processors, integer division can be performed 10-50 times slower than multiplication. While some arrays, such as the base type Array
, occupy a linear area of memory and directly use a linear index in their implementation, others, for example Diagonal
, require a full set of Cartesian indexes to search for elements (to determine the indexing style, you can use IndexStyle
).
When iterating over all indexes of an array, it is better to iterate over |
Omitted and additional indexes
In addition to linear indexing, in some situations an N
-dimensional array can be indexed using more or less than N
indexes.
Indexes can be omitted if the last non-indexed dimensions have a single length. In other words, the last indexes can be omitted only if they have the only possible value within the acceptable range in the indexing expression. For example, a four-dimensional array of size (3, 4, 2, 1)
It can be indexed using three indexes, since the dimension being omitted (the fourth) has a unit length. Keep in mind that linear indexing takes precedence over this rule.
julia> A = reshape(1:24, 3, 4, 2, 1)
3×4×2×1 reshape(::UnitRange{Int64}, 3, 4, 2, 1) with eltype Int64:
[:, :, 1, 1] =
1 4 7 10
2 5 8 11
3 6 9 12
[:, :, 2, 1] =
13 16 19 22
14 17 20 23
15 18 21 24
julia> A[1, 3, 2] # Опускает четвертое измерение (длиной 1)
19
julia> A[1, 3] # Пытается опустить измерения 3 и 4 (длиной 2 и 1)
ERROR: BoundsError: attempt to access 3×4×2×1 reshape(::UnitRange{Int64}, 3, 4, 2, 1) with eltype Int64 at index [1, 3]
julia> A[19] # Линейное индексирование
19
You can omit all indexes using the expression A[]
- this is an easy way to get the only element in the array and at the same time check if there was only one element in it.
Similarly, you can specify more than N
indexes if all indexes over the dimension of the array are equal to 1
(or, more formally, they are the first and only element of axes(A, d)
, where d
is the number of the corresponding dimension). For example, it allows you to index vectors as single-column matrices.:
julia> A = [8, 6, 7]
3-element Vector{Int64}:
8
6
7
julia> A[2, 1]
6
Iteration
The following methods are recommended for iterating over the entire array:
for a in A
# Какие-либо действия с элементом a
end
for i in eachindex(A)
# Какие-либо действия с элементом i и (или) A[i]
end
The first construction is used when the value of each element is required, but not its index. In the second construction, i
will be of type Int
if A
is a fast linear indexing array, or type CartesianIndex
otherwise.:
julia> A = rand(4, 3);
julia> B = view(A, 1:3, 2:3);
julia> for i in eachindex(B)
@show i
end
i = CartesianIndex(1, 1)
i = CartesianIndex(2, 1)
i = CartesianIndex(3, 1)
i = CartesianIndex(1, 2)
i = CartesianIndex(2, 2)
i = CartesianIndex(3, 2)
Unlike |
Array Styles
When creating a custom type AbstractArray
you can specify that fast linear indexing is used as follows:
Base.IndexStyle(::Type{<:MyArray}) = IndexLinear()
As a result, iteration using eachindex
over the myArray
array will be performed using integer values. If you do not specify this style, the default value `IndexCartesian()' is used.
Operators and functions for arrays and vectors
The following operators are supported for arrays:
-
Unary arithmetic:
-
,+
-
Binary arithmetic:
-
,+
,*
,/
,\
,^
-
Comparisons:
==
,!=
,≈
(isapprox
),≉
For the convenience of vectorization of mathematical and other operations, Julia supports dot syntax f.(args...)
, for example sin.(x)
or min.(x, y)
. It provides element-wise operations with arrays or combinations of arrays and scalar values (operation broadcasts). An additional advantage is the ability to combine into a single loop when used in combination with other point calls, such as sin.(cos.(x))
.
In addition, all binary operators have dot version, which can be applied to arrays (and combinations of arrays and scalar values) in such cases translation operations with concatenation, for example, z .== sin.(x .* y)
.
Note that comparison operators, such as ==
, are applied to entire arrays, yielding a single logical result. For an element-by-element comparison, use dot operators, for example .==
. (In the case of comparison operations such as <
, only the element-by-element version .<
is applicable to arrays.)
Also note the difference between calling max.(a,b)
, which translates (broadcast
) function max
by a
and b
element by element, and maximum(a)
, which finds the largest value in a'. Similarly, the calls `min.(a, b)
and minimum(a)
differ.
Broadcast
Sometimes it is necessary to perform an element-by-element binary operation for arrays of different sizes, for example, to add a vector to each column of the matrix. Replicating a vector to the size of a matrix is an inefficient way.
julia> a = rand(2, 1); A = rand(2, 3);
julia> repeat(a, 1, 3) + A
2×3 Array{Float64,2}:
1.20813 1.82068 1.25387
1.56851 1.86401 1.67846
When the measurements are large, a lot of resources are consumed, so Julia has a function broadcast
, which expands individual dimensions in array arguments to the size of the corresponding dimensions of another array without using additional memory, and then applies the specified function element-wise:
julia> broadcast(+, a, A)
2×3 Array{Float64,2}:
1.20813 1.82068 1.25387
1.56851 1.86401 1.67846
julia> b = rand(1,2)
1×2 Array{Float64,2}:
0.867535 0.00457906
julia> broadcast(+, a, b)
2×2 Array{Float64,2}:
1.71056 0.847604
1.73659 0.873631
Dot operators such as .+
and .*
are equivalent to broadcast
calls (except that they are combined as described above). There is also a function broadcast!
to explicitly specify the location to which the translation should be performed (you can also use the assignment operator .=
to call it with a join). In fact, the call is f.(args...)
is equivalent to calling broadcast(f, args...)
, providing a convenient syntax for translating any function (dot syntax). Nested point calls f.(...)
(including calls .+
, etc.) automatically combined into a single broadcast
call.
In addition, the applicability of the function broadcast
is not limited to arrays (see the function documentation). It also works with scalar values, tuples, and other collections. By default, only certain types of arguments are considered scalar, including, but not limited to, Number
, String', `Symbol
, Type
, Function
and some standard single objects such as missing
and `nothing'. All other arguments are iterated or indexed element by element.
julia> convert.(Float32, [1, 2])
2-element Vector{Float32}:
1.0
2.0
julia> ceil.(UInt8, [1.2 3.4; 5.6 6.7])
2×2 Matrix{UInt8}:
0x02 0x04
0x06 0x07
julia> string.(1:3, ". ", ["First", "Second", "Third"])
3-element Vector{String}:
"1. First"
"2. Second"
"3. Third"
Sometimes it is necessary to prevent translation when iterating over the elements of a container (for example, an array), which usually allows translation. If you place such a container in another container (for example, in an element Tuple
), it will be treated as a single value during translation.
julia> ([1, 2, 3], [4, 5, 6]) .+ ([1, 2, 3],)
([2, 4, 6], [5, 7, 9])
julia> ([1, 2, 3], [4, 5, 6]) .+ tuple([1, 2, 3])
([2, 4, 6], [5, 7, 9])
Realization
The base array type in Julia is an abstract type AbstractArray{T,N}
. It is parameterized by the number of dimensions N
and the type of elements T
. AbstractVector
and AbstractMatrix
are aliases for one-dimensional and two-dimensional cases. Operations with AbstractArray are defined using high-level operators and functions so that they do not depend on how the array is stored in memory. They usually work correctly for any particular array implementation.
The AbstractArray
type includes anything that even remotely resembles an array, and the implementation may differ significantly from traditional arrays. For example, elements can be calculated on request rather than stored in memory. However, any particular type of AbstractArray'{T,N}
should usually implement at least a function size(A)
(returning a tuple of elements of type Int
), method getindex(A, i)
and the function getindex(A, i1, ..., iN)
. Mutable arrays must also implement the function setindex!
. It is desirable that the time complexity of these operations be close to constant, otherwise some functions for working with arrays may take an unexpectedly long time to execute. Specific types should generally also provide a method. similar(A
, which is used to place an identical array for the function in memory copy
and other external operations. Regardless of the internal representation of the AbstractArray{T,N}
T
is the type of object returned with integer indexing (A[1, ..., 1]
with a non-empty A
), and N
should be the length of the tuple returned by the function size
. For more information about defining custom implementations of `AbstractArray', see see the Array interface guide in the chapter on interfaces.
'DenseArray' is an abstract subtype of the AbstractArray
type for all arrays whose elements are stored as a continuous sequence in columns (see additional notes in the performance tips). Array
is a specific instance of DenseArray
; Vector
and Matrix
are aliases for one-dimensional and two-dimensional cases. In addition to the operations required for all subtypes of AbstractArray, very few special operations are implemented for Array; the library for working with arrays is basically universal, so that all user arrays function in a similar way.
SubArray
is a specialization of the AbstractArray' type, which uses for indexing the same memory area that the original array occupies, instead of copying it. The `SubArray
object is created using the function view
, which is called in the same way as getindex
(with an array and a set of indexes as arguments). The result of the function view
looks the same as the result 'getindex`, but the data remains in place. Function view
stores the input index vectors in a SubArray
object, which can then be used to index the source array indirectly. If you put a macro '@views` before an expression or block of code, any slice of the array array[...The ]
in this expression will be transformed to create a SubArray
representation.
BitArray
are compact "packed" logical arrays that store one bit for each logical value. They can be used in the same way as arrays Array{Bool}
(to store a logical value in which one byte is required), and convert to such arrays or vice versa using Array(bitarray)
and BitArray(array)
, respectively.
An array with a specified step is an array whose elements are stored in memory at a certain interval (step). An array with a specified step can be passed along with a supported element type to an external library (not related to Julia), such as BLAS or LAPACK, simply by passing its pointer (pointer
) and a step for each dimension. stride(A, d)
is the distance between the elements in dimension d'. For example, the elements of an array of the built-in type `Array
returned by calling rand(5,7,2)
are placed in memory continuously in columns. This means that the step in the first dimension, i.e. the distance between the elements of the same column, is 1
:
julia> A = rand(5, 7, 2);
julia> stride(A, 1)
1
The step in the second dimension is the distance between the elements in the same row, skipping the elements in the column (5
). Similarly, the distance between two "pages" (in the third dimension) requires skipping 5*7 == 35
elements. Steps (strides
) arrays are a tuple of these three numbers:
julia> strides(A)
(1, 5, 35)
In this case, the number of elements skipped in memory corresponds to the number of linear indexes skipped. This is true only for continuous arrays such as Array
(and other subtypes of DenseArray
), but is not the case in general. Representations with range indexes are a good example of _connected arrays with a given step. Consider the example of V = @view A[1:3:4, 2:2:6, 2:-1:1]
. The representation V
refers to the same memory area as the array A', but some elements are skipped and reordered. The step of the first measurement `V
is `3', since only every third row is selected from the source array:
julia> V = @view A[1:3:4, 2:2:6, 2:-1:1];
julia> stride(V, 1)
3
Similarly, every second column is selected from the original array A
, so when switching between indexes in the second dimension, two columns of five elements should be skipped.:
julia> stride(V, 2)
10
The third dimension is more interesting because the order is reversed here! To move from the first "page" to the second, it is necessary to follow the memory in the back direction, therefore, the step in this dimension is negative!
julia> stride(V, 3)
-35
This means that the pointer
to represent V
actually points to the middle of the memory block occupied by the array A
, and refers to elements in memory both forward and backward. For more information about defining your own arrays with a given step, see interface guide for arrays with a given step. StridedVector
and StridedMatrix
are convenient aliases for many built-in array types that are considered arrays with a given step. They allow you to select specific implementations that invoke specialized, optimized BLAS and LAPACK functions using only a pointer and the number of array steps.
It is important to emphasize that the array steps are related to memory offsets, not indexing. If you need to convert linear (single) indexing to Cartesian (multiple) or vice versa, see the description of the types. LinearIndices
and CartesianIndices
.