در برنامهنویسی، تکرار به فرایند تکرار یک «بلوک کد» چندین بار اشاره دارد. این کار را میتوان با استفاده از مفاهیمی مانند «حلقههای while» و «حلقههای until» انجام داد. همچنین میتوان از «بازگشتی» استفاده کرد، که تابعی است که خودش را فراخوانی میکند.
اما بیشتر اوقات میخواهید روی مجموعهای از عنصرها تکرار کنید، مانند Array، Range یا String. هر دو کلاس Array و Range در Crystal ماژول Enumerable را شامل میشوند که روشهای متعددی برای تکرار روی عنصرها فراهم میکند. در همین حال، کلاس String مجموعهی روشهای مخصوص خود را برای تکرار روی کاراکترها دارد.
Crystal همچنین هیچ دستور for مانند سایر زبانها ندارد. در عوض، چندین روش دارد که میتوان برای تکرار از آنها استفاده کرد.
رایجترین روش برای تکرار روی یک مجموعه، استفاده از روش each است که هر عنصر مجموعه را به یک بلوک میدهد.
این کار را بهراحتی میتوان با یک Range انجام داد.
فرض کنید میخواهید بین ۱ و ۳ حلقه بزنید؛ میتوانید از روش 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 است؛ آرایهی جدیدی برمیگرداند که نتیجهی اعمال بلوک روی هر عنصر را در خود دارد.
این روش شبیه 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 یا همان fold که در زبانهای دیگر به این اسم شناخته میشود، روشی است که با یک روش ترکیب و یک مقدار اولیه، همهی عنصرهای مجموعه را با هم ترکیب میکند.
روش reduce یک «انباره» دارد که به بلوک داده میشود؛ این انباره نتیجهی تکرار قبلی است.
این فرایند به یک روند بازگشتی تبدیل میشود که همهی عنصرهای مجموعه را با هم ترکیب میکند.
arr = [1, 2, 3]
arr.reduce(0) do |acc, n|
acc + n
end
# => 6
reduce ممکن است شبیه sum به نظر برسد، اما reduce انعطافپذیرتر است، چون به شما اجازه میدهد مقدار اولیهی انباره و روش ترکیب را مشخص کنید.
میتوان از reduce برای پیادهسازی sum، count و روشهای دیگر استفاده کرد.
در بازی کارتی Spellbound Steel، یک دسته کارت دارید که روی کارتهایش یا شخصیتهای مختلفی مثل warriors، mages و rogues آمده است، یا طلسمهای مختلفی مثل 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"]
در این بازی، محاسبهی سطح قدرت همهی کارتها مهم است. قدرت همهی کارتها را میتوانید در جدول زیر ببینید:
| Card Type | Power Level |
|---|---|
| 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"