المسارات
/
Factor
Factor
/
التمارين
/
سجل أمين المكتبة
سجل أمين المكتبة

سجل أمين المكتبة

تمرين تعلّمي

مقدمة

أحيانًا تريد دمج تسلسل في قيمة واحدة، وأحيانًا تريد رؤية كل قيمة وسيطة ينتجها الدمج على طول الطريق. ويقسم Factor هذا إلى أداتين: reduce (في sequences) من أجل الطيّ إلى قيمة واحدة، والعائلة التراكمية في math.statistics من أجل الصيغة الجارية.

reduce: الطيّ العام

reduce ( seq init quot: ( prev elt -- next ) -- result )

يمرّ reduce على تسلسل عنصرًا عنصرًا، حاملًا معه نتيجة جارية (المراكم) ويمرّرها إلى اقتباس من وسيطين. يستقبل الاقتباس المراكم الجاري والعنصر التالي، وأيًّا كان ما يتركه على المكدّس يصبح المراكم الجديد.

USING: math sequences ;

{ 1 2 3 4 } 0 [ + ] reduce .         ! => 10
{ 1 2 3 4 } 1 [ * ] reduce .         ! => 24

القيمة الأولية غير الصفرية ودالة الدمج المخصّصة هما ما لا تستطيع sum وproduct بلوغه في reduce. على سبيل المثال، أكبر قيمة في تسلسل، مع قيمة افتراضية إن لم يتفوّق عليها أي عنصر:

USING: math.order ;

{ 3 1 -4 5 -2 } 0 [ max ] reduce .   ! => 5
{ -3 -1 -4 }    0 [ max ] reduce .   ! => 0

تشارك القيمة الأولية 0 في المقارنة: فهي تعمل كنتيجة عندما يخسر كل عنصر، لذا يُنتج تسلسل كل قيمه سالبة 0 بدلًا من أصغر قيمة اعتباطية.

الاختزالات التراكمية

أحيانًا تريد كل النتائج الوسيطة، لا النتيجة النهائية فقط. وتُرجع العائلة التراكمية في math.statistics تسلسلًا بالطول نفسه الذي للمدخل، حيث يكون كل موضع هو الاختزال على البادئة المنتهية عند ذلك الموضع:

cum-sum     ( seq -- newseq )    ! running total
cum-product ( seq -- newseq )    ! running product
cum-min     ( seq -- newseq )    ! running minimum
cum-max     ( seq -- newseq )    ! running maximum
USING: math.statistics ;

{ 3 1 4 1 5 9 2 6 } cum-sum .        ! => { 3 4 8 9 14 23 25 31 }
{ 1 2 3 4 } cum-product .            ! => { 1 2 6 24 }
{ 3 1 4 1 5 9 2 6 } cum-min .        ! => { 3 1 1 1 1 1 1 1 }
{ 3 1 4 1 5 9 2 6 } cum-max .        ! => { 3 3 4 4 5 9 9 9 }

من الأنماط المفيدة ربط الاختزالات التراكمية ببعضها: فمخرجات إحداها تسلسل في نفسه، جاهز للتغذية في أخرى. وهذا يجعل «ملخّص جارٍ لملخّص جارٍ» قابلًا للتعبير عنه بكلمتين. والتركيبات مرنة، فاقرن بينها بناءً على ما يلخّصه كل خطوة.

produce: النشر

يستهلك reduce تسلسلًا ليصير قيمة. أما produce (في sequences) فيسير في الاتجاه المعاكس: يُنشئ تسلسلًا من قيمة أولية عبر الاختبار والتقدّم بصورة متكررة:

produce ( pred quot -- seq )

تُشغّل كل تكرار أولًا pred على الحالة الراهنة، وإذا أرجعت قيمة تُعتبر صحيحة، يُستدعى quot لإنتاج العنصر التالي وتحديث الحالة. وعندما تُرجع pred القيمة f، يتوقف التكرار وتُرجَع العناصر المجمّعة.

ومن الأمثلة الكلاسيكية متتالية فيبوناتشي (كل عدد هو مجموع العددين السابقين). والحالة الجارية هي الزوج (a, b). وتُصدر كل خطوة b، ثم تستبدل الزوج بـ (b, a + b):

USING: kernel math sequences ;

! Fibonacci numbers strictly below 100:
0 1 [ dup 100 < ] [ tuck + over ] produce 2nip .
! => { 1 1 2 3 5 8 13 21 34 55 89 }

تمتد الحالة الجارية على قيمتين، لذا يستخدم الجسم tuck (في kernel)، وهو الخلط الثلاثي الذي ينسخ العنصر الأعلى أسفل العنصر الثاني، لتقديم الزوج، ويستخدم 2nip (وهو أيضًا في kernel، النظير الثنائي لـ nip) للترتيب في النهاية. وإذا قرأنا الاستدعاء من اليسار إلى اليمين:

  • ينظر الشرط [ dup 100 < ] إلى أعلى الزوج (وهو العدد التالي الذي سيُصدر) ويستمر ما دام لا يزال دون الحد.
  • يقدّم الجسم [ tuck + over ] الحالة إلى (b, a + b) ويُصدر b، تاركًا ثلاث قيم على المكدّس: الزوج الجديد في الأسفل، والعدد المُصدر في الأعلى.
  • بعد توقف produce، تُهمَل القيمتان الزائدتان (الزوج الأخير) بـ 2nip، فلا يبقى إلا التسلسل الناتج.

produce هو النظير التام لـ reduce: فبينما يطوي reduce تسلسلًا حتى يصير قيمة، ينشر produce قيمة حتى تصير تسلسلًا.

التعليمات

أنت أمين المكتبة، وتُمسك بدفتر حسابات المستعيرين. يصل إلى مكتبك كل أسبوع نوعان من العمل:

  • طابور من الطلبات، وهي أرصدة دائنة يطلب المستعير تطبيقها (إرجاع الكتب، والغرامات المدفوعة)، وأرصدة مدينة جديدة سجّلها النظام (غرامات التأخير المستحقة حديثًا). حساب المستعير محميّ بالرصيد الدائن: الرصيد الدائن الكبير بما يكفي لإيقاع المستعير في المديونية لا يُطبَّق إلا بمقدار ما هو مستحق، فلا ينخفض الرصيد الجاري أبدًا عن الصفر.
  • قائمة من المعاملات، وهي قيود سُجّلت بالفعل على الحساب. المبالغ الموجبة أرصدة مدينة (غرامات جديدة)، والمبالغ السالبة أرصدة دائنة (مدفوعات).

في كل أسبوع تُجري الجرد: رصيد نهائي بعد تنفيذ الطلبات، ورصيد جارٍ يومي من المعاملات، وحد أدنى جارٍ لرصد الفترات التي ارتفعت فيها الغرامات.

1. نفّذ طابور الطلبات

عرّف protected-balance لتأخذ رصيد opening ومصفوفة من requests (مبالغ بإشارة) وتُرجع الرصيد النهائي بعد تنفيذ كل طلب على التوالي. السحب الذي قد يخفض الرصيد إلى ما دون الصفر يُنفَّذ فقط بمقدار المبلغ المتاح، بحيث يستقر الرصيد الجاري عند الصفر.

100 { 50 -200 30 } protected-balance .
! => 30

500 { 100 -300 -250 } protected-balance .
! => 50

0 { -10 50 } protected-balance .
! => 50

2. الرصيد الجاري

عرّف running-balance لتأخذ مصفوفة من transactions وتُرجع متتالية بالطول نفسه، حيث يكون عنصرها ذو الفهرس i هو الرصيد بعد أول i+1 من المعاملات (نسبةً إلى رصيد ابتدائي صفر).

{ 50 -30 -20 100 } running-balance .
! => { 50 20 0 100 }

3. أقل رصيد حتى الآن

عرّف least-balance-so-far لتأخذ مصفوفة من transactions وتُرجع متتالية بالطول نفسه، حيث يكون عنصرها ذو الفهرس i هو أدنى رصيد جارٍ لوحظ حتى الموضع i ضمناً. هذا هو الحد الأدنى الجاري، وهو مفيد لرصد الأيام التي بدا فيها الحساب محفوفًا بالمخاطر.

{ 50 -30 -20 100 } least-balance-so-far .
! => { 50 20 0 0 }

{ 200 -50 -100 -200 } least-balance-so-far .
! => { 200 150 50 -150 }

4. التنصيف حتى الهدف

تُشغّل المكتبة برنامج عفو عن الغرامات: يُنصَّف الرصيد المستحق للمستعير في كل فترة سداد حتى يصل إلى حد التسامح أو ينخفض عنه. عرّف halve-until لتأخذ principal وtarget، وتُرجع متتالية القيم المنصَّفة (باستخدام القسمة الصحيحة) بدءًا من أول تنصيف، وتستمر ما دامت القيمة الجارية لا تزال أكبر تمامًا من target. وستكون آخر قيمة مُصدَرة هي أول قيمة تنخفض إلى target أو عنه.

100 5 halve-until .
! => { 50 25 12 6 3 }

64 1 halve-until .
! => { 32 16 8 4 2 1 }

3 5 halve-until .
! => { }
تعديل عبر GitHub يفتح الرابط في نافذة أو علامة تبويب جديدة
Factor Exercism

مستعد لبدء سجل أمين المكتبة؟

سجّل في Exercism لتتعلّم وتتقن Factor عبر 47 مفهومًا163 تمرينًا، وإرشاد بشري حقيقي، وكل ذلك مجانًا.