المجالات (Ranges) في لغة Kotlin

من موسوعة حسوب
اذهب إلى: تصفح، ابحث

استخدام المجالات

تُصاغُ تعابير المجالات (range expressions) من خلال دوال rangeTo التي تعتمد على المعامل .. والذي بدوره يتُمَّم بالمعاملين inو ‎!in، إذ من الممكن أن يُعرَّف المجال لأيّ نوعٍ يقبل المقارنة، أما في حالة الأنواع الأساسيّة المتكاملة (integral primitive types) فلها تعريف استخدام مُحسَّن (optimized)، وهذه بعض الأمثلة:
if (i in 1..10) { // مكافئ للصياغة
                  // 1 <= i && i <= 10
    println(i)
}
ولمجالات الأنواع المتكاملة (IntRangeو LongRangeو CharRange) ميّزة إضافيّة إذ يمكن المرور بعناصرها (iteration)، حيث يحوِّلها المترجم لشكلٍ مناظرٍ لحلقة for المُفهرَسة (indexed) في Java دون أيّ تكلفةٍ زائدةٍ، مثل:
for (i in 1..4) print(i) // "1234" ستظهر القيم

for (i in 4..1) print(i) // لن تظهر أيّة قيمة
ولكن ماذا لو دعت الحاجة للمرور بالعناصر بالترتيب العكسيّ؟ هذا بسيط! ويكون باستخدام الدالة downTo()‎ المُعرَّفة في المكتبة القياسيّة، بالشكل:
for (i in 4 downTo 1) print(i) // "4321" ستظهر القيم بالترتيب العكسي
وبالإمكان أيضًا الانتقال بقيمة خطوةٍ غير الواحد وذلك باستخدام الدالة step()‎ كما في الشيفرة الآتية:
for (i in 1..4 step 2) print(i) // "13" ستظهر القيم

for (i in 4 downTo 1 step 2) print(i) // "42" ستظهر القيم
ولإنشاء مجالٍ مفتوح النهاية (دون الوصول للعنصر الأخير فيه) يُستفاد من الدالة until كما في الشيفرة:
for (i in 1 until 10) { // تُستثنى القيمة 10 وهي مكافئة للشكل
                        // i in [1, 10)
     println(i)
}

آليّة عمل المجالات

تُعرَّف المجالاتُ استخدامَ (implement) الواجهة المشتركة ClosedRange<T>‎ في المكتبة، والتي تعبِّر عن المفهوم الرياضيّ للمجال المُغلَق (يشمل قيمتيّ البداية والنهاية) والمُعرَّفِ للقيم القابلة للمقارنة (comparable)، وله نقطتان تُحدِّدانه: البداية start والنهاية endInclusive (المُتضمَّنة في المجال)، و تكون contains العمليةَ الأساسيّة المُستخدمَة فيها وهي تعتمد على صيغةٍ تستخدم المعاملين inو ‎!in.

أمّا أنواع المتتاليات المتكاملة (integral type progressions) مثل IntProgressionوLongProgressionو CharProgressionفهي تعبِّر عن المتتاليات الرياضيّة وتُعرَّف بالعنصر الأول first والعنصر الأخير last وبخطوة step غير صفريّة، حيث يبدأ المجال بالقيمة الأولى first وتنتُج كلُّ قيمةٍ تاليةٍ عن القيمة السابقة بعد إضافة الخطوة step وصولًا للعنصر الأخير last وذلك ما لم تكن المتتالية فارغة.

وتُعدُّ المتتاليات نوعًا فرعيًا (subtype) منIterable<N>‎ حيث يعبِّر النوع N عن أحد الأنواع Int أو Long أو Char ، وبالتالي فبالإمكان استخدامها في حلقات for وبعض الدوالّ مثل map و filter ومايماثلهما، ويكون عبور المتتالية Progression  مماثلًا لحلقة for المُفهرَسة (indexed) في Java أو JavaScript ، مثل:
for (int i = first; i != last; i += step) {
  // ...
}
إذ يُنشِئ المُعامِل .. -في حالة الأنواع المتكاملة- كائنًا (object) يعرِّف استخدام (implement) كلٍ من ClosedRange<T>‎ و ‎*Progression فعلى سبيل المثال؛ يعرِّف المجال IntRange استخدام ClosedRange<T>‎ ويُوسِّع (extend) المتتالية IntProgression ، وبالتالي فإنّ كلَّ العمليات المُعرَّفة لصالح IntProgression ستُتاح أيضًا في IntRange، وتكون النتيجة الصادرة عن الدالتين downTo()‎ و step()‎ من النوع ‎*Progression دائمًا. وتُنشَأ المتتاليات باستخدام الدالة fromClosedRange المُعرَّفة في كائناتها المُرافِقة (companion objects) بالشكل:
IntProgression.fromClosedRange(start, end, step)
حيث يُحسَب العنصرُ الأخير last من المتتالية لإيجاد أكبر قيمةٍ لا تتجاوز القيمة end إن كانت الخطوة step موجبة، أو لإيجاد أصغر قيمةٍ لا تقِّل عن القيمة end في حالة الخطوة step السالبة بحيث يكون ‎(last - first) % step == 0 .

بعض الدوال المساعدة

الدالة rangTo()‎

تستدعي معامِلات الدالة rangTo()‎ -في الأنواع المتكاملة- البواني الموجودة في الأصناف ‎*Range مثل:
class Int {
    //...
    operator fun rangeTo(other: Long): LongRange = LongRange(this, other)
    //...
    operator fun rangeTo(other: Int): IntRange = IntRange(this, other)
    //...
}
ولا تُعرِّف أعداد الفاصلة العائمة (مثل Float و Double) المعامِل rangeTo ، بل يُستخدَم بدلًا من ذلك النوع المُعمَّم (generic) والموجود في المكتبة القياسيّة مثل:
    public operator fun <T: Comparable<T>> T.rangeTo(that: T): ClosedRange<T>
ولا يُمكِن المرور بعناصر (iteration) المجال الناتج عن هذه الدالة.

الدالة downTo()‎

تٌعرَّف الدالة الإضافيّة (extension function)‏ downTo()‎ لأيّ زوجٍ (pair) من الأنواع المتكاملة (integral types)، وفيما يلي مثالان عنها:
fun Long.downTo(other: Int): LongProgression {
    return LongProgression.fromClosedRange(this, other.toLong(), -1L)
}

fun Byte.downTo(other: Int): IntProgression {
    return IntProgression.fromClosedRange(this.toInt(), other, -1)
}

الدالة reversed()‎ 

تٌعرَّف الدالة الإضافيّة (extension function)‏ reversed()‎ لكلّ أصناف المتتاليات ‎*Progression، وينتُج عن ذلك متتاليةٌ معكوسةٌ، أي:
fun IntProgression.reversed(): IntProgression {
    return IntProgression.fromClosedRange(last, first, -step)
}

الدالة step()‎

تٌعرَّف الدالة الإضافيّة (extension function)‏ step()‎ لكلّ أصناف المتتاليات ‎*Progression، وينتُج عن ذلك متتالياتٌ بخطوةٍ مُعدَّلةٍ، حيث تكون قيمة الخطوة موجبةً دائمًا وبالتالي لا تغيّر هذه الدالة من اتجاه المرور بعناصر المجال، مثل:
fun IntProgression.step(step: Int): IntProgression {
    if (step <= 0) throw IllegalArgumentException("Step must be positive, was: $step")
    return IntProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}

fun CharProgression.step(step: Int): CharProgression {
    if (step <= 0) throw IllegalArgumentException("Step must be positive, was: $step")
    return CharProgression.fromClosedRange(first, last, if (this.step > 0) step else -step)
}
وقد تختلف القيمة last للمتتالية المُعادَة عن مثيلتها في المتتالية الأصليّة للحفاظ على الشرط ‎(last - first) % step == 0، مثل:
(1..12 step 2).last == 11  // [1, 3, 5, 7, 9, 11] المتتالية بالقيم
(1..12 step 3).last == 10  // [1, 4, 7, 10] المتتالية بالقيم
(1..12 step 4).last == 9   // [1, 5, 9] المتتالية بالقيم

مصادر