Track
/
Ruby
Ruby
/
Esercizi
/
Macchinista
Macchinista

Macchinista

Esercizio di apprendimento

Introduzione

La scomposizione è l'atto di estrarre gli elementi di una collezione, come un Array o un Hash. I valori scomposti possono poi essere assegnati a delle variabili all'interno della stessa istruzione.

L'assegnazione multipla è la capacità di assegnare più variabili per scomporre dei valori all'interno di una sola istruzione. Questo permette di scrivere codice più conciso e leggibile, e si ottiene separando con una virgola le variabili da assegnare, come in first, second, third = [1, 2, 3].

L'operatore splat (*) e il doppio operatore splat (**) sono spesso usati nei contesti di scomposizione.

Caution

*<variable_name> e **<variable_name> non vanno confusi con * e **. Mentre * e ** sono usati rispettivamente per la moltiplicazione e l'elevamento a potenza, *<variable_name> e **<variable_name> sono usati come operatori di composizione e scomposizione.

Assegnazione multipla

L'assegnazione multipla ti permette di assegnare più variabili in una sola riga. Per separare i valori, usa una virgola ,:

>> a, b = 1, 2
=> [1, 2]
>> a
=> 1

L'assegnazione multipla non è limitata a un solo tipo di dato:

>> x, y, z = 1, "Hello", true
=> [1, "Hello", true]
>> x
=> 1
>> y
=> 'Hello'
>> z
=> true

L'assegnazione multipla può essere usata per scambiare elementi all'interno degli array. Questa pratica è piuttosto comune negli algoritmi di ordinamento. Per esempio:

>> numbers = [1, 2]
=> [1, 2]
>> numbers[0], numbers[1] = numbers[1], numbers[0]
=> [2, 1]
>> numbers
=> [2, 1]
Note

Questa tecnica è anche nota come «Parallel Assignment» e può essere usata per evitare una variabile temporanea.

Se ci sono più variabili che valori, alle variabili in eccesso verrà assegnato nil:

>> a, b, c = 1, 2
=> [1, 2]
>> b
=> 2
>> c
=> nil

Scomposizione

In Ruby è possibile scomporre gli elementi di array/hash in variabili distinte. Dato che i valori compaiono all'interno degli array in un ordine di indice, vengono scompattati nelle variabili nello stesso ordine:

>> fruits = ["apple", "banana", "cherry"]
>> x, y, z = fruits
>> x
=> "apple"

Se ci sono valori che non ti servono, puoi usare _ per indicare «raccolto ma non usato»:

>> fruits = ["apple", "banana", "cherry"]
>> _, _, z = fruits
>> z
=> "cherry"

Scomposizione profonda

Scomporre e assegnare valori da array dentro un array (noto anche come array annidato) funziona allo stesso modo di una scomposizione superficiale, ma richiede un'espressione di scomposizione delimitata (()) per chiarire il contesto o la posizione dei valori:

>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> (a, b), (c, d) = fruits_vegetables
>> a
=> "apple"
>> d
=> "potato"

Puoi anche scompattare in profondità solo una parte di un array annidato:

>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> a, (c, d) = fruits_vegetables
>> a
=> ["apple", "banana"]
>> c
=> "carrot"

Se la scomposizione ha variabili posizionate in modo errato e/o un numero errato di valori, otterrai un errore di sintassi:

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 '['

Fai degli esperimenti qui e noterai che è il primo schema a dettare le regole, non i valori disponibili sul lato destro. L'errore di sintassi non è legato alla struttura dati.

Scomporre un array con il singolo operatore splat (*)

Quando scomponi un array puoi usare l'operatore splat (*) per catturare i valori «rimanenti». Questo è più chiaro rispetto a fare slicing dell'array (che in alcune situazioni risulta meno leggibile). Per esempio, possiamo estrarre il primo elemento e poi assegnare i valori rimanenti a un nuovo array senza il primo elemento:

>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *last = fruits
>> x
=> "apple"
>> last
=> ["banana", "cherry", "orange", "kiwi", "melon", "mango"]

Possiamo anche estrarre i valori all'inizio e alla fine dell'array, raggruppando tutti i valori in mezzo:

>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *middle, y, z = fruits
>> y
=> "melon"
>> middle
=> ["banana", "cherry", "orange", "kiwi"]

Possiamo anche usare * nella scomposizione profonda:

>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>> (a, *rest), b = fruits_vegetables
>> a
=> "apple"
>> rest
=> ["banana", "melon"]

Scomporre un Hash

Scomporre un hash è un po' diverso dallo scomporre un array. Per poter scompattare un hash devi prima convertirlo in un array. Altrimenti non ci sarà alcuna scomposizione:

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory
>> x
=> {:apple=>6, :banana=>2, :cherry=>3}
>> y
=> nil

Per forzare un Hash a diventare un array puoi usare il metodo to_a:

>> 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]

Se vuoi scompattare le chiavi, puoi usare il metodo keys:

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.keys
>> x
=> :apple

Se vuoi scompattare i valori, puoi usare il metodo values:

>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.values
>> x
=> 6

Composizione

Comporre è la capacità di raggruppare più valori in un unico array che viene assegnato a una variabile. Questo è utile quando vuoi scomporre dei valori, apportare delle modifiche e poi ricomporre i risultati in una variabile. Rende anche possibile eseguire delle fusioni su 2 o più array/hash.

Comporre un array con l'operatore splat (*)

Comporre un array si può fare usando l'operatore splat (*). Questo impacchetterà tutti i valori in un array.

>> 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"]

Comporre un hash con il doppio operatore splat (**)

Comporre un hash si fa usando il doppio operatore splat (**). Questo impacchetterà tutte le coppie chiave/valore di un hash in un altro hash, oppure combinerà due hash insieme.

>> 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}

Uso dell'operatore splat (*) e del doppio operatore splat (**) con i metodi

Composizione con i parametri dei metodi

Quando crei un metodo che accetta un numero arbitrario di argomenti, puoi usare *arguments o **keyword_arguments nella definizione del metodo. *arguments si usa per impacchettare un numero arbitrario di argomenti posizionali (non keyword) e **keyword_arguments si usa per impacchettare un numero arbitrario di argomenti keyword.

Uso di *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"]

Uso di **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}

Se il metodo definito non ha alcun parametro definito per gli argomenti keyword (**keyword_arguments o <key_word>: <value>), allora gli argomenti keyword verranno impacchettati in un hash e assegnati all'ultimo parametro.

>> def my_method(a)= a

>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}

*arguments e **keyword_arguments possono anche essere usati in combinazione tra loro:

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"

Puoi anche scrivere argomenti prima e dopo *arguments per avere degli argomenti posizionali specifici. Funziona allo stesso modo della scomposizione di un array.

Caution

Gli argomenti devono essere strutturati in un ordine preciso:

def my_method(<positional_arguments>, *arguments, <positional_arguments>, <keyword_arguments>, **keyword_arguments)

Se non rispetti questo ordine, otterrai un errore.

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]

Puoi scrivere argomenti posizionali prima e dopo *arguments:

>> def my_method(a, *middle, b)= middle

>> my_method(1, 2, 3, 4, 5)
=> [2, 3, 4]

Puoi anche combinare argomenti posizionali, *arguments, argomenti keyword e **keyword_arguments:

>> 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}

Scrivere gli argomenti in un ordine errato provocherà un errore:

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)

Scomporre nelle chiamate ai metodi

Puoi usare l'operatore splat (*) per scompattare un array di argomenti in una chiamata a un metodo:

def my_method(a, b, c)
  p c
  p b
  p a
end

numbers = [1, 2, 3]
my_method(*numbers)
3
2
1

Puoi anche usare il doppio operatore splat (**) per scompattare un hash di argomenti in una chiamata a un metodo:

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

Istruzioni

Il tuo amico Linus è un macchinista che guida treni merci tra le città. È bravissimo con i treni, ma non altrettanto con la logistica o i computer. Vorrebbe chiederti una mano con la programmazione per organizzare i dati dei treni e correggere gli errori nei dati dei percorsi.

Note

Questo esercizio si potrebbe risolvere facilmente con lo slicing, l'indicizzazione e vari metodi di hash. Vorremmo però che ti esercitassi con il packing, l'unpacking e l'assegnazione multipla per risolvere ciascuno dei compiti qui sotto.

1. Crea un array con tutti i vagoni

Il tuo amico tiene traccia dell'identificativo (ID) di ogni vagone, ma non sa mai quanti vagoni il sistema dovrà elaborare in un dato momento. Per il resto del programma di logistica sarebbe molto più semplice avere questi dati raggruppati in un unico array.

Implementa un metodo generate_list_of_wagons() che accetta un numero arbitrario di ID di vagoni. Ogni ID sarà un numero intero positivo. Il metodo deve quindi usare return per restituire gli ID ricevuti come un unico array.

LocomotiveEngineer.generate_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
# => [1, 7, 12, 3, 14, 8, 5]

2. Correggi l'array dei vagoni

A questo punto, inizi a capire come sono fatti i dati e come vengono usati nel programma di logistica. Il sistema degli ID assegna sempre alla locomotiva l'ID 1, mentre agli altri vagoni del treno viene assegnato un ID scelto a caso, maggiore di 1.

Il tuo amico ha dovuto agganciare due nuovi vagoni al treno e si è dimenticato di aggiornare il sistema! Ora i primi due vagoni dell'array del treno devono essere spostati in fondo, altrimenti tutto sarà fuori ordine.

E per complicare le cose, il tuo amico ha appena trovato un secondo array che sembra contenere gli ID dei vagoni mancanti. Ricorda solo che, una volta spostati i nuovi vagoni, gli ID di questo secondo array vanno inseriti subito dopo la locomotiva designata.

Linus ti sarebbe davvero grato se correggessi i suoi errori e unissi i dati.

Implementa un metodo fix_list_of_wagons() che riceve due array contenenti ID di vagoni. Deve spostare i primi due elementi del primo array in fondo e inserire i valori del secondo array dietro (alla destra di) l'ID della locomotiva (1). Il metodo deve poi usare return per restituire un array con le modifiche.

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]

3. Aggiungi le fermate mancanti

Ora che i dati di tutti i vagoni sono corretti, Linus vorrebbe che aggiornassi le informazioni di instradamento del sistema. Lungo un percorso, un treno può fermarsi in alcune stazioni per caricare e/o scaricare merci. Ogni viaggio può avere un numero diverso di questi punti di consegna intermedi. Il tuo amico vorrebbe che aggiornassi l'hash di instradamento del sistema con le informazioni di consegna mancanti o aggiuntive.

Implementa un metodo add_missing_stops() che accetta un hash di instradamento seguito da un numero variabile di argomenti keyword. Questi argomenti possono essere sotto forma di un hash che contiene una o più fermate, oppure un numero qualsiasi di coppie keyword stop_<number>: "city". Il metodo deve poi restituire l'hash di instradamento aggiornato con una chiave aggiuntiva che contiene un array con tutte le fermate aggiunte, in ordine.

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"]}
Note

Le fermate verranno sempre passate nell'ordine corretto, quindi non devi preoccuparti di ordinarle.

4. Estendi le informazioni di instradamento

Linus lavora al programma di instradamento e ha notato che alcuni percorsi mancano di dettagli importanti. Le informazioni iniziali sul percorso sono state costruite come un hash e il tuo amico vorrebbe che aggiornassi quell'hash con tutto ciò che può mancare. Ogni percorso del sistema richiede dettagli un po' diversi, quindi Linus preferirebbe davvero una soluzione generica.

Implementa un metodo chiamato extend_route_information() che accetta due hash. Il primo hash contiene le città di partenza e di destinazione tra cui passa il percorso del treno.

Il secondo hash contiene altri dettagli di instradamento, come la velocità, la lunghezza o la temperatura del treno. Il metodo deve restituire un unico hash con tutte le informazioni di instradamento.

Note

Il secondo hash può contenere proprietà diverse o più numerose rispetto a quelle mostrate nell'esempio.

LocomotiveEngineer.extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
# => {"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
Modifica tramite GitHub Il link si apre in una nuova finestra o scheda
Ruby Exercism

Vuoi iniziare Macchinista?

Iscriviti a Exercism per imparare e padroneggiare Ruby con 21 concetti121 esercizi e il mentoring di persone reali, tutto gratis.