Tracks
/
Crystal
Crystal
/
Übungen
/
Küchenrechner
Küchenrechner

Küchenrechner

Lernübung

Einführung

Ein Tupel ist eine endliche, geordnete Liste von Elementen, die unveränderlich ist. Tupel verlangen, dass alle Positionen einen festen Typ haben. Das wiederum bedeutet, dass der Compiler weiß, welcher Typ an welcher Position steht. Die Typen in einem Tupel können sich an jeder Position unterscheiden, aber sie müssen zur Kompilierzeit bekannt sein.

Ein Tupel erstellen

Je nachdem, ob die Typen der Werte zur Kompilierzeit bekannt sind, kannst du ein Tupel auf verschiedene Arten erstellen. Wenn die Werte zur Kompilierzeit bekannt sind, kannst du ein Tupel mit der Tupel-Literal-Syntax erstellen, andernfalls musst du sie explizit deklarieren. Wichtig ist außerdem, dass die Typen der Werte zu den im Tupel angegebenen Typen passen und dass die Anzahl der Werte mit der Anzahl der angegebenen Typen übereinstimmt. Hier ist ein Beispiel für die Definition mit der Tupel-Literal-Syntax:

tuple = {1, "foo", 'c'} # Tuple(Int32, String, Char)

Du kannst ein Tupel auch mit der Klasse Tuple erstellen.

tuple = Tuple(Int32, String, Char).new(1, "foo", 'c')

Alternativ kannst du den Typ der Variablen, der das Tupel zugewiesen wird, explizit angeben.

tuple : Tuple(Int32, String, Char) = {1, "foo", 'c'}

Den Typ des Tupels explizit anzugeben, kann nützlich sein, denn so kannst du festlegen, dass eine Position einen Union-Typ enthalten soll. Das bedeutet, dass eine Position mehrere Typen enthalten kann.

tuple : Tuple(Int32 | String, String, Char) = {1, "foo", 'c'}

Umwandlung

Ein Tupel aus einem Array erstellen

Du kannst ein Tupel mit der Methode from der Klasse Tuple aus einem Array erstellen. Dazu musst du den Typ des Tupels angeben.

array = [1, "foo", 'c']
tuple = Tuple(Int32, String, Char).from(array)

Umwandlung in ein Array

Du kannst ein Tupel mit der Methode to_a in ein Array umwandeln. Der Elementtyp des resultierenden Arrays ist die Union der Typen der einzelnen Felder des Tupels.

tuple = {1, "foo", 'c'}
array = tuple.to_a
array # => [1, "foo", 'c']

Zugriff auf Elemente

Wie Arrays sind Tupel nullbasiert, das heißt, das erste Element hat den Index 0. Anders als bei Arrays ist der Typ jedes Elements jedoch fest und zur Kompilierzeit bekannt. Wenn du ein Tupel indexierst, hat das Element also einen Typ, der von seiner Position abhängt. Um auf ein Element in einem Tupel zuzugreifen, kannst du den Operator [] verwenden.

array = [1, "foo", 'c']
array[0]         # => 1
typeof(array[0]) # => Int32 | String | Char

tuple = {1, "foo", 'c'}
tuple[0]         # => 1
typeof(tuple[0]) # => Int32

Ein weiterer Unterschied beim Zugriff auf Elemente in Arrays: Wenn der Index angegeben ist, prüft der Compiler, ob er innerhalb der Grenzen des Tupels liegt. Das bedeutet, dass du einen Fehler zur Kompilierzeit statt einen Laufzeitfehler bekommst.

tuple = {1, "foo", 'c'}
tuple[3]
# => Error: index out of bounds for Tuple(Int32, String, Char) (3 not in -3..2)

Wenn der Index jedoch in einer Variablen gespeichert ist, kann der Compiler zur Kompilierzeit nicht prüfen, ob er innerhalb der Grenzen des Tupels liegt, und es kommt stattdessen zu einem Laufzeitfehler.

Subtupel

Du kannst ein Subtupel eines Tupels erhalten, indem du den Operator [] mit einem Range verwendest. Zurückgegeben wird ein neues Tupel mit den Elementen aus dem angegebenen Range. Der Range muss zur Kompilierzeit angegeben werden, sonst kann der Compiler die Typen der Elemente im Subtupel nicht kennen. Das bedeutet, dass der Range ein Range-Literal sein muss und nicht einer Variablen zugewiesen werden darf.

tuple = {1, "foo", 'c'}
subtuple = tuple[0..1] # Tuple(Int32, String)

i = 0..1
tuple[i]
# Error: Tuple#[](Range) can only be called with range literals known at compile-time

Wann du ein Tupel verwendest

Tupel sind nützlich, wenn du eine feste Anzahl von Werten gruppieren möchtest, deren Typen zur Kompilierzeit bekannt sind. Das liegt daran, dass Tupel wegen ihrer Unveränderlichkeit weniger Speicher brauchen und schneller sind als Arrays. Ein weiterer Anwendungsfall ist die Rückgabe mehrerer Werte aus einer Methode. Das ist besonders hilfreich, wenn die Werte unterschiedliche Typen haben, denn jede Position im Tupel kann einen anderen Typ haben.

Tupel solltest du nicht verwenden, wenn du eine Datenstruktur brauchst, die wachsen oder schrumpfen kann oder die oft geändert werden muss.

Symbole sind benannte Bezeichner, mit denen du auf einen Wert verweisen kannst. Symbole werden über ein Symbol-Literal erstellt, indem du einem Namen ein : voranstellst, z. B. :foo. Sie können auch in Anführungszeichen geschrieben werden, z. B. :"foo", was zum Beispiel Leerzeichen im Namen erlaubt.

:foo           # => :foo
:"foo boo"     # => :"foo boo"
:foo == :"foo" # => true

Symbole werden an vielen Stellen in der Sprache verwendet, unter anderem als Schlüssel in NamedTuples und um Methoden- und Variablennamen darzustellen.

Symbole in Crystal

Symbole in Crystal unterscheiden sich deutlich von denen in Ruby. In Crystal ist ein Symbol eine Art Konstante und wird daher zur Kompilierzeit zugewiesen. Das bedeutet, dass Symbole nicht dynamisch erstellt werden können, was in Ruby möglich ist.

Symbole in Crystal werden als Int32 dargestellt, was sie sehr effizient macht.

Umwandlung

Symbole können in Strings umgewandelt werden, aber nicht umgekehrt. Der Grund ist, dass Symbole zur Kompilierzeit und Strings zur Laufzeit erstellt werden.

:foo.to_s # => "foo"

Anleitung

Während du Kekse für deine Freunde backen willst, stellst du fest, dass du einige der im Rezept verwendeten Maßeinheiten umrechnen musst. Da du nur das metrische System kennst, musst du dir etwas überlegen, um gängige US-Backmaße für dich bequem in Milliliter (mL) umzurechnen.

Verwende diese Umrechnungstabelle für deine Lösung:

Umzurechnende Einheit Volumen in Milliliter (mL)
mL 1 1
US-Cup 1 240
US-Flüssigunze 1 30
US-Teelöffel 1 5
US-Esslöffel 1 15

Als talentierter Programmierer in Ausbildung entscheidest du dich, Milliliter als Zwischeneinheit zu verwenden, um die Umrechnung von jeder aufgeführten Einheit in jede andere (sogar in sich selbst) zu erleichtern.

1. Ermittle die numerische Komponente aus einem Volumenpaar

Implementiere die Methode KitchenCalculator.get_volume. Wenn du ein Volumenpaar-Tupel übergibst, soll sie nur die numerische Komponente zurückgeben.

KitchenCalculator.get_volume({:cup, 2.0})
# => 2.0

2. Rechne das Volumenpaar in Milliliter um

Implementiere die Methode KitchenCalculator.to_milliliter. Wenn du ein Volumenpaar-Tupel übergibst, soll sie das Volumen mithilfe der Umrechnungstabelle in Milliliter umrechnen.

KitchenCalculator.to_milliliter({:cup, 2.5})
# => {:milliliter, 600.0}

3. Rechne das Milliliter-Volumenpaar in eine andere Einheit um

Implementiere die Methode KitchenCalculator.from_milliliter. Wenn du ein Volumenpaar-Tupel und die gewünschte Einheit übergibst, soll sie das Volumen mithilfe der Umrechnungstabelle in die gewünschte Einheit umrechnen.

KitchenCalculator.from_milliliter({:milliliter, 1320.0}, :cup)
# => {:cup, 5.5}

4. Rechne von einer beliebigen Einheit in eine andere um

Implementiere die Methode KitchenCalculator.convert. Wenn du ein Volumenpaar-Tupel und die gewünschte Einheit übergibst, soll sie das angegebene Volumen in die gewünschte Einheit umrechnen.

KitchenCalculator.convert({:teaspoon, 9.0}, :tablespoon)
# => {:tablespoon, 3.0}
Über GitHub bearbeiten Der Link öffnet sich in einem neuen Fenster oder Tab
Crystal Exercism

Bereit, mit Küchenrechner zu starten?

Melde dich bei Exercism an, um Crystal mit 26 Konzepte142 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.