Декомпозиція означає вилучення елементів колекції, як-от Array або Hash.
Декомпозовані значення потім можна присвоїти змінним у межах однієї інструкції.
Множинне присвоєння - це можливість присвоїти значення кільком змінним, декомпозуючи їх, у межах однієї інструкції.
Це робить код лаконічнішим і зрозумілішим, і досягається це розділенням змінних, яким присвоюються значення, комою, як-от first, second, third = [1, 2, 3].
Оператор splat (*) і подвійний splat-оператор (**) часто використовуються в контексті декомпозиції.
Оператор splat (*) можна використати, щоб поєднати кілька масивів в один масив, декомпозуючи кожен із них у новий спільний масив.
Подвійний splat-оператор (**) можна використати, щоб поєднати кілька хешів в один хеш, декомпозуючи кожен із них у новий спільний хеш.
Цей синтаксис потрібен, щоб відрізнити кілька значень, прийнятих як позиційні аргументи, від випадку, коли ми приймаємо будь-яку кількість ключових аргументів.
Коли оператор splat (*) використовується без колекції, він пакує (або компонує) кілька значень в один масив.
Це часто використовується в множинному присвоєнні, щоб згрупувати всі «залишкові» елементи, які не мають окремих присвоєнь, в одну змінну.
У Ruby часто використовують таку поведінку декомпозиції та композиції, коли використовують або визначають методи, що приймають довільну кількість позиційних або ключових аргументів.
Ми часто бачимо, як ці аргументи визначають як def some_method(*arguments, **keyword_arguments), а потім використовують як some_method(*some_array, **some_hash).
*<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
Якщо значень більше, ніж змінних, зайві значення буде проігноровано:
>> a, b, c = 1, 2, 3, 4
>> b
=> 2
>> c
=> 3
У 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
Поекспериментуймо тут, і ми помітимо, що все визначає перша схема, а не наявні значення праворуч. Синтаксична помилка не повʼязана зі структурою даних.
*)Коли ми [декомпозуємо масив][decomposition], можна використати оператор 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>, <key-word_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