Module ৭ · array আর array method: map, filter, reduce-এর pipeline
Array-র ছবি: ক্রম মেনে সাজানো list আর map, filter, reduce-এর pipeline
এই lesson-এ যা শিখবে
- অনেকগুলো value একটা array-তে রাখো, আর index দিয়ে যেকোনোটা পড়ো, শেষেরটা
at(-1)দিয়ে। pushদিয়ে array বড় করো, আরlengthদিয়ে তার মাপ জানো।map,filterআরreduce-কে কয়েকটা ধাপ হিসেবে কল্পনা করো, যার ভেতর দিয়ে element-গুলো একটা একটা করে যায়।
Maria তিরিশ জনের একটা quiz class পড়ায়। প্রতিটা quiz-এর পরে ওর হাতে তিরিশটা mark। এখন পর্যন্ত যা শিখেছ তা দিয়ে করতে গেলে লাগবে তিরিশটা variable: mark1, mark2, এভাবে mark30 পর্যন্ত। গড় বের করতে এক লম্বা লাইনে তিরিশটা নাম লিখতে হবে। একজন নতুন student এলে program-এর চার জায়গায় বদলাতে হবে।
সমাধানটা তুমি আসলে আগেই দেখেছ, চুপচাপ। যে starter তোমার input পড়ে, সে input-এর প্রতিটা শব্দ রাখে tokens-এ, আর tokens একটা array। এই module-এ array ঠিকঠাক শিখবে: array কী, ওর প্রতিটা method, আর map, filter, reduce-এর সেই pipeline, যেটা list-এর বেশিরভাগ কাজকে এক লাইনের পড়ার মতো code বানিয়ে দেয়।
Array মানে ক্রম মেনে সাজানো একটা list
Array হলো এমন একটা value যার ভেতরে অন্য value-দের একটা list থাকে, ক্রম মেনে। ভেতরের প্রতিটা value-কে বলে element। Array লেখো square bracket দিয়ে, element-গুলোর মাঝে comma। একে বলে array literal, কারণ ভেতরে কী আছে সেটা তুমি code-এ হুবহু লিখে দিচ্ছ।
const marks = [72, 45, 88, 91, 60];
console.log(marks);
console.log(marks.length);
[ 72, 45, 88, 91, 60 ]
5
প্রথম লাইন পুরো array-টা print করে। দ্বিতীয়টা print করে length, মানে কয়টা element আছে। ক্রম গুরুত্বপূর্ণ: [72, 45] আর [45, 72] দুটো আলাদা array।
Array-তে যেকোনো value রাখা যায়, এমনকি মিশিয়েও: সংখ্যা, string, boolean, আরেকটা array। কাজের বেলায় অবশ্য এক array-তে এক রকম জিনিস রাখো। Mark-এর list-এ শুধু mark থাকুক, তাহলে প্রতিটা element-এর সাথে একইভাবে কাজ করা যায়।
Index শুরু হয় 0 থেকে
প্রতিটা element একটা নম্বর দেওয়া জায়গায় বসে, ওটাকে বলে index। প্রথম element থাকে index 0-তে, দ্বিতীয়টা index 1-এ, আর শেষটা index length - 1-এ। কোনো element পড়তে array-র নামের পরে square bracket-এ index লেখো।
const marks = [72, 45, 88, 91, 60];
console.log(marks[0]);
console.log(marks[2]);
console.log(marks[marks.length - 1]);
console.log(marks[5]);
72
88
60
undefined
শেষ লাইনটা চেয়েছে index 5, মানে শেষেরও এক ঘর পরে। JavaScript program থামায় না। দেয় undefined, যে value-র মানে "এখানে কিছু নেই"। এটা ভদ্র ব্যবহার, আবার বিপজ্জনকও: ভুল index চিৎকার করে না, চুপচাপ ফাঁকা হাতে ফেরত পাঠায়।
0 থেকে গোনাটা দুই-এক দিন অদ্ভুত লাগবে। Index-কে ভাবো "এই element-এর আগে কয়টা element আছে"। প্রথম element-এর আগে কেউ নেই, তাই 0।
at(-1) শেষ থেকে পড়ে
শেষ element পড়তে marks[marks.length - 1] লেখা লম্বা ব্যাপার, আর Bob সপ্তাহে একবার - 1-টা গুলিয়ে ফেলে। ES2022 থেকে array-তে আছে at। Positive সংখ্যা দিলে এটা bracket-এর মতোই কাজ করে। Negative দিলে গোনে শেষ থেকে: at(-1) শেষ element, at(-2) তার আগেরটা।
const marks = [72, 45, 88, 91, 60];
console.log(marks.at(0));
console.log(marks.at(-1));
console.log(marks.at(-2));
72
60
91
Square bracket কিন্তু শেষ থেকে গোনে না। marks[-1] হলো undefined, কারণ -1 কোনো index-ই না।
Array বড় হয়
Array-র মাপ বাঁধা না। push শেষে একটা element যোগ করে আর length এক বাড়িয়ে দেয়। Starter ঠিক এভাবেই out বানায়: তোমার উত্তরের প্রতিটা লাইন ওতে push হয়, শেষে join করে print।
const marks = [];
console.log(marks.length);
marks.push(72);
marks.push(45);
marks.push(88);
console.log(marks);
console.log(marks.length);
0
[ 72, 45, 88 ]
3
const-টা খেয়াল করো। এটা array-কে জমিয়ে দেয় না। শুধু বলে, marks নামটা সবসময় একই array-কে দেখাবে। সেই array-র ভেতরটা কিন্তু বদলাতে পারে, তাই const array-তেও push চলে। Module 8 এটা reference আর copy দিয়ে খুলে বলবে।
সরাসরি কোনো index-এ লেখাও যায়: marks[1] = 50 দ্বিতীয় element-টা বদলে দেয়। শেষের অনেক পরে লিখলে মাঝে ফাঁক থেকে যায়, আর Lesson 02 দেখাবে কেন সেটা একটা ফাঁদ।
Pipeline-এর ছবি
List-এর বেশিরভাগ কাজ তিন রকমের একটা। হয় প্রতিটা element-এর জন্য একটা নতুন value চাও, যেমন প্রতিটা mark-এ 5 grace mark যোগ। নয়তো শুধু কিছু element চাও, যেমন 80 বা তার বেশি mark। নয়তো সবগুলো থেকে একটা উত্তর চাও, যেমন মোট বা কয়টা।
JavaScript প্রতিটা রকমের জন্য আলাদা method দেয়:
mapপ্রতিটা element-এর জন্য তোমার function একবার call করে, আর উত্তরগুলো দিয়ে একটা নতুন array বানায়। নতুন array-র length একই।filterপ্রতিটা element-এর জন্য তোমার function একবার call করে, আর যেগুলোতেtrueএসেছে শুধু সেগুলো রাখে। নতুন array ছোট হতে পারে।reduceএকটা চলতি value নিয়ে array-র ভেতর দিয়ে হাঁটে, আর শেষে সেই একটা value ফেরত দেয়।
তিনটার কোনোটাই যে array-তে call করছ সেটাকে বদলায় না। প্রতিটা নতুন কিছু ফেরত দেয়, তাই এদের পরপর জোড়া যায়: এক ধাপের output পরের ধাপের input। এই জোড়া লাগানো শিকলটাই pipeline।
const marks = [72, 45, 88, 91, 60];
const graced = marks.map((m) => m + 5);
const high = graced.filter((m) => m >= 80);
const count = high.reduce((c, m) => c + 1, 0);
console.log(graced);
console.log(high);
console.log(count);
console.log(marks);
[ 77, 50, 93, 96, 65 ]
[ 93, 96 ]
2
[ 72, 45, 88, 91, 60 ]
শেষ লাইনটাই প্রমাণ: marks যেমন ছিল ঠিক তেমনই আছে। প্রতিটা ধাপে যে function দিচ্ছ, সেটা Module 5-এর arrow function। (m) => m + 5 একটা element নেয় আর তার নতুন value ফেরত দেয়।
এবার একই pipeline একটা একটা element করে দেখো। প্রতিটা ধাপে একটা element একটা stage পার হয়। 45 element-টা map-এ 50 হয়, তারপর filter-এর test-এ আটকে যায়, তাই reduce পর্যন্ত আর পৌঁছায় না।
reduce-এর চলতি value চোখের সামনে দেখলে ওকে বিশ্বাস করা সহজ হয়। নিচে একই mark-গুলোর যোগফল, প্রতি element-এ এক row।
শেষ row-এর "sum after"-টাই reduce ফেরত দেয়। Call-এর শেষের 0 হলো শুরুর value: প্রথম element-এর আগে চলতি যোগফল।
Array আর তিনটা ধাপ
const a = [x, y, z]; // an array literal
a[0] a.at(-1) a.length // first element, last element, size
a.push(v); // add v at the end
a.map((el) => newValue) // new array, same length
a.filter((el) => trueOrFalse) // new array, the elements that passed
a.reduce((soFar, el) => next, start) // one value
Maria-র input-এ প্রথমে student-এর সংখ্যা, তারপর তাদের mark। এই report print করে class-এর গড়, সবচেয়ে বেশি mark, আর 80 বা তার বেশি mark-গুলো। পড়ে starter-এর প্রথম দুই লাইন দিয়ে, যেটা Module 1 থেকে চেনো।
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
const marks = tokens.slice(1).map(Number);
const total = marks.reduce((sum, m) => sum + m, 0);
const best = marks.reduce((hi, m) => (m > hi ? m : hi), marks[0]);
const high = marks.filter((m) => m >= 80);
console.log("average " + total / marks.length);
console.log("best " + best);
console.log("80 or more: " + high.join(" "));
average 73.33333333333333
best 91
80 or more: 88 91 84
Code-এর তৃতীয় লাইনটা দেখো। tokens.slice(1) প্রথম token, মানে সংখ্যাটা, বাদ দেয়, আর map(Number) বাকি প্রতিটা string-কে সংখ্যা বানায়। ওই এক লাইনটাই নিজে একটা ছোট pipeline।
এই program প্রতিটা ধাপের জন্য এক লাইন print করে: ধাপের নাম আর তার পরের array-র length। নিচের chart ওই সংখ্যাগুলো আঁকে।
const marks = [72, 45, 88, 91, 60, 84, 39, 77];
const graced = marks.map((m) => m + 5);
const high = graced.filter((m) => m >= 80);
console.log("input " + marks.length);
console.log("map " + graced.length);
console.log("filter " + high.length);
input 8
map 8
filter 4
এটা কোথায় কাজে লাগে
- React: ওদের documentation-এর "Rendering Lists" page-এ screen-এ list আঁকা হয় একটা array-র ওপর
mapcall করে, আর কিছু item দেখাতেfilterদিয়ে। - Node.js:
fs.readdirSyncএকটা folder-এর file-গুলোর নাম string-এর array হিসেবে ফেরত দেয়। - V8: Chrome আর Node.js-এর ভেতরের engine পূর্ণ সংখ্যার ছোট array-কে একটা ঘন, জায়গা বাঁচানো রূপে রাখে; Lesson 05 ভেতরে উঁকি দেবে।
যে ভুলগুলো সবাই করে
১. শেষের এক ঘর পরে পড়া
const marks = [72, 45, 88];
for (let i = 0; i <= marks.length; i++) {
console.log(marks[i]);
}
72
45
88
undefined
যেখানে < লাগে, Bob সেখানে লিখেছে <=। তিনটা element-এর জন্য loop চলে চারবার, আর চতুর্থবার পড়ে index 3, যেটা নেই। JavaScript undefined print করে এগিয়ে যায়, তাই bug-টা অনেক দূর চলে যেতে পারে। লেখো i < marks.length, অথবা আরও ভালো, for...of loop, যেখানে ভুল করার মতো index-ই নেই।
২. যেটা এখনো array না, তার ওপর push call করা
let marks;
marks.push(72);
console.log(marks);
TypeError: Cannot read properties of undefined (reading 'push')
Node এখানে থামে: TypeError: Cannot read properties of undefined (reading 'push')। Variable declare করা হয়েছে কিন্তু কোনো array দেওয়া হয়নি, তাই ওতে আছে undefined, আর undefined-এর কোনো push নেই। List শুরু করো একটা খালি array দিয়ে: const marks = [];।
৩. দুটো element-এর মাঝে comma নেই
const marks = [72 45 88];
console.log(marks.length);
Node file-টা চালাতেই রাজি হয় না, বলে SyntaxError: Unexpected number। 72-এর পরে parser একটা comma বা বন্ধ bracket আশা করে। দুটো element আলাদা করতে শুধু space যথেষ্ট না।
School-এর library চালায় Maria। কোনো বই ফেরত এলে desk-এ লিখে রাখা হয় বইটা কত দিন দেরিতে এসেছে। 0 বা negative সংখ্যা মানে বই সময়মতো বা আগেই এসেছে। দেরির প্রতিটা দিনের জন্য 5 টাকা জরিমানা। Maria চায় দেরিতে আসা প্রতিটা বইয়ের জরিমানা, ফেরত আসার ক্রমে, আর সব জরিমানার মোট।
Input. প্রথম লাইনে n, কয়টা বই ফেরত এসেছে। দ্বিতীয় লাইনে n টা পূর্ণ সংখ্যা: প্রতিটা বই কত দিন দেরিতে এসেছে, ফেরত আসার ক্রমে।
Output. দুই লাইন। প্রথম লাইনে দেরিতে আসা প্রতিটা বইয়ের জরিমানা, তাদের ক্রমে, একটা করে space দিয়ে আলাদা; কোনো বই দেরিতে না এলে শুধু none শব্দটা। দ্বিতীয় লাইনে জরিমানার মোট।
Constraints. 1 <= n <= 100000; -30 <= each number of days <= 60.
Sample.
6
3 0 -2 10 1 0
15 50 5
70
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Run in Compiler
late-returns নামে grade হয়।
David bus-এর ticket বিক্রি করে। যাওয়া-আসার ticket-এর দাম এক দিকের ভাড়ার দ্বিগুণ। ও চায় এক লাইনে প্রতিটা যাওয়া-আসার ভাড়া, তারপর কয়টা যাওয়া-আসার ভাড়া 100 টাকার বেশি, তারপর সবগুলোর মোট।
Input. n, তারপর এক দিকের n টা পূর্ণ সংখ্যার ভাড়া।
Output. তিন লাইন: যাওয়া-আসার ভাড়াগুলো ক্রমে, space দিয়ে আলাদা; 100-এর বেশি এমন ভাড়ার সংখ্যা; আর সব যাওয়া-আসার ভাড়ার মোট।
Constraints. 1 <= n <= 1000. 1 <= each fare <= 500.
Sample. নিচের input।
5
40 75 50 120 30
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Sample-এর output.
80 150 100 240 60
2
630
Run in Compiler
Kenji প্রতিদিন দুপুরের তাপমাত্রা লিখে রাখে। ও চায় পাশাপাশি দুই দিনের মধ্যে সবচেয়ে বড় বদল, ওঠা বা নামা যেটাই হোক, আর কোন দিন সেটা হয়েছে: দুই দিনের পরেরটা, 1 থেকে গুনে। দুটো বদল সমান হলে আগের দিনটা বলো।
Input. n, তারপর n টা পূর্ণ সংখ্যার তাপমাত্রা।
Output. এক লাইন: সবচেয়ে বড় বদল, একটা space, আর দিনটা।
Constraints. 2 <= n <= 1000. -20 <= each temperature <= 50.
Sample. নিচের input।
6
21 25 19 19 30 27
const input = require("fs").readFileSync(0, "utf8");
const tokens = input.split(/\s+/).filter(Boolean);
let at = 0;
const next = () => tokens[at++];
const nextInt = () => Number(next());
const out = [];
// your code: read with next() and nextInt(), push every line of output to out
console.log(out.join("\n"));
Sample-এর output.
11 5
Run in Compiler
সচরাচর যে প্রশ্নগুলো আসে
Index 1 থেকে না, 0 থেকে কেন শুরু?
JavaScript যেসব language থেকে বেড়ে উঠেছে, যেমন C, ওদের বেশিরভাগই 0 থেকে গোনে, কারণ index বলে element-টা শুরু থেকে কত দূরে। প্রথম element শুরু থেকে শূন্য ধাপ দূরে। এক সপ্তাহ practice করলেই অভ্যাস হয়ে যায়।
এক array-তে সংখ্যা আর string একসাথে রাখা যায়?
হ্যাঁ, JavaScript দেয়। কিন্তু মেশানো list নিয়ে কাজ করা কঠিন, কারণ প্রতিটা ধাপে দেখতে হয় কোন element কী। এক array-তে এক রকম value রাখো, আর একটা record-এ কয়েক রকম জিনিস লাগলে object ব্যবহার করো (Module 8)।
at(-1)কি সব জায়গায় চলে?এটা ES2022-এ language-এ এসেছে, Node 22 আর এখনকার সব browser-এ চলে। খুব পুরনো browser-এ নেই, সেখানে
a[a.length - 1]একই কাজ করে।map, filter আর reduce পেয়ে গেলাম, loop কি আর লাগবে?
লাগবে। মাঝপথে থামতে হলে, বা এক element থেকে পরেরটায় কোনো অবস্থা বয়ে নিতে হলে, loop-ই সবচেয়ে পরিষ্কার। কখন কোনটা, তার নিয়ম Lesson 04-এ।
মূল কথা
- Array হলো element-দের ক্রম মেনে সাজানো list, লেখা হয়
[a, b, c], আরlengthতার মাপ। - Index শুরু 0 থেকে; শেষের পরে পড়লে আসে
undefined, error না। at(-1)শেষ element পড়ে; square bracket কখনো শেষ থেকে গোনে না।pushশেষে যোগ করে, আরconstarray-তেও চলে, কারণconstশুধু নামটা বাঁধে।map,filterআরreduceনতুন কিছু ফেরত দেয়, array যেমন ছিল তেমন রাখে, তাই এরা জুড়ে pipeline বানায়।- আরও গভীরে যেতে চাইলে: ভেতরের কলকবজা, object হিসেবে array, V8-এর elements kind, আর hole, shift ও spread-এর খরচ (Pro)।
পরের lesson: array-র প্রতিটা method, একটা একটা করে, কী ফেরত দেয় আর array বদলায় কিনা।
lesson ১ শেষ
শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।
পরেরটা: Array-র প্রতিটা method, একটা একটা করে, কী ফেরত দেয় সহ