گاهی میخواهید یک دنباله را در یک مقدار واحد ترکیب کنید؛ گاهی میخواهید هر مقدار میانی را ببینید که این ترکیب در طول مسیر تولید میکند. 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 یک مقدار را به یک دنباله میگستراند.
شما کتابدار هستید و دفتر حساب اعضا را نگه میدارید. هر هفته دو نوع کار روی میزتان میرسد:
هر هفته حسابها را جمعبندی میکنید: یک ماندهی نهایی پس از اعمال درخواستها، یک ماندهی جاری روزبهروز بر پایهی تراکنشها و یک کمترین ماندهی تدریجی برای علامتزدن بازههایی که در آنها جریمهها جهش کردهاند.
تابعی به اسم protected-balance تعریف کنید که یک ماندهی opening و یک آرایه از requests (مبالغ علامتدار) میگیرد و ماندهی نهایی را پس از اعمال هر درخواست به ترتیب برمیگرداند. برداشتی که مانده را به زیر صفر ببرد، فقط تا اندازهی مبلغ موجود اعمال میشود، پس ماندهی جاری در صفر ثابت میماند.
100 { 50 -200 30 } protected-balance .
! => 30
500 { 100 -300 -250 } protected-balance .
! => 50
0 { -10 50 } protected-balance .
! => 50
تابعی به اسم running-balance تعریف کنید که یک آرایه از transactions میگیرد و دنبالهای به همان طول برمیگرداند که عنصر i-اُم آن، ماندهی پس از نخستین i+1 تراکنش است (نسبت به ماندهی شروع صفر).
{ 50 -30 -20 100 } running-balance .
! => { 50 20 0 100 }
تابعی به اسم 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 }
کتابخانه برنامهی بخشودگی جریمه اجرا میکند: ماندهی بدهی هر عضو در هر دورهی پرداخت نصف میشود تا به آستانهی بخشش برسد یا از آن پایینتر بیاید. تابعی به اسم 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 .
! => { }