۴. گزارشگیری با آرایه، Map و Set؛ از ردیف خام تا نتیجهٔ قابل اعتماد
در دورهٔ مقدماتی، آرایه را برای نگهداری چند مقدار به کار بردیم. اکنون میخواهیم از فهرست هزینهها، گزارش دستهای بسازیم، گزینههای تکراری فیلتر را حذف کنیم و نتیجه را بدون دستکاری دادهٔ اصلی مرتب کنیم. انتخاب ابزار باید تابع مسئله باشد؛ زنجیرهٔ بلند روشها لزوماً خواناتر یا سریعتر از یک حلقهٔ روشن نیست.
کدهای این درس مستقل و قابل اجرا در Node.js ۲۴ یا مرورگر جدیدند. دادهها را معتبر فرض میکنیم؛ این فرض را در تابعی که مرز دریافت فایل یا شبکه است به کار نبرید. پیش از گزارشگیری واقعی، قرارداد درس سوم باید بررسی شود. محاسبه روی مبلغ معتبر هم باید از نظر عبور مجموع از محدودهٔ عدد صحیح امن کنترل شود.
گزارش دستهای با Map
در گزارش، هر دسته یک کلید و مجموع مبلغ یک مقدار است. Map این رابطه را صریح نگه میدارد. از get برای خواندن و از set برای ثبت نتیجه استفاده میکنیم:
const rows = [
{ id: 'e1', category: 'آموزش', amount: 180000 },
{ id: 'e2', category: 'خوراک', amount: 90000 },
{ id: 'e3', category: 'آموزش', amount: 70000 }
];
function totalsByCategory(items) {
const totals = new Map();
for (const item of items) {
const next = (totals.get(item.category) ?? 0) + item.amount;
if (!Number.isSafeInteger(next)) throw new RangeError('Unsafe total');
totals.set(item.category, next);
}
return [...totals].map(([category, amount]) => ({ category, amount }));
}
console.log(totalsByCategory(rows));
// [{ category: 'آموزش', amount: 250000 },
// { category: 'خوراک', amount: 90000 }]ترتیب نتیجه فعلاً ترتیب نخستین برخورد با دستههاست؛ به معنی پرفروشترین یا گرانترین نیست. در رابط، اگر ترتیب خاصی لازم دارید، آن را جدا اعمال کنید. استفاده از ?? هم عمدی است: نبود کلید را صفر میگیریم. این عبارت قرار نیست NaN یا مبلغ خراب را درمان کند؛ معتبر بودن ردیفها پیششرط تابع است.
مرتبسازی بدون تغییر منبع
برای مرتبسازی عددی باید مقایسهگر داشته باشیم. toSorted آرایهٔ نتیجه را تازه میسازد، ولی شیءهای داخل آن همچنان ممکن است با ورودی مشترک باشند. این مثال را مستقل اجرا کنید:
const products = [
{ id: 'p1', price: 90000 },
{ id: 'p2', price: 70000 },
{ id: 'p3', price: 90000 }
];
const sorted = products.toSorted((a, b) => a.price - b.price);
console.log(sorted.map(item => item.id)); // ['p2', 'p1', 'p3']
console.log(products.map(item => item.id)); // ['p1', 'p2', 'p3']در مرتبسازی پایدار، دو ردیفی که مقایسهگر برای آنها صفر میدهد، ترتیب نسبی قبلی را حفظ میکنند؛ پس p1 پیش از p3 باقی میماند. پایداری به معنی ترتیب یکسان در همهٔ دریافتهای شبکه نیست. اگر منبع هربار ترتیب متفاوت میدهد و شما نتیجهٔ قطعی میخواهید، مقایسهٔ دوم مثلاً بر اساس شناسه را اضافه کنید. برای عنوان فارسی نیز مقایسهٔ عددی مناسب نیست و میتوان سیاست زبانی را با Intl.Collator('fa') تعیین کرد.
تکرارینبودن شناسه با Set
const ids = ['e1', 'e2', 'e1'];
const unique = [...new Set(ids)];
console.log(unique); // ['e1', 'e2']
console.log(new Set(ids).size !== ids.length); // true
console.log(new Set([{ id: 'e1' }, { id: 'e1' }]).size); // 2دو شیء جدا با فیلدهای مشابه، برای این مجموعه یک شیء واحد محسوب نمیشوند. اگر مسئلهٔ شما یکتایی شناسه است، مجموعه را از خود شناسهها بسازید. در بازیابی پشتیبان دفتر هزینه، شناسهٔ تکراری باید به خطا منجر شود، چون حذف خاموش یک ردیف ممکن است اطلاعات کاربر را از بین ببرد. در مقابل، حذف نام دستههای تکراری برای ساختن فهرست فیلتر، رفتار مناسبی است. ابزار یکی است، ولی قرارداد محصول فرق دارد.
خط لولهای که بتوان آن را توضیح داد
یک گزارش را به مراحل قابل نامگذاری تقسیم کنید: انتخاب داده، محاسبه، مرتبسازی و قالببندی. نمایش رقم فارسی را در آخر انجام دهید؛ اگر پیش از مرتبسازی مبلغها را به متن تبدیل کنید، مقایسهٔ عددی دشوار و مستعد خطا میشود. همچنین مجموع کل دفتر را با مجموع نتیجهٔ فیلتر اشتباه نگیرید. بهتر است برچسب رابط صریحاً بگوید «مجموع نتایج فیلترشده».
در دادههای کوچک آموزشی، خوانایی مهم است. برای دادهٔ بسیار بزرگ، پیش از ادعای بهبود کارایی، اندازهگیری کنید؛ چند عبور از آرایه ممکن است هزینه داشته باشد، اما بازنویسی عجولانه به یک حلقهٔ دشوار هم عیبیابی را سخت میکند. بهینهسازی باید یک مسئلهٔ مشاهدهشده را حل کند.
تمرین و پاسخ تشریحی
گزارش را توسعه دهید تا برای هر دسته، تعداد، مجموع و بیشترین هزینه را بسازد و نتیجه را با مجموع نزولی مرتب کند. فیلتر مبلغ حداقل را پیش از گزارش اعمال کنید. ورودی خالی باید آرایهٔ خالی بدهد. دادهٔ اصلی و ترتیبش نباید تغییر کند.
پاسخ راهنما: مقدار هر کلید Map را شیئی شامل count، total و max بگیرید. برای هر ردیف، مقدار قبلی یا مقدار اولیه را بخوانید و نتیجهٔ تازه ثبت کنید. پس از تبدیل به آرایه، toSorted را با مقایسهگر نزولی فراخوانی کنید. معیار تحویل شامل دستهٔ تکراری، مجموع برابر، دادهٔ خالی و ثابتماندن ورودی است. در بازبینی، توضیح دهید چرا برای گزارش معتبر، حذف خودکار شناسهٔ تکراری راهحل مناسبی نیست.
منابع رسمی برای مطالعهٔ تکمیلی
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Map
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Set
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/toSorted
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort
تألیف و مثالهای اختصاصی: تحریریه درسآوند.