Dekomposition bezeichnet das Herauslösen der Elemente aus einer Sammlung wie einem Array oder Hash.
Die dekomponierten Werte lassen sich anschließend innerhalb derselben Anweisung Variablen zuweisen.
Die Mehrfachzuweisung ermöglicht es, Werte innerhalb einer Anweisung zu dekomponieren, indem du mehrere Variablen zuweist.
Dadurch wird der Code kürzer und lesbarer, und sie geschieht, indem du die zuzuweisenden Variablen durch ein Komma trennst, zum Beispiel first, second, third = [1, 2, 3].
Der Splat-Operator (*) und der Double-Splat-Operator (**) werden häufig in Dekompositionskontexten eingesetzt.
*<variable_name> und **<variable_name> sollten nicht mit * und ** verwechselt werden.
Während * und ** für Multiplikation bzw. Potenzierung verwendet werden, werden *<variable_name> und **<variable_name> als Kompositions- und Dekompositionsoperatoren verwendet.
Mit der Mehrfachzuweisung kannst du mehrere Variablen in einer Zeile zuweisen.
Um die Werte zu trennen, verwendest du ein Komma ,:
>> a, b = 1, 2
=> [1, 2]
>> a
=> 1
Die Mehrfachzuweisung ist nicht auf einen Datentyp beschränkt:
>> x, y, z = 1, "Hello", true
=> [1, "Hello", true]
>> x
=> 1
>> y
=> 'Hello'
>> z
=> true
Mit der Mehrfachzuweisung kannst du Elemente in Arrays vertauschen. Diese Praxis ist in Sortieralgorithmen recht verbreitet. Zum Beispiel:
>> numbers = [1, 2]
=> [1, 2]
>> numbers[0], numbers[1] = numbers[1], numbers[0]
=> [2, 1]
>> numbers
=> [2, 1]
Das wird auch als „Parallele Zuweisung“ bezeichnet und kann verwendet werden, um eine temporäre Variable zu vermeiden.
Gibt es mehr Variablen als Werte, werden den überzähligen Variablen nil zugewiesen:
>> a, b, c = 1, 2
=> [1, 2]
>> b
=> 2
>> c
=> nil
In Ruby ist es möglich, die Elemente von Arrays/Hashes in einzelne Variablen zu dekomponieren. Da Werte in Arrays in einer Indexreihenfolge auftreten, werden sie in derselben Reihenfolge in Variablen entpackt:
>> fruits = ["apple", "banana", "cherry"]
>> x, y, z = fruits
>> x
=> "apple"
Wenn es Werte gibt, die du nicht brauchst, kannst du _ verwenden, um „gesammelt, aber nicht verwendet“ anzugeben:
>> fruits = ["apple", "banana", "cherry"]
>> _, _, z = fruits
>> z
=> "cherry"
Das Dekomponieren und Zuweisen von Werten aus Arrays innerhalb eines Arrays (auch als verschachteltes Array bekannt) funktioniert genauso wie das flache Dekomponieren, benötigt aber einen abgegrenzten Dekompositionsausdruck (()), um den Kontext oder die Position der Werte zu klären:
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> (a, b), (c, d) = fruits_vegetables
>> a
=> "apple"
>> d
=> "potato"
Du kannst auch nur einen Teil eines verschachtelten Arrays tiefgehend entpacken:
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> a, (c, d) = fruits_vegetables
>> a
=> ["apple", "banana"]
>> c
=> "carrot"
Wenn die Dekomposition Variablen mit falscher Platzierung und/oder einer falschen Anzahl von Werten enthält, erhältst du einen Syntaxfehler:
fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
(a, b), (d) = fruits_vegetables
# syntax error, unexpected '=', expecting '.' or &. or :: or '['
((a, b), (d)) = fruits_vegetables
# syntax error, unexpected ')', expecting '.' or &. or :: or '['
Experimentiere hier, und dir wird auffallen, dass das erste Muster ausschlaggebend ist, nicht die verfügbaren Werte auf der rechten Seite. Der Syntaxfehler hängt nicht an der Datenstruktur.
*) dekomponierenWenn du ein Array dekomponierst, kannst du den Splat-Operator (*) verwenden, um die „übrig gebliebenen“ Werte einzufangen.
Das ist klarer, als das Array zu slicen (was in manchen Situationen weniger lesbar ist).
Zum Beispiel können wir das erste Element herauslösen und dann die übrigen Werte einem neuen Array ohne das erste Element zuweisen:
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *last = fruits
>> x
=> "apple"
>> last
=> ["banana", "cherry", "orange", "kiwi", "melon", "mango"]
Wir können auch die Werte am Anfang und am Ende des Arrays herauslösen und dabei alle Werte in der Mitte gruppieren:
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *middle, y, z = fruits
>> y
=> "melon"
>> middle
=> ["banana", "cherry", "orange", "kiwi"]
Wir können * auch in der tiefen Dekomposition verwenden:
>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>> (a, *rest), b = fruits_vegetables
>> a
=> "apple"
>> rest
=> ["banana", "melon"]
Hash dekomponierenEinen Hash zu dekomponieren ist etwas anders, als ein Array zu dekomponieren. Um einen Hash entpacken zu können, musst du ihn zuerst in ein Array umwandeln. Andernfalls findet keine Dekomposition statt:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory
>> x
=> {:apple=>6, :banana=>2, :cherry=>3}
>> y
=> nil
Um einen Hash in ein Array umzuwandeln, kannst du die Methode to_a verwenden:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> fruits_inventory.to_a
=> [[:apple, 6], [:banana, 2], [:cherry, 3]]
>> x, y, z = fruits_inventory.to_a
>> x
=> [:apple, 6]
Wenn du die Schlüssel entpacken möchtest, kannst du die Methode keys verwenden:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.keys
>> x
=> :apple
Wenn du die Werte entpacken möchtest, kannst du die Methode values verwenden:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.values
>> x
=> 6
Komponieren ist die Möglichkeit, mehrere Werte zu einem Array zusammenzufassen, das einer Variablen zugewiesen wird. Das ist nützlich, wenn du Werte dekomponieren, Änderungen vornehmen und die Ergebnisse dann wieder in eine Variable komponieren möchtest. Außerdem ermöglicht es das Zusammenführen von 2 oder mehr Arrays/Hashes.
*) komponierenEin Array lässt sich mit dem Splat-Operator (*) komponieren.
Dabei werden alle Werte in ein Array gepackt.
>> fruits = ["apple", "banana", "cherry"]
>> more_fruits = ["orange", "kiwi", "melon", "mango"]
# fruits and more_fruits are unpacked and then their elements are packed into combined_fruits
>> combined_fruits = *fruits, *more_fruits
>> combined_fruits
=> ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
**) komponierenEinen Hash komponierst du mit dem Double-Splat-Operator (**).
Dabei werden alle Schlüssel/Wert-Paare aus einem Hash in einen anderen Hash gepackt oder zwei Hashes miteinander kombiniert.
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> more_fruits_inventory = {orange: 4, kiwi: 1, melon: 2, mango: 3}
# fruits_inventory and more_fruits_inventory are unpacked into key-values pairs and combined.
>> combined_fruits_inventory = {**fruits_inventory, **more_fruits_inventory}
# then the pairs are packed into combined_fruits_inventory
>> combined_fruits_inventory
=> {:apple=>6, :banana=>2, :cherry=>3, :orange=>4, :kiwi=>1, :melon=>2, :mango=>3}
*) und des Double-Splat-Operators (**) mit MethodenWenn du eine Methode erstellst, die eine beliebige Anzahl von Argumenten akzeptiert, kannst du *arguments oder **keyword_arguments in der Methodendefinition verwenden.
*arguments wird verwendet, um eine beliebige Anzahl von Positionsargumenten (also Argumenten ohne Schlüsselwort) zu packen, und
**keyword_arguments wird verwendet, um eine beliebige Anzahl von Schlüsselwortargumenten zu packen.
Verwendung von *arguments:
# This method is defined to take any number of positional arguments
# (Using the single line form of the definition of a method.)
>> def my_method(*arguments)= arguments
# Arguments given to the method are packed into an array
>> my_method(1, 2, 3)
=> [1, 2, 3]
>> my_method("Hello")
=> ["Hello"]
>> my_method(1, 2, 3, "Hello", "Mars")
=> [1, 2, 3, "Hello", "Mars"]
Verwendung von **keyword_arguments:
# This method is defined to take any number of keyword arguments
>> def my_method(**keyword_arguments)= keyword_arguments
# Arguments given to the method are packed into a dictionary
>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}
Wenn die definierte Methode keine Parameter für Schlüsselwortargumente (**keyword_arguments oder <key_word>: <value>) hat, werden die Schlüsselwortargumente in einen Hash gepackt und dem letzten Parameter zugewiesen.
>> def my_method(a)= a
>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}
*arguments und **keyword_arguments können auch in Kombination miteinander verwendet werden:
def my_method(*arguments, **keyword_arguments)
p arguments.sum
for (key, value) in keyword_arguments.to_a
p key.to_s + " = " + value.to_s
end
end
my_method(1, 2, 3, a: 1, b: 2, c: 3)
6
"a = 1"
"b = 2"
"c = 3"
Du kannst Argumente auch vor und nach *arguments schreiben, um bestimmte Positionsargumente zu ermöglichen.
Das funktioniert genauso wie das Dekomponieren eines Arrays.
Argumente müssen in einer bestimmten Reihenfolge angeordnet sein:
def my_method(<positional_arguments>, *arguments, <positional_arguments>, <keyword_arguments>, **keyword_arguments)
Wenn du diese Reihenfolge nicht einhältst, bekommst du einen Fehler.
def my_method(a, b, *arguments)
p a
p b
p arguments
end
my_method(1, 2, 3, 4, 5)
1
2
[3, 4, 5]
Du kannst Positionsargumente vor und nach *arguments schreiben:
>> def my_method(a, *middle, b)= middle
>> my_method(1, 2, 3, 4, 5)
=> [2, 3, 4]
Du kannst auch Positionsargumente, *arguments, Schlüsselwortargumente und **keyword_arguments kombinieren:
>> def my_method(first, *many, last, a:, **keyword_arguments)
p first
p many
p last
p a
p keyword_arguments
end
>> my_method(1, 2, 3, 4, 5, a: 6, b: 7, c: 8)
1
[2, 3, 4]
5
6
{:b => 7, :c => 8}
Wenn du die Argumente in einer falschen Reihenfolge schreibst, führt das zu einem Fehler:
def my_method(a:, **keyword_arguments, first, *arguments, last)
arguments
end
my_method(1, 2, 3, 4, a: 5)
syntax error, unexpected local variable or method, expecting & or '&'
... my_method(a:, **keyword_arguments, first, *arguments, last)
Du kannst den Splat-Operator (*) verwenden, um ein Array von Argumenten in einen Methodenaufruf zu entpacken:
def my_method(a, b, c)
p c
p b
p a
end
numbers = [1, 2, 3]
my_method(*numbers)
3
2
1
Du kannst auch den Double-Splat-Operator (**) verwenden, um einen Hash von Argumenten in einen Methodenaufruf zu entpacken:
def my_method(a:, b:, c:)
p c
p b
p a
end
numbers = {a: 1, b: 2, c: 3}
my_method(**numbers)
3
2
1
Dein Freund Linus ist Lokführer und fährt Güterzüge zwischen Städten. Er ist zwar großartig im Umgang mit Zügen, aber alles andere als großartig in Sachen Logistik oder Computer. Er würde gerne deine Programmierhilfe in Anspruch nehmen, um Zugdaten zu organisieren und Fehler in den Routendaten zu korrigieren.
Diese Übung ließe sich leicht mit Slicing, Indexing und verschiedenen hash-Methoden lösen.
Wir möchten aber, dass du beim Lösen der folgenden Aufgaben das Packen, das Entpacken und die Mehrfachzuweisung übst.
Dein Freund hat bisher jede Waggon-Kennung (ID) mitgeschrieben, aber er weiß nie genau, wie viele Waggons das System jeweils verarbeiten muss. Für den Rest des Logistikprogramms wäre es viel einfacher, diese Daten in einem einheitlichen Array zusammengefasst zu haben.
Implementiere eine Methode generate_list_of_wagons(), die eine beliebige Anzahl von Waggon-IDs entgegennimmt.
Jede ID ist eine positive Ganzzahl.
Die Methode soll die übergebenen IDs dann per return als einzelnes Array zurückgeben.
LocomotiveEngineer.generate_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
# => [1, 7, 12, 3, 14, 8, 5]
So langsam bekommst du ein Gefühl für die Daten und dafür, wie sie im Logistikprogramm verwendet werden. Das ID-System weist der Lokomotive immer die ID 1 zu, während die übrigen Waggons des Zugs eine zufällig gewählte ID größer als 1 erhalten.
Dein Freund musste zwei neue Waggons an den Zug anhängen und hat vergessen, das System zu aktualisieren! Jetzt müssen die ersten beiden Waggons im Array des Zugs ans Ende verschoben werden, sonst gerät alles durcheinander.
Um die Sache noch komplizierter zu machen, hat dein Freund gerade ein zweites Array entdeckt, das offenbar fehlende Waggon-IDs enthält. Er erinnert sich nur noch daran, dass die IDs aus diesem zweiten Array direkt hinter der dafür vorgesehenen Lokomotive eingefügt werden sollen, sobald die neuen Waggons verschoben sind.
Linus wäre dir sehr dankbar, wenn du seine Fehler korrigierst und die Daten zusammenführst.
Implementiere eine Methode fix_list_of_wagons(), die zwei Arrays mit Waggon-IDs entgegennimmt.
Sie soll die ersten beiden Elemente des ersten Arrays ans Ende verschieben und die Werte aus dem zweiten Array hinter (rechts von) der Lokomotiv-ID (1) einfügen.
Die Methode soll dann per return ein Array mit den Änderungen zurückgeben.
LocomotiveEngineer.fix_list_of_wagons([2, 5, 1, 7, 4, 12, 6, 3, 13], [3, 17, 6, 15])
# => [1, 3, 17, 6, 15, 7, 4, 12, 6, 3, 13, 2, 5]
Jetzt, wo alle Waggondaten korrekt sind, möchte Linus, dass du die Routing-Informationen des Systems aktualisierst. Auf einer Transportstrecke hält ein Zug vielleicht an ein paar verschiedenen Bahnhöfen, um Fracht aufzunehmen und/oder abzugeben. Jede Fahrt kann eine unterschiedliche Anzahl dieser Zwischenstopps haben. Dein Freund möchte, dass du den Routing-Hash des Systems um alle fehlenden/zusätzlichen Lieferinformationen ergänzt.
Implementiere eine Methode add_missing_stops(), die einen Routing-Hash gefolgt von einer variablen Anzahl von Schlüsselwortargumenten entgegennimmt.
Diese Argumente können entweder in Form eines Hashs vorliegen, der einen oder mehrere Zwischenstopps enthält, oder als beliebig viele stop_<number>: "city"-Schlüsselwortpaare.
Deine Methode soll dann den Routing-Hash zurückgeben, erweitert um einen zusätzlichen Schlüssel, der ein Array mit allen hinzugefügten Zwischenstopps in der richtigen Reihenfolge enthält.
LocomotiveEngineer.add_missing_stops({from: "New York", to: "Miami"},
stop_1: "Washington, DC", stop_2: "Charlotte", stop_3: "Atlanta",
stop_4: "Jacksonville", stop_5: "Orlando")
# => {from: "New York", to: "Miami", stops: ["Washington, DC", "Charlotte", "Atlanta", "Jacksonville", "Orlando"]}
Die Zwischenstopps werden immer in der richtigen Reihenfolge übergeben, du musst dir also keine Gedanken übers Sortieren machen.
Linus hat am Routing-Programm gearbeitet und bemerkt, dass bei bestimmten Routen einige wichtige Details fehlen. Die anfänglichen Routeninformationen wurden als Hash aufgebaut, und dein Freund möchte, dass du diesen Hash um alles ergänzt, was noch fehlen könnte. Jede Route im System braucht etwas andere Details, deshalb wäre Linus eine allgemeine Lösung wirklich lieber.
Implementiere eine Methode namens extend_route_information(), die zwei Hashes entgegennimmt.
Der erste Hash enthält die Start- und Zielstädte, zwischen denen die Zugroute verläuft.
Der zweite Hash enthält weitere Routing-Details wie Geschwindigkeit, Länge oder Temperatur des Zugs. Die Methode soll einen zusammengeführten Hash mit allen Routing-Informationen zurückgeben.
Der zweite Hash kann andere/weitere Eigenschaften enthalten als die im Beispiel gezeigten.
LocomotiveEngineer.extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
# => {"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
Melde dich bei Exercism an, um Ruby mit 21 Konzepte121 Übungen und echtem menschlichen Mentoring zu lernen und zu meistern, alles kostenlos.