Vector{Float64} === Array{Float64,1} && Matrix{Float64} === Array{Float64,2}
true
Kommen wir nun zu den wohl wichtigsten Containern für numerische Mathematik:
Vector{T}
Matrix{T}
mit zwei IndizesArray{T,N}
Tatsächlich ist Vector{T}
ein Alias für Array{T,1}
und Matrix{T}
ein Alias für Array{T,2}
.
Vector{Float64} === Array{Float64,1} && Matrix{Float64} === Array{Float64,2}
true
Beim Anlegen durch eine explizite Elementliste wird der ‘kleinste gemeinsame Typ’ für den Typparameter T
ermittelt.
= [33, "33", 1.2] v
3-element Vector{Any}:
33
"33"
1.2
Falls T
ein numerischer Typ ist, werden die Elemente in diesen Typ umgewandelt.
= [3//7, 4, 2im] v
3-element Vector{Complex{Rational{Int64}}}:
3//7 + 0//1*im
4//1 + 0//1*im
0//1 + 2//1*im
Informationen über einen Array liefern die Funktionen:
length(A)
— Anzahl der Elementeeltype(A)
— Typ der Elementendims(A)
— Anzahl der Dimensionen (Indizes)size(A)
— Tupel mit den Dimensionen des Arrays= [12, 13, 15]
v1
= [ 1 2.5
m1 6 -3 ]
for f ∈ (length, eltype, ndims, size)
println("$(f)(v) = $(f(v)), $(f)(m1) = $(f(m1))")
end
length(v) = 3, length(m1) = 4
eltype(v) = Complex{Rational{Int64}}, eltype(m1) = Float64
ndims(v) = 1, ndims(m1) = 2
size(v) = (3,), size(m1) = (2, 2)
v[i]
ist sofort aus i
berechenbar.Array{T,N}
(und damit Vektoren und Matrizen) ist für die üblichen numerischen Typen T
in dieser Weise implementiert. Die Elemente werden unboxed gespeichert. Im Gegensatz dazu ist z.B. ein Vector{Any}
implementiert als Liste von Adressen von Objekten (boxed) und nicht als Liste der Objekte selbst.Array{T,N}
speichert seine Elemente direkt (unboxed), wenn isbitstype(T) == true
.isbitstype(Float64),
isbitstype(Complex{Rational{Int64}}),
isbitstype(String)
(true, true, false)
push!(vector, items...)
— fügt Elemente am Ende des Vektors anpushfirst!(vector, items...)
— fügt Elemente am Anfang des Vektors anpop!(vector)
— entfernt letztes Element und liefert es als Ergebnis zurück,popfirst!(vector)
— entfernt erstes Element und liefert es zurück= Float64[] # leerer Vector{Float64}
v
push!(v, 3, 7)
pushfirst!(v, 1)
= pop!(v)
a println("a= $a")
push!(v, 17)
a= 7.0
3-element Vector{Float64}:
1.0
3.0
17.0
Ein push!()
kann sehr aufwändig sein, da eventuell neuer Speicher alloziert und dann der ganze bestehende Vektor umkopiert werden muss. Julia optimiert das Speichermanagement. Es wird in einem solchen Fall Speicher auf Vorrat alloziert, so dass weitere push!
s sehr schnell sind und man ‘fast O(1)-Geschwindigkeit’ erreicht.
Trotzdem sollte man bei zeitkritischem Code und sehr großen Feldern Operationen wie push!()
oder resize()
vermeiden.
Man kann Vektoren mit vorgegebener Länge und Typ uninitialisiert anlegen. Das geht am Schnellsten, die Elemente sind zufällige Bitmuster.
# fixe Länge 1000, uninitialisiert
= Vector{Float64}(undef, 1000)
v 345] v[
5.27556352e-315
zeros(n)
legt einen Vector{Float64}
der Länge n
an und initialisiert mit Null.= zeros(7) v
7-element Vector{Float64}:
0.0
0.0
0.0
0.0
0.0
0.0
0.0
zeros(T,n)
legt einen Nullvektor vom Typ T
an.=zeros(Int, 4) v
4-element Vector{Int64}:
0
0
0
0
fill(x, n)
legt Vector{typeof(x)}
der Länge n
an und füllt mit x
.= fill(sqrt(2), 5) v
5-element Vector{Float64}:
1.4142135623730951
1.4142135623730951
1.4142135623730951
1.4142135623730951
1.4142135623730951
similar(v)
legt einen uninitialisierten Vektor von gleichem Typ und Größe wie v
an.= similar(v) w
5-element Vector{Float64}:
6.1793523506736e-310
6.1793523506736e-310
6.17935235067675e-310
6.17935235112496e-310
0.0
Implizite for
-Schleifen sind eine weitere Methode, Vektoren zu erzeugen.
= [i for i in 1.0:8] v4
8-element Vector{Float64}:
1.0
2.0
3.0
4.0
5.0
6.0
7.0
8.0
= [log(i^2) for i in 1:4 ] v5
4-element Vector{Float64}:
0.0
1.3862943611198906
2.1972245773362196
2.772588722239781
Man kann sogar noch ein if
unterbringen.
= [i^2 for i in 1:8 if i%3 != 2] v6
5-element Vector{Int64}:
1
9
16
36
49
Neben Vector{Bool}
gibt es noch den speziellen Datentyp BitVector
(und allgemeiner auch BitArray
) zur Speicherung von Feldern mit Wahrheitswerten.
Während für die Speicherung eines Bool
s ein Byte verwendet wird, erfolgt die Speicherung in einem BitVector bitweise.
Der Konstruktor wandelt einen Vector{Bool}
in einen BitVector
um.
= BitVector([true, false, true, true]) vb
4-element BitVector:
1
0
1
1
Für die Gegenrichtung gibt es collect()
.
collect(vb)
4-element Vector{Bool}:
1
0
1
1
BitVectoren entstehen z.B. als Ergebnis von elementweisen Vergleichen (s. Kapitel 12.7).
.> 3.5 v4
8-element BitVector:
0
0
0
1
1
1
1
1
Indizes sind Ordinalzahlen. Also startet die Indexzählung mit 1.
Als Index kann man verwenden:
Mit Indizes kann man Arrayelemente/teile lesen und schreiben.
= [ 3i + 5.2 for i in 1:8] v
8-element Vector{Float64}:
8.2
11.2
14.2
17.2
20.2
23.2
26.2
29.2
5] v[
20.2
Bei Zuweisungen wird die rechte Seite wenn nötig mit convert(T,x)
in den Vektorelementetyp umgewandelt.
6] = 9999
v[ v
8-element Vector{Float64}:
8.2
11.2
14.2
17.2
20.2
9999.0
26.2
29.2
Überschreiten der Indexgrenzen führt zu einem BoundsError
.
77] v[
BoundsError: attempt to access 8-element Vector{Float64} at index [77]
Stacktrace:
[1] throw_boundserror(A::Vector{Float64}, I::Tuple{Int64})
@ Base ./essentials.jl:14
[2] getindex(A::Vector{Float64}, i::Int64)
@ Base ./essentials.jl:916
[3] top-level scope
@ ~/Julia/23/Book-ansipatch/chapters/7_ArraysP2.qmd:214
Mit einem range
-Objekt kann man einen Teilvektor adressieren.
= v[3:5]
vp vp
3-element Vector{Float64}:
14.2
17.2
20.2
= v[1:2:7] # range mit Schrittweite
vp vp
4-element Vector{Float64}:
8.2
14.2
20.2
26.2
end
verwendet werden.:
als Abkürzung von 1:end
verwendet werden. Das ist nützlich bei Matrizen: A[:, 3]
adressiert die gesamte 3. Spalte von A
.6:end] = [7, 7, 7]
v[ v
8-element Vector{Float64}:
8.2
11.2
14.2
17.2
20.2
7.0
7.0
7.0
Die indirekte Indizierung mit einem Vector of Integers/Indices erfolgt nach der Formel
\(v[ [i_1,\ i_2,\ i_3,...]] = [\ v[i_1],\ v[i_2],\ v[i_3],...]\)
1, 3, 4] ] v[ [
3-element Vector{Float64}:
8.2
14.2
17.2
ist also gleich
1], v[3], v[4] ] [ v[
3-element Vector{Float64}:
8.2
14.2
17.2
Als Index kann man auch einen Vector{Bool}
oder BitVector
(s. Kapitel 12.2.4) derselben Länge verwenden.
true, true, false, false, true, false, true, true] ] v[ [
5-element Vector{Float64}:
8.2
11.2
20.2
7.0
7.0
Das ist nützlich, da man z.B.
&
und |
verknüpft werden können..> 13) .& (v.<20) ] v[ (v
2-element Vector{Float64}:
14.2
17.2
Die bisher vorgestellten Methoden für Vektoren übertragen sich auch auf höherdimensionale Arrays.
Man kann sie uninitialisiert anlegen:
= Array{Float64,3}(undef, 6,9,3) A
6×9×3 Array{Float64, 3}:
[:, :, 1] =
1.0e-323 1.5e-323 2.0e-323 … 5.4e-323 7.0e-323
5.0e-324 1.0e-323 1.5e-323 4.4e-323 5.4e-323
6.17933e-310 6.17933e-310 5.0e-324 6.17935e-310 6.17934e-310
7.0e-323 7.0e-323 3.0e-323 6.0e-323 6.17935e-310
1.0e-323 1.5e-323 2.0e-323 5.0e-323 6.17935e-310
6.17933e-310 0.0 5.0e-324 … 6.17935e-310 6.17935e-310
[:, :, 2] =
6.17935e-310 6.17935e-310 6.17935e-310 … 6.17935e-310 6.17934e-310
6.17935e-310 6.17935e-310 6.17935e-310 6.17935e-310 6.17935e-310
6.17935e-310 6.17935e-310 6.17935e-310 6.17936e-310 6.17934e-310
6.17935e-310 6.17935e-310 6.17935e-310 6.17935e-310 6.17935e-310
6.17935e-310 6.17935e-310 6.17935e-310 6.17936e-310 6.17936e-310
6.17935e-310 6.17935e-310 6.17935e-310 … 6.17935e-310 6.17935e-310
[:, :, 3] =
6.17934e-310 6.17936e-310 5.0e-324 … 5.4e-323 1.63e-322
6.17935e-310 6.17935e-310 0.0 4.4e-323 5.4e-323
6.17935e-310 6.17936e-310 5.0e-324 5.0e-324 6.17935e-310
6.17935e-310 6.17935e-310 1.0e-323 6.0e-323 6.4e-323
6.17936e-310 2.03e-322 5.0e-324 5.0e-323 5.4e-323
6.17935e-310 6.17933e-310 2.75859e-313 … 6.17935e-310 6.17935e-310
In den meisten Funktionen kann man die Dimensionen auch als Tupel übergeben. Die obige Anweisung lässt sich auch so schreiben:
= Array{Float64, 3}(undef, (6,9,3)) A
Funktionen wie zeros()
usw. funktionieren natürlich auch.
= zeros(3, 4, 2) # oder zeros((3,4,2)) m2
3×4×2 Array{Float64, 3}:
[:, :, 1] =
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
[:, :, 2] =
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
0.0 0.0 0.0 0.0
= fill(5 , (3, 3)) # oder fill(5, 3, 3) M
3×3 Matrix{Int64}:
5 5 5
5 5 5
5 5 5
Die Funktion similar()
, die einen Array gleicher Größe uninitialisiert erzeugt, kann auch einen Typ als weiteres Argument bekommen.
= similar(M, Float64) M2
3×3 Matrix{Float64}:
6.17935e-310 6.17935e-310 6.17935e-310
6.17935e-310 6.17935e-310 6.17935e-310
6.17935e-310 6.17935e-310 6.17935e-310
Während man Vektoren kommagetrennt in eckigen Klammern notiert, ist die Notation für höherdimensionale Objekte etwas anders.
= [2 3 -1
M2 4 5 -2]
2×3 Matrix{Int64}:
2 3 -1
4 5 -2
= [2 3 -1; 4 5 -2] M2
2×3 Matrix{Int64}:
2 3 -1
4 5 -2
= [2 3 -1
M3 4 5 6 ;;;
7 8 9
11 12 13]
2×3×2 Array{Int64, 3}:
[:, :, 1] =
2 3 -1
4 5 6
[:, :, 2] =
7 8 9
11 12 13
M2
:= [2;4;; 3;5;; -1;-2] M2
2×3 Matrix{Int64}:
2 3 -1
4 5 -2
Im letzten Beispiel kommen diese Regeln zur Anwendung:
;
erhöht den 1. Index.;;
erhöhen den 2. Index.;;;
erhöhen den 3. Index usw.In den Beispielen davor wurde folgende Syntaktische Verschönerung (syntactic sugar) angewendet:
= [2,3,4] v1
3-element Vector{Int64}:
2
3
4
= [2;3;4] v2
3-element Vector{Int64}:
2
3
4
= [2 3 4] v3
1×3 Matrix{Int64}:
2 3 4
= [2;3;4;;] v3
3×1 Matrix{Int64}:
2
3
4
Einen “vector of vectors” a la C/C++ kann man natürlich auch konstruieren.
= [[2,3,4], [5,6,7,8]] v
2-element Vector{Vector{Int64}}:
[2, 3, 4]
[5, 6, 7, 8]
2][3] v[
7
Das sollte man nur in Spezialfällen tun. Die Array-Sprache von Julia ist in der Regel bequemer und schneller.
# 6x6 Matrix mit Zufallszahlen gleichverteilt aus [0,1) ∈ Float64
= rand(6,6) A
6×6 Matrix{Float64}:
0.169398 0.452612 0.475178 0.904675 0.94847 0.714828
0.351193 0.00174175 0.902776 0.395178 0.914661 0.416559
0.00109605 0.16115 0.353172 0.527975 0.751782 0.562037
0.0223763 0.998882 0.336013 0.959604 0.771769 0.0474043
0.862148 0.214111 0.211747 0.140627 0.987201 0.279285
0.308576 0.641049 0.229695 0.20216 0.700358 0.940126
Die übliche Indexnotation:
2, 3] = 77.77777
A[ A
6×6 Matrix{Float64}:
0.169398 0.452612 0.475178 0.904675 0.94847 0.714828
0.351193 0.00174175 77.7778 0.395178 0.914661 0.416559
0.00109605 0.16115 0.353172 0.527975 0.751782 0.562037
0.0223763 0.998882 0.336013 0.959604 0.771769 0.0474043
0.862148 0.214111 0.211747 0.140627 0.987201 0.279285
0.308576 0.641049 0.229695 0.20216 0.700358 0.940126
Man kann mit Ranges Teilfelder adressieren:
= A[1:2, 1:3] B
2×3 Matrix{Float64}:
0.169398 0.452612 0.475178
0.351193 0.00174175 77.7778
Das Adressieren von Teilen mit geringerer Dimension wird auch slicing genannt.
# die 3. Spalte als Vektor (slicing)
= A[:, 3] C
6-element Vector{Float64}:
0.4751777214121483
77.77777
0.3531723977012804
0.33601322076700646
0.21174688880149883
0.22969544744803772
# die 3. Zeile als Vektor (slicing)
= A[3, :] E
6-element Vector{Float64}:
0.0010960453817544513
0.16114953399420406
0.3531723977012804
0.52797520667903
0.7517819137326569
0.5620369628617311
Natürlich sind damit auch Zuweisungen möglich:
# Man kann slices und Teilfeldern auch etwas zuweisen
2, :] = [1,2,3,4,5,6]
A[ A
6×6 Matrix{Float64}:
0.169398 0.452612 0.475178 0.904675 0.94847 0.714828
1.0 2.0 3.0 4.0 5.0 6.0
0.00109605 0.16115 0.353172 0.527975 0.751782 0.562037
0.0223763 0.998882 0.336013 0.959604 0.771769 0.0474043
0.862148 0.214111 0.211747 0.140627 0.987201 0.279285
0.308576 0.641049 0.229695 0.20216 0.700358 0.940126
copy()
und deepcopy()
, Views= [1, 2, 3]
A = A B
3-element Vector{Int64}:
1
2
3
A
und B
sind jetzt Namen desselben Objekts.
1] = 77
A[@show B;
B = [77, 2, 3]
3] = 300
B[@show A;
A = [77, 2, 300]
Dieses Verhalten spart viel Zeit und Speicher, ist aber nicht immer gewünscht. Die Funktion copy()
erzeugt eine ‘echte’ Kopie des Objekts.
= [1, 2, 3]
A = copy(A)
B 1] = 100
A[@show A B;
A = [100, 2, 3]
B = [1, 2, 3]
Die Funktion deepcopy(A)
kopiert rekursiv. Auch von den Elementen, aus denen A
besteht, werden (wieder rekursive) Kopien erstellt.
Solange ein Array nur primitive Objekte (Zahlen) enthält, sind copy()
und deepcopy()
äquivalent.
Das folgende Beispiel zeigt den Unterschied zwischen copy()
und deepcopy()
.
mutable struct Person
:: String
name :: Int
age end
= [Person("Meier", 20), Person("Müller", 21), Person("Schmidt", 23)]
A = A
B = copy(A)
C = deepcopy(A) D
3-element Vector{Person}:
Person("Meier", 20)
Person("Müller", 21)
Person("Schmidt", 23)
1] = Person("Mustermann", 83)
A[3].age = 199
A[
@show B C D;
B = Main.Notebook.Person[Main.Notebook.Person("Mustermann", 83), Main.Notebook.Person("Müller", 21), Main.Notebook.Person("Schmidt", 199)]
C = Main.Notebook.Person[Main.Notebook.Person("Meier", 20), Main.Notebook.Person("Müller", 21), Main.Notebook.Person("Schmidt", 199)]
D = Main.Notebook.Person[Main.Notebook.Person("Meier", 20), Main.Notebook.Person("Müller", 21), Main.Notebook.Person("Schmidt", 23)]
Wenn man mittels indices/ranges/slices einer Variablen ein Teilstück eines Arrays zuweist, wird von Julia grundsätzlich ein neues Objekt konstruiert.
= [1 2 3
A 3 4 5]
= A[:, 2]
v @show v
1, 2] = 77
A[@show A v;
v = [2, 4]
A = [1 77 3; 3 4 5]
v = [2, 4]
Manchmal möchte man aber gerade hier eine Referenz-Semantik haben im Sinne von: “Vektor v
soll der 2. Spaltenvektor von A
sein und auch bleiben (d.h., sich mitändern, wenn sich A
ändert).”
Dies bezeichnet man in Julia als views: Wir wollen, dass die Variable v
nur einen ‘alternativen Blick’ auf die Matrix A
darstellt.
Das kann man erreichen durch das @view
-Macro:
= [1 2 3
A 3 4 5]
= @view A[:,2]
v @show v
1, 2] = 77
A[@show v;
v = [2, 4]
v = [77, 4]
Diese Technik wird von Julia aus Effizienzgründen auch bei einigen Funktionen der linearen Algebra verwendet. Ein Beispiel ist der Operator '
, der zu einer Matrix A
die adjungierte Matrix A'
liefert.
A'
ist die transponierte und elementweise komplex-konjugierte Matrix zu A
.adjoint(A)
.adjoint()
als lazy function, d.h.,adjoint()
eine \(1\!\times\!n\)-Matrix (einen Zeilenvektor).= [ 1. 2.
A 3. 4.]
= A' B
2×2 adjoint(::Matrix{Float64}) with eltype Float64:
1.0 3.0
2.0 4.0
Die Matrix B
ist nur ein modifizierter ‘View’ auf A
:
1, 2] =10
A[ B
2×2 adjoint(::Matrix{Float64}) with eltype Float64:
1.0 3.0
10.0 4.0
Aus Vektoren macht adjoint()
eine \(1\!\times\!n\)-Matrix (einen Zeilenvektor).
= [1, 2, 3]
v ' v
1×3 adjoint(::Vector{Int64}) with eltype Int64:
1 2 3
Eine weitere solche Funktion, die einen alternativen ‘View’, eine andere Indizierung, derselben Daten liefert, ist reshape()
.
Hier wird ein Vektor mit 12 Einträgen in eine 3x4-Matrix verwandelt.
= [1,2,3,4,5,6,7,8,9,10,11,12]
A
= reshape(A, 3, 4) B
3×4 Matrix{Int64}:
1 4 7 10
2 5 8 11
3 6 9 12
Diese Information ist wichtig, um effizient über Matrizen zu iterieren:
function column_major_add(A, B)
= size(A)
(n,m) for j = 1:m
for i = 1:n # innere Schleife durchläuft eine Spalte
+= B[i,j]
A[i,j] end
end
end
function row_major_add(A, B)
= size(A)
(n,m) for i = 1:n
for j = 1:m # inere Schleife durchläuft eine Zeile
+= B[i,j]
A[i,j] end
end
end
row_major_add (generic function with 1 method)
= rand(10000, 10000);
A = rand(10000, 10000); B
using BenchmarkTools
@benchmark row_major_add($A, $B)
BenchmarkTools.Trial: 5 samples with 1 evaluation per sample.
Range (min … max): 989.481 ms … 1.035 s ┊ GC (min … max): 0.00% … 0.00%
Time (median): 1.005 s ┊ GC (median): 0.00%
Time (mean ± σ): 1.008 s ± 18.607 ms ┊ GC (mean ± σ): 0.00% ± 0.00%
█ █ █ █ █
█▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁█ ▁
989 ms Histogram: frequency by time 1.03 s <
Memory estimate: 0 bytes, allocs estimate: 0.
@benchmark column_major_add($A, $B)
BenchmarkTools.Trial: 61 samples with 1 evaluation per sample.
Range (min … max): 69.358 ms … 249.199 ms ┊ GC (min … max): 0.00% … 0.00%
Time (median): 72.889 ms ┊ GC (median): 0.00%
Time (mean ± σ): 83.290 ms ± 31.111 ms ┊ GC (mean ± σ): 0.00% ± 0.00%
▇█▄
███▅▄▃▃▄▄▁▁▁▁▁▃▁▁▁▃▁▃▁▁▁▁▁▁▃▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▁▃▁▁▁▁▁▁▃▁▁▁▁▁▃ ▁
69.4 ms Histogram: frequency by time 181 ms <
Memory estimate: 0 bytes, allocs estimate: 0.
Wir haben gesehen, dass die Reihenfolge von innerem und äußerem Loop einen erheblichen Geschwindigkeitsunterschied macht:
Es ist effizienter, wenn die innerste Schleife über den linken Index läuft, also eine Spalte und nicht eine Zeile durchläuft. Die Ursache dafür liegt in der Architektur moderner Prozessoren.
Arrays der gleichen Dimension (z.B. alle \(7\!\times\!3\)-Matrizen) bilden einen linearen Raum.
0.5 * [2, 3, 4, 5]
4-element Vector{Float64}:
1.0
1.5
2.0
2.5
0.5 * [ 1 3
2 7] - [ 2 3; 1 2]
2×2 Matrix{Float64}:
-1.5 -1.5
0.0 1.5
Das Matrixprodukt ist definiert für
1. Faktor | 2. Faktor | Produkt |
---|---|---|
\((n\!\times\!m)\)-Matrix | \((m\!\times\!k)\)-Matrix | \((n\times k)\)-Matrix |
\((n\!\times\!m)\)-Matrix | \(m\)-Vektor | \(n\)-Vektor |
\((1\!\times\!m)\)-Zeilenvektor | \((m\!\times\!n)\)-Matrix | \(n\)-Vektor |
\((1\!\times\!m)\)-Zeilenvektor | \(m\)-Vektor | Skalarprodukt |
\(m\)-Vektor | \((1\times n)\)-Zeilenvektor | \((m\!\times\!n)\)-Matrix |
Beispiele:
= [1 2 3
A 4 5 6]
= [2, 3]
v = [1, 3, 4]; w
*
(3,2)-Matrix* A' A
2×2 Matrix{Int64}:
14 32
32 77
*
(2,3)-Matrix' * A A
3×3 Matrix{Int64}:
17 22 27
22 29 36
27 36 45
*
3-Vektor* w A
2-element Vector{Int64}:
19
43
*
(2,3)-Matrix' * A v
1×3 adjoint(::Vector{Int64}) with eltype Int64:
14 19 24
*
2-Vektor' * v A
3-element Vector{Int64}:
14
19
24
*
2-Vektor (Skalarprodukt)' * v v
13
2-Vektor *
(1,3)-Vektor (äußeres Produkt)
* w' v
2×3 Matrix{Int64}:
2 6 8
3 9 12
f.(x,y)
um zu broadcast(f, x, y)
und analog für Operatoren x .⊙ y
zu broadcast(⊙, z, y)
..=
, .+=
,… verändert die Semantik. Es wird kein neues Objekt erzeugt, sondern die Werte werden in das links stehende Objekt (welches die richtige Dimension haben muss) eingetragen.Einige Beispiele:
sin.([1, 2, 3])
3-element Vector{Float64}:
0.8414709848078965
0.9092974268256817
0.1411200080598672
= [8 2
A 3 4]
sqrt.(A)
2×2 Matrix{Float64}:
2.82843 1.41421
1.73205 2.0
.^2 A
2×2 Matrix{Int64}:
64 4
9 16
@show A^2 A^(1/2);
A ^ 2 = [70 24; 36 22]
A ^ (1 / 2) = [2.780234855920959 0.42449510866609885; 0.6367426629991483 1.9312446385887614]
hyp(a,b) = sqrt(a^2+b^2)
= [3 4
B 5 7]
hyp.(A, B)
2×2 Matrix{Float64}:
8.544 4.47214
5.83095 8.06226
Bei Operanden verschiedener Dimension wird der Operand mit fehlenden Dimensionen in diesen durch Vervielfältigung virtuell ‘aufgeblasen’.
Wir addieren einen Skalar zu einer Matrix:
= [ 1 2 3
A 4 5 6]
2×3 Matrix{Int64}:
1 2 3
4 5 6
.+ 300 A
2×3 Matrix{Int64}:
301 302 303
304 305 306
Der Skalar wurde durch Replikation auf dieselbe Dimension wie die Matrix gebracht. Wir lassen uns von broadcast()
die Form des 2. Operanden nach dem broadcasting anzeigen:
broadcast( (x,y) -> y, A, 300)
2×3 Matrix{Int64}:
300 300 300
300 300 300
(Natürlich findet diese Replikation nur virtuell statt. Dieses Objekt wird bei anderen Operationen nicht wirklich erzeugt.)
Als weiteres Beispiel: Matrix und (Spalten-)Vektor
.+ [10, 20] A
2×3 Matrix{Int64}:
11 12 13
24 25 26
Der Vektor wird durch Wiederholung der Spalten aufgeblasen:
broadcast((x,y)->y, A, [10,20])
2×3 Matrix{Int64}:
10 10 10
20 20 20
Matrix und Zeilenvektor: Der Zeilenvektor wird zeilenweise vervielfältigt:
.* [1,2,3]' # Adjungierter Vektor A
2×3 Matrix{Int64}:
1 4 9
4 10 18
Der 2. Operand wird von broadcast()
durch Vervielfältigung der Zeilen ‘aufgeblasen’.
broadcast((x,y)->y, A, [1,2,3]')
2×3 Matrix{Int64}:
1 2 3
1 2 3
Zuweisungen =
, +=
, /=
,…, bei denen links ein Name steht, laufen in Julia so ab, dass aus der rechten Seite ein Objekt konstruiert und diesem Objekt der neue Name zugewiesen wird.
Beim Arbeiten mit Arrays will man allerdings sehr oft aus Effizienzgründen einen bestehenden Array weiterverwenden. Die rechts berechneten Einträge sollen in das bereits existierende Objekt auf der linken Seite eingetragen werden.
Das erreicht man mit den Broadcast-Varianten .=
, .+=
,… der Zuweisungsoperatoren.
.= 3 A
2×3 Matrix{Int64}:
3 3 3
3 3 3
.+= [1, 4] A
2×3 Matrix{Int64}:
4 4 4
7 7 7
Julia stellt eine große Anzahl von Funktionen bereit, die mit Arrays arbeiten.
= [22 -17 8 ; 4 6 9] A
2×3 Matrix{Int64}:
22 -17 8
4 6 9
maximum(A)
22
maximum(A, dims=1)
1×3 Matrix{Int64}:
22 6 9
maximum(A, dims=2)
2×1 Matrix{Int64}:
22
9
= findmin(A) amin, i
(-17, CartesianIndex(1, 2))
CartesianIndex
?dump(i)
CartesianIndex{2}
I: Tuple{Int64, Int64}
1: Int64 1
2: Int64 2
i.I
(1, 2)
sum(A), prod(A)
(32, -646272)
sum(A, dims=1)
1×3 Matrix{Int64}:
26 -11 17
sum(A, dims=2)
2×1 Matrix{Int64}:
13
19
sum(x->sqrt(abs(x)), A) # sum_ij sqrt(|a_ij|)
19.09143825297046
reduce(+, A) # equivalent to sum(A)
32
mapreduce(f, op, array)
: Wende f
auf alle Einträge an, dann reduziere mit op
mapreduce(x -> x^2, +, A ) # Summe der Quadrate aller Einträge
970
any(x -> x>5, A)
true
count(x-> x>5, A)
4
all(x-> x>0, A)
false