فرض کنید میخواهیم اعداد یک آرایه را با هم جمع بزنیم. راههای زیادی برای رسیدن به این هدف وجود دارد.
در بسیاری از زبانها، این مسئله به شکل یک حلقه با یک متغیر انباشتگر بیان میشود.
این مسئله را میتوان به شکل یک تابع بازگشتی نوشت. در شبهکد، ممکن است چیزی شبیه این داشته باشیم.
function Add(X, Sum=0):
if X is empty then
return Sum
else
return Add(rest(X), Sum + first(X))
end
end
این روش تقسیم مسئله به قطعات کوچکتر را میتوان «کاهش به سمت حالت پایه» هم توصیف کرد.
کاهش روشی برای ترکیب همهی عناصر یک ساختار داده در یک مقدار واحد است. این فرایند روی ساختار داده پیمایش میکند و یک تابع را به هر عنصر اعمال میکند تا نتیجهی انباشتهشده را بهروزرسانی کند.
در jq، این فرایند در فیلتر reduce پیادهسازی شده است.
در زبانهای دیگر، ممکن است fold، fold-left، inject یا aggregate نامیده شود.
عبارت reduce در jq به این شکل است.
reduce STREAM_EXPRESSION as $var (INITIAL_VALUE; UPDATE_EXPRESSION)
STREAM_EXPRESSION یک «جریان» از عناصر است که هر کدام به نوبت در متغیر $var ذخیره میشود.
.[] استفاده کنید: $myArray | .[].INITIAL_VALUE مقدار شروع نتیجهی انباشتهشده است (که به آن «انباشتگر» میگویند).UPDATE_EXPRESSION مقدار جاری ($var) را در انباشتگر ترکیب («فولد») میکند.
. مقدار انباشتگر است.reduce است.بیایید به یک مثال نگاه کنیم: جمع زدن اعداد یک آرایه.
فیلتر add دقیقاً همین کار را میکند، اما خواهیم دید چطور میتوان آن را پیادهسازی کرد.
اگر [10, 20, 30, 40] را به عنوان ورودی در نظر بگیریم و صفر را حالت اولیه بگیریم، هر مرحله به این شکل است.
| # | حالت | عنصر | کاهنده | نتیجه |
|---|---|---|---|---|
| ۱ | ۰ | ۱۰ | ۰ + ۱۰ | ۱۰ |
| ۲ | ۱۰ | ۲۰ | ۱۰ + ۲۰ | ۳۰ |
| ۳ | ۳۰ | ۳۰ | ۳۰ + ۳۰ | ۶۰ |
| ۴ | ۶۰ | ۴۰ | ۶۰ + ۴۰ | ۱۰۰ |
در نحوهی نگارش jq، این به شکل کد زیر است.
0 + 10 | . + 20 | . + 30 | . + 40
میتوانیم آن را با فیلتر reduce بیان کنیم.
[10, 20, 30, 40] | reduce .[] as $n (0; . + $n) # => 100
فیلتر درونی add در واقع با reduce پیادهسازی شده است، اما null را به عنوان حالت اولیه به کار میبرد (هر نوع دادهای را میتوان به null اضافه کرد).
def add: reduce .[] as $x (null; . + $x);
در عبارت کاهش، . همان انباشتگر است.
اگر ورودی شیئی باشد که لازم است داخل تابع کاهش به آن ارجاع دهید، باید آن را در یک متغیر ذخیره کنید.
{"apple": 10, "banana": 16, "carrot": 4}
| . as $obj
| reduce (keys | .[]) as $key (0; . + $obj[$key]) # => 30
انباشتگر میتواند از هر نوع دادهای باشد. برای مثال ممکن است بخواهید ترتیب یک آرایه را برعکس کنید.
["A", "B", "C", "D"]
| reduce .[] as $elem ([]; [$elem] + .) # => ["D", "C", "B", "A"]
شما یک معلم هستید. در پایان سال، برای هر یک از دانشآموزانتان یک نمرهی عددی ثبت کردهاید. حالا باید آن را به نمرهی حرفی تبدیل کنید و بشمارید که چند دانشآموز هر نمرهی حرفی را گرفتهاند
تابع letter_grade یک نمرهی عددی را به عنوان ورودی میگیرد و حرف را به عنوان خروجی برمیگرداند.
از این بازهها استفاده کنید:
| حرف | نمره |
|---|---|
| A | ۹۰٪ - ۱۰۰٪ |
| B | ۸۰٪ - ۸۹٪ |
| C | ۷۰٪ - ۷۹٪ |
| D | ۶۰٪ - ۶۹٪ |
| F | ۰٪ - ۵۹٪ |
مثال:
75 | letter_grade # => "C"
تابع count_letter_grades یک شیء میگیرد که اسم دانشآموزان را به نمرههایشان نگاشت میکند.
خروجی یک شیء خواهد بود که هر نمرهی حرفی را به تعداد دانشآموزانی با آن نمره نگاشت میکند.
مثال:
{"Joe": 78, "Jane": 93, "Richard": 72} | count_letter_grades
# => {"A": 1, "B": 0, "C": 2, "D": 0, "F": 0}
راههای متفاوتی برای حل این مسئله وجود دارد.
برای تمرین از فیلتر reduce استفاده کنید.