الصف هو قائمة مرتّبة ومنتهية من العناصر، وهو غير قابل للتغيير. ويشترط الصف أن يكون لكل موضع نوع ثابت. وهذا يعني بدوره أن المترجم يعرف نوع كل موضع. يمكن أن تختلف الأنواع المستخدمة في الصف من موضع إلى آخر، لكن يجب أن تكون الأنواع معروفة في وقت الترجمة.
حسب ما إذا كان يمكن استنتاج أنواع قيم الصف وقت الترجمة أم لا، يمكن إنشاء الصف بطرق مختلفة. إذا كانت القيم معروفة في وقت الترجمة، فيمكن إنشاء الصف باستخدام صيغة الصف الحرفية، وإلا فيجب تعريفها صراحةً. ومن المهم أيضًا أن تتوافق أنواع القيم مع الأنواع المحددة في الصف، وأن يتوافق عدد القيم مع عدد الأنواع المحددة. إليك مثالًا على التعريف باستخدام صيغة الصف الحرفية:
tuple = {1, "foo", 'c'} # Tuple(Int32, String, Char)
ويمكنك أيضًا إنشاء صف باستخدام الصنف Tuple.
tuple = Tuple(Int32, String, Char).new(1, "foo", 'c')
أو يمكنك تحديد نوع المتغير الذي يُسنَد إليه الصف صراحةً.
tuple : Tuple(Int32, String, Char) = {1, "foo", 'c'}
قد يكون تحديد نوع الصف صراحةً مفيدًا، فهو يسمح بتعريف موضع يحمل نوعًا اتحاديًا. وهذا يعني أن الموضع الواحد يمكن أن يحمل عدة أنواع.
tuple : Tuple(Int32 | String, String, Char) = {1, "foo", 'c'}
يمكنك إنشاء صف من مصفوفة باستخدام الطريقة from في الصنف Tuple.
ويتطلب ذلك تحديد نوع الصف.
array = [1, "foo", 'c']
tuple = Tuple(Int32, String, Char).from(array)
يمكنك تحويل الصف إلى مصفوفة باستخدام الطريقة to_a.
نوع عناصر المصفوفة الناتجة هو اتحاد أنواع الحقول في الصف.
tuple = {1, "foo", 'c'}
array = tuple.to_a
array # => [1, "foo", 'c']
مثل المصفوفات، تفهرس الصفوف بدءًا من الصفر، أي أن العنصر الأول عند الفهرس 0.
لكن على عكس المصفوفات، نوع كل عنصر ثابت ومعروف في وقت الترجمة، ولذلك عندما تفهرس صفًا فإن نوع العنصر يخص الموضع نفسه.
للوصول إلى عنصر في صف، يمكنك استخدام العامل [].
array = [1, "foo", 'c']
array[0] # => 1
typeof(array[0]) # => Int32 | String | Char
tuple = {1, "foo", 'c'}
tuple[0] # => 1
typeof(tuple[0]) # => Int32
وثمة فرق آخر في الوصول إلى العناصر مقارنةً بالمصفوفات: إذا حُدّد الفهرس، فسيتحقق المترجم من أنه يقع ضمن حدود الصف. وهذا يعني أنك ستحصل على خطأ في وقت الترجمة بدلًا من خطأ في وقت التشغيل.
tuple = {1, "foo", 'c'}
tuple[3]
# => Error: index out of bounds for Tuple(Int32, String, Char) (3 not in -3..2)
لكن إذا كان الفهرس مخزّنًا في متغير، فلن يتمكن المترجم من التحقق من وقوعه ضمن حدود الصف في وقت الترجمة، وسيعطيك خطأ في وقت التشغيل بدلًا من ذلك.
يمكنك الحصول على صف فرعي من صف باستخدام العامل [] مع مدى.
والناتج صف جديد يضم العناصر ضمن المدى المحدد.
يجب تحديد المدى في وقت الترجمة، وإلا فلن يتمكن المترجم من معرفة أنواع العناصر في الصف الفرعي.
وهذا يعني أن المدى يجب أن يكون مدى حرفيًا، لا مسندًا إلى متغير.
tuple = {1, "foo", 'c'}
subtuple = tuple[0..1] # Tuple(Int32, String)
i = 0..1
tuple[i]
# Error: Tuple#[](Range) can only be called with range literals known at compile-time
تكون الصفوف مفيدة عندما تريد جمع عدد ثابت من القيم معًا وتكون أنواع هذه القيم معروفة في وقت الترجمة. وذلك لأن الصفوف تستهلك ذاكرة أقل وتكون أسرع من المصفوفات بسبب عدم قابليتها للتغيير. ومن حالات الاستخدام الأخرى إرجاع عدة قيم من طريقة. وهذا مفيد بشكل خاص إذا كانت القيم ذات أنواع مختلفة، لأن كل موضع في الصف يمكن أن يكون له نوع مختلف.
لا ينبغي استخدام الصفوف عندما تحتاج إلى بنية بيانات يمكن أن يزيد حجمها أو ينقص، أو تحتاج إلى تعديل متكرر.
الرموز هي معرّفات مسمّاة يمكن استخدامها للإشارة إلى قيمة.
تُنشأ الرموز من خلال رمز حرفي، وذلك بوضع المحرف : قبل الاسم، مثل :foo.
كما يمكن كتابتها بين علامات تنصيص، مثل :"foo"، مما يسمح مثلًا بوجود مسافات في الاسم.
:foo # => :foo
:"foo boo" # => :"foo boo"
:foo == :"foo" # => true
تُستخدم الرموز في مواضع كثيرة في اللغة، بما في ذلك كمفاتيح في الصفوف المسمّاة، ولتمثيل أسماء الطرق والمتغيرات.
تختلف الرموز في Crystal اختلافًا كبيرًا عنها في Ruby. ففي Crystal، الرمز نوع من الثوابت، ولذلك يُسنَد في وقت الترجمة. وهذا يعني أنه لا يمكن إنشاء الرموز ديناميكيًا، وهو أمر ممكن في Ruby.
تُمثَّل الرموز في Crystal على هيئة Int32، ما يجعلها فعّالة جدًا.
يمكن تحويل الرموز إلى سلاسل نصية، لكن ليس العكس. وذلك لأن الرموز تُنشأ في وقت الترجمة، أما السلاسل النصية فتُنشأ في وقت التشغيل.
:foo.to_s # => "foo"
بينما كنت تستعد لخبز الكوكيز لأصدقائك، اكتشفت أن عليك تحويل بعض القياسات المستخدمة في الوصفة. وبما أنك لا تعرف سوى النظام المتري، فأنت بحاجة إلى إيجاد طريقة لتحويل قياسات الخبز الأمريكية الشائعة إلى المليلتر (mL) لتسهيل الأمر عليك.
استخدم جدول التحويل التالي في حلك:
| الوحدة المراد تحويلها | الحجم | بالمليلتر (mL) |
|---|---|---|
| mL | 1 | 1 |
| كوب أمريكي | 1 | 240 |
| أونصة سائلة أمريكية | 1 | 30 |
| ملعقة صغيرة أمريكية | 1 | 5 |
| ملعقة كبيرة أمريكية | 1 | 15 |
وبما أنك مبرمج موهوب في طور التدريب، فقد قررت استخدام المليلتر كوحدة وسيطة لتسهيل التحويل من أي وحدة مذكورة إلى أي وحدة أخرى (حتى إلى نفسها).
نفّذ الطريقة KitchenCalculator.get_volume.
عند تمرير زوج الحجم، ينبغي أن تُرجع المكوّن الرقمي فقط.
KitchenCalculator.get_volume({:cup, 2.0})
# => 2.0
نفّذ الطريقة KitchenCalculator.to_milliliter.
عند تمرير زوج الحجم، ينبغي أن تُحوّل الحجم إلى مليلترات باستخدام جدول التحويل.
KitchenCalculator.to_milliliter({:cup, 2.5})
# => {:milliliter, 600.0}
نفّذ الطريقة KitchenCalculator.from_milliliter.
عند تمرير زوج الحجم والوحدة المطلوبة، ينبغي أن تُحوّل الحجم إلى الوحدة المطلوبة باستخدام جدول التحويل.
KitchenCalculator.from_milliliter({:milliliter, 1320.0}, :cup)
# => {:cup, 5.5}
نفّذ الطريقة KitchenCalculator.convert.
عند تمرير زوج الحجم والوحدة المطلوبة، ينبغي أن تُحوّل الحجم المُعطى إلى الوحدة المطلوبة.
KitchenCalculator.convert({:teaspoon, 9.0}, :tablespoon)
# => {:tablespoon, 3.0}
سجّل في Exercism لتتعلّم وتتقن Crystal عبر 26 مفهومًا142 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.