t = (33, 4.5, "Hello")
@show t[2] # indizierbar
for i ∈ t println(i) end # iterierbart[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 ... endx = container[i]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.
t = (33, 4.5, "Hello")
@show t[2] # indizierbar
for i ∈ t println(i) end # iterierbart[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
q, r = divrem(71, 6)
@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:
x, y, z = 12, 17, 203(12, 17, 203)
y17
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:
x = (13,) # ein 1-Element-Tupel(13,)
Das Komma - und nicht die Klammern – macht das Tupel.
x= (13) # kein Tupel13
Wir haben range-Objekte schon in numerischen for-Schleifen verwendet.
r = 1:1000
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 Element22
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:200
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
EW = Dict("Berlin" => 3.66, "Hamburg" => 1.85,
"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:
EW["Berlin"]3.66
Das Abfragen eines nicht existierenden keys ist natürlich ein Fehler.
EW["Leipzig"]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)
n = EW[i]
println("Die Stadt $i hat $n Millionen Einwohner.")
endDie 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.")
endMü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…
EW["Leipzig"] = 0.52
EW["Dresden"] = 0.52
EWDict{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
EW["Leipzig"] = 0.597
EWDict{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 …
d1 = Dict()Dict{Any, Any}()
und mit Typspezifikation:
d2 = Dict{String, Int}()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)
n = EW[k]
println("$k hat $n Millionen Einw. ")
endMü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")
endLeipzig 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:
l = rand(1:6, 100_000) .+ rand(1:6, 100_000)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
d = Dict{Int,Int}() # das Dict zum 'reinzählen'
for i in l # für jedes i wird d[i] erhöht.
d[i] = get(d, i, 0) + 1
end
dDict{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)