У програмуванні ітерація означає процес багаторазового повторення блоку коду.
Цього можна досягти за допомогою таких концепцій, як цикли while та until.
Цього також можна досягти за допомогою рекурсії, тобто функції, яка викликає саму себе.
Однак найчастіше ми хочемо перебрати колекцію елементів, як-от Array, Range або String.
І клас Array, і клас Range у Crystal включають модуль Enumerable, який надає низку методів для перебирання елементів.
Водночас клас String має власний набір методів для перебирання символів.
Crystal також не має інструкції for, як інші мови.
Замість неї є кілька методів, які можна використати для перебирання.
Найпоширеніший спосіб перебрати колекцію - скористатися методом each, який передає кожен елемент колекції у блок.
Це легко зробити з Range.
Скажімо, ми хочемо пройтися циклом від 1 до 3, тоді можна скористатися методом each, щоб перебрати цей діапазон.
(1..3).each do |n|
puts n
end
# Output:
# 1
# 2
# 3
Ще простіше, якщо потрібно перебрати щось певну кількість разів, можна скористатися методом times, який існує в класі Int.
3.times do |n|
puts n
end
# Output:
# 0
# 1
# 2
String
String - це послідовність символів, і він не належить до модуля Enumerable, а отже, має власний набір методів для перебирання символів.
Найпоширеніший спосіб перебрати String - скористатися методом each_char, який передає кожен символ String у блок.
Метод each_char передає у блок обʼєкт Char, а не обʼєкт String.
str = "hello"
str.each_char { |char| puts char }
# Output:
# h
# e
# l
# l
# o
Ще один спосіб перебрати String - скористатися методом each_line.
Цей метод переважно використовують, коли читають файл рядок за рядком.
str = "hello\nworld"
str.each_line do |line|
puts line
end
# Output:
# hello
# world
Enumerable
Модуль Enumerable надає низку методів для перебирання елементів колекції.
Колекції, які включають модуль Enumerable, - це Array, Range, Hash, Set та інші; про решту йтиметься в наступних концепціях.
Найпоширеніший спосіб перебрати Array - скористатися методом each, який передає кожен елемент Array у блок.
arr = [5, 2, 3]
arr.each do |element|
puts element
end
# Output:
# 5
# 2
# 3
Метод map - це ще один спосіб перебрати Array; він повертає новий Array, що містить результати застосування блоку до кожного елемента.
Він схожий на метод each, але повертає новий Array з перетвореними елементами.
Перетворення дуже корисне, коли ми хочемо застосувати функцію до кожного елемента колекції, що є поширеною операцією.
arr = [1, 2, 3]
new_arr = arr.map do |element|
element * 2
end
new_arr
# => [2, 4, 6]
За допомогою керуючої конструкції, як-от if, ми можемо фільтрувати, які елементи перетворювати.
arr = [1, 2, 3]
new_arr = arr.map do |element|
if element.odd?
element * 2
else
element
end
end
new_arr
# => [2, 2, 6]
Іноді нам потрібно знати індекс елемента, який ми перебираємо; для цього можна скористатися методом each_with_index або map_with_index.
Він передає кожен елемент та його індекс у блок; метод також приймає необовʼязковий аргумент, щоб указати початковий індекс.
arr = [1, 2, 3]
arr.each_with_index(4) do |element, index|
puts "Element: #{element}, Index: #{index}"
end
# Output:
# Element: 1, Index: 4
# Element: 2, Index: 5
# Element: 3, Index: 6
Зауважмо, що метод each_char_with_index також доступний для обʼєктів String.
Метод sum повертає суму всіх елементів колекції; він також приймає необовʼязковий блок, щоб перетворити елементи перед підсумовуванням.
Він також приймає необовʼязковий аргумент, щоб указати початкове значення суми.
arr = [1, 2, 3]
arr.sum
# => 6
arr.sum(2) { |n| n * 2 }
# => 14
reduce, або згортка, як його знають в інших мовах, - це метод, який разом із методом поєднання та початковим значенням поєднує всі елементи колекції.
Метод reduce має акумулятор, який передається в блок; акумулятор - це результат попередньої ітерації.
Це стає рекурсивним процесом, який поєднає всі елементи колекції.
arr = [1, 2, 3]
arr.reduce(0) do |acc, n|
acc + n
end
# => 6
reduce може здаватися схожим на sum, але reduce гнучкіший, бо дає змогу вказати початкове значення акумулятора та метод поєднання.
reduce можна використати, щоб реалізувати sum, count та інші методи.
У картковій грі Spellbound Steel у нас є колода карток із різними персонажами, як-от warrior, mage і rogue, або з різними закляттями, як-от fireball, ice storm і lightning bolt. У грі беруть участь від двох до чотирьох гравців, і мета - перемогти інших гравців, граючи картками з вищим рівнем сили.
Ми хочемо отримати перевагу в грі, створивши різні методи для обчислення, сортування та порівняння рівнів сили карток.
Ми не впевнені, чи є картка в колоді. Тому нам потрібен метод, який підкаже, де знайти першу картку в колоді того типу, який ми шукаємо. Або, якщо картки в колоді немає, ми хочемо знати й це.
Реалізуйте метод SpellboundSteel.find_card?, який приймає Array з String-карток і String-картку для пошуку.
Метод повинен повернути індекс першої картки в колоді, яка збігається з карткою для пошуку.
Якщо картки немає в колоді, метод повинен повернути nil.
cards = ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt", "Mage"]
SpellboundSteel.find_card?(cards, "Mage")
# => 1
Щоб картки було легше читати, ми хочемо писати всі символи в назвах карток з великої літери. Так ми легко будемо бачити назви карток, коли граємо ними.
Реалізуйте метод SpellboundSteel.capitalize_cards, який приймає Array з String-карток і повертає картки з усіма символами, написаними з великої літери.
cards = ["warrior", "mage", "rogue", "fireball", "ice storm", "lightning bolt"]
SpellboundSteel.capitalize_cards(cards)
# => ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt"]
У грі важливо обчислювати рівень сили всіх карток. Силу всіх карток можна побачити в наступній таблиці:
| Тип картки | Рівень сили |
|---|---|
| Warrior | 10 |
| Mage | 20 |
| Rogue | 30 |
| Fireball | 15 |
| Ice Storm | 25 |
| Lightning Bolt | 35 |
Реалізуйте метод SpellboundSteel.calculate_power_level, який приймає Array з String-карток і повертає сумарний рівень сили всіх карток.
cards = ["Warrior", "Mage", "Rogue", "Fireball", "Ice Storm", "Lightning Bolt"]
SpellboundSteel.calculate_power_level(cards)
# => 135
У грі є так звані прокляті картки. Це прихована картка, яку може декодувати лише гравець, який її зіграв. Щоб декодувати картку, потрібно взяти кожен другий символ у назві картки й прибрати його. Це дасть назву картки.
Реалізуйте метод SpellboundSteel.decode_card, який приймає String-картку і повертає декодовану картку.
SpellboundSteel.decode_card("Wbalrrrlimoqr")
# => "Warrior"
Зареєструйтеся на Exercism, щоб вивчати й опановувати Crystal, а також 26 концепцій142 вправи та справжнє наставництво від людей, і все це безкоштовно.