في البرمجة، يشير التكرار إلى عملية إعادة تنفيذ كتلة من الكود عدة مرات.
ويمكن تحقيق ذلك باستخدام مفاهيم مثل حلقات 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، لديك مجموعة من البطاقات تحمل إما شخصيات مختلفة مثل المحاربين والسحرة واللصوص، أو تعاويذ مختلفة مثل كرة النار وعاصفة الجليد وصاعقة البرق. يشارك في اللعبة من لاعبَين إلى أربعة لاعبين، والهدف هو هزيمة اللاعبين الآخرين بلعب بطاقات ذات مستويات قوة أعلى.
تريد أن تكون لك اليد العليا في اللعبة عبر إنشاء طرق متنوعة لحساب مستويات قوة البطاقات وترتيبها ومقارنتها.
لست متأكدًا مما إذا كانت بطاقة ما موجودة في المجموعة أم لا. لذلك تريد طريقة تخبرك بمكان العثور على أول بطاقة في المجموعة من النوع الذي تبحث عنه. وإن لم تكن البطاقة في المجموعة، فأنت تريد أن تعرف ذلك أيضًا.
نفّذ طريقة 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 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.