Learn JavaScript

lesson ১ / ৯ · array আর array method: map, filter, reduce-এর pipeline

Module ৭ · array আর array method: map, filter, reduce-এর pipeline

Array-র ছবি: ক্রম মেনে সাজানো list আর map, filter, reduce-এর pipeline

Freeপড়া

এই 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
Run in Compiler

প্রথম লাইন পুরো 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
Run in Compiler

শেষ লাইনটা চেয়েছে 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
Run in Compiler

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
Run in Compiler

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 ]
Run in Compiler

শেষ লাইনটাই প্রমাণ: marks যেমন ছিল ঠিক তেমনই আছে। প্রতিটা ধাপে যে function দিচ্ছ, সেটা Module 5-এর arrow function। (m) => m + 5 একটা element নেয় আর তার নতুন value ফেরত দেয়।

এবার একই pipeline একটা একটা element করে দেখো। প্রতিটা ধাপে একটা element একটা stage পার হয়। 45 element-টা map-এ 50 হয়, তারপর filter-এর test-এ আটকে যায়, তাই reduce পর্যন্ত আর পৌঁছায় না।

একই ধাপগুলো table-এ, প্রতি ধাপে একটা row।

ধাপStageElementOutKeptAccকী হলো
107277map 5 grace mark যোগ করে: 72 হয়ে যায় 77।
2177false77 80-এর কম, তাই filter এটা বাদ দেয়।
304550map 5 grace mark যোগ করে: 45 হয়ে যায় 50।
4150false50 80-এর কম, তাই filter এটা বাদ দেয়।
508893map 5 grace mark যোগ করে: 88 হয়ে যায় 93।
6193true93 80 বা তার বেশি, তাই filter এটা রাখে।
72931reduce 93-কে গোনে; count এখন 1।
809196map 5 grace mark যোগ করে: 91 হয়ে যায় 96।
9196true96 80 বা তার বেশি, তাই filter এটা রাখে।
102962reduce 96-কে গোনে; count এখন 2।
1106065map 5 grace mark যোগ করে: 60 হয়ে যায় 65।
12165false65 80-এর কম, তাই filter এটা বাদ দেয়।

reduce-এর চলতি value চোখের সামনে দেখলে ওকে বিশ্বাস করা সহজ হয়। নিচে একই mark-গুলোর যোগফল, প্রতি element-এ এক row।

একই ধাপগুলো table-এ, প্রতি ধাপে একটা row।

indexmsum beforesum after
072072
14572117
288117205
391205296
460296356

শেষ 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
Example 1: input থেকে class-এর report

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।

Run in Compiler
Example 2: প্রতিটা ধাপ কয়টা mark রাখে

এই 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
Run in Compiler
Example 2-এর প্রতিটা ধাপের পরে কয়টা element থাকে elements input 8 map 8 filter 4
Example 2-এর প্রতিটা ধাপের পরে কয়টা element থাকে

এটা কোথায় কাজে লাগে

  • React: ওদের documentation-এর "Rendering Lists" page-এ screen-এ list আঁকা হয় একটা array-র ওপর map call করে, আর কিছু 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 যথেষ্ট না।

মাথা খাটাও

ফাঁকওয়ালা array

Zara কোণার case আগে পরখ করতে ভালোবাসে, তাই ও শেষের অনেক পরের একটা index-এ লেখে।

const a = [10, 20, 30];
a[6] = 70;
console.log(a.length);

এটা কী print করবে: 4 না 7? length আসলে কী মাপে, সেটা ভাবো।

Exercise 1সহজ

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 হয়।

Exercise 2মাঝারি

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
Exercise 3কঠিন

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 শেষে যোগ করে, আর const array-তেও চলে, কারণ const শুধু নামটা বাঁধে।
  • map, filter আর reduce নতুন কিছু ফেরত দেয়, array যেমন ছিল তেমন রাখে, তাই এরা জুড়ে pipeline বানায়।
  • আরও গভীরে যেতে চাইলে: ভেতরের কলকবজা, object হিসেবে array, V8-এর elements kind, আর hole, shift ও spread-এর খরচ (Pro)।

পরের lesson: array-র প্রতিটা method, একটা একটা করে, কী ফেরত দেয় আর array বদলায় কিনা।

lesson ১ শেষ

শেষ হলে চিহ্ন দিন, অগ্রগতি আপনার সাথে থাকবে।

পরেরটা: Array-র প্রতিটা method, একটা একটা করে, কী ফেরত দেয় সহ