Декомпозиція полягає у вилученні елементів колекції, як-от Array або Hash.
Декомпозовані значення потім можна присвоїти змінним у межах однієї інструкції.
Множинне присвоєння дає змогу присвоїти кілька змінних, щоб декомпозувати значення в межах однієї інструкції.
Завдяки цьому код стає лаконічнішим і зрозумілішим; змінні, яким присвоюються значення, розділяють комою, як-от first, second, third = [1, 2, 3].
Оператор splat (*) і подвійний оператор splat (**) часто використовують у контексті декомпозиції.
*<variable_name> і **<variable_name> не варто плутати з * і **.
* і ** використовують для множення та піднесення до степеня відповідно, а *<variable_name> і **<variable_name> — як оператори композиції та декомпозиції.
Множинне присвоєння дає змогу присвоїти кілька змінних в одному рядку.
Щоб розділити значення, використаймо кому ,:
>> a, b = 1, 2
=> [1, 2]
>> a
=> 1
Множинне присвоєння не обмежується одним типом даних:
>> x, y, z = 1, "Hello", true
=> [1, "Hello", true]
>> x
=> 1
>> y
=> 'Hello'
>> z
=> true
Множинне присвоєння можна використати, щоб обміняти місцями елементи масиву. Цей прийом досить поширений в алгоритмах сортування. Наприклад:
>> numbers = [1, 2]
=> [1, 2]
>> numbers[0], numbers[1] = numbers[1], numbers[0]
=> [2, 1]
>> numbers
=> [2, 1]
Це також називають «паралельним присвоєнням»; його можна використати, щоб уникнути тимчасової змінної.
Якщо змінних більше, ніж значень, зайвим змінним буде присвоєно nil:
>> a, b, c = 1, 2
=> [1, 2]
>> b
=> 2
>> c
=> nil
У Ruby можна декомпозувати елементи масивів/хешів в окремі змінні. Оскільки значення розташовані в масивах у порядку індексів, вони розпаковуються у змінні в тому самому порядку:
>> fruits = ["apple", "banana", "cherry"]
>> x, y, z = fruits
>> x
=> "apple"
Якщо якісь значення не потрібні, можна використати _, щоб позначити «зібрано, але не використано»:
>> fruits = ["apple", "banana", "cherry"]
>> _, _, z = fruits
>> z
=> "cherry"
Декомпозиція й присвоєння значень із масивів усередині масиву (який ще називають вкладеним масивом) відбувається так само, як і поверхнева декомпозиція, але потребує виразу декомпозиції з обмежувачами (()), щоб уточнити контекст або позицію значень:
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> (a, b), (c, d) = fruits_vegetables
>> a
=> "apple"
>> d
=> "potato"
Можна також глибоко розпакувати лише частину вкладеного масиву:
>> fruits_vegetables = [["apple", "banana"], ["carrot", "potato"]]
>> a, (c, d) = fruits_vegetables
>> a
=> ["apple", "banana"]
>> c
=> "carrot"
Якщо у декомпозиції змінні розташовані неправильно та/або кількість значень неправильна, ми отримаємо помилку синтаксису:
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 '['
Поекспериментуймо тут, і ми помітимо, що саме перший зразок визначає розподіл, а не наявні значення з правого боку. Помилка синтаксису не повʼязана зі структурою даних.
*)Коли ми декомпозуємо масив, можна використати оператор splat (*), щоб захопити «зайві» значення.
Це зрозуміліше, ніж зріз масиву (який у деяких ситуаціях менш читабельний).
Наприклад, ми можемо вилучити перший елемент, а потім присвоїти решту значень новому масиву без першого елемента:
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *last = fruits
>> x
=> "apple"
>> last
=> ["banana", "cherry", "orange", "kiwi", "melon", "mango"]
Можна також вилучити значення на початку й у кінці масиву, згрупувавши всі значення посередині:
>> fruits = ["apple", "banana", "cherry", "orange", "kiwi", "melon", "mango"]
>> x, *middle, y, z = fruits
>> y
=> "melon"
>> middle
=> ["banana", "cherry", "orange", "kiwi"]
Оператор * можна використати і в глибокій декомпозиції:
>> fruits_vegetables = [["apple", "banana", "melon"], ["carrot", "potato", "tomato"]]
>> (a, *rest), b = fruits_vegetables
>> a
=> "apple"
>> rest
=> ["banana", "melon"]
Hash
Декомпозиція хеша дещо відрізняється від декомпозиції масиву. Щоб розпакувати хеш, спершу треба перетворити його на масив. Інакше декомпозиції не станеться:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory
>> x
=> {:apple=>6, :banana=>2, :cherry=>3}
>> y
=> nil
Щоб привести Hash до масиву, можна використати метод 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]
Якщо потрібно розпакувати ключі, можна використати метод keys:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.keys
>> x
=> :apple
Якщо потрібно розпакувати значення, можна використати метод values:
>> fruits_inventory = {apple: 6, banana: 2, cherry: 3}
>> x, y, z = fruits_inventory.values
>> x
=> 6
Компонування дає змогу згрупувати кілька значень в один масив, який присвоюється змінній. Це стає в пригоді, коли ми хочемо декомпозувати значення, щось у них змінити, а потім скомпонувати результат назад у змінну. Це також дозволяє обʼєднувати два або більше масивів/хешів.
*)Скомпонувати масив можна за допомогою оператора splat (*).
Він спакує всі значення в один масив.
>> 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"]
**)Компонування хеша виконують за допомогою подвійного оператора splat (**).
Він пакує всі пари ключ/значення з одного хеша в інший хеш або обʼєднує два хеші разом.
>> 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}
*) і подвійного оператора splat (**) з методамиКоли ми створюємо метод, який приймає довільну кількість аргументів, у визначенні методу можна використати *arguments або **keyword_arguments.
*arguments використовують, щоб спакувати довільну кількість позиційних (неключових) аргументів, а **keyword_arguments — довільну кількість ключових аргументів.
Використання *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"]
Використання **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}
Якщо у визначеному методі немає параметрів для ключових аргументів (**keyword_arguments або <key_word>: <value>), то ключові аргументи буде спаковано в хеш і присвоєно останньому параметру.
>> def my_method(a)= a
>> my_method(a: 1, b: 2, c: 3)
=> {:a => 1, :b => 2, :c => 3}
*arguments і **keyword_arguments можна також використовувати разом:
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"
Можна також писати аргументи до й після *arguments, щоб задати конкретні позиційні аргументи.
Це працює так само, як декомпозиція масиву.
Аргументи потрібно впорядкувати в певній послідовності:
def my_method(<positional_arguments>, *arguments, <positional_arguments>, <keyword_arguments>, **keyword_arguments)
Якщо не дотриматися цього порядку, виникне помилка.
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]
Позиційні аргументи можна писати до й після *arguments:
>> def my_method(a, *middle, b)= middle
>> my_method(1, 2, 3, 4, 5)
=> [2, 3, 4]
Можна також поєднувати позиційні аргументи, *arguments, ключові аргументи та **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}
Якщо записати аргументи в неправильному порядку, виникне помилка:
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)
Оператор splat (*) можна використати, щоб розпакувати масив аргументів у виклик методу:
def my_method(a, b, c)
p c
p b
p a
end
numbers = [1, 2, 3]
my_method(*numbers)
3
2
1
Подвійний оператор splat (**) можна також використати, щоб розпакувати хеш аргументів у виклик методу:
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
Наш друг Лінус працює машиністом локомотива й водить вантажні потяги між містами. Хоч він чудово дає раду потягам, із логістикою та компʼютерами в нього не так добре. Він хотів би, щоб ми допомогли йому з програмуванням: упорядкували дані про потяги та виправили помилки в даних маршрутів.
Цю вправу можна було б легко розвʼязати за допомогою зрізів, індексації та різних методів hash.
Однак, розвʼязуючи кожне із завдань нижче, ми хочемо попрактикувати пакування, розпакування та множинне присвоєння.
Наш друг веде облік ідентифікаторів (ID) кожного вагона, але ніколи не знає наперед, скільки вагонів системі доведеться обробити. Решті логістичної програми було б набагато простіше, якби ці дані були запаковані в один масив.
Реалізуйте метод generate_list_of_wagons(), який приймає довільну кількість ID вагонів.
Кожен ID - додатне ціле число.
Далі метод має повернути (return) передані ID як один масив.
LocomotiveEngineer.generate_list_of_wagons(1, 7, 12, 3, 14, 8, 5)
# => [1, 7, 12, 3, 14, 8, 5]
На цьому етапі ми вже потроху розуміємо дані та те, як вони використовуються в логістичній програмі. Система ID завжди присвоює локомотиву ID 1, а решті вагонів у потязі - випадково вибраний ID, більший за 1.
Наш друг причепив до потяга два нові вагони й забув оновити систему! Тепер перші два вагони в масиві потяга треба перемістити в кінець, інакше порядок порушиться.
А щоб було ще складніше, наш друг щойно знайшов другий масив, який, схоже, містить зниклі ID вагонів. Він памʼятає лише, що після переміщення нових вагонів ID із цього другого масиву треба поставити одразу після локомотива.
Лінус буде дуже вдячний нам за виправлення його помилок і впорядкування даних.
Реалізуйте метод fix_list_of_wagons(), який приймає два масиви з ID вагонів.
Він має перемістити перші два елементи першого масиву в кінець і вставити значення з другого масиву позаду (праворуч від) ID локомотива (1).
Далі метод має повернути (return) масив із цими змінами.
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]
Тепер, коли всі дані про вагони правильні, Лінус хотів би, щоб ми оновили інформацію про маршрути системи. На транспортному маршруті потяг може зупинятися на кількох різних станціях, щоб узяти й/або залишити вантаж. У кожної подорожі може бути різна кількість таких проміжних точок доставки. Наш друг хотів би, щоб ми оновили хеш маршрутизації системи, додавши будь-яку відсутню або додаткову інформацію про доставку.
Реалізуйте метод add_missing_stops(), який приймає хеш маршрутизації, а далі довільну кількість іменованих аргументів.
Ці аргументи можуть бути подані як хеш з однією чи кількома зупинками або як будь-яка кількість пар іменованих аргументів stop_<number>: "city".
Далі метод має повернути хеш маршрутизації, доповнений додатковим ключем, який містить масив усіх доданих зупинок у порядку.
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"]}
Зупинки завжди передаються в правильному порядку, тож про їхнє сортування дбати не потрібно.
Лінус працював над програмою маршрутизації й помітив, що в деяких маршрутах бракує важливих деталей. Початкову інформацію про маршрут побудовано як хеш, і наш друг хотів би, щоб ми доповнили цей хеш усім, чого бракує. Кожен маршрут у системі потребує трохи інших деталей, тож Лінус дуже хотів би мати універсальне рішення.
Реалізуйте метод extend_route_information(), який приймає два хеші.
Перший хеш містить міста відправлення й прибуття, між якими пролягає маршрут потяга.
Другий хеш містить інші деталі маршруту, як-от швидкість, довжину чи температуру. Метод має повернути зведений хеш з усією інформацією про маршрут.
Другий хеш може містити інші або додаткові властивості, ніж ті, що показані в прикладі.
LocomotiveEngineer.extend_route_information({"from": "Berlin", "to": "Hamburg"}, {"length": "100", "speed": "50"})
# => {"from": "Berlin", "to": "Hamburg", "length": "100", "speed": "50"}
Зареєструйтеся на Exercism, щоб вивчати й опановувати Ruby, а також 21 концепція121 вправа та справжнє наставництво від людей, і все це безкоштовно.