مسیرها
/
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

یک مقدار آغازین ناصفر و یک ترکیب‌کننده‌ی سفارشی همان بخش‌هایی از reduce هستند که sum و product به آن‌ها دسترسی ندارند. برای نمونه، بزرگ‌ترین مقدار در یک دنباله، همراه با یک مقدار پیش‌فرض در صورتی که هیچ مقداری از آن پیشی نگیرد:

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 تمرین و مربی‌گری انسانی واقعی یاد بگیرید و در آن استاد شوید، همه‌ی این‌ها رایگان.