সরাসরি পর্বে যান
পর্ব ১৬ · ৫৯-এর মধ্যে · মডিউল ৩

map, filter, reduce আর সাজানো — array method দিয়ে ডেটার পাইপলাইন

JavaScript Array Methods — forEach, map, filter, reduce, find, flatMap, Chaining, sort & toSorted, Grouping

Taught in Bangla, this lesson moves from loops to declarative array methods: forEach and map, filter, find, some and every, reducing a list to one value with reduce, chaining methods into a data pipeline, sort's default string comparison trap and writing comparators, toSorted, flatMap, and grouping, counting and de-duplicating without overusing reduce.

এই পর্বে যা শিখবেন

  • লুপের “কীভাবে” থেকে array method-এর “কী চাই” ভাবনায় যাওয়া, আর callback কোন তিনটি argument পায়
  • forEach, map, filter, find, some, every, flatMap — কোনটি কী ফেরত দেয়, কোনটি কখন
  • reduce কীভাবে accumulator নিয়ে ধাপে ধাপে চলে, আর কেন সবসময় initial value দেবেন
  • Method chaining দিয়ে পাঠযোগ্য ডেটা পাইপলাইন — কার্ট থেকে মোট দাম, সেরা পণ্য, জেলাভিত্তিক অর্ডার
  • sort-এর ডিফল্ট ফাঁদ, comparator, বাংলা নাম সাজানো, stability, toSorted, group করা আর গোনা

১ · লুপ থেকে বর্ণনায় — “কীভাবে” নয়, “কী চাই”

পর্ব ১৫-এ array বানানো, বদলানো আর খোঁজা দেখেছেন। এবার একটি বাস্তব কাজ ভাবুন। একটি অনলাইন মুদি দোকানের কার্টে কয়েকটি পণ্য আছে। কিছু পণ্য স্টকে নেই। আপনাকে স্টকে থাকা পণ্যগুলোর মোট দাম বের করতে হবে। পর্ব ১২-এর লুপ দিয়ে লিখলে এমন দাঁড়ায়:

let total = 0;
for (let i = 0; i < cart.length; i++) {
  if (cart[i].inStock) {
    total += cart[i].price * cart[i].qty;
  }
}

কাজ করে। কিন্তু পড়ার সময় আপনার মাথাকে অনেক কিছু সামলাতে হয়: একটি counter i, শুরুর মান, থামার শর্ত, এক এক করে বাড়ানো, একটি শর্ত, আর একটি চলমান যোগফল। আসল উদ্দেশ্য — “স্টকে থাকাগুলো রাখো, প্রতিটির দাম বের করো, সব যোগ করো” — এই যন্ত্রপাতির নিচে চাপা পড়ে যায়। একই কাজ array method দিয়ে:

const total = cart
  .filter((item) => item.inStock)            // কোনগুলো রাখব
  .map((item) => item.price * item.qty)     // প্রতিটিকে কী বানাব
  .reduce((sum, x) => sum + x, 0);          // সব মিলিয়ে একটি মান

তিনটি লাইন, তিনটি বাক্যের মতো পড়া যায়। কোনো i নেই, কোনো < বা <= নিয়ে off-by-one-এর ভয় নেই, কোনো variable পরে বদলায় না (total এখন const). লুপটা এখনো আছে — কিন্তু filter, map, reduce-এর ভেতরে, একবার ঠিকভাবে লেখা অবস্থায়। আপনি শুধু প্রতিটি ধাপের নিয়ম দিচ্ছেন, একটি ছোট function হিসেবে।

একটি garments factory-র কথা ভাবুন। সুপারভাইজার শ্রমিককে বলেন না, “প্রথম শার্টটা তোলো, দেখো, তারপর দ্বিতীয়টা…”। তিনি বলেন, “ত্রুটিওয়ালাগুলো সরাও” (filter), “প্রতিটিতে লেবেল লাগাও” (map), “মোট কয়টা হলো গুনে বলো” (reduce). কীভাবে একটার পর একটা ধরতে হবে — সেটা লাইনের শ্রমিক জানেন। সুপারভাইজার শুধু নিয়মটা দেন। Array method-এ আপনি সুপারভাইজার, method হলো লাইনের শ্রমিক, আর আপনার দেওয়া ছোট function হলো নিয়ম।

এই ধরনের লেখাকে বলে declarative (কী চাই, তা বর্ণনা করা), আর লুপের ধরনকে imperative (ধাপে ধাপে কীভাবে করতে হবে, তা নির্দেশ দেওয়া)। দুটোই বৈধ, আর লুপের এখনো জায়গা আছে (অংশ ৭-এ দেখবেন কখন)। কিন্তু তালিকা নিয়ে বেশিরভাগ দৈনন্দিন কাজ তিনটি প্রশ্নের একটি:

🔎 কোনগুলো রাখব?

filter — শর্ত মানে এমনগুলো নিয়ে নতুন array. একটিমাত্র চাইলে find; “আছে কি?” জানতে some; “সবাই কি?” জানতে every.

🔁 প্রতিটিকে কী বানাব?

map — প্রতিটি উপাদান থেকে একটি নতুন মান; ফলের array-এর দৈর্ঘ্য মূলটির সমান। একটি থেকে কয়েকটি বানাতে flatMap.

🧮 সব মিলিয়ে কী পাব?

reduce — পুরো array থেকে একটি মান: যোগফল, সবচেয়ে বড়টি, গোনার হিসাব, এমনকি একটি নতুন object.

↕️ কোন ক্রমে দেখাব?

sort / toSorted — একটি তুলনার নিয়ম (comparator) দিয়ে সাজানো। অংশ ৬-এ এর ফাঁদগুলো।

প্রতিটি method একটি callback নেয় — আর সেটি তিনটি জিনিস পায়

পর্ব ১৩-এ দেখেছেন, function একটি মান, আর যে function অন্য function নেয় সেটি higher-order function. এই পর্বের প্রতিটি method ঠিক তাই। আপনি একটি callback দেন, আর method array-এর প্রতিটি উপাদানের জন্য সেটিকে ডাকে — প্রতিবার তিনটি argument দিয়ে:

Argumentকী থাকেউদাহরণ ["ক", "খ"]-এ দ্বিতীয় কলে
১ · elementএখনকার উপাদান"খ"
২ · indexতার অবস্থান (০ থেকে শুরু)1
৩ · arrayপুরো মূল array["ক", "খ"]

বেশিরভাগ সময় আপনি শুধু প্রথমটি ব্যবহার করবেন: (item) => item.price. বাকি দুটি JavaScript-এ চুপচাপ উপেক্ষিত হয় — পর্ব ১৩-এ দেখেছেন, বাড়তি argument কোনো error দেয় না। এই নিরীহ নিয়মটিই অংশ ২-এর সবচেয়ে বিখ্যাত ফাঁদের জন্ম দেয়। আরেকটি কম ব্যবহৃত জিনিস: বেশিরভাগ method একটি দ্বিতীয় argument thisArg নেয় — arr.map(fn, obj) লিখলে fn-এর ভেতরে this হবে obj. Arrow function-এর নিজের this নেই বলে সেখানে এটি কাজই করে না; আধুনিক কোডে প্রায় দেখবেন না (this পর্ব ২৪-এ)।

MethodCallback কী ফেরত দেবেMethod কী ফেরত দেয়মূল array বদলায়?
forEachকিছু না (ফেরত দিলেও উপেক্ষিত)undefinedনিজে না
mapনতুন মানসমান দৈর্ঘ্যের নতুন arrayনা
filtertruthy / falsyনতুন array (ছোট বা সমান)না
find / findIndextruthy / falsyপ্রথম মিল / তার index (না পেলে undefined / -1)না
some / everytruthy / falsytrue বা falseনা
reduceনতুন accumulatorযেকোনো একটি মাননা
sortসংখ্যা (ঋণাত্মক / ০ / ধনাত্মক)সেই একই arrayহ্যাঁ
toSortedসংখ্যানতুন সাজানো arrayনা

২ · forEach আর map — প্রতিটির জন্য কাজ, প্রতিটিকে বদলানো

forEach — শুধু কাজ করো, কিছু ফেরত দিও না

forEach প্রতিটি উপাদানের জন্য আপনার function ডাকে — ব্যস। ফল জমা করে না, ফেরত দেয় undefined. তাই এর ব্যবহার শুধু side effect-এর জন্য: console-এ ছাপা, পাতায় কিছু বসানো, কোথাও পাঠানো। orders.forEach((o) => console.log(o.id)) ঠিক আছে।

দুটি সীমাবদ্ধতা মনে রাখুন। এক, forEach মাঝপথে থামানো যায় না। Callback-এর ভেতরে break লিখলে SyntaxError, কারণ break শুধু লুপের ভেতরে চলে, আর callback একটি আলাদা function. return লিখলে শুধু সেই একটি কল শেষ হয় — পরের উপাদানে আবার চলে। থামাতে চাইলে for…of লুপ (পর্ব ১২), অথবা find/some/every ব্যবহার করুন — এরা উত্তর পেলেই থেমে যায়। দুই, forEach async callback-এর জন্য অপেক্ষা করে না — এর গল্প পর্ব ৩৪-এ।

forEach + push = ছদ্মবেশী map. নতুন শিক্ষার্থী আর AI প্রায়ই লেখে: const prices = []; items.forEach((i) => prices.push(i.price));. কাজ করে, কিন্তু তিনটি ক্ষতি: prices-কে আগে খালি বানিয়ে পরে বদলাতে হচ্ছে (তাই const হলেও আসলে বদলায়), পাঠক পুরো লাইন পড়ে তবে বোঝেন এটি একটি রূপান্তর, আর ভুল করে দুবার চালালে দ্বিগুণ উপাদান। একই কাজ: const prices = items.map((i) => i.price);. নতুন array তৈরি করাই উদ্দেশ্য হলে map বা filter; বাইরের জগতে কিছু করাই উদ্দেশ্য হলে forEach.

map — একটি থেকে একটি

map প্রতিটি উপাদানকে আপনার callback-এ পাঠায়, আর callback যা ফেরত দেয় তা দিয়ে একটি নতুন array বানায়। নিয়ম সহজ: ৫টি ঢুকলে ৫টি বেরোবে, একই ক্রমে। মূল array অক্ষত থাকে।

  • দামের তালিকা থেকে VAT-সহ দাম: prices.map((p) => p * 1.15)
  • object থেকে একটি field তুলে আনা: students.map((s) => s.name)
  • ডেটা থেকে পাতায় দেখানোর লেখা: items.map((i) => `${i.name} — ৳${i.price}`)

Callback কিছু ফেরত না দিলে কী হয়? প্রতিটির জায়গায় undefined. সবচেয়ে সাধারণ কারণ পর্ব ১৩-এর block body: items.map((i) => { i.price * 2 }) — বাঁকা বন্ধনী দিলে return নিজে লিখতে হয়। ফল [undefined, undefined, …].

React-ধাঁচের আপডেট — id দেখে একটি উপাদান বদলানো

একটি to-do তালিকায় id ৩-এর কাজটি “শেষ” চিহ্নিত করতে হবে। পুরোনো ধরন হলো খুঁজে বের করে সরাসরি বদলে দেওয়া: todos.find((t) => t.id === 3).done = true — এটি মূল object-কে mutation করে। React-এর মতো framework বদল ধরতে পারে reference বদলালে, তাই সেখানে নিয়ম হলো: বদলাবেন না, নতুন বানান।

const updated = todos.map((t) =>
  t.id === 3 ? { ...t, done: true } : t
);

প্রতিটি to-do-র জন্য প্রশ্ন: “এটাই কি id ৩?” হ্যাঁ হলে spread দিয়ে (পর্ব ১৭) একটি নতুন object, যার done বদলানো; না হলে আগের object-টিই হুবহু। ফলে নতুন array, শুধু একটি নতুন object, বাকিগুলো একই reference — সস্তা, আর মূল todos অক্ষত। একে বলে immutable update. মুছতে চাইলে todos.filter((t) => t.id !== 3), যোগ করতে [...todos, newTodo].

বিখ্যাত ফাঁদ — ["1", "7", "11"].map(parseInt)

একটি ফর্ম থেকে সংখ্যাগুলো string হিসেবে এসেছে। কেউ চতুর হয়ে লিখলেন ["1", "7", "11"].map(parseInt) — parseInt তো একটি function, সরাসরি পাঠিয়ে দিলেই হয়! ফল আসে [1, NaN, 3]. কেন? মনে রাখুন, map callback-কে তিনটি argument দেয়, আর parseInt দ্বিতীয় argument হিসেবে নেয় radix — সংখ্যাটি কোন ভিত্তিতে লেখা (পর্ব ১০)। ধাপে ধাপে দেখুন:

1["1", "7", "11"].map(parseInt);2// map প্রতিবার ডাকে: fn(element, index, array)3// parseInt-এর চেহারা:  parseInt(string, radix)4["1", "7", "11"].map((s) => parseInt(s, 10));
প্রতিটি কলে কী গেল
আসলে ডাকা হলো
radix কী হলো
ফল
  1. দুই পক্ষের চুক্তি মেলে না। map প্রতিটি উপাদানে callback ডাকে (element, index, array) দিয়ে। parseInt নেয় (string, radix). সরাসরি পাঠালে index গিয়ে বসে radix-এর ঘরে — কেউ সতর্ক করে না।
  2. প্রথম কল: parseInt("1", 0). Radix ০ মানে “নির্দিষ্ট করা হয়নি” — তখন ১০ ধরা হয় (string 0x দিয়ে শুরু হলে ১৬)। ফল 1 — ঠিক, তবে ভাগ্যক্রমে।
  3. দ্বিতীয় কল: parseInt("7", 1). বৈধ radix ২ থেকে ৩৬; ১ এর বাইরে। তাই ফল NaN. (Radix ২ হলেও "7" ভুল হতো — binary-তে শুধু ০ আর ১।)
  4. তৃতীয় কল: parseInt("11", 2). Binary-তে 11 মানে ১×২ + ১ = ৩. ফল 3. পুরো ফল [1, NaN, 3] — কোনো error ছাড়াই ভুল ডেটা।
  5. সমাধান: নিজে argument ঠিক করুন। map((s) => parseInt(s, 10)) — একটি ছোট arrow function মাঝখানে বসে শুধু যা দরকার তাই পাঠায়। অথবা map(Number): Number একটিই argument নেয়, বাকিগুলো উপেক্ষা করে, তাই নিরাপদ (তবে Number("12px") হয় NaN, যেখানে parseInt দেয় ১২)।
মূল নিয়ম

নিজের লেখা নয় এমন function (parseInt, লাইব্রেরির function) সরাসরি callback হিসেবে পাঠাবেন না, যদি না নিশ্চিত হন সেটি শুধু প্রথম argument দেখে। মাঝখানে একটি ছোট arrow বসান: (x) => f(x). এতে ভবিষ্যতে লাইব্রেরি নতুন ঐচ্ছিক parameter যোগ করলেও আপনার কোড ভাঙবে না।

৩ · filter, find, some, every — বাছাই আর প্রশ্ন

filter-এর callback একটি প্রশ্নের উত্তর দেয়: “এটি কি রাখব?” এমন হ্যাঁ/না উত্তর দেওয়া function-কে বলে predicate. Callback যা ফেরত দেয় তা truthy হলে উপাদানটি নতুন array-তে যায়, falsy হলে বাদ। খেয়াল করুন: উপাদানটিই যায়, callback-এর ফল নয় — filter কিছু বদলায় না, শুধু বাছে।

  • স্টকে থাকা পণ্য: products.filter((p) => p.stock > 0)
  • ঢাকার অর্ডার: orders.filter((o) => o.district === "ঢাকা")
  • ফাঁকা নাম বাদ: names.filter((n) => n.trim() !== "")
  • falsy মান সব বাদ: values.filter(Boolean) — এখানে Boolean নিজেই predicate; তবে এতে 0-ও বাদ পড়ে, যা হয়তো বৈধ ডেটা ছিল (পর্ব ০৮)।

একটিই চাই, বা শুধু হ্যাঁ/না — find, findIndex, some, every

পর্ব ১৫-এ এগুলোর পরিচয় পেয়েছেন। এখানে predicate-এর চোখে আরেকবার দেখুন, কারণ এরা filter-এর “তাড়াতাড়ি থামা” আত্মীয়:

প্রশ্নMethodকখন থামেখালি array-এ
শর্ত মানে এমন সবগুলো দাওfilterকখনো না — সব দেখে[]
প্রথম যেটি মানে, সেটি দাওfind (শেষ থেকে findLast, ES2023)প্রথম মিলেইundefined
তার অবস্থান দাওfindIndexপ্রথম মিলেই-1
অন্তত একটি কি মানে?someপ্রথম truthy-তেfalse
সবাই কি মানে?everyপ্রথম falsy-তেtrue

শেষ কলামটি পরীক্ষায় প্রায়ই আসে: [].every(fn) কেন true? গণিতের যুক্তিতে, “সবাই শর্ত মানে” মিথ্যা হতে হলে অন্তত একজন না-মানা সদস্য লাগবে — খালি দলে কেউ নেই, তাই কথাটি সত্য। একে বলে vacuous truth. বাস্তবে এর মানে: “কার্টের সব পণ্য কি স্টকে আছে?” — খালি কার্টে উত্তর true, তাই চেকআউট বোতাম চালু করার আগে cart.length > 0-ও যাচাই করুন।

some আর every পর্ব ০৮-এর short-circuit-এর মতো আচরণ করে — উত্তর জানা হয়ে গেলেই থামে। ১০ লাখ অর্ডারের প্রথমটিতেই বাতিল অর্ডার পেলে orders.some((o) => o.cancelled) বাকি ৯,৯৯,৯৯৯টি দেখেই না। filter(…).length > 0 লিখলে সবগুলো দেখে একটি অপ্রয়োজনীয় array-ও বানায় — একই উত্তর, অনেক বেশি কাজ।

flatMap — একটি থেকে কয়েকটি (বা শূন্য)

map সবসময় একটির বদলে একটি দেয়। কিন্তু ধরুন প্রতিটি অর্ডারে কয়েকটি পণ্য, আর আপনি সব অর্ডারের সব পণ্যের একটি সমতল তালিকা চান। orders.map((o) => o.items) দেবে array-এর array: [["চাল", "ডাল"], ["তেল"]]. flatMap (ES2019) একই কাজ করে, তারপর এক স্তর সমতল করে: orders.flatMap((o) => o.items) → ["চাল", "ডাল", "তেল"].

একটি চতুর ব্যবহার: callback থেকে [] ফেরত দিলে উপাদানটি বাদ পড়ে, [x] দিলে থাকে — তাই flatMap একসাথে filter আর map দুটোই করতে পারে। যেমন বাক্যগুলোকে শব্দে ভাঙা: sentences.flatMap((s) => s.split(" ")).

৪ · reduce — পুরো তালিকাকে একটি মানে নামিয়ে আনা

map আর filter array থেকে array দেয়। কিন্তু অনেক সময় আপনি চান একটিমাত্র উত্তর: মোট দাম, সবচেয়ে দামি পণ্য, প্রতিটি শব্দ কতবার এসেছে তার হিসাব। এখানে আসে reduce. পর্ব ১২-এ লুপের accumulator দেখেছেন — একটি শুরু-মান, প্রতিটি ঘোরায় তাতে কিছু যোগ। reduce ঠিক সেই নকশাকে একটি method বানিয়েছে:

array.reduce((acc, element, index, array) => newAcc, initialValue);

Callback পায় এখন পর্যন্ত জমা হওয়া ফল (acc) আর এখনকার উপাদান; তাকে ফেরত দিতে হবে নতুন জমা ফল। পরের কলে সেই ফলই acc হয়ে আসে। শেষ কলের ফেরত মানই reduce-এর উত্তর। তিনটি দামের যোগফল ধাপে ধাপে দেখুন:

1const prices = [120, 80, 45];2const total = prices.reduce(3  (sum, p) => sum + p,4  05);
প্রতিটি কলে accumulator
কল
sum (acc)
p (index)
ফেরত = নতুন sum
  1. শুরুর মান। দ্বিতীয় argument 0 হলো initial value. কোনো callback চলার আগেই accumulator sum-এর মান ০ — যেমন লুপের আগে let total = 0.
  2. ১ম কল। sum = 0, p = 120. Callback ফেরত দিল 0 + 120 = 120. এই ফেরত মানটিই পরের কলের sum — reduce নিজে কিছু যোগ করে না, শুধু ফলটা হাতে হাতে এগিয়ে দেয়।
  3. ২য় কল। sum = 120, p = 80 → 200.
  4. ৩য় কল। sum = 200, p = 45 → 245. এটিই শেষ উপাদান।
  5. শেষ ফেরত মানই উত্তর। total = 245. তিনটি উপাদান, তিনটি কল, একটি মান।
  6. Initial value না দিলে? তখন প্রথম উপাদান (১২০) নিজেই শুরুর accumulator হয়, আর কল শুরু হয় index ১ থেকে — দুটি কলে একই ২৪৫. সংখ্যার যোগে ফল এক, কিন্তু array খালি হলে শুরুর মান কোথাও নেই — তখন TypeError. পরের অংশে কারণসহ।
acc₀ = initial value → acc₁ = f(acc₀, x₀) → acc₂ = f(acc₁, x₁) → … → শেষ acc = উত্তর

কেন সবসময় initial value দেবেন

Initial value ঐচ্ছিক, কিন্তু না দেওয়া প্রায় সবসময় ভুল। তিনটি কারণ:

  1. খালি array-এ program ভাঙে। [].reduce((a, b) => a + b) চালালে Chrome, Edge আর Node.js-এ (V8) বার্তা আসে TypeError: Reduce of empty array with no initial value. আজকের অর্ডার তালিকা খালি হতেই পারে — আর ঠিক সেদিন রিপোর্ট পাতা ভেঙে যাবে। Initial value দিলে [].reduce((a, b) => a + b, 0) শান্তভাবে 0 দেয়।
  2. Object-এর array-এ ভুল ফল। cart.reduce((sum, item) => sum + item.price) — প্রথম কলে sum হলো পুরো প্রথম object, সংখ্যা নয়! Object + সংখ্যা মানে string জোড়া: ফল আসে "[object Object]80"-এর মতো অর্থহীন কিছু (পর্ব ০৬-এর coercion).
  3. একটি উপাদানে callback চলেই না। Initial value ছাড়া এক উপাদানের array-এ reduce callback না ডেকেই সেই উপাদানটি ফেরত দেয় — কোনো রূপান্তর হয় না। ফলের type বদলে যেতে পারে।

Initial value accumulator-এর type-ও ঠিক করে দেয়: যোগফলে 0, গুণফলে 1, তালিকা বানাতে [], হিসাবের খাতা বানাতে {}. reduceRight একই কাজ করে শেষ উপাদান থেকে শুরু করে — যোগে ক্রম কোনো পার্থক্য করে না, কিন্তু string জোড়া বা function একের পর এক চালানোর (composition, পর্ব ২৩) সময় ক্রম গুরুত্বপূর্ণ।

reduce দিয়ে গোনা — একটি বাংলা বাক্যে শব্দের সংখ্যা

reduce-এর accumulator সংখ্যাই হতে হবে, এমন নয়। একটি object-কে খাতা বানিয়ে প্রতিটি শব্দ কতবার এল, গোনা যায়:

const words = "আমি ভাত খাই আমি মাছ খাই আমি".split(" ");
const freq = words.reduce((acc, w) => {
  acc[w] = (acc[w] ?? 0) + 1;   // আগে না থাকলে 0 ধরে 1 যোগ
  return acc;                   // return ভুললে পরের কলে acc = undefined
}, {});
// { 'আমি': 3, 'ভাত': 1, 'খাই': 2, 'মাছ': 1 }

শুরুতে খাতা খালি {}. প্রতিটি শব্দে, খাতায় সেই নামের পাতা থাকলে তার সংখ্যা বাড়াও, না থাকলে ?? (পর্ব ০৮) দিয়ে ০ ধরে ১ লেখো। Block body ব্যবহার করায় return acc নিজে লিখতে হচ্ছে — এটি ভুলে যাওয়া reduce-এর সবচেয়ে সাধারণ বাগ। খেয়াল করুন, এখানে accumulator object-টি callback-এর ভেতরে বদলানো হচ্ছে; বাইরের কোনো ডেটা নয়, reduce নিজেই এটি বানিয়েছে, তাই এটি নিরাপদ। প্রতিবার { ...acc, [w]: … } দিয়ে নতুন object বানালে বড় তালিকায় অকারণে ধীর হয়।

৫ · Chaining — ডেটার পাইপলাইন

map আর filter নতুন array ফেরত দেয়, আর array-এর ওপর আবার method ডাকা যায়। তাই একটির পর একটি জুড়ে দেওয়া যায় — একে বলে method chaining. প্রতিটি ধাপ আগের ধাপের ফল নেয়, ঠিক কারখানার লাইনের মতো। পুরো জিনিসটিকে বলে একটি data pipeline. একটি মুদি দোকানের অর্ডার তালিকা কীভাবে ধাপে ধাপে একটি মোট আয়ে পরিণত হয়, দেখুন:

  1. orders৫টি অর্ডারমূল ডেটা
  2. filterinStock৪টি থাকল
  3. mapprice × qty৪টি সংখ্যা
  4. reducesum + x১টি মান
orders৫টি object — কিছুই বদলায়নি
{ item: "চাল", price: 120, qty: 2, inStock: true  }
{ item: "ডাল", price: 80,  qty: 1, inStock: false }
{ item: "তেল", price: 45,  qty: 3, inStock: true  }
{ item: "চিনি", price: 140, qty: 1, inStock: true  }
{ item: "আটা", price: 60,  qty: 4, inStock: true  }
.filter((o) => o.inStock)নতুন array, ৪টি — ডাল বাদ
{ item: "চাল", price: 120, qty: 2, ... }
{ item: "তেল", price: 45,  qty: 3, ... }
{ item: "চিনি", price: 140, qty: 1, ... }
{ item: "আটা", price: 60,  qty: 4, ... }

(একই object-গুলো — কপি নয়, reference)
.map((o) => o.price * o.qty)নতুন array, ৪টি সংখ্যা
[ 240, 135, 140, 240 ]

চাল 120×2 = 240
তেল  45×3 = 135
চিনি 140×1 = 140
আটা  60×4 = 240
.reduce((sum, x) => sum + x, 0)একটি মান
0 → 240 → 375 → 515 → 755

মোট আয়: 755 টাকা
orders এখনো ৫টি, অক্ষত
  1. শুরুর ডেটা। পাঁচটি অর্ডার, প্রতিটিতে পণ্য, দাম, পরিমাণ আর স্টকে আছে কি না। পাইপলাইন শেষে এই array-টি একটুও বদলাবে না।
  2. filter — বাছাই। Predicate o.inStock. ডাল false, তাই বাদ; একটি নতুন ৪-উপাদানের array তৈরি হলো। ভেতরের object-গুলো নতুন নয় — মূল object-গুলোরই reference (পর্ব ১৪)।
  3. map — রূপান্তর। প্রতিটি object থেকে একটি সংখ্যা: দাম × পরিমাণ। আবার একটি নতুন array — ৪টি ঢুকল, ৪টি বেরোল।
  4. reduce — এক মানে। ০ থেকে শুরু করে প্রতিটি সংখ্যা যোগ: ২৪০, ৩৭৫, ৫১৫, ৭৫৫. মাঝের দুটি array-র আর দরকার নেই — garbage collector সেগুলো পরে সরিয়ে দেবে (পর্ব ০৫)।

পাইপলাইনের সৌন্দর্য হলো প্রতিটি ধাপ আলাদাভাবে পরীক্ষা করা যায়। কোনো সন্দেহ হলে মাঝখানে একটি লাইন কেটে console.log করুন — মাঝের array-টি দেখতে পাবেন। আর ধাপের ক্রম গুরুত্বপূর্ণ: আগে filter, তারপর map — এতে বাদ পড়া উপাদানের জন্য অকারণে হিসাব হয় না। এবার নিজে চালান — মোট আয়, সবচেয়ে বেশি বিক্রির ৩টি পণ্য, আর অংশ ২–৪-এর তিনটি ফাঁদ:

const orders = [
  { item: "চাল", price: 120, qty: 2, inStock: true },
  { item: "ডাল", price: 80, qty: 1, inStock: false },
  { item: "তেল", price: 45, qty: 3, inStock: true },
  { item: "চিনি", price: 140, qty: 1, inStock: true },
  { item: "আটা", price: 60, qty: 4, inStock: true },
];
const lines = orders
  .filter((o) => o.inStock)
  .map((o) => ({ item: o.item, total: o.price * o.qty }));
const revenue = lines.reduce((sum, l) => sum + l.total, 0);
const top3 = lines.toSorted((a, b) => b.total - a.total)
  .slice(0, 3).map((l) => l.item);
console.log(revenue, top3, orders.length);

console.log(["1", "7", "11"].map(parseInt), ["1", "7", "11"].map(Number));
const doubled = [];
console.log([1, 2].forEach((x) => doubled.push(x * 2)), doubled);
try { [].reduce((a, b) => a + b); }
catch (e) { console.log(e.name + ": " + e.message); }

প্রথম লাইনে 755 [ 'চাল', 'আটা', 'চিনি' ] 5. চাল আর আটা দুটোরই বিক্রি ২৪০ — সমান। তবু চাল আগে, কারণ মূল তালিকায় চাল আগে ছিল, আর আধুনিক sort/toSorted সমান উপাদানের ক্রম রাখে (stable sort, অংশ ৬)। orders.length এখনো ৫ — কোনো ধাপ মূল ডেটা ছোঁয়নি। দ্বিতীয় লাইনে [ 1, NaN, 3 ] [ 1, 7, 11 ] — parseInt-এর ফাঁদ আর Number-এর নিরাপদ বিকল্প। তৃতীয় লাইনে undefined [ 2, 4 ] — forEach নিজে কিছু ফেরত দেয় না। শেষ লাইনে TypeError: Reduce of empty array with no initial value — এটি try/catch (পর্ব ২৯) দিয়ে ধরে ছাপা হয়েছে, তাই প্রোগ্রাম ভাঙেনি।

লেবেলের উদাহরণ — বাছাই করা পণ্যে ১০% ছাড়

একটি দোকানের ঈদ অফার: শুধু “মুদি” ক্যাটাগরির পণ্যে ১০% ছাড়, আর প্রতিটির জন্য একটি দামের লেবেল চাই। সব পণ্যই তালিকায় থাকবে, শুধু কিছুর দাম বদলাবে — তাই এটি filter নয়, map-এর ভেতরে একটি শর্ত:

const labels = products.map((p) => {
  const price = p.category === "মুদি" ? Math.round(p.price * 0.9) : p.price;
  return `${p.name}: ৳${price}`;
});
// [ 'চাল: ৳108', 'সাবান: ৳50', 'ডাল: ৳72' ]  (চাল 120, সাবান 50, ডাল 80 হলে)

শুধু ছাড় পাওয়া পণ্যের লেবেল চাইলে আগে filter, তারপর map. সব পণ্যের লেবেল চাইলে শুধু map, ভেতরে ternary (পর্ব ১১)। এই ছোট সিদ্ধান্ত — “বাদ দেব, নাকি বদলাব?” — প্রতিটি পাইপলাইনের প্রথম প্রশ্ন। টাকার হিসাবে দশমিক নিয়ে সতর্ক থাকুন: Math.round এখানে পূর্ণ টাকায় নিচ্ছে; floating-point-এর সমস্যা পর্ব ১০-এ দেখেছেন।

৬ · সাজানো — sort-এর ডিফল্ট ফাঁদ আর comparator

পর্ব ১৩-এ একঝলক দেখেছেন: [10, 9, 100].sort() দেয় [10, 100, 9]. এটা কোনো বাগ নয় — এটাই নিয়ম। comparator না দিলে sort প্রতিটি উপাদানকে string বানিয়ে অভিধানের মতো অক্ষর ধরে ধরে তুলনা করে — একে বলে lexicographic order. তারপর comparator দিলে কী হয়, আর sort কীভাবে মূল array বদলে দেয় — ধাপে ধাপে দেখুন:

1const nums = [10, 9, 100];2nums.sort();3nums.sort((a, b) => a - b);4const desc = nums.toSorted((a, b) => b - a);
তুলনা আর array-এর অবস্থা
কীসের তুলনা
উদাহরণ
nums
desc
  1. তিনটি সংখ্যা। মানুষের চোখে স্পষ্ট ক্রম ৯, ১০, ১০০. দেখা যাক sort কী ভাবে।
  2. Comparator নেই → সব string. মানদণ্ড বলে: comparator না দিলে প্রতিটি মানকে string বানিয়ে তুলনা করো। এখন সামনে "10", "9", "100" — আর সংখ্যার মান কোনো ভূমিকাই রাখে না।
  3. অভিধানের নিয়ম। প্রথম অক্ষর দেখো: "1" আগে, "9" পরে — তাই "10" আর "100" দুটোই "9"-এর আগে। "10" আর "100"-এর প্রথম দুই অক্ষর এক, "10" ছোট বলে আগে — ঠিক যেমন অভিধানে “কল” আসে “কলম”-এর আগে। ফল [10, 100, 9], আর sort এটি মূল array-তেই লিখে দিয়েছে।
  4. Comparator দিলে। sort দুটি উপাদান a, b দিয়ে আপনার function ডাকে। ফল ঋণাত্মক হলে a আগে, ধনাত্মক হলে b আগে, ০ হলে যেমন ছিল তেমন। a - b ঠিক এটাই দেয়: ছোট সংখ্যা আগে। এবার [9, 10, 100] — আবারও মূল array বদলে।
  5. toSorted — মূলটি অক্ষত। ES2023-এর toSorted একই comparator নেয়, কিন্তু একটি নতুন সাজানো array ফেরত দেয়। b - a দিলে বড় থেকে ছোট: [100, 10, 9]. nums যেমন ছিল তেমনই। কোন তুলনা কোন ক্রমে হয়, তা engine-ভেদে আলাদা — আপনার comparator-কে শুধু সঠিক চিহ্ন দিতে হবে।
ঋণাত্মক → a আগে · ধনাত্মক → b আগে · ০ → ক্রম অপরিবর্তিত
ছোট থেকে বড়: (a, b) => a - b · বড় থেকে ছোট: (a, b) => b - a

বাংলা নাম সাজানো — localeCompare

String-এর ক্ষেত্রে ডিফল্ট sort তুলনা করে UTF-16 code unit (পর্ব ০৯) দিয়ে। ইংরেজিতে এর ফল অদ্ভুত: সব বড় হাতের অক্ষর ছোট হাতের আগে, তাই "Zara" আসে "apple"-এর আগে। বাংলায় কোড পয়েন্টের ক্রম মোটামুটি বর্ণমালার মতো, তবে যুক্তাক্ষর, নুকতা আর ভিন্নভাবে লেখা একই অক্ষরে (যেমন “য়” দুই রকমভাবে লেখা যায়) গোলমাল হতে পারে। ভাষার নিয়ম মেনে সাজাতে localeCompare ব্যবহার করুন:

const fruits = ["আম", "কলা", "আনারস", "লিচু", "জাম"];
fruits.toSorted((a, b) => a.localeCompare(b, "bn"));
// [ 'আনারস', 'আম', 'কলা', 'জাম', 'লিচু' ]

a.localeCompare(b, "bn") বাংলা ভাষার নিয়মে তুলনা করে ঋণাত্মক, ০ বা ধনাত্মক সংখ্যা দেয় — ঠিক comparator-এর যা দরকার। “আনারস” “আম”-এর আগে, কারণ দ্বিতীয় অক্ষরে ন বর্ণমালায় ম-এর আগে। অনেক নাম বারবার সাজাতে হলে const coll = new Intl.Collator("bn"); একবার বানিয়ে coll.compare পাঠানো দ্রুততর (Intl পর্ব ২০-এ)।

Stability — সমান হলে আগের ক্রম থাকে কি?

একটি stable sort সমান উপাদানগুলোর আগের আপেক্ষিক ক্রম বদলায় না। অংশ ৫-এর কোডে চাল আর আটার বিক্রি সমান ছিল, আর চাল আগেই থাকল — stability-র কারণে। ES2019 থেকে মানদণ্ড sort-কে stable হতে বাধ্য করেছে; এর আগে কিছু engine-এ বড় array-তে সমান উপাদানের ক্রম এলোমেলো হতে পারত (ইতিহাস ভাবনার প্রশ্ন ৪-এ)। Stability-র বাস্তব সুবিধা: আগে নাম দিয়ে সাজিয়ে তারপর GPA দিয়ে সাজালে, একই GPA-র মধ্যে নামের ক্রম রয়ে যায়।

sort মূল array বদলায় — toSorted দেয় নতুন

sort একটি in-place অপারেশন: মূল array-টিই সাজিয়ে দেয়, আর সেই একই array ফেরত দেয়। ফলে const ranked = students.sort(…) লিখলে ranked আর students একই array — যে কোডটি মূল ক্রমের ওপর নির্ভর করছিল (যেমন রোল নম্বরের তালিকা), সেটি চুপচাপ ভেঙে যায়। পুরোনো সমাধান ছিল আগে কপি করা: [...students].sort(…). ES2023 থেকে সরাসরি students.toSorted(…). একই পরিবারে আরও তিনটি এসেছে: toReversed() (reverse-এর বদলে), toSpliced() (splice-এর বদলে), আর with(index, value) (arr[i] = v-এর বদলে)।

একাধিক key — GPA বেশি আগে, সমান হলে নাম অনুযায়ী

মেধাতালিকা বানাতে প্রথমে GPA বড় থেকে ছোট, আর GPA সমান হলে নামের বর্ণানুক্রমে। Comparator-এ || ব্যবহার করুন: প্রথম তুলনা ০ (সমান) হলে falsy, তখন দ্বিতীয় তুলনা চলে।

const students = [
  { name: "রহিম", gpa: 4.5 }, { name: "আয়েশা", gpa: 5.0 },
  { name: "করিম", gpa: 4.5 }, { name: "তানিয়া", gpa: 5.0 },
  { name: "নাসির", gpa: 4.0 }];
const ranked = students.toSorted(
  (a, b) => b.gpa - a.gpa || a.name.localeCompare(b.name, "bn")
);
console.log(ranked.map((s) => `${s.name} ${s.gpa}`).join(", "));
console.log(students[0].name, [10, 9, 100].toSorted());

const orders = [{ id: 1, district: "ঢাকা" }, { id: 2, district: "খুলনা" },
  { id: 3, district: "ঢাকা" }, { id: 4, district: "সিলেট" }];
const byDistrict = Object.groupBy(orders, (o) => o.district);
console.log(Object.keys(byDistrict), byDistrict["ঢাকা"].length);

const counts = orders.reduce((acc, o) => {
  acc[o.district] = (acc[o.district] ?? 0) + 1;
  return acc;
}, {});
console.log(JSON.stringify(counts));
console.log([...new Set(orders.map((o) => o.district))]);

প্রথম লাইনে আয়েশা 5, তানিয়া 5, করিম 4.5, রহিম 4.5, নাসির 4 — ৫ পাওয়া দুজন আগে, তাদের মধ্যে বর্ণানুক্রমে; তারপর ৪.৫-এর দুজন। b.gpa - a.gpa ০ হলেই ||-এর ডান পাশ চলেছে। দ্বিতীয় লাইনে রহিম [ 10, 100, 9 ] — toSorted মূল তালিকা ছোঁয়নি, আর comparator ছাড়া সংখ্যা আবারও string-এর মতো সাজল। তৃতীয় লাইনে [ 'ঢাকা', 'খুলনা', 'সিলেট' ] 2 — Object.groupBy প্রতিটি জেলার নামে একটি তালিকা বানিয়েছে। চতুর্থ লাইনে {"ঢাকা":2,"খুলনা":1,"সিলেট":1} — reduce দিয়ে গোনা। শেষ লাইনে [ 'ঢাকা', 'খুলনা', 'সিলেট' ] — Set দিয়ে পুনরাবৃত্তি বাদ। পরের অংশে এগুলোর ব্যাখ্যা।

Comparator-এর তিনটি ভুল। (১) (a, b) => a > b — boolean ফেরত দেয়, ঋণাত্মক কখনো নয়; কিছু ক্ষেত্রে কাজ করার মতো দেখায়, কিন্তু ফল engine আর ইনপুটভেদে ভুল। (২) String-এ a - b — "রহিম" - "করিম" হলো NaN, ক্রম অনির্ধারিত। (৩) এলোমেলো করতে () => Math.random() - 0.5 — comparator-কে একই জোড়ার জন্য সবসময় একই উত্তর দিতে হয়; এটি সমানভাবে এলোমেলো করে না। ঠিকভাবে এলোমেলো করতে Fisher–Yates পদ্ধতি লাগে।

৭ · Group, গোনা, unique — আর কখন reduce নয়

জেলাভিত্তিক অর্ডার — Object.groupBy আর reduce

একটি e-commerce সাইটে ডেলিভারি দল জেলা ধরে অর্ডার ভাগ করে। Object.groupBy (ES2024) ঠিক এই কাজের জন্য: একটি array আর একটি callback নেয়, callback প্রতিটি উপাদানের জন্য একটি key ফেরত দেয়, আর ফল একটি object যার প্রতিটি key-তে সেই দলের উপাদানগুলোর array. ফলের object-এর prototype নেই (null prototype), তাই "toString" নামের জেলাও নিরাপদ। একই পরিবারে Map.groupBy আছে, যা Map (পর্ব ১৮) ফেরত দেয় — key string না হলে সেটি ব্যবহার করুন।

তবে এটি নতুন: Chrome আর Edge-এ ২০২৩ সাল থেকে, Node.js-এ ভার্সন 21 থেকে। Node 20 বা পুরোনো ব্রাউজারে চালাতে হলে reduce-ভিত্তিক বিকল্প লিখুন — সব জায়গায় চলে:

const byDistrict = orders.reduce((acc, o) => {
  (acc[o.district] ??= []).push(o);   // দল না থাকলে বানাও, তারপর যোগ করো
  return acc;
}, {});

??= (পর্ব ০৮-এর logical assignment) বলে: “বাঁ পাশ null বা undefined হলে ডান পাশের মান বসাও।” প্রথমবার ঢাকা এলে acc["ঢাকা"] নেই, তাই খালি array বসে, তারপর তাতে অর্ডারটি push হয়। পরের ঢাকার অর্ডার সরাসরি সেই array-তে যায়। গোনা (frequency) আর group করা একই নকশা — শুধু খাতার প্রতিটি পাতায় সংখ্যার বদলে তালিকা।

Unique — Set দিয়ে পুনরাবৃত্তি বাদ

কোন কোন জেলা থেকে অর্ডার এসেছে, প্রতিটি একবার করে? [...new Set(arr)] — একটি Set একই মান দ্বিতীয়বার রাখে না, আর spread তাকে আবার array বানায়। প্রথমবার দেখা ক্রম বজায় থাকে। Set কীভাবে কাজ করে, কেন এটি filter + indexOf-এর চেয়ে অনেক দ্রুত, আর object-এর ক্ষেত্রে কেন কাজ করে না (object-এর তুলনা হয় reference দিয়ে) — পুরোটা পর্ব ১৮-এ।

পারফরম্যান্স আর পাঠযোগ্যতা — আগে পাঠযোগ্যতা

একটি যুক্তিসংগত প্রশ্ন: filter → map → reduce মানে তিনবার ঘোরা আর দুটি মাঝের array. একটিমাত্র reduce বা for লুপে সব করলে কি দ্রুত হয় না? হ্যাঁ, সামান্য। কিন্তু তিনটি কথা মনে রাখুন:

  • বেশিরভাগ তালিকা ছোট। একটি কার্টে ২০টি পণ্য, একটি পাতায় ১০০টি অর্ডার — এখানে পার্থক্য মাইক্রোসেকেন্ডের। আধুনিক engine (পর্ব ০৫-এর JIT) এই ছোট callback-গুলো খুব ভালোভাবে অপটিমাইজ করে।
  • পাঠযোগ্যতার দাম বেশি। কোড লেখা হয় একবার, পড়া হয় শতবার। একটি বাগ খুঁজতে আধঘণ্টা বাঁচানো কয়েক মাইক্রোসেকেন্ডের চেয়ে অনেক বেশি মূল্যবান।
  • মেপে তারপর বদলান। লাখো উপাদান, প্রতি সেকেন্ডে বহুবার — এমন জায়গায় DevTools-এর profiler (পর্ব ৫৩) দিয়ে মেপে দেখুন সত্যিই এখানে সময় যাচ্ছে কি না। তখন একটি সাধারণ for লুপ সম্পূর্ণ গ্রহণযোগ্য।

আর উল্টো দিকের ভুলটিও আছে: সবকিছু reduce দিয়ে করা। reduce এত শক্তিশালী যে map, filter, find — সবই এটি দিয়ে লেখা যায়। কিন্তু arr.reduce((acc, x) => x > 0 ? [...acc, x * 2] : acc, []) পড়তে যত সময় লাগে, arr.filter((x) => x > 0).map((x) => x * 2) পড়তে তার অর্ধেকও লাগে না — আর প্রথমটি প্রতিবার নতুন array কপি করে বলে বড় তালিকায় আসলে অনেক বেশি ধীর। নিয়ম: যে method-এর নাম আপনার উদ্দেশ্য বলে, সেটি ব্যবহার করুন। reduce রাখুন যখন সত্যিই অনেক থেকে একটি মান বানাচ্ছেন — যোগফল, গোনা, group, সবচেয়ে বড়টি।

C/C++/Python/Java জানলে

Java-র Stream API (Java-র stream) দেখতে একই রকম, কিন্তু lazy — collect-এর মতো terminal operation না আসা পর্যন্ত কিছুই চলে না; JavaScript-এর array method eager, প্রতিটি ধাপ সাথে সাথে একটি পুরো নতুন array বানায়। Python-এ একই কাজ সাধারণত list comprehension দিয়ে হয় (Python-এর comprehension), আর sorted()-ও stable. C++-এ std::transform, std::accumulate, std::sort (C++-এর STL algorithm) — তবে std::sort stable নয়, তার জন্য std::stable_sort. আর C++/Java-র comparator-এর মতো এখানেও ঋণাত্মক/০/ধনাত্মক নিয়ম, কিন্তু ডিফল্ট sort যে string-এ সাজায়, এমন ফাঁদ ওদের কোনোটিতে নেই।

পরীক্ষা ও ইন্টারভিউতে
  • map আর forEach-এর পার্থক্য? — map callback-এর ফল দিয়ে সমান দৈর্ঘ্যের নতুন array দেয়; forEach শুধু side effect-এর জন্য, ফেরত দেয় undefined.
  • ["1", "7", "11"].map(parseInt) কী দেয়, কেন? — [1, NaN, 3]; map index পাঠায়, parseInt সেটিকে radix ধরে (০ → ১০, ১ → অবৈধ, ২ → binary).
  • reduce-এ initial value কেন দেবেন? — খালি array-এ TypeError এড়াতে, আর accumulator-এর type ঠিক রাখতে (object-এর array-এ বিশেষ জরুরি)।
  • [10, 9, 100].sort() কী দেয়? — [10, 100, 9]; comparator ছাড়া string হিসেবে সাজায়। সংখ্যায় (a, b) => a - b.
  • sort আর toSorted-এর পার্থক্য? — sort মূল array বদলায় (in-place); toSorted (ES2023) নতুন array দেয়, মূলটি অক্ষত।

৮ · AI-এর যুগে — AI-এর লেখা array কোড যাচাই করা

“অর্ডার তালিকা থেকে জেলাভিত্তিক মোট আয় বের করো” — AI-কে বললে কয়েক সেকেন্ডে কোড পাবেন। প্রায়ই তা চলবেও। কিন্তু array নিয়ে AI-এর কয়েকটি নির্দিষ্ট অভ্যাস আছে, যা ছোট টেস্ট ডেটায় ধরা পড়ে না, পড়ে বাস্তব ডেটায়:

AI-এর ভুলকেন সমস্যাকী চাইবেন
খালি array বানিয়ে forEach + pushউদ্দেশ্য লুকিয়ে থাকে, variable বদলায়, দুবার চললে দ্বিগুণ ডেটাmap / filter
সবকিছু একটি বিশাল reduce-এপড়া কঠিন; [...acc, x] প্রতিবার কপি করে ধীরনাম দেখে বোঝা যায় এমন method-এর chain
সংখ্যায় comparator ছাড়া sort()১০০ আসে ৯-এর আগে; ছোট টেস্টে (একক অঙ্ক) ধরা পড়ে না(a, b) => a - b
Function-এর ভেতরে পাওয়া array-তে sort / reverseকলকারীর ডেটা গোপনে বদলায়toSorted / toReversed
.map(parseInt)index radix হয়ে যায় — [1, NaN, 3].map(Number) বা (s) => parseInt(s, 10)
Initial value ছাড়া reduceখালি তালিকায় TypeError, object-এ string জোড়াসবসময় initial value
সব পরিবেশে Object.groupByNode 20-এ নেই — সার্ভারে ভেঙে যায়পরিবেশ যাচাই, বা reduce-ভিত্তিক বিকল্প

AI-কে array-এর কাজ দেওয়ার সময় পরিবেশ, অপরিবর্তনীয়তা আর কঠিন টেস্ট — তিনটিই আগে থেকে বলে দিন:

একটি JavaScript function লিখে দাও: অর্ডারের array থেকে জেলাভিত্তিক মোট আয় আর সবচেয়ে বেশি বিক্রির ৩টি পণ্য বের করবে। প্রতিটি অর্ডার: { id, district, item, price, qty, status }। নিয়ম: ১. শুধু status "delivered" অর্ডার গুনবে। ২. map, filter, reduce, toSorted ব্যবহার করো; forEach + push দিয়ে নতুন array বানাবে না। reduce শুধু যেখানে অনেক থেকে একটি মান বানাতে হয়। ৩. পাওয়া array বা object কোনোভাবেই বদলাবে না — sort, reverse, splice নিষেধ। ৪. প্রতিটি reduce-এ initial value দেবে। প্রতিটি sort-এ comparator দেবে। ৫. কোডটি Node.js 20-এ চলবে, তাই Object.groupBy ব্যবহার করবে না, বা করলে বিকল্প দেবে। ৬. শেষে console.log টেস্ট দাও: খালি array, সব অর্ডার বাতিল, দুটি পণ্যের বিক্রি সমান, আর দাম 9, 10, 100 — প্রত্যেকটির প্রত্যাশিত ফলসহ।

AI-এর লেখা array কোড হাতে পেলে — যাচাইয়ের চেকলিস্ট

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিয়ে নিজে কিছুক্ষণ ভাবুন — তারপর “→ উত্তর” চাপুন।

প্র ০১map, filter আর reduce কোনো JavaScript আবিষ্কার নয় — এগুলো এসেছে কোথা থেকে, আর “reduce” কেন অন্য ভাষায় “fold” নামে পরিচিত?

এই ধারণাগুলোর শিকড় প্রোগ্রামিংয়ের একেবারে শুরুর যুগে। ১৯৫৮ সালে MIT-তে John McCarthy Lisp ভাষার কাজ শুরু করেন, আর ১৯৬০ সালে তার ভিত্তি নিয়ে একটি বিখ্যাত গবেষণাপত্র প্রকাশ করেন। Lisp-এর কেন্দ্রে ছিল তালিকা (list) আর function — এবং শুরু থেকেই এমন function ছিল যা একটি তালিকার প্রতিটি অংশে আরেকটি function প্রয়োগ করে (প্রথমদিকে maplist, পরে mapcar নামে)। অর্থাৎ “একটি function নাও, তালিকার প্রতিটিতে চালাও” — আজকের map — ৬০ বছরেরও বেশি পুরোনো চিন্তা।

“অনেক থেকে এক” ধারণাটির আরেকটি বিখ্যাত পূর্বসূরি হলো APL. Kenneth Iverson-এর ১৯৬২ সালের বইয়ে বর্ণিত এই ভাষায় +/ লিখলে একটি সংখ্যার তালিকার সব উপাদানের মাঝে + বসে যায় — যোগফল। ×/ দিলে গুণফল। এই অপারেটরের নামই ছিল reduction. পরে Haskell, ML-এর মতো functional ভাষায় একই কাজের নাম হয় fold — কল্পনা করুন একটি লম্বা কাগজ বারবার ভাঁজ করে একটি ছোট টুকরো বানানো। বাঁ দিক থেকে ভাঁজ করলে foldl (JavaScript-এর reduce), ডান দিক থেকে foldr (reduceRight).

Functional programming-এর তাত্ত্বিকরা দেখিয়েছেন, fold এত মৌলিক যে তালিকার ওপর অনেক কাজ — map, filter, যোগফল, উল্টানো — fold দিয়ে প্রকাশ করা যায়। এই কারণেই অংশ ৭-এ বলা হয়েছে, reduce দিয়ে সব লেখা সম্ভব। কিন্তু সম্ভব আর উচিত আলাদা: map আর filter-এর নামই পাঠককে বলে দেয় “আকার একই থাকবে” বা “শুধু বাছাই হবে” — fold সেই নিশ্চয়তা দেয় না।

JavaScript-এর ভেতরে Scheme-এর প্রভাব পর্ব ১৩-এর ভাবনার প্রশ্নে দেখেছেন — function প্রথম দিন থেকেই মান ছিল। কিন্তু মজার ব্যাপার, প্রথম ১০ বছর ভাষায় map বা reduce ছিল না; মানুষ লুপই লিখত। পদ্ধতিগুলো এসেছে অনেক পরে (প্রশ্ন ২)। আর এগুলোর নকশায় Lisp-এর ছাপ স্পষ্ট: callback নেয়, নতুন তালিকা দেয়, মূলটি ছোঁয় না — immutable ডেটার সেই পুরোনো আদর্শ।

মূল উপলব্ধি: map, filter, reduce হলো তালিকা নিয়ে চিন্তা করার ৬০ বছরেরও বেশি পুরোনো, পরীক্ষিত শব্দভান্ডার। এগুলো শিখলে আপনি শুধু JavaScript নয় — Python, Java, Rust, SQL, এমনকি Excel-এর অনেক ফাংশনের পেছনের চিন্তাও একসাথে শিখে ফেলেন।

প্র ০২JavaScript-এ ১৯৯৫ সাল থেকে function মান হলেও map, filter, reduce মানদণ্ডে এল ২০০৯ সালে। এতদিন লাগল কেন, আর মাঝের সময়ে মানুষ কী করত?

১৯৯৯ সালের ECMAScript 3 মানদণ্ডে array-এর method ছিল হাতে গোনা: push, pop, slice, splice, sort, join, concat, reverse ইত্যাদি। কোনো callback-ভিত্তিক তালিকা-প্রক্রিয়াকরণ নেই। তারপর প্রায় দশ বছর মানদণ্ড কার্যত থেমে ছিল — ECMAScript 4 নিয়ে বিশাল বিতর্কে শেষ পর্যন্ত সেটি পরিত্যক্ত হয় (২০০৮)।

এই শূন্যতায় এগিয়ে আসে Mozilla. Firefox 1.5-এর (২০০৫) সাথে আসা “JavaScript 1.6”-এ তারা নিজেদের উদ্যোগে যোগ করে forEach, map, filter, some, every, indexOf আর lastIndexOf. Firefox 3-এর (২০০৮) “JavaScript 1.8”-এ আসে reduce আর reduceRight. এগুলোকে তখন বলা হতো “array extras”. সমস্যা: অন্য ব্রাউজারে, বিশেষত তখনকার বাজার দখল করে থাকা Internet Explorer-এ, এগুলো ছিল না।

তাই ডেভেলপাররা লাইব্রেরির ওপর নির্ভর করতেন। Prototype.js (২০০৫) সরাসরি Array.prototype-এ নিজের method বসিয়ে দিত — যা পরে মানদণ্ডের সাথে নামের সংঘাত তৈরি করে। jQuery দিত $.each আর $.map, আর ২০০৯ সালে আসে Underscore.js, যার _.map, _.filter, _.reduce, _.groupBy পরে Lodash-এ আরও বিস্তৃত হয়। মজার বিষয়, _.groupBy-এর মতো সুবিধা ভাষায় সরাসরি আসতে লেগেছে আরও প্রায় ১৫ বছর — ES2024-এর Object.groupBy.

অবশেষে ডিসেম্বর ২০০৯-এ ECMAScript 5 Mozilla-র এই array extras প্রায় হুবহু মানদণ্ডে নিয়ে নেয়। Internet Explorer 9 (২০১১) থেকে সব বড় ব্রাউজারে এগুলো চলতে শুরু করে। এই অভিজ্ঞতা থেকে TC39 একটি শিক্ষা নেয়, যা আজও মানা হয়: নতুন feature আগে ব্রাউজার বা লাইব্রেরিতে বাস্তবে পরীক্ষিত হোক, তারপর মানদণ্ডে — আজকের stage ০–৪ প্রক্রিয়া এই চিন্তারই ফল। নামের সংঘাতও একটি স্থায়ী শিক্ষা: পুরোনো সাইট ভাঙার ভয়ে নতুন method-এর নাম কয়েকবার বদলাতে হয়েছে (যেমন flatten-এর বদলে flat).

মূল উপলব্ধি: আজ যা “মৌলিক” মনে হয়, তা একসময় ব্রাউজার-নির্মাতা আর লাইব্রেরি-লেখকদের পরীক্ষা ছিল। ভাষা এগোয় বাস্তব ব্যবহার দেখে — আর built-in প্রোটোটাইপে নিজের method বসানো কেন বিপজ্জনক, সেটিও এই ইতিহাসের শিক্ষা।

প্র ০৩Google-এর MapReduce (২০০৪) আর JavaScript-এর map/reduce — একই নাম কেন? একটি laptop-এর array আর হাজার কম্পিউটারের ডেটাসেন্টারে কী মিল?

২০০৪ সালে Google-এর দুই প্রকৌশলী Jeffrey Dean আর Sanjay Ghemawat OSDI সম্মেলনে একটি গবেষণাপত্র প্রকাশ করেন: “MapReduce: Simplified Data Processing on Large Clusters”. সমস্যাটি ছিল বিশাল: ওয়েবের কোটি কোটি পাতা থেকে index বানানো, লগ বিশ্লেষণ — ডেটা এত বড় যে একটি কম্পিউটারে ধরে না, আর হাজার কম্পিউটারে ভাগ করে চালানোর কোড লেখা (কোনো মেশিন মাঝপথে বন্ধ হলে কী হবে, ডেটা কীভাবে পাঠানো হবে) ভয়ংকর কঠিন।

তাঁদের সমাধান ছিল প্রোগ্রামারকে শুধু দুটি function লিখতে বলা — গবেষণাপত্রেই তাঁরা উল্লেখ করেছেন যে নামগুলো Lisp ও অন্যান্য functional ভাষার map আর reduce থেকে অনুপ্রাণিত। map প্রতিটি রেকর্ড আলাদাভাবে নিয়ে (key, value) জোড়া বের করে; reduce একই key-র সব value নিয়ে একটি ফলে নামিয়ে আনে। বাকি সব — ভাগ করা, পাঠানো, ব্যর্থ মেশিনের কাজ আবার চালানো — framework নিজে সামলায়। ক্লাসিক উদাহরণ word count: map প্রতিটি শব্দের জন্য (শব্দ, ১) দেয়, reduce প্রতিটি শব্দের সব ১ যোগ করে। অংশ ৪-এর বাংলা বাক্যের শব্দ গোনার কোডটি ঠিক এই কাজই এক মেশিনে করেছে।

এটা কেন সম্ভব? কারণ map-এর callback যদি pure হয় — শুধু নিজের input দেখে, বাইরের কিছু বদলায় না — তবে কোন রেকর্ড কোন মেশিনে, কোন ক্রমে চলল, তাতে কিছু যায় আসে না। সব একসাথে চালানো যায়। আর reduce-এর অপারেশন যদি associative হয় (যোগের মতো: (a+b)+c = a+(b+c)), তবে আংশিক ফলগুলো আলাদা মেশিনে হিসাব করে পরে জোড়া দেওয়া যায়। এই দুটি গাণিতিক বৈশিষ্ট্যই হাজার মেশিনে কাজ ভাগ করার চাবি।

MapReduce-এর open-source রূপ Hadoop (২০০৬ নাগাদ) পুরো একটি “big data” শিল্প তৈরি করে; পরে Apache Spark-এর মতো দ্রুততর ব্যবস্থা একই চিন্তা এগিয়ে নেয়। আজ আপনি যখন একটি callback-এ বাইরের variable বদলান, তখন আপনি ঠিক সেই বৈশিষ্ট্যটি ভাঙছেন যা এই সব ব্যবস্থাকে সম্ভব করেছে।

মূল উপলব্ধি: map আর reduce শুধু লেখার সুবিধা নয় — এগুলো এমন একটি চুক্তি যা কাজকে ভাগ করা যায় বানায়। Callback pure রাখার অভ্যাস একটি laptop-এ পাঠযোগ্যতা দেয়, আর ডেটাসেন্টারে দেয় হাজারগুণ গতি।

প্র ০৪ডিফল্ট sort কেন সংখ্যাকেও string হিসেবে সাজায়? আর ২০১৯ সালের আগে sort “stable” না হওয়া কী সমস্যা তৈরি করত?

JavaScript-এর array-এ যেকোনো type একসাথে থাকতে পারে: [3, "আম", null, {}, true]. এমন array সাজাতে একটি নিয়ম লাগবে যা যেকোনো দুটি মানকে তুলনা করতে পারে। ১৯৯৭ সালের প্রথম ECMAScript মানদণ্ড থেকেই নিয়ম হলো: comparator না দিলে সবকিছুকে string বানাও, তারপর string হিসেবে তুলনা করো (আর undefined সবসময় শেষে)। যেকোনো মানকে string বানানো যায়, আর string-এর তুলনা সবসময় একটি নির্দিষ্ট উত্তর দেয় — তাই এটি সরল ও “সব সময় কাজ করে” এমন একটি ডিফল্ট। দাম হলো সংখ্যার ক্ষেত্রে অবাক করা ফল।

আর কেন এখন বদলানো যায় না? কারণ ওয়েবের মূল নীতি: “don't break the web”. লাখো পুরোনো সাইট এই আচরণের ওপর নির্ভর করতে পারে। ডিফল্ট বদলালে সেগুলো চুপচাপ ভিন্ন ফল দেখাবে। তাই ফাঁদটি চিরস্থায়ী — সমাধান হলো সবসময় comparator দেওয়া, যা ESLint-এর মতো tool (পর্ব ৪৬) মনে করিয়ে দিতে পারে।

Stability-র গল্পটি আরও আকর্ষণীয়। মানদণ্ড বহু বছর শুধু বলত “sort করো” — stable হতে হবে, এমন কোনো শর্ত ছিল না। V8 (Chrome, Node.js) দীর্ঘদিন ছোট array-তে (১০ বা কম উপাদান) insertion sort আর বড় array-তে quicksort ব্যবহার করত — আর quicksort stable নয়। ফল: একটি টেবিল প্রথমে নাম দিয়ে, তারপর শ্রেণি দিয়ে সাজালে, ছোট টেস্ট ডেটায় নাম ক্রমে থাকত, কিন্তু ১০টির বেশি সারি হতেই একই শ্রেণির ভেতরে নাম এলোমেলো হয়ে যেত। Firefox-এর merge sort stable ছিল, তাই একই কোড ব্রাউজারভেদে ভিন্ন ফল দিত — খুঁজে পাওয়া কঠিন একটি বাগ।

২০১৮ সালে V8 তার sort পুরোপুরি নতুন করে লেখে TimSort দিয়ে (Chrome 70) — Tim Peters ২০০২ সালে Python-এর জন্য যে অ্যালগরিদম বানিয়েছিলেন, যা stable আর বাস্তবের আংশিক সাজানো ডেটায় খুব দ্রুত। সব বড় engine stable হয়ে যাওয়ার পর ES2019 মানদণ্ড stability বাধ্যতামূলক করে। আজ আপনি নিশ্চিন্তে বহু-ধাপে সাজাতে পারেন।

মূল উপলব্ধি: ভাষার ডিফল্ট তৈরি হয় সেই সময়ের বাস্তবতায়, আর পরে বদলানো প্রায় অসম্ভব। তাই ডিফল্টের ওপর ভরসা না করে নিজের উদ্দেশ্য স্পষ্ট লিখুন — comparator দিন — আর মনে রাখুন, “আমার মেশিনে কাজ করে” মানে সব engine-এ কাজ করে না।

প্র ০৫Array method-এর প্রতিটি ধাপ পুরো একটি নতুন array বানায়। ES2025-এর iterator helper কীভাবে এই কাজ “lazy” করে, আর কখন সেটি সত্যিই দরকার?

অংশ ৫-এর পাইপলাইনে filter শেষ করে পুরো একটি array বানায়, তারপর map আরেকটি — একে বলে eager (তাড়াহুড়ো) মূল্যায়ন। ছোট তালিকায় এটি কোনো সমস্যা নয়। কিন্তু ভাবুন একটি ১০ লাখ লাইনের লগ ফাইল থেকে প্রথম ৫টি error খুঁজতে হবে: lines.filter(isError).map(parse).slice(0, 5) — ১০ লাখ লাইন পরীক্ষা হবে, হয়তো হাজারটি error parse হবে, তারপর ৯৯৫টি ফেলে দেওয়া হবে। অথবা ডেটার কোনো শেষই নেই — একটি অসীম ক্রম, বা নেটওয়ার্ক থেকে আসতে থাকা ঘটনা।

এর সমাধান হলো lazy evaluation: প্রতিটি উপাদান একটি একটি করে পুরো পাইপলাইন পার হয়, আর শেষ ধাপ যখন যথেষ্ট পেয়ে যায়, উৎস থেকে আর চাওয়াই হয় না। Java-র Stream (C/C++/Python/Java নোটে দেখেছেন) আর Python-এর generator এভাবে কাজ করে। JavaScript-এ এতদিন এর জন্য generator (পর্ব ২৭) নিজে লিখতে হতো, বা লাইব্রেরি লাগত।

ES2025 মানদণ্ডে যুক্ত হয়েছে iterator helper: Iterator.prototype-এ map, filter, take, drop, flatMap, reduce, toArray, some, every, find, forEach, আর যেকোনো iterable থেকে iterator বানাতে Iterator.from. Chrome 122 (২০২৪) থেকে এগুলো আছে, Node.js-এ 22 থেকে; Node 20-এ নেই। একটি array থেকে lazy পাইপলাইন: lines.values().filter(isError).map(parse).take(5).toArray() — এখন পঞ্চম error পাওয়া মাত্র কাজ থামে, মাঝে কোনো বড় array তৈরি হয় না।

তবে দুটি সতর্কতা। প্রথমত, iterator একবারই ব্যবহার করা যায় — শেষ হয়ে গেলে আবার ঘোরাতে নতুন করে বানাতে হয়। দ্বিতীয়ত, iterator-এ sort নেই, আর থাকা সম্ভবও নয়: সাজাতে হলে সব উপাদান আগে দেখতে হয়, যা laziness-এর বিপরীত। আর ছোট array-তে lazy পাইপলাইন সাধারণত বেশি দ্রুত নয় — প্রতিটি ধাপে প্রতিটি উপাদানের জন্য বাড়তি function কলের খরচ আছে। তাই দৈনন্দিন কাজে array method-ই প্রথম পছন্দ; বিশাল বা অসীম ডেটা, বা “প্রথম কয়েকটি পেলেই হবে” ধরনের প্রশ্নে iterator helper.

মূল উপলব্ধি: Eager আর lazy — দুটোই একই map/filter চিন্তা, শুধু কখন হিসাব হয় তাতে পার্থক্য। ডেটার আকার আর “সব লাগবে, নাকি কয়েকটি” — এই দুই প্রশ্নই ঠিক করে কোনটি বেছে নেবেন।

অনুশীলন

এগুলোর বেশিরভাগে কোড লিখতে হবে না — আগে অনুমান করুন, তারপর console-এ চালিয়ে মিলিয়ে নিন।

  1. ফল অনুমান করুন: খাতায় লিখে তারপর চালান — (ক) [1, 2, 3, 4].filter((n) => n % 2).map((n) => n * 10) (খ) [5, 25, 100, 1].sort() (গ) [].some((x) => x > 0) আর [].every((x) => x > 0) (ঘ) [3].reduce((a, b) => a + b)

    (ক) [10, 30] — n % 2 বিজোড়ে ১ (truthy), জোড়ে ০ (falsy); তাই ১ আর ৩ থাকে, তারপর ১০ গুণ। (খ) [1, 100, 25, 5] — string হিসেবে: "1" < "100" < "25" < "5". (গ) false আর true — খালি দলে “অন্তত একজন” নেই, কিন্তু “সবাই” কথাটি vacuously সত্য। (ঘ) 3 — initial value ছাড়া এক উপাদানের array-এ callback চলেই না, উপাদানটি সরাসরি ফেরত আসে।

  2. লুপ থেকে method: নিচের কোডকে একটি chain-এ লিখুন: const names = []; for (const s of students) { if (s.gpa >= 4) names.push(s.name.toUpperCase()); }. তারপর ভাবুন, ধাপ দুটির ক্রম উল্টালে (আগে map, পরে filter) কোথায় সমস্যা হবে।

    const names = students.filter((s) => s.gpa >= 4).map((s) => s.name.toUpperCase());. ক্রম উল্টালে map-এর পরে হাতে থাকে শুধু নামের string — gpa আর নেই, তাই filter শর্তই যাচাই করতে পারবে না। সাধারণ নিয়ম: যে ধাপের যে তথ্য লাগে, সেই তথ্য হারানোর আগে সেটি চালান — বাছাই সাধারণত আগে, রূপান্তর পরে। (বাংলা নামে toUpperCase কিছু বদলায় না, ইংরেজি নামে বদলায়।)

  3. বাগ খুঁজুন: একজন ডেভেলপার লিখলেন function topScores(scores) { return scores.sort().reverse().slice(0, 3); }. স্কোর [95, 100, 88, 9] দিয়ে ডাকলে কী ফেরত আসে? আর ফাংশনটি ডাকার পরে কলকারীর scores-এর কী হলো? দুটি সমস্যাই ঠিক করুন।

    sort() string হিসেবে সাজায়: [100, 88, 9, 95] ("1" < "8" < "9" < "95" — "9" ছোট বলে "95"-এর আগে)। reverse দেয় [95, 9, 88, 100], আর প্রথম তিনটি [95, 9, 88] — ১০০ বাদ পড়ল, ৯ ঢুকে গেল! দ্বিতীয় সমস্যা: sort আর reverse দুটোই in-place, তাই কলকারীর scores-ও এখন [95, 9, 88, 100]. সমাধান: return scores.toSorted((a, b) => b - a).slice(0, 3); → [100, 95, 88], মূল array অক্ষত।

  4. নিজে লিখুন (একটিমাত্র কোডের কাজ): const sales = [{ item: "চা", amount: 30 }, { item: "বিস্কুট", amount: 20 }, { item: "চা", amount: 30 }, { item: "কলা", amount: 10 }, { item: "চা", amount: 30 }]; — একটি চায়ের দোকানের দিনের বিক্রি। বের করুন: (ক) মোট বিক্রি, (খ) প্রতিটি পণ্যে মোট কত টাকা — reduce দিয়ে, (গ) কোন কোন পণ্য বিক্রি হয়েছে, প্রতিটি একবার।

    (ক) sales.reduce((sum, s) => sum + s.amount, 0) → 120. (খ) sales.reduce((acc, s) => { acc[s.item] = (acc[s.item] ?? 0) + s.amount; return acc; }, {}) → { 'চা': 90, 'বিস্কুট': 20, 'কলা': 10 }. (গ) [...new Set(sales.map((s) => s.item))] → [ 'চা', 'বিস্কুট', 'কলা' ]. লক্ষ করুন, তিনটিতেই initial value দেওয়া, আর sales একবারও বদলায়নি। (খ)-তে initial value {} না দিলে প্রথম কলে acc হতো প্রথম বিক্রির object-টি — আর ফলাফল হতো অর্থহীন।

নিজেকে যাচাই করুন

ভুল হলে কারণসহ ব্যাখ্যা পাবেন, আবার চেষ্টাও করতে পারবেন।

ক্রমঅর্ডার তালিকা থেকে “ডেলিভারি হওয়া অর্ডারের মোট আয়” বের করার পাইপলাইন সঠিক ক্রমে সাজান (▲▼ চেপে বা টেনে)।

  1. 🛒 মূল অর্ডারের array নেওয়া (বদলানো হবে না)
  2. 🔎 filter — শুধু ডেলিভারি হওয়া অর্ডার রাখা
  3. 🔁 map — প্রতিটি অর্ডার থেকে দাম × পরিমাণ বের করা
  4. 🧮 reduce — initial value ০ থেকে সব যোগ করা
  5. 🖨️ ফলাফল টাকার ফরম্যাটে দেখানো

JavaScript-এ ["1", "7", "11"].map(parseInt) কী ফেরত দেয়?

  • [ 1, 7, 11 ]
  • [ 1, NaN, 3 ]
  • TypeError
  • [ NaN, NaN, NaN ]

JavaScript-এ initial value ছাড়া খালি array-এ [].reduce((a, b) => a + b) চালালে কী হয়?

  • 0 ফেরত দেয়
  • undefined ফেরত দেয়
  • NaN ফেরত দেয়
  • TypeError ছোড়ে

JavaScript-এ সংখ্যার array [10, 9, 100]-কে ছোট থেকে বড় ক্রমে সাজানোর সঠিক উপায় কোনটি?

  • arr.sort((a, b) => a - b)
  • arr.sort()
  • arr.sort((a, b) => b - a)
  • arr.sort((a, b) => a > b)

JavaScript-এর forEach সম্পর্কে কোন কথাটি সঠিক?

  • এটি callback-এর ফল দিয়ে একটি নতুন array ফেরত দেয়
  • Callback-এর ভেতরে break লিখে মাঝপথে থামানো যায়
  • এটি সবসময় undefined ফেরত দেয়, আর মাঝপথে থামানো যায় না
  • এটি সবসময় মূল array-এর উপাদান বদলে দেয়

JavaScript-এ কোন method মূল array না বদলে একটি নতুন সাজানো array ফেরত দেয়?

  • sort()
  • toSorted()
  • reverse()
  • splice()

JavaScript-এ [1, 2, 3, 4].filter((n) => n > 2).map((n) => n * 10)-এর ফল কী?

  • [ 10, 20, 30, 40 ]
  • [ true, true ]
  • [ 10, 20 ]
  • [ 30, 40 ]

শব্দকোষ

লেখার মধ্যে ডটেড দাগ দেওয়া শব্দে মাউস রাখুন বা চাপুন — অর্থ দেখতে পাবেন।

মনে রাখুন

  1. Array method দিয়ে বলুন কী চান: filter রাখে, map বদলায় (দুটোই নতুন array), reduce এক মানে নামায়। Callback পায় (element, index, array).
  2. forEach শুধু side effect-এর জন্য — ফেরত undefined, থামে না। নতুন array বানাতে forEach + push নয়, map/filter. Built-in function সরাসরি পাঠাবেন না: map(parseInt) → [1, NaN, 3].
  3. reduce-এ সবসময় initial value দিন — খালি array-এ TypeError, object-এ ভুল ফল এড়াতে। সবকিছু reduce দিয়ে নয়; যে method-এর নাম উদ্দেশ্য বলে, সেটি নিন।
  4. Comparator ছাড়া sort string হিসেবে সাজায়। সংখ্যায় a - b, বাংলায় localeCompare(b, "bn"), একাধিক key-তে ||. ES2019 থেকে sort stable.
  5. sort মূল array বদলায় — toSorted নতুন দেয়। Group করতে Object.groupBy (Node 21+) বা reduce, unique করতে [...new Set(arr)].

পরের পর্বে: এই পর্বে বারবার { ...t, done: true } আর [...new Set(arr)] দেখেছেন — তিনটি ডটের সেই জাদু এবার পুরোটা। পর্ব ১৭-এ destructuring, spread আর rest: একটি object বা array থেকে এক লাইনে দরকারি অংশ তুলে আনা, কপি বানিয়ে নিরাপদে বদলানো, আর callback-এর parameter-এই সরাসরি ({ name, gpa }) লেখা।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

এই পর্বটি শেয়ার করুন:
এই পর্বে ভুল পেয়েছেন? জানান