Tu

Tupel in Julia

1 Übung

Über Tupel

Ein Tuple ist einem Vector (eindimensionales array) in vielerlei Hinsicht recht ähnlich. Es gibt jedoch ein paar wichtige Unterschiede.

  • Tupel werden in runden Klammern ( ) statt in eckigen Klammern [ ] geschrieben, wobei die Klammern weggelassen werden können, wenn die Bedeutung eindeutig ist.
  • Tupel müssen nicht homogen sein: Der Typ jedes Elements wird unabhängig gespeichert.
  • Tupel sind unveränderlich.

Unveränderlichkeit ermöglicht mehr Compiler-Optimierungen und macht Tupel performanter als Vektoren. Deshalb solltest du sie bevorzugen, wenn Veränderlichkeit nicht nötig ist.

julia> t = (3, 5.2, "xyz")
(3, 5.2, "xyz")

# Non-homogeneous:
julia> dump(t)
Tuple{Int64, Float64, String}
  1: Int64 3
  2: Float64 5.2
  3: String "xyz"

julia> t[2]
5.2

# Immutable:
julia> t[2] = 7.3
ERROR: MethodError: no method matching setindex!(::Tuple{Int64, Float64, String}

Es gibt außerdem einen Tuple()-Konstruktor, der jede iterierbare Collection in ein Tupel umwandelt. Praktischerweise behandelt er einen String als iterierbare Collection von Zeichen.

julia> Tuple("Julia")
('J', 'u', 'l', 'i', 'a')

Bei jedem Tupel kannst du auf die Elemente über den Index zugreifen oder sie in einer Schleife durchlaufen.

Jedes rein numerische Tupel kann außerdem in mathematischen Funktionen wie sum() verwendet werden, genau wie Arrays.

Entpacken

Ein Tupel kann mit Hilfe der Mehrfachzuweisung in einzelne Elemente entpackt werden. Für Elemente, die du nicht brauchst, verwendest du den Unterstrich _ als Platzhaltervariable.

julia> tup = (1, "Julia", true)
(1, "Julia", true)

julia> num, name, _ = tup
(1, "Julia", true)

julia> uppercase(name), 2num
("JULIA", 2)

Um alle Elemente eines Tupels in ein anderes Tupel (oder allgemeiner in eine Collection) zu kopieren, gibt es einen splat-Operator ..., der es entpackt. Das wird im Konzept Funktionen ausführlicher behandelt.

julia> new_tup = (tup..., π, '😊')
(1, "Julia", true, π, '😊')

Benannte Tupel

Ein Tuple enthält nur Werte. Ein NamedTuple ordnet jedem Wert einen Namen zu.

Auf einzelne Felder kannst du dann mit der „Punkt“-Notation zugreifen:

julia> nt = (a = 1, b = 7.3)
(a = 1, b = 7.3)

julia> dump(nt)
@NamedTuple{a::Int64, b::Float64}
  a: Int64 1
  b: Float64 7.3

julia> nt.b
7.3

Der Name wird intern als Symbol gespeichert (ein unveränderlicher Typ, und dem Namen wird ein Doppelpunkt : vorangestellt), daher gibt es eine alternative Syntax:

julia> nt[:b]
7.3

Manchmal ist es nützlich, die Erstellung eines benannten Tupels in zwei Schritte aufzuteilen:

  1. Definiere die Felder als benutzerdefinierten Datentyp.
  2. Verwende diesen als Konstruktor, um ein Tupel in ein benanntes Tupel umzuwandeln.
# Step 1
julia> Student = NamedTuple{(:name, :grade), Tuple{String, Int}}
@NamedTuple{name::String, grade::Int64}

julia> typeof(Student)
DataType

# Step 2
julia> s = Student( ("Helen", 3) )
(name = "Helen", grade = 3)

julia> dump(s)
@NamedTuple{name::String, grade::Int64}
  name: String "Helen"
  grade: Int64 3

Es gibt überraschend viele andere Möglichkeiten, ein benanntes Tupel zu erstellen. Schau dir die Dokumentation an, wenn du dich dafür interessierst.

Es ist einfach, entweder nur die Namen (auch „Schlüssel“ genannt) oder nur die Werte zu erhalten:

julia> keys(s)
(:name, :grade)

julia> values(s)
("Helen", 3)

Beachte, dass eine Iteration über ein benanntes Tupel nur die Werte liefert und die Namen ignoriert werden.

Wenn die Namen bei einer Iteration ebenfalls nützlich sind, sind mehr Schritte nötig. Das NamedTuple kann in einen Vector aus Pairs umgewandelt werden (Pairs sind das Thema eines anderen Konzepts).

julia> pairs(s)
pairs(::NamedTuple) with 2 entries:
  :name  => "Helen"
  :grade => 3

julia> collect(pairs(s))
2-element Vector{Pair{Symbol, Any}}:
  :name => "Helen"
  :grade => 3

Das benannte Tupel ist also ziemlich wörterbuchartig. Es unterscheidet sich jedoch in einigen wichtigen Punkten von einem [Dict][dict] in Julia:

  • Ein Dict ist veränderlich; ein NamedTuple ist, wie jedes Tupel, unveränderlich.
  • Ein Dict ist flexibel beim Typ der Schlüssel, die es akzeptiert; ein NamedTuple kann nur Symbole als Schlüssel verwenden.

Tupel (benannt oder nicht) sind eine sehr verbreitete Möglichkeit, strukturierte Daten an Funktionen zu übergeben und von ihnen zurückzubekommen. Unveränderlichkeit ist in diesem Anwendungsfall von Vorteil: Sie sorgt für Datensicherheit und ermöglicht aggressivere Compiler-Optimierungen.

Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab

Lerne Tupel