= (33, 4.5, "Hello")
t
@show t[2] # indizierbar
for i ∈ t println(i) end # iterierbar
t[2] = 4.5
33
4.5
Hello
Julia bietet eine große Auswahl von Containertypen mit weitgehend ähnlichem Interface an. Wir stellen hier Tuple
, Range
und Dict
vor, im nächsten Kapitel dann Array
, Vector
und Matrix
.
Diese Container sind:
for x ∈ container ... end
= container[i] x
und einige sind auch
Weiterhin gibt es eine Reihe gemeinsamer Funktionen, z.B.
length(container)
— Anzahl der Elementeeltype(container)
— Typ der Elementeisempty(container)
— Test, ob Container leer istempty!(container)
— leert Container (nur wenn mutierbar)Ein Tupel ist ein nicht mutierbarer Container von Elementen. Es ist also nicht möglich, neue Elemente dazuzufügen oder den Wert eines Elements zu ändern.
= (33, 4.5, "Hello")
t
@show t[2] # indizierbar
for i ∈ t println(i) end # iterierbar
t[2] = 4.5
33
4.5
Hello
Ein Tupel ist ein inhomogener Typ. Jedes Element hat seinen eigenen Typ und das zeigt sich auch im Typ des Tupels:
typeof(t)
Tuple{Int64, Float64, String}
Man verwendet Tupel gerne als Rückgabewerte von Funktionen, um mehr als ein Objekt zurückzulieferen.
# Ganzzahldivision und Rest:
# Quotient und Rest werden den Variablen q und r zugewiesen
= divrem(71, 6)
q, r @show q r;
q = 11
r = 5
Wie man hier sieht, kann man in bestimmten Konstrukten die Klammern auch weglassen. Dieses implict tuple packing/unpacking verwendet man auch gerne in Mehrfachzuweisungen:
= 12, 17, 203 x, y, z
(12, 17, 203)
y
17
Manche Funktionen bestehen auf Tupeln als Argument oder geben immer Tupeln zurück. Dann braucht man manchmal ein Tupel aus einem Element.
Das notiert man so:
= (13,) # ein 1-Element-Tupel x
(13,)
Das Komma - und nicht die Klammern – macht das Tupel.
= (13) # kein Tupel x
13
Wir haben range-Objekte schon in numerischen for
-Schleifen verwendet.
= 1:1000
r typeof(r)
UnitRange{Int64}
Es gibt verschiedene range-Typen. Wie man sieht, sind es über den Zahlentyp parametrisierte Typen und UnitRange
ist z.B. ein range mit der Schrittweite 1. Ihre Konstruktoren heißen in der Regel range()
.
Der Doppelpunkt ist eine spezielle Syntax.
a:b
wird vom Parser umgesetzt zu range(a, b)
a:b:c
wird umgesetzt zu range(a, c, step=b)
Ranges sind offensichtlich iterierbar, nicht mutierbar aber indizierbar.
3:100)[20] # das zwanzigste Element (
22
Wir erinnern an die Semantik der for
-Schleife: for i in 1:1000
heißt nicht:
i
wird bei jedem Durchlauf um eins erhöht’ sondernAllerdings wäre es sehr ineffektiv, diesen Container tatsächlich explizit anzulegen.
for
-Schleifen so nützlich: speichersparend und schnell.AbstractRange
ein Subtyp von AbstractVector
.Das Macro @allocated
gibt aus, wieviel Bytes an Speicher bei der Auswertung eines Ausdrucks alloziert wurden.
@allocated r = [1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20]
224
@allocated r = 1:20
0
Zum Umwandeln in einen ‘richtigen’ Vektor dient die Funktion collect()
.
collect(20:-3:1)
7-element Vector{Int64}:
20
17
14
11
8
5
2
Recht nützlich, z.B. beim Vorbereiten von Daten zum Plotten, ist der range-Typ LinRange
.
LinRange(2, 50, 300)
300-element LinRange{Float64, Int64}:
2.0, 2.16054, 2.32107, 2.48161, 2.64214, …, 49.5184, 49.6789, 49.8395, 50.0
LinRange(start, stop, n)
erzeugt eine äquidistante Liste von n
Werten von denen der erste und der letzte die vorgegebenen Grenzen sind. Mit collect()
kann man bei Bedarf auch daraus den entsprechenden Vektor gewinnen.
v
sind durch einen Index 1,2,3…. addressierbar: v[i]
Dict{S,T}
, wobei S
der Typ der keys und T
der Typ der values istMan kann sie explizit anlegen:
# Einwohner 2020 in Millionen, Quelle: wikipedia
= Dict("Berlin" => 3.66, "Hamburg" => 1.85,
EW "München" => 1.49, "Köln" => 1.08)
Dict{String, Float64} with 4 entries:
"München" => 1.49
"Köln" => 1.08
"Berlin" => 3.66
"Hamburg" => 1.85
typeof(EW)
Dict{String, Float64}
und mit den keys indizieren:
"Berlin"] EW[
3.66
Das Abfragen eines nicht existierenden keys ist natürlich ein Fehler.
"Leipzig"] EW[
KeyError: key "Leipzig" not found
Stacktrace:
[1] getindex(h::Dict{String, Float64}, key::String)
@ Base ./dict.jl:477
[2] top-level scope
@ ~/Julia/23/Book-ansipatch/chapters/6_ArraysEtcP1.qmd:191
Man kann ja auch vorher mal anfragen…
haskey(EW, "Leipzig")
false
… oder die Funktion get(dict, key, default)
benutzen, die bei nicht existierendem Key keinen Fehler wirft sondern das 3. Argument zurückgibt.
@show get(EW, "Leipzig", -1) get(EW, "Berlin", -1);
get(EW, "Leipzig", -1) = -1
get(EW, "Berlin", -1) = 3.66
Man kann sich auch alle keys
und values
als spezielle Container geben lassen.
keys(EW)
KeySet for a Dict{String, Float64} with 4 entries. Keys:
"München"
"Köln"
"Berlin"
"Hamburg"
values(EW)
ValueIterator for a Dict{String, Float64} with 4 entries. Values:
1.49
1.08
3.66
1.85
Man kann über die keys
iterieren…
for i in keys(EW)
= EW[i]
n println("Die Stadt $i hat $n Millionen Einwohner.")
end
Die Stadt München hat 1.49 Millionen Einwohner.
Die Stadt Köln hat 1.08 Millionen Einwohner.
Die Stadt Berlin hat 3.66 Millionen Einwohner.
Die Stadt Hamburg hat 1.85 Millionen Einwohner.
odere gleich über key-value
-Paare.
for (stadt, ew) ∈ EW
println("$stadt : $ew Mill.")
end
München : 1.49 Mill.
Köln : 1.08 Mill.
Berlin : 3.66 Mill.
Hamburg : 1.85 Mill.
Man kann in ein Dict
zusätzliche key-value
-Paare eintragen…
"Leipzig"] = 0.52
EW["Dresden"] = 0.52
EW[ EW
Dict{String, Float64} with 6 entries:
"Dresden" => 0.52
"München" => 1.49
"Köln" => 1.08
"Berlin" => 3.66
"Leipzig" => 0.52
"Hamburg" => 1.85
und einen value
ändern.
# Oh, das war bei Leipzig die Zahl von 2010, nicht 2020
"Leipzig"] = 0.597
EW[ EW
Dict{String, Float64} with 6 entries:
"Dresden" => 0.52
"München" => 1.49
"Köln" => 1.08
"Berlin" => 3.66
"Leipzig" => 0.597
"Hamburg" => 1.85
Ein Paar kann über seinen key
auch gelöscht werden.
delete!(EW, "Dresden")
Dict{String, Float64} with 5 entries:
"München" => 1.49
"Köln" => 1.08
"Berlin" => 3.66
"Leipzig" => 0.597
"Hamburg" => 1.85
Zahlreiche Funktionen können mit Dicts
wie mit anderen Containern arbeiten.
maximum(values(EW))
3.66
Ohne Typspezifikation …
= Dict() d1
Dict{Any, Any}()
und mit Typspezifikation:
= Dict{String, Int}() d2
Dict{String, Int64}()
collect()
keys(dict)
und values(dict)
sind spezielle Datentypen.collect()
macht daraus eine Liste vom Typ Vector
.collect(dict)
liefert eine Liste vom Typ Vector{Pair{S,T}}
collect(EW)
5-element Vector{Pair{String, Float64}}:
"München" => 1.49
"Köln" => 1.08
"Berlin" => 3.66
"Leipzig" => 0.597
"Hamburg" => 1.85
collect(keys(EW)), collect(values(EW))
(["München", "Köln", "Berlin", "Leipzig", "Hamburg"], [1.49, 1.08, 3.66, 0.597, 1.85])
Wir sortieren die Keys. Als Strings werden sie alphabetisch sortiert. Mit dem rev
-Parameter wird rückwärts sortiert.
for k in sort(collect(keys(EW)), rev = true)
= EW[k]
n println("$k hat $n Millionen Einw. ")
end
München hat 1.49 Millionen Einw.
Leipzig hat 0.597 Millionen Einw.
Köln hat 1.08 Millionen Einw.
Hamburg hat 1.85 Millionen Einw.
Berlin hat 3.66 Millionen Einw.
Wir sortieren collect(dict)
. Das ist ein Vektor von Paaren. Mit by
definieren wir, wonach zu sortieren ist: nach dem 2. Element des Paares.
for (k,v) in sort(collect(EW), by = pair -> last(pair), rev=false)
println("$k hat $v Mill. EW")
end
Leipzig hat 0.597 Mill. EW
Köln hat 1.08 Mill. EW
München hat 1.49 Mill. EW
Hamburg hat 1.85 Mill. EW
Berlin hat 3.66 Mill. EW
Wir machen ‘experimentelle Stochastik’ mit 2 Würfeln:
Gegeben sei l
, eine Liste mit den Ergebnissen von 100 000 Pasch-Würfen, also 100 000 Zahlen zwischen 2 und 12.
Wie häufig sind die Zahlen 2 bis 12?
Wir (lassen) würfeln:
= rand(1:6, 100_000) .+ rand(1:6, 100_000) l
100000-element Vector{Int64}:
9
7
2
6
9
8
9
6
2
6
⋮
7
2
7
8
7
7
6
7
6
Wir zählen mit Hilfe eines Dictionaries die Häufigkeiten der Ereignisse. Dazu nehmen wir das Ereignis als key
und seine Häufigkeit als value
.
# In diesem Fall könnte man das auch mit einem einfachen Vektor
# lösen. Eine bessere Illustration wäre z.B. Worthäufigkeit in
# einem Text. Dann ist i keine ganze Zahl sondern ein Wort=String
= Dict{Int,Int}() # das Dict zum 'reinzählen'
d
for i in l # für jedes i wird d[i] erhöht.
= get(d, i, 0) + 1
d[i] end
d
Dict{Int64, Int64} with 11 entries:
5 => 10983
12 => 2824
8 => 13923
6 => 13999
11 => 5451
9 => 11210
3 => 5635
7 => 16596
4 => 8257
2 => 2819
10 => 8303
Das Ergebnis:
using Plots
plot(collect(keys(d)), collect(values(d)), seriestype=:scatter)